Skip to content
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

Bind expression variables appearing in a goto case statement. #41709

Merged
merged 5 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ protected void FindExpressionVariables(
case SyntaxKind.VariableDeclarator:
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.SwitchExpressionArm:
case SyntaxKind.GotoCaseStatement:
break;
case SyntaxKind.ArgumentList:
Debug.Assert(node.Parent is ConstructorInitializerSyntax);
Expand Down Expand Up @@ -99,6 +100,12 @@ public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
VisitNodeToBind(node.Initializer);
}

public override void VisitGotoStatement(GotoStatementSyntax node)
{
if (node.Kind() == SyntaxKind.GotoCaseStatement)
Visit(node.Expression);
}

private void VisitNodeToBind(CSharpSyntaxNode node)
{
SyntaxNode previousNodeToBind = _nodeToBind;
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ protected ImmutableArray<LocalSymbol> BuildLocals(SyntaxList<StatementSyntax> st
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.GotoCaseStatement:
gafter marked this conversation as resolved.
Show resolved Hide resolved
ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder);
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ internal static LocalSymbol MakeLocalSymbolWithEnclosingContext(
nodeToBind.Kind() == SyntaxKind.BaseConstructorInitializer ||
nodeToBind.Kind() == SyntaxKind.SwitchExpressionArm ||
nodeToBind.Kind() == SyntaxKind.ArgumentList && nodeToBind.Parent is ConstructorInitializerSyntax ||
nodeToBind.Kind() == SyntaxKind.GotoCaseStatement || // for error recovery
nodeToBind.Kind() == SyntaxKind.VariableDeclarator &&
new[] { SyntaxKind.LocalDeclarationStatement, SyntaxKind.ForStatement, SyntaxKind.UsingStatement, SyntaxKind.FixedStatement }.
Contains(nodeToBind.Ancestors().OfType<StatementSyntax>().First().Kind()) ||
Expand Down Expand Up @@ -742,6 +743,7 @@ public LocalSymbolWithEnclosingContext(
nodeToBind.Kind() == SyntaxKind.ArgumentList && nodeToBind.Parent is ConstructorInitializerSyntax ||
nodeToBind.Kind() == SyntaxKind.VariableDeclarator ||
nodeToBind.Kind() == SyntaxKind.SwitchExpressionArm ||
nodeToBind.Kind() == SyntaxKind.GotoCaseStatement ||
nodeToBind is ExpressionSyntax);
Debug.Assert(!(nodeToBind.Kind() == SyntaxKind.SwitchExpressionArm) || nodeBinder is SwitchExpressionArmBinder);
this._nodeBinder = nodeBinder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,62 @@ static void Main(string[] args)
VerifyOperationTreeAndDiagnosticsForTest<GotoStatementSyntax>(source, expectedOperationTree, expectedDiagnostics);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact, WorkItem(40714, "https://github.com/dotnet/roslyn/issues/40714")]
public void InvalidGotoCaseStatement_BadLabel()
{
string source = @"
using System;

class Program
{
static void Main(string[] args)
{
switch (args[0], args[1])
{
case (string s1, string s2) _:
/*<bind>*/goto case args is (var x1, var x2);/*</bind>*/
x1 = x2;
case (string str, null) _:
break;
}
}
}
";
string expectedOperationTree = @"
IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'goto case a ... 1, var x2);')
Children(1):
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: (System.String, System.String), IsInvalid, IsImplicit) (Syntax: 'args is (var x1, var x2)')
Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
Operand:
IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'args is (var x1, var x2)')
Value:
IParameterReferenceOperation: args (OperationKind.ParameterReference, Type: System.String[], IsInvalid) (Syntax: 'args')
Pattern:
IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '(var x1, var x2)') (InputType: System.String[], DeclaredSymbol: null, MatchedType: System.String[], DeconstructSymbol: null)
DeconstructionSubpatterns (2):
IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null, IsInvalid) (Syntax: 'var x1') (InputType: ?, DeclaredSymbol: ?? x1, MatchesNull: True)
IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null, IsInvalid) (Syntax: 'var x2') (InputType: ?, DeclaredSymbol: ?? x2, MatchesNull: True)
PropertySubpatterns (0)
";
var expectedDiagnostics = new DiagnosticDescription[] {
// file.cs(10,18): error CS0163: Control cannot fall through from one case label ('(string s1, string s2) _') to another
// case (string s1, string s2) _:
Diagnostic(ErrorCode.ERR_SwitchFallThrough, "(string s1, string s2) _").WithArguments("(string s1, string s2) _").WithLocation(10, 18),
// file.cs(11,27): error CS0029: Cannot implicitly convert type 'bool' to '(string, string)'
// /*<bind>*/goto case args is (var x1, var x2);/*</bind>*/
Diagnostic(ErrorCode.ERR_NoImplicitConv, "goto case args is (var x1, var x2);").WithArguments("bool", "(string, string)").WithLocation(11, 27),
// file.cs(11,45): error CS1061: 'string[]' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'string[]' could be found (are you missing a using directive or an assembly reference?)
// /*<bind>*/goto case args is (var x1, var x2);/*</bind>*/
Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "(var x1, var x2)").WithArguments("string[]", "Deconstruct").WithLocation(11, 45),
// file.cs(11,45): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'string[]', with 2 out parameters and a void return type.
// /*<bind>*/goto case args is (var x1, var x2);/*</bind>*/
Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(var x1, var x2)").WithArguments("string[]", "2").WithLocation(11, 45)
};

