Enable Nullability Information in IOperation.Type#42403
Enable Nullability Information in IOperation.Type#4240313 commits merged intorelease/dev16.8-preview1from
Conversation
src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
Outdated
Show resolved
Hide resolved
|
@dotnet/roslyn-compiler @AlekseyTs This is now ready for review. My testing strategy here was to update everywhere that we are currently testing the nullable api to get the IOperation tree for that location, and assert that it matches the info from GetTypeInfo. |
|
@dotnet/roslyn-compiler @AlekseyTs for a review please. |
| out SnapshotManager? snapshotManager, | ||
| ref ImmutableDictionary<Symbol, Symbol>? remappedSymbols) | ||
| ref ImmutableDictionary<Symbol, Symbol>? remappedSymbols, | ||
| bool avoidRewrite) |
There was a problem hiding this comment.
The prefix avoid does not line up with the documentation that says "force". Feel like one of those needs to change. #Resolved
There was a problem hiding this comment.
| #if DEBUG | ||
| Debug.Assert(avoidRewrite != compilation.NullableSemanticAnalysisEnabled); | ||
| #else | ||
| if (avoidRewrite) throw ExceptionUtilities.Unreachable; |
There was a problem hiding this comment.
Overall this dance feels a bit awkward. We're using runtime checks to enforce behavior that is actually codified at compile time. I think this would be a lot cleaner if we split it up into two methods: Analyze and AnalyzeAndRewrite. #Resolved
src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
Outdated
Show resolved
Hide resolved
|
|
||
| if (avoidRewrite) | ||
| { | ||
| return node; |
There was a problem hiding this comment.
return node [](start = 16, length = 11)
return node [](start = 16, length = 11)
Would it make sense to push this down to the Rewrite to disable actual node modifications, but do all the other work? #Closed
There was a problem hiding this comment.
Responded in the other comment, but I don't believe that running the rewriter actually gets us any real verification and it would significantly complicate the generated code.
In reply to: 402669321 [](ancestors = 402669321)
src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
Outdated
Show resolved
Hide resolved
| // in the IOperation tree, and we should retrieve the IOperation node underlying the suppression. | ||
| if (expression.IsKind(SyntaxKind.SuppressNullableWarningExpression)) | ||
| { | ||
| expression = ((PostfixUnaryExpressionSyntax)expression).Operand; |
There was a problem hiding this comment.
expression = ((PostfixUnaryExpressionSyntax)expression).Operand; [](start = 20, length = 64)
expression = ((PostfixUnaryExpressionSyntax)expression).Operand; [](start = 20, length = 64)
Should we do this in a loop and, perhaps, dig through parenthesis too? #Closed
There was a problem hiding this comment.
I believe this was originally in getOperation, I am proceeding with that assumption.
In reply to: 402673404 [](ancestors = 402673404)
|
|
||
| static bool shouldAvoidRewrite(CSharpCompilation comp) => | ||
| #if DEBUG | ||
| !comp.NullableSemanticAnalysisEnabled; |
There was a problem hiding this comment.
Putting this comment back in line after rebasing:
Doesn't this disable a good chunk of testing we were trying to do in DEBUG? #Resolved
There was a problem hiding this comment.
The only portion that is disabled is the actual rewrite step. I does technically add more coverage, but in practice I don't believe that's a huge gain. The rewriter is almost entirely generated and does very little validation.
In reply to: 438466952 [](ancestors = 438466952)
| Assert.Equal("System.String?", model.GetTypeInfo(declaration.Type).ConvertedType.ToTestDisplayString()); | ||
| Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, model.GetTypeInfo(declaration.Type).ConvertedNullability.Annotation); | ||
| Assert.Equal("System.String?", model.GetTypeInfoAndVerifyIOperation(declaration.Type).Type.ToTestDisplayString()); | ||
| Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, model.GetTypeInfoAndVerifyIOperation(declaration.Type).Nullability.Annotation); |
There was a problem hiding this comment.
GetTypeInfoAndVerifyIOperation [](start = 74, length = 30)
Putting this comment back in line after rebase:
Perhaps the second time we can skip IOperation check? There are several more similar cases like this. #Resolved
|
@AlekseyTs @jaredpar this is ready for another review. |
|
When I have sign-offs on this change I'm going to change the base branch to be 16.8 as this doesn't meant the 16.7 bar. Keeping this in master for now though so review comments don't jump. |
|
@AlekseyTs @jaredpar for another review please. |
|
@AlekseyTs @jaredpar for another review please. |
src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.DebugVerifier.cs
Show resolved
Hide resolved
| return null; | ||
| } | ||
|
|
||
| public override BoundNode? VisitSwitchExpressionArm(BoundSwitchExpressionArm node) |
There was a problem hiding this comment.
VisitSwitchExpressionArm [](start = 39, length = 24)
What is the actual change in behavior for a switch expression here? What does base implementation do differently and why is that a problem? #Resolved
There was a problem hiding this comment.
The base unconditionally visits the when clause. This is an issue for the reason in the comment.
In reply to: 441175291 [](ancestors = 441175291)
|
@dotnet/roslyn-compiler for a second review. |
| if (node.Left is BoundObjectInitializerMember { MemberSymbol: null }) | ||
| { | ||
| VerifyExpression(node); | ||
| return Visit(node.Left); |
There was a problem hiding this comment.
return Visit(node.Left); [](start = 20, length = 24)
This looks suspicious. Why are we replacing an assignment with its left side? If return value is not important, let's return null. #Pending
|
@dotnet/roslyn-compiler for a second review. |
|
@dotnet/roslyn-compiler for a second review please. |
|
@dotnet/roslyn-compiler for another review. |
| /// Performs just the analysis step of getting nullability information for a semantic model. This is only used during DEBUG builds where nullable | ||
| /// is turned off on a compilation level, giving some extra debug verification of the nullable flow analysis. | ||
| /// </summary> | ||
| protected abstract void AnalyzeBoundNodeNullability(BoundNode boundRoot, Binder binder, DiagnosticBag diagnostics, bool takeSnapshots); |
There was a problem hiding this comment.
The declaration of this method has the parameter name takeSnapshots but all of the overrides have the parameter craeteSnapshots. Feel like we should be consistent here. #Pending
| #if DEBUG | ||
| if (!Compilation.NullableSemanticAnalysisEnabled) | ||
| { | ||
| AnalyzeBoundNodeNullability(boundRoot, binder, diagnosticBag, takeSnapshots: true); |
There was a problem hiding this comment.
I understand that diagnosticBag here will be a freshly created bag whenever we hit this code. This still feels very indirect though. Have to think a bit to verify this code isn't potentially adding new diagnostics in DEBUG mode. Is there a reason to not just use new DiagnosticBag() here to make this clearer? #ByDesign
There was a problem hiding this comment.
I wanted to avoid having the debug verification step create multiple throwaway diagnostic bags.
In reply to: 443950238 [](ancestors = 443950238)
This enables nullability information from rewritten BoundNodes in IOperation, exposed via the NullableAnnotation property on IOperation.Type. The bulk of the change is creating a simple helper on BoundExpression to return the correctly annotated ITypeSymbol, and using that in the factory instead of .Type.GetPublicSymbol. This exposed another issue with a few of our tests though: in DEBUG mode, when nullable is gobally disabled, we perform a full nullable analysis and rewrite and then discard the results, to ensure that we don't crash and to run our general verification code. However, there are a few nodes that get cached by binders (such as LockOrUsingBinder). These nodes do not get rewritten as their constituent elements did not change, so when we set lazy properties like TopLevelNullability info, the cache from the throwaway binding gets updated as well. To remedy this and still get coverage, we pass along a flag to abort before the actual rewrite in DEBUG scenarios.
…s are an rvalue context). Add testing to everywhere we're currently testing GetTypeInfo with nullable enabled. Enable digging through nullable suppression operators like we do for GetTypeInfo.
* Split debug verification out into AnalyzeWithoutRewrite to simplify code. * Add additional GetPublicTypeSymbol calls in OperationFactory with changes in master. * Add a few NullableWalker.DebugVerifier cases for bugs.
…ssions when validating IOperation nullability.
This enables nullability information from rewritten BoundNodes
in IOperation, exposed via the NullableAnnotation property on
IOperation.Type. The bulk of the change is creating a simple helper
on BoundExpression to return the correctly annotated ITypeSymbol,
and using that in the factory instead of .Type.GetPublicSymbol. This
exposed another issue with a few of our tests though: in DEBUG mode,
when nullable is gobally disabled, we perform a full nullable analysis
and rewrite and then discard the results, to ensure that we don't
crash and to run our general verification code. However, there are
a few nodes that get cached by binders (such as LockOrUsingBinder).
These nodes do not get rewritten as their constituent elements did
not change, so when we set lazy properties like TopLevelNullability
info, the cache from the throwaway binding gets updated as well.
To remedy this and still get coverage, we pass along a flag to abort
before the actual rewrite in DEBUG scenarios. This fixes #40352