-
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
Add nullability information to extract method #37851
Conversation
4031564
to
56f48e2
Compare
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.
Bunch of nitpick questions or things that looked like you went one way and then decided to go another way.
src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.ExpressionResult.cs
Show resolved
Hide resolved
src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
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 comment
The 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 comment
The 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 comment
The 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 comment
The 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 comment
The 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?
👍 |
src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs
Show resolved
Hide resolved
var newRoot = oldRoot.ReplaceNode(methodDeclaration, newMethodDeclaration); | ||
|
||
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 comment
The 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 comment
The 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 comment
The 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.
…hod after generating it
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 few remaining questions; happy to sign off assuming they're either answered or we've appropriately tracked any deferred work.
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
|
||
foreach (var returnOperation in returnOperations) | ||
{ | ||
var syntax = returnOperation.ReturnedValue?.Syntax ?? returnOperation.Syntax; |
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 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 comment
The 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 None
for flow state
var newRoot = oldRoot.ReplaceNode(methodDeclaration, newMethodDeclaration); | ||
|
||
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 comment
The 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.
src/EditorFeatures/CSharpTest/CodeActions/ExtractMethod/ExtractMethodTests.cs
Show resolved
Hide resolved
// follow our recommendation of only using nullable when necessary. | ||
// This is done after method generation instead of at analyzer time because it's purely | ||
// based on the resulting code, which the generator can modify as needed. If return statements | ||
// are added, the flow analysis could change to indicate something different. It's cleaner to rely |
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.
I'm still not sure I understand the case where that would happen. That would seem like the transformation we're doing in extract method has some deeper issue.
src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
|
||
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)] | ||
public Task TestFlowNullable_LambdaWithReturn() | ||
=> TestInRegularAndScriptAsync( |
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.
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.Analyzer.cs
Outdated
Show resolved
Hide resolved
src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs
Show resolved
Hide resolved
{ | ||
BaseMethodDeclarationSyntax _ => true, | ||
AnonymousFunctionExpressionSyntax _ => true, | ||
LocalFunctionStatementSyntax _ => true, |
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.
i feel like there's are ISyntaxFacts for this...
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.
I didn't see one. I saw for method body, and a few that take a position for method head.
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.
Remaining comments may be addressed after merge.
} | ||
|
||
bool AreAllReferencesNotNull(IEnumerable<IOperation> references) | ||
=> references.All(r => semanticModel.GetTypeInfo(r.Syntax).Nullability.FlowState == NullableFlowState.NotNull); |
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.
=> references.All(r => semanticModel.GetTypeInfo(r.Syntax).Nullability.FlowState == NullableFlowState.NotNull); | |
=> references.All(r => semanticModel.GetTypeInfo(r.Syntax, cancellationToken).Nullability.FlowState == NullableFlowState.NotNull); |
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.
(which will also need it to be passed in)
|
||
foreach (var returnOperation in returnOperations) | ||
{ | ||
// If thereturn statement is located in a nested local function or lambda it |
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.
// If thereturn statement is located in a nested local function or lambda it | |
// If the return statement is located in a nested local function or lambda it |
The general premise is to favor non nullable reference types when possible, but we need to use flow analysis to be sure.