VerifyOperationTreeAndDiagnosticsForTest<GotoStatementSyntax>(source, expectedOperationTree, expectedDiagnostics);
}

[CompilerTrait(CompilerFeature.IOperation)]
[Fact, WorkItem(17607, "https://github.com/dotnet/roslyn/issues/17607")]
public void InvalidGotoCaseStatement_OutsideSwitchStatement()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@ protected static void VerifyModelForDuplicateVariableDeclarationInSameScope(Sema
Assert.True(model.LookupNames(declarator.SpanStart).Contains(declarator.Identifier.ValueText));
}

internal static void VerifyModelForDuplicateVariableDeclarationInSameScope(
SemanticModel model,
SingleVariableDesignationSyntax designation,
LocalDeclarationKind kind)
{
var symbol = model.GetDeclaredSymbol(designation);
Assert.Equal(designation.Identifier.ValueText, symbol.Name);
Assert.Equal(designation, symbol.DeclaringSyntaxReferences.Single().GetSyntax());
Assert.Equal(kind, symbol.GetSymbol<LocalSymbol>().DeclarationKind);
Assert.Same(symbol, model.GetDeclaredSymbol((SyntaxNode)designation));
Assert.NotEqual(symbol, model.LookupSymbols(designation.SpanStart, name: designation.Identifier.ValueText).Single());
Assert.True(model.LookupNames(designation.SpanStart).Contains(designation.Identifier.ValueText));
}

protected static void VerifyNotAPatternField(SemanticModel model, IdentifierNameSyntax reference)
{
var symbol = model.GetSymbolInfo(reference).Symbol;
Expand Down
117 changes: 117 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1771,5 +1771,122 @@ static void M(int i)
Diagnostic(ErrorCode.ERR_AmbigUDConv, "new A()").WithArguments("A.implicit operator B(A)", "B.implicit operator B(A)", "A", "B").WithLocation(16, 43)
);
}

[WorkItem(40714, "https://github.com/dotnet/roslyn/issues/40714")]
[Fact]
public void BadGotoCase_01()
gafter marked this conversation as resolved.
Show resolved Hide resolved
{
var source = @"
class C
{
static void Example(object a, object b)
{
switch ((a, b))
{
case (string str, int[] arr) _:
goto case (string str, decimal[] arr);
case (string str, decimal[] arr) _:
break;
}
}
}
";
var compilation = CreateCompilation(source);
compilation.VerifyDiagnostics(
// (8,18): error CS0163: Control cannot fall through from one case label ('(string str, int[] arr) _') to another
// case (string str, int[] arr) _:
Diagnostic(ErrorCode.ERR_SwitchFallThrough, "(string str, int[] arr) _").WithArguments("(string str, int[] arr) _").WithLocation(8, 18),
// (8,26): error CS0136: A local or parameter named 'str' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
// case (string str, int[] arr) _:
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "str").WithArguments("str").WithLocation(8, 26),
// (8,37): error CS0136: A local or parameter named 'arr' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
// case (string str, int[] arr) _:
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "arr").WithArguments("arr").WithLocation(8, 37),
// (9,17): error CS0150: A constant value is expected
// goto case (string str, decimal[] arr);
Diagnostic(ErrorCode.ERR_ConstantExpected, "goto case (string str, decimal[] arr);").WithLocation(9, 17),
// (9,28): error CS8185: A declaration is not allowed in this context.
// goto case (string str, decimal[] arr);
Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "string str").WithLocation(9, 28),
// (9,28): error CS0165: Use of unassigned local variable 'str'
// goto case (string str, decimal[] arr);
Diagnostic(ErrorCode.ERR_UseDefViolation, "string str").WithArguments("str").WithLocation(9, 28),
// (9,40): error CS8185: A declaration is not allowed in this context.
// goto case (string str, decimal[] arr);
Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "decimal[] arr").WithLocation(9, 40),
// (9,40): error CS0165: Use of unassigned local variable 'arr'
// goto case (string str, decimal[] arr);
Diagnostic(ErrorCode.ERR_UseDefViolation, "decimal[] arr").WithArguments("arr").WithLocation(9, 40),
// (10,26): error CS0136: A local or parameter named 'str' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
// case (string str, decimal[] arr) _:
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "str").WithArguments("str").WithLocation(10, 26),
// (10,41): error CS0136: A local or parameter named 'arr' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
// case (string str, decimal[] arr) _:
Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "arr").WithArguments("arr").WithLocation(10, 41)
);

