Fix #3909: emit nullable override disambiguators#3910
Conversation
Override constraints are normally inherited and omitted, but nullable type parameters still require class or default to distinguish annotations from Nullable<T>. Derive that legal discriminator from the method metadata. Assisted-by: Copilot:gpt-5.6-sol:GitHub Copilot CLI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86d2918e-5a24-48b4-9a86-41d331ec3720
|
Which version of c# was this feature introduced? Is there a specification? |
|
C# 9.0. There is a specification, though the situation is worth spelling out, because the standard has not caught up with it. VersionThe compiler is explicit about it. Compiling the fixture with The second one matters here: SpecificationThe normative text is the csharplang speclet Unconstrained type parameter annotations (champion issue dotnet/csharplang#3297, design meetings LDM 2019-11-25 and LDM 2020-06-17). Its "
ECMA-334 does not cover the feature. The published 7th edition (December 2023) standardizes C# 7, and even the committee's C# 9 draft ( and 15.6.1 still reads "Such declarations may only have type_parameter_constraints_clauses containing the primary_constraints A defect in this PR, found while re-deriving the rule from the specletPlease hold off reviewing the current head - answering your question made me check the patch against the speclet rather than against my own test cases, and it is not correct for one class of constraints. I would rather report that myself than have you find it. Measuring what the compiler actually requires, for a base method
The patch derives the discriminator from ILSpy already has the right predicate: the tri-state This mirrors the wording of the compiler's own diagnostics and reproduces every row of the table above. I will push that shortly, together with fixture cases for a class-type constraint, One residual I would leave unless you prefer it hardened: for constraint shapes only IL can express (a concrete delegate, array, struct or enum constraint - CS0701 / CS0706 in C#), Language versionThe clause is only emitted where a |
008cf7a to
65aef35
Compare
|
Pushed the correction. What changed. The discriminator is now derived from the tri-state bool? isReferenceType = tp.IsReferenceType;
if (isReferenceType == false)
continue;
c.BaseTypes.Add(new PrimitiveType(isReferenceType == true ? "class" : "default"));
The collector now matches on type-parameter identity rather than owner kind plus index, so a specialized signature carrying a foreign type parameter that shares an index with one of the method's own cannot produce a spurious clause. Test coverage. The Checked as a red/green pair: with the previous implementation the fixture fails on exactly the three class-type rows, for every compiler in the matrix: Validation
|
A class-type constraint such as Stream or Delegate sets no ReferenceTypeConstraint flag, so keying the disambiguator off that flag gave those overrides the default constraint, which is CS8822, and the output still did not recompile. The restated disambiguator leaves no metadata trace of its own, so the choice has to follow from whether the inherited constraints make the type parameter a reference type, a value type, or neither. Matching the annotated type parameters by identity rather than by owner kind and index also keeps a specialized signature from contributing a foreign type parameter that happens to share an index. Assisted-by: Copilot:claude-opus-5:GitHub Copilot CLI
65aef35 to
0e60038
Compare
IsReferenceType is a bool?, so choosing between class, default and no constraint at all is a three-state decision. Spelling those states out keeps that visible where the choice is made, rather than leaving it implied by a comparison against true. Assisted-by: Copilot:claude-opus-5:GitHub Copilot CLI
Fixes #3909.
Problem
C# normally inherits generic constraints on overrides and explicit interface implementations, so
ILSpy correctly avoids restating them. Nullable annotations introduce one exception: when a
method type parameter appears as
T?, C# requiresclass,struct, ordefaultto distinguisha nullable annotation from
Nullable<T>.Omitting that discriminator can change the signature and produce CS0115 / CS0453.
Solution
For overrides and explicit interface implementations:
parameter types;
T?already meansNullable<T>;classfor an inherited reference-type constraint, otherwise emitdefault.Roslyn records the inherited reference/value constraint flags on the implementation method's own
type parameters, so this remains exact even when the base assembly cannot be resolved. Other
inherited constraints remain omitted, as required by C#.
Tests
Added a Roslyn 3+ Pretty fixture covering:
notnullparameters requiringdefault;classandclass?contracts requiring plainclass;structand non-nullable-signature controls;Validation:
Issue3909Pretty matrix: 6 passedrelated generic / nullable Pretty matrices: 26 passed
full Pretty suite: 1,919 passed, 5 existing skips
type-system unit tests: 174 passed
Release solution build: 0 warnings, 0 errors
full solution test suite: 4,927 passed, 15 existing skips
At least one test covering the code changed