Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Short term field keyword nullable analysis #75246

Merged
merged 8 commits into from
Sep 26, 2024

Conversation

RikkiGibson
Copy link
Contributor

@RikkiGibson RikkiGibson commented Sep 25, 2024

Part of #75244

I found that the dotnet/csharplang#8425 did not really include enough details to attack the attribute problem.

Basically, we have a precedent with auto properties using nullable attributes, that the field is subject to those same attributes in the same way. For example, [AllowNull] string Prop { get; set; } = null; is valid, even though the = null is assigning to the backing field rather than the auto-prop which claims to accept null.

However, our goal with the field keyword nullability proposal was to allow customizing the field's nullability independently of the property. Therefore, I have arrived at the following principle:

  • For classic auto properties, i.e. field-backed properties which don't use the field keyword, field: targeted nullability attributes are ignored, and the attributes from the property are used for the field instead. Essentially, the field is not a distinct thing from the property for the purposes of nullable analysis.
  • For properties using field keyword, the field is a distinct thing. field: targeted nullability attributes are used on the field, and nullability attributes on the property are not applied to the field.
    • Therefore, [AllowNull] string Prop { get => field; set => field = value } = null; results in a warning--since the initializer is assigning to the field, and the AllowNull doesn't apply to the field.
    • However, we still view the property as a proxy to the field in a constructor. This is accomplished by sharing a slot between the property and the backing field in constructors. In other words, within a constructor, whatever flow state the property has, we assume the field also has that same state, and any state changes to either one are reflected in both. This means the right thing happens whether field/property assignment is used, or if (Prop is null) throw ...; is used, to ensure that the property is in the correct state before exiting the constructor.
    • We use the property's nullability attributes to decide what values can be assigned to the property, when the language defines that the property is the thing being assigned--such as when a property with a setter is assigned in a constructor. Similarly, the field's nullability attributes decide what values can be assigned to the field when the field is the thing being assigned, such as in a property initializer.

The short term solution reflects the need for user to specify whether the field is nullable or not independently of the property. When we introduce a solution which infers the field's nullability across members, we expect the need for the user to specify will disappear in most cases.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Sep 25, 2024
@RikkiGibson RikkiGibson changed the title Short term field nullability analysis solution Short term field keyword nullable analysis Sep 25, 2024
@RikkiGibson RikkiGibson marked this pull request as ready for review September 25, 2024 23:59
@RikkiGibson RikkiGibson requested a review from a team as a code owner September 25, 2024 23:59
@RikkiGibson
Copy link
Contributor Author

Test failure appears to be just the following

[xUnit.net 00:00:47.12]     Microsoft.CodeAnalysis.CSharp.UnitTests.DiagnosticAnalyzerTests.TestDescriptorForConfigurableCompilerDiagnostics [FAIL]
  Failed Microsoft.CodeAnalysis.CSharp.UnitTests.DiagnosticAnalyzerTests.TestDescriptorForConfigurableCompilerDiagnostics [7 ms]
  Error Message:
   Add resource string named 'WRN_UninitializedNonNullableBackingField_Title' for Title of 'CS9264' to 'CSharpResources'
Expected: True
Actual:   False
  Stack Trace:
     at Microsoft.CodeAnalysis.CSharp.UnitTests.DiagnosticAnalyzerTests.TestDescriptorForConfigurableCompilerDiagnostics() in /_/src/Compilers/CSharp/Test/Emit3/Diagnostics/DiagnosticAnalyzerTests.cs:line 733

   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

I will try to address tomorrow.

// Share a slot between backing field and associated property/event in the context of a constructor which owns initialization of that backing field.
if (this._symbol is MethodSymbol constructor
&& constructor.IsConstructor()
&& constructor.IsStatic == symbol.IsStatic)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructor.IsStatic == symbol.IsStatic

Are we testing this case? That is, are we testing use of static property in an instance constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is only really going to be relevant in a scenario like the following:

  1. static property is used in instance constructor, and
  2. somehow, backing field of the same static property is also used in instance constructor. I think this is not possible.

I am adding some tests to poke at that area, but, even if the tests don't show the necessity of the check, I think I will feel more comfortable having it there.


var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (6,12): warning CS9264: Non-nullable property 'Prop' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier, or declaring the property as nullable, or adding '[field: MaybeNull, AllowNull]' attributes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Existing problem, but you can't exactly mark Prop as required...

}

[Fact]
public void Nullability_07()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nullability_07

Minor: It seems like Nullability_08 covers this test. Similar comment for _07_Getter_only.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal of this separation was to test the constructor exit checks separately from testing field flow state being reflected when reading the property.

[MaybeNull, AllowNull]
public string Prop
{
get => field.ToString(); // 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// 1

Minor: Consider moving // 1 to setter.

}

[Fact]
public void Nullability_20_ManualGetter_AutoSetter_NullTest()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nullability_20_ManualGetter_AutoSetter_NullTest

Is this distinct from the previous test? (What are we testing with the addition of if (Prop is null) throw null!?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to demonstrate that a null-test+throw on the property puts a non-nullable field into the correct state. Adding a version without initializer to demonstrate that more clearly.

@RikkiGibson RikkiGibson merged commit dc21958 into dotnet:main Sep 26, 2024
24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Sep 26, 2024
@RikkiGibson RikkiGibson deleted the field-nullability branch September 26, 2024 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants