-
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 manual recursion for binary patterns #75212
base: main
Are you sure you want to change the base?
Conversation
Much like binary operators, we can have deeply-nested binary patterns. An example of this is our own IsBuildOnlyDiagnostic, which has ~2500 binary patterns in a single arm, and growing every release. To avoid stack depth issues, I took the same approach we do for binary operators; rewrite recursion to use a manual stack for these cases. Fixes dotnet#73439.
@dotnet/roslyn-compiler for review. I cannot stress strongly enough, review with whitespace off. Otherwise all the extractions to local functions will be much harder to review. |
Also note: A much simpler workaround would just be to break up IsBuildOnlyDiagnostic into a few different pattern arms. I didn't take that approach because that would just leave a maintenance task on us every so often to fix up the compilation when some builds randomly start failing; this seems much less painful long term, and I don't think the original code is unreasonable. |
Converting this back to draft while I find a few more places that binary operators are handled that we probably need to handle patterns as well. |
…t code worst-case.
Alright. There are a couple of locations in the optimizer and emit where we do specially handle binary operators, but we these nodes don't survive the local rewriter, and there are existing assertions that we don't have any patterns at that point. This is ready for review. |
@333fred It looks like there are legitimate test failures |
@AlekseyTs I believe they should be fixed now. |
@333fred It looks like EndToEnd tests are crashing, likely related to the added test |
src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs
Outdated
Show resolved
Hide resolved
|
||
// Compute the common type. This algorithm is quadratic, but disjunctive patterns are unlikely to be huge | ||
var narrowedTypeCandidates = ArrayBuilder<TypeSymbol>.GetInstance(2); | ||
collectCandidates(preboundLeft, narrowedTypeCandidates); |
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 the assumption from the comment above that "disjunctive patterns are unlikely to be huge" has been proven wrong. Therefore, it might make sense to keep the set of candidate types from the left as we bubble up, instead of revisiting the subtree over and over again. Also, it might be worth considering to collect only distinct types to decrease amount of memory used. #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.
I'll take the former suggestion, but I'll avoid the latter suggestion for now. We can do it in a followup if it's necessary, but it's a bigger change and I don't think we need to look at it yet.
|
||
var rightPatternStack = ArrayBuilder<PatternSyntax>.GetInstance(); | ||
|
||
while (currentPattern is BinaryPatternSyntax binaryPattern) |
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 think that results in less-clear code, like we have in VisitBinaryExpression
above. I'd prefer to keep this as is.
src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs
Outdated
Show resolved
Hide resolved
@@ -111,8 +111,26 @@ public override BoundNode VisitNegatedPattern(BoundNegatedPattern node) | |||
|
|||
public override BoundNode VisitBinaryPattern(BoundBinaryPattern node) | |||
{ | |||
Visit(node.Left); | |||
Visit(node.Right); | |||
// Users (such as ourselves) can have many, many nested binary patterns. To avoid crashing, do left recursion manually. |
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.
src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs
Outdated
Show resolved
Hide resolved
Done with review pass (commit 5) |
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 10)
@dotnet/roslyn-compiler for a second review |
static BoundPattern bindBinaryPattern( | ||
BoundPattern preboundLeft, | ||
Binder binder, | ||
(BinaryPatternSyntax pat, bool permitDesignations) patternAndPermitDesignations, |
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.
It was originally separate parameters; I moved it to the tuple for simplicity of the calling code.
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 method is not using the tuple as a tuple though. It feels like the caller should be responsible for deconstructing the tuple rather than the method.
@@ -184,22 +184,23 @@ public static Location GetTooLongOrComplexExpressionErrorLocation(BoundNode node | |||
{ | |||
SyntaxNode syntax = node.Syntax; | |||
|
|||
if (!(syntax is ExpressionSyntax)) | |||
if (syntax is not (ExpressionSyntax or PatternSyntax)) |
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.
This doesn't really feel like the type of thing that needs to be extracted to a local function; it honestly feels like that would impede readability, not help. I'd prefer to leave this as is.
"""; | ||
const string append = $""" | ||
|
||
or |
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.
Much like binary operators, we can have deeply-nested binary patterns. An example of this is our own IsBuildOnlyDiagnostic, which has ~2500 binary patterns in a single arm, and growing every release. To avoid stack depth issues, I took the same approach we do for binary operators; rewrite recursion to use a manual stack for these cases. Fixes #73439.