Skip to content

G# + cs2gs: generalized variadic carriers — ...X[T] ≡ C# params X<T> (ADR-0173, #3627) - #3630

Merged
DavidObando merged 2 commits into
mainfrom
feature/variadic-carriers
Aug 29, 2026
Merged

G# + cs2gs: generalized variadic carriers — ...X[T] ≡ C# params X<T> (ADR-0173, #3627)#3630
DavidObando merged 2 commits into
mainfrom
feature/variadic-carriers

Conversation

@DavidObando

Copy link
Copy Markdown
Owner

Fixes #3627 — the params ReadOnlySpan<T> cs2gs gap, the last Cs2Gs.Tests translate wall in the #3501 selfmig nightly. Implements the agreed scope: ...X[T] becomes semantically equivalent to C#13 params X<T>, with no magic span types or new sugar (design for those stays in #3627's discussion).

Semantics (ADR-0173)

The type written after ... is interpreted like C#'s params type:

  • Carrier shapes become the parameter's exact CLR type: ...[]T (explicit array), ...List[T], the five IEnumerable-family interfaces (IEnumerable/ICollection/IList/IReadOnlyCollection/IReadOnlyList), ...Span[T], ...ReadOnlySpan[T].
  • Anything else keeps the ADR-0101 element meaning: ...int32params int[]; ...HashSet[int32] is still a params array of HashSets (HashSet is not a supported carrier).

Call sites mirror C#: expanded trailing args are element-coerced (#1493 rules) and packed — array as-is, interface upcast, new List[T](T[]), span T[] ctor (heap-allocating v1); a single trailing arg already convertible to the carrier passes through unchanged. Parser needed zero changes.

Metadata: array family keeps [ParamArrayAttribute]; every other carrier stamps C#13's [ParamCollectionAttribute], so csc consumers see a genuine params X<T> member (verified by reflection + runtime invoke witness).

New GS0544 when a List/span carrier's element is a same-compilation erased type with no closed CLR construction shape (the array carrier still covers that case).

cs2gs

  • params X<T> declarations translate to ...X[T] for supported carriers — spans included, retiring the gap.
  • Expanded calls on source-declared callees stay in natural form (gsc packs); referenced/BCL span callees keep the pre-existing lowerings (import-side [ParamCollection] expanded calls are a recorded follow-up; pass-through works).
  • Array params with carrier-shaped elements print explicit spellings (params byte[][]...[][]uint8, params List<int>[]...[]List[int32]) so they don't reinterpret.
  • The deliberately untranslatable params HashSet<int> fixture in Cs2Gs.Tests is now compiled in-memory by its test, so the project's own source is fully translatable.

Breaking-change note

Carrier-shaped ...X spellings are reinterpreted (previously: params array of X elements). No in-tree G# used such spellings; the escape hatch for the old meaning is ...[]X. Bare ...T for non-collection T is unchanged.

Coverage

One shared resolver (VariadicCarriers) feeds all eight ADR-0101/0102 declaration sites: free functions, methods, constructors, primary constructors, interface methods, named delegates, lambdas, and function-type clauses.

Verification

  • New Issue3627VariadicCarrierTests (13 binder/eval tests) + Issue3627VariadicCarrierEmitTests (attribute reflection, ILVerify, E2E run) — all green.
  • Full Core.Tests locally: 8227/8227 passed.
  • Cs2Gs.Tests: full run surfaced two pinned-old-behavior tests (Issue1727 params byte[][], Issue3466 params-collection demotion) — first exposed a real translator bug (array-of-array spelling), both fixed and green; Issue1901/Issue3460 suites green.
  • New conformance sample samples/VariadicCarriers.gs + golden; PE baseline regenerated (+1 entry).
  • Local selfmig proof over the cs2gs subtree: Cs2Gs.Tests translate stage now PASSES (both prior fingerprints — the ReadOnlySpan gap and the fixture — gone). Remaining local compile-stage failures are the known Debug-SDK staleness artifact of skipping the gate script's prerequisite builds, identical fingerprints before/after this change; the nightly gate is the authoritative check.

Docs

ADR-0173 (new), GS0544 in diagnostics docs + website mirror, spec variadic section, coverage-matrix inventory row (regenerated via cs2gs coverage --write).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs

…T>` (ADR-0173, #3627)

The type written after `...` is now interpreted like C#13 params
collections: when it is a supported collection shape it is the CARRIER
(the exact CLR parameter type), else it stays the ADR-0101 element type
with the implicit slice carrier. Supported carriers: `...[]T` (explicit
array), `List[T]`, the five IEnumerable-family interfaces, `Span[T]`,
`ReadOnlySpan[T]`.

gsc:
- VariadicCarriers resolver shared by all eight ADR-0101/0102
  declaration sites (functions, methods, ctors, primary ctors,
  interfaces, delegates, lambdas, function-type clauses).
- Call sites mirror C#: element coercion (#1493) then pack — array
  as-is, interface upcast, `new List[T](T[])`, span `T[]` ctor; a single
  trailing argument already convertible to the carrier passes through.
- Metadata: array family keeps [ParamArrayAttribute]; other carriers
  stamp C#13 [ParamCollectionAttribute], so csc sees `params X<T>`.
- New GS0544 when a List/span carrier's element is a same-compilation
  erased type with no closed CLR construction shape.

cs2gs:
- `params X<T>` declarations translate to `...X[T]` for supported
  carriers — retiring the `params ReadOnlySpan<T>` gap (#3627), the
  last Cs2Gs.Tests translate wall in the selfmig nightly.
- Expanded calls on source-declared callees stay in natural form (gsc
  packs); referenced/BCL callees keep the existing lowerings.
- Array params with carrier-shaped elements keep explicit spellings
  (`params byte[][]` → `...[][]uint8`) to avoid reinterpretation.
- The deliberately untranslatable `params HashSet<int>` test fixture is
  now compiled in-memory so Cs2Gs.Tests' own source stays translatable.

Breaking note (ADR-0173): carrier-shaped `...X` spellings are
reinterpreted; no in-tree usage existed, escape hatch is `...[]X`.

Fixes #3627

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs
@DavidObando
DavidObando enabled auto-merge (squash) August 29, 2026 07:32
…load context; ADR-0173 allowlist rationale

The List/span pack path resolved the element CLR type via
GetEffectiveClrType (runtime typeofs) and passed it into the carrier's
GetConstructor — under the SDK compile path the carrier comes from a
MetadataLoadContext, so mixing contexts threw ArgumentException ("Type
must be a type provided by the MetadataLoadContext") and failed the
cs2gs-corpus CI gate on G09-Functions-Console. The element is now taken
from the carrier's own generic arguments, which are always in the
carrier's context. In-process tests (runtime reflection end to end)
could not catch this; the corpus diagnostic-run repro is green now.

Also records in ADR-0173 WHY the carrier set is a closed allowlist
versus C#13's open collection-expression rule (ADR-0101 element-spelling
collision + no collection-expression lowering), and lists widening it as
explicit future work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs
@DavidObando
DavidObando merged commit b83c025 into main Aug 29, 2026
36 checks passed
@DavidObando
DavidObando deleted the feature/variadic-carriers branch August 29, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cs2gs: params ReadOnlySpan<T> (C#13 params collections) has no lowering — last translate gap for Cs2Gs.Tests selfmig

1 participant