Skip to content

Commit

Permalink
IncrementalBinder - properly bind else-if statement (#75579)
Browse files Browse the repository at this point in the history
This is a follow up on #74317
  • Loading branch information
AlekseyTs authored Oct 23, 2024
1 parent 77c6704 commit bc89495
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 8 deletions.
12 changes: 12 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 29 additions & 8 deletions src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/BindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IdentifierNameSyntax>().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<IdentifierNameSyntax>().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<IfStatementSyntax>().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);
}
}
}

0 comments on commit bc89495

Please sign in to comment.