Skip to content

Nullness issue - Incorrect NullableAttribute on records #18180

@pchalamet

Description

@pchalamet

Issue description

C# (with Nullable enabled) complains about non-nullable arguments when using F# records (warning CS8625: Cannot convert null literal to non-nullable reference type.).

This is probably due to missing attributes on generated properties and constructors:

  • NullableAttribute(2) is missing on ctor for nullable params
  • NullableAttribute(2) is missing on generated properties (also setter when using [<CLIMutable>]

Choose one or more from the following categories of impact

  • Unexpected nullness warning (false positive in nullness checking, code uses --checknulls and langversion:preview).
  • Missing nullness warning in a case which can produce nulls (false negative, code uses --checknulls and langversion:preview).
  • Breaking change related to older null constructs in code not using the checknulls switch.
  • Breaking change related to generic code and explicit type constraints (null, not null).
  • Type inference issue (i.e. code worked without type annotations before, and applying the --checknulls enforces type annotations).
  • C#/F# interop issue related to nullness metadata.
  • Other (none of the categories above apply).

Operating System

macOS

What .NET runtime/SDK kind are you seeing the issue on

.NET SDK (.NET Core, .NET 5+)

.NET Runtime/SDK version

.net sdk 9.0.100

Reproducible code snippet and actual behavior

Considering following type:

type NRTCollection = { Name: string | null }

Several problems can be observed:

var coll = new NRTCollection(null); // `warning CS8625: Cannot convert null literal to non-nullable reference type.`
Console.WriteLine(coll.Name.ToString()); // C# shall warn here since Name can be null - but it's not

Looking at the emitted metadata for the F# type:

[Serializable]
[CompilationMapping(SourceConstructFlags.RecordType)]
[NullableContext(1)]
public sealed class NRTCollection : IEquatable<NRTCollection>, IStructuralEquatable, IComparable<NRTCollection>, IComparable, IStructuralComparable
{
	[CompilerGenerated]
	[DebuggerBrowsable(/*Could not decode attribute arguments.*/)]
	[System.Runtime.CompilerServices.Nullable(2)]
	internal string Name@;

	[CompilationMapping(SourceConstructFlags.Field, 0)]
	public string Name
	{
		[CompilerGenerated]
		[DebuggerNonUserCode]
		get
		{
			return Name@;
		}
	}

	[System.Diagnostics.CodeAnalysis.DynamicDependency(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicProperties, typeof(NRTCollection))]
	public NRTCollection(string name)
	{
		Name@ = name;
	}

I can see that only the backing field Name@ has the Nullable(2) - this is fine but useless as value is accessed through property.

But:

  • Constructor parameter name has no attribute
  • Property Name has no attribute

This is probably the reason why C# compiler is complaining for records.

if [<CLIMutable>] is set on the record, it's also expected to have the setter annotated with Nullable(2) - this is not the case as of now.

Possible workarounds

None as of now.

Metadata

Metadata

Assignees

Labels

Type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions