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

Support pattern variables declared under "is not" #44513

Merged
merged 2 commits into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 9 additions & 7 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node,

Debug.Assert(expression.Type is { });
uint inputValEscape = GetValEscape(expression, LocalScopeDepth);
BoundPattern pattern = BindPattern(node.Pattern, expression.Type, inputValEscape, permitDesignations: true, hasErrors, diagnostics);
BoundPattern pattern = BindPattern(node.Pattern, expression.Type, inputValEscape, permitDesignations: true, hasErrors, diagnostics, underIsPattern: true);
hasErrors |= pattern.HasErrors;
return MakeIsPatternExpression(
node, expression, pattern, GetSpecialType(SpecialType.System_Boolean, diagnostics, node),
Expand Down Expand Up @@ -140,7 +140,8 @@ internal BoundPattern BindPattern(
uint inputValEscape,
bool permitDesignations,
bool hasErrors,
DiagnosticBag diagnostics)
DiagnosticBag diagnostics,
bool underIsPattern = false)
{
return node switch
{
Expand All @@ -149,9 +150,9 @@ internal BoundPattern BindPattern(
ConstantPatternSyntax p => BindConstantPatternWithFallbackToTypePattern(p, inputType, hasErrors, diagnostics),
RecursivePatternSyntax p => BindRecursivePattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics),
VarPatternSyntax p => BindVarPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics),
ParenthesizedPatternSyntax p => BindPattern(p.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics),
ParenthesizedPatternSyntax p => BindPattern(p.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics, underIsPattern),
BinaryPatternSyntax p => BindBinaryPattern(p, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics),
UnaryPatternSyntax p => BindUnaryPattern(p, inputType, inputValEscape, hasErrors, diagnostics),
UnaryPatternSyntax p => BindUnaryPattern(p, inputType, inputValEscape, hasErrors, diagnostics, underIsPattern),
RelationalPatternSyntax p => BindRelationalPattern(p, inputType, hasErrors, diagnostics),
TypePatternSyntax p => BindTypePattern(p, inputType, hasErrors, diagnostics),
_ => throw ExceptionUtilities.UnexpectedValue(node.Kind()),
Expand Down Expand Up @@ -1315,10 +1316,11 @@ private BoundPattern BindUnaryPattern(
TypeSymbol inputType,
uint inputValEscape,
bool hasErrors,
DiagnosticBag diagnostics)
DiagnosticBag diagnostics,
bool underIsPattern)
{
const bool permitDesignations = false; // prevent designators under 'not'
var subPattern = BindPattern(node.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics);
bool permitDesignations = underIsPattern; // prevent designators under 'not' except under an is-pattern
gafter marked this conversation as resolved.
Show resolved Hide resolved
var subPattern = BindPattern(node.Pattern, inputType, inputValEscape, permitDesignations, hasErrors, diagnostics, underIsPattern);
return new BoundNegatedPattern(node, subPattern, inputType: inputType, convertedType: inputType, hasErrors);
}

Expand Down
16 changes: 15 additions & 1 deletion src/Compilers/CSharp/Portable/FlowAnalysis/AbstractFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,16 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node
{
Debug.Assert(!IsConditionalState);
VisitRvalue(node.Expression);
VisitPattern(node.Pattern);

var pattern = node.Pattern;
bool negated = false;
while (pattern is BoundNegatedPattern n)
gafter marked this conversation as resolved.
Show resolved Hide resolved
{
negated = !negated;
pattern = n.Negated;
}

VisitPattern(pattern);
var reachableLabels = node.DecisionDag.ReachableLabels;
if (!reachableLabels.Contains(node.WhenTrueLabel))
{
Expand All @@ -972,6 +981,11 @@ public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node
SetConditionalState(this.State, UnreachableState());
}

if (negated)
{
SetConditionalState(this.StateWhenFalse, this.StateWhenTrue);
}

return node;
}

Expand Down
255 changes: 224 additions & 31 deletions src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,9 @@ void Good(object o)
if (o is 1 and int x3) { }
if (o is (1 or 2) and int x4) { }
if (o is not (1 or 2) and int x5) { }

if (o is not int x6) { }
if (o is not (1 and int x7)) { }
}

void Bad(object o)
Expand All @@ -1958,11 +1961,9 @@ void Bad(object o)
if (o is int y2 or (1 or 2)) { }
if (o is 1 or int y3) { }
if (o is (1 or 2) or int y4) { }
if (o is not int y5) { }
if (o is not (1 and int y6)) { }
if (o is Point { X: var y7 } or Animal _) { }
if (o is Point(var y8, _) or Animal _) { }
if (o is object or (1 or var y9)) { }
if (o is Point { X: var y5 } or Animal _) { }
if (o is Point(var y6, _) or Animal _) { }
if (o is object or (1 or var y7)) { }
}

