From bc894953de6011cbe8d26c8c7a884c64ca8a045a Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 22 Oct 2024 19:52:14 -0700 Subject: [PATCH] IncrementalBinder - properly bind else-if statement (#75579) This is a follow up on #74317 --- .../Portable/Binder/Binder_Statements.cs | 12 ++ .../Compilation/MemberSemanticModel.cs | 37 ++++-- .../Test/Semantic/Semantics/BindingTests.cs | 118 ++++++++++++++++++ 3 files changed, 159 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index fd3df303cd2fd..0b4c03c795406 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -2553,6 +2553,12 @@ static BoundStatement bindIfStatement(Binder binder, IfStatementSyntax node, Bin { var b = binder.GetBinder(ifStatementSyntax); Debug.Assert(b != null); + + if (b.TryGetBoundElseIfStatement(ifStatementSyntax, out alternative)) + { + break; + } + binder = b; node = ifStatementSyntax; } @@ -2583,6 +2589,12 @@ static BoundStatement bindIfStatement(Binder binder, IfStatementSyntax node, Bin return result; } } + + protected virtual bool TryGetBoundElseIfStatement(IfStatementSyntax node, out BoundStatement? alternative) + { + alternative = null; + return false; + } #nullable disable internal BoundExpression BindBooleanExpression(ExpressionSyntax node, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 5d59391bf30bb..1d90cfbda29fd 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -2415,9 +2415,8 @@ internal override Binder GetBinder(SyntaxNode node) return null; } - public override BoundStatement BindStatement(StatementSyntax node, BindingDiagnosticBag diagnostics) + private BoundStatement TryGetBoundStatementFromMap(StatementSyntax node) { - // Check the bound node cache to see if the statement was already bound. if (node.SyntaxTree == _semanticModel.SyntaxTree) { BoundStatement synthesizedStatement = _semanticModel.GuardedGetSynthesizedStatementFromMap(node); @@ -2427,15 +2426,23 @@ public override BoundStatement BindStatement(StatementSyntax node, BindingDiagno return synthesizedStatement; } - BoundNode boundNode = TryGetBoundNodeFromMap(node); + return (BoundStatement)TryGetBoundNodeFromMap(node); + } - if (boundNode != null) - { - return (BoundStatement)boundNode; - } + return null; + } + + public override BoundStatement BindStatement(StatementSyntax node, BindingDiagnosticBag diagnostics) + { + // Check the bound node cache to see if the statement was already bound. + BoundStatement statement = TryGetBoundStatementFromMap(node); + + if (statement != null) + { + return statement; } - BoundStatement statement = base.BindStatement(node, diagnostics); + statement = base.BindStatement(node, diagnostics); // Synthesized statements are not added to the _guardedNodeMap, we cache them explicitly here in // _lazyGuardedSynthesizedStatementsMap @@ -2515,6 +2522,20 @@ internal override BoundBlock BindExpressionBodyAsBlock(ArrowExpressionClauseSynt return block; } + + protected override bool TryGetBoundElseIfStatement(IfStatementSyntax node, out BoundStatement alternative) + { + alternative = TryGetBoundStatementFromMap(node); + + if (alternative is not null) + { + Debug.Assert(alternative is BoundIfStatement); + alternative = WrapWithVariablesIfAny(node, alternative); + return true; + } + + return base.TryGetBoundElseIfStatement(node, out alternative); + } } internal sealed class MemberSemanticBindingCounter diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs index 0d5e3f4c706bc..3032926c5e92c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs @@ -3962,5 +3962,123 @@ public class MainVersion "); CompileAndVerify(compilation).VerifyDiagnostics(); } + + [Fact] + public void ElseIf_01() + { + var source = +@" +class Program +{ + static void M(bool a, bool b) + { + if (a) + {} + else if (b) + {} + } +}"; + var comp = CreateCompilation(source); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var ids = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + var id = ids[1]; + + Assert.Equal("b", id.ToString()); + Assert.Equal("System.Boolean b", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + id = ids[0]; + + Assert.Equal("a", id.ToString()); + Assert.Equal("System.Boolean a", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + model = comp.GetSemanticModel(tree); + Assert.Equal("System.Boolean a", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + id = ids[1]; + Assert.Equal("System.Boolean b", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + } + + [Fact] + public void ElseIf_02() + { + var source = +@" +class Program +{ + static void M(bool a, object b) + { + if (a) + {} + else if (b is bool bb && bb) + { + bb = false; + } + } +}"; + var comp = CreateCompilation(source); + CompileAndVerify(comp).VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var ids = tree.GetRoot().DescendantNodes().OfType().ToArray(); + + var id = ids[1]; + + Assert.Equal("b", id.ToString()); + Assert.Equal("System.Object b", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + id = ids[0]; + + Assert.Equal("a", id.ToString()); + Assert.Equal("System.Boolean a", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + var ifStmt = tree.GetRoot().DescendantNodes().OfType().First(); + + var operationString = @" +IConditionalOperation (OperationKind.Conditional, Type: null) (Syntax: 'if (a) ... }') + Condition: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'a') + WhenTrue: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{}') + WhenFalse: + IBlockOperation (1 statements, 1 locals) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'if (b is bo ... }') + Locals: Local_1: System.Boolean bb + IConditionalOperation (OperationKind.Conditional, Type: null) (Syntax: 'if (b is bo ... }') + Condition: + IBinaryOperation (BinaryOperatorKind.ConditionalAnd) (OperationKind.Binary, Type: System.Boolean) (Syntax: 'b is bool bb && bb') + Left: + IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'b is bool bb') + Value: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'b') + Pattern: + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'bool bb') (InputType: System.Object, NarrowedType: System.Boolean, DeclaredSymbol: System.Boolean bb, MatchesNull: False) + Right: + ILocalReferenceOperation: bb (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'bb') + WhenTrue: + IBlockOperation (1 statements) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'bb = false;') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: 'bb = false') + Left: + ILocalReferenceOperation: bb (OperationKind.LocalReference, Type: System.Boolean) (Syntax: 'bb') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Boolean, Constant: False) (Syntax: 'false') + WhenFalse: + null +"; + + VerifyOperationTree(comp, model.GetOperation(ifStmt), operationString); + + model = comp.GetSemanticModel(tree); + Assert.Equal("System.Boolean a", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + id = ids[1]; + Assert.Equal("System.Object b", model.GetSymbolInfo(id).Symbol.ToTestDisplayString()); + + VerifyOperationTree(comp, model.GetOperation(ifStmt), operationString); + } } }