-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Assert shape of the bound tree #81486
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
Conversation
|
Cool idea! |
142ac09 to
70574bb
Compare
70574bb to
70a0b95
Compare
| LocalRewriting, | ||
| ClosureConversion, | ||
| StateMachineRewriting, | ||
| None |
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 this should be called "All" instead of "None"? Since "does not survive none" sounds wrong. #Resolved
| <!-- | ||
| This node will not survive the local rewriting, | ||
| except in some scenarios in a Linq Expression Tree when the containing node | ||
| survives. |
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.
except in some scenarios in a Linq Expression Tree
Are these cases tested? Why doesn't the validator fail for them? Are the bound nodes flagged as erroneous? #WontFix
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 was not able to hit that, so I don't think the comment was correct. I'll revisit if we do end up hitting the assertion
| } | ||
|
|
||
| /// <summary> | ||
| /// Note: do not use a static/singleton instance of this type, as it holds state. |
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.
nit: this comment looks unnecessary since the class has a private constructor #Resolved
| @@ -6,6 +6,7 @@ | |||
|
|
|||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics; | |||
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.
nit: this added using looks unnecessary #Resolved
| public static void AssertAfterInitialBinding(BoundNode node) | ||
| { | ||
| #if DEBUG | ||
| Assert(node, PipelinePhase.InitialBinding); |
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.
Consider exposing just the single Assert method (and yes, having the enum outside #if DEBUG) to avoid this boilerplate #ByDesign
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 had that initially, but that means shipping the enum and adding #if DEBUG at the call-sites.
I prefer this way, where we limit what is in the release assembly and we don't need #if DEBUG at the call-sites (we hide the boilerplate 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.
I think the callsites wouldn't need #if DEBUG since the method is marked as [Conditional]
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.
Let me try this again, but I think callsites do need #if DEBUG if a PipelinePhase is given as argument and that type is DEBUG-only.
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.
Right, I meant that the enum wouldn't be DEBUG-only.
The current state seems fine to me though, thanks.
| } | ||
| } | ||
|
|
||
| #if DEBUG |
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.
Outside DEBUG, consider having a private constructor of PipelinePhaseValidator too (perhaps a one that throws unreachable exception). #Resolved
| { | ||
| if (node is BoundIfStatement) | ||
| { | ||
| Fail(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.
Looks like originally we didn't expect BoundIfStatement to survive local rewriting even if it had errors but now the check is relaxed. Perhaps we can preserve that by adding another knob to BoundNodes.xml ("doesn't survive even with errors"). #WontFix
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 true. However, I'm fine with this minor relaxation given that we're overall tightening and uniformizing the checks. Seems fine to lose this special handling.
| InitialBinding, | ||
| LocalRewriting, | ||
| ClosureConversion, | ||
| StateMachineRewriting, |
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.
I don't think we can. There can be yields remaining after we passed the stage of doing iterator rewriting. Such yields are handled by async/async-iterator rewriter.
| LocalRewriting, | ||
| ClosureConversion, | ||
| StateMachineRewriting, | ||
| All |
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.
"All" feels somewhat strange. This enum is not a [Flags] enum. And returning All from DoesNotSurvive also feels strange. It can be interpreted as "node cannot survive any phase". Only after examining enum underlying values and DoesNotSurvive call sites it becomes clear that the meaning is the opposite. Perhaps "Emit" name would be clearer. It is true that emit phase is not a rewrite phase, but we could pretend that result of "Emit" is an empty tree. #Closed
| { | ||
|
|
||
| RoslynDebug.Assert(binder is object, "Field 'binder' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); | ||
| RoslynDebug.Assert(valueSymbol is object, "Field 'valueSymbol' cannot be null (make the type nullable in BoundNodes.xml to remove this check)"); |
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.
| } | ||
|
|
||
| codeCoverageSpans = codeCoverageInstrumenter?.DynamicAnalysisSpans ?? ImmutableArray<SourceSpan>.Empty; | ||
| PipelinePhaseValidator.AssertAfterLocalRewriting(loweredStatement); |
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.
Similarly, it feels like localRewriter.AssertNoPlaceholderReplacements(); should be done earlier too.
| /// See PipelinePhase enum | ||
| /// </summary> | ||
| [XmlAttribute] | ||
| public string DoesNotSurvive; | ||
| } | ||
|
|
||
| public class 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.
|
|
||
| internal sealed partial class PipelinePhaseValidator | ||
| { | ||
| private PipelinePhaseValidator() |
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.
I see below that we are actually creating instances of this type. Then why do we need this constructor?
| All | ||
| } | ||
|
|
||
| internal sealed partial class PipelinePhaseValidator : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator |
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.
Consider also following a general pattern for member declarations: first fields, then constructors, then the rest.
|
Done with review pass (commit 2) #Closed |
|
I didn't make a mistake regarding "initial binding" in my original comment. In reply to: 3608074660 Refers to: src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml:305 in b5ae4aa. [](commit_id = b5ae4aa, deletion_comment = False) |
|
My mistake In reply to: 3609153340 Refers to: src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml:305 in b5ae4aa. [](commit_id = b5ae4aa, deletion_comment = False) |
|
Done with review pass (commit 4) #Closed |
AlekseyTs
left a comment
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 (commit 5)
| #endif | ||
|
|
||
| #if DEBUG | ||
| private PipelinePhaseValidator(PipelinePhase completedPhase) |
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.
Should we have a private constructor outside DEBUG too, perhaps a throwing one? (I think you've added it previously, but it seems removed now.)
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.
Aleksey asked why have the constructor.
It's not necessary, but the benefit is it would help avoid misuse of the type. But I'm not too worried about that.
This PR generalizes the
LocalRewritingValidatorenforcement and allows declaring how far bound nodes are supposed to survive in the lowering pipeline.Addresses part of #16057