-
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
Add nullability information to extract method #37851
Changes from 2 commits
3c3d918
b436141
3d15242
cfb883a
afce4a6
56f48e2
25598cf
1dd3aa5
11543dc
48b9fb4
db74834
822938e
0d27151
cc13d6b
e274e92
6013d1e
1e5bc1f
ca9b5ba
5a4e59c
c88ddae
824a348
4881f5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,37 +147,42 @@ protected override ITypeSymbol GetSymbolType(SemanticModel semanticModel, ISymbo | |
var references = selectionOperation.DescendantsAndSelf() | ||
.Where(IsSymbolReferencedByOperation); | ||
|
||
var flowState = GetFlowStateFromLocations(references); | ||
return base.GetSymbolType(semanticModel, symbol).WithNullability(flowState); | ||
if (AreAllReferencesNotNull(references)) | ||
{ | ||
return base.GetSymbolType(semanticModel, symbol).WithNullability(NullableAnnotation.NotAnnotated); | ||
} | ||
|
||
return base.GetSymbolType(semanticModel, symbol); | ||
|
||
default: | ||
return base.GetSymbolType(semanticModel, symbol); | ||
} | ||
|
||
NullableFlowState GetFlowStateFromLocations(IEnumerable<IOperation> references) | ||
bool AreAllReferencesNotNull(IEnumerable<IOperation> references) | ||
ryzngard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
foreach (var reference in references) | ||
{ | ||
var typeInfo = semanticModel.GetTypeInfo(reference.Syntax); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the assignment case, is this the flow state of the left hand or right hand side? Does it matter? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing it's left hand, but I'm not really sure. @333fred ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flow state is always RHS, Annotations are always LHS. If I understand your question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So in the case of assignment, we're looking to see if it ever can be assigned to null. That makes sense then, we want the RHS. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the question was "if you ask for the flow state of foo" in "foo = bar", is that the flow state after the assignment to bar (so it would have the same flow state of bar) or is that the flow state before the assignment of bar? |
||
|
||
switch (typeInfo.Nullability.FlowState) | ||
ryzngard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
case NullableFlowState.MaybeNull: return NullableFlowState.MaybeNull; | ||
case NullableFlowState.None: return NullableFlowState.None; | ||
case NullableFlowState.MaybeNull: | ||
case NullableFlowState.None: | ||
return false; | ||
} | ||
} | ||
|
||
return NullableFlowState.NotNull; | ||
return true; | ||
} | ||
|
||
bool IsSymbolReferencedByOperation(IOperation operation) | ||
=> operation switch | ||
{ | ||
ILocalReferenceOperation localReference => localReference.Local == symbol, | ||
IParameterReferenceOperation parameterReference => parameterReference.Parameter == symbol, | ||
IAssignmentOperation assignment => IsSymbolReferencedByOperation(assignment.Target), | ||
_ => false | ||
}; | ||
=> operation switch | ||
{ | ||
ILocalReferenceOperation localReference => localReference.Local == symbol, | ||
ryzngard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
IParameterReferenceOperation parameterReference => parameterReference.Parameter == symbol, | ||
IAssignmentOperation assignment => IsSymbolReferencedByOperation(assignment.Target), | ||
_ => false | ||
}; | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -677,6 +677,11 @@ protected override async Task<SemanticDocument> UpdateMethodAfterGenerationAsync | |
|
||
foreach (var returnOperation in returnOperations) | ||
{ | ||
if (!ReturnOperationBelongsToMethod(returnOperation.Syntax, methodOperation.Syntax)) | ||
ryzngard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
continue; | ||
} | ||
|
||
var syntax = returnOperation.ReturnedValue?.Syntax ?? returnOperation.Syntax; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to blow up if we have a return; inside a method that is non-void-returning? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not exactly. We end up not modifying the return type because it reports as |
||
var returnTypeInfo = semanticModel.GetTypeInfo(syntax, cancellationToken); | ||
if (returnTypeInfo.Nullability.FlowState == NullableFlowState.MaybeNull) | ||
|
@@ -694,6 +699,19 @@ protected override async Task<SemanticDocument> UpdateMethodAfterGenerationAsync | |
|
||
var newDocument = originalDocument.Document.WithSyntaxRoot(newRoot); | ||
return await SemanticDocument.CreateAsync(newDocument, cancellationToken).ConfigureAwait(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this fixing up "after the fact" seems super hinky and error prone. is there a reason we can't generate it with the right nullability right from the start? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I tried to make this work in generating the right nullability annotation and I found it too difficult since we can modify the code by adding return statements. I ended up basically writing a predictive flow analyzer for all the cases we could have to deal with where we would inject a return statement. I'm time boxing to the current design of fixing up after the fact, and I'll do my best to put a reasonable explanation as a comment here as to why it's this way. The only option I see is to accept the annotated nullability as desired and move on, but we asserted that we would like to be smarter than that. I'm very much open to ideas and discussion on this, but I think this is the most robust solution at this time based on my investigation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nullability project has a "IDE cleanup" column to track debt we created; this might be worth an entry there. |
||
|
||
static bool ReturnOperationBelongsToMethod(SyntaxNode returnOperationSyntax, SyntaxNode methodSyntax) | ||
{ | ||
var enclosingMethod = returnOperationSyntax.FirstAncestorOrSelf<SyntaxNode>(n => n switch | ||
{ | ||
BaseMethodDeclarationSyntax _ => true, | ||
AnonymousFunctionExpressionSyntax _ => true, | ||
LocalFunctionStatementSyntax _ => true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i feel like there's are ISyntaxFacts for this... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't see one. I saw for method body, and a few that take a position for method head. |
||
_ => false | ||
}); | ||
|
||
return enclosingMethod == methodSyntax; | ||
} | ||
} | ||
} | ||
} | ||
|
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.
in a future PR, could you update everything to be consistent here :)
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.
Naming style, or the expression body style?
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.
expression-body style.