-
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
Use flag to represent suppression in bound tree #32850
Conversation
if (unboundLambda.IsSuppressed) | ||
{ | ||
boundLambda = boundLambda.WithSuppression(); | ||
} |
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 a reason the implementation of Bind
shouldn't be handling this? #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.
That's a good idea. #Resolved
@@ -268,6 +270,14 @@ private BoundExpression BindArgListOperator(InvocationExpressionSyntax node, Dia | |||
CheckRestrictedTypeReceiver(result, this.Compilation, diagnostics); | |||
|
|||
return result; | |||
|
|||
void reportSuppressionIfNeeded() |
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.
reportSuppressionIfPresent
? #Resolved
@@ -2041,7 +2041,17 @@ private BoundExpression BindIncrementOperator(CSharpSyntaxNode node, ExpressionS | |||
private BoundExpression BindSuppressNullableWarningExpression(PostfixUnaryExpressionSyntax node, DiagnosticBag diagnostics) | |||
{ | |||
var expr = BindExpression(node.Operand, diagnostics); | |||
return new BoundSuppressNullableWarningExpression(node, expr, expr.Type); | |||
switch (expr.Kind) |
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 not use IsLegalSuppressionValueKind
here? Isn't that the same logic? #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.
They are different logic.
Maybe IsLegalSuppressionValueKind
is not the best name, but it checks which ValueKind
s can be used to access a suppression expression.
For instance, ValueKind.IncrementDecrement
is disallowed. That means we're blocking some cases where suppression could appear (x!++
).
On the other hand, here, we're blocking what could appear underneath a suppression. #Resolved
Why is this context illegal? I can imagine that Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:47 in edd850f. [](commit_id = edd850f, deletion_comment = False) |
Done with review pass (iteration 3). Really like this approach. I think it's much cleaner. #Resolved |
I have a follow-up issue to refine the list of private static bool IsLegalSuppressionValueKind(BindValueKind valueKind)
{
// Need to review allowed uses of the suppression operator
// Tracked by https://github.com/dotnet/roslyn/issues/31297
``` #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.
} | ||
else | ||
{ | ||
Debug.Assert((_attributes & BoundNodeAttributes.IsSuppressed) == 0, "flag should not be reset"); |
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.
Debug.Assert((_attributes & BoundNodeAttributes.IsSuppressed) == 0, "flag should not be reset"); [](start = 20, length = 96)
Should we assert this always (before the if
), to ensure the bit is not set twice? #Closed
{ | ||
if (expression.IsSuppressed) | ||
{ | ||
Error(ErrorCode.ERR_ExpressionTreeCantContainSuppressNullableWarning, expression); |
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.
ERR_ExpressionTreeCantContainSuppressNullableWarning [](start = 32, length = 52)
Why disallow !
in an expression tree?
Won't we report a nullability warning for => x
?
static void F(object? x)
{
Expression<Func<object>> e = () => x;
}
``` #Closed
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.
When suppressions were represented as a bound node, I think it made more sense to disallow. Now I could go either way, but I'm still leaning on disallowing since it serves no purpose. That reserves our options in the future.
Why do you think we should allow?
Update: Chuck convinced me offline :-)
In reply to: 251513004 [](ancestors = 251513004)
@@ -5317,7 +5314,11 @@ private void CheckPossibleNullReceiver(BoundExpression receiverOpt, bool checkNu | |||
{ | |||
return; | |||
} | |||
ReportSafetyDiagnostic(isValueType ? ErrorCode.WRN_NullableValueTypeMayBeNull : ErrorCode.WRN_NullReferenceReceiver, syntaxOpt ?? receiverOpt.Syntax); | |||
|
|||
if (!receiverOpt.IsSuppressed) |
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 (!receiverOpt.IsSuppressed) [](start = 20, length = 30)
Is this check necessary? Won't _resultType
have been set appropriately by the caller? #Closed
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.
Thanks. Indeed. I think I added this before I fixed the result type from VisitExpressionWithoutStackGuard
and should have revisited this afterwards.
In reply to: 251524384 [](ancestors = 251524384)
@@ -238,6 +238,7 @@ protected ConstantValue MakeDefaultExpression(DiagnosticBag diagnostics, Binder | |||
parameterType.NullableAnnotation.IsAnyNotNullable() && | |||
convertedExpression.ConstantValue?.IsNull == true && | |||
!suppressNullableWarning(convertedExpression) && | |||
!convertedExpression.IsSuppressed && |
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.
convertedExpression.IsSuppressed [](start = 17, length = 32)
Should this be handled in suppressNullableWarning()
? #Closed
return (_attributes & BoundNodeAttributes.IsSuppressed) != 0; | ||
} | ||
internal set | ||
{ |
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 existence of this internal setter is inherently dangerous. Can you make it protected? Or better yet remove it and instead add a protected method whose purpose is to set suppressed to true. #Resolved
BoundQueryClause has three fields declared in BoundQueryClause.cs. Does that need to be handled? #Resolved Refers to: src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs:8064 in eaa8738. [](commit_id = eaa8738, deletion_comment = False) |
@@ -69,6 +69,13 @@ public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, BoundBlock bo | |||
); | |||
} | |||
|
|||
public BoundLambda WithSuppression() |
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.
WithSuppression [](start = 27, length = 15)
This method does not override anything virtual. Where is it called?
I recommend making WithSuppression
a virtual method in BoundExpression
(rather than an extension method) so that it can be overridden.
Structured that way, you might consider renaming WithSuppressionCore
to ShallowClone
and move the setting of the suppression bit to the WithSuppression
method. #Resolved
{ | ||
Error(ErrorCode.ERR_ExpressionTreeCantContainRefStruct, node, type.Name); | ||
ReportSuppressionIfNeeded(expression); | ||
|
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 don't see a problem with suppression in expression trees. Like parens, they would be applied at compile-time only. #Resolved
{ | ||
return (T)node.WithSuppressionCore(); | ||
} | ||
|
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.
Does any caller need this to be generic? #Resolved
@@ -1594,6 +1645,11 @@ private static bool SkipInVisitor(Field f) | |||
return f.SkipInVisitor != null && string.Compare(f.SkipInVisitor, "true", true) == 0; | |||
} | |||
|
|||
private static bool SkipShallowClone(Node n) | |||
{ | |||
return n.SkipShallowClone != null && string.Compare(n.SkipShallowClone, "true", true) == 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.
The n.SkipShallowClone != null
check seems unnecessary. (Same comment for similar existing methods above.) #Closed
@@ -885,11 +885,49 @@ private void WriteType(TreeType node) | |||
{ | |||
WriteAccept(node.Name); | |||
WriteUpdateMethod(node as Node); | |||
WriteWithSuppressionMethodIfNeeded(node as Node); |
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.
WriteWithSuppressionMethodIfNeeded [](start = 16, length = 34)
Should be WriteShallowCloneMethodIfNeeded
#Resolved
That's covered in |
Consider using Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:515 in 5bc7a16. [](commit_id = 5bc7a16, deletion_comment = False) |
Consider testing: static void Main()
{
_ = Main()!;
}
``` #Closed
---
Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:1476 in 5bc7a16. [](commit_id = 5bc7a161b6fc688e8b728d306f9afaa45e838674, deletion_comment = False) |
Consider reducing down to: static void Main()
{
var x = (() => { return 1; } !) ?.ToString();
} #Closed
---
Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:1537 in 5bc7a16. [](commit_id = 5bc7a161b6fc688e8b728d306f9afaa45e838674, deletion_comment = False) |
(type.IsNull ? "<null>" : type.ToDisplayString(TypeSymbolWithAnnotations.TestDisplayFormat)) : | ||
null; | ||
// We don't support VerifyTypes on suppressions at the moment | ||
Debug.Assert(syntaxOpt.Kind() != SyntaxKind.SuppressNullableWarningExpression); |
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.
Assert.NotEqual(syntaxOpt.Kind(), SyntaxKind.SuppressNullableWarningExpression);
#Closed
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 was intentional. It's documenting the contract of the local function, rather than a test assertion. But I don't mind either way...
In reply to: 252035524 [](ancestors = 252035524)
This is on purpose because I found the test output more useful this way. I don't like to click on the stack trace to figure out if the problem is with In reply to: 458714835 [](ancestors = 458714835) Refers to: src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs:515 in 5bc7a16. [](commit_id = 5bc7a16, deletion_comment = False) |
@@ -100,6 +100,20 @@ public SyntaxTree SyntaxTree | |||
} | |||
} | |||
|
|||
protected void CopyAttributes(BoundNode original) | |||
{ | |||
if (this.WasCompilerGenerated) |
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.
Shouild this be if (original.WasCompilerGenerated)
? #Closed
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 (original.IsSuppressed) | ||
{ | ||
this.IsSuppressed = original.IsSuppressed; | ||
} |
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 realize each attribute checks the value is not already set, but ignoring that, it seems strange that the contract of this method is to only set the attributes if the original attributes were set.
Consider setting the attributes unconditionally:
this.WasCompilerGenerated = original.WasCompilerGenerated;
this.IsSuppressed = original.IsSuppressed;
``` #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.
What about VB?
I tried to align the two implementations since it is possible.
In reply to: 252046440 [](ancestors = 252046440)
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 two implementations will be distinct since VB uses SetWasCompilerGenerated()
that does not allow setting False
.
In reply to: 252049465 [](ancestors = 252049465,252046440)
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 C# WasCompilerGenerated
setter also does not allow setting to false
In reply to: 252051070 [](ancestors = 252051070,252049465,252046440)
This PR addresses some issues with the suppression operator. It does not address tuple scenarios or the semantic model.
Compared to #32699 which added many special cases based on bound node for suppression, this PR drops the suppression node from the bound tree and represents suppression as a flag.
Fixed a number of crashes
Fixes #29862 (top-level nullability of !)
Fixes #29903 (
t! = s
should be an error)I also noticed that we skipped some warnings in cast scenarios (latest LDM notes on the topic)
Fixes #31294 (
throw null!;
isn't allowed)Fixes #29905
Fixes #29906
Follow-ups:
Notes: