-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Respect [MemberNotNull] on local functions #75448
Conversation
74ff889
to
f38640c
Compare
ccce2b8
to
f49a448
Compare
src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
Show resolved
Hide resolved
This reverts commit 3237e0e.
@dotnet/roslyn-compiler for second review. Thanks |
field4.ToString(); // 1 | ||
|
||
[MemberNotNull(nameof(field1), nameof(field2))] | ||
bool init() => throw null!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
field4.ToString(); // 1 | ||
|
||
[MemberNotNull(nameof(field1), nameof(field2))] | ||
bool init() => throw null!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""", MemberNotNullAttributeDefinition]); | ||
|
||
c.VerifyDiagnostics( | ||
// (6,23): error CS0103: The name 'field1' does not exist in the current context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
} | ||
} | ||
else if (returnStatement.ExpressionOpt is { ConstantValueOpt: { IsBoolean: true, BooleanValue: bool value } }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 this if
block was removed (it was not reachable)
@@ -791,113 +791,6 @@ void checkMemberStateOnConstructorExit(MethodSymbol constructor, Symbol member, | |||
} | |||
} | |||
|
|||
void enforceMemberNotNullOnMember(SyntaxNode? syntaxOpt, LocalState state, MethodSymbol method, string memberName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 A number of local functions were switched to methods below
|
||
return GetOrCreateSlot(member, containingSlot); | ||
if (member.IsStatic != (containingSlot == 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
This is illustrated by MemberNotNull_StaticMemberOnInstanceMethod
The opposite scenario is illustrated by MemberNotNull_InstanceMemberOnStaticMethod
Update: there's something fishy with that scenario. Investigating...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making a fix to align the logic in ApplyMemberPostConditions
and GetSlotForMemberPostCondition
regarding this scenario
} | ||
|
||
var container = current.ContainingSymbol; | ||
if (container.Kind == SymbolKind.NamedType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps check for SymbolKind.Method
instead:
if (container.Kind != SymbolKind.Method)
{
...
}
current = (MethodSymbol)container;
Then, the containing loop could be the following, and the final return -1;
removed.
MethodSymbol current = method;
while (true)
``` #Closed
{ | ||
init(); | ||
field1.ToString(); | ||
field2.ToString(); // 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this warning expected? (It looks like we don't report a warning for the corresponding static method case - see sharplab.io.) #Closed
{ | ||
init(); | ||
field1.ToString(); | ||
field2.ToString(); // 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
Show resolved
Hide resolved
{ | ||
MakeMembersMaybeNull(lambdaOrFunctionSymbol, lambdaOrFunctionSymbol.NotNullMembers); | ||
MakeMembersMaybeNull(lambdaOrFunctionSymbol, lambdaOrFunctionSymbol.NotNullWhenTrueMembers); | ||
MakeMembersMaybeNull(lambdaOrFunctionSymbol, lambdaOrFunctionSymbol.NotNullWhenFalseMembers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why set these members to maybe null rather than relying on the state from the calling method? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in another comment
[MemberNotNull(nameof(field1), nameof(field2))] | ||
void init() | ||
{ | ||
field1.ToString(); // 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's why.
For the enforcement of MemberNotNull
attribute within a method body, we set the initial state of the field to maybe-null when entering the body and check that it's been changed to not-null by the time you exit the method. That way we know that the method did explicitly set a not-null state.
I used the same design for local functions. That analysis/enforcement can be performed by looking at the local function in isolation, without regard to its invocations.
// Note: we add a synthesized BoundReturnStatement in BindLocalFunctionStatement | ||
// using the block body as syntax, but we report the diagnostic on the closing brace in that case | ||
c.VerifyEmitDiagnostics( | ||
// (16,9): warning CS8774: Member 'field1' must have a non-null value when exiting. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the same behavior as with methods. If you put the MemberNotNull
attribute, you must set the field to a not-null value by the time you exit the method. sharplab
@cston @RikkiGibson This is ready for another look. Thanks |
[Fact] | ||
public void MemberNotNull_LocalFunction() | ||
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56256")] | ||
public void MemberNotNull_InstanceLocalFunction() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. Sorry, I missed those
while (current.ContainingSymbol is MethodSymbol container) | ||
{ | ||
current = container; | ||
anyStatic |= container.IsStatic; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[MemberNotNull(nameof(field1), nameof(field2))] | ||
bool init() => throw null!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be interesting to have versions of a few of these tests (e.g. referencing instance members in 'static context'), where the end of the local function is reachable. #Pending
@@ -447,7 +447,7 @@ lSelect: | |||
Debug.Assert(node.MethodGroupOpt Is Nothing) | |||
|
|||
Dim delegateType As NamedTypeSymbol = DirectCast(node.Type, NamedTypeSymbol) | |||
Debug.Assert(delegateType.TypeKind = TypeKind.Delegate) | |||
Debug.Assert(delegateType.TypeKind = TYPEKIND.Delegate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this change intended?
@@ -21002,7 +21002,7 @@ public void M() | |||
} | |||
|
|||
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56256")] | |||
public void MemberNotNull_LocalFunction_BaseField() | |||
public void MemberNotNull_NonStaticLocalFunction_BaseField() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any test for 'MemberNotNull' on a local function within a constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM just minor questions/nits.
Fixes #56256
The
MemberNotNull
attribute takes effect if either:When the
MemberNotNull
takes effect, three things happen:Note: I didn't gate this change behind LangVer as it seems small and only affects nullability warnings, but I'm open to it.