var tree = compilation.SyntaxTrees.Single();
var model = compilation.GetSemanticModel(tree);

var strDecl = tree.GetRoot().DescendantNodes().OfType<SingleVariableDesignationSyntax>().Where(s => s.Identifier.ValueText == "str").ToArray();
Assert.Equal(3, strDecl.Length);
VerifyModelForDuplicateVariableDeclarationInSameScope(model, strDecl[1], LocalDeclarationKind.DeclarationExpressionVariable);

var arrDecl = tree.GetRoot().DescendantNodes().OfType<SingleVariableDesignationSyntax>().Where(s => s.Identifier.ValueText == "arr").ToArray();
Assert.Equal(3, arrDecl.Length);
VerifyModelForDuplicateVariableDeclarationInSameScope(model, arrDecl[1], LocalDeclarationKind.DeclarationExpressionVariable);
}

[WorkItem(40714, "https://github.com/dotnet/roslyn/issues/40714")]
[Fact]
public void BadGotoCase_02()
{
var source = @"
class C
{
static void Example(object a, object b)
{
switch ((a, b))
{
case (string str, int[] arr) _:
goto case a is (var x1, var x2);
x1 = x2;
case (string str, decimal[] arr) _:
break;
}
}
}
";
var compilation = CreateCompilation(source);
compilation.VerifyDiagnostics(
// (8,18): error CS0163: Control cannot fall through from one case label ('(string str, int[] arr) _') to another
// case (string str, int[] arr) _:
Diagnostic(ErrorCode.ERR_SwitchFallThrough, "(string str, int[] arr) _").WithArguments("(string str, int[] arr) _").WithLocation(8, 18),
// (9,17): error CS0029: Cannot implicitly convert type 'bool' to '(object a, object b)'
// goto case a is (var x1, var x2);
Diagnostic(ErrorCode.ERR_NoImplicitConv, "goto case a is (var x1, var x2);").WithArguments("bool", "(object a, object b)").WithLocation(9, 17),
// (9,32): error CS1061: 'object' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
// goto case a is (var x1, var x2);
Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "(var x1, var x2)").WithArguments("object", "Deconstruct").WithLocation(9, 32),
// (9,32): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'object', with 2 out parameters and a void return type.
// goto case a is (var x1, var x2);
Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(var x1, var x2)").WithArguments("object", "2").WithLocation(9, 32)
);

var tree = compilation.SyntaxTrees.Single();
var model = compilation.GetSemanticModel(tree);

var x1Decl = GetPatternDeclarations(tree, "x1").ToArray();
var x1Ref = GetReferences(tree, "x1").ToArray();
Assert.Equal(1, x1Decl.Length);
Assert.Equal(1, x1Ref.Length);
VerifyModelForDeclarationOrVarSimplePattern(model, x1Decl[0], x1Ref);

var x2Decl = GetPatternDeclarations(tree, "x2").ToArray();
var x2Ref = GetReferences(tree, "x2").ToArray();
Assert.Equal(1, x2Decl.Length);
Assert.Equal(1, x2Ref.Length);
VerifyModelForDeclarationOrVarSimplePattern(model, x2Decl[0], x2Ref);
}
}
}