dotnet / dotnet/roslyn

Roslyn compiles C#14 extension members with incorrect nullability constraints metadata

Open
#79,896 6 comments 3 reactions 1 assignee Claimed by @RikkiGibson View on GitHub
Area-Compilers Feature - Extension Everything
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

**Version Used**: 2e23a0df5bf8d49f6bf856287b0eb10de08f97d9

**Steps to Reproduce**:

Compile the following code:
```csharp
#nullable enable
#pragma warning disable CS8631

I? iNull = null;
E.M(iNull); // CS8714 is reported if the library is compiled, no warnings if referenced as source code
iNull.M(); // always no warnings
```

Which references this library:
```csharp
#nullable enable

public interface I { }

public static class E
{
extension(T t) where T : I
{
public T M() => t;
}
}
```

**Expected Behavior**:
Nullability warnings are the same regardless of whether you place the extension in the same project or in a compiled assembly.

**Actual Behavior**:
The following warning is reported when the extension comes from a compiled assembly but not if you use it from source code:

```warning CS8714: The type 'I?' cannot be used as type parameter 'T' in the generic type or method 'E.M(T)'. Nullability of type argument 'I?' doesn't match 'notnull' constraint.```

**Notes**:
The behavior is caused by a bogus `notnull` constraint encoded in the metadata for the extension method compiled for `E.M`. The constraint doesn't exist in the source code and therefore is not accounted for when the extension code is used from sources.

Note that `E.M` extension is present in the metadata twice, as a static member of `E` and as a member of the nested extension type. These two copies have different type parameter constraints. The nested extension member doesn't include the bogus `notnull` constraint, which is why the extension form of the call doesn't depend on whether you are using a compiled assembly or not.

E.M:
```
.method public hidebysig static !!0/*T*/
M<(class I) T>(
!!0/*T*/ t
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
= (01 00 00 00 )
.param [0]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.param constraint [1] /*T*/, class I
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
```

E.<>E__0`1. extension:
```
.class nested public sealed auto ansi beforefieldinit
'<>E__0`1'<(class I) T>
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.param type [1] /*T*/
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 00 00 00 ) // .....
// unsigned int8(0) // 0x00
```

Full compiled IL:

```
.class public abstract sealed auto ansi beforefieldinit
E
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
= (01 00 00 00 )

.class nested public sealed auto ansi beforefieldinit
'<>E__0`1'<(class I) T>
extends [System.Runtime]System.Object
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.param type [1] /*T*/
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 00 00 00 ) // .....
// unsigned int8(0) // 0x00

.method private hidebysig static specialname void
'$'(
!0/*T*/ t
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
= (01 00 00 00 )
.maxstack 8

IL_0000: ret

} // end of method '<>E__0`1'::'$'

.method public hidebysig instance !0/*T*/
M() cil managed
{
.maxstack 8

IL_0000: ldnull
IL_0001: throw

} // end of method '<>E__0`1'::M
} // end of class '<>E__0`1'

.method public hidebysig static !!0/*T*/
M<(class I) T>(
!!0/*T*/ t
) cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
= (01 00 00 00 )
.param [0]
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.param constraint [1] /*T*/, class I
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableAttribute::.ctor(unsigned int8)
= (01 00 01 00 00 ) // .....
// unsigned int8(1) // 0x01
.maxstack 8

// [9 25 - 9 26]
IL_0000: ldarg.0 // t
IL_0001: ret

} // end of method E::M
} // end of class E
```

Roslyn test code to verify:

Neither empty expected diagnostics nor CS8714 pass both verify calls, showing the difference between compiled and source-code behavior
```csharp
var src = """
#nullable enable
#pragma warning disable CS8631

I? iNull = null;
E.M(iNull);
iNull.M();
""";
var libSrc = """
#nullable enable

public interface I { }

public static class E
{
extension(T t) where T : I
{
public T M() => t;
}
}
""";
DiagnosticDescription[] expected = [
// (5,1): warning CS8714: The type 'I?' cannot be used as type parameter 'T' in the generic type or method 'E.M(T)'. Nullability of type argument 'I?' doesn't match 'notnull' constraint.
// E.M(iNull);
Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, "E.M").WithArguments("E.M(T)", "T", "I?").WithLocation(5, 1)
];

var comp = CreateCompilation([src, libSrc]);
comp.VerifyEmitDiagnostics(expected);

var libComp = CreateCompilation(libSrc);
var comp2 = CreateCompilation(src, references: [libComp.EmitToImageReference()]);
comp2.VerifyEmitDiagnostics(expected);
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.