void NotBad(object o)
Expand All @@ -1984,36 +1985,30 @@ class Animal { }
";
var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);
compilation.VerifyDiagnostics(
// (16,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// (19,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
gafter marked this conversation as resolved.
Show resolved Hide resolved
// if (o is int y1 or 1) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y1").WithLocation(16, 22),
// (17,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y1").WithLocation(19, 22),
// (20,22): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is int y2 or (1 or 2)) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y2").WithLocation(17, 22),
// (18,27): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y2").WithLocation(20, 22),
// (21,27): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is 1 or int y3) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y3").WithLocation(18, 27),
// (19,34): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y3").WithLocation(21, 27),
// (22,34): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is (1 or 2) or int y4) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y4").WithLocation(19, 34),
// (20,26): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is not int y5) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y5").WithLocation(20, 26),
// (21,33): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is not (1 and int y6)) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y6").WithLocation(21, 33),
// (22,33): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is Point { X: var y7 } or Animal _) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y7").WithLocation(22, 33),
// (23,28): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is Point(var y8, _) or Animal _) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y8").WithLocation(23, 28),
// (24,13): warning CS8794: An expression of type 'object' always matches the provided pattern.
// if (o is object or (1 or var y9)) { }
Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is object or (1 or var y9)").WithArguments("object").WithLocation(24, 13),
// (24,38): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is object or (1 or var y9)) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y9").WithLocation(24, 38)
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y4").WithLocation(22, 34),
// (23,33): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is Point { X: var y5 } or Animal _) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y5").WithLocation(23, 33),
// (24,28): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is Point(var y6, _) or Animal _) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y6").WithLocation(24, 28),
// (25,13): warning CS8794: An expression of type 'object' always matches the provided pattern.
// if (o is object or (1 or var y7)) { }
Diagnostic(ErrorCode.WRN_IsPatternAlways, "o is object or (1 or var y7)").WithArguments("object").WithLocation(25, 13),
// (25,38): error CS8780: A variable may not be declared within a 'not' or 'or' pattern.
// if (o is object or (1 or var y7)) { }
Diagnostic(ErrorCode.ERR_DesignatorBeneathPatternCombinator, "y7").WithLocation(25, 38)
);
}

Expand Down Expand Up @@ -5641,5 +5636,203 @@ public void M() {
Diagnostic(ErrorCode.ERR_IntDivByZero, "0/0").WithLocation(4, 22)
);
}

[Fact]
public void IsNot_01()
gafter marked this conversation as resolved.
Show resolved Hide resolved
{
var source =
@"using System;
class C
{
static void Main()
{
object o = ""s"";
if (o is not string s) return;
Console.WriteLine(s);
}
}";
string expectedOutput = "s";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
);
var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact]
public void IsNot_02()
{
var source =
@"using System;
class C
{
static void Main()
{
object o = ""s"";
if (o is (not (string s))) return;
Console.WriteLine(s);
}
}";
string expectedOutput = "s";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
);
var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact]
public void IsNot_03()
{
var source =
@"class C
{
static void Main()
{
object o = ""s"";
{
if (o is string s)
_ = s;
else
_ = s; // 1
}
{
if (o is not string s)
_ = s; // 2
else
_ = s;
}
{
if (o is not not string s)
_ = s;
else
_ = s; // 3
}
{
if (o is not not not string s)
_ = s; // 4
else
_ = s;
}
}
}";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
// (10,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 1
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(10, 21),
// (14,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 2
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(14, 21),
// (22,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 3
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(22, 21),
// (26,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 4
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(26, 21)
);
}

[Fact]
public void IsNot_04()
{
var source =
@"class C
{
static void Main()
{
object o = ""s"";
{
if (o is (string s))
_ = s;
else
_ = s; // 1
}
{
if (o is (not (string s)))
_ = s; // 2
else
_ = s;
}
{
if (o is (not (not (string s))))
_ = s;
else
_ = s; // 3
}
{
if (o is (not (not (not (string s)))))
_ = s; // 4
else
_ = s;
}
}
}";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
// (10,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 1
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(10, 21),
// (14,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 2
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(14, 21),
// (22,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 3
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(22, 21),
// (26,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 4
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(26, 21)
);
}

[Fact]
public void IsNot_05()
{
var source =
@"class C
{
static void Main()
{
(object, object) o = (1, 2);
{
if (o is (1, string s))
_ = s;
else
_ = s; // 1
}
{
if (o is (not (1, string s)))
_ = s; // 2
else
_ = s;
}
{
if (o is (not (not (1, string s))))
_ = s;
else
_ = s; // 3
}
{
if (o is (not (not (not (1, string s)))))
_ = s; // 4
else
_ = s;
}
}
}";
var compilation = CreateCompilation(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.RegularWithPatternCombinators);
compilation.VerifyDiagnostics(
// (10,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 1
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(10, 21),
// (14,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 2
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(14, 21),
// (22,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 3
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(22, 21),
// (26,21): error CS0165: Use of unassigned local variable 's'
// _ = s; // 4
Diagnostic(ErrorCode.ERR_UseDefViolation, "s").WithArguments("s").WithLocation(26, 21)
);
}
}
}