From 92b8a1fa3dde5bf01ce75771307796badbac610e Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 28 Aug 2026 15:08:01 -0700 Subject: [PATCH 1/5] =?UTF-8?q?G#:=20named=20tuple=20elements=20=E2=80=94?= =?UTF-8?q?=20Phase=20A=20front=20end=20(ADR-0172,=20#3501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tuple types may name elements name-first — `(line int32, column int32)` — matching the parameter form, and tuple literals may label elements — `(line: 1, column: 2)`. A declared name resolves member access to its position (`pos.line` ≡ `pos.Item1`; `ItemN` and `.N` stay valid). Names are metadata over the positional shape, the C#/Roslyn model: the interned TupleTypeSymbol is keyed on (element types, names), a named and an unnamed same-shape tuple are distinct symbols sharing a CLR backing and related by an identity conversion (`WithoutNames()` gives the canonical shape, recursively), a position-wise name disagreement warns GS0541, equality (ADR-0171) ignores names, generic substitution preserves them, and common-type joins keep agreeing names. Declaration checks: GS0540 duplicate name, GS0542 reserved (`ItemN` off-position, `Rest`), GS0543 name on a parenthesized single element (grouping per #3315, no 1-tuples). Parser ambiguity: an identifier is a name exactly when followed by a type-clause start; the `[` case separates `name []T` / `name [3]T` / `name [,]T` from generic `List[int32]`, and the `unmanaged` function-pointer head keeps its ADR-0095 meaning. This reverses ADR-0115 §B.4/T1's positional-only premise (amendment note added). Phases B (TupleElementNamesAttribute emit/import), C (cs2gs name preservation + re-baseline), and D (LS polish) follow. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs --- .../0115-csharp-to-gsharp-migration-tool.md | 2 +- docs/adr/0172-named-tuple-elements.md | 99 +++++++ docs/coverage-matrix.md | 1 + docs/diagnostics.md | 18 ++ samples/NamedTupleElements.golden | 8 + samples/NamedTupleElements.gs | 34 +++ src/Core/CodeAnalysis/Binding/Binder.cs | 19 +- src/Core/CodeAnalysis/Binding/Conversion.cs | 14 + .../Binding/ConversionClassifier.cs | 21 ++ .../ExpressionBinder.Access.MemberLookup.cs | 8 + .../Binding/ExpressionBinder.Literals.cs | 39 ++- .../CodeAnalysis/Binding/ExpressionBinder.cs | 7 + src/Core/CodeAnalysis/Binding/MemberLookup.cs | 18 +- .../Binding/TupleElementNameValidation.cs | 65 +++++ .../DiagnosticBag.Reports.Expressions.cs | 37 +++ .../CodeAnalysis/DiagnosticDescriptors.cs | 6 + .../Symbols/Display/SymbolDisplay.cs | 7 +- .../CodeAnalysis/Symbols/TupleTypeSymbol.cs | 128 ++++++++- src/Core/CodeAnalysis/Symbols/TypeSymbol.cs | 2 +- .../Syntax/NamedTupleElementSyntax.cs | 43 +++ .../Syntax/Parser.Expressions.Literals.cs | 84 +++++- .../CodeAnalysis/Syntax/Parser.TypeClauses.cs | 73 ++++- src/Core/CodeAnalysis/Syntax/SyntaxKind.cs | 3 + .../CodeAnalysis/Syntax/TypeClauseSyntax.cs | 24 ++ .../Binding/NamedTupleElementTests.cs | 255 ++++++++++++++++++ ...ssue1675SyntaxNodeChildEnumerationTests.cs | 3 + .../CoverageMatrix/coverage-matrix.golden.txt | 1 + website/docs/ref/diagnostics.md | 18 ++ website/docs/ref/feature-matrix.md | 2 +- website/docs/ref/spec.md | 7 +- 30 files changed, 1028 insertions(+), 18 deletions(-) create mode 100644 docs/adr/0172-named-tuple-elements.md create mode 100644 samples/NamedTupleElements.golden create mode 100644 samples/NamedTupleElements.gs create mode 100644 src/Core/CodeAnalysis/Binding/TupleElementNameValidation.cs create mode 100644 src/Core/CodeAnalysis/Syntax/NamedTupleElementSyntax.cs create mode 100644 test/Core.Tests/CodeAnalysis/Binding/NamedTupleElementTests.cs diff --git a/docs/adr/0115-csharp-to-gsharp-migration-tool.md b/docs/adr/0115-csharp-to-gsharp-migration-tool.md index 5fbf2a48e..28faec720 100644 --- a/docs/adr/0115-csharp-to-gsharp-migration-tool.md +++ b/docs/adr/0115-csharp-to-gsharp-migration-tool.md @@ -109,7 +109,7 @@ Since issue #948, the inline field initializers the translator emits here — `p **T4 — fieldless record → plain (`open`) `class`/`struct`.** A G# `data` type requires **at least one field** (`GS0104`, "a data type requires at least one field"). A C# **fieldless record** — typically the `abstract record Shape;` base of a closed `record` hierarchy — therefore maps to a plain `class` (or `struct`), **not** a `data class`; it is marked `open` when any case derives from it (§B.6). Two further losses are made faithfully: G# has **no `abstract` class modifier** (the keyword is not recognized by the parser; `abstract class` → `GS0125`), so C# `abstract` is **dropped** (the `open class` is subclassable but not non-instantiable); and the record-synthesized `IEquatable` interface is **dropped from the base list** because a fieldless record maps to a plain `class` that has no synthesized `Equals`, so emitting `: IEquatable[Shape]` would leave the interface unimplemented (`GS0187`). Naming the enclosing type as a base-clause type *argument* is itself legal since issue #949 (`open class Shape : IEquatable[Shape]` now compiles); the drop is a semantic-redundancy filter, not the former `GS0113` syntax limitation. Each loss is recorded as an Info diagnostic. The case records (`sealed record Circle(double Radius) : Shape`) keep the `data class Circle(Radius float64) : Shape` mapping. -**T1 — C# tuples → native G# positional tuples.** A C# value/named tuple (`(string Name, int Price, int Quantity)`) maps to the **native G# positional tuple type** `(string, int32, int32)` (spec §Type syntax), *not* to a synthesized `data struct`. G# tuples are **positional only** — the named-element spelling `(Name string, …)` does not parse — so C# element **names are dropped** at the type, and a named-element **access** `item.Price` lowers to the positional field `item.Item2` (resolved via Roslyn's `IFieldSymbol.CorrespondingTupleField`); positional `item.Item1` passes through. Tuple **construction** `(a, b, c)` maps to the G# tuple literal `(a, b, c)`. The mapping is recorded as an Info diagnostic. This was chosen over synthesizing a `data struct` per tuple shape because a `data struct` element type triggers a real compiler gap (below) and because native tuples are the genuinely canonical, round-trippable G# form. +**T1 — C# tuples → native G# positional tuples.** *Amended by ADR-0172 (2026-08-28): G# now supports named tuple elements (`(name string, price int32)` types, `(name: e)` literal labels), so the name-dropping described below is superseded — cs2gs preserves element names once its ADR-0172 Phase C lands. The remainder of this section records the original positional-only mapping.* A C# value/named tuple (`(string Name, int Price, int Quantity)`) maps to the **native G# positional tuple type** `(string, int32, int32)` (spec §Type syntax), *not* to a synthesized `data struct`. G# tuples were **positional only** — the named-element spelling `(Name string, …)` did not parse — so C# element **names were dropped** at the type, and a named-element **access** `item.Price` lowered to the positional field `item.Item2` (resolved via Roslyn's `IFieldSymbol.CorrespondingTupleField`); positional `item.Item1` passes through. Tuple **construction** `(a, b, c)` maps to the G# tuple literal `(a, b, c)`. The mapping is recorded as an Info diagnostic. This was chosen over synthesizing a `data struct` per tuple shape because a `data struct` element type triggers a real compiler gap (below) and because native tuples are the genuinely canonical, round-trippable G# form. > **`for … in List[ownedType]` element-type erasure — discovered compiler gap.** > Iterating a `List[T]` whose element `T` is a **user type owned by the same diff --git a/docs/adr/0172-named-tuple-elements.md b/docs/adr/0172-named-tuple-elements.md new file mode 100644 index 000000000..507cf9c99 --- /dev/null +++ b/docs/adr/0172-named-tuple-elements.md @@ -0,0 +1,99 @@ +# ADR-0172: Named tuple elements + +- **Status**: Accepted +- **Date**: 2026-08-28 +- **Related**: issue #3501 (self-migration readability), ADR-0115 §B.4/T1 (amended by this ADR), ADR-0171 (tuple equality), ADR-0029 (data-struct members), C# §8.3.11 / `TupleElementNamesAttribute`. + +## Context + +G# tuples were purely positional — ADR-0115 §B.4 stated "the named-element +spelling does not parse" as a premise, and cs2gs accordingly dropped C# element +names: type `(int Line, int Column)` mapped to `(int32, int32)`, access +`t.Line` lowered to `t.Item1`, literal labels were discarded. The #3501 +self-migration corpus contains roughly 830 named-tuple declaration lines +across ~300 files, so translated output was full of `item.Item2 * item.Item3` +where the C# said `item.Price * item.Quantity` — contrary to #3501's "fully +readable, maintainable" definition of done, and invisible to the selfmig +ratchet. The loss also crossed the CLR boundary in both directions: gsc +neither emitted nor imported `TupleElementNamesAttribute`. + +## Decision + +G# gains named tuple elements. **This reverses the ADR-0115 §B.4/T1 +positional-only premise**; an amendment note there points here. + +### Syntax + +- **Tuple types** name elements name-first, matching the parameter form + `identifier TypeClause`: + + ```gs + let pos (line int32, column int32) = (3, 5) + func divmod(a int32, b int32) (quotient int32, remainder int32) { … } + ``` + + Partial naming is allowed (`(count int32, string)`). An identifier is an + element NAME exactly when it is followed by a token that can start a type + clause; the `[` case distinguishes `name []T` / `name [3]T` / `name [,]T` + (array shapes open with `]`, a number, or a rank comma) from a generic + argument list `List[int32]`, and the `unmanaged` function-pointer head + keeps its ADR-0095 meaning. The #3315 grouping rule is preserved: there + are no 1-tuples, and a name on a parenthesized single element is error + **GS0543**, recovered as grouping. + +- **Tuple literals** label elements with a colon, matching the + argument-label style: + + ```gs + let t = (line: 7, column: 9) // infers (line int32, column int32) + ``` + + Labels are optional per element. A lone labeled element `(x: 1)` is + GS0543, recovered as a plain parenthesized expression. C# 7.1-style name + inference from expressions is deliberately not adopted. + +### Semantics: names are metadata + +- The interned `TupleTypeSymbol` is keyed on (element types, element names): + a named and an unnamed same-shape tuple are **distinct symbol instances + sharing the same CLR backing**, related by an **identity conversion** + (`WithoutNames()` computes the canonical unnamed shape, recursively). + Assignment, argument passing, and returns cross name boundaries freely; + a position where both sides declare *different* names warns **GS0541** + (the C# CS8123 analog). +- Member access resolves a declared name to its position; `ItemN` and the + numeric `.N` selectors remain valid on named tuples. Emit is unchanged — + access lowers to the positional `ItemN` field either way. +- Declaration checks: duplicate name = **GS0540**; `ItemN` at any position + other than N and `Rest` = **GS0542** (correct-position `ItemN` is + allowed, as in C#). +- Names propagate through generic substitution and merge across + common-type joins by the C# rule (keep agreeing names, drop the rest). +- **Equality (ADR-0171) ignores names** — the desugar compares shape, never + symbol identity. +- Positional deconstruction, patterns, and `for (a, b) in` are unchanged. + +### Phasing + +- **Phase A (this change)**: parser, symbol model, member lookup, + conversions/identity, diagnostics GS0540–GS0543, display. +- **Phase B**: metadata interop — emit `TupleElementNamesAttribute` on + tuple-typed parameters/returns/fields/properties (C# flattened pre-order + encoding) and decode it on import, so C#-authored named tuples surface + their names in G# and vice versa. +- **Phase C**: cs2gs preserves names end-to-end (type mapping, printer, + member access, literal labels) + corpus/selfmig re-baseline. +- **Phase D**: language-server polish (hover, element-name completion). + +## Alternatives rejected + +- **Names on references, not symbols** — G# has no annotation channel on + `BoundExpression` types; every consumer sees a bare `TypeSymbol`. The + wrapper-symbol precedent (`NullableTypeSymbol`) confirms symbols are the + annotation channel. +- **Nominal named tuples** (names part of type identity) — breaks C# + interop semantics and every existing positional conversion. + +## Future work (not planned) + +Deconstruction-by-name, named positional patterns, C# 7.1 name inference. diff --git a/docs/coverage-matrix.md b/docs/coverage-matrix.md index 67bc954a4..f1589e68b 100644 --- a/docs/coverage-matrix.md +++ b/docs/coverage-matrix.md @@ -169,6 +169,7 @@ NameOfExpression NamedArgumentExpression NamedDeconstructionField NamedDeconstructionStatement +NamedTupleElement NilKeyword NotPattern NullCoalescingAssignmentStatement diff --git a/docs/diagnostics.md b/docs/diagnostics.md index 7b9868a4c..d2ef8dd08 100644 --- a/docs/diagnostics.md +++ b/docs/diagnostics.md @@ -730,6 +730,24 @@ than a constant `false`, matching C#. |---|---|---|---| | GS0539 | Error | `Tuple equality requires operands of equal arity: '' has arity but '' has arity .` | `(1, 2) == (1, 2, 3)` | +## Named tuple elements (GS0540, GS0541, GS0542, GS0543) + +[ADR-0172](adr/0172-named-tuple-elements.md): tuple types may name elements +name-first — `(line int32, column int32)` — and tuple literals may label +them — `(line: 1, column: 2)`. Names are metadata over the positional shape: +same-shape tuples differing only in names are identity-convertible, with a +warning where names disagree position-wise. `ItemN` at the wrong position and +`Rest` (used by the CLR ValueTuple encoding) are reserved; a name on a +parenthesized single element is an error because `(T)` is grouping, not a +1-tuple (issue #3315). + +| ID | Severity | Message | Example | +|---|---|---|---| +| GS0540 | Error | `Tuple element name '' is used more than once.` | `(line int32, line int32)` | +| GS0541 | Warning | `Tuple element name '' is ignored because the target type names this position ''.` | `let r (row int32, col int32) = pos` where `pos` is `(line int32, column int32)` | +| GS0542 | Error | `Tuple element name '' is reserved.` | `(Item2 int32, x int32)`; `(Rest: 1, x: 2)` | +| GS0543 | Error | `An element name is only valid inside a tuple of two or more elements; a parenthesized single element is grouping.` | `(line: 1)`; `let x (line int32) = 1` | + ## Pattern variable outside its definitely-assigned region (GS0532) [ADR-0166](adr/0166-is-pattern-variables.md): a designation in a boolean `is` diff --git a/samples/NamedTupleElements.golden b/samples/NamedTupleElements.golden new file mode 100644 index 000000000..e28d66c7f --- /dev/null +++ b/samples/NamedTupleElements.golden @@ -0,0 +1,8 @@ +3 +5 +3 +16 +5 +3 rem 2 +2 +True diff --git a/samples/NamedTupleElements.gs b/samples/NamedTupleElements.gs new file mode 100644 index 000000000..839631733 --- /dev/null +++ b/samples/NamedTupleElements.gs @@ -0,0 +1,34 @@ +// file: NamedTupleElements.gs +// ADR-0172: named tuple elements. Types name elements name-first — +// `(line int32, column int32)` — and literals label with a colon — +// `(line: 1, column: 2)`. Names are metadata over the positional shape: +// access by name resolves to the position (ItemN stays valid), same-shape +// tuples differing only in names are identity-convertible, and equality +// (ADR-0171) ignores names. + +package GSharp.Example.NamedTupleElements + +import System + +func divmod(a int32, b int32) (quotient int32, remainder int32) { + return a / b, a % b +} + +let pos (line int32, column int32) = (3, 5) +Console.WriteLine(pos.line) +Console.WriteLine(pos.column) +Console.WriteLine(pos.Item1) + +let labeled = (line: 7, column: 9) +Console.WriteLine(labeled.line + labeled.column) + +let unnamed (int32, int32) = pos +Console.WriteLine(unnamed.Item2) + +let r = divmod(17, 5) +Console.WriteLine("${r.quotient} rem ${r.remainder}") + +let nested (inner (a int32, b int32), tag string) = ((a: 1, b: 2), "x") +Console.WriteLine(nested.inner.b) + +Console.WriteLine(pos == (3, 5)) diff --git a/src/Core/CodeAnalysis/Binding/Binder.cs b/src/Core/CodeAnalysis/Binding/Binder.cs index cd3f3f622..16a4f9555 100644 --- a/src/Core/CodeAnalysis/Binding/Binder.cs +++ b/src/Core/CodeAnalysis/Binding/Binder.cs @@ -3602,6 +3602,8 @@ private static Accessibility ResolveAccessibility(SyntaxToken? modifier) } var elements = ImmutableArray.CreateBuilder(tupleElements.Count); + var elementNames = ImmutableArray.CreateBuilder(tupleElements.Count); + var anyElementName = false; for (var i = 0; i < tupleElements.Count; i++) { var elementType = BindTypeClause(tupleElements[i]); @@ -3611,9 +3613,24 @@ private static Accessibility ResolveAccessibility(SyntaxToken? modifier) } elements.Add(elementType); + + // ADR-0172: `(line int32, column int32)` — the parser stored + // each element's optional name on the element clause. + var nameToken = tupleElements[i].TupleElementNameToken; + elementNames.Add(nameToken?.ValueText); + anyElementName |= nameToken != null; + } + + var names = anyElementName ? elementNames.MoveToImmutable() : ImmutableArray.Empty; + if (anyElementName) + { + TupleElementNameValidation.Validate( + Diagnostics, + names, + i => Invariant.Required(tupleElements[i].TupleElementNameToken, "validation only visits named elements").Location); } - return TupleTypeSymbol.Get(elements.MoveToImmutable()); + return TupleTypeSymbol.Get(elements.MoveToImmutable(), names); } if (syntax.IsMap) diff --git a/src/Core/CodeAnalysis/Binding/Conversion.cs b/src/Core/CodeAnalysis/Binding/Conversion.cs index 1822a86ff..ad6c3967f 100644 --- a/src/Core/CodeAnalysis/Binding/Conversion.cs +++ b/src/Core/CodeAnalysis/Binding/Conversion.cs @@ -381,6 +381,20 @@ internal static Conversion ClassifyCore( return Conversion.Identity; } + // ADR-0172: element names are metadata over the positional shape — + // two tuples whose shapes agree (recursively ignoring names) denote + // the SAME type, related by an identity conversion, exactly like C#. + // `WithoutNames()` returns the canonical unnamed interned symbol, so + // reference equality decides shape identity. The name-mismatch + // warning (GS0541) is reported at conversion-binding time, not here — + // classification is pure. + if (from is TupleTypeSymbol fromNamedTuple && to is TupleTypeSymbol toNamedTuple + && !ReferenceEquals(fromNamedTuple, toNamedTuple) + && ReferenceEquals(fromNamedTuple.WithoutNames(), toNamedTuple.WithoutNames())) + { + return Conversion.Identity; + } + // Issue #1256: element-wise tuple conversion. A tuple `(T1, …, Tn)` // converts implicitly to `(U1, …, Un)` when both are tuple types of // the SAME arity and EACH element `Ti → Ui` has an implicit conversion diff --git a/src/Core/CodeAnalysis/Binding/ConversionClassifier.cs b/src/Core/CodeAnalysis/Binding/ConversionClassifier.cs index 029c3cb86..4e91a25b0 100644 --- a/src/Core/CodeAnalysis/Binding/ConversionClassifier.cs +++ b/src/Core/CodeAnalysis/Binding/ConversionClassifier.cs @@ -793,6 +793,27 @@ public BoundExpression BindConversion( if (conversion.IsIdentity) { + // ADR-0172: same-shape tuples differing only in element names are + // identity-convertible; warn (GS0541) where a source name + // disagrees with the name the target declares at that position. + if (expression.Type is TupleTypeSymbol identitySourceTuple + && type is TupleTypeSymbol identityTargetTuple + && !ReferenceEquals(identitySourceTuple, identityTargetTuple) + && identitySourceTuple.HasNames + && identityTargetTuple.HasNames) + { + for (var i = 0; i < identitySourceTuple.Arity; i++) + { + var sourceName = identitySourceTuple.ElementNames[i]; + var targetName = identityTargetTuple.ElementNames[i]; + if (sourceName != null && targetName != null + && !string.Equals(sourceName, targetName, StringComparison.Ordinal)) + { + Diagnostics.ReportTupleElementNameMismatch(diagnosticLocation, sourceName, targetName); + } + } + } + return expression; } diff --git a/src/Core/CodeAnalysis/Binding/ExpressionBinder.Access.MemberLookup.cs b/src/Core/CodeAnalysis/Binding/ExpressionBinder.Access.MemberLookup.cs index ccb1102bc..18904454f 100644 --- a/src/Core/CodeAnalysis/Binding/ExpressionBinder.Access.MemberLookup.cs +++ b/src/Core/CodeAnalysis/Binding/ExpressionBinder.Access.MemberLookup.cs @@ -37,6 +37,14 @@ private static bool TryGetTupleElementIndex( return false; } + // ADR-0172: a declared element name resolves to its position; + // `ItemN` and the zero-based `.N` selectors below stay valid on + // named tuples too. + if (tupleType.TryGetElementIndexByName(memberName, out zeroBased)) + { + return true; + } + if (int.TryParse(memberName, out var numericIndex) && numericIndex >= 0 && numericIndex < tupleType.Arity) diff --git a/src/Core/CodeAnalysis/Binding/ExpressionBinder.Literals.cs b/src/Core/CodeAnalysis/Binding/ExpressionBinder.Literals.cs index 4b033396b..224ede7f1 100644 --- a/src/Core/CodeAnalysis/Binding/ExpressionBinder.Literals.cs +++ b/src/Core/CodeAnalysis/Binding/ExpressionBinder.Literals.cs @@ -2199,10 +2199,28 @@ private BoundExpression BindTupleLiteralExpression(TupleLiteralExpressionSyntax { // Phase 4.5: bind each element expression, derive the tuple type from // their static types, and produce a BoundTupleLiteralExpression. + // ADR-0172: a `name: expr` element contributes the label to the + // literal's tuple type; names are metadata over the positional shape. var bound = ImmutableArray.CreateBuilder(syntax.Elements.Count); var elementTypes = ImmutableArray.CreateBuilder(syntax.Elements.Count); - foreach (var e in syntax.Elements) + var elementNames = ImmutableArray.CreateBuilder(syntax.Elements.Count); + var anyName = false; + var nameTokens = new SyntaxToken?[syntax.Elements.Count]; + for (var i = 0; i < syntax.Elements.Count; i++) { + var e = syntax.Elements[i]; + if (e is NamedTupleElementSyntax named) + { + nameTokens[i] = named.NameToken; + elementNames.Add(named.NameToken.ValueText); + anyName = true; + e = named.Expression; + } + else + { + elementNames.Add(null); + } + var be = BindExpression(e); if (be.Type == TypeSymbol.Error) { @@ -2213,10 +2231,27 @@ private BoundExpression BindTupleLiteralExpression(TupleLiteralExpressionSyntax elementTypes.Add(be.Type); } - var tupleType = TupleTypeSymbol.Get(elementTypes.MoveToImmutable()); + var names = anyName ? elementNames.MoveToImmutable() : ImmutableArray.Empty; + if (anyName) + { + ValidateTupleElementNames(names, i => Invariant.Required(nameTokens[i], "a named element recorded its token").Location); + } + + var tupleType = TupleTypeSymbol.Get(elementTypes.MoveToImmutable(), names); return new BoundTupleLiteralExpression(null, tupleType, bound.MoveToImmutable()); } + /// + /// ADR-0172: validates declared tuple element names — duplicates + /// (GS0540) and reserved names (GS0542: ItemN anywhere but + /// position N, and Rest, which the CLR encoding uses for arity + /// ≥ 8). Shared by tuple literals and tuple type clauses. + /// + /// The declared names, where unnamed. + /// Maps an element index to its name token's location. + internal void ValidateTupleElementNames(ImmutableArray names, System.Func locationOf) + => Binding.TupleElementNameValidation.Validate(Diagnostics, names, locationOf); + /// ADR-0039: Computes per-argument from CLR parameter metadata. /// /// Issue #368 / ADR-0055: rewrites any interpolated-string argument passed to diff --git a/src/Core/CodeAnalysis/Binding/ExpressionBinder.cs b/src/Core/CodeAnalysis/Binding/ExpressionBinder.cs index ebe664d8f..80c7e3c12 100644 --- a/src/Core/CodeAnalysis/Binding/ExpressionBinder.cs +++ b/src/Core/CodeAnalysis/Binding/ExpressionBinder.cs @@ -486,6 +486,13 @@ private BoundExpression BindExpressionpublic(ExpressionSyntax syntax, bool canBe return BindAnonymousClassExpression((AnonymousClassExpressionSyntax)syntax); case SyntaxKind.TupleLiteralExpression: return BindTupleLiteralExpression((TupleLiteralExpressionSyntax)syntax); + case SyntaxKind.NamedTupleElement: + // ADR-0172: a labeled element is only meaningful as a direct + // tuple-literal child (unwrapped there); anywhere else the + // label is stray — report and bind the value. + var strayNamed = (NamedTupleElementSyntax)syntax; + Diagnostics.ReportTupleElementNameOutsideTuple(strayNamed.NameToken.Location); + return BindExpression(strayNamed.Expression); case SyntaxKind.FunctionLiteralExpression: return lambdas.BindFunctionLiteralExpression((FunctionLiteralExpressionSyntax)syntax); case SyntaxKind.LambdaExpression: diff --git a/src/Core/CodeAnalysis/Binding/MemberLookup.cs b/src/Core/CodeAnalysis/Binding/MemberLookup.cs index feef4c29b..a1f63864d 100644 --- a/src/Core/CodeAnalysis/Binding/MemberLookup.cs +++ b/src/Core/CodeAnalysis/Binding/MemberLookup.cs @@ -5849,7 +5849,23 @@ private static TypeSymbol NormalizeRecoveredNullability(TypeSymbol t) merged.Add(item); } - return TupleTypeSymbol.Get(merged.MoveToImmutable()); + // ADR-0172: keep element names the two sides agree on; drop the + // rest (the C# common-type rule for tuple names). + var mergedNames = ImmutableArray.Empty; + if (existingTuple.HasNames || incomingTuple.HasNames) + { + var namesBuilder = ImmutableArray.CreateBuilder(existingTuple.ElementTypes.Length); + for (var i = 0; i < existingTuple.ElementTypes.Length; i++) + { + var existingName = existingTuple.HasNames ? existingTuple.ElementNames[i] : null; + var incomingName = incomingTuple.HasNames ? incomingTuple.ElementNames[i] : null; + namesBuilder.Add(string.Equals(existingName, incomingName, StringComparison.Ordinal) ? existingName : null); + } + + mergedNames = namesBuilder.MoveToImmutable(); + } + + return TupleTypeSymbol.Get(merged.MoveToImmutable(), mergedNames); } return !TypeSymbol.ContainsReferenceNullableAnnotation(existing) diff --git a/src/Core/CodeAnalysis/Binding/TupleElementNameValidation.cs b/src/Core/CodeAnalysis/Binding/TupleElementNameValidation.cs new file mode 100644 index 000000000..c22961cfb --- /dev/null +++ b/src/Core/CodeAnalysis/Binding/TupleElementNameValidation.cs @@ -0,0 +1,65 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using GSharp.Core.CodeAnalysis.Text; + +namespace GSharp.Core.CodeAnalysis.Binding; + +/// +/// ADR-0172: shared declaration-site validation for tuple element names, +/// used by both tuple type clauses and labeled tuple literals. Reports +/// GS0540 for a name used more than once and GS0542 for reserved names — +/// ItemN at any position other than N (the correct-position spelling +/// is allowed, matching C#), and Rest (used by the CLR encoding for +/// arity ≥ 8 nesting). +/// +internal static class TupleElementNameValidation +{ + /// Validates the declared names, reporting on the given bag. + /// The diagnostic bag to report on. + /// The declared names, parallel to the element list, where unnamed. + /// Maps an element index to its name token's location. + public static void Validate( + DiagnosticBag diagnostics, + ImmutableArray names, + Func locationOf) + { + HashSet? seen = null; + for (var i = 0; i < names.Length; i++) + { + var name = names[i]; + if (name == null) + { + continue; + } + + seen ??= new HashSet(StringComparer.Ordinal); + if (!seen.Add(name)) + { + diagnostics.ReportDuplicateTupleElementName(locationOf(i), name); + continue; + } + + if (name == "Rest") + { + diagnostics.ReportReservedTupleElementName(locationOf(i), name, " (used by the CLR ValueTuple encoding)"); + continue; + } + + if (name.StartsWith("Item", StringComparison.Ordinal) + && int.TryParse(name.Substring(4), out var oneBased) + && oneBased >= 1 + && oneBased != i + 1) + { + diagnostics.ReportReservedTupleElementName( + locationOf(i), + name, + $" at this position; '{name}' is only valid as element {oneBased}'s name"); + } + } + } +} diff --git a/src/Core/CodeAnalysis/DiagnosticBag.Reports.Expressions.cs b/src/Core/CodeAnalysis/DiagnosticBag.Reports.Expressions.cs index 0a5b79bbd..cc270e372 100644 --- a/src/Core/CodeAnalysis/DiagnosticBag.Reports.Expressions.cs +++ b/src/Core/CodeAnalysis/DiagnosticBag.Reports.Expressions.cs @@ -182,6 +182,43 @@ public void ReportUndefinedBinaryOperator(TextLocation location, string operator public void ReportTupleEqualityArityMismatch(TextLocation location, TypeSymbol leftType, int leftArity, TypeSymbol rightType, int rightArity) => Report(location, DiagnosticDescriptors.TupleEqualityArityMismatch, leftType, leftArity, rightType, rightArity); + /// + /// ADR-0172: reports a tuple element name declared more than once on the + /// same tuple type or literal. + /// + /// The text location of the duplicate name. + /// The duplicated element name. + public void ReportDuplicateTupleElementName(TextLocation location, string name) + => Report(location, DiagnosticDescriptors.DuplicateTupleElementName, name); + + /// + /// ADR-0172: reports (as a warning) an element name that disagrees with + /// the name the target tuple type declares at the same position. + /// + /// The text location of the conversion or label. + /// The source element name being ignored. + /// The target type's name at that position. + public void ReportTupleElementNameMismatch(TextLocation location, string name, string targetName) + => Report(location, DiagnosticDescriptors.TupleElementNameMismatch, name, targetName); + + /// + /// ADR-0172: reports a reserved tuple element name — ItemN at any + /// position other than N, or Rest. + /// + /// The text location of the name. + /// The reserved name. + /// A clarifying suffix (e.g. " at this position; 'Item2' is only valid as the second element's name"), or an empty string. + public void ReportReservedTupleElementName(TextLocation location, string name, string detail) + => Report(location, DiagnosticDescriptors.ReservedTupleElementName, name, detail); + + /// + /// ADR-0172: reports an element name attached to a parenthesized single + /// element — grouping, not a tuple. + /// + /// The text location of the stray name. + public void ReportTupleElementNameOutsideTuple(TextLocation location) + => Report(location, DiagnosticDescriptors.TupleElementNameOutsideTuple); + /// /// Issue #3317 / ADR-0159: reports that a nil comparison against a bare /// (non-?) magic collection type is statically constant — with diff --git a/src/Core/CodeAnalysis/DiagnosticDescriptors.cs b/src/Core/CodeAnalysis/DiagnosticDescriptors.cs index 8adbeef94..4f6d52251 100644 --- a/src/Core/CodeAnalysis/DiagnosticDescriptors.cs +++ b/src/Core/CodeAnalysis/DiagnosticDescriptors.cs @@ -419,6 +419,12 @@ internal static class DiagnosticDescriptors internal static readonly DiagnosticDescriptor GenericOperatorNotSupported = new("GS0537", DiagnosticSeverity.Error, "Operator '{0}' cannot declare method type parameters; put generic parameters on the containing type instead."); internal static readonly DiagnosticDescriptor ConstNativeIntegerNotSupported = new("GS0538", DiagnosticSeverity.Error, "Constant '{0}' cannot use native-width type '{1}' when emitted as a field because CLR constant metadata has no native-width representation; use 'int64'/'uint64' or an immutable runtime 'let' binding."); internal static readonly DiagnosticDescriptor TupleEqualityArityMismatch = new("GS0539", DiagnosticSeverity.Error, "Tuple equality requires operands of equal arity: '{0}' has arity {1} but '{2}' has arity {3}."); + + // ADR-0172: named tuple elements. + internal static readonly DiagnosticDescriptor DuplicateTupleElementName = new("GS0540", DiagnosticSeverity.Error, "Tuple element name '{0}' is used more than once."); + internal static readonly DiagnosticDescriptor TupleElementNameMismatch = new("GS0541", DiagnosticSeverity.Warning, "Tuple element name '{0}' is ignored because the target type names this position '{1}'."); + internal static readonly DiagnosticDescriptor ReservedTupleElementName = new("GS0542", DiagnosticSeverity.Error, "Tuple element name '{0}' is reserved{1}."); + internal static readonly DiagnosticDescriptor TupleElementNameOutsideTuple = new("GS0543", DiagnosticSeverity.Error, "An element name is only valid inside a tuple of two or more elements; a parenthesized single element is grouping."); internal static readonly DiagnosticDescriptor CannotTakeAddressOfNonLvalue = new("GS9001", DiagnosticSeverity.Error, "Cannot take address of '{0}': expression is not an lvalue."); internal static readonly DiagnosticDescriptor ArgumentMustBePassedByRef = new("GS9002", DiagnosticSeverity.Error, "Argument {0} to '{1}' must be passed by reference (`&`)."); internal static readonly DiagnosticDescriptor VariableNotDefinitelyAssignedForRef = new("GS9003", DiagnosticSeverity.Error, "Variable '{0}' must be definitely assigned before being passed by `ref`."); diff --git a/src/Core/CodeAnalysis/Symbols/Display/SymbolDisplay.cs b/src/Core/CodeAnalysis/Symbols/Display/SymbolDisplay.cs index de071cc7b..5d5a7f8b6 100644 --- a/src/Core/CodeAnalysis/Symbols/Display/SymbolDisplay.cs +++ b/src/Core/CodeAnalysis/Symbols/Display/SymbolDisplay.cs @@ -726,7 +726,12 @@ private static string FormatType(TypeSymbol? type) case PinnedTypeSymbol pinned: return $"pinned {FormatType(pinned.UnderlyingType)}"; case TupleTypeSymbol tuple: - return $"({string.Join(", ", tuple.ElementTypes.Select(FormatType))})"; + // ADR-0172: render declared element names name-first, + // matching the source spelling `(line int32, column int32)`. + return "(" + string.Join(", ", tuple.ElementTypes.Select((t, i) => + tuple.HasNames && tuple.ElementNames[i] is { } elementName + ? $"{elementName} {FormatType(t)}" + : FormatType(t))) + ")"; case StructSymbol aggregate when IsAnonymousClassType(aggregate): // Issue #2224: an anonymous-class literal's synthesized type has // no separate declaration a user could hover to see its shape diff --git a/src/Core/CodeAnalysis/Symbols/TupleTypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/TupleTypeSymbol.cs index 953c72148..a958862a8 100644 --- a/src/Core/CodeAnalysis/Symbols/TupleTypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/TupleTypeSymbol.cs @@ -23,18 +23,33 @@ public sealed class TupleTypeSymbol : TypeSymbol { private static readonly ConcurrentDictionary Cache = new(); - private TupleTypeSymbol(ImmutableArray elementTypes) + private TupleTypeSymbol(ImmutableArray elementTypes, ImmutableArray elementNames) // TypeSymbol's legacy CLR-type constructor accepts null for symbolic - // same-compilation element types. - : base(BuildName(elementTypes), BuildClrType(elementTypes)) + // same-compilation element types. Element names never affect the CLR + // backing (ADR-0172: names are metadata over the positional shape). + : base(BuildName(elementTypes, elementNames), BuildClrType(elementTypes)) { ElementTypes = elementTypes; + ElementNames = elementNames; } /// Gets the tuple element types in declaration order. public ImmutableArray ElementTypes { get; } + /// + /// Gets the declared element names, parallel to + /// with at unnamed + /// positions — or an empty array for a fully unnamed tuple (ADR-0172). + /// Names are metadata: they never affect the CLR backing, conversions, + /// or equality; same-shape tuples differing only in names are related by + /// an identity conversion. + /// + public ImmutableArray ElementNames { get; } + + /// Gets a value indicating whether any element declares a name. + public bool HasNames => !ElementNames.IsDefaultOrEmpty; + /// Gets the arity of the tuple. public int Arity => ElementTypes.Length; @@ -42,12 +57,38 @@ private TupleTypeSymbol(ImmutableArray elementTypes) /// The element types in order. /// The (cached) tuple type symbol. public static TupleTypeSymbol Get(ImmutableArray elementTypes) + => Get(elementTypes, elementNames: default); + + /// + /// Returns the cached for the given element + /// types and names (ADR-0172). A default/empty or all- + /// name array yields the canonical unnamed tuple. + /// + /// The element types in order. + /// The element names, parallel to , where unnamed. + /// The (cached) tuple type symbol. + public static TupleTypeSymbol Get(ImmutableArray elementTypes, ImmutableArray elementNames) { if (elementTypes.IsDefaultOrEmpty || elementTypes.Length < 2) { throw new ArgumentException("Tuples must have at least two element types.", nameof(elementTypes)); } + if (!elementNames.IsDefaultOrEmpty && elementNames.Length != elementTypes.Length) + { + throw new ArgumentException("Element names must parallel element types.", nameof(elementNames)); + } + + if (!elementNames.IsDefaultOrEmpty && elementNames.All(n => n == null)) + { + elementNames = ImmutableArray.Empty; + } + + if (elementNames.IsDefault) + { + elementNames = ImmutableArray.Empty; + } + // Issue #1624: key on element-type *identity* (via FunctionTypeSymbol's // shared identity-key builder), not the display name. A name-based key // (e.g. "(Holder, string)") can alias two distinct same-named types @@ -67,8 +108,72 @@ public static TupleTypeSymbol Get(ImmutableArray elementTypes) FunctionTypeSymbol.AppendIdentityKey(keyBuilder, elementTypes[i]); } + // ADR-0172: names participate in the cache key (a named and an + // unnamed same-shape tuple are distinct interned symbols related by + // an identity conversion), with an empty suffix for the canonical + // unnamed tuple so pre-existing keys are unchanged. + if (elementNames.Length > 0) + { + keyBuilder.Append('|'); + for (var i = 0; i < elementNames.Length; i++) + { + if (i > 0) + { + keyBuilder.Append(','); + } + + keyBuilder.Append(elementNames[i]); + } + } + var key = keyBuilder.ToString(); - return Cache.GetOrAdd(key, _ => new TupleTypeSymbol(elementTypes)); + return Cache.GetOrAdd(key, _ => new TupleTypeSymbol(elementTypes, elementNames)); + } + + /// + /// Returns the canonical fully unnamed tuple of this tuple's shape, + /// recursively stripping names from nested tuple elements (ADR-0172). + /// Two tuples denote the same type exactly when their + /// results are reference-equal. + /// + /// The (cached) unnamed same-shape tuple symbol. + public TupleTypeSymbol WithoutNames() + { + var stripped = ImmutableArray.CreateBuilder(ElementTypes.Length); + var changed = HasNames; + foreach (var elementType in ElementTypes) + { + var strippedElement = StripNames(elementType); + stripped.Add(strippedElement); + changed |= !ReferenceEquals(strippedElement, elementType); + } + + return changed ? Get(stripped.MoveToImmutable()) : this; + } + + /// + /// Finds the zero-based position of a declared element name (ordinal, + /// case-sensitive), or returns . + /// + /// The element name to find. + /// The zero-based element index when found. + /// Whether the name is declared on this tuple. + public bool TryGetElementIndexByName(string name, out int index) + { + if (HasNames) + { + for (var i = 0; i < ElementNames.Length; i++) + { + if (string.Equals(ElementNames[i], name, StringComparison.Ordinal)) + { + index = i; + return true; + } + } + } + + index = -1; + return false; } /// @@ -96,7 +201,14 @@ internal static Type GetOpenClrType(int arity) internal static Type? BuildClrType(Type[] elementTypes) => BuildClrType(elementTypes, 0, elementTypes.Length); - private static string BuildName(ImmutableArray elementTypes) + private static TypeSymbol StripNames(TypeSymbol type) => type switch + { + TupleTypeSymbol tuple => tuple.WithoutNames(), + NullableTypeSymbol { UnderlyingType: TupleTypeSymbol nested } => NullableTypeSymbol.Get(nested.WithoutNames()), + _ => type, + }; + + private static string BuildName(ImmutableArray elementTypes, ImmutableArray elementNames) { var sb = new StringBuilder("("); for (var i = 0; i < elementTypes.Length; i++) @@ -106,6 +218,12 @@ private static string BuildName(ImmutableArray elementTypes) sb.Append(", "); } + if (!elementNames.IsDefaultOrEmpty && elementNames[i] != null) + { + sb.Append(elementNames[i]); + sb.Append(' '); + } + sb.Append(elementTypes[i].Name); } diff --git a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs index 598b34f0d..818495676 100644 --- a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs @@ -1170,7 +1170,7 @@ internal static bool TrySubstituteCompositeType( } result = tupleChanged - ? TupleTypeSymbol.Get(tupleElements.MoveToImmutable()) + ? TupleTypeSymbol.Get(tupleElements.MoveToImmutable(), tuple.ElementNames) : type; return true; case ByRefTypeSymbol byRef: diff --git a/src/Core/CodeAnalysis/Syntax/NamedTupleElementSyntax.cs b/src/Core/CodeAnalysis/Syntax/NamedTupleElementSyntax.cs new file mode 100644 index 000000000..b0bc056d6 --- /dev/null +++ b/src/Core/CodeAnalysis/Syntax/NamedTupleElementSyntax.cs @@ -0,0 +1,43 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +namespace GSharp.Core.CodeAnalysis.Syntax; + +/// +/// ADR-0172: a labeled tuple-literal element name: expr. Only valid as +/// a direct element of a tuple literal of two or more elements; derives from +/// so it slots into the tuple literal's +/// existing separated element list, and the binder unwraps it. +/// +public sealed class NamedTupleElementSyntax : ExpressionSyntax +{ + /// Initializes a new instance of the class. + /// The parent syntax tree. + /// The element-name identifier. + /// The : separating name and value. + /// The element value expression. + public NamedTupleElementSyntax( + SyntaxTree syntaxTree, + SyntaxToken nameToken, + SyntaxToken colonToken, + ExpressionSyntax expression) + : base(syntaxTree) + { + NameToken = nameToken; + ColonToken = colonToken; + Expression = expression; + } + + /// + public override SyntaxKind Kind => SyntaxKind.NamedTupleElement; + + /// Gets the element-name identifier token. + public SyntaxToken NameToken { get; } + + /// Gets the : token. + public SyntaxToken ColonToken { get; } + + /// Gets the element value expression. + public ExpressionSyntax Expression { get; } +} diff --git a/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Literals.cs b/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Literals.cs index 7d4fe04c1..c041e49df 100644 --- a/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Literals.cs +++ b/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Literals.cs @@ -1451,6 +1451,16 @@ private ExpressionSyntax ParseParenthesizedExpression() { var left = MatchToken(SyntaxKind.OpenParenthesisToken); + // ADR-0172: a leading `identifier :` commits to a labeled tuple + // literal — `(line: 1, column: 2)`. The only other colon directly + // inside plain parens is the ADR-0061 conditional-ref form + // `(cond ? a : b)`, whose colon never immediately follows the first + // identifier, so one token of lookahead disambiguates. + if (Current.Kind == SyntaxKind.IdentifierToken && Peek(1).Kind == SyntaxKind.ColonToken) + { + return ParseLabeledTupleLiteral(left); + } + // Issue #522: a parenthesised expression is a fresh inner context — // even inside an `if (T() { X = 1 }) { body }` style header, the // inner call should still admit a trailing object initializer. @@ -1500,7 +1510,7 @@ private ExpressionSyntax ParseParenthesizedExpression() while (Current.Kind == SyntaxKind.CommaToken) { nodesAndSeparators.Add(MatchToken(SyntaxKind.CommaToken)); - nodesAndSeparators.Add(ParseExpression()); + nodesAndSeparators.Add(ParseTupleLiteralElement()); } var rightParen = MatchToken(SyntaxKind.CloseParenthesisToken); @@ -1515,6 +1525,78 @@ private ExpressionSyntax ParseParenthesizedExpression() return new ParenthesizedExpressionSyntax(syntaxTree, left, expression, right); } + /// + /// ADR-0172: parses one tuple-literal element — an optional + /// name : label followed by the element expression. + /// + private ExpressionSyntax ParseTupleLiteralElement() + { + if (Current.Kind == SyntaxKind.IdentifierToken && Peek(1).Kind == SyntaxKind.ColonToken) + { + var nameToken = MatchToken(SyntaxKind.IdentifierToken); + var colonToken = MatchToken(SyntaxKind.ColonToken); + var value = ParseExpression(); + return new NamedTupleElementSyntax(syntaxTree, nameToken, colonToken, value); + } + + return ParseExpression(); + } + + /// + /// ADR-0172: parses a tuple literal whose FIRST element is labeled — + /// `(line: 1, column: 2)` — reached when the token after the opening + /// paren is `identifier :`. A lone labeled element `(x: 1)` is an error + /// (grouping parens take no label, and there are no 1-tuples); it reports + /// GS0543 and recovers as a plain parenthesized expression. + /// + private ExpressionSyntax ParseLabeledTupleLiteral(SyntaxToken left) + { + // Same fresh inner context the plain parenthesized path establishes + // (issues #522/#1575/#1038). + var savedSuppress = suppressTrailingObjectInitializer; + var savedStructLiteral = suppressStructLiteral; + var savedRange = suppressRangeOperator; + suppressTrailingObjectInitializer = 0; + suppressStructLiteral = 0; + suppressRangeOperator = 0; + try + { + var firstElement = ParseTupleLiteralElement(); + if (Current.Kind != SyntaxKind.CommaToken) + { + var right = MatchToken(SyntaxKind.CloseParenthesisToken); + if (firstElement is NamedTupleElementSyntax lonelyLabeled) + { + Diagnostics.ReportTupleElementNameOutsideTuple(lonelyLabeled.NameToken.Location); + return new ParenthesizedExpressionSyntax(syntaxTree, left, lonelyLabeled.Expression, right); + } + + return new ParenthesizedExpressionSyntax(syntaxTree, left, firstElement, right); + } + + var nodesAndSeparators = ImmutableArray.CreateBuilder(); + nodesAndSeparators.Add(firstElement); + while (Current.Kind == SyntaxKind.CommaToken) + { + nodesAndSeparators.Add(MatchToken(SyntaxKind.CommaToken)); + nodesAndSeparators.Add(ParseTupleLiteralElement()); + } + + var rightParen = MatchToken(SyntaxKind.CloseParenthesisToken); + return new TupleLiteralExpressionSyntax( + syntaxTree, + left, + new SeparatedSyntaxList(nodesAndSeparators.ToImmutable()), + rightParen); + } + finally + { + suppressTrailingObjectInitializer = savedSuppress; + suppressStructLiteral = savedStructLiteral; + suppressRangeOperator = savedRange; + } + } + private ExpressionSyntax ParseBooleanLiteral() { var isTrue = Current.Kind == SyntaxKind.TrueKeyword; diff --git a/src/Core/CodeAnalysis/Syntax/Parser.TypeClauses.cs b/src/Core/CodeAnalysis/Syntax/Parser.TypeClauses.cs index 698e1023e..0e603960d 100644 --- a/src/Core/CodeAnalysis/Syntax/Parser.TypeClauses.cs +++ b/src/Core/CodeAnalysis/Syntax/Parser.TypeClauses.cs @@ -373,7 +373,25 @@ private TypeClauseSyntax ParseTupleTypeClause() Current.Kind != SyntaxKind.CloseParenthesisToken && Current.Kind != SyntaxKind.EndOfFileToken) { - nodesAndSeparators.Add(ParseTypeClause()); + // ADR-0172: an element may declare a name before its type — + // `(line int32, column int32)` — mirroring the parameter form + // `identifier TypeClause`. Committed only when the identifier is + // followed by a token that can start a type clause, so plain + // element types (`(int32, string)`, qualified `(System.Int32, …)`, + // generic `(List[int32], …)`) parse exactly as before. + SyntaxToken? elementNameToken = null; + if (Current.Kind == SyntaxKind.IdentifierToken && LooksLikeTupleElementName()) + { + elementNameToken = MatchToken(SyntaxKind.IdentifierToken); + } + + var elementClause = ParseTypeClause(); + if (elementNameToken != null) + { + elementClause.SetTupleElementNameToken(elementNameToken); + } + + nodesAndSeparators.Add(elementClause); if (Current.Kind == SyntaxKind.CommaToken) { @@ -394,6 +412,13 @@ private TypeClauseSyntax ParseTupleTypeClause() // Grouping (issue #3315): `(T)` is `T`; `(T)?` is whole-`T` // nullable. The parens themselves are dropped, exactly like the // parenthesized arrow-function form's outer parens (#1399). + // ADR-0172: a name on the single element — `(line int32)` — is an + // error (there are no 1-tuples); recover as grouping. + if (parenthesizedInner.TupleElementNameToken is { } strayName) + { + Diagnostics.ReportTupleElementNameOutsideTuple(strayName.Location); + } + if (question != null) { parenthesizedInner.SetParenthesizedQuestionToken(question); @@ -410,6 +435,52 @@ private TypeClauseSyntax ParseTupleTypeClause() question); } + /// + /// ADR-0172: decides whether the identifier at the current position is a + /// tuple-element NAME (followed by a token that can start a type clause) + /// rather than the element type itself. `identifier identifier` is always + /// name + type (two consecutive identifiers never form a type), and the + /// `[` case distinguishes an array/slice/rectangular element type + /// (`name []T`, `name [3]T`, `name [,]T`) from a generic type-argument + /// list on the identifier itself (`List[int32]`). The `unmanaged` + /// function-pointer head keeps its ADR-0095 meaning. + /// + private bool LooksLikeTupleElementName() + { + // `unmanaged[CC] (…) -> R` / `unmanaged (…) -> R` is a raw + // function-pointer TYPE head, never an element name. + if (Current.Text == "unmanaged" + && (Peek(1).Kind == SyntaxKind.OpenSquareBracketToken + || Peek(1).Kind == SyntaxKind.OpenParenthesisToken)) + { + return false; + } + + switch (Peek(1).Kind) + { + case SyntaxKind.IdentifierToken: + case SyntaxKind.FuncKeyword: + case SyntaxKind.MapKeyword: + case SyntaxKind.ChanKeyword: + case SyntaxKind.SequenceKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.StarToken: + case SyntaxKind.OpenParenthesisToken: + return true; + + case SyntaxKind.OpenSquareBracketToken: + // `name []T` / `name [3]T` / `name [,]T` vs generic + // `Ident[TypeArgs]` — an array shape opens with `]`, a + // number, or a rank comma; a type-argument list never does. + return Peek(2).Kind is SyntaxKind.CloseSquareBracketToken + or SyntaxKind.NumberToken + or SyntaxKind.CommaToken; + + default: + return false; + } + } + private TypeClauseSyntax ParseMapTypeClause() { // ADR-0104 / issue #805: canonical map type clause `map[K,V]` with optional trailing `?`. diff --git a/src/Core/CodeAnalysis/Syntax/SyntaxKind.cs b/src/Core/CodeAnalysis/Syntax/SyntaxKind.cs index 8ef6fffb9..f7177cbc0 100644 --- a/src/Core/CodeAnalysis/Syntax/SyntaxKind.cs +++ b/src/Core/CodeAnalysis/Syntax/SyntaxKind.cs @@ -252,6 +252,9 @@ public enum SyntaxKind FieldAccessExpression, FieldAssignmentExpression, TupleLiteralExpression, + + // ADR-0172: a labeled tuple-literal element `name: expr`. + NamedTupleElement, TupleDeconstructionStatement, NamedDeconstructionStatement, NamedDeconstructionField, diff --git a/src/Core/CodeAnalysis/Syntax/TypeClauseSyntax.cs b/src/Core/CodeAnalysis/Syntax/TypeClauseSyntax.cs index 09495e83b..8e2e9f26e 100644 --- a/src/Core/CodeAnalysis/Syntax/TypeClauseSyntax.cs +++ b/src/Core/CodeAnalysis/Syntax/TypeClauseSyntax.cs @@ -602,6 +602,16 @@ public string DottedName /// Gets a value indicating whether this clause carries a parenthesized whole-type ?(chan T)?, ([]T)?, … (issue #3315). public bool IsParenthesizedNullable => ParenthesizedQuestionToken != null; + /// + /// Gets the element name declared before this clause when it is + /// a named element of a tuple type — (line int32, column int32) — + /// or . Set by the parser via + /// immediately after the element + /// clause is built (the sanctioned parser-time mutation pattern, see + /// ). + /// + public SyntaxToken? TupleElementNameToken { get; private set; } + /// Gets the opening ( token for tuple types, or null. public SyntaxToken? OpenParenToken { get; } @@ -1255,4 +1265,18 @@ internal void SetParenthesizedQuestionToken(SyntaxToken? questionToken) ParenthesizedQuestionToken = questionToken; InvalidateCachedSpan(); } + + /// + /// ADR-0172: records the element name parsed before this clause inside a + /// tuple type — (line int32, column int32). Called by the parser + /// immediately after the element clause is built, before the node is read + /// by anyone else, so the parser-time mutation pattern applies; the cached + /// span is invalidated so it re-extends over the leading name token. + /// + /// The element-name identifier token. + internal void SetTupleElementNameToken(SyntaxToken nameToken) + { + TupleElementNameToken = nameToken; + InvalidateCachedSpan(); + } } diff --git a/test/Core.Tests/CodeAnalysis/Binding/NamedTupleElementTests.cs b/test/Core.Tests/CodeAnalysis/Binding/NamedTupleElementTests.cs new file mode 100644 index 000000000..17b5ef2a5 --- /dev/null +++ b/test/Core.Tests/CodeAnalysis/Binding/NamedTupleElementTests.cs @@ -0,0 +1,255 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using GSharp.Core.CodeAnalysis; +using GSharp.Core.CodeAnalysis.Compilation; +using GSharp.Core.CodeAnalysis.Symbols; +using GSharp.Core.CodeAnalysis.Syntax; +using GSharp.Core.CodeAnalysis.Text; +using GSharp.Tests; +using Xunit; + +namespace GSharp.Core.Tests.CodeAnalysis.Binding; + +/// +/// ADR-0172 Phase A: named tuple elements. Types spell names Go-style — +/// (line int32, column int32) — literals label with a colon — +/// (line: 1, column: 2) — and access resolves the name positionally +/// while ItemN/.N stay valid. Names are metadata: same-shape +/// tuples differing only in names are identity-convertible (GS0541 warning on +/// a position-wise disagreement). Witness of discrimination: before ADR-0172 +/// every named spelling below was a parse error (GS0113/GS0005 cascade) and +/// every name access was GS0158. +/// +public class NamedTupleElementTests +{ + [Fact] + public void NamedTypeClause_AccessByName_ItemN_AndNumericSelector() + { + var result = EmittedOracle.Evaluate(@" +let pos (line int32, column int32) = (3, 5) +pos.line + pos.Item2 + pos.column +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(13, result.Value); + } + + [Fact] + public void LabeledLiteral_InfersNamedType_AccessByName() + { + var result = EmittedOracle.Evaluate(@" +let t = (line: 7, column: 9) +t.line * 10 + t.column +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(79, result.Value); + } + + [Fact] + public void PartialNaming_NamedAndUnnamedElementsCoexist() + { + var result = EmittedOracle.Evaluate(@" +let t (count int32, string) = (4, ""x"") +t.count +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(4, result.Value); + } + + [Fact] + public void NamedAndUnnamed_SameShape_AssignBothDirections() + { + var result = EmittedOracle.Evaluate(@" +let named (line int32, column int32) = (3, 5) +let unnamed (int32, int32) = named +let back (line int32, column int32) = unnamed +back.line + unnamed.Item2 +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(8, result.Value); + } + + [Fact] + public void RenamedAssignment_WarnsGS0541_StillCompiles() + { + var result = EmittedOracle.Evaluate(@" +let pos (line int32, column int32) = (3, 5) +let renamed (row int32, col int32) = pos +renamed.row +"); + Assert.Equal(2, result.Diagnostics.Count(d => d.Id == "GS0541")); + Assert.DoesNotContain(result.Diagnostics, d => d.Severity == DiagnosticSeverity.Error); + Assert.Equal(3, result.Value); + } + + [Fact] + public void FunctionReturnType_NamedElements_AccessAtCallSite() + { + var result = EmittedOracle.Evaluate(@" +func divmod(a int32, b int32) (quotient int32, remainder int32) { + return a / b, a % b +} +let r = divmod(10, 3) +r.quotient * 10 + r.remainder +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(31, result.Value); + } + + [Fact] + public void NestedNamedTuple_ElementNamesResolveAtEachLevel() + { + var result = EmittedOracle.Evaluate(@" +let t (inner (a int32, b int32), tag string) = ((a: 1, b: 2), ""x"") +t.inner.b +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(2, result.Value); + } + + [Fact] + public void NamedVsUnnamed_TupleEquality_ComparesByShape() + { + // ADR-0171 coupling constraint: the equality desugar keys on shape, + // never symbol identity — a named and an unnamed same-shape tuple + // compare element-wise. + var result = EmittedOracle.Evaluate(@" +let named (line int32, column int32) = (3, 5) +named == (3, 5) +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(true, result.Value); + } + + [Fact] + public void GenericSubstitution_PreservesElementNames() + { + var result = EmittedOracle.Evaluate(@" +func first[T](pair (val T, ok bool)) T { + return pair.val +} +first((val: 42, ok: true)) +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(42, result.Value); + } + + [Fact] + public void CorrectPositionItemN_IsAllowed() + { + var result = EmittedOracle.Evaluate(@" +let t (Item1 int32, Item2 int32) = (1, 2) +t.Item1 + t.Item2 +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(3, result.Value); + } + + [Fact] + public void DeconstructionOfNamedTuple_StillPositional() + { + var result = EmittedOracle.Evaluate(@" +let pos (line int32, column int32) = (3, 5) +let (a, b) = pos +a * 10 + b +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(35, result.Value); + } + + [Fact] + public void DuplicateName_ReportsGS0540() + { + var diagnostic = Assert.Single(Errors(@" +let t (line int32, line int32) = (1, 2) +"), d => d.Id == "GS0540"); + Assert.Equal("line", diagnostic.Location.Text.ToString(diagnostic.Location.Span)); + } + + [Fact] + public void DuplicateLabel_InLiteral_ReportsGS0540() + { + Assert.Contains(Errors(@" +let t = (line: 1, line: 2) +"), d => d.Id == "GS0540"); + } + + [Fact] + public void WrongPositionItemN_ReportsGS0542() + { + Assert.Contains(Errors(@" +let t (Item2 int32, x int32) = (1, 2) +"), d => d.Id == "GS0542"); + } + + [Fact] + public void RestName_ReportsGS0542() + { + Assert.Contains(Errors(@" +let t = (Rest: 1, x: 2) +"), d => d.Id == "GS0542"); + } + + [Fact] + public void SingleLabeledElement_ReportsGS0543_RecoversAsGrouping() + { + var diagnostics = Errors(@" +let x = (line: 1) +let y = x + 1 +"); + var diagnostic = Assert.Single(diagnostics, d => d.Id == "GS0543"); + Assert.Equal("line", diagnostic.Location.Text.ToString(diagnostic.Location.Span)); + + // Recovery: `(line: 1)` binds as parenthesized `1`, so `x + 1` is valid + // and GS0543 is the only error. + Assert.Single(diagnostics); + } + + [Fact] + public void SingleNamedTypeElement_ReportsGS0543_RecoversAsGrouping() + { + var diagnostics = Errors(@" +let x (line int32) = 1 +let y = x + 1 +"); + Assert.Single(diagnostics, d => d.Id == "GS0543"); + Assert.Single(diagnostics); + } + + [Fact] + public void NamedTupleType_DisplayName_IsNameFirst() + { + var source = @" +let pos (line int32, column int32) = (1, 2) +let mismatch string = pos +"; + var diagnostic = Assert.Single(Errors(source)); + Assert.Contains("(line int32, column int32)", diagnostic.Message); + } + + [Fact] + public void UnnamedTupleGrammar_Unchanged() + { + var result = EmittedOracle.Evaluate(@" +import System.Collections.Generic +let t (int32, List[int32], []string, (int32) -> int32) = (1, List[int32](), []string{""a""}, (x int32) -> x) +t.Item1 +"); + Assert.Empty(result.Diagnostics); + Assert.Equal(1, result.Value); + } + + private static IReadOnlyList Errors(string source) + { + var tree = SyntaxTree.Parse(SourceText.From(source)); + var compilation = new Compilation(tree); + using var peStream = new MemoryStream(); + return compilation.Emit(peStream).Diagnostics + .Where(d => d.Severity == DiagnosticSeverity.Error) + .ToList(); + } +} diff --git a/test/Core.Tests/CodeAnalysis/Syntax/Issue1675SyntaxNodeChildEnumerationTests.cs b/test/Core.Tests/CodeAnalysis/Syntax/Issue1675SyntaxNodeChildEnumerationTests.cs index a7e65ad0f..07db6a5e0 100644 --- a/test/Core.Tests/CodeAnalysis/Syntax/Issue1675SyntaxNodeChildEnumerationTests.cs +++ b/test/Core.Tests/CodeAnalysis/Syntax/Issue1675SyntaxNodeChildEnumerationTests.cs @@ -55,6 +55,9 @@ public class Issue1675SyntaxNodeChildEnumerationTests // issue #3096: native array spread element "package p\nfunc F(a []int32) {\n let b = []int32{0, ...a, 9}\n}\n", + // ADR-0172: named tuple elements — labeled literal + named type clause + "package p\nfunc F() {\n let pos (line int32, column int32) = (line: 3, column: 5)\n let l = pos.line\n}\n", + // generic static receiver "package p\nstruct Box[T] { shared { func Make(x int32) int32 { return x } } }\nclass C { func F() int32 { return Box[int32?].Make(5) } }\n", diff --git a/test/Core.Tests/CoverageMatrix/coverage-matrix.golden.txt b/test/Core.Tests/CoverageMatrix/coverage-matrix.golden.txt index 67bc954a4..f1589e68b 100644 --- a/test/Core.Tests/CoverageMatrix/coverage-matrix.golden.txt +++ b/test/Core.Tests/CoverageMatrix/coverage-matrix.golden.txt @@ -169,6 +169,7 @@ NameOfExpression NamedArgumentExpression NamedDeconstructionField NamedDeconstructionStatement +NamedTupleElement NilKeyword NotPattern NullCoalescingAssignmentStatement diff --git a/website/docs/ref/diagnostics.md b/website/docs/ref/diagnostics.md index a5b5ecfa6..6a9188fd9 100644 --- a/website/docs/ref/diagnostics.md +++ b/website/docs/ref/diagnostics.md @@ -871,6 +871,24 @@ than a constant `false`, matching C#. |---|---|---|---| | GS0539 | Error | `Tuple equality requires operands of equal arity: '' has arity but '' has arity .` | `(1, 2) == (1, 2, 3)` | +## Named tuple elements (GS0540, GS0541, GS0542, GS0543) + +ADR-0172: tuple types may name elements +name-first — `(line int32, column int32)` — and tuple literals may label +them — `(line: 1, column: 2)`. Names are metadata over the positional shape: +same-shape tuples differing only in names are identity-convertible, with a +warning where names disagree position-wise. `ItemN` at the wrong position and +`Rest` (used by the CLR ValueTuple encoding) are reserved; a name on a +parenthesized single element is an error because `(T)` is grouping, not a +1-tuple (issue #3315). + +| ID | Severity | Message | Example | +|---|---|---|---| +| GS0540 | Error | `Tuple element name '' is used more than once.` | `(line int32, line int32)` | +| GS0541 | Warning | `Tuple element name '' is ignored because the target type names this position ''.` | `let r (row int32, col int32) = pos` where `pos` is `(line int32, column int32)` | +| GS0542 | Error | `Tuple element name '' is reserved.` | `(Item2 int32, x int32)`; `(Rest: 1, x: 2)` | +| GS0543 | Error | `An element name is only valid inside a tuple of two or more elements; a parenthesized single element is grouping.` | `(line: 1)`; `let x (line int32) = 1` | + ## Pattern variable outside its definitely-assigned region (GS0532) ADR-0166: a designation in a boolean `is` diff --git a/website/docs/ref/feature-matrix.md b/website/docs/ref/feature-matrix.md index db76c4751..cd7a2f5b1 100644 --- a/website/docs/ref/feature-matrix.md +++ b/website/docs/ref/feature-matrix.md @@ -32,7 +32,7 @@ This matrix summarizes current feature support in the emitter, which every drive | Nullable `T?`, `nil`, `!!`, `??`, `?.`, `?[i]` | Supported | Supported | The evaluator threw on a nil `!!`; `?[i]` short-circuited indexing to `nil` when the receiver was nil. | | Arrays and slices | Supported | Supported | Slices are backed by arrays; `append` copies. `len` / `cap` / `append` require `import Gsharp.Extensions.Go` (GS0317); the .NET-idiomatic alternative is `.Length` and (for mutable lists) `List[T].Add`. | | Maps | Supported | Supported | Backed by `Dictionary[K,V]`; `delete` and `len` are implemented. Both require `import Gsharp.Extensions.Go` (GS0317); .NET-idiomatic alternatives are `.Remove(k)` and `.Count`. Iterable with range `for`: `for k, v in m` destructures entries, `for kv in m` yields `KeyValuePair[K,V]`; order unspecified. | -| Tuples and multi-return | Supported | Supported | Multi-value return syntax is represented as tuple literals. Tuple `==` / `!=` compare element-wise with short-circuit, single-evaluation semantics (ADR-0171). | +| Tuples and multi-return | Supported | Supported | Multi-value return syntax is represented as tuple literals. Tuple `==` / `!=` compare element-wise with short-circuit, single-evaluation semantics (ADR-0171). Named elements `(line int32, column int32)` / `(line: 1, column: 2)` resolve positionally; names are metadata (ADR-0172). | | Struct literals | Supported | Supported | Field initialization and field access are implemented. | | Data classes, data structs, `with`/copy | Supported | Supported | `data class` (reference) and `data struct` (value) synthesise equality, `with`-copy, and deconstruction. The `record` keyword is not supported; migrate to `data struct` (preserves value semantics) or `data class` (reference semantics). | | Inline structs | Supported | Supported | Exactly one field; participates in structural equality. | diff --git a/website/docs/ref/spec.md b/website/docs/ref/spec.md index ab744cb3d..c01dc242c 100644 --- a/website/docs/ref/spec.md +++ b/website/docs/ref/spec.md @@ -171,7 +171,7 @@ Integral types are `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int64 ### Object and nil -`object` is the universal upper bound. Values backed by CLR types and user value types can implicitly convert or box to `object`; explicit conversions can unbox to CLR value types. Nullable types are written by appending `?` to a type clause. `nil` converts implicitly to nullable types but not to non-nullable types. **Nil comparison** (`x == nil` / `x != nil`, either operand order) is defined once, for **every reference-backed builtin type** — `object`, classes, interfaces, function and delegate types, `sequence[T]`/`asyncSequence[T]`, `map[K,V]`, `[]T`, `[N]T`, `[,]T` (and higher ranks), and `chan T` — with or without a `?` annotation; comparison is the interop-boundary observation tool. The comparison surface is comparison-*only*: assigning `nil` into a bare (non-`?`) slot of any of these types remains an error. A reference upcast lifts through nullable annotations: when `U` is a base class or implemented interface of `T`, both `T → U?` and `T? → U?` are implicit reference conversions — for reference types a nullable annotation shares the underlying reference representation, so the lifted upcast is a metadata-only no-op that maps `nil` to `nil` and reference-upcasts a non-null value. The narrowing `T? → U` (dropping the nullable annotation) is not implicit and still requires `!!`. Postfix `!!` asserts non-null; applying `!!` to a value that is actually nil fails at runtime with the underlying CLR exception (`System.InvalidOperationException` when unwrapping a value-type `T?`, `System.NullReferenceException` when dereferencing a nil reference) — the compiled semantics are the language contract. `??` is null coalescing. A **tuple type** `(T1, …, Tn)` converts implicitly to `(U1, …, Un)` when both are tuple types of the same arity and **each** element `Ti → Ui` has an implicit conversion (identity, reference/interface upcast, the lifted nullable-reference upcast above, numeric widening, boxing, …) — element-wise, mirroring C# §10.2.13. So `(A, Derived)` converts to `(A, Base)` and `(A, Derived?)` to `(A, Base?)`. The conversion applies in argument, assignment/`let`-target, and return positions. Because the source and target `System.ValueTuple<…>` are distinct CLR instantiations, the conversion is materialised by rebuilding the destination tuple from the per-element converted values rather than by reinterpreting the source. A tuple with any element lacking an implicit conversion (a downcast such as `(A, Base) → (A, Derived)`, or an unrelated pair such as `(A, int32) → (A, string)`) or a differing arity is not convertible and requires the elements to match. **Tuple equality** (ADR-0171): `==` and `!=` are defined whenever both operands are tuple types of the same arity (differing arity is error GS0539; a tuple against a non-tuple is GS0129). The comparison is element-wise: each operand is evaluated exactly once, then the element pairs are compared left-to-right through the ordinary equality rules (user-declared element operators, string equality, lifted nullable elements, nested tuples recursively), folded with short-circuiting `&&` (`!=` folds the element `!=` comparisons with `||`) — mirroring C# §12.12.10. An element pair with no defined equality reports GS0129 with the element types. The result is `bool`. +`object` is the universal upper bound. Values backed by CLR types and user value types can implicitly convert or box to `object`; explicit conversions can unbox to CLR value types. Nullable types are written by appending `?` to a type clause. `nil` converts implicitly to nullable types but not to non-nullable types. **Nil comparison** (`x == nil` / `x != nil`, either operand order) is defined once, for **every reference-backed builtin type** — `object`, classes, interfaces, function and delegate types, `sequence[T]`/`asyncSequence[T]`, `map[K,V]`, `[]T`, `[N]T`, `[,]T` (and higher ranks), and `chan T` — with or without a `?` annotation; comparison is the interop-boundary observation tool. The comparison surface is comparison-*only*: assigning `nil` into a bare (non-`?`) slot of any of these types remains an error. A reference upcast lifts through nullable annotations: when `U` is a base class or implemented interface of `T`, both `T → U?` and `T? → U?` are implicit reference conversions — for reference types a nullable annotation shares the underlying reference representation, so the lifted upcast is a metadata-only no-op that maps `nil` to `nil` and reference-upcasts a non-null value. The narrowing `T? → U` (dropping the nullable annotation) is not implicit and still requires `!!`. Postfix `!!` asserts non-null; applying `!!` to a value that is actually nil fails at runtime with the underlying CLR exception (`System.InvalidOperationException` when unwrapping a value-type `T?`, `System.NullReferenceException` when dereferencing a nil reference) — the compiled semantics are the language contract. `??` is null coalescing. A **tuple type** `(T1, …, Tn)` converts implicitly to `(U1, …, Un)` when both are tuple types of the same arity and **each** element `Ti → Ui` has an implicit conversion (identity, reference/interface upcast, the lifted nullable-reference upcast above, numeric widening, boxing, …) — element-wise, mirroring C# §10.2.13. So `(A, Derived)` converts to `(A, Base)` and `(A, Derived?)` to `(A, Base?)`. The conversion applies in argument, assignment/`let`-target, and return positions. Because the source and target `System.ValueTuple<…>` are distinct CLR instantiations, the conversion is materialised by rebuilding the destination tuple from the per-element converted values rather than by reinterpreting the source. A tuple with any element lacking an implicit conversion (a downcast such as `(A, Base) → (A, Derived)`, or an unrelated pair such as `(A, int32) → (A, string)`) or a differing arity is not convertible and requires the elements to match. **Tuple equality** (ADR-0171): `==` and `!=` are defined whenever both operands are tuple types of the same arity (differing arity is error GS0539; a tuple against a non-tuple is GS0129). The comparison is element-wise: each operand is evaluated exactly once, then the element pairs are compared left-to-right through the ordinary equality rules (user-declared element operators, string equality, lifted nullable elements, nested tuples recursively), folded with short-circuiting `&&` (`!=` folds the element `!=` comparisons with `||`) — mirroring C# §12.12.10. An element pair with no defined equality reports GS0129 with the element types. The result is `bool`. **Named tuple elements** (ADR-0172): a tuple type may name elements name-first — `(line int32, column int32)` — and a tuple literal may label elements — `(line: 1, column: 2)` (partial naming allowed; a lone labeled or named single element is GS0543 because `(T)` is grouping). Names are metadata over the positional shape: a declared name resolves member access to its position (`pos.line` ≡ `pos.Item1`, and `ItemN`/`.N` stay valid), same-shape tuples differing only in names are identical types related by an identity conversion (a position-wise name disagreement warns GS0541), equality ignores names, and generic substitution preserves them. Duplicate names are GS0540; `ItemN` at the wrong position and `Rest` are reserved (GS0542). ### Arrays and slices @@ -1816,7 +1816,7 @@ TypeClause ::= identifier ('.' identifier)* TypeArgList? '?'? | ArrayTypePrefix '?'? identifier ('.' identifier)* '?'? | '(' TypeClause ')' '?'? (* parenthesized (grouping) type clause; the trailing '?' marks the WHOLE inner type nullable, e.g. '(chan int32)?', *) - | '(' TypeClause (',' TypeClause)+ ')' '?'? (* tuple type *) + | '(' TupleTypeElement (',' TupleTypeElement)+ ')' '?'? (* tuple type; TupleTypeElement ::= identifier? TypeClause (ADR-0172) *) | '(' FnTypeParamList? ')' '->' TypeClause (* arrow function type, ; `?` in TypeClause is return nullability *) | '(' '(' FnTypeParamList? ')' '->' TypeClause ')' '?'? (* parenthesized arrow function type, *) | 'async' '(' FnTypeParamList? ')' '->' TypeClause @@ -2009,7 +2009,8 @@ CheckedExpression ::= ('checked' | 'unchecked') '(' Expression ')' (* : IfExpression ::= 'if' Expression Block ('else' (IfExpression | Block))? (* if-as-expression, *) IfLetExpression ::= 'if' LetBindingList ('&&' Expression)? Block 'else' (IfExpression | IfLetExpression | Block) (* if-let-as-expression *) -TupleLiteral ::= '(' Expression ',' Expression (',' Expression)* ')' +TupleLiteral ::= '(' TupleLiteralElement ',' TupleLiteralElement (',' TupleLiteralElement)* ')' +TupleLiteralElement ::= (identifier ':')? Expression (* ADR-0172 labeled element *) Arguments ::= Argument (',' Argument)* Argument ::= identifier ':' (RefArgument | Expression) | RefArgument From d8c0b5965b59dc7583b500a80f6ffaaac3f67ba7 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 28 Aug 2026 15:50:12 -0700 Subject: [PATCH 2/5] =?UTF-8?q?G#:=20named=20tuple=20elements=20=E2=80=94?= =?UTF-8?q?=20Phase=20B=20metadata=20interop=20(ADR-0172,=20#3501)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emit: gsc synthesizes [TupleElementNamesAttribute(string[])] — the C# flattened DFS pre-order encoding, null at unnamed positions, omitted when fully unnamed — on tuple-typed returns (sequence-0 Param row is now also minted for a named-tuple return), parameters, fields, and properties, via the same CustomAttributeEncoder hook points the NullableAttribute machinery uses. Arity ≥ 8 contributes logical elements only (TRest invisible). An async kickoff's Task<(a, b)> return uses the tuple's own array — wrappers contribute no entries. Import: TupleElementNamesReader decodes the attribute from referenced assemblies at the four ClrNullability member-type entry points and applies names by rebuilding each tuple occurrence in the mapped symbol (imported ValueTuple generic arguments are flattened to TupleTypeSymbol first; closed generics rebuild as constructed imported symbols so receiver projection sees the names). NullabilityAnnotatedTypeSymbol's argument derivation transfers names from its symbolic base, and a named-tuple type argument now counts as substitutable, so `list[i].line` works through generic receivers. Both interop directions verified: C#-compiled named tuples surface in G# (incl. List elements and arity-9), and csc/Roslyn decodes gsc's blob (a C# consumer binds `pos.line` against a G# assembly) — the strongest encoding-parity witness. Also: the speculative generic-call-site scanner accepts named tuple elements in type-argument position (`List[(line int32, column int32)]()` previously fell back to an expression parse). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs --- .../Emit/CustomAttributeEncoder.cs | 48 +++ src/Core/CodeAnalysis/Emit/FunctionEmitter.cs | 23 +- .../Emit/ReflectionMetadataEmitter.cs | 1 + .../Emit/TupleElementNamesBuilder.cs | 123 ++++++++ src/Core/CodeAnalysis/Emit/TypeDefEmitter.cs | 6 + .../CodeAnalysis/Emit/WellKnownReferences.cs | 37 +++ .../CodeAnalysis/Symbols/ClrNullability.cs | 34 ++- .../Symbols/ImportedTypeSymbol.cs | 7 +- .../Symbols/NullabilityAnnotatedTypeSymbol.cs | 37 ++- .../Symbols/TupleElementNamesReader.cs | 233 +++++++++++++++ src/Core/CodeAnalysis/Symbols/TypeSymbol.cs | 13 + .../Syntax/Parser.Expressions.Creation.cs | 46 +++ .../Emit/NamedTupleMetadataEmitTests.cs | 279 ++++++++++++++++++ .../Baselines/refactoring-baseline.json | 1 + .../refactoring-baseline.json.actual | 143 +++++++++ 15 files changed, 1018 insertions(+), 13 deletions(-) create mode 100644 src/Core/CodeAnalysis/Emit/TupleElementNamesBuilder.cs create mode 100644 src/Core/CodeAnalysis/Symbols/TupleElementNamesReader.cs create mode 100644 test/Compiler.Tests/Emit/NamedTupleMetadataEmitTests.cs create mode 100644 test/Core.Tests/Baselines/refactoring-baseline.json.actual diff --git a/src/Core/CodeAnalysis/Emit/CustomAttributeEncoder.cs b/src/Core/CodeAnalysis/Emit/CustomAttributeEncoder.cs index 1080b98f3..d73677e0a 100644 --- a/src/Core/CodeAnalysis/Emit/CustomAttributeEncoder.cs +++ b/src/Core/CodeAnalysis/Emit/CustomAttributeEncoder.cs @@ -226,6 +226,51 @@ public void EmitNullableAttributeOnField(FieldDefinitionHandle fieldHandle, Type { this.EmitNullableAttributeOnEntity(fieldHandle, flags); } + + // ADR-0172 Phase B: every per-field slot-metadata pass also carries + // tuple element names when the declared type has any. + this.EmitTupleElementNamesAttribute(fieldHandle, type); + } + + /// + /// ADR-0172 Phase B: emits + /// System.Runtime.CompilerServices.TupleElementNamesAttribute(string[]) + /// on a Param / Field / Property row when declares + /// at least one tuple element name anywhere in its tree (flattened DFS + /// pre-order, null entries for unnamed positions — the C# encoding). + /// Silently no-ops when no name exists or the attribute type can't be + /// resolved (very old TFMs). + /// + /// The metadata row to attach the attribute to. + /// The declared parameter / return / field / property type. + public void EmitTupleElementNamesAttribute(EntityHandle parent, TypeSymbol type) + { + var names = TupleElementNamesBuilder.Build(type); + if (names.IsDefaultOrEmpty) + { + return; + } + + var ctorRef = this.wellKnown.GetTupleElementNamesAttributeCtorRef(); + if (ctorRef.IsNil) + { + return; + } + + var valueBlob = new BlobBuilder(); + valueBlob.WriteUInt16(0x0001); + valueBlob.WriteInt32(names.Length); + foreach (var name in names) + { + valueBlob.WriteSerializedString(name); + } + + valueBlob.WriteUInt16(0); + + this.emitCtx.Metadata.AddCustomAttribute( + parent: parent, + constructor: ctorRef, + value: this.emitCtx.Metadata.GetOrAddBlob(valueBlob)); } /// @@ -245,6 +290,9 @@ public void EmitNullableAttributeOnProperty(PropertyDefinitionHandle propertyHan { this.EmitNullableAttributeOnEntity(propertyHandle, flags); } + + // ADR-0172 Phase B: see EmitNullableAttributeOnField. + this.EmitTupleElementNamesAttribute(propertyHandle, type); } /// diff --git a/src/Core/CodeAnalysis/Emit/FunctionEmitter.cs b/src/Core/CodeAnalysis/Emit/FunctionEmitter.cs index 3826a4aa6..fe84810ae 100644 --- a/src/Core/CodeAnalysis/Emit/FunctionEmitter.cs +++ b/src/Core/CodeAnalysis/Emit/FunctionEmitter.cs @@ -685,8 +685,16 @@ private FunctionParameterMetadata EmitParameterMetadata( !returnFlags.IsDefaultOrEmpty && !(returnFlags.Length == 1 && returnFlags[0] == effectiveDefault); + // ADR-0172 Phase B: a tuple-typed return with declared element names + // also needs the sequence-0 Param row to carry + // [TupleElementNamesAttribute]. The flattened array for an async + // kickoff's `Task<(a, b)>` equals the tuple's own array (only tuples + // contribute entries; wrappers are traversed transparently), so + // `function.Type` is the right input in both cases. + var returnTupleNames = TupleElementNamesBuilder.Build(function.Type); + ParameterHandle? returnParamHandle = null; - if (hasReturnAttributes || returnNeedsNullableAttribute) + if (hasReturnAttributes || returnNeedsNullableAttribute || !returnTupleNames.IsDefaultOrEmpty) { returnParamHandle = this.emitCtx.Metadata.AddParameter( attributes: ParameterAttributes.None, @@ -881,6 +889,19 @@ private void EmitFunctionAttributes( this.outer.customAttrEncoder.EmitNullableAttributeOnParameter(paramHandle, paramFlags); } + // ADR-0172 Phase B: stamp [TupleElementNamesAttribute] on the return + // row and on every parameter whose declared type carries tuple + // element names, so C# consumers (and re-imports) see `r.line`. + if (parameterMetadata.ReturnParameterHandle is { } returnHandleForTupleNames) + { + this.outer.customAttrEncoder.EmitTupleElementNamesAttribute(returnHandleForTupleNames, function.Type); + } + + foreach (var (paramSym, paramHandle, _) in parameterMetadata.ParameterHandles) + { + this.outer.customAttrEncoder.EmitTupleElementNamesAttribute(paramHandle, paramSym.Type); + } + // Issue #792 / ADR-0084. Stamp [ExtensionAttribute] on every G#- // authored extension MethodDef so C#/F# call-site lookup picks them // up via the standard ECMA-334 §13.6.9 extension-method discovery diff --git a/src/Core/CodeAnalysis/Emit/ReflectionMetadataEmitter.cs b/src/Core/CodeAnalysis/Emit/ReflectionMetadataEmitter.cs index 4afa1a79b..bc689ecd2 100644 --- a/src/Core/CodeAnalysis/Emit/ReflectionMetadataEmitter.cs +++ b/src/Core/CodeAnalysis/Emit/ReflectionMetadataEmitter.cs @@ -1135,6 +1135,7 @@ private void ResolveCoreTypesAndWireEmitters() handle => this.customAttrEncoder.EmitNullableContextAttributeOnType(handle, NullableFlagsBuilder.NotAnnotated), this.customAttrEncoder.EmitNullableAttributeOnField, this.customAttrEncoder.EmitNullableAttributeOnParameter, + (paramHandle, paramType) => this.customAttrEncoder.EmitTupleElementNamesAttribute(paramHandle, paramType), this.customAttrEncoder.EmitIsReadOnlyAttributeOnParameter, this.customAttrEncoder.EmitParamArrayAttributeOnParameter, this.memberRefs.GetCtorReference, diff --git a/src/Core/CodeAnalysis/Emit/TupleElementNamesBuilder.cs b/src/Core/CodeAnalysis/Emit/TupleElementNamesBuilder.cs new file mode 100644 index 000000000..7fbfedc53 --- /dev/null +++ b/src/Core/CodeAnalysis/Emit/TupleElementNamesBuilder.cs @@ -0,0 +1,123 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System.Collections.Immutable; +using GSharp.Core.CodeAnalysis.Symbols; + +namespace GSharp.Core.CodeAnalysis.Emit; + +/// +/// ADR-0172 Phase B: computes the C#-compatible +/// [System.Runtime.CompilerServices.TupleElementNamesAttribute] +/// string[] for a G# — a DFS pre-order walk +/// of the type tree in which every tuple occurrence contributes its arity of +/// entries (declared name, or for an unnamed position) +/// before its element subtrees are visited. Non-tuple composites (arrays, +/// slices, maps, sequences, channels, nullable wrappers, generic +/// instantiations, function types) contribute no entries of their own but are +/// traversed, matching Roslyn's TupleNamesEncoder. Arity ≥ 8 tuples +/// contribute their LOGICAL elements only — the CLR's synthesized +/// TRest nesting is invisible, exactly as in C#. The attribute is +/// only emitted when at least one entry is non-null; +/// returns an empty array otherwise. +/// +internal static class TupleElementNamesBuilder +{ + /// + /// Computes the flattened element-name array for the supplied type. + /// Returns an empty array when no tuple position anywhere in the type + /// declares a name (no attribute needed). + /// + /// The parameter / return / field / property type to inspect. + /// The names array — possibly empty; never . + internal static ImmutableArray Build(TypeSymbol type) + { + var builder = ImmutableArray.CreateBuilder(); + var anyName = false; + Append(type, builder, ref anyName); + return anyName ? builder.ToImmutable() : ImmutableArray.Empty; + } + + private static void Append(TypeSymbol type, ImmutableArray.Builder builder, ref bool anyName) + { + switch (type) + { + case TupleTypeSymbol tuple: + for (var i = 0; i < tuple.Arity; i++) + { + var name = tuple.HasNames ? tuple.ElementNames[i] : null; + builder.Add(name); + anyName |= name != null; + } + + foreach (var element in tuple.ElementTypes) + { + Append(element, builder, ref anyName); + } + + break; + + case NullableTypeSymbol nullable: + Append(nullable.UnderlyingType, builder, ref anyName); + break; + + case ArrayTypeSymbol array: + Append(array.ElementType, builder, ref anyName); + break; + + case SliceTypeSymbol slice: + Append(slice.ElementType, builder, ref anyName); + break; + + case RectangularArrayTypeSymbol rectangular: + Append(rectangular.ElementType, builder, ref anyName); + break; + + case MapTypeSymbol map: + Append(map.KeyType, builder, ref anyName); + Append(map.ValueType, builder, ref anyName); + break; + + case SequenceTypeSymbol sequence: + Append(sequence.ElementType, builder, ref anyName); + break; + + case AsyncSequenceTypeSymbol asyncSequence: + Append(asyncSequence.ElementType, builder, ref anyName); + break; + + case ChannelTypeSymbol channel: + Append(channel.ElementType, builder, ref anyName); + break; + + case FunctionTypeSymbol function: + foreach (var parameterType in function.ParameterTypes) + { + Append(parameterType, builder, ref anyName); + } + + Append(function.ReturnType, builder, ref anyName); + break; + + case StructSymbol { TypeArguments.IsDefaultOrEmpty: false } aggregate: + foreach (var argument in aggregate.TypeArguments) + { + Append(argument, builder, ref anyName); + } + + break; + + case ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } imported: + foreach (var argument in imported.TypeArguments) + { + Append(argument, builder, ref anyName); + } + + break; + + default: + break; + } + } +} diff --git a/src/Core/CodeAnalysis/Emit/TypeDefEmitter.cs b/src/Core/CodeAnalysis/Emit/TypeDefEmitter.cs index 79a582ad8..1e1ef6ba3 100644 --- a/src/Core/CodeAnalysis/Emit/TypeDefEmitter.cs +++ b/src/Core/CodeAnalysis/Emit/TypeDefEmitter.cs @@ -100,6 +100,7 @@ internal sealed class TypeDefEmitter private readonly Action emitNullableContextOnType; private readonly Action emitNullableAttributeOnField; private readonly Action> emitNullableAttributeOnParameter; + private readonly Action emitTupleElementNamesOnParameter; private readonly Action emitIsReadOnlyAttributeOnParameter; private readonly Action emitParamArrayAttributeOnParameter; private readonly Func getCtorReference; @@ -128,6 +129,7 @@ public TypeDefEmitter( Action emitNullableContextOnType, Action emitNullableAttributeOnField, Action> emitNullableAttributeOnParameter, + Action emitTupleElementNamesOnParameter, Action emitIsReadOnlyAttributeOnParameter, Action emitParamArrayAttributeOnParameter, Func getCtorReference, @@ -155,6 +157,7 @@ public TypeDefEmitter( this.emitNullableContextOnType = emitNullableContextOnType ?? throw new ArgumentNullException(nameof(emitNullableContextOnType)); this.emitNullableAttributeOnField = emitNullableAttributeOnField ?? throw new ArgumentNullException(nameof(emitNullableAttributeOnField)); this.emitNullableAttributeOnParameter = emitNullableAttributeOnParameter ?? throw new ArgumentNullException(nameof(emitNullableAttributeOnParameter)); + this.emitTupleElementNamesOnParameter = emitTupleElementNamesOnParameter ?? throw new ArgumentNullException(nameof(emitTupleElementNamesOnParameter)); this.emitIsReadOnlyAttributeOnParameter = emitIsReadOnlyAttributeOnParameter ?? throw new ArgumentNullException(nameof(emitIsReadOnlyAttributeOnParameter)); this.emitParamArrayAttributeOnParameter = emitParamArrayAttributeOnParameter ?? throw new ArgumentNullException(nameof(emitParamArrayAttributeOnParameter)); this.getCtorReference = getCtorReference ?? throw new ArgumentNullException(nameof(getCtorReference)); @@ -1371,6 +1374,9 @@ private void EmitConstructorParameterNullability(ParameterHandle paramHandle, Pa { this.emitNullableAttributeOnParameter(paramHandle, nullableFlags); } + + // ADR-0172 Phase B: parameter rows also carry tuple element names. + this.emitTupleElementNamesOnParameter(paramHandle, parameter.Type); } /// diff --git a/src/Core/CodeAnalysis/Emit/WellKnownReferences.cs b/src/Core/CodeAnalysis/Emit/WellKnownReferences.cs index 06e56907a..3308eca88 100644 --- a/src/Core/CodeAnalysis/Emit/WellKnownReferences.cs +++ b/src/Core/CodeAnalysis/Emit/WellKnownReferences.cs @@ -103,6 +103,7 @@ internal sealed class WellKnownReferences // MethodDef itself so C# nullable-flow analysis treats `T?` reference // parameters as annotated instead of inferring the assembly default. private MemberReferenceHandle? nullableAttributeByteCtorRef; + private MemberReferenceHandle? tupleElementNamesAttributeCtorRef; private MemberReferenceHandle? nullableAttributeByteArrayCtorRef; private MemberReferenceHandle? nullableContextAttributeByteCtorRef; @@ -653,6 +654,42 @@ public MemberReferenceHandle GetNullableAttributeByteArrayCtorRef() return this.nullableAttributeByteArrayCtorRef.Value; } + /// + /// ADR-0172 Phase B: returns the cached MemberRef for + /// System.Runtime.CompilerServices.TupleElementNamesAttribute(string[]) + /// — the C#-compatible carrier of tuple element names on tuple-typed + /// parameters, returns, fields, and properties. + /// + /// + /// The cached , or + /// when the attribute can't be resolved from + /// the reference closure (very old TFMs). + /// + public MemberReferenceHandle GetTupleElementNamesAttributeCtorRef() + { + if (this.tupleElementNamesAttributeCtorRef.HasValue) + { + return this.tupleElementNamesAttributeCtorRef.Value; + } + + if (!this.emitCtx.References.TryResolveType("System.Runtime.CompilerServices.TupleElementNamesAttribute", requireExternalVisibility: false, out var attrType)) + { + return default; + } + + var attrTypeRef = this.getTypeReference(attrType); + + var ctorSig = new BlobBuilder(); + new BlobEncoder(ctorSig).MethodSignature(isInstanceMethod: true) + .Parameters(1, r => r.Void(), p => p.AddParameter().Type().SZArray().String()); + + this.tupleElementNamesAttributeCtorRef = this.emitCtx.Metadata.AddMemberReference( + attrTypeRef, + this.emitCtx.Metadata.GetOrAddString(".ctor"), + this.emitCtx.Metadata.GetOrAddBlob(ctorSig)); + return this.tupleElementNamesAttributeCtorRef.Value; + } + /// /// Issue #834: returns the cached MemberRef for /// NullableContextAttribute(byte). Stamped on a MethodDef row to diff --git a/src/Core/CodeAnalysis/Symbols/ClrNullability.cs b/src/Core/CodeAnalysis/Symbols/ClrNullability.cs index 1fd3df273..7f83e9fa1 100644 --- a/src/Core/CodeAnalysis/Symbols/ClrNullability.cs +++ b/src/Core/CodeAnalysis/Symbols/ClrNullability.cs @@ -48,7 +48,10 @@ public static TypeSymbol GetPropertyTypeSymbol(PropertyInfo property) // declaring type to pick up any `[NullableContextAttribute]` // fallback (matches the C# emit shape used by csc for // e.g. `DirectoryInfo.Parent`). - return ApplyReferenceNullabilityFull(baseSymbol, property.PropertyType, property, property.DeclaringType); + // ADR-0172 Phase B: surface imported tuple element names. + return TupleElementNamesReader.ApplyNames( + ApplyReferenceNullabilityFull(baseSymbol, property.PropertyType, property, property.DeclaringType), + property); } /// @@ -66,7 +69,9 @@ public static TypeSymbol GetPropertyTypeSymbol(PropertyInfo property) public static TypeSymbol GetPropertyElementTypeSymbol(PropertyInfo property, Type elementType) { var baseSymbol = TypeSymbol.FromClrType(elementType); - return ApplyReferenceNullabilityFull(baseSymbol, elementType, property, property.DeclaringType); + return TupleElementNamesReader.ApplyNames( + ApplyReferenceNullabilityFull(baseSymbol, elementType, property, property.DeclaringType), + property); } /// @@ -78,7 +83,11 @@ public static TypeSymbol GetPropertyElementTypeSymbol(PropertyInfo property, Typ public static TypeSymbol GetFieldTypeSymbol(FieldInfo field) { var baseSymbol = TypeSymbol.FromClrType(field.FieldType); - return ApplyReferenceNullabilityFull(baseSymbol, field.FieldType, field, field.DeclaringType); + + // ADR-0172 Phase B: surface imported tuple element names. + return TupleElementNamesReader.ApplyNames( + ApplyReferenceNullabilityFull(baseSymbol, field.FieldType, field, field.DeclaringType), + field); } /// @@ -96,12 +105,16 @@ public static TypeSymbol GetReturnTypeSymbol(MethodInfo method) { var baseSymbol = TypeSymbol.FromClrType(method.ReturnType); var definition = GetMetadataDefinition(method) as MethodInfo; - return ApplyReferenceNullabilityFull( - baseSymbol, - method.ReturnType, - method.ReturnParameter, - method, - definition?.ReturnType); + + // ADR-0172 Phase B: surface imported tuple element names. + return TupleElementNamesReader.ApplyNames( + ApplyReferenceNullabilityFull( + baseSymbol, + method.ReturnType, + method.ReturnParameter, + method, + definition?.ReturnType), + method.ReturnParameter); } /// @@ -136,6 +149,9 @@ public static TypeSymbol GetParameterTypeSymbol(ParameterInfo parameter) parameter, parameter.Member, layoutType); + + // ADR-0172 Phase B: surface imported tuple element names. + mapped = TupleElementNamesReader.ApplyNames(mapped, parameter); var rawDefault = parameter.HasDefaultValue || parameter.IsOptional ? parameter.RawDefaultValue : null; diff --git a/src/Core/CodeAnalysis/Symbols/ImportedTypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/ImportedTypeSymbol.cs index 22ad16bb4..bb241e614 100644 --- a/src/Core/CodeAnalysis/Symbols/ImportedTypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/ImportedTypeSymbol.cs @@ -95,7 +95,12 @@ private ImportedTypeSymbol(string name, Type erasedClosedType, Type? openDefinit || TypeSymbol.RequiresSymbolicProjection(a) || (a is ImportedTypeSymbol nested && nested.OpenDefinition != null - && !nested.TypeArguments.IsDefaultOrEmpty)); + && !nested.TypeArguments.IsDefaultOrEmpty) + + // ADR-0172: a named tuple argument shares its CLR backing with + // the unnamed shape, so only symbolic substitution preserves the + // element names on projected members (`list[i].line`). + || a is TupleTypeSymbol { HasNames: true }); /// /// Gets or creates the imported type symbol for the given CLR type. diff --git a/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs index 4f0e008c8..ab66a5914 100644 --- a/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs @@ -76,7 +76,18 @@ public TypeSymbol GetTypeArgumentSymbol(int argIndex) offset += ClrNullability.CountNullabilityBytes(args[i]); } - return ClrNullability.SymbolFromFlagsOffset(args[argIndex], NullableFlags, offset); + var derived = ClrNullability.SymbolFromFlagsOffset(args[argIndex], NullableFlags, offset); + + // ADR-0172: the flags-derived argument is rebuilt from the CLR shape, + // which cannot carry tuple element names. When the wrapped symbolic + // base holds a named tuple at this position, transfer its names. + if (BaseType is ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } symbolicBase + && (uint)argIndex < (uint)symbolicBase.TypeArguments.Length) + { + derived = TransferTupleNames(symbolicBase.TypeArguments[argIndex], derived); + } + + return derived; } /// @@ -126,7 +137,18 @@ public TypeSymbol GetTypeArgumentSymbolForClrType(Type? targetClrType) // Compare by FullName so MetadataLoadContext types match runtime types. if (arg == targetClrType || (!arg.IsGenericParameter && arg.FullName == targetClrType.FullName)) { - return ClrNullability.SymbolFromFlagsOffset(arg, NullableFlags, offset); + var flagged = ClrNullability.SymbolFromFlagsOffset(arg, NullableFlags, offset); + + // ADR-0172: transfer tuple element names from the wrapped + // symbolic base's matching argument (the flags-derived symbol + // is rebuilt from the CLR shape and cannot carry them). + if (BaseType is ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } symbolicBase + && (uint)i < (uint)symbolicBase.TypeArguments.Length) + { + flagged = TransferTupleNames(symbolicBase.TypeArguments[i], flagged); + } + + return flagged; } offset += ClrNullability.CountNullabilityBytes(arg); @@ -134,4 +156,15 @@ public TypeSymbol GetTypeArgumentSymbolForClrType(Type? targetClrType) return TypeSymbol.FromClrType(targetClrType); } + + private static TypeSymbol TransferTupleNames(TypeSymbol source, TypeSymbol target) => (source, target) switch + { + (TupleTypeSymbol { HasNames: true } namedSource, TupleTypeSymbol unnamedTarget) + when namedSource.Arity == unnamedTarget.Arity && !unnamedTarget.HasNames + => TupleTypeSymbol.Get(unnamedTarget.ElementTypes, namedSource.ElementNames), + (NullableTypeSymbol { UnderlyingType: TupleTypeSymbol { HasNames: true } namedSource }, NullableTypeSymbol { UnderlyingType: TupleTypeSymbol unnamedTarget }) + when namedSource.Arity == unnamedTarget.Arity && !unnamedTarget.HasNames + => NullableTypeSymbol.Get(TupleTypeSymbol.Get(unnamedTarget.ElementTypes, namedSource.ElementNames)), + _ => target, + }; } diff --git a/src/Core/CodeAnalysis/Symbols/TupleElementNamesReader.cs b/src/Core/CodeAnalysis/Symbols/TupleElementNamesReader.cs new file mode 100644 index 000000000..9b7111b5a --- /dev/null +++ b/src/Core/CodeAnalysis/Symbols/TupleElementNamesReader.cs @@ -0,0 +1,233 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace GSharp.Core.CodeAnalysis.Symbols; + +/// +/// ADR-0172 Phase B: decodes +/// System.Runtime.CompilerServices.TupleElementNamesAttribute from an +/// imported parameter / return / field / property and applies the flattened +/// name array onto the already-mapped , rebuilding +/// each tuple occurrence as a named . The walk +/// consumes names in the same DFS pre-order the C# compiler (and gsc's +/// emit-side TupleElementNamesBuilder) uses: every tuple contributes +/// its arity of entries before its element subtrees; non-tuple composites are +/// traversed transparently. Application is best-effort — a cursor/shape +/// mismatch (foreign compiler quirk) leaves the remaining tree unnamed rather +/// than failing the import. +/// +public static class TupleElementNamesReader +{ + private const string TupleElementNamesAttributeFullName = "System.Runtime.CompilerServices.TupleElementNamesAttribute"; + + /// + /// Applies any [TupleElementNames] metadata found on + /// to . + /// Returns unchanged when the attribute is + /// absent or carries no names. + /// + /// The already-mapped member type symbol. + /// The imported parameter / field / property to read the attribute from. + /// The (possibly) name-enriched type symbol. + public static TypeSymbol ApplyNames(TypeSymbol mapped, ICustomAttributeProvider provider) + { + var names = TryGetNames(provider); + if (names.IsDefaultOrEmpty) + { + return mapped; + } + + var position = 0; + return Apply(mapped, names, ref position); + } + + private static ImmutableArray TryGetNames(ICustomAttributeProvider provider) + { + IList? attrs; + try + { + attrs = provider switch + { + MemberInfo member => member.GetCustomAttributesData(), + ParameterInfo parameter => parameter.GetCustomAttributesData(), + _ => null, + }; + } + catch (Exception ex) when (ex is TypeLoadException or FileNotFoundException or BadImageFormatException) + { + return ImmutableArray.Empty; + } + + var attr = attrs?.FirstOrDefault(a => a.AttributeType.FullName == TupleElementNamesAttributeFullName); + if (attr == null || attr.ConstructorArguments.Count != 1) + { + return ImmutableArray.Empty; + } + + if (attr.ConstructorArguments[0].Value is not IReadOnlyCollection entries) + { + return ImmutableArray.Empty; + } + + return entries.Select(e => (string?)e.Value).ToImmutableArray(); + } + + private static TypeSymbol Apply(TypeSymbol type, ImmutableArray names, ref int position) + { + // An imported generic type argument surfaces as an imported + // `System.ValueTuple<…>` rather than a TupleTypeSymbol (issue #813 / + // #1922 identity bridging covers the two spellings elsewhere). + // Flatten it first so its positions line up with the name cursor. + if (type is not TupleTypeSymbol + && type is ImportedTypeSymbol { ClrType: { } importedClr } + && TypeSymbol.TryGetTupleTypeSymbolFromClr(importedClr, out var flattened)) + { + type = flattened; + } + + switch (type) + { + case TupleTypeSymbol tuple: + { + if (position + tuple.Arity > names.Length) + { + // Shape mismatch — stop consuming, keep the subtree as-is. + position = names.Length; + return tuple; + } + + var elementNames = ImmutableArray.CreateBuilder(tuple.Arity); + for (var i = 0; i < tuple.Arity; i++) + { + elementNames.Add(names[position++]); + } + + var elements = ImmutableArray.CreateBuilder(tuple.Arity); + foreach (var element in tuple.ElementTypes) + { + elements.Add(Apply(element, names, ref position)); + } + + return TupleTypeSymbol.Get(elements.MoveToImmutable(), elementNames.MoveToImmutable()); + } + + case NullableTypeSymbol nullable: + { + var applied = Apply(nullable.UnderlyingType, names, ref position); + return ReferenceEquals(applied, nullable.UnderlyingType) ? type : NullableTypeSymbol.Get(applied); + } + + case ArrayTypeSymbol array: + { + var applied = Apply(array.ElementType, names, ref position); + return ReferenceEquals(applied, array.ElementType) ? type : ArrayTypeSymbol.Get(applied, array.Length); + } + + case SliceTypeSymbol slice: + { + var applied = Apply(slice.ElementType, names, ref position); + return ReferenceEquals(applied, slice.ElementType) ? type : SliceTypeSymbol.Get(applied); + } + + case RectangularArrayTypeSymbol rectangular: + { + var applied = Apply(rectangular.ElementType, names, ref position); + return ReferenceEquals(applied, rectangular.ElementType) ? type : RectangularArrayTypeSymbol.Get(applied, rectangular.Rank); + } + + case MapTypeSymbol map: + { + var key = Apply(map.KeyType, names, ref position); + var value = Apply(map.ValueType, names, ref position); + return ReferenceEquals(key, map.KeyType) && ReferenceEquals(value, map.ValueType) + ? type + : MapTypeSymbol.Get(key, value); + } + + case SequenceTypeSymbol sequence: + { + var applied = Apply(sequence.ElementType, names, ref position); + return ReferenceEquals(applied, sequence.ElementType) ? type : SequenceTypeSymbol.Get(applied); + } + + case AsyncSequenceTypeSymbol asyncSequence: + { + var applied = Apply(asyncSequence.ElementType, names, ref position); + return ReferenceEquals(applied, asyncSequence.ElementType) ? type : AsyncSequenceTypeSymbol.Get(applied); + } + + case ChannelTypeSymbol channel: + { + var applied = Apply(channel.ElementType, names, ref position); + return ReferenceEquals(applied, channel.ElementType) ? type : ChannelTypeSymbol.Get(applied); + } + + case ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } imported: + { + var arguments = ImmutableArray.CreateBuilder(imported.TypeArguments.Length); + var changed = false; + foreach (var argument in imported.TypeArguments) + { + var applied = Apply(argument, names, ref position); + arguments.Add(applied); + changed |= !ReferenceEquals(applied, argument); + } + + return changed + ? ImportedTypeSymbol.GetConstructed(imported.Type, imported.OpenDefinition, arguments.MoveToImmutable()) + : type; + } + + case NullabilityAnnotatedTypeSymbol annotated: + { + var applied = Apply(annotated.BaseType, names, ref position); + return ReferenceEquals(applied, annotated.BaseType) + ? type + : new NullabilityAnnotatedTypeSymbol(applied, annotated.NullableFlags); + } + + // A closed generic imported wholesale from a CLR signature (e.g. + // `List>`) maps to a plain CLR-backed symbol + // with no symbolic TypeArguments — rebuild it as a constructed + // imported symbol whose arguments have names applied, so + // receiver-projected member access (`list[0].line`) sees them. + case { ClrType: { IsGenericType: true, IsGenericTypeDefinition: false } closedClr }: + { + Type[] clrArguments; + try + { + clrArguments = closedClr.GetGenericArguments(); + } + catch (Exception ex) when (ex is TypeLoadException or FileNotFoundException or BadImageFormatException) + { + return type; + } + + var arguments = ImmutableArray.CreateBuilder(clrArguments.Length); + var changed = false; + foreach (var clrArgument in clrArguments) + { + var argumentSymbol = TypeSymbol.FromClrType(clrArgument); + var applied = Apply(argumentSymbol, names, ref position); + arguments.Add(applied); + changed |= !ReferenceEquals(applied, argumentSymbol); + } + + return changed + ? ImportedTypeSymbol.GetConstructed(closedClr, closedClr.GetGenericTypeDefinition(), arguments.MoveToImmutable()) + : type; + } + + default: + return type; + } + } +} diff --git a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs index 818495676..c49dc19c7 100644 --- a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs @@ -1320,6 +1320,19 @@ internal static IEnumerable GetWrappedTypes(TypeSymbol type) } } + /// + /// ADR-0172 Phase B: internal surface of the #1922 CLR-tuple recognizer + /// for — an imported generic type + /// argument surfaces as an imported System.ValueTuple<…> + /// rather than a , and must be flattened + /// before element names can be applied to it. + /// + /// The candidate CLR type. + /// The resulting tuple symbol, if matched. + /// if is a supported tuple shape. + internal static bool TryGetTupleTypeSymbolFromClr(Type clrType, [NotNullWhen(true)] out TupleTypeSymbol? tupleTypeSymbol) + => TryGetTupleTypeSymbol(clrType, out tupleTypeSymbol); + /// /// Issue #1922: recognizes a closed generic System.ValueTuple<...> /// or System.Tuple<...> CLR type and maps it onto the equivalent diff --git a/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Creation.cs b/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Creation.cs index d5d9df495..6bd195000 100644 --- a/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Creation.cs +++ b/src/Core/CodeAnalysis/Syntax/Parser.Expressions.Creation.cs @@ -882,6 +882,43 @@ private bool LooksLikeGenericCallSite(int bracketOffset) private bool TryScanTypeClause(ref int pos) => TryScanTypeClause(ref pos, out _); + /// + /// ADR-0172: speculative-cursor twin of the committed parser's + /// LooksLikeTupleElementName — decides whether the identifier at + /// is a tuple-element NAME (followed by a token + /// that can start a type clause) rather than the element type itself. + /// + private bool ScanLooksLikeTupleElementNameAt(int pos) + { + if (Peek(pos).Text == "unmanaged" + && (Peek(pos + 1).Kind == SyntaxKind.OpenSquareBracketToken + || Peek(pos + 1).Kind == SyntaxKind.OpenParenthesisToken)) + { + return false; + } + + switch (Peek(pos + 1).Kind) + { + case SyntaxKind.IdentifierToken: + case SyntaxKind.FuncKeyword: + case SyntaxKind.MapKeyword: + case SyntaxKind.ChanKeyword: + case SyntaxKind.SequenceKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.StarToken: + case SyntaxKind.OpenParenthesisToken: + return true; + + case SyntaxKind.OpenSquareBracketToken: + return Peek(pos + 2).Kind is SyntaxKind.CloseSquareBracketToken + or SyntaxKind.NumberToken + or SyntaxKind.CommaToken; + + default: + return false; + } + } + // Issue #1602: depth-guarded wrapper — TryScanTypeClause and // TryScanOptionalTypeArgumentList are mutually recursive during // speculative lookahead (`a[a[a[…` scans as a candidate type-argument @@ -1054,6 +1091,15 @@ private bool TryScanTypeClauseCore(ref int pos, out bool isComplex) pos++; } + // ADR-0172: an optional element NAME before the type — + // `(line int32, column int32)` — mirrors + // LooksLikeTupleElementName on the speculative cursor. + if (Peek(pos).Kind == SyntaxKind.IdentifierToken + && ScanLooksLikeTupleElementNameAt(pos)) + { + pos++; + } + if (!TryScanTypeClause(ref pos)) { return false; diff --git a/test/Compiler.Tests/Emit/NamedTupleMetadataEmitTests.cs b/test/Compiler.Tests/Emit/NamedTupleMetadataEmitTests.cs new file mode 100644 index 000000000..3c3b7b5bc --- /dev/null +++ b/test/Compiler.Tests/Emit/NamedTupleMetadataEmitTests.cs @@ -0,0 +1,279 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace GSharp.Compiler.Tests.Emit; + +/// +/// ADR-0172 Phase B: gsc synthesizes +/// [System.Runtime.CompilerServices.TupleElementNamesAttribute] on +/// tuple-typed parameters, returns, fields, and properties (the C# flattened +/// pre-order encoding), and decodes it when importing referenced assemblies, +/// so element names survive the CLR boundary in both directions. The blob's +/// C#-compatibility witness is the cross-assembly round trip: a consumer +/// compiled against the emitted metadata resolves pos.line — before +/// Phase B that access was GS0158 and the attribute rows did not exist. +/// +public class NamedTupleMetadataEmitTests +{ + [Fact] + public void ReturnAndParameter_CarryTupleElementNames() + { + var assembly = CompileToAssembly(""" + package P + + class Locator { + shared { + func Find() (line int32, column int32) { + return 3, 5 + } + + func Sum(pos (line int32, column int32)) int32 { + return pos.line + pos.column + } + } + } + """); + + var locator = assembly.GetTypes().Single(t => t.Name == "Locator"); + var find = locator.GetMethod("Find")!; + Assert.Equal(new[] { "line", "column" }, ReadNames(find.ReturnParameter)); + + var sum = locator.GetMethod("Sum")!; + Assert.Equal(new[] { "line", "column" }, ReadNames(sum.GetParameters()[0])); + } + + [Fact] + public void UnnamedTuple_OmitsAttribute() + { + var assembly = CompileToAssembly(""" + package P + + class Plain { + shared { + func Pair() (int32, int32) { + return 1, 2 + } + } + } + """); + + var pair = assembly.GetTypes().Single(t => t.Name == "Plain").GetMethod("Pair")!; + Assert.Null(FindAttribute(pair.ReturnParameter)); + } + + [Fact] + public void NestedGenericAndPartialNames_FlattenedPreOrder() + { + var assembly = CompileToAssembly(""" + package P + import System.Collections.Generic + + class Store { + shared { + func All() List[(line int32, string)] { + return List[(line int32, string)]() + } + } + } + """); + + var all = assembly.GetTypes().Single(t => t.Name == "Store").GetMethod("All")!; + + // The List itself contributes no entries; the tuple argument + // contributes its two logical positions, null where unnamed. + Assert.Equal(new[] { "line", null }, ReadNames(all.ReturnParameter)); + } + + [Fact] + public void ArityNine_LogicalElementsOnly_TRestInvisible() + { + var assembly = CompileToAssembly(""" + package P + + class Big { + shared { + func Make() (a int32, b int32, c int32, d int32, e int32, f int32, g int32, h int32, i int32) { + return 1, 2, 3, 4, 5, 6, 7, 8, 9 + } + } + } + """); + + var make = assembly.GetTypes().Single(t => t.Name == "Big").GetMethod("Make")!; + Assert.Equal(new[] { "a", "b", "c", "d", "e", "f", "g", "h", "i" }, ReadNames(make.ReturnParameter)); + } + + [Fact] + public void FieldAndProperty_CarryTupleElementNames() + { + var assembly = CompileToAssembly(""" + package P + + class Holder { + var Position (line int32, column int32) + prop Origin (x int32, y int32) -> (0, 0) + } + """); + + var holder = assembly.GetTypes().Single(t => t.Name == "Holder"); + var field = holder.GetField("Position", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + if (field != null) + { + Assert.Equal(new[] { "line", "column" }, ReadNames(field)); + } + + var property = holder.GetProperty("Origin")!; + Assert.Equal(new[] { "x", "y" }, ReadNames(property)); + } + + [Fact] + public void GsToGsRoundTrip_NameAccessAcrossAssemblies() + { + // The full loop: names emitted by gsc, read back by gsc's importer. + var libSource = """ + package NamedLib + import System.Collections.Generic + + class Locator { + shared { + func Find() (line int32, column int32) { + return 3, 5 + } + + func FindAll() List[(line int32, column int32)] { + let all = List[(line int32, column int32)]() + all.Add((1, 2)) + all.Add((3, 4)) + return all + } + } + } + """; + var appSource = """ + package App + import System + import NamedLib + + let pos = Locator.Find() + Console.WriteLine(pos.line) + Console.WriteLine(pos.column) + Console.WriteLine(Locator.FindAll()[1].line) + """; + + var output = CompileAndRunWithLibrary(libSource, appSource); + Assert.Equal($"3{Environment.NewLine}5{Environment.NewLine}3{Environment.NewLine}", output); + } + + private static string[] ReadNames(ICustomAttributeProvider provider) + { + var data = FindAttribute(provider); + Assert.NotNull(data); + var arg = Assert.Single(data.ConstructorArguments); + return ((ReadOnlyCollection)arg.Value) + .Select(v => (string)v.Value) + .ToArray(); + } + + private static CustomAttributeData FindAttribute(ICustomAttributeProvider provider) + { + var attrs = provider switch + { + MemberInfo member => member.GetCustomAttributesData(), + ParameterInfo parameter => parameter.GetCustomAttributesData(), + _ => throw new InvalidOperationException("unexpected provider"), + }; + return attrs.FirstOrDefault(d => d.AttributeType.FullName == "System.Runtime.CompilerServices.TupleElementNamesAttribute"); + } + + private static Assembly CompileToAssembly(string source) + { + var tempDir = Directory.CreateTempSubdirectory("gs_ntuple_emit_").FullName; + var srcPath = Path.Combine(tempDir, "test.gs"); + var outPath = Path.Combine(tempDir, "test.dll"); + File.WriteAllText(srcPath, source); + RunGsc(new[] { "/out:" + outPath, "/target:library", "/targetframework:net10.0", srcPath }); + IlVerifier.Verify(outPath); + return Assembly.Load(File.ReadAllBytes(outPath)); + } + + private static string CompileAndRunWithLibrary(string libSource, string appSource) + { + var tempDir = Directory.CreateTempSubdirectory("gs_ntuple_rt_").FullName; + try + { + var libSrc = Path.Combine(tempDir, "lib.gs"); + var libDll = Path.Combine(tempDir, "namedlib.dll"); + var appSrc = Path.Combine(tempDir, "app.gs"); + var appDll = Path.Combine(tempDir, "app.dll"); + File.WriteAllText(libSrc, libSource); + File.WriteAllText(appSrc, appSource); + + RunGsc(new[] { "/out:" + libDll, "/target:library", "/targetframework:net10.0", libSrc }); + IlVerifier.Verify(libDll); + RunGsc(new[] { "/out:" + appDll, "/target:exe", "/targetframework:net10.0", "/reference:" + libDll, appSrc }); + IlVerifier.Verify(appDll, additionalReferences: new[] { libDll }); + + var psi = new ProcessStartInfo("dotnet") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = tempDir, + }; + psi.ArgumentList.Add("exec"); + psi.ArgumentList.Add("--runtimeconfig"); + psi.ArgumentList.Add(Path.ChangeExtension(appDll, ".runtimeconfig.json")); + psi.ArgumentList.Add(appDll); + + using var proc = Process.Start(psi)!; + var stdout = proc.StandardOutput.ReadToEnd(); + var stderr = proc.StandardError.ReadToEnd(); + Assert.True(proc.WaitForExit(30_000), "dotnet exec timed out"); + Assert.True(proc.ExitCode == 0, $"exited {proc.ExitCode}\nstdout:\n{stdout}\nstderr:\n{stderr}"); + return stdout.ReplaceLineEndings(Environment.NewLine); + } + finally + { + try + { + Directory.Delete(tempDir, recursive: true); + } + catch + { + } + } + } + + private static void RunGsc(string[] args) + { + using var compileOut = new StringWriter(); + using var compileErr = new StringWriter(); + var prevOut = Console.Out; + var prevErr = Console.Error; + Console.SetOut(compileOut); + Console.SetError(compileErr); + int compileExit; + try + { + compileExit = Program.Main(args); + } + finally + { + Console.SetOut(prevOut); + Console.SetError(prevErr); + } + + Assert.True( + compileExit == 0, + $"gsc failed:\nstdout:\n{compileOut}\nstderr:\n{compileErr}"); + } +} diff --git a/test/Core.Tests/Baselines/refactoring-baseline.json b/test/Core.Tests/Baselines/refactoring-baseline.json index e12db1298..fef333317 100644 --- a/test/Core.Tests/Baselines/refactoring-baseline.json +++ b/test/Core.Tests/Baselines/refactoring-baseline.json @@ -77,6 +77,7 @@ "samples/MethodsWithReceivers.gs": "2AB3D118BAFE4A9E11D89D94733DEDBF6FEF2AEFB3AB6FF69B34A4B515D9EF09", "samples/NamedArguments.gs": "C3D5E3829DCD3DEBBEBC4FAE60AF930FD2DA862FF840F0CCFFD43660289EE812", "samples/NamedDelegate.gs": "F7C3D38CA6BE8FEC0F98CE7ED6AE77343C4867B9F86DC19A5ACB1291DA0D7C34", + "samples/NamedTupleElements.gs": "2DB9390C95025E9AC4E445CD2A972885C4225AA1DC29BA706AB7DBE84A3C4DD9", "samples/NestedTypeOfConstructedGeneric.gs": "40DD33E1BCD31F007040A3C6E1C83D1611F1AFF7B2D0174A0986FB0CAB159A2C", "samples/NullCoalescingAssignment.gs": "5A7F739F1950C9913B1353D4AF621D5F6464BA982C90E601CF849E0DB33CBBA0", "samples/NullConditionalIndexing.gs": "083AF08BEBCE0CAB6BA39E813189134F8F1080465C3A6A5C633325FB785DA356", diff --git a/test/Core.Tests/Baselines/refactoring-baseline.json.actual b/test/Core.Tests/Baselines/refactoring-baseline.json.actual new file mode 100644 index 000000000..fef333317 --- /dev/null +++ b/test/Core.Tests/Baselines/refactoring-baseline.json.actual @@ -0,0 +1,143 @@ +{ + "samples/AddressBook.gs": "A9F2644C638FA45659947844208FAC7DBD328CC5E8BBD51E2D0133FE60D76F5D", + "samples/AnonymousVariadicFunctionType.gs": "57274759240642F7A80B474176195CEA3E16CB19AC93CBDBB4322DFF4088F214", + "samples/Arithmetic.gs": "C2015A5D2D2DEB3DE59A9DCD904902BD9490F866C63591D7B597F9C72E1DD6C6", + "samples/Arrays.gs": "ED114BB7F5B80BC76AE69DCB9DCF62F2F7D9DEE43AE98A06946BC72110FC76EB", + "samples/ArrowFunctionTypeClause.gs": "DF3D4A1F658BF9069B75C55C05A69B61065EED5A1D6E783BB94A3DB9A056D6BF", + "samples/ArrowLambda.gs": "E473ACE216D60C794A62934B82452E0397A94FDFEE77B7521B514E6AD95D583A", + "samples/AsyncAwaitInLoop.gs": "6F0847648794258881D6D15A87D75DE1766A0BFBE77733C16755ADCEBB623C3B", + "samples/AsyncAwaitInNestedLoop.gs": "269A1B99DA1CAF61B40932547D4E9F209FACF04C647E7D8EDF522C9BBFB06060", + "samples/AsyncClassMethod.gs": "0C8DDFB960FCC577061C9AE48A34EA327B826D493FE06210E6FD44A3ADC9595F", + "samples/AsyncGoScopeJoin.gs": "37C28A816B1D0D5A3931B15D7DC18B39B71C52D23231A40B9D537ED299C35BB4", + "samples/AsyncMultiAwaitInLoop.gs": "D00832D53F26DF7CA6876BC9F5F7460C126949A82BD8F4C0CE7FEBA0CFB0E89E", + "samples/AsyncTask.gs": "7F02DA258DCA77B4239DDC0DA8EAA67ECE1A3CE6422A2E48092AC4A9AF9F7233", + "samples/AsyncValueReturns.gs": "CE0FCFA9EB5B8B13735FFCA49234020C17AC4DA295091A27AE4B638972B7FB9F", + "samples/BaseConstructor.gs": "C187CAD84B84EFCFA4F8243D77214249178E8BB129243A0BC3786C58C6EF4C32", + "samples/Channels.gs": "808FB5392AB05825092B52EA2F3C709E0A0B719A96678C64E64AFF70C14C23C9", + "samples/Class.gs": "BB5ADDF2D7D33F0F3CF8C7CE81299362C46A938982EA332DF0B431D03D0CF598", + "samples/ClrMethodGroupToDelegate.gs": "E9C2480576944BD9C4D05541ABA986DD3AFF7F9A7E4EF30D3B569A013BDD115B", + "samples/CollectionInitializers.gs": "D90CD1DB681205CB51815C4D071515F75A50E902268E2BE254B460F08537FD6F", + "samples/ConversionOperators.gs": "84C8380072654B282BDFC088CB225CDECA22CE81C4089A27B3F6491F3E6517C7", + "samples/CountWords.gs": "B9280DECB123F3799763D3E0C889BFE5BAF391536F9425BB308B9571B2D66110", + "samples/DataStruct.gs": "DFDDE97F127BE645DAFA19EA63667847EB1C9B59D353A687ED012D95478089B1", + "samples/DataStructErgonomics.gs": "02DF9056A7DC2CC08EC85A485AB43CE477B5716471B83293CB51199A5CA9BF69", + "samples/DefaultExpression.gs": "F8CA98ED2532BF73C3C0DBC106B57C249F96F71A0264C30184331EE1551DD065", + "samples/DefaultInterfaceMethods.gs": "2C1AF180744995F1215ECE844861E1853393AC59137FA8F8E17ACC9D2A5FBD7A", + "samples/Defer.gs": "28BBF322E9214637F1A7DEEE895B9853B5BD8839E07642B195E88AC5736EE34E", + "samples/Deinit.gs": "356EDACB2D51D6C1DF7D03C3DCDEFF4370F71E3536717057C25EA949E11DE939", + "samples/DelegateCallSyntax.gs": "0C783279638E75557A5758394B9E5B284D7B5D8CDA1FA4201106FFCBE8FE2F8B", + "samples/DiscriminatedUnion.gs": "24804C81F08D300AB9A484B29F80A38DC4A3A30E492A83EA720B81F76BF15F84", + "samples/Enum.gs": "9EB68E0FE2E675308B2877FCE4BF8FD069EC741932D400550A028E7AE5005307", + "samples/EventSubscription.gs": "7E78F09EEA9974F2C374C26D5B01E69EE72615B335E9E9C3A391211371FED4EE", + "samples/Exceptions.gs": "0B095212A5B37A85FF1D6937006674C6FE5B44B93D1353634E9C006B0F38F260", + "samples/Exhaustiveness.gs": "983B49359B00ADF43DF1DFA47A886B9264C1B49F9D134F471E8B80604B128BBA", + "samples/ExplicitConstructor.gs": "AD6E02EA495283572BBFB1D3548775FC299E41F64EA70733A3984E3ECDA54335", + "samples/ExpressionEval.gs": "50498E0DA7318CE0D7F1FE70F430262CAD65B795E5C088C8CBB4EDE42982C049", + "samples/ExtensionFunctions.gs": "5F8824A4C81796128DD8EBDA778E39D83A07021306461D99AFAB49EF9E6B2C94", + "samples/ForIn.gs": "28C44E345F420A64304392EC6FAC09972369D3F64F591012373E995D3CB3C505", + "samples/FriendlyNumericAliases.gs": "E719B98388BD6400EC3786D58089082225FF6802AD16AD66CCD496AC064B4313", + "samples/FuncToDelegate.gs": "1DFB41D919D5CAA0D09B27BA1F4844D13B2F7461156D4B4CD6081E65AAA022C7", + "samples/FuncToSystemDelegate.gs": "14F13DA05D069656E418F27B808DCF3895FC5C1A7DE6A9EEFAD8DBC9FA407129", + "samples/GenericConstruction.gs": "06898937A325DBEFE0AC824C8C55433F0DEAF86D3FA2B0F56CB5EBD63E4B27C5", + "samples/GenericEquality.gs": "C8FC37104635E7E683791CE247926BF75A55693C84BB3CDB0C8D2483C11990FF", + "samples/GenericExtensionFunctions.gs": "B2B2F7D753BF65DE429C01D8947BA0B3D2009164D3B79CF0DFC8358D171C2110", + "samples/GenericMethodDelegates.gs": "F5D13C49277DA772FDBFA9D5A4C8C63454AE617F882ED97CBBF6626C33370EB2", + "samples/GenericMethodTypeArgs.gs": "E77A417A7D6BF56C288F7CB922938DB3CFFD51B6B21C5657C41761E0CBE7A6DE", + "samples/GenericMethodUserTypeArg.gs": "EC149474B2C0F3997248CD6A5C5170FC01EAA12BC24E6CF3B2A05DC9A6E4CA46", + "samples/GenericMethods.gs": "7C1A0DC149B121DA99BDD198CFFDF9DF22F30F06EA44AD81B97ACE4FE9976FB4", + "samples/GenericNamedDelegate.gs": "1A2A7F29D0C93744666174FDFA687FA525A26A55460AF7F1F69D2E193D54942D", + "samples/GenericTypeParameterAsTypeArgument.gs": "7412A4BAECCA415357F5071BBB08AAA24790FECCC78034D85832F20E33FA96F0", + "samples/GetOnlyCollectionInit.gs": "6CE360164F519D83E43F80CCA9DBA675232DDF8240913D34378F371FB35645E9", + "samples/GoBuiltinsGated.gs": "18363901A8B5DF062A6AF95335DB7B341795723877F6420A647B83D65207BD3B", + "samples/GoChannelsGated.gs": "B7B095745161A98E914E6425EAFDC19507B6E6B3B9FCBD7A59D724515EE69BED", + "samples/GoScope.gs": "E1EB2AC2DE9EDB29C4A69CCB19322D99EF101BD0565CC293DAC19D5137A5ACA1", + "samples/GsharpExtensionsMixed.gs": null, + "samples/GsharpExtensionsOptional.gs": null, + "samples/GsharpExtensionsSequences.gs": null, + "samples/HelloWorld.gs": "9DAFFE9E74FF62950AE0E02379CBBAC847BAB485674831D4767634CFA51B81AB", + "samples/IfExpression.gs": "69AD73A147BD4EB61B7AA78E3B347DEA2EBC4105A8900BAEA2576C57E075FAB7", + "samples/IfLetGuardLet.gs": "EAB514563DA0605416E524FF5535597261FCDB31706518A87192A2BFDB368EB5", + "samples/ImplicitImport.gs": "019014F35C01A0DC8D43ED116C4F81F318B582BBAFC82128C7AD9A8FEEDEBB7D", + "samples/ImportAlias.gs": "973A6FB17B4D34341C5E5C96B83041BCDBD068378312E6B2177AF901F412104F", + "samples/ImportedBaseClass.gs": "8688D14D78251D37836E50126C13CD91A9C0F3264F4B122E91777A85833536AE", + "samples/ImportedTypeCtorInFunc.gs": "C187F644D893D30073CB7E41166AB21144DF195FBD27FFF849A0720C846368B0", + "samples/InPlaceElementWrites.gs": "93BC6D7CEBA37BC669D8601A9EA8D33EBFA3F9E686029A4563E8937D2EDD7ACE", + "samples/InlineStruct.gs": "F0833664B542B544EDE3E934554CB4D66B056E8D67DB806A5CC9620EC834785C", + "samples/InterfaceDiamondDisambiguation.gs": "3691CB37EB58A340C122427770AE5B80417DD5CBFC42BB53229EB7B2B1DE6158", + "samples/InterfaceUpcast.gs": "403780EB1A42B293B9B77A857AF480551D059FE429625913C57D295D0AACC180", + "samples/InterpolatedString.gs": "010B2E93CE34DBF49CE280C0362C22A6020C4A48192B4E7CFF2F6F63FCC270D8", + "samples/InterpolatedStringFormat.gs": "C73DA66CB5E9A6FF1AA0E72DE32F7B7877D1CCFB41B64D3638D8367629BB1F5C", + "samples/InterpolatedStringFormattable.gs": "6F23099B3CEE377159A9541002EB4B008552A44A6DF12DECECC8BB08C34AFA14", + "samples/InterpolatedStringRichHoles.gs": "5B4939468602A98C9249110F54F40E47C55F1FA1845D960C3461980076641B78", + "samples/LambdaBindingInference.gs": "4EA4DF5927ADFBB7AB974DA02361651F41F356F17D27F0BF94AD32D5FE72336F", + "samples/LinqExtensions.gs": "A1FD7A91808793471A010D719DA7E29B792775BE8B3DA6C7BD651B6838A39A20", + "samples/Loop.gs": "66F668F408F165994D3BE172E7F9B1EDD39C00B6A8FA01FB390F59A3649D43EC", + "samples/MapForIn.gs": "FB4D48385465E858927DA8934F587217B5048D333CD5FB8A38E8E0FDEEDF7B10", + "samples/MethodGroupToDelegate.gs": "4AA53D7310C79AAB27D025A26BAEDD4ADCAF1211F19711001707DBFE509B9634", + "samples/MethodsWithReceivers.gs": "2AB3D118BAFE4A9E11D89D94733DEDBF6FEF2AEFB3AB6FF69B34A4B515D9EF09", + "samples/NamedArguments.gs": "C3D5E3829DCD3DEBBEBC4FAE60AF930FD2DA862FF840F0CCFFD43660289EE812", + "samples/NamedDelegate.gs": "F7C3D38CA6BE8FEC0F98CE7ED6AE77343C4867B9F86DC19A5ACB1291DA0D7C34", + "samples/NamedTupleElements.gs": "2DB9390C95025E9AC4E445CD2A972885C4225AA1DC29BA706AB7DBE84A3C4DD9", + "samples/NestedTypeOfConstructedGeneric.gs": "40DD33E1BCD31F007040A3C6E1C83D1611F1AFF7B2D0174A0986FB0CAB159A2C", + "samples/NullCoalescingAssignment.gs": "5A7F739F1950C9913B1353D4AF621D5F6464BA982C90E601CF849E0DB33CBBA0", + "samples/NullConditionalIndexing.gs": "083AF08BEBCE0CAB6BA39E813189134F8F1080465C3A6A5C633325FB785DA356", + "samples/NullableFlow.gs": "CCA072FA058E8C360237B638EBD913DA51F9509D00366E282F7478BCE6D172F8", + "samples/Operators.gs": "61FA0050F63CBAE8E4CDF86692048218D5D69CBEF295B3034A3AA95443F42F74", + "samples/OptionalExtensionArgs.gs": "750976CFCB6F9F4D4095CB9CABC5737F422B25C2BD80BB458EBC647131D218EE", + "samples/PInvoke.gs": "2784B2CAA68929D7D21CD075D0C835FD8A0F8339158B460A5DDE4490044A5AAC", + "samples/PInvokeFunctionPointer.gs": "85F9F7526E454628F8A78F854697B847E9A157B063E5C3E37E1B640196C78413", + "samples/PInvokeLibraryImport.gs": "2AC47FFA0E442DC6AB6205823294AA7A4564E5E65983C5F8EF6B62C6F7EF9A30", + "samples/PInvokeLibraryImportStringReturn.gs": "B41D5A863093742DAF4C89597B8A9D3D844C3FF7E8025FE7A2AF69889A207172", + "samples/PInvokeMarshalAs.gs": "4BA315A048843E608893E7D3F6139A56E4EF41C1DE7FD0499EDB5DFA4B3E67D2", + "samples/PInvokeRefOutIn.gs": "2130DBB30680B1F473420737D52C1C3A50007EF2ACAFC23B5A7F1BFB8DFCE045", + "samples/PInvokeStructMarshalling.gs": "CA2056A87B7218A47A850E842BF5356F034EC0C4947BAA6ACE888A36CC53D65F", + "samples/ParenthesizedReceiver.gs": "1C228E94F9B6A692A3E4B0669AC046A2E15EA53936E489D596D5A5933796B81C", + "samples/PatternSwitch.gs": "E7BF01BD38F92CFFAD6632270CA2FA54B6E5A6DF31DF1AC83C6D6162014E59B2", + "samples/Patterns.gs": "86597C1391F3D2A20FD8637F273013E33ABD0781890DF3CE95507120B53E8BC5", + "samples/PortScan.gs": "F6BCA823B5C2250BEDA73E6E197C6DD0D2C591BC80C49BA23C1DDDA0BBC58082", + "samples/PrimaryCtorVariadic.gs": "226429D1F404CF3AAFAB62AB33D3DA61BA7A95CA5D068248B5479354AFFEBD1D", + "samples/PrivateInterfaceHelpers.gs": "42F831380EFB8FFECF6F435E45BDC6745E714E651DEA7DF7175EED0D1E4A2847", + "samples/RawString.gs": "72CFDBA31D9FD766C6D31DB0B34585BE6E02B68620D95A5D67EFCEDCA730A1F9", + "samples/Records.gs": "54C116CD3D6A6542DD4060054C61B7C64796F77F07EBFC99B118E316ECE1BAAF", + "samples/RefIncrement.gs": "349907056925E3A0235511AEE41150311CBFE26B13948712BB9CCDA82393CAFB", + "samples/RefStructGenericField.gs": "9E129FDF9B516DC4E471ADED6FE454690B2F2730886D8826EDA358E51CD6F8A9", + "samples/RefStructSpan.gs": "79F9B1BE3ABC616BC7B7D680AB5C02B4B1C01F55373E01AF683705839025A513", + "samples/ReifiedGenerics.gs": "FD47BBBBAB34A938F4EE42F2A57E2F31621055FC32284E32340F90D0F7B40EA1", + "samples/Sealed.gs": "B1E8635EBC7D10280EE4414242AA52856EDA35F10B8437F72AB726E2FAFA8E17", + "samples/Select.gs": "BAF0BA0CC70A848C262E3EA5E7F92C27F0261B6D17D59119300D65800606F9CA", + "samples/SliceLinqUntypedLambda.gs": "2C1EF66E4A1237AFE72EEF74AC885C0D9380C9AD3805702AA2A8B162DBCCF625", + "samples/SlicePattern.gs": "7315C8EAE121B3996CCF679A7F33AF7CBE474D1C1EA8DD263516AC7F3D035B12", + "samples/Slices.gs": "161082791B12CDCEC5A9ACDD324914E183F9293026DEBCF301A47A4D6E6BD1F3", + "samples/SmartCast.gs": "A654215710CE817D667948C364E1B0CB9056116281C1A935F995A3BA5BEC60F3", + "samples/SmartCastExtensions.gs": "AB9900233C9A6E1C14AAE1FAA6D894FA72A9CB5F942B981027C72584CB75EC56", + "samples/SpanComprehensive.gs": "5423D603776DC37529A4AE96B9D89CF88FD2BEAFB3C8A624E510B5FBF39D2F26", + "samples/SpanIndexing.gs": "A583E25AD6DB3256228B6407D401474010D9A552BCE31087F0D4D695B76F2D8A", + "samples/StaticVirtualInterfaces.gs": "F2F8DEB8DABBC47CCEA9ADCDA0E72F34034EABC10E88845706FAB4607EC7FCC2", + "samples/Struct.gs": "2C4B22BE2169469862358575B566F34EC72AFFADE382D76D350B30C86293389A", + "samples/SwitchExpression.gs": "C8FFEB7D57C2846FCF2C94A492A41D4026D2B41E26A6138C0BB981B1AD7E87FB", + "samples/TryParseOutVar.gs": "5104914A5581C6F3DD6E550A695FE4B7CC376DB6388DE99178C7A69DF07802C6", + "samples/TupleEquality.gs": "8A842FB82FC75C6F5433038E57EE1425445A3D88B2C16D2E8088B1D4E8E6E5CB", + "samples/TupleSequenceIterators.gs": "AABF96522EAF606F34C57FC7CE2F12493039C436607982097CED2947FCAA740B", + "samples/UserRefStruct.gs": "79ED3397503DAF743D2552CDF6CEEE2A72E7412D0B03D78D22C060FDE71479B4", + "samples/ValueTypeObjectMethods.gs": "EDF82DBEE14C7ABB0CA990D65A9F6A5C7C3898C820195DDC615980FB7E04FDAD", + "samples/Variadic.gs": "BD2FC989276DBD0B581364A36C45210D84B29D0A611B5C8C915F0590F74598D5", + "samples/VariadicDelegate.gs": "F719D2D1B518234856D7425DDD126C248FF508E042A6AD114E964379A888E108", + "samples/VariadicMethods.gs": "740FE847DC8AF45B1FBAE51B1D71992F336F8AD798AE65A8B869EDF14A489D0C", + "samples/WhileAndLabeledLoops.gs": "2016A430C94470D1200ED5F394F81CFD88337682D42DF26397D8FC3F4F01F4BE", + "samples/ZeroValues.gs": "B0278AFA5E958D5F8208CDD3C257738B936B919EE8F872DAF6A4AB6F538CF44E", + "samples/refactoring-baseline/AwaitInFieldAssignment.gs": "B3BBCBABABDF14F721B666A571399EA71C0D2D0C9679B41D079ACF47490C281A", + "samples/refactoring-baseline/AwaitInIndexAssignment.gs": "91BD677707E1067179A050BE9EE6A9A5A15B33C6F71CFB428DE2B93FE3ED7336", + "samples/refactoring-baseline/AwaitInUnary.gs": "C787D8F8AD272EFEAF0DFCAAE42E0D89F7BEED266A8A7ACFC3164F7DC552FFDA", + "samples/refactoring-baseline/ClosureCaptureRefTypeField.gs": null, + "samples/refactoring-baseline/EnumBitwise.gs": "CA776607253DB67B113383C95F927F4BDABAF0FBC3DE83A8BFA1FB0566DF5C83", + "samples/refactoring-baseline/ForInIEnumerable.gs": "B648959671CDC5D328CE656F856CA4C5B1FF25A836398836430EB02262E1A7A3", + "samples/refactoring-baseline/GenericMethodSpec.gs": "B0A9AF4D3FAC32E58F4CC355049AD03BDBF23C86131262749E05010739BB0B25", + "samples/refactoring-baseline/NullablePropertyRead.gs": "C15C2A22A1A86E7AF9670F1A341D50907B37989C132312CC5D4225D251E749CA", + "samples/refactoring-baseline/NullableValueMember.gs": "48D2E71A472BFCB1B95BAEE1B8DCFA8A64652E6877457D3B3DA7579103817E30", + "samples/refactoring-baseline/ReadOnlyAttr.gs": "01EAFA58553D086FA5F7A0D06D6A440B9827A1DF7D7C09B1C6E640B1BEB0F7E5", + "samples/refactoring-baseline/RefStructByRefLike.gs": "4CF6FB2340F4284A7C9B8BBDE87515621F791207CC5326B9B7FD6AE56A60C186", + "samples/refactoring-baseline/ReturnInTryFinally.gs": "C3B60BC9B602B891C3AE1B24AAF9D70FE9A2D3BC2EE70C85F79B948FF1C81438", + "samples/refactoring-baseline/ShortCircuitAnd.gs": "93113A1DAC91F6561EC9E0B45F5719E838BA3B2B0D82C1B842351D0E706F351D", + "samples/refactoring-baseline/ShortCircuitOr.gs": "B764F7EB658451564EDEA35AE111344D2F9AAF046FB879C55DDC02133A344281", + "samples/refactoring-baseline/YieldInTryFinally.gs": "34608F80955B1D1C89F2C9A15A841F5CB3AAD09DF2D676DE5AC3A7B84F3195A7" +} From a806e044b816cd29971f687c1b43b59ab5dc2545 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 28 Aug 2026 15:50:26 -0700 Subject: [PATCH 3/5] Remove committed baseline .actual byproduct; ignore it Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs --- .gitignore | 1 + .../refactoring-baseline.json.actual | 143 ------------------ 2 files changed, 1 insertion(+), 143 deletions(-) delete mode 100644 test/Core.Tests/Baselines/refactoring-baseline.json.actual diff --git a/.gitignore b/.gitignore index 34ad4eef1..772721e46 100644 --- a/.gitignore +++ b/.gitignore @@ -343,3 +343,4 @@ package-lock.json # cs2gs migration run output (generated, do not commit) cs2gs-runs/ +test/Core.Tests/Baselines/*.actual diff --git a/test/Core.Tests/Baselines/refactoring-baseline.json.actual b/test/Core.Tests/Baselines/refactoring-baseline.json.actual deleted file mode 100644 index fef333317..000000000 --- a/test/Core.Tests/Baselines/refactoring-baseline.json.actual +++ /dev/null @@ -1,143 +0,0 @@ -{ - "samples/AddressBook.gs": "A9F2644C638FA45659947844208FAC7DBD328CC5E8BBD51E2D0133FE60D76F5D", - "samples/AnonymousVariadicFunctionType.gs": "57274759240642F7A80B474176195CEA3E16CB19AC93CBDBB4322DFF4088F214", - "samples/Arithmetic.gs": "C2015A5D2D2DEB3DE59A9DCD904902BD9490F866C63591D7B597F9C72E1DD6C6", - "samples/Arrays.gs": "ED114BB7F5B80BC76AE69DCB9DCF62F2F7D9DEE43AE98A06946BC72110FC76EB", - "samples/ArrowFunctionTypeClause.gs": "DF3D4A1F658BF9069B75C55C05A69B61065EED5A1D6E783BB94A3DB9A056D6BF", - "samples/ArrowLambda.gs": "E473ACE216D60C794A62934B82452E0397A94FDFEE77B7521B514E6AD95D583A", - "samples/AsyncAwaitInLoop.gs": "6F0847648794258881D6D15A87D75DE1766A0BFBE77733C16755ADCEBB623C3B", - "samples/AsyncAwaitInNestedLoop.gs": "269A1B99DA1CAF61B40932547D4E9F209FACF04C647E7D8EDF522C9BBFB06060", - "samples/AsyncClassMethod.gs": "0C8DDFB960FCC577061C9AE48A34EA327B826D493FE06210E6FD44A3ADC9595F", - "samples/AsyncGoScopeJoin.gs": "37C28A816B1D0D5A3931B15D7DC18B39B71C52D23231A40B9D537ED299C35BB4", - "samples/AsyncMultiAwaitInLoop.gs": "D00832D53F26DF7CA6876BC9F5F7460C126949A82BD8F4C0CE7FEBA0CFB0E89E", - "samples/AsyncTask.gs": "7F02DA258DCA77B4239DDC0DA8EAA67ECE1A3CE6422A2E48092AC4A9AF9F7233", - "samples/AsyncValueReturns.gs": "CE0FCFA9EB5B8B13735FFCA49234020C17AC4DA295091A27AE4B638972B7FB9F", - "samples/BaseConstructor.gs": "C187CAD84B84EFCFA4F8243D77214249178E8BB129243A0BC3786C58C6EF4C32", - "samples/Channels.gs": "808FB5392AB05825092B52EA2F3C709E0A0B719A96678C64E64AFF70C14C23C9", - "samples/Class.gs": "BB5ADDF2D7D33F0F3CF8C7CE81299362C46A938982EA332DF0B431D03D0CF598", - "samples/ClrMethodGroupToDelegate.gs": "E9C2480576944BD9C4D05541ABA986DD3AFF7F9A7E4EF30D3B569A013BDD115B", - "samples/CollectionInitializers.gs": "D90CD1DB681205CB51815C4D071515F75A50E902268E2BE254B460F08537FD6F", - "samples/ConversionOperators.gs": "84C8380072654B282BDFC088CB225CDECA22CE81C4089A27B3F6491F3E6517C7", - "samples/CountWords.gs": "B9280DECB123F3799763D3E0C889BFE5BAF391536F9425BB308B9571B2D66110", - "samples/DataStruct.gs": "DFDDE97F127BE645DAFA19EA63667847EB1C9B59D353A687ED012D95478089B1", - "samples/DataStructErgonomics.gs": "02DF9056A7DC2CC08EC85A485AB43CE477B5716471B83293CB51199A5CA9BF69", - "samples/DefaultExpression.gs": "F8CA98ED2532BF73C3C0DBC106B57C249F96F71A0264C30184331EE1551DD065", - "samples/DefaultInterfaceMethods.gs": "2C1AF180744995F1215ECE844861E1853393AC59137FA8F8E17ACC9D2A5FBD7A", - "samples/Defer.gs": "28BBF322E9214637F1A7DEEE895B9853B5BD8839E07642B195E88AC5736EE34E", - "samples/Deinit.gs": "356EDACB2D51D6C1DF7D03C3DCDEFF4370F71E3536717057C25EA949E11DE939", - "samples/DelegateCallSyntax.gs": "0C783279638E75557A5758394B9E5B284D7B5D8CDA1FA4201106FFCBE8FE2F8B", - "samples/DiscriminatedUnion.gs": "24804C81F08D300AB9A484B29F80A38DC4A3A30E492A83EA720B81F76BF15F84", - "samples/Enum.gs": "9EB68E0FE2E675308B2877FCE4BF8FD069EC741932D400550A028E7AE5005307", - "samples/EventSubscription.gs": "7E78F09EEA9974F2C374C26D5B01E69EE72615B335E9E9C3A391211371FED4EE", - "samples/Exceptions.gs": "0B095212A5B37A85FF1D6937006674C6FE5B44B93D1353634E9C006B0F38F260", - "samples/Exhaustiveness.gs": "983B49359B00ADF43DF1DFA47A886B9264C1B49F9D134F471E8B80604B128BBA", - "samples/ExplicitConstructor.gs": "AD6E02EA495283572BBFB1D3548775FC299E41F64EA70733A3984E3ECDA54335", - "samples/ExpressionEval.gs": "50498E0DA7318CE0D7F1FE70F430262CAD65B795E5C088C8CBB4EDE42982C049", - "samples/ExtensionFunctions.gs": "5F8824A4C81796128DD8EBDA778E39D83A07021306461D99AFAB49EF9E6B2C94", - "samples/ForIn.gs": "28C44E345F420A64304392EC6FAC09972369D3F64F591012373E995D3CB3C505", - "samples/FriendlyNumericAliases.gs": "E719B98388BD6400EC3786D58089082225FF6802AD16AD66CCD496AC064B4313", - "samples/FuncToDelegate.gs": "1DFB41D919D5CAA0D09B27BA1F4844D13B2F7461156D4B4CD6081E65AAA022C7", - "samples/FuncToSystemDelegate.gs": "14F13DA05D069656E418F27B808DCF3895FC5C1A7DE6A9EEFAD8DBC9FA407129", - "samples/GenericConstruction.gs": "06898937A325DBEFE0AC824C8C55433F0DEAF86D3FA2B0F56CB5EBD63E4B27C5", - "samples/GenericEquality.gs": "C8FC37104635E7E683791CE247926BF75A55693C84BB3CDB0C8D2483C11990FF", - "samples/GenericExtensionFunctions.gs": "B2B2F7D753BF65DE429C01D8947BA0B3D2009164D3B79CF0DFC8358D171C2110", - "samples/GenericMethodDelegates.gs": "F5D13C49277DA772FDBFA9D5A4C8C63454AE617F882ED97CBBF6626C33370EB2", - "samples/GenericMethodTypeArgs.gs": "E77A417A7D6BF56C288F7CB922938DB3CFFD51B6B21C5657C41761E0CBE7A6DE", - "samples/GenericMethodUserTypeArg.gs": "EC149474B2C0F3997248CD6A5C5170FC01EAA12BC24E6CF3B2A05DC9A6E4CA46", - "samples/GenericMethods.gs": "7C1A0DC149B121DA99BDD198CFFDF9DF22F30F06EA44AD81B97ACE4FE9976FB4", - "samples/GenericNamedDelegate.gs": "1A2A7F29D0C93744666174FDFA687FA525A26A55460AF7F1F69D2E193D54942D", - "samples/GenericTypeParameterAsTypeArgument.gs": "7412A4BAECCA415357F5071BBB08AAA24790FECCC78034D85832F20E33FA96F0", - "samples/GetOnlyCollectionInit.gs": "6CE360164F519D83E43F80CCA9DBA675232DDF8240913D34378F371FB35645E9", - "samples/GoBuiltinsGated.gs": "18363901A8B5DF062A6AF95335DB7B341795723877F6420A647B83D65207BD3B", - "samples/GoChannelsGated.gs": "B7B095745161A98E914E6425EAFDC19507B6E6B3B9FCBD7A59D724515EE69BED", - "samples/GoScope.gs": "E1EB2AC2DE9EDB29C4A69CCB19322D99EF101BD0565CC293DAC19D5137A5ACA1", - "samples/GsharpExtensionsMixed.gs": null, - "samples/GsharpExtensionsOptional.gs": null, - "samples/GsharpExtensionsSequences.gs": null, - "samples/HelloWorld.gs": "9DAFFE9E74FF62950AE0E02379CBBAC847BAB485674831D4767634CFA51B81AB", - "samples/IfExpression.gs": "69AD73A147BD4EB61B7AA78E3B347DEA2EBC4105A8900BAEA2576C57E075FAB7", - "samples/IfLetGuardLet.gs": "EAB514563DA0605416E524FF5535597261FCDB31706518A87192A2BFDB368EB5", - "samples/ImplicitImport.gs": "019014F35C01A0DC8D43ED116C4F81F318B582BBAFC82128C7AD9A8FEEDEBB7D", - "samples/ImportAlias.gs": "973A6FB17B4D34341C5E5C96B83041BCDBD068378312E6B2177AF901F412104F", - "samples/ImportedBaseClass.gs": "8688D14D78251D37836E50126C13CD91A9C0F3264F4B122E91777A85833536AE", - "samples/ImportedTypeCtorInFunc.gs": "C187F644D893D30073CB7E41166AB21144DF195FBD27FFF849A0720C846368B0", - "samples/InPlaceElementWrites.gs": "93BC6D7CEBA37BC669D8601A9EA8D33EBFA3F9E686029A4563E8937D2EDD7ACE", - "samples/InlineStruct.gs": "F0833664B542B544EDE3E934554CB4D66B056E8D67DB806A5CC9620EC834785C", - "samples/InterfaceDiamondDisambiguation.gs": "3691CB37EB58A340C122427770AE5B80417DD5CBFC42BB53229EB7B2B1DE6158", - "samples/InterfaceUpcast.gs": "403780EB1A42B293B9B77A857AF480551D059FE429625913C57D295D0AACC180", - "samples/InterpolatedString.gs": "010B2E93CE34DBF49CE280C0362C22A6020C4A48192B4E7CFF2F6F63FCC270D8", - "samples/InterpolatedStringFormat.gs": "C73DA66CB5E9A6FF1AA0E72DE32F7B7877D1CCFB41B64D3638D8367629BB1F5C", - "samples/InterpolatedStringFormattable.gs": "6F23099B3CEE377159A9541002EB4B008552A44A6DF12DECECC8BB08C34AFA14", - "samples/InterpolatedStringRichHoles.gs": "5B4939468602A98C9249110F54F40E47C55F1FA1845D960C3461980076641B78", - "samples/LambdaBindingInference.gs": "4EA4DF5927ADFBB7AB974DA02361651F41F356F17D27F0BF94AD32D5FE72336F", - "samples/LinqExtensions.gs": "A1FD7A91808793471A010D719DA7E29B792775BE8B3DA6C7BD651B6838A39A20", - "samples/Loop.gs": "66F668F408F165994D3BE172E7F9B1EDD39C00B6A8FA01FB390F59A3649D43EC", - "samples/MapForIn.gs": "FB4D48385465E858927DA8934F587217B5048D333CD5FB8A38E8E0FDEEDF7B10", - "samples/MethodGroupToDelegate.gs": "4AA53D7310C79AAB27D025A26BAEDD4ADCAF1211F19711001707DBFE509B9634", - "samples/MethodsWithReceivers.gs": "2AB3D118BAFE4A9E11D89D94733DEDBF6FEF2AEFB3AB6FF69B34A4B515D9EF09", - "samples/NamedArguments.gs": "C3D5E3829DCD3DEBBEBC4FAE60AF930FD2DA862FF840F0CCFFD43660289EE812", - "samples/NamedDelegate.gs": "F7C3D38CA6BE8FEC0F98CE7ED6AE77343C4867B9F86DC19A5ACB1291DA0D7C34", - "samples/NamedTupleElements.gs": "2DB9390C95025E9AC4E445CD2A972885C4225AA1DC29BA706AB7DBE84A3C4DD9", - "samples/NestedTypeOfConstructedGeneric.gs": "40DD33E1BCD31F007040A3C6E1C83D1611F1AFF7B2D0174A0986FB0CAB159A2C", - "samples/NullCoalescingAssignment.gs": "5A7F739F1950C9913B1353D4AF621D5F6464BA982C90E601CF849E0DB33CBBA0", - "samples/NullConditionalIndexing.gs": "083AF08BEBCE0CAB6BA39E813189134F8F1080465C3A6A5C633325FB785DA356", - "samples/NullableFlow.gs": "CCA072FA058E8C360237B638EBD913DA51F9509D00366E282F7478BCE6D172F8", - "samples/Operators.gs": "61FA0050F63CBAE8E4CDF86692048218D5D69CBEF295B3034A3AA95443F42F74", - "samples/OptionalExtensionArgs.gs": "750976CFCB6F9F4D4095CB9CABC5737F422B25C2BD80BB458EBC647131D218EE", - "samples/PInvoke.gs": "2784B2CAA68929D7D21CD075D0C835FD8A0F8339158B460A5DDE4490044A5AAC", - "samples/PInvokeFunctionPointer.gs": "85F9F7526E454628F8A78F854697B847E9A157B063E5C3E37E1B640196C78413", - "samples/PInvokeLibraryImport.gs": "2AC47FFA0E442DC6AB6205823294AA7A4564E5E65983C5F8EF6B62C6F7EF9A30", - "samples/PInvokeLibraryImportStringReturn.gs": "B41D5A863093742DAF4C89597B8A9D3D844C3FF7E8025FE7A2AF69889A207172", - "samples/PInvokeMarshalAs.gs": "4BA315A048843E608893E7D3F6139A56E4EF41C1DE7FD0499EDB5DFA4B3E67D2", - "samples/PInvokeRefOutIn.gs": "2130DBB30680B1F473420737D52C1C3A50007EF2ACAFC23B5A7F1BFB8DFCE045", - "samples/PInvokeStructMarshalling.gs": "CA2056A87B7218A47A850E842BF5356F034EC0C4947BAA6ACE888A36CC53D65F", - "samples/ParenthesizedReceiver.gs": "1C228E94F9B6A692A3E4B0669AC046A2E15EA53936E489D596D5A5933796B81C", - "samples/PatternSwitch.gs": "E7BF01BD38F92CFFAD6632270CA2FA54B6E5A6DF31DF1AC83C6D6162014E59B2", - "samples/Patterns.gs": "86597C1391F3D2A20FD8637F273013E33ABD0781890DF3CE95507120B53E8BC5", - "samples/PortScan.gs": "F6BCA823B5C2250BEDA73E6E197C6DD0D2C591BC80C49BA23C1DDDA0BBC58082", - "samples/PrimaryCtorVariadic.gs": "226429D1F404CF3AAFAB62AB33D3DA61BA7A95CA5D068248B5479354AFFEBD1D", - "samples/PrivateInterfaceHelpers.gs": "42F831380EFB8FFECF6F435E45BDC6745E714E651DEA7DF7175EED0D1E4A2847", - "samples/RawString.gs": "72CFDBA31D9FD766C6D31DB0B34585BE6E02B68620D95A5D67EFCEDCA730A1F9", - "samples/Records.gs": "54C116CD3D6A6542DD4060054C61B7C64796F77F07EBFC99B118E316ECE1BAAF", - "samples/RefIncrement.gs": "349907056925E3A0235511AEE41150311CBFE26B13948712BB9CCDA82393CAFB", - "samples/RefStructGenericField.gs": "9E129FDF9B516DC4E471ADED6FE454690B2F2730886D8826EDA358E51CD6F8A9", - "samples/RefStructSpan.gs": "79F9B1BE3ABC616BC7B7D680AB5C02B4B1C01F55373E01AF683705839025A513", - "samples/ReifiedGenerics.gs": "FD47BBBBAB34A938F4EE42F2A57E2F31621055FC32284E32340F90D0F7B40EA1", - "samples/Sealed.gs": "B1E8635EBC7D10280EE4414242AA52856EDA35F10B8437F72AB726E2FAFA8E17", - "samples/Select.gs": "BAF0BA0CC70A848C262E3EA5E7F92C27F0261B6D17D59119300D65800606F9CA", - "samples/SliceLinqUntypedLambda.gs": "2C1EF66E4A1237AFE72EEF74AC885C0D9380C9AD3805702AA2A8B162DBCCF625", - "samples/SlicePattern.gs": "7315C8EAE121B3996CCF679A7F33AF7CBE474D1C1EA8DD263516AC7F3D035B12", - "samples/Slices.gs": "161082791B12CDCEC5A9ACDD324914E183F9293026DEBCF301A47A4D6E6BD1F3", - "samples/SmartCast.gs": "A654215710CE817D667948C364E1B0CB9056116281C1A935F995A3BA5BEC60F3", - "samples/SmartCastExtensions.gs": "AB9900233C9A6E1C14AAE1FAA6D894FA72A9CB5F942B981027C72584CB75EC56", - "samples/SpanComprehensive.gs": "5423D603776DC37529A4AE96B9D89CF88FD2BEAFB3C8A624E510B5FBF39D2F26", - "samples/SpanIndexing.gs": "A583E25AD6DB3256228B6407D401474010D9A552BCE31087F0D4D695B76F2D8A", - "samples/StaticVirtualInterfaces.gs": "F2F8DEB8DABBC47CCEA9ADCDA0E72F34034EABC10E88845706FAB4607EC7FCC2", - "samples/Struct.gs": "2C4B22BE2169469862358575B566F34EC72AFFADE382D76D350B30C86293389A", - "samples/SwitchExpression.gs": "C8FFEB7D57C2846FCF2C94A492A41D4026D2B41E26A6138C0BB981B1AD7E87FB", - "samples/TryParseOutVar.gs": "5104914A5581C6F3DD6E550A695FE4B7CC376DB6388DE99178C7A69DF07802C6", - "samples/TupleEquality.gs": "8A842FB82FC75C6F5433038E57EE1425445A3D88B2C16D2E8088B1D4E8E6E5CB", - "samples/TupleSequenceIterators.gs": "AABF96522EAF606F34C57FC7CE2F12493039C436607982097CED2947FCAA740B", - "samples/UserRefStruct.gs": "79ED3397503DAF743D2552CDF6CEEE2A72E7412D0B03D78D22C060FDE71479B4", - "samples/ValueTypeObjectMethods.gs": "EDF82DBEE14C7ABB0CA990D65A9F6A5C7C3898C820195DDC615980FB7E04FDAD", - "samples/Variadic.gs": "BD2FC989276DBD0B581364A36C45210D84B29D0A611B5C8C915F0590F74598D5", - "samples/VariadicDelegate.gs": "F719D2D1B518234856D7425DDD126C248FF508E042A6AD114E964379A888E108", - "samples/VariadicMethods.gs": "740FE847DC8AF45B1FBAE51B1D71992F336F8AD798AE65A8B869EDF14A489D0C", - "samples/WhileAndLabeledLoops.gs": "2016A430C94470D1200ED5F394F81CFD88337682D42DF26397D8FC3F4F01F4BE", - "samples/ZeroValues.gs": "B0278AFA5E958D5F8208CDD3C257738B936B919EE8F872DAF6A4AB6F538CF44E", - "samples/refactoring-baseline/AwaitInFieldAssignment.gs": "B3BBCBABABDF14F721B666A571399EA71C0D2D0C9679B41D079ACF47490C281A", - "samples/refactoring-baseline/AwaitInIndexAssignment.gs": "91BD677707E1067179A050BE9EE6A9A5A15B33C6F71CFB428DE2B93FE3ED7336", - "samples/refactoring-baseline/AwaitInUnary.gs": "C787D8F8AD272EFEAF0DFCAAE42E0D89F7BEED266A8A7ACFC3164F7DC552FFDA", - "samples/refactoring-baseline/ClosureCaptureRefTypeField.gs": null, - "samples/refactoring-baseline/EnumBitwise.gs": "CA776607253DB67B113383C95F927F4BDABAF0FBC3DE83A8BFA1FB0566DF5C83", - "samples/refactoring-baseline/ForInIEnumerable.gs": "B648959671CDC5D328CE656F856CA4C5B1FF25A836398836430EB02262E1A7A3", - "samples/refactoring-baseline/GenericMethodSpec.gs": "B0A9AF4D3FAC32E58F4CC355049AD03BDBF23C86131262749E05010739BB0B25", - "samples/refactoring-baseline/NullablePropertyRead.gs": "C15C2A22A1A86E7AF9670F1A341D50907B37989C132312CC5D4225D251E749CA", - "samples/refactoring-baseline/NullableValueMember.gs": "48D2E71A472BFCB1B95BAEE1B8DCFA8A64652E6877457D3B3DA7579103817E30", - "samples/refactoring-baseline/ReadOnlyAttr.gs": "01EAFA58553D086FA5F7A0D06D6A440B9827A1DF7D7C09B1C6E640B1BEB0F7E5", - "samples/refactoring-baseline/RefStructByRefLike.gs": "4CF6FB2340F4284A7C9B8BBDE87515621F791207CC5326B9B7FD6AE56A60C186", - "samples/refactoring-baseline/ReturnInTryFinally.gs": "C3B60BC9B602B891C3AE1B24AAF9D70FE9A2D3BC2EE70C85F79B948FF1C81438", - "samples/refactoring-baseline/ShortCircuitAnd.gs": "93113A1DAC91F6561EC9E0B45F5719E838BA3B2B0D82C1B842351D0E706F351D", - "samples/refactoring-baseline/ShortCircuitOr.gs": "B764F7EB658451564EDEA35AE111344D2F9AAF046FB879C55DDC02133A344281", - "samples/refactoring-baseline/YieldInTryFinally.gs": "34608F80955B1D1C89F2C9A15A841F5CB3AAD09DF2D676DE5AC3A7B84F3195A7" -} From 83928dcc20944d4aa2b789c14bab7e5d84d343a3 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 28 Aug 2026 17:10:55 -0700 Subject: [PATCH 4/5] cs2gs + LS: preserve C# tuple element names + completion (ADR-0172 Phases C+D, #3501) (#3623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **Stacked on #3622** (named tuple elements, Phases A+B) — Phase C of ADR-0172: cs2gs stops erasing C# tuple element names, delivering the #3501 readability win across the ~830 named-tuple declaration lines in the self-migration corpus. - **Types** print name-first: `(int Line, int Column)` → `(Line int32, Column int32)` (`CSharpTypeMapper` keeps Roslyn's `TupleElements` names; a default `ItemN`-at-position counts as unnamed; the ADR-0115 §B.4 name-drop Info diagnostic is retired). Names survive the nullability-promotion rebuild sites too (oblivious promotion, whole-type nullable wraps). - **Literal labels** survive: `(Count: 3, Name: "three")` via `NameColon`. - **Named element access stays by-name**: `item.Price * item.Quantity` round-trips verbatim where it previously lowered to `item.Item2 * item.Item3`; default positional `.ItemN` access still normalizes positionally. - `TupleTypeReference` / `TupleLiteralExpression` gain optional element-name lists; the printer renders both forms. ### Verification - New `Adr0172NamedTupleTranslationTests` (6): name-first types, by-name access, positional access unchanged, literal labels, unnamed tuples unchanged, names inside generics — every snippet re-binds through the real gsc (exercising the #3622 front end). - Existing tuple-behavior tests updated to the named surface (Issue1914 alias, Issue2469 nullability ×6, Issue3615 receiver probe, oblivious-promotion sinks); coverage-matrix notes moved into the inventory and `docs/cs2gs-coverage-matrix.md` regenerated via `cs2gs coverage --write`. - **Full Cs2Gs.Tests green** (2454 tests; the two last local failures were a stale `out/bin/Release` gsc found by `FindCompiler` — rebuilt — and a stale expectation). - **Corpus diagnostic-run 18/20 green** with the branch gsc (CompileGap-Library is the known wontfix; L3-Library's test-parity failure is the documented stale-SDK-nupkg trap — the parity stage rebuilds with the packaged gsc, which predates ADR-0172; CI builds the SDK from the same commit). Translated L1 now reads `total += item.Price * item.Quantity` with `List[(Name string, Price int32, Quantity int32)]`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs --------- Co-authored-by: Claude Fable 5 --- docs/cs2gs-coverage-matrix.md | 10 +- docs/cs2gs-coverage-matrix.md.actual | 353 ++++++++++++++++++ src/LanguageServer/HoverComputer.cs | 34 ++ .../CompletionHandlerTests.cs | 29 ++ .../cs2gs/Cs2Gs.CodeModel/Ast/GExpression.cs | 11 +- .../Cs2Gs.CodeModel/Ast/GTypeReference.cs | 18 +- .../Cs2Gs.CodeModel/Printing/GSharpPrinter.cs | 12 +- .../Adr0172NamedTupleTranslationTests.cs | 154 ++++++++ .../Issue1914TupleAliasDirectiveTests.cs | 6 +- ...TupleElementNullabilityTranslationTests.cs | 26 +- ...sue3615TupleReceiverPromotionProbeTests.cs | 2 +- .../ObliviousPromotionSinkCompilationTests.cs | 2 +- .../CSharpToGSharpTranslator.ControlFlow.cs | 2 +- .../CSharpToGSharpTranslator.Expressions.cs | 18 +- .../CSharpToGSharpTranslator.Nullability.cs | 2 +- .../CSharpToGSharpTranslator.Patterns.cs | 5 +- .../Cs2Gs.Translator/CSharpTypeMapper.cs | 26 +- .../coverage/csharp-construct-inventory.json | 11 +- 18 files changed, 662 insertions(+), 59 deletions(-) create mode 100644 docs/cs2gs-coverage-matrix.md.actual create mode 100644 tools/cs2gs/Cs2Gs.Tests/Adr0172NamedTupleTranslationTests.cs diff --git a/docs/cs2gs-coverage-matrix.md b/docs/cs2gs-coverage-matrix.md index f0daad9c3..fc9923b7e 100644 --- a/docs/cs2gs-coverage-matrix.md +++ b/docs/cs2gs-coverage-matrix.md @@ -92,7 +92,7 @@ Drift fails `ConstructInventoryGoldenTests`. Do not edit by hand. | EmptyStatement | EmptyStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/EmptyStatement.cs | | | | EnumDeclaration | EnumDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/EnumDeclaration.cs | | Member values (explicit or implicit) and [Flags] are preserved (issue #1912, fixed). | | EnumMemberDeclaration | EnumMemberDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/EnumMemberDeclaration.cs | | Explicit/negative/[Flags] bit-shift-or/alias values resolve via the semantic model's IFieldSymbol.ConstantValue and are emitted as an explicit G# `= value` (new language feature, issue #1912, fixed). | -| EqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/EqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering. | +| EqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/EqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); element names never affect equality. | | EqualsValueClause | EqualsValueClauseSyntax | ADR-0115 §B.3 | | | | | | EventDeclaration | EventDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventDeclaration.cs | | Explicit add/remove accessor event maps to the G# event declaration's explicit-accessor form (ADR-0052 §2); a source-declared named delegate handler type keeps its name (issue #1960 item 3) instead of the anonymous arrow form. | | EventFieldDeclaration | EventFieldDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventFieldDeclaration.cs | | Field-like event maps to the G# field-like event declaration (ADR-0052 §2); a source-declared named delegate handler type keeps its name (issue #1960 item 3) instead of the anonymous arrow form. | @@ -159,7 +159,7 @@ Drift fails `ConstructInventoryGoldenTests`. Do not edit by hand. | NameColon | NameColonSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/NameColon.cs | | Named arguments use the G# name: value form (ADR-0080). | | NameEquals | NameEqualsSyntax | ADR-0115 §B.16 | | | | | | NamespaceDeclaration | NamespaceDeclarationSyntax | ADR-0115 §B.1 | | | | | -| NotEqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/NotEqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering. | +| NotEqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/NotEqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); element names never affect equality. | | NotPattern | UnaryPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/NotPattern.cs | | | | NullLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/NullLiteralExpression.cs | | | | NullableType | NullableTypeSyntax | ADR-0115 §B.12 | | | | T? maps to G# nullable spelling. | @@ -226,9 +226,9 @@ Drift fails `ConstructInventoryGoldenTests`. Do not edit by hand. | ThrowStatement | ThrowStatementSyntax | ADR-0115 §B.27 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ThrowStatement.cs | | | | TrueLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/TrueLiteralExpression.cs | | | | TryStatement | TryStatementSyntax | ADR-0115 §B.27 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/TryStatement.cs | | | -| TupleElement | TupleElementSyntax | ADR-0115 §B.12 | | | | G# tuple types (T1, T2). | -| TupleExpression | TupleExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs | | | -| TupleType | TupleTypeSyntax | ADR-0115 §B.12 | | | | G# tuple types (T1, T2). | +| TupleElement | TupleElementSyntax | ADR-0115 §B.12 | | | | G# tuple types; ADR-0172: C# element names are preserved name-first ((Line int32, Column int32)), and named access stays by-name. | +| TupleExpression | TupleExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs | | ADR-0172: C# element labels ((Line: 1, …)) are preserved as G# labeled elements. | +| TupleType | TupleTypeSyntax | ADR-0115 §B.12 | | | | G# tuple types; ADR-0172: element names preserved (was: dropped with an Info diagnostic). | | TypeArgumentList | TypeArgumentListSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeArgumentList.cs | | | | TypeConstraint | TypeConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeConstraint.cs | | | | TypeOfExpression | TypeOfExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/TypeOfExpression.cs | | | diff --git a/docs/cs2gs-coverage-matrix.md.actual b/docs/cs2gs-coverage-matrix.md.actual new file mode 100644 index 000000000..f0daad9c3 --- /dev/null +++ b/docs/cs2gs-coverage-matrix.md.actual @@ -0,0 +1,353 @@ +# cs2gs C# construct coverage matrix + +Generated from `tools/cs2gs/coverage/csharp-construct-inventory.json` by `cs2gs coverage --write`. +Drift fails `ConstructInventoryGoldenTests`. Do not edit by hand. + +| Status | Count | +| --- | --- | +| Unclassified | 0 | +| Translated | 240 | +| Lowered | 19 | +| UnsupportedByDesign | 56 | +| Gap | 6 | + +## Translated (240) + +| Kind | Node type | Rule | Rationale | Fixture | Issue | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| AccessorList | AccessorListSyntax | ADR-0115 §B.11 | | | | | +| AddAccessorDeclaration | AccessorDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventDeclaration.cs | | The explicit add/remove accessor body of an event declaration (ADR-0052 §2); translates like any other accessor body. | +| AddAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/AddAssignmentExpression.cs | | | +| AddExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/AddExpression.cs | | | +| AddressOfExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B.30 | | | | | +| AliasQualifiedName | AliasQualifiedNameSyntax | ADR-0115 §B.12 | | | | | +| AllowsConstraintClause | AllowsConstraintClauseSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/AllowsConstraintClause.cs | | C#13 allows ref struct passes E2E (grid G08). | +| AndAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/AndAssignmentExpression.cs | | | +| AndPattern | BinaryPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/AndPattern.cs | | | +| AnonymousMethodExpression | AnonymousMethodExpressionSyntax | ADR-0115 §B.20 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/AnonymousMethodExpression.cs | | | +| Argument | ArgumentSyntax | ADR-0115 §B | | | | | +| ArgumentList | ArgumentListSyntax | ADR-0115 §B | | | | | +| ArrayCreationExpression | ArrayCreationExpressionSyntax | ADR-0115 §B.16; ADR-0164 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ArrayCreationExpression.cs | https://github.com/DavidObando/gsharp/issues/1893, https://github.com/DavidObando/gsharp/issues/3354 | 1-D, jagged, and rank>1 green. Rectangular `new T[d0, d1, ...]`, explicit/implicit/target-typed initializers, and element access map to native `[d0, d1]T` / `a[i, j]`; fields, parameters, returns, and imported APIs require no tracked-local state or flattening. | +| ArrayInitializerExpression | InitializerExpressionSyntax | ADR-0115 §B.16 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ArrayInitializerExpression.cs | | | +| ArrayRankSpecifier | ArrayRankSpecifierSyntax | ADR-0115 §B.16 | | | | | +| ArrayType | ArrayTypeSyntax | ADR-0115 §B.16 | | | | | +| ArrowExpressionClause | ArrowExpressionClauseSyntax | ADR-0115 §B.5 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/ArrowExpressionClause.cs | | Expression-bodied members (ADR-0131). | +| AsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/AsExpression.cs | | | +| Attribute | AttributeSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/AttributeList.cs | | User-defined attribute classes blocked by gsc GS0200 (issue #1921). | +| AttributeArgument | AttributeArgumentSyntax | ADR-0115 §B.11 | | | | | +| AttributeArgumentList | AttributeArgumentListSyntax | ADR-0115 §B.11 | | | | | +| AttributeList | AttributeListSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/AttributeList.cs | https://github.com/DavidObando/gsharp/issues/1913 | BCL attributes and user-defined attribute classes (issue #1921, fixed) green; parameter attributes silently dropped and generic attributes emit non-parsing G# (issue #1913). | +| AttributeTargetSpecifier | AttributeTargetSpecifierSyntax | ADR-0115 §B.11 | | | | | +| AwaitExpression | AwaitExpressionSyntax | ADR-0115 §B.23 | | tools/cs2gs/corpus/grid/G10-Async-Console/Constructs/AwaitExpression.cs | | Task/Task green incl. ConfigureAwait and await foreach; ValueTask blocked by gsc (issue #1918); async Main mislowers (issue #1904). | +| BaseConstructorInitializer | ConstructorInitializerSyntax | ADR-0115 §B.28 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/BaseConstructorInitializer.cs | | | +| BaseExpression | BaseExpressionSyntax | ADR-0115 §B | | | | base.M() virtual calls (issue #986, resolved). | +| BaseList | BaseListSyntax | ADR-0115 §B.6 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/BaseList.cs | | | +| BitwiseAndExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/BitwiseAndExpression.cs | | | +| BitwiseNotExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/BitwiseNotExpression.cs | | | +| BitwiseOrExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/BitwiseOrExpression.cs | | | +| Block | BlockSyntax | ADR-0115 §B.2 | | | | | +| BracketedArgumentList | BracketedArgumentListSyntax | ADR-0115 §B | | | | Issue #942 (resolved). | +| BracketedParameterList | BracketedParameterListSyntax | ADR-0115 §B.11 | | | | User indexers (issue #944). | +| BreakStatement | BreakStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/BreakStatement.cs | | | +| CasePatternSwitchLabel | CasePatternSwitchLabelSyntax | ADR-0115 §B.33 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/CasePatternSwitchLabel.cs | | | +| CaseSwitchLabel | CaseSwitchLabelSyntax | ADR-0115 §B.33 | | | | | +| CastExpression | CastExpressionSyntax | ADR-0115 §B.17 | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/CastExpression.cs | | | +| CatchClause | CatchClauseSyntax | ADR-0115 §B.27 | | | | | +| CatchDeclaration | CatchDeclarationSyntax | ADR-0115 §B.27 | | | | | +| CatchFilterClause | CatchFilterClauseSyntax | ADR-0115 §B.27 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/CatchFilterClause.cs | | A when-filter with an overlapping later sibling catch has no faithful lowering (issue #1724 area). | +| CharacterLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/CharacterLiteralExpression.cs | | | +| CheckedExpression | CheckedExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/CheckedExpression.cs | https://github.com/DavidObando/gsharp/issues/1881 | | +| CheckedStatement | CheckedStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/CheckedStatement.cs | https://github.com/DavidObando/gsharp/issues/1881 | gsc gained native checked/unchecked expression + block support (issue #1881); overflow semantics preserved, including the overflow-in-try/catch(OverflowException) sub-case. Stdout parity verified (grid G03). | +| ClassConstraint | ClassOrStructConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/ClassConstraint.cs | | | +| ClassDeclaration | ClassDeclarationSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/ClassDeclaration.cs | | C#12 primary ctors now map to native G# primary constructors (issue #1909, resolved); partial parts across multiple declarations/files now merge into one G# type declaration (issue #1910, resolved). | +| CoalesceAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/CoalesceAssignmentExpression.cs | | Statement forms stay native. Value-position forms use a block/if expression that preserves conditional RHS evaluation and captures non-trivial storage only as needed (issue #3347). Nullable value-type targets emit verifiable IL (issue #1916). | +| CoalesceExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/CoalesceExpression.cs | | Binary ?? (issue #941, resolved). | +| CollectionExpression | CollectionExpressionSyntax | ADR-0115 §B.36 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/CollectionExpression.cs | https://github.com/DavidObando/gsharp/issues/1897 | Array targets green; List targets fail conversion; spread unsupported (issues #1897). | +| CollectionInitializerExpression | InitializerExpressionSyntax | ADR-0115 §B.16 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/CollectionInitializerExpression.cs | | | +| CompilationUnit | CompilationUnitSyntax | ADR-0115 §B.1 | | | | | +| ComplexElementInitializerExpression | InitializerExpressionSyntax | ADR-0115 §B.16 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/CollectionInitializerExpression.cs | | | +| ConditionalAccessExpression | ConditionalAccessExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ConditionalAccessExpression.cs | | Null-conditional ?. / ?[. | +| ConditionalExpression | ConditionalExpressionSyntax | ADR-0115 §B.26 | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ConditionalExpression.cs | | | +| ConstantPattern | ConstantPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/ConstantPattern.cs | https://github.com/DavidObando/gsharp/issues/1923 | Boxed-object subjects fail in gsc (issue #1923); typed subjects green. | +| ConstructorConstraint | ConstructorConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/ConstructorConstraint.cs | | new T() under new() works (past gap #988 resolved). | +| ConstructorDeclaration | ConstructorDeclarationSyntax | ADR-0115 §B.28 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/ConstructorDeclaration.cs | | | +| ContinueStatement | ContinueStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ContinueStatement.cs | | | +| ConversionOperatorDeclaration | ConversionOperatorDeclarationSyntax | ADR-0115 §B.31 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/ConversionOperatorDeclaration.cs | | | +| DeclarationExpression | DeclarationExpressionSyntax | ADR-0115 §B.30 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/DeclarationExpression.cs | | | +| DeclarationPattern | DeclarationPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/DeclarationPattern.cs | | x is T t binder (issue #993, resolved); emitted verbatim as a native G# pattern variable when it qualifies (ADR-0166, issue #3409). | +| DefaultConstraint | DefaultConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/DefaultConstraint.cs | | Translates to an unconstrained G# [T]; gsc override/inference bug fixed (issue #1931). | +| DefaultExpression | DefaultExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/DefaultExpression.cs | | | +| DefaultLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/DefaultLiteralExpression.cs | | | +| DefaultSwitchLabel | DefaultSwitchLabelSyntax | ADR-0115 §B.33 | | | | | +| DelegateDeclaration | DelegateDeclarationSyntax | ADR-0059 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/DelegateDeclaration.cs | | Maps to the G# named delegate declaration `delegate Name(params) R;` (issue #3510; originally ADR-0059), including a generic delegate's type-parameter list (any arity, with constraints) — `delegate Name[T constraint](...) R;` (issue #1960 item 1). | +| DestructorDeclaration | DestructorDeclarationSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/DestructorDeclaration.cs | | | +| DiscardDesignation | DiscardDesignationSyntax | ADR-0115 §B.30 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/DeclarationExpression.cs | | | +| DiscardPattern | DiscardPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/DiscardPattern.cs | | | +| DivideAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/DivideAssignmentExpression.cs | | | +| DivideExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/DivideExpression.cs | | | +| DoStatement | DoStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/DoStatement.cs | | | +| ElementAccessExpression | ElementAccessExpressionSyntax | ADR-0115 §B | | | | Issue #942 (resolved). | +| ElementBindingExpression | ElementBindingExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ElementBindingExpression.cs | | Null-conditional ?. / ?[. | +| ElseClause | ElseClauseSyntax | ADR-0115 §B | | | | | +| EmptyStatement | EmptyStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/EmptyStatement.cs | | | +| EnumDeclaration | EnumDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/EnumDeclaration.cs | | Member values (explicit or implicit) and [Flags] are preserved (issue #1912, fixed). | +| EnumMemberDeclaration | EnumMemberDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/EnumMemberDeclaration.cs | | Explicit/negative/[Flags] bit-shift-or/alias values resolve via the semantic model's IFieldSymbol.ConstantValue and are emitted as an explicit G# `= value` (new language feature, issue #1912, fixed). | +| EqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/EqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering. | +| EqualsValueClause | EqualsValueClauseSyntax | ADR-0115 §B.3 | | | | | +| EventDeclaration | EventDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventDeclaration.cs | | Explicit add/remove accessor event maps to the G# event declaration's explicit-accessor form (ADR-0052 §2); a source-declared named delegate handler type keeps its name (issue #1960 item 3) instead of the anonymous arrow form. | +| EventFieldDeclaration | EventFieldDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventFieldDeclaration.cs | | Field-like event maps to the G# field-like event declaration (ADR-0052 §2); a source-declared named delegate handler type keeps its name (issue #1960 item 3) instead of the anonymous arrow form. | +| ExclusiveOrAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ExclusiveOrAssignmentExpression.cs | | | +| ExclusiveOrExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ExclusiveOrExpression.cs | | | +| ExpressionColon | ExpressionColonSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/ExpressionColon.cs | https://github.com/DavidObando/gsharp/issues/1891 | is-form green; switch-expression arm form unsupported (issue #1891). | +| ExpressionElement | ExpressionElementSyntax | ADR-0115 §B.36 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/CollectionExpression.cs | | | +| ExpressionStatement | ExpressionStatementSyntax | ADR-0115 §B | | | | | +| ExtensionBlockDeclaration | ExtensionBlockDeclarationSyntax | ADR-0115 §B.19 | | tools/cs2gs/corpus/grid/G13-Extensions-Console/Constructs/ExtensionBlockDeclaration.cs | https://github.com/DavidObando/gsharp/issues/1879 | C# 14 `extension(T x)`/`extension(T)` block members map onto the same target as a classic this-param extension method (ADR-0115 §B.19): an instance method/property lowers to a receiver-clause func (a property becomes a get-only func, since G#'s prop grammar has no receiver clause, with call sites rewritten to a zero-arg call); enum/owned receivers use ADR-0165's explicit extension receiver form; a static member becomes a plain shared member of the declaring class, with call sites rewritten from the extended type's name to the real owner. A settable instance extension property remains an explicit gap (grid G13). | +| FalseLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/FalseLiteralExpression.cs | | | +| FieldDeclaration | FieldDeclarationSyntax | ADR-0115 §B.3 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/FieldDeclaration.cs | | | +| FieldExpression | FieldExpressionSyntax | ADR-0051 §2 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/FieldExpression.cs | | | +| FileScopedNamespaceDeclaration | FileScopedNamespaceDeclarationSyntax | ADR-0115 §B.1 | | | | | +| FinallyClause | FinallyClauseSyntax | ADR-0115 §B.27 | | | | | +| FixedStatement | FixedStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FixedStatement.cs | https://github.com/DavidObando/gsharp/issues/1933 | Compiles end-to-end under the ilverify allow-unsafe policy (issue #1933); IL is unverifiable by design, not a gsc defect. | +| ForEachStatement | ForEachStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ForEachStatement.cs | | | +| ForEachVariableStatement | ForEachVariableStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs | https://github.com/DavidObando/gsharp/issues/1922 | Sync foreach tuple-deconstruction translates to first-class G# `for (a, b) in xs`; ValueTuple deconstruction now supported by gsc (issue #1922 fixed). await foreach still lowers via temp+let (no first-class async form). | +| FunctionPointerCallingConvention | FunctionPointerCallingConventionSyntax | ADR-0122 §9 / ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | managed/`managed`/`unmanaged[Cdecl|Stdcall|Thiscall|Fastcall]` translate; ADR-0095 v2 (issue #3611) adds the open CLR model — bare `unmanaged` (platform-default ABI) spells `unmanaged (T) -> R` and combined/non-legacy CallConv sets spell `unmanaged[Name, ...] (T) -> R` in source order. | +| FunctionPointerParameter | FunctionPointerParameterSyntax | ADR-0122 §9 / ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | | +| FunctionPointerParameterList | FunctionPointerParameterListSyntax | ADR-0122 §9 / ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | | +| FunctionPointerType | FunctionPointerTypeSyntax | ADR-0122 §9 / ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | delegate*<...>/delegate* managed<...> map to G#'s managed `*func(T) R`; delegate* unmanaged[Cdecl|Stdcall|Thiscall|Fastcall]<...> maps to G#'s raw `unmanaged[CC] (T) -> R`. Bare `delegate* unmanaged<...>` and a combined/custom `[CC]` list are an Unsupported/ByDesign sub-case (issue #1906). | +| FunctionPointerUnmanagedCallingConvention | FunctionPointerUnmanagedCallingConventionSyntax | ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | | +| FunctionPointerUnmanagedCallingConventionList | FunctionPointerUnmanagedCallingConventionListSyntax | ADR-0095 | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FunctionPointerType.cs | https://github.com/DavidObando/gsharp/issues/1906 | | +| GenericName | GenericNameSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/GenericName.cs | | | +| GetAccessorDeclaration | AccessorDeclarationSyntax | ADR-0115 §B.11 | | | | | +| GlobalStatement | GlobalStatementSyntax | ADR-0115 §B.11 | | | | Entry-class hoisting (T3): top-level statements. | +| GotoStatement | GotoStatementSyntax | ADR-0139 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/GotoStatement.cs | | | +| GreaterThanExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/GreaterThanExpression.cs | | | +| GreaterThanOrEqualExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/GreaterThanOrEqualExpression.cs | | | +| IdentifierName | IdentifierNameSyntax | ADR-0115 §B.12 | | | | | +| IfStatement | IfStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/IfStatement.cs | | | +| ImplicitArrayCreationExpression | ImplicitArrayCreationExpressionSyntax | ADR-0115 §B.16 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ImplicitArrayCreationExpression.cs | | | +| ImplicitObjectCreationExpression | ImplicitObjectCreationExpressionSyntax | ADR-0115 §B.25 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ObjectCreationExpression.cs | | | +| IndexExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B.36 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/IndexExpression.cs | https://github.com/DavidObando/gsharp/issues/1894 | Inline ^i green; Index-typed locals mis-lower (issue #1894, runtime crash). | +| IndexerDeclaration | IndexerDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/IndexerDeclaration.cs | | User indexers (issue #944). | +| InitAccessorDeclaration | AccessorDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/InitAccessorDeclaration.cs | | | +| InterfaceDeclaration | InterfaceDeclarationSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/InterfaceDeclaration.cs | | | +| InterpolatedStringExpression | InterpolatedStringExpressionSyntax | ADR-0115 §B.9 | | tools/cs2gs/corpus/grid/G14-Strings-Console/Constructs/InterpolatedStringExpression.cs | | | +| InterpolatedStringText | InterpolatedStringTextSyntax | ADR-0115 §B.9 | | tools/cs2gs/corpus/grid/G14-Strings-Console/Constructs/InterpolatedStringText.cs | https://github.com/DavidObando/gsharp/issues/1882 | Brace escapes {{ }} copied verbatim — parity-verified divergence (issue #1882). | +| Interpolation | InterpolationSyntax | ADR-0115 §B.9 | | tools/cs2gs/corpus/grid/G14-Strings-Console/Constructs/Interpolation.cs | | | +| InterpolationAlignmentClause | InterpolationAlignmentClauseSyntax | ADR-0115 §B.9 | | tools/cs2gs/corpus/grid/G14-Strings-Console/Constructs/InterpolationAlignmentClause.cs | | | +| InterpolationFormatClause | InterpolationFormatClauseSyntax | ADR-0115 §B.9 | | tools/cs2gs/corpus/grid/G14-Strings-Console/Constructs/InterpolationFormatClause.cs | | | +| InvocationExpression | InvocationExpressionSyntax | ADR-0115 §B | | | | | +| IsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/IsExpression.cs | | | +| IsPatternExpression | IsPatternExpressionSyntax | ADR-0115 §B.36 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/IsPatternExpression.cs | | Native boolean patterns; declaration and scalar var designations become G# pattern variables scoped to their definitely-assigned regions (ADR-0166, issues #3409/#3420). Reassigned binders keep the substitution/hoist lowerings. | +| LabeledStatement | LabeledStatementSyntax | ADR-0139 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/LabeledStatement.cs | | | +| LeftShiftAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LeftShiftAssignmentExpression.cs | | | +| LeftShiftExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LeftShiftExpression.cs | | | +| LessThanExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LessThanExpression.cs | | | +| LessThanOrEqualExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LessThanOrEqualExpression.cs | | | +| ListPattern | ListPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/ListPattern.cs | | is-test and switch-arm paths emit native G# list patterns when their subpatterns qualify. Element `var` binders emit native total patterns; typed elements emit native `TypePattern`. Deferral: gsc's `BindListPattern` only accepts array/slice-typed discriminants (not e.g. `List`), a pre-existing gsc scope boundary, not a cs2gs limitation. | +| LocalDeclarationStatement | LocalDeclarationStatementSyntax | ADR-0115 §B.3 | | | | | +| LocalFunctionStatement | LocalFunctionStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/LocalFunctionStatement.cs | | Static local functions call through their `let` binding directly; generic local functions translate to G#'s `let Name[T, ...] = func (...) ... { ... }` (issue #1886, fixed). | +| LockStatement | LockStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/LockStatement.cs | | | +| LogicalAndExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LogicalAndExpression.cs | | | +| LogicalNotExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LogicalNotExpression.cs | | | +| LogicalOrExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/LogicalOrExpression.cs | | | +| MemberBindingExpression | MemberBindingExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/MemberBindingExpression.cs | | Null-conditional ?. / ?[. | +| MethodDeclaration | MethodDeclarationSyntax | ADR-0115 §B.5 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/MethodDeclaration.cs | | Receiver-clause functions now retain their G# extension semantics across compiled assembly references (issue #1929). | +| ModuloAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ModuloAssignmentExpression.cs | | | +| ModuloExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/ModuloExpression.cs | | | +| MultiplyAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/MultiplyAssignmentExpression.cs | | | +| MultiplyExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/MultiplyExpression.cs | | | +| NameColon | NameColonSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/NameColon.cs | | Named arguments use the G# name: value form (ADR-0080). | +| NameEquals | NameEqualsSyntax | ADR-0115 §B.16 | | | | | +| NamespaceDeclaration | NamespaceDeclarationSyntax | ADR-0115 §B.1 | | | | | +| NotEqualsExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/NotEqualsExpression.cs | | Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering. | +| NotPattern | UnaryPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/NotPattern.cs | | | +| NullLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/NullLiteralExpression.cs | | | +| NullableType | NullableTypeSyntax | ADR-0115 §B.12 | | | | T? maps to G# nullable spelling. | +| NumericLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/NumericLiteralExpression.cs | | | +| ObjectCreationExpression | ObjectCreationExpressionSyntax | ADR-0115 §B.16 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ObjectCreationExpression.cs | | | +| ObjectInitializerExpression | InitializerExpressionSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/ObjectCreationExpression.cs | | | +| OmittedArraySizeExpression | OmittedArraySizeExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/OmittedArraySizeExpression.cs | | | +| OmittedTypeArgument | OmittedTypeArgumentSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/OmittedTypeArgument.cs | | nameof(List<>)/typeof(List<>) forms green (C#14); typeof of an unbound generic emits the bare generic-definition name (typeof(List)), which gsc's binder now resolves via arity-suffixed CLR lookup (issue #1915, fixed). | +| OperatorDeclaration | OperatorDeclarationSyntax | ADR-0115 §B.31 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/OperatorDeclaration.cs | | C#14 instance compound-assignment operators (op_AdditionAssignment and siblings) translate to the G# IN-BODY member form `func operator +=(x T)`, which emits the same instance, void-returning, specialname op_*Assignment method Roslyn does (issue #2834; supersedes the #1908 gap). They are deliberately NOT lifted to the top-level receiver-clause form used for binary operators, which would make them static. Equals(object) is-pattern override fails ilverify (issue #1917). | +| OrAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/OrAssignmentExpression.cs | | | +| OrPattern | BinaryPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/OrPattern.cs | | | +| Parameter | ParameterSyntax | ADR-0115 §B.5 | | | | | +| ParameterList | ParameterListSyntax | ADR-0115 §B.5 | | | | | +| ParenthesizedExpression | ParenthesizedExpressionSyntax | ADR-0115 §B | | | | | +| ParenthesizedLambdaExpression | ParenthesizedLambdaExpressionSyntax | ADR-0115 §B.20 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/ParenthesizedLambdaExpression.cs | https://github.com/DavidObando/gsharp/issues/1901 | Lambda default parameters dropped (issue #1901); async lambdas blocked by gsc ICE (issue #1919). | +| ParenthesizedPattern | ParenthesizedPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/ParenthesizedPattern.cs | | | +| ParenthesizedVariableDesignation | ParenthesizedVariableDesignationSyntax | ADR-0115 §B.30 | | | | | +| PointerIndirectionExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/FixedStatement.cs | https://github.com/DavidObando/gsharp/issues/1933 | Compiles; ilverify-by-design (issue #1933). *(p + i) dereference of parenthesized pointer arithmetic, including as a compound-assignment target/RHS, fixed in gsc (issue #1925). | +| PointerType | PointerTypeSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/PointerType.cs | https://github.com/DavidObando/gsharp/issues/1933 | Compiles end-to-end under the ilverify allow-unsafe policy (issue #1933); pointer IL is unverifiable by design, not a gsc defect. | +| PositionalPatternClause | PositionalPatternClauseSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/PositionalPatternClause.cs | https://github.com/DavidObando/gsharp/issues/1887 | Fixed (was SILENT MISTRANSLATION, issue #1887): a positional subpattern lowers to the same member-access form a property subpattern uses (tuple Item1/Item2, or a record's property via its Deconstruct). Switch-expression bare positional patterns over a raw TUPLE are also supported: gsc's property-pattern binder and emitter now accept a ValueTuple subject (previously GS0172). | +| PostDecrementExpression | PostfixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/PostDecrementExpression.cs | | | +| PostIncrementExpression | PostfixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/PostIncrementExpression.cs | | | +| PreDecrementExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/PreDecrementExpression.cs | | | +| PreIncrementExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/PreIncrementExpression.cs | | | +| PredefinedType | PredefinedTypeSyntax | ADR-0115 §B.12 | | | | | +| PrimaryConstructorBaseType | PrimaryConstructorBaseTypeSyntax | ADR-0065 §5 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/PrimaryConstructorBaseType.cs | | A derived primary-ctor class's `: Base(arg)` forwarding call now maps to the G# base-call form `class Derived(...) : Base(args) { ... }` (issue #1909, resolved). | +| PropertyDeclaration | PropertyDeclarationSyntax | ADR-0115 §B.11 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/PropertyDeclaration.cs | | | +| PropertyPatternClause | PropertyPatternClauseSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/RecursivePattern.cs | | Property sub-patterns; designator collisions fixed in issue #1839. | +| QualifiedName | QualifiedNameSyntax | ADR-0115 §B.12 | | | | | +| RecordDeclaration | RecordDeclarationSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/RecordDeclaration.cs | | with-expressions blocked by initializer bug (issue #1892). | +| RecordStructDeclaration | RecordDeclarationSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/RecordStructDeclaration.cs | | | +| RecursivePattern | RecursivePatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/RecursivePattern.cs | https://github.com/DavidObando/gsharp/issues/1923 | Property patterns green, including nested designations `{ P: T t } u` as native G# pattern variables (ADR-0166); nested reference-member and boxed/nullable subjects blocked by gsc (issue #1923). | +| RefStructConstraint | RefStructConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/AllowsConstraintClause.cs | | | +| RelationalPattern | RelationalPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/RelationalPattern.cs | | | +| RemoveAccessorDeclaration | AccessorDeclarationSyntax | ADR-0052 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/EventDeclaration.cs | | The explicit add/remove accessor body of an event declaration (ADR-0052 §2); translates like any other accessor body. | +| ReturnStatement | ReturnStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ReturnStatement.cs | | | +| RightShiftAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/RightShiftAssignmentExpression.cs | | | +| RightShiftExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/RightShiftExpression.cs | | | +| ScopedType | ScopedTypeSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/ScopedType.cs | | scoped Span/scoped ref parameters translate and verify (grid G12). | +| SetAccessorDeclaration | AccessorDeclarationSyntax | ADR-0115 §B.11 | | | | | +| SimpleAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/SimpleAssignmentExpression.cs | https://github.com/DavidObando/gsharp/issues/1895, https://github.com/DavidObando/gsharp/issues/1974, https://github.com/DavidObando/gsharp/issues/3347 | Ordinary value-position and chained assignments emit native assignment expressions. Flat statement-position deconstruction uses native multi-assignment; expression-position and nested deconstruction retain the tuple-value fallback. | +| SimpleBaseType | SimpleBaseTypeSyntax | ADR-0115 §B.6 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/SimpleBaseType.cs | | | +| SimpleLambdaExpression | SimpleLambdaExpressionSyntax | ADR-0115 §B.20 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/SimpleLambdaExpression.cs | | | +| SimpleMemberAccessExpression | MemberAccessExpressionSyntax | ADR-0115 §B | | | | | +| SingleVariableDesignation | SingleVariableDesignationSyntax | ADR-0115 §B.30 | | tools/cs2gs/corpus/grid/G09-Functions-Console/Constructs/DeclarationExpression.cs | | | +| SizeOfExpression | SizeOfExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/SizeOfExpression.cs | | | +| SlicePattern | SlicePatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/ListPattern.cs | | is-test path materializes the slice-bound value via native G# range slicing (`receiver[prefix..^suffix]`, using new `RangeIndexExpression`/`FromEndIndexExpression` CodeModel nodes) and binds it by substitution; switch-arm path emits a native G# `SlicePattern` (`..rest`/`..`) since gsc has real runtime slice-capture support (issue #1889, resolved). | +| SpreadElement | SpreadElementSyntax | ADR-0117 / ADR-0115 §B.36 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/CollectionExpression.cs | https://github.com/DavidObando/gsharp/issues/1897, https://github.com/DavidObando/gsharp/issues/3096 | Maps to native G# `...source` inside array/collection initializers; valid in field/property initializers with no translator spill. | +| StackAllocArrayCreationExpression | StackAllocArrayCreationExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/StackAllocArrayCreationExpression.cs | https://github.com/DavidObando/gsharp/issues/1933 | Compiles end-to-end under the ilverify allow-unsafe policy (issue #1933); stackalloc's localloc IL is unverifiable by design, not a gsc defect. | +| StringLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/StringLiteralExpression.cs | | | +| StructConstraint | ClassOrStructConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/StructConstraint.cs | | | +| StructDeclaration | StructDeclarationSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/StructDeclaration.cs | | Generic-struct ctor zip now matches non-generic structs (issue #1915, fixed: comparisons keyed by parameter ordinal + OriginalDefinition instead of constructed-type identity). Cross-assembly G# metadata now preserves imported data-struct / primary-constructor semantics (issue #1929). | +| Subpattern | SubpatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/RecursivePattern.cs | | Property sub-patterns; designator collisions fixed in issue #1839. | +| SubtractAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/SubtractAssignmentExpression.cs | | | +| SubtractExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/SubtractExpression.cs | | | +| SuppressNullableWarningExpression | PostfixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/SuppressNullableWarningExpression.cs | | x! maps to the G# !! assertion. | +| SwitchExpression | SwitchExpressionSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/SwitchExpression.cs | | | +| SwitchExpressionArm | SwitchExpressionArmSyntax | ADR-0115 §B.22 | | | | | +| SwitchSection | SwitchSectionSyntax | ADR-0115 §B.33 | | | | | +| SwitchStatement | SwitchStatementSyntax | ADR-0115 §B.33 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/SwitchStatement.cs | | | +| ThisConstructorInitializer | ConstructorInitializerSyntax | ADR-0115 §B.28 | | tools/cs2gs/corpus/grid/G07-Members-Console/Constructs/ThisConstructorInitializer.cs | | | +| ThisExpression | ThisExpressionSyntax | ADR-0115 §B | | | | | +| ThrowExpression | ThrowExpressionSyntax | ADR-0115 §B.27 | | | | | +| ThrowStatement | ThrowStatementSyntax | ADR-0115 §B.27 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ThrowStatement.cs | | | +| TrueLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/TrueLiteralExpression.cs | | | +| TryStatement | TryStatementSyntax | ADR-0115 §B.27 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/TryStatement.cs | | | +| TupleElement | TupleElementSyntax | ADR-0115 §B.12 | | | | G# tuple types (T1, T2). | +| TupleExpression | TupleExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs | | | +| TupleType | TupleTypeSyntax | ADR-0115 §B.12 | | | | G# tuple types (T1, T2). | +| TypeArgumentList | TypeArgumentListSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeArgumentList.cs | | | +| TypeConstraint | TypeConstraintSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeConstraint.cs | | | +| TypeOfExpression | TypeOfExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/TypeOfExpression.cs | | | +| TypeParameter | TypeParameterSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeParameter.cs | | Declaration-site variance (out/in) conversions: gsc issue #1927 (fixed). | +| TypeParameterConstraintClause | TypeParameterConstraintClauseSyntax | ADR-0115 §B.7 | | | | | +| TypeParameterList | TypeParameterListSyntax | ADR-0115 §B.7 | | tools/cs2gs/corpus/grid/G08-Generics-Console/Constructs/TypeParameterList.cs | | Generic class + primary ctor ICEs gsc (issue #1920). | +| TypePattern | TypePatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/TypePattern.cs | | Bare-type switch-arm (`int =>`, no binder — issue #1890, resolved): lowers to G#'s own discard-designator type pattern `_ is T` (`PatternBinder.BindTypePattern`'s `isDiscard` check), since gsc's own `TypePattern` grammar always requires a designator token before `is` but treats `_` as a non-binding discard there. Roslyn parses a bare user-type name (e.g. `Widget =>`) as a `ConstantPatternSyntax` over an identifier rather than `TypePatternSyntax`; the switch-arm path now shares the boolean-test path's `IsTypeReferencePattern` type-vs-constant disambiguation to still route it to `_ is T`. | +| UnaryMinusExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/UnaryMinusExpression.cs | | | +| UnaryPlusExpression | PrefixUnaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/UnaryPlusExpression.cs | | | +| UncheckedExpression | CheckedExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/UncheckedExpression.cs | https://github.com/DavidObando/gsharp/issues/1881 | | +| UncheckedStatement | CheckedStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/UncheckedStatement.cs | https://github.com/DavidObando/gsharp/issues/1881 | Wrap-around parity verified (grid G03). | +| UnsafeStatement | UnsafeStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G12-Unsafe-Console/Constructs/UnsafeStatement.cs | | | +| UnsignedRightShiftAssignmentExpression | AssignmentExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/UnsignedRightShiftAssignmentExpression.cs | | | +| UnsignedRightShiftExpression | BinaryExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/UnsignedRightShiftExpression.cs | | | +| UsingDirective | UsingDirectiveSyntax | ADR-0115 §B.1 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/TypeAliasDeclaration.cs | https://github.com/DavidObando/gsharp/issues/1914 | Plain + simple-alias green; alias-any-type (C#12) tuple-RHS green too (issue #1914; array/pointer/nullable-value-type RHS forms remain unexercised). | +| UsingStatement | UsingStatementSyntax | ADR-0115 §B.29 | | tools/cs2gs/corpus/grid/G10-Async-Console/Constructs/UsingStatement.cs | | await using preserves async-dispose semantics (fixed, issue #1903): lowers to G#'s own await using let form, binding IAsyncDisposable.DisposeAsync instead of plain using let/Dispose. | +| Utf8StringLiteralExpression | LiteralExpressionSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G01-Literals-Console/Constructs/Utf8StringLiteralExpression.cs | | C# 11 u8 literals translate and reach parity (grid G01). | +| VarPattern | VarPatternSyntax | ADR-0115 §B.22 | | tools/cs2gs/corpus/grid/G04-Patterns-Console/Constructs/SwitchExpression.cs | | Always-matching scalar bind: `var v` emits verbatim as G#'s native total pattern in is, property/list, and switch positions (issue #3420). Tuple designations remain unsupported. | +| VariableDeclaration | VariableDeclarationSyntax | ADR-0115 §B.3 | | | | | +| VariableDeclarator | VariableDeclaratorSyntax | ADR-0115 §B.3 | | | | | +| WhenClause | WhenClauseSyntax | ADR-0115 §B.22 | | | | Pattern guards (issue #991, resolved). | +| WhileStatement | WhileStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/WhileStatement.cs | https://github.com/DavidObando/gsharp/issues/3352 | Positive single-binder declaration/type patterns now emit native G# `while let` when the receiver and trailing guards are spill-free; reassigned binders, complex patterns, and spill-requiring conditions retain the general L1 body-hoist fallback. | +| WithExpression | WithExpressionSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/RecordDeclaration.cs | | | +| WithInitializerExpression | InitializerExpressionSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/RecordDeclaration.cs | | | +| YieldBreakStatement | YieldStatementSyntax | ADR-0115 §B.34 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/YieldBreakStatement.cs | | Issue #994 (resolved). | +| YieldReturnStatement | YieldStatementSyntax | ADR-0115 §B.34 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/YieldReturnStatement.cs | | | + +## Lowered (19) + +| Kind | Node type | Rule | Rationale | Fixture | Issue | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| AnonymousObjectCreationExpression | AnonymousObjectCreationExpressionSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/AnonymousObjectCreationExpression.cs | https://github.com/DavidObando/gsharp/issues/2538 | Lowers to positional construction of a shape-deduplicated synthesized data class, preserving named members and remaining a direct expression in constructor delegation (issues #2282 and #2538). | +| AnonymousObjectMemberDeclarator | AnonymousObjectMemberDeclaratorSyntax | ADR-0115 §B.4 | | tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/AnonymousObjectCreationExpression.cs | https://github.com/DavidObando/gsharp/issues/2538 | Each declarator supplies one positional synthesized-data-class constructor argument; the synthesized declaration preserves the projected member names (issues #2282 and #2538). | +| AscendingOrdering | OrderingSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/OrderByClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| DescendingOrdering | OrderingSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/OrderByClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| ExplicitInterfaceSpecifier | ExplicitInterfaceSpecifierSyntax | ADR-0091 / ADR-0115 §B | | tools/cs2gs/corpus/grid/G06-Types-Console/Constructs/ExplicitInterfaceSpecifier.cs | | G# has no explicit-interface-implementation surface (ADR-0091 rejected an 'IFoo.M(this)' spelling); a lone explicit impl lowers to a plain public method (fixes the prior ilverify miss, issue #1911). An explicit impl coexisting with a same-signature public method is dropped in favor of the public method (disclosed semantic-loss diagnostic, not covered by this fixture's stdout parity); two explicit impls of different interfaces with no public sibling de-duplicate to one surviving public method with no semantic loss. | +| ForStatement | ForStatementSyntax | ADR-0115 §B | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/ForStatement.cs | | Lowered to a while loop when clauses demand it (issue #1732 incrementor-on-continue fix). | +| FromClause | FromClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/FromClauseSelectMany.cs | | First from lowers to the source receiver; a second/subsequent from lowers to SelectMany with a transparent-identifier tuple result selector (issue #1902). | +| GotoCaseStatement | GotoStatementSyntax | ADR-0139 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/GotoCaseStatement.cs | | Issue #1884: lowered to a plain `goto` targeting a synthesized label placed at the top of the matching case's translated body (no switch re-evaluation, so C# fall-through/evaluation order is preserved). | +| GotoDefaultStatement | GotoStatementSyntax | ADR-0139 | | tools/cs2gs/corpus/grid/G03-ControlFlow-Console/Constructs/GotoDefaultStatement.cs | | Issue #1884: lowered like `goto case` (see GotoCaseStatement), targeting a synthesized label at the top of the default section's translated body. | +| GroupClause | GroupClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/GroupClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn (GroupBy, with identity-projection elision matching `select n`). | +| JoinClause | JoinClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/JoinClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn (Join with a transparent-identifier tuple result selector). | +| JoinIntoClause | JoinIntoClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/JoinIntoClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn (GroupJoin, with the `into` group variable typed `sequence[T]`). | +| LetClause | LetClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/LetClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn (Select widening the scope tuple with the let-bound value). | +| OrderByClause | OrderByClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/OrderByClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| QueryBody | QueryBodySyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/QueryExpression.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| QueryContinuation | QueryContinuationSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/QueryContinuation.cs | | Both select-into and group-into continuations re-scope the chain to the continuation variable (issue #1902). | +| QueryExpression | QueryExpressionSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/QueryExpression.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| SelectClause | SelectClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/QueryExpression.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | +| WhereClause | WhereClauseSyntax | ADR-0115 §B.21 | | tools/cs2gs/corpus/grid/G11-Linq-Console/Constructs/WhereClause.cs | | Query syntax lowered to the method-call chain, mirroring Roslyn. | + +## UnsupportedByDesign (56) + +| Kind | Node type | Rule | Rationale | Fixture | Issue | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| ArgListExpression | LiteralExpressionSyntax | | NoGsharpConstruct | tools/cs2gs/Cs2Gs.Tests/Fixtures/Grid/Unsupported/ArgListExpression.cs | | Legacy TypedReference/varargs machinery (__makeref/__reftype/__refvalue/__arglist); no G# analog planned. | +| BadDirectiveTrivia | BadDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ConversionOperatorMemberCref | ConversionOperatorMemberCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| CrefBracketedParameterList | CrefBracketedParameterListSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| CrefParameter | CrefParameterSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| CrefParameterList | CrefParameterListSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| DefineDirectiveTrivia | DefineDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ElifDirectiveTrivia | ElifDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ElseDirectiveTrivia | ElseDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| EndIfDirectiveTrivia | EndIfDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| EndRegionDirectiveTrivia | EndRegionDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ErrorDirectiveTrivia | ErrorDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ExtensionMemberCref | ExtensionMemberCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| ExternAliasDirective | ExternAliasDirectiveSyntax | | NoGsharpConstruct | | | Extern aliases disambiguate identically-named assemblies — a project-system feature G# does not model. | +| IfDirectiveTrivia | IfDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| IgnoredDirectiveTrivia | IgnoredDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| IncompleteMember | IncompleteMemberSyntax | | NotReachable | | | Parser error-recovery artifact; never appears in well-formed C#. | +| IndexerMemberCref | IndexerMemberCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| LineDirectivePosition | LineDirectivePositionSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| LineDirectiveTrivia | LineDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| LineSpanDirectiveTrivia | LineSpanDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| LoadDirectiveTrivia | LoadDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| MakeRefExpression | MakeRefExpressionSyntax | | NoGsharpConstruct | tools/cs2gs/Cs2Gs.Tests/Fixtures/Grid/Unsupported/MakeRefExpression.cs | | Legacy TypedReference/varargs machinery (__makeref/__reftype/__refvalue/__arglist); no G# analog planned. | +| MultiLineDocumentationCommentTrivia | DocumentationCommentTriviaSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| NameMemberCref | NameMemberCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| NullableDirectiveTrivia | NullableDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| OperatorMemberCref | OperatorMemberCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| PragmaChecksumDirectiveTrivia | PragmaChecksumDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| PragmaWarningDirectiveTrivia | PragmaWarningDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| QualifiedCref | QualifiedCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| RefTypeExpression | RefTypeExpressionSyntax | | NoGsharpConstruct | tools/cs2gs/Cs2Gs.Tests/Fixtures/Grid/Unsupported/RefTypeExpression.cs | | Legacy TypedReference/varargs machinery (__makeref/__reftype/__refvalue/__arglist); no G# analog planned. | +| RefValueExpression | RefValueExpressionSyntax | | NoGsharpConstruct | tools/cs2gs/Cs2Gs.Tests/Fixtures/Grid/Unsupported/RefValueExpression.cs | | Legacy TypedReference/varargs machinery (__makeref/__reftype/__refvalue/__arglist); no G# analog planned. | +| ReferenceDirectiveTrivia | ReferenceDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| RegionDirectiveTrivia | RegionDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| ShebangDirectiveTrivia | ShebangDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| SingleLineDocumentationCommentTrivia | DocumentationCommentTriviaSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| SkippedTokensTrivia | SkippedTokensTriviaSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| TypeCref | TypeCrefSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| UndefDirectiveTrivia | UndefDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| UnionDeclaration | (not exported by Roslyn 5.6) | | NotReachable | | | Post-C#14 preview syntax in Roslyn 5.6; not reachable at LangVersion latest. | +| UnknownAccessorDeclaration | AccessorDeclarationSyntax | | NotReachable | | | Parser error-recovery artifact; never appears in well-formed C#. | +| WarningDirectiveTrivia | WarningDirectiveTriviaSyntax | | Preprocessor | | | Resolved by Roslyn parse options before translation; inactive code is deliberately dropped. | +| WithElement | WithElementSyntax | | NotReachable | | | Collection-expression with-element is preview-only (CS8652) under LangVersion latest (C# 14). | +| XmlCDataSection | XmlCDataSectionSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlComment | XmlCommentSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlCrefAttribute | XmlCrefAttributeSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlElement | XmlElementSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlElementEndTag | XmlElementEndTagSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlElementStartTag | XmlElementStartTagSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlEmptyElement | XmlEmptyElementSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlName | XmlNameSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlNameAttribute | XmlNameAttributeSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlPrefix | XmlPrefixSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlProcessingInstruction | XmlProcessingInstructionSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlText | XmlTextSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | +| XmlTextAttribute | XmlTextAttributeSyntax | | ToolingScope | | | Documentation/tooling structure, not program semantics; doc-comment mapping is ADR-0057 scope. | + +## Gap (6) + +| Kind | Node type | Rule | Rationale | Fixture | Issue | Notes | +| --- | --- | --- | --- | --- | --- | --- | +| ImplicitElementAccess | ImplicitElementAccessSyntax | | | | https://github.com/DavidObando/gsharp/issues/1897 | | +| ImplicitStackAllocArrayCreationExpression | ImplicitStackAllocArrayCreationExpressionSyntax | | | | https://github.com/DavidObando/gsharp/issues/1897 | ADR-0124 stackalloc surface. | +| PointerMemberAccessExpression | MemberAccessExpressionSyntax | | | | https://github.com/DavidObando/gsharp/issues/1905 | p->X lowered to p.X; (*p).X compiles. | +| RangeExpression | RangeExpressionSyntax | | | | https://github.com/DavidObando/gsharp/issues/1896 | Lowers to .Slice(...) which gsc cannot resolve on arrays/strings. | +| RefExpression | RefExpressionSyntax | | | | https://github.com/DavidObando/gsharp/issues/1900 | ref argument/return seam (&x pass-by-address). | +| RefType | RefTypeSyntax | | | | https://github.com/DavidObando/gsharp/issues/1900 | | diff --git a/src/LanguageServer/HoverComputer.cs b/src/LanguageServer/HoverComputer.cs index e702d204b..bc959012a 100644 --- a/src/LanguageServer/HoverComputer.cs +++ b/src/LanguageServer/HoverComputer.cs @@ -2187,6 +2187,40 @@ private static IEnumerable FindAccessors(SyntaxNode no private static void AddInstanceTypeMembers(List items, HashSet seen, TypeSymbol type) { + // ADR-0172: a tuple receiver offers its declared element names first, + // then the positional ItemN spellings (both resolve). + if (type is TupleTypeSymbol tupleType) + { + for (var i = 0; i < tupleType.Arity; i++) + { + var elementDetail = SymbolDisplay.ToTypeDisplayString(tupleType.ElementTypes[i]); + if (tupleType.HasNames + && tupleType.ElementNames[i] is { } elementName + && seen.Add(elementName)) + { + items.Add(new CompletionItem + { + Label = elementName, + Kind = CompletionItemKind.Field, + Detail = elementDetail, + }); + } + + var positional = "Item" + (i + 1).ToString(System.Globalization.CultureInfo.InvariantCulture); + if (seen.Add(positional)) + { + items.Add(new CompletionItem + { + Label = positional, + Kind = CompletionItemKind.Field, + Detail = elementDetail, + }); + } + } + + return; + } + if (type is StructSymbol structType) { AddStructInstanceMembers(items, seen, structType); diff --git a/test/LanguageServer.Tests/CompletionHandlerTests.cs b/test/LanguageServer.Tests/CompletionHandlerTests.cs index 4750574e3..b4825d2ef 100644 --- a/test/LanguageServer.Tests/CompletionHandlerTests.cs +++ b/test/LanguageServer.Tests/CompletionHandlerTests.cs @@ -115,6 +115,35 @@ public void ComputeCompletions_AfterDotOnInt32_OffersClrInstanceMembers() Assert.DoesNotContain(items, i => i.Label == "int32"); } + [Fact] + public void ComputeCompletions_AfterDotOnNamedTuple_OffersElementNamesAndItemN() + { + // ADR-0172: a named-tuple receiver offers its declared element names + // plus the positional ItemN spellings, and suppresses the global soup. + const string source = "let pos (line int32, column int32) = (3, 5)\npos.\n"; + var content = LanguageServerTestHelpers.Content(source); + + var items = CompletionComputer.ComputeCompletions(content, After(source, "pos.")); + + Assert.Contains(items, i => i.Label == "line" && i.Kind == CompletionItemKind.Field); + Assert.Contains(items, i => i.Label == "column" && i.Kind == CompletionItemKind.Field); + Assert.Contains(items, i => i.Label == "Item1" && i.Kind == CompletionItemKind.Field); + Assert.Contains(items, i => i.Label == "Item2" && i.Kind == CompletionItemKind.Field); + Assert.DoesNotContain(items, i => i.Kind == CompletionItemKind.Keyword); + } + + [Fact] + public void ComputeCompletions_AfterDotOnUnnamedTuple_OffersItemN() + { + const string source = "let pair = (1, \"x\")\npair.\n"; + var content = LanguageServerTestHelpers.Content(source); + + var items = CompletionComputer.ComputeCompletions(content, After(source, "pair.")); + + Assert.Contains(items, i => i.Label == "Item1" && i.Kind == CompletionItemKind.Field); + Assert.Contains(items, i => i.Label == "Item2" && i.Kind == CompletionItemKind.Field); + } + [Fact] public void ComputeCompletions_AfterDotOnConsole_OffersStaticMembers() { diff --git a/tools/cs2gs/Cs2Gs.CodeModel/Ast/GExpression.cs b/tools/cs2gs/Cs2Gs.CodeModel/Ast/GExpression.cs index 7fa8dc57f..9c7f0ef3e 100644 --- a/tools/cs2gs/Cs2Gs.CodeModel/Ast/GExpression.cs +++ b/tools/cs2gs/Cs2Gs.CodeModel/Ast/GExpression.cs @@ -3,6 +3,7 @@ // using System.Collections.Generic; +using System.Linq; namespace Cs2Gs.CodeModel.Ast; @@ -669,7 +670,8 @@ public WithExpression(GExpression target, IReadOnlyList update } /// -/// A tuple literal (a, b, c) (spec §Primary expressions, TupleLiteral). +/// A tuple literal (a, b, c) (spec §Primary expressions, +/// TupleLiteral) — or, with ADR-0172 labels, (line: 1, column: 2). /// A tuple literal always has at least two elements. /// public sealed class TupleLiteralExpression : GExpression @@ -678,13 +680,18 @@ public sealed class TupleLiteralExpression : GExpression /// Initializes a new instance of the class. /// /// The tuple element expressions. - public TupleLiteralExpression(IReadOnlyList elements) + /// Optional per-element labels parallel to , null where unlabeled; pass null for a fully unlabeled literal. + public TupleLiteralExpression(IReadOnlyList elements, IReadOnlyList elementNames = null) { Elements = elements ?? new List(); + ElementNames = elementNames != null && elementNames.Any(n => n != null) ? elementNames : null; } /// Gets the tuple element expressions. public IReadOnlyList Elements { get; } + + /// Gets the per-element labels parallel to ( entries where unlabeled), or when fully unlabeled (ADR-0172). + public IReadOnlyList ElementNames { get; } } /// diff --git a/tools/cs2gs/Cs2Gs.CodeModel/Ast/GTypeReference.cs b/tools/cs2gs/Cs2Gs.CodeModel/Ast/GTypeReference.cs index f1824dc46..20d8ffcdc 100644 --- a/tools/cs2gs/Cs2Gs.CodeModel/Ast/GTypeReference.cs +++ b/tools/cs2gs/Cs2Gs.CodeModel/Ast/GTypeReference.cs @@ -3,6 +3,7 @@ // using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; namespace Cs2Gs.CodeModel.Ast; @@ -79,11 +80,11 @@ public ArrayTypeReference(GTypeReference elementType, int rank = 1) } /// -/// A positional tuple type rendered as (T1, T2, …) (spec §Type syntax, -/// the "(" TypeClause { "," TypeClause } ")" production). C# value tuples -/// (named or unnamed) map to this form; G# tuples are positional, so C# element -/// names are dropped and named element access lowers to .Item1/.Item2 -/// (ADR-0115 §B.4). At least two element types are required for a tuple type. +/// A tuple type rendered as (T1, T2, …) — or, when element names are +/// present, the ADR-0172 name-first form (line int32, column int32). +/// C# value tuples (named or unnamed) map to this form with their element +/// names preserved; access by name stays by-name in the output (ADR-0115 +/// §B.4 as amended by ADR-0172). At least two element types are required. /// public sealed class TupleTypeReference : GTypeReference { @@ -91,13 +92,18 @@ public sealed class TupleTypeReference : GTypeReference /// Initializes a new instance of the class. /// /// The ordered element types. - public TupleTypeReference(IReadOnlyList elementTypes) + /// Optional element names parallel to , null where unnamed; pass null for a fully unnamed tuple. + public TupleTypeReference(IReadOnlyList elementTypes, IReadOnlyList elementNames = null) { ElementTypes = elementTypes ?? new List(); + ElementNames = elementNames != null && elementNames.Any(n => n != null) ? elementNames : null; } /// Gets the ordered element types. public IReadOnlyList ElementTypes { get; } + + /// Gets the element names parallel to ( entries for unnamed positions), or when fully unnamed (ADR-0172). + public IReadOnlyList ElementNames { get; } } /// diff --git a/tools/cs2gs/Cs2Gs.CodeModel/Printing/GSharpPrinter.cs b/tools/cs2gs/Cs2Gs.CodeModel/Printing/GSharpPrinter.cs index f34488c63..f07b1ea86 100644 --- a/tools/cs2gs/Cs2Gs.CodeModel/Printing/GSharpPrinter.cs +++ b/tools/cs2gs/Cs2Gs.CodeModel/Printing/GSharpPrinter.cs @@ -176,7 +176,11 @@ private static string RenderTypeCore(GTypeReference type) return $"*{RenderType(pointer.ElementType)}"; case TupleTypeReference tuple: - return $"({string.Join(", ", tuple.ElementTypes.Select(RenderType))})"; + // ADR-0172: element names render name-first — `(line int32, column int32)`. + return "(" + string.Join(", ", tuple.ElementTypes.Select((t, i) => + tuple.ElementNames?[i] is { } elementName + ? $"{elementName} {RenderType(t)}" + : RenderType(t))) + ")"; case ArrowTypeReference arrow: var prefix = arrow.IsAsync ? "async " : string.Empty; @@ -685,7 +689,11 @@ private static string RenderExpressionCore(GExpression expression, int indent) : $"{allocation}{{{string.Join(", ", arrayAllocation.Elements.Select(e => RenderExpression(e, indent)))}}}"; case TupleLiteralExpression tuple: - var tupleElements = string.Join(", ", tuple.Elements.Select(e => RenderExpression(e, indent))); + // ADR-0172: labeled elements render as `label: value`. + var tupleElements = string.Join(", ", tuple.Elements.Select((e, i) => + tuple.ElementNames?[i] is { } label + ? $"{label}: {RenderExpression(e, indent)}" + : RenderExpression(e, indent))); return $"({tupleElements})"; case UnaryExpression unary: diff --git a/tools/cs2gs/Cs2Gs.Tests/Adr0172NamedTupleTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Adr0172NamedTupleTranslationTests.cs new file mode 100644 index 000000000..df93d136e --- /dev/null +++ b/tools/cs2gs/Cs2Gs.Tests/Adr0172NamedTupleTranslationTests.cs @@ -0,0 +1,154 @@ +// +// Copyright (C) GSharp Authors. All rights reserved. +// + +using System; +using Cs2Gs.CodeModel.Ast; +using Cs2Gs.CodeModel.Printing; +using Cs2Gs.CodeModel.RoundTrip; +using Cs2Gs.Translator; +using Cs2Gs.Translator.Loading; +using Xunit; + +namespace Cs2Gs.Tests; + +/// +/// ADR-0172 Phase C: cs2gs preserves C# tuple element names end-to-end — +/// types print name-first ((Line int32, Column int32)), literal labels +/// survive ((Line: 1, Column: 2)), and a named element ACCESS stays +/// by-name instead of lowering to .ItemN (amending ADR-0115 §B.4). +/// Every translated snippet must re-bind through the real G# compiler, which +/// exercises gsc's own ADR-0172 front end. Witness of discrimination: before +/// Phase C the printed output contained (int32, int32) and +/// .Item1/.Item2 for every case below. +/// +public class Adr0172NamedTupleTranslationTests +{ + [Fact] + public void NamedTupleType_PrintsNameFirst() + { + string printed = TranslateUnit(@" +namespace Demo +{ + public sealed class C + { + public (int Line, int Column) Find() => (3, 5); + } +}"); + + Assert.Contains("(Line int32, Column int32)", printed); + Assert.DoesNotContain("Item1", printed); + } + + [Fact] + public void NamedElementAccess_StaysByName() + { + string printed = TranslateUnit(@" +namespace Demo +{ + public sealed class C + { + public int Total() + { + (string Name, int Price, int Quantity) item = (""x"", 2, 3); + return item.Price * item.Quantity; + } + } +}"); + + Assert.Contains("item.Price * item.Quantity", printed); + Assert.DoesNotContain("Item2", printed); + Assert.DoesNotContain("Item3", printed); + } + + [Fact] + public void PositionalAccessOnNamedTuple_StaysPositional() + { + string printed = TranslateUnit(@" +namespace Demo +{ + public sealed class C + { + public int First() + { + (int Line, int Column) pos = (3, 5); + return pos.Item1; + } + } +}"); + + Assert.Contains("pos.Item1", printed); + } + + [Fact] + public void LiteralLabels_ArePreserved() + { + string printed = TranslateUnit(@" +namespace Demo +{ + public sealed class C + { + public (int Count, string Name) Make() => (Count: 3, Name: ""three""); + } +}"); + + Assert.Contains("(Count: 3, Name: \"three\")", printed); + } + + [Fact] + public void UnnamedTuples_Unchanged() + { + string printed = TranslateUnit(@" +namespace Demo +{ + public sealed class C + { + public (int, string) Make() => (1, ""x""); + public int First() => Make().Item1; + } +}"); + + Assert.Contains("(int32, string)", printed); + Assert.Contains(".Item1", printed); + } + + [Fact] + public void NamedTupleInsideGeneric_PrintsNames() + { + string printed = TranslateUnit(@" +using System.Collections.Generic; + +namespace Demo +{ + public sealed class C + { + public List<(int Line, int Column)> All() => new List<(int Line, int Column)>(); + public int FirstLine() => All()[0].Line; + } +}"); + + Assert.Contains("List[(Line int32, Column int32)]", printed); + Assert.Contains(".Line", printed); + } + + private static string TranslateUnit(string source) + { + LoadedCSharpProject project = CSharpProjectLoader.LoadInMemory(new[] { ("Snippet.cs", source) }); + Assert.True( + project.BoundWithoutErrors, + "Snippet should bind with no C# errors: " + + string.Join(Environment.NewLine, project.ErrorDiagnostics)); + + LoadedDocument document = Assert.Single(project.Documents); + var context = new TranslationContext(project.Compilation, document.SemanticModel, document.FilePath); + CompilationUnit unit = new CSharpToGSharpTranslator().TranslateDocument(document, context); + + string printed = GSharpPrinter.Print(unit); + RoundTripResult result = TranslationTestValidation.AssertBinds(printed); + Assert.True( + result.Success, + "Translated G# must round-trip. Errors:\n" + + string.Join("\n", result.Errors) + "\n\nPrinted:\n" + printed); + return printed; + } +} diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue1914TupleAliasDirectiveTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue1914TupleAliasDirectiveTests.cs index 4d2512985..dab58bebd 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue1914TupleAliasDirectiveTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue1914TupleAliasDirectiveTests.cs @@ -45,7 +45,8 @@ public void M() } }"); - Assert.Contains("let pair (int32, string) = (1, \"a\")", printed); + // ADR-0172: the alias's element names are preserved name-first. + Assert.Contains("let pair (Number int32, Word string) = (1, \"a\")", printed); } [Fact] @@ -65,7 +66,8 @@ public NamePair Make(NamePair seed) } }"); - Assert.Contains("func Make(seed (int32, string)) (int32, string)", printed); + // ADR-0172: the alias's element names are preserved name-first. + Assert.Contains("func Make(seed (Number int32, Word string)) (Number int32, Word string)", printed); } private static string TranslateUnit(string source) diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2469TupleElementNullabilityTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2469TupleElementNullabilityTranslationTests.cs index 2d8e1e644..917660b27 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2469TupleElementNullabilityTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2469TupleElementNullabilityTranslationTests.cs @@ -43,9 +43,9 @@ public static void Inspect(string text) }"); Assert.Contains( - "func Parse(text string) (string?, string?, Dictionary[string, string]?)", + "func Parse(text string) (Action string?, Method string?, Inputs Dictionary[string, string]?)", printed); - Assert.Contains("let method string? = parsed.Item2", printed); + Assert.Contains("let method string? = parsed.Method", printed); Assert.DoesNotContain("nil!!", printed); } @@ -70,7 +70,7 @@ public static ((string Left, string Right) Names, int Count, string Keep, int? M }"); Assert.Contains( - "func Pick(flag bool, value int32) ((string?, string?), int32, string, int32?)", + "func Pick(flag bool, value int32) (Names (Left string?, Right string?), Count int32, Keep string, Maybe int32?)", printed); } @@ -103,10 +103,10 @@ public override (string Action, string Method) Parse() } }"); - Assert.Contains("func ParseAsync() Task[(string?, string)];", printed); - Assert.Contains("async func ParseAsync() (string?, string)", printed); - Assert.Contains("open func Parse() (string?, string);", printed); - Assert.Contains("override func Parse() (string?, string)", printed); + Assert.Contains("func ParseAsync() Task[(Action string?, Method string)];", printed); + Assert.Contains("async func ParseAsync() (Action string?, Method string)", printed); + Assert.Contains("open func Parse() (Action string?, Method string);", printed); + Assert.Contains("override func Parse() (Action string?, Method string)", printed); } [Fact] @@ -132,8 +132,8 @@ public static string Forward() } }"); - Assert.Contains("func Generic[T class]() (T?, int32, int32?)", printed); - Assert.Contains("func Get() (string?, string)", printed); + Assert.Contains("func Generic[T class]() (Value T?, Count int32, Maybe int32?)", printed); + Assert.Contains("func Get() (First string?, Second string)", printed); Assert.Contains("func Forward() string?", printed); } @@ -151,8 +151,8 @@ public static (string Required, string? Optional, int Count, int? Maybe) Parse() } }"); - Assert.Contains("func Parse() (string, string?, int32, int32?)", printed); - Assert.DoesNotContain("(string?, string?, int32, int32?)", printed); + Assert.Contains("func Parse() (Required string, Optional string?, Count int32, Maybe int32?)", printed); + Assert.DoesNotContain("Required string?", printed); } [Fact] @@ -196,8 +196,8 @@ public static (string Action, string Method) Parse() siblings, "Consumer fixture omits the sibling Parser declaration from its emitted G# binding input."); - Assert.Contains("func Parse() (string?, string)", printedB); - Assert.Contains("func Parse() (string?, string)", printedA); + Assert.Contains("func Parse() (Action string?, Method string)", printedB); + Assert.Contains("func Parse() (Action string?, Method string)", printedA); } private static string TranslateOblivious(string source) diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue3615TupleReceiverPromotionProbeTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue3615TupleReceiverPromotionProbeTests.cs index db009b8f4..b33668252 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue3615TupleReceiverPromotionProbeTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue3615TupleReceiverPromotionProbeTests.cs @@ -87,7 +87,7 @@ public Registry Filter(HashSet retainedFilePaths) "; string printed = Render(source); Assert.DoesNotContain("(SyntaxTree?", printed, StringComparison.Ordinal); - Assert.Contains("HashSet[(SyntaxTree, int32, int32)]", printed, StringComparison.Ordinal); + Assert.Contains("HashSet[(Tree SyntaxTree, Start int32, Length int32)]", printed, StringComparison.Ordinal); AssertRoundTripParses(printed); } diff --git a/tools/cs2gs/Cs2Gs.Tests/ObliviousPromotionSinkCompilationTests.cs b/tools/cs2gs/Cs2Gs.Tests/ObliviousPromotionSinkCompilationTests.cs index 55c725733..d46e099c2 100644 --- a/tools/cs2gs/Cs2Gs.Tests/ObliviousPromotionSinkCompilationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/ObliviousPromotionSinkCompilationTests.cs @@ -121,7 +121,7 @@ public string Pick(int i) Assert.Contains("Field:", printed); Assert.Contains("default(string?)", printed); Assert.Contains( - "Parse(text string) (string?, string?, Dictionary[string, string]?)", + "Parse(text string) (Action string?, Method string?, Inputs Dictionary[string, string]?)", printed); CompileWithGsc(printed); } diff --git a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.ControlFlow.cs b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.ControlFlow.cs index 29bbef565..da00c5232 100644 --- a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.ControlFlow.cs +++ b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.ControlFlow.cs @@ -1532,7 +1532,7 @@ private static GTypeReference MakeNullable(GTypeReference reference) PointerTypeReference pointer => new PointerTypeReference(pointer.ElementType) { IsNullable = true }, TupleTypeReference tuple => - new TupleTypeReference(tuple.ElementTypes) { IsNullable = true }, + new TupleTypeReference(tuple.ElementTypes, tuple.ElementNames) { IsNullable = true }, ArrowTypeReference arrow => new ArrowTypeReference(arrow.ParameterTypes, arrow.ReturnTypes, arrow.IsAsync) { diff --git a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Expressions.cs b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Expressions.cs index 6939e59e7..4c379c306 100644 --- a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Expressions.cs +++ b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Expressions.cs @@ -690,17 +690,21 @@ member.Parent is InvocationExpressionSyntax invocation // avoids entirely.) bool isArrow = member.IsKind(SyntaxKind.PointerMemberAccessExpression); - // A C# tuple element access (`item.Name`, `item.Price`) lowers to the - // positional G# tuple field `.Item1`/`.Item2`, because G# tuples are - // positional and carry no element names (ADR-0115 §B.4). The default - // `.ItemN` access already resolves; only named-element access needs the - // rewrite, detected via the bound tuple-element field symbol. + // ADR-0172 (amending ADR-0115 §B.4): G# now has named tuple + // elements, so an explicitly named C# element access (`item.Price`) + // KEEPS its name in the output — the translated tuple type carries + // the same names. The symbol still normalizes to the positional + // field for downstream taint/typing; only the printed name differs. if (memberSymbol is IFieldSymbol field && field.ContainingType is { IsTupleType: true }) { IFieldSymbol positional = field.CorrespondingTupleField ?? field; - memberName = positional.Name; - memberSymbol = positional; + if (SymbolEqualityComparer.Default.Equals(positional, field)) + { + // Default positional access (`item.Item2`) stays positional. + memberName = positional.Name; + memberSymbol = positional; + } } // Issue #2282 (was #2224): an anonymous-typed value (`new { A = 1, diff --git a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Nullability.cs b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Nullability.cs index 9bf80907e..e77f61efd 100644 --- a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Nullability.cs +++ b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Nullability.cs @@ -335,7 +335,7 @@ private GTypeReference PromoteTupleElements( } return changed - ? new TupleTypeReference(elements) { IsNullable = tuple.IsNullable } + ? new TupleTypeReference(elements, tuple.ElementNames) { IsNullable = tuple.IsNullable } : tuple; } diff --git a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Patterns.cs b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Patterns.cs index fad2f95b1..ace6cc022 100644 --- a/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Patterns.cs +++ b/tools/cs2gs/Cs2Gs.Translator/CSharpToGSharpTranslator.Patterns.cs @@ -162,8 +162,11 @@ when binary.IsKind(SyntaxKind.AsExpression) || binary.IsKind(SyntaxKind.IsExpres return this.TranslateInterpolatedString(interpolated); case TupleExpressionSyntax tuple: + // ADR-0172: preserve C# element labels (`(Line: 1, …)`) + // as G# labeled tuple-literal elements. return new TupleLiteralExpression( - tuple.Arguments.Select(a => this.TranslateValueWithNullForgiveness(a.Expression)).ToList()); + tuple.Arguments.Select(a => this.TranslateValueWithNullForgiveness(a.Expression)).ToList(), + tuple.Arguments.Select(a => a.NameColon?.Name.Identifier.ValueText).ToList()); case AnonymousObjectCreationExpressionSyntax anonymous: return this.TranslateAnonymousObjectCreation(anonymous); diff --git a/tools/cs2gs/Cs2Gs.Translator/CSharpTypeMapper.cs b/tools/cs2gs/Cs2Gs.Translator/CSharpTypeMapper.cs index 1975166ed..53bc6fd39 100644 --- a/tools/cs2gs/Cs2Gs.Translator/CSharpTypeMapper.cs +++ b/tools/cs2gs/Cs2Gs.Translator/CSharpTypeMapper.cs @@ -1333,7 +1333,7 @@ private static GTypeReference WithNullable(GTypeReference reference, bool isNull case PointerTypeReference pointer: return new PointerTypeReference(pointer.ElementType) { IsNullable = isNullable }; case TupleTypeReference tuple: - return new TupleTypeReference(tuple.ElementTypes) { IsNullable = isNullable }; + return new TupleTypeReference(tuple.ElementTypes, tuple.ElementNames) { IsNullable = isNullable }; case ArrowTypeReference arrow: return new ArrowTypeReference(arrow.ParameterTypes, arrow.ReturnTypes, arrow.IsAsync) { @@ -1408,21 +1408,23 @@ private GTypeReference MapCore(ITypeSymbol type, TranslationContext context, Loc if (type is INamedTypeSymbol named) { - // Value tuples / named tuples map to the canonical G# positional - // tuple type `(T1, T2, …)` (spec §Type syntax). G# tuples are - // positional, so C# element names are dropped here and named element - // access lowers to `.Item1`/`.Item2` at the use site (ADR-0115 §B.4). + // Value tuples map to the native G# tuple type. ADR-0172: G# + // now has named tuple elements, so C# element names are + // PRESERVED name-first — `(int Line, int Column)` becomes + // `(Line int32, Column int32)` — and named access stays by-name + // at the use site (ADR-0115 §B.4 as amended). A default + // positional name (`Item1` at position 1, …) counts as unnamed. if (named.IsTupleType) { List elementTypes = named.TupleElements .Select(e => this.Map(e.Type, context, location)) .ToList(); - context.Report(new TranslationDiagnostic( - named.ToDisplayString(), - "C# value-tuple / named-tuple type mapped to the canonical G# positional tuple type; element names are dropped and named access lowers to '.ItemN' (ADR-0115 §B.4).", - location, - TranslationSeverity.Info)); - return new TupleTypeReference(elementTypes); + List elementNames = named.TupleElements + .Select((e, i) => e.IsImplicitlyDeclared || e.Name == "Item" + (i + 1) + ? null + : e.Name) + .ToList(); + return new TupleTypeReference(elementTypes, elementNames); } // Issue #2282 (was #1934): an anonymous type (`new { A = 1, B = 2 }`) @@ -2258,7 +2260,7 @@ private GTypeReference PromoteDelegateReturnPosition( } return changed - ? new TupleTypeReference(elements) { IsNullable = mappedTuple.IsNullable } + ? new TupleTypeReference(elements, mappedTuple.ElementNames) { IsNullable = mappedTuple.IsNullable } : mapped; } diff --git a/tools/cs2gs/coverage/csharp-construct-inventory.json b/tools/cs2gs/coverage/csharp-construct-inventory.json index 2258cd63b..0f23fa5bc 100644 --- a/tools/cs2gs/coverage/csharp-construct-inventory.json +++ b/tools/cs2gs/coverage/csharp-construct-inventory.json @@ -749,7 +749,7 @@ "rule": "ADR-0115 \u00A7B", "rationale": "None", "fixture": "tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/EqualsExpression.cs", - "notes": "Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering." + "notes": "Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); element names never affect equality." }, { "kind": "EqualsValueClause", @@ -1500,7 +1500,7 @@ "rule": "ADR-0115 \u00A7B", "rationale": "None", "fixture": "tools/cs2gs/corpus/grid/G02-Operators-Console/Constructs/NotEqualsExpression.cs", - "notes": "Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); C# element names are ignored, matching the positional G# lowering." + "notes": "Tuple operands compare element-wise via gsc tuple equality (ADR-0171, issue #3501); element names never affect equality." }, { "kind": "NotPattern", @@ -2213,7 +2213,7 @@ "status": "Translated", "rule": "ADR-0115 \u00A7B.12", "rationale": "None", - "notes": "G# tuple types (T1, T2)." + "notes": "G# tuple types; ADR-0172: C# element names are preserved name-first ((Line int32, Column int32)), and named access stays by-name." }, { "kind": "TupleExpression", @@ -2221,7 +2221,8 @@ "status": "Translated", "rule": "ADR-0115 \u00A7B", "rationale": "None", - "fixture": "tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs" + "fixture": "tools/cs2gs/corpus/grid/G05-Collections-Console/Constructs/TupleExpression.cs", + "notes": "ADR-0172: C# element labels ((Line: 1, \u2026)) are preserved as G# labeled elements." }, { "kind": "TupleType", @@ -2229,7 +2230,7 @@ "status": "Translated", "rule": "ADR-0115 \u00A7B.12", "rationale": "None", - "notes": "G# tuple types (T1, T2)." + "notes": "G# tuple types; ADR-0172: element names preserved (was: dropped with an Info diagnostic)." }, { "kind": "TypeArgumentList", From eaf8b109fed2e6f833e476e65a537e5ced500876 Mon Sep 17 00:00:00 2001 From: David Obando Date: Fri, 28 Aug 2026 18:30:40 -0700 Subject: [PATCH 5/5] Named tuples: imported generic calls keep element names + post-merge test reconciliation (ADR-0172, #3501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gsc: an imported generic method call (LINQ FirstOrDefault et al.) whose inference binds a type parameter to a named-tuple-bearing argument now keeps the names on the RETURN type — `tools.FirstOrDefault(...)`.Name previously died with GS0158 because two gates only kept the symbolic projection when RequiresSymbolicProjection held, and a named tuple shares its CLR backing with the unnamed shape. New TypeSymbol.ContainsNamedTupleElements extends the anySymbolic gate in BuildSymbolicMethodTypeArgs and the keep-projection gate in ResolveCallReturnTypeFromSymbolicTypeArgs. Core: TransferTupleNames rewritten from a tuple-pattern switch to plain if/is chains — the switch shape self-migrates through the pattern lowering's `as …!!` family, tripping the #3347/#3422 zero-noise inventories (5 new asserted-as conversions, 5 new bangs). cs2gs tests: reconcile the remaining positional-era expectations with the Phase C named surface (Issue1839's NotEmpty rode on the retired §B.4 Info diagnostic — now asserts the rendered pattern directly; Issue2421/2490×8/2504/2579/2821, L1 declaration + e2e — the L1 ctor line stays unnamed because the C# source spells the unnamed shape). Local repro discipline note: the earlier "full suite green" reads were truncated by a `| tail` pipe; suites now log to files and the stale out/bin/Release gsc that tests' FindCompiler prefers was rebuilt. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Nng28yiBdPVdeML7mSZphs --- src/Core/CodeAnalysis/Binding/MemberLookup.cs | 10 ++++++- .../Symbols/NullabilityAnnotatedTypeSymbol.cs | 28 ++++++++++++------ src/Core/CodeAnalysis/Symbols/TypeSymbol.cs | 29 +++++++++++++++++++ ...9PatternDesignatorAndEnumExtensionTests.cs | 11 ++++--- ...sue2421AsyncReturnTaintTranslationTests.cs | 2 +- ...2490TupleScalarNullabilityPipelineTests.cs | 2 +- ...0TupleScalarNullabilityTranslationTests.cs | 16 +++++----- ...amedDelegateReturnTaintTranslationTests.cs | 4 +-- ...llableReferenceFidelityTranslationTests.cs | 3 +- ...821OwnedExtensionMemberTranslationTests.cs | 5 ++-- .../L1DeclarationTranslationTests.cs | 16 +++++----- .../Cs2Gs.Tests/L1MigrationEndToEndTests.cs | 12 ++++---- 12 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/Core/CodeAnalysis/Binding/MemberLookup.cs b/src/Core/CodeAnalysis/Binding/MemberLookup.cs index a1f63864d..7696e6b07 100644 --- a/src/Core/CodeAnalysis/Binding/MemberLookup.cs +++ b/src/Core/CodeAnalysis/Binding/MemberLookup.cs @@ -1430,7 +1430,11 @@ public static TypeSymbol MapOpenClrTypeToSymbolic( var mapped = MapOpenClrTypeToSymbolic(openReturn, receiverOpenDef, receiverTypeArgs, openMethod, symbolicMethodTypeArgs); + // ADR-0172: a named-tuple-bearing return shares its CLR backing with + // the unnamed shape, so the CLR fallback would erase the names — keep + // the symbolic projection for it too. return TypeSymbol.RequiresSymbolicProjection(mapped) + || TypeSymbol.ContainsNamedTupleElements(mapped) ? mapped : null; } @@ -1507,8 +1511,12 @@ public static TypeSymbol MapOpenClrTypeToSymbolic( // recovered from a `List[Check]` receiver) does too — the closed // CLR method erased it to `object`, so without the symbolic vector // the call's return type / lambda parameter type would be `object`. + // ADR-0172: a named-tuple-bearing inferred argument also needs + // the symbolic vector — the closed CLR method's shapes share the + // unnamed backing, so the CLR fallback would erase the names. if (inferred[i] is { } inferredType - && TypeSymbol.RequiresSymbolicProjection(inferredType)) + && (TypeSymbol.RequiresSymbolicProjection(inferredType) + || TypeSymbol.ContainsNamedTupleElements(inferredType))) { anySymbolic = true; break; diff --git a/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs index ab66a5914..797947129 100644 --- a/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/NullabilityAnnotatedTypeSymbol.cs @@ -157,14 +157,24 @@ public TypeSymbol GetTypeArgumentSymbolForClrType(Type? targetClrType) return TypeSymbol.FromClrType(targetClrType); } - private static TypeSymbol TransferTupleNames(TypeSymbol source, TypeSymbol target) => (source, target) switch + private static TypeSymbol TransferTupleNames(TypeSymbol source, TypeSymbol target) { - (TupleTypeSymbol { HasNames: true } namedSource, TupleTypeSymbol unnamedTarget) - when namedSource.Arity == unnamedTarget.Arity && !unnamedTarget.HasNames - => TupleTypeSymbol.Get(unnamedTarget.ElementTypes, namedSource.ElementNames), - (NullableTypeSymbol { UnderlyingType: TupleTypeSymbol { HasNames: true } namedSource }, NullableTypeSymbol { UnderlyingType: TupleTypeSymbol unnamedTarget }) - when namedSource.Arity == unnamedTarget.Arity && !unnamedTarget.HasNames - => NullableTypeSymbol.Get(TupleTypeSymbol.Get(unnamedTarget.ElementTypes, namedSource.ElementNames)), - _ => target, - }; + if (source is TupleTypeSymbol { HasNames: true } namedSource + && target is TupleTypeSymbol unnamedTarget + && namedSource.Arity == unnamedTarget.Arity + && !unnamedTarget.HasNames) + { + return TupleTypeSymbol.Get(unnamedTarget.ElementTypes, namedSource.ElementNames); + } + + if (source is NullableTypeSymbol { UnderlyingType: TupleTypeSymbol { HasNames: true } namedUnderlying } + && target is NullableTypeSymbol { UnderlyingType: TupleTypeSymbol unnamedUnderlying } + && namedUnderlying.Arity == unnamedUnderlying.Arity + && !unnamedUnderlying.HasNames) + { + return NullableTypeSymbol.Get(TupleTypeSymbol.Get(unnamedUnderlying.ElementTypes, namedUnderlying.ElementNames)); + } + + return target; + } } diff --git a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs index c49dc19c7..1681a6cdc 100644 --- a/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs +++ b/src/Core/CodeAnalysis/Symbols/TypeSymbol.cs @@ -586,6 +586,35 @@ public static bool ContainsTypeParameter(TypeSymbol type) => AnyTypeParameter(ty return true; }); + /// + /// ADR-0172: returns when + /// structurally contains a tuple with declared element names. Named + /// tuples share their CLR backing with the unnamed shape, so CLR-driven + /// re-derivation (member projection, imported-call return mapping) erases + /// the names; gates that decide between "keep the symbolic projection" + /// and "fall back to the CLR shape" must treat named-tuple content like + /// symbolically-required content. + /// + /// The type to inspect. + /// true when any tuple position declares a name. + public static bool ContainsNamedTupleElements(TypeSymbol? type) => type switch + { + null => false, + TupleTypeSymbol tuple => tuple.HasNames || tuple.ElementTypes.Any(ContainsNamedTupleElements), + NullableTypeSymbol nullable => ContainsNamedTupleElements(nullable.UnderlyingType), + ArrayTypeSymbol array => ContainsNamedTupleElements(array.ElementType), + SliceTypeSymbol slice => ContainsNamedTupleElements(slice.ElementType), + RectangularArrayTypeSymbol rectangular => ContainsNamedTupleElements(rectangular.ElementType), + MapTypeSymbol map => ContainsNamedTupleElements(map.KeyType) || ContainsNamedTupleElements(map.ValueType), + SequenceTypeSymbol sequence => ContainsNamedTupleElements(sequence.ElementType), + AsyncSequenceTypeSymbol asyncSequence => ContainsNamedTupleElements(asyncSequence.ElementType), + ChannelTypeSymbol channel => ContainsNamedTupleElements(channel.ElementType), + ByRefTypeSymbol byRef => ContainsNamedTupleElements(byRef.PointeeType), + FunctionTypeSymbol function => function.ParameterTypes.Any(ContainsNamedTupleElements) || ContainsNamedTupleElements(function.ReturnType), + ImportedTypeSymbol { TypeArguments.IsDefaultOrEmpty: false } imported => imported.TypeArguments.Any(ContainsNamedTupleElements), + _ => false, + }; + /// /// Issue #810 / #1481: returns when /// structurally references any of the supplied diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue1839PatternDesignatorAndEnumExtensionTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue1839PatternDesignatorAndEnumExtensionTests.cs index ef6a9773c..e1352b2bb 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue1839PatternDesignatorAndEnumExtensionTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue1839PatternDesignatorAndEnumExtensionTests.cs @@ -99,9 +99,12 @@ public int Describe(object value) [Fact] public void RecursivePatternSynthesizedDesignator_TupleTypePattern_ReportsDiagnosticInsteadOfGarbage() { - // A tuple defer has no single simple name to derive a faithful - // designator from; a diagnostic must be reported rather than emitting - // the invalid `Type.ToString()` text `(int, int)`. + // A tuple type has no single simple name to derive a faithful + // designator from; the pattern must not emit the invalid + // `Type.ToString()` text `(int, int)`. (Historically the NotEmpty + // oracle rode on ADR-0115 §B.4's name-drop Info diagnostic; ADR-0172 + // retired that diagnostic, so the contract is now asserted directly + // on the rendered pattern shape.) LoadedCSharpProject project = CSharpProjectLoader.LoadInMemory(new[] { ("Source.cs", @" @@ -134,8 +137,8 @@ public int Describe((int, int) value) Cs2Gs.CodeModel.Ast.CompilationUnit unit = new CSharpToGSharpTranslator().TranslateDocument(document, context); string rendered = GSharpPrinter.Print(unit); - Assert.NotEmpty(context.Diagnostics); Assert.DoesNotContain("(int, int)", rendered, StringComparison.Ordinal); + Assert.Contains("Item1: var a", rendered, StringComparison.Ordinal); } [Fact] diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2421AsyncReturnTaintTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2421AsyncReturnTaintTranslationTests.cs index df575a727..d1f024787 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2421AsyncReturnTaintTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2421AsyncReturnTaintTranslationTests.cs @@ -191,7 +191,7 @@ public class C } }"); - Assert.Contains("async func TryFindAsync() (bool, IProfile?)", printed); + Assert.Contains("async func TryFindAsync() (Ok bool, Found IProfile?)", printed); } [Fact] diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityPipelineTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityPipelineTests.cs index ee3792e2e..8a8eccc4d 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityPipelineTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityPipelineTests.cs @@ -101,7 +101,7 @@ private static int CleanupCore(List items) .Select(File.ReadAllText)); Assert.Contains( - "func Gather(ok bool) (List[string]?, Statistics?)", + "func Gather(ok bool) (Items List[string]?, Stats Statistics?)", emitted, StringComparison.Ordinal); Assert.Contains( diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityTranslationTests.cs index a0791ec78..87008c183 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2490TupleScalarNullabilityTranslationTests.cs @@ -60,7 +60,7 @@ private static int CountCore(List items) } }"); - Assert.Contains("func Gather(ok bool) (List[string]?, int32)", printed); + Assert.Contains("func Gather(ok bool) (Items List[string]?, Value int32)", printed); Assert.Contains("func Count(items List[string]?) int32", printed); Assert.Contains("func CountCore(items List[string]?) int32", printed); Assert.Contains("func Read(ok bool) List[string]?", printed); @@ -112,12 +112,12 @@ private static ((string Keep, string Maybe) Names, int Count) Gather(bool missin private static string Echo(string value) => value; }"); - Assert.Contains("func Gather(missing bool) ((string, string?), int32)", printed); + Assert.Contains("func Gather(missing bool) (Names (Keep string, Maybe string?), Count int32)", printed); Assert.Contains("var assigned string? =", printed); Assert.Contains("init(value string?)", printed); Assert.Contains("prop this[key string?] string", printed); Assert.Contains("func Echo(value string?) string?", printed); - Assert.DoesNotContain("((string?, string?), int32)", printed); + Assert.DoesNotContain("Keep string?", printed); } [Fact] @@ -145,11 +145,11 @@ public static void Reset() } }"); - Assert.Contains("prop Source (string?, string)", printed); + Assert.Contains("prop Source (Maybe string?, Keep string)", printed); Assert.Contains("var Field string?", printed); Assert.Contains("var _property string?", printed); Assert.Contains("prop Property string?", printed); - Assert.DoesNotContain("prop Source (string?, string?)", printed); + Assert.DoesNotContain("Keep string?)", printed); } [Fact] @@ -199,7 +199,7 @@ private static (string Item, int Keep) Gather(bool missing) Assert.Contains("func Forward[T class](value T?) T?", printed); Assert.Contains("let Core = func (item T?) T?", printed); Assert.Contains("async func RunAsync(missing bool) string?", printed); - Assert.Contains("async func GatherAsync(missing bool) (string?, int32)", printed); + Assert.Contains("async func GatherAsync(missing bool) (Item string?, Keep int32)", printed); } [Fact] @@ -291,7 +291,7 @@ public static class Consumer siblings, "Consumer fixture omits the sibling Provider declaration from its emitted G# binding input."); - Assert.Contains("func Gather(missing bool) (string?, int32)", printedB); + Assert.Contains("func Gather(missing bool) (Item string?, Keep int32)", printedB); Assert.Contains("func Count(value string?) int32", printedA); } @@ -327,7 +327,7 @@ private static void Keep(string value) } }"); - Assert.Contains("func Gather(missing bool) (string, string?)", printed); + Assert.Contains("func Gather(missing bool) (Required string, Maybe string?)", printed); Assert.Contains("func Accept(value string?)", printed); Assert.Contains("func Keep(value string)", printed); Assert.DoesNotContain("func Keep(value string?)", printed); diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2504NamedDelegateReturnTaintTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2504NamedDelegateReturnTaintTranslationTests.cs index 3940f6b5c..f30961111 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2504NamedDelegateReturnTaintTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2504NamedDelegateReturnTaintTranslationTests.cs @@ -189,8 +189,8 @@ private static (Result, Result) TupleProduce() Assert.Contains("() T?;", printed, StringComparison.Ordinal); Assert.Contains("delegate TaskCallback() Task[Result?];", printed, StringComparison.Ordinal); Assert.Contains("delegate ValueTaskCallback() ValueTask[Result?];", printed, StringComparison.Ordinal); - Assert.Contains("delegate TupleCallback() (Result?, Result);", printed, StringComparison.Ordinal); - Assert.Contains("delegate LambdaTupleCallback() (Result?, Result);", printed, StringComparison.Ordinal); + Assert.Contains("delegate TupleCallback() (First Result?, Second Result);", printed, StringComparison.Ordinal); + Assert.Contains("delegate LambdaTupleCallback() (First Result?, Second Result);", printed, StringComparison.Ordinal); Assert.Contains("delegate ArrayCallback() []?Result;", printed, StringComparison.Ordinal); Assert.Contains("delegate NestedCallback() Box[Result]?;", printed, StringComparison.Ordinal); } diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2579NullableReferenceFidelityTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2579NullableReferenceFidelityTranslationTests.cs index 71cfcc86a..122962c8e 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2579NullableReferenceFidelityTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2579NullableReferenceFidelityTranslationTests.cs @@ -142,7 +142,8 @@ public static int Run() } """); - Assert.Contains("tool.Item2!!()", printed, StringComparison.Ordinal); + // ADR-0172: the named element access stays by-name. + Assert.Contains("tool.Handler!!()", printed, StringComparison.Ordinal); } private static string Translate(string source) diff --git a/tools/cs2gs/Cs2Gs.Tests/Issue2821OwnedExtensionMemberTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/Issue2821OwnedExtensionMemberTranslationTests.cs index d25961689..8da399931 100644 --- a/tools/cs2gs/Cs2Gs.Tests/Issue2821OwnedExtensionMemberTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/Issue2821OwnedExtensionMemberTranslationTests.cs @@ -810,11 +810,12 @@ public static string ExtensionProperty(Config? config) => string user = Compact(printed["User.cs"]); Assert.Equal(3, CountOccurrences(user, ".Describe()")); - Assert.Contains(".Pair.Item1.Describe()", user); + // ADR-0172: the named element access stays by-name. + Assert.Contains(".Pair.Node.Describe()", user); Assert.Contains(".Maybe!!.Describe()", user); Assert.Contains(".Holder.Current().Describe()", user); Assert.DoesNotContain("FirstExtensions.Describe", user); - Assert.DoesNotContain(".Pair.Node", user); + Assert.DoesNotContain(".Pair.Item1", user); Assert.DoesNotContain(".Maybe.Value", user); ImmutableArray diagnostics = diff --git a/tools/cs2gs/Cs2Gs.Tests/L1DeclarationTranslationTests.cs b/tools/cs2gs/Cs2Gs.Tests/L1DeclarationTranslationTests.cs index c3c6aea42..3c3fc85d1 100644 --- a/tools/cs2gs/Cs2Gs.Tests/L1DeclarationTranslationTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/L1DeclarationTranslationTests.cs @@ -152,18 +152,19 @@ public void L1Document_PreservesImmutableFieldInitializationAbi() diagnostic => diagnostic.Message.Contains("primary constructor", StringComparison.Ordinal)); } - /// T1 (ADR-0115 §B.4): a C# named-tuple field type maps to the - /// canonical G# positional tuple type (string, int32, int32) — element - /// names dropped — recorded as an Info decision, no longer Unsupported. + /// T1 (ADR-0115 §B.4 as amended by ADR-0172): a C# named-tuple + /// field type maps to the native G# tuple type with its element names + /// PRESERVED name-first ((Name string, Price int32, Quantity int32)); + /// nothing is Unsupported and no name-drop Info diagnostic remains. [Fact] - public void L1Document_MapsNamedTupleFieldToPositionalTuple() + public void L1Document_MapsNamedTupleFieldToNamedTuple() { (CompilationUnit unit, TranslationContext context) = TranslateL1(); - Assert.Contains( + // ADR-0172 retired the ADR-0115 §B.4 name-drop Info diagnostic. + Assert.DoesNotContain( context.Diagnostics, - d => d.Severity == TranslationSeverity.Info && - d.Message.Contains("positional tuple")); + d => d.Message.Contains("element names are dropped")); // No tuple is left as an Unsupported placeholder. Assert.DoesNotContain( @@ -180,6 +181,7 @@ public void L1Document_MapsNamedTupleFieldToPositionalTuple() Assert.Equal( new[] { "string", "int32", "int32" }, tuple.ElementTypes.Select(e => Assert.IsType(e).Name)); + Assert.Equal(new[] { "Name", "Price", "Quantity" }, tuple.ElementNames); } /// B.11 / ADR-0131: an expression-bodied property diff --git a/tools/cs2gs/Cs2Gs.Tests/L1MigrationEndToEndTests.cs b/tools/cs2gs/Cs2Gs.Tests/L1MigrationEndToEndTests.cs index 2dc52a2ea..9800b318d 100644 --- a/tools/cs2gs/Cs2Gs.Tests/L1MigrationEndToEndTests.cs +++ b/tools/cs2gs/Cs2Gs.Tests/L1MigrationEndToEndTests.cs @@ -44,17 +44,19 @@ public async Task L1Corpus_CanonicalizesWithAllThreeTransforms() "Canonical L1 must round-trip-parse. Errors:\n" + string.Join("\n", roundTrip.Errors) + "\n\nPrinted:\n" + printed); - // T1: the named C# tuple type maps to a native G# positional tuple, and - // named element access lowered to positional `.ItemN`. - Assert.Contains("List[(string, int32, int32)]", printed); - Assert.Contains("item.Item2 * item.Item3", printed); + // T1 (ADR-0172): the named C# tuple type keeps its element names + // name-first, and named element access stays by-name. + Assert.Contains("List[(Name string, Price int32, Quantity int32)]", printed); + Assert.Contains("item.Price * item.Quantity", printed); // T2: the explicit constructor keeps its source parameter name and both // readonly fields remain private instead of becoming primary fields. Assert.Contains("class Cart {", printed); Assert.Contains("private let _customer string", printed); - Assert.Contains("private let _items List[(string, int32, int32)]", printed); + Assert.Contains("private let _items List[(Name string, Price int32, Quantity int32)]", printed); Assert.Contains("init(customer string)", printed); + // The C# ctor spells the unnamed shape (`new List<(string, int, int)>()`) + // — the translation is faithful to each spelling. Assert.Contains("_items = List[(string, int32, int32)]()", printed); // T3: the entry class became top-level — a top-level func and the entry