未验证 提交 5fd3c4c4 编写于 作者: N Neal Gafter 提交者: GitHub

Add support for recursive patterns in `is` (#23139)

- Add support for recursive patterns in `is`.
- Add the discard pattern `_`.
- Add skeletal IOperation support.
- Adjust tests per changes in `is pattern` code gen.
上级 dfe87b3b
......@@ -259,7 +259,7 @@ private BoundExpression FixTupleLiteral(ArrayBuilder<DeconstructionVariable> che
ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders;
var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type);
var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count,
inputPlaceholder, rightSyntax, diagnostics, out outPlaceholders);
inputPlaceholder, rightSyntax, diagnostics, out outPlaceholders, requireTwoOrMoreElements: true);
if (deconstructInvocation.HasAnyErrors)
{
......@@ -589,10 +589,11 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres
/// </summary>
private BoundExpression MakeDeconstructInvocationExpression(
int numCheckedVariables, BoundExpression receiver, SyntaxNode rightSyntax,
DiagnosticBag diagnostics, out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders)
DiagnosticBag diagnostics, out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders,
bool requireTwoOrMoreElements)
{
var receiverSyntax = (CSharpSyntaxNode)receiver.Syntax;
if (numCheckedVariables < 2)
if (requireTwoOrMoreElements && numCheckedVariables < 2)
{
Error(diagnostics, ErrorCode.ERR_DeconstructTooFewElements, receiverSyntax);
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);
......@@ -690,9 +691,12 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres
private BoundBadExpression MissingDeconstruct(BoundExpression receiver, SyntaxNode rightSyntax, int numParameters, DiagnosticBag diagnostics,
out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, BoundExpression childNode)
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type, numParameters);
outPlaceholders = default;
if (!receiver.Type.IsErrorType())
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type, numParameters);
}
outPlaceholders = default;
return BadExpression(rightSyntax, childNode);
}
......
......@@ -2682,7 +2682,7 @@ private BoundExpression BindIsOperator(BinaryExpressionSyntax node, DiagnosticBa
}
var boundConstantPattern = BindConstantPattern(
node.Right, operand.Type, node.Right, node.Right.HasErrors, isPatternDiagnostics, out wasExpression, wasSwitchCase: false);
node.Right, operand.Type, node.Right, node.Right.HasErrors, isPatternDiagnostics, out wasExpression);
if (wasExpression)
{
isTypeDiagnostics.Free();
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal class DecisionDagBuilder
{
private readonly Conversions Conversions;
private readonly TypeSymbol BooleanType;
private readonly TypeSymbol ObjectType;
internal DecisionDagBuilder(CSharpCompilation compilation)
{
this.Conversions = compilation.Conversions;
this.BooleanType = compilation.GetSpecialType(SpecialType.System_Boolean);
this.ObjectType = compilation.GetSpecialType(SpecialType.System_Object);
}
public BoundDecisionDag CreateDecisionDag(BoundPatternSwitchStatement statement)
{
ImmutableArray<PartialCaseDecision> cases = MakeCases(statement);
BoundDecisionDag dag = MakeDecisionDag(cases);
return dag;
}
public BoundDagTemp LowerPattern(
BoundExpression input,
BoundPattern pattern,
out ImmutableArray<BoundDagDecision> decisions,
out ImmutableArray<(BoundExpression, BoundDagTemp)> bindings)
{
var rootIdentifier = new BoundDagTemp(input.Syntax, input.Type, null, 0);
MakeAndSimplifyDecisionsAndBindings(rootIdentifier, pattern, out decisions, out bindings);
return rootIdentifier;
}
private ImmutableArray<PartialCaseDecision> MakeCases(BoundPatternSwitchStatement statement)
{
var rootIdentifier = new BoundDagTemp(statement.Expression.Syntax, statement.Expression.Type, null, 0);
int i = 0;
var builder = ArrayBuilder<PartialCaseDecision>.GetInstance();
foreach (var section in statement.SwitchSections)
{
foreach (var label in section.SwitchLabels)
{
builder.Add(MakePartialCaseDecision(++i, rootIdentifier, label));
}
}
return builder.ToImmutableAndFree();
}
private PartialCaseDecision MakePartialCaseDecision(int index, BoundDagTemp input, BoundPatternSwitchLabel label)
{
MakeAndSimplifyDecisionsAndBindings(input, label.Pattern, out var decisions, out var bindings);
return new PartialCaseDecision(index, decisions, bindings, label.Guard, label.Label);
}
private void MakeAndSimplifyDecisionsAndBindings(
BoundDagTemp input,
BoundPattern pattern,
out ImmutableArray<BoundDagDecision> decisions,
out ImmutableArray<(BoundExpression, BoundDagTemp)> bindings)
{
var decisionsBuilder = ArrayBuilder<BoundDagDecision>.GetInstance();
var bindingsBuilder = ArrayBuilder<(BoundExpression, BoundDagTemp)>.GetInstance();
// use site diagnostics will have been produced during binding of the patterns, so can be discarded here
HashSet<DiagnosticInfo> discardedUseSiteDiagnostics = null;
MakeDecisionsAndBindings(input, pattern, decisionsBuilder, bindingsBuilder, ref discardedUseSiteDiagnostics);
// Now simplify the decisions and bindings. We don't need anything in decisions that does not
// contribute to the result. This will, for example, permit us to match `(2, 3) is (2, _)` without
// fetching `Item2` from the input.
var usedValues = PooledHashSet<BoundDagEvaluation>.GetInstance();
foreach (var (_, temp) in bindingsBuilder)
{
if (temp.Source != (object)null)
{
usedValues.Add(temp.Source);
}
}
for (int i = decisionsBuilder.Count - 1; i >= 0; i--)
{
switch (decisionsBuilder[i])
{
case BoundDagEvaluation e:
{
if (usedValues.Contains(e))
{
if (e.Input.Source != (object)null)
{
usedValues.Add(e.Input.Source);
}
}
else
{
decisionsBuilder.RemoveAt(i);
}
}
break;
case BoundDagDecision d:
{
usedValues.Add(d.Input.Source);
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(decisionsBuilder[i]);
}
}
// We also do not need to compute any result more than once. This will permit us to fetch
// a property once even if it is used more than once, e.g. `o is { X: P1, X: P2 }`
usedValues.Clear();
usedValues.Add(input.Source);
for (int i = 0; i < decisionsBuilder.Count; i++)
{
switch (decisionsBuilder[i])
{
case BoundDagEvaluation e:
if (usedValues.Contains(e))
{
decisionsBuilder.RemoveAt(i);
i--;
}
else
{
usedValues.Add(e);
}
break;
}
}
usedValues.Free();
decisions = decisionsBuilder.ToImmutableAndFree();
bindings = bindingsBuilder.ToImmutableAndFree();
}
private void MakeDecisionsAndBindings(
BoundDagTemp input,
BoundPattern pattern,
ArrayBuilder<BoundDagDecision> decisions,
ArrayBuilder<(BoundExpression, BoundDagTemp)> bindings,
ref HashSet<DiagnosticInfo> discardedUseSiteDiagnostics)
{
switch (pattern)
{
case BoundDeclarationPattern declaration:
MakeDecisionsAndBindings(input, declaration, decisions, bindings, ref discardedUseSiteDiagnostics);
break;
case BoundConstantPattern constant:
MakeDecisionsAndBindings(input, constant, decisions, bindings, ref discardedUseSiteDiagnostics);
break;
case BoundDiscardPattern wildcard:
// Nothing to do. It always matches.
break;
case BoundRecursivePattern recursive:
MakeDecisionsAndBindings(input, recursive, decisions, bindings, ref discardedUseSiteDiagnostics);
break;
default:
throw new NotImplementedException(pattern.Kind.ToString());
}
}
private void MakeDecisionsAndBindings(
BoundDagTemp input,
BoundDeclarationPattern declaration,
ArrayBuilder<BoundDagDecision> decisions,
ArrayBuilder<(BoundExpression, BoundDagTemp)> bindings,
ref HashSet<DiagnosticInfo> discardedUseSiteDiagnostics)
{
var type = declaration.DeclaredType.Type;
var syntax = declaration.Syntax;
// Add a null and type test if needed.
if (!declaration.IsVar)
{
NullCheck(input, declaration.Syntax, decisions);
input = ConvertToType(input, declaration.Syntax, type, decisions, ref discardedUseSiteDiagnostics);
}
var left = declaration.VariableAccess;
if (left != null)
{
Debug.Assert(left.Type == input.Type);
bindings.Add((left, input));
}
else
{
Debug.Assert(declaration.Variable == null);
}
}
private void NullCheck(
BoundDagTemp input,
SyntaxNode syntax,
ArrayBuilder<BoundDagDecision> decisions)
{
if (input.Type.CanContainNull())
{
// Add a null test
decisions.Add(new BoundNonNullDecision(syntax, input));
}
}
private BoundDagTemp ConvertToType(
BoundDagTemp input,
SyntaxNode syntax,
TypeSymbol type,
ArrayBuilder<BoundDagDecision> decisions,
ref HashSet<DiagnosticInfo> discardedUseSiteDiagnostics)
{
if (input.Type != type)
{
var inputType = input.Type.StrippedType(); // since a null check has already been done
var conversion = Conversions.ClassifyBuiltInConversion(inputType, type, ref discardedUseSiteDiagnostics);
if (input.Type.IsDynamic() ? type.SpecialType == SpecialType.System_Object : conversion.IsImplicit)
{
// type test not needed, only the type cast
}
else
{
// both type test and cast needed
decisions.Add(new BoundTypeDecision(syntax, type, input));
}
var evaluation = new BoundDagTypeEvaluation(syntax, type, input);
input = new BoundDagTemp(syntax, type, evaluation, 0);
decisions.Add(evaluation);
}
return input;
}
private void MakeDecisionsAndBindings(
BoundDagTemp input,
BoundConstantPattern constant,
ArrayBuilder<BoundDagDecision> decisions,
ArrayBuilder<(BoundExpression, BoundDagTemp)> bindings,
ref HashSet<DiagnosticInfo> discardedUseSiteDiagnostics)
{
input = ConvertToType(input, constant.Syntax, constant.Value.Type, decisions, ref discardedUseSiteDiagnostics);
decisions.Add(new BoundValueDecision(constant.Syntax, constant.ConstantValue, input));
}
private void MakeDecisionsAndBindings(
BoundDagTemp input,
BoundRecursivePattern recursive,
ArrayBuilder<BoundDagDecision> decisions,
ArrayBuilder<(BoundExpression, BoundDagTemp)> bindings,
ref HashSet<DiagnosticInfo> discardedUseSiteDiagnostics)
{
Debug.Assert(input.Type == recursive.InputType);
NullCheck(input, recursive.Syntax, decisions);
if (recursive.DeclaredType != null && recursive.DeclaredType.Type != input.Type)
{
input = ConvertToType(input, recursive.Syntax, recursive.DeclaredType.Type, decisions, ref discardedUseSiteDiagnostics);
}
if (!recursive.Deconstruction.IsDefault)
{
// we have a "deconstruction" form, which is either an invocation of a Deconstruct method, or a disassembly of a tuple
if (recursive.DeconstructMethodOpt != null)
{
var method = recursive.DeconstructMethodOpt;
var evaluation = new BoundDagDeconstructEvaluation(recursive.Syntax, method, input);
decisions.Add(evaluation);
int extensionExtra = method.IsStatic ? 1 : 0;
int count = Math.Min(method.ParameterCount - extensionExtra, recursive.Deconstruction.Length);
for (int i = 0; i < count; i++)
{
var pattern = recursive.Deconstruction[i];
var syntax = pattern.Syntax;
var output = new BoundDagTemp(syntax, method.Parameters[i + extensionExtra].Type, evaluation, i);
MakeDecisionsAndBindings(output, pattern, decisions, bindings, ref discardedUseSiteDiagnostics);
}
}
else if (input.Type.IsTupleType)
{
var elements = input.Type.TupleElements;
var elementTypes = input.Type.TupleElementTypes;
int count = Math.Min(elementTypes.Length, recursive.Deconstruction.Length);
for (int i = 0; i < count; i++)
{
var pattern = recursive.Deconstruction[i];
var syntax = pattern.Syntax;
var field = elements[i];
var evaluation = new BoundDagFieldEvaluation(syntax, field, input); // fetch the ItemN field
decisions.Add(evaluation);
var output = new BoundDagTemp(syntax, field.Type, evaluation, 0);
MakeDecisionsAndBindings(output, pattern, decisions, bindings, ref discardedUseSiteDiagnostics);
}
}
else
{
// TODO(patterns2): This should not occur except in error cases. Perhaps this will be used to handle the ITuple case.
throw new NotImplementedException();
}
}
if (recursive.PropertiesOpt != null)
{
// we have a "property" form
for (int i = 0; i < recursive.PropertiesOpt.Length; i++)
{
var prop = recursive.PropertiesOpt[i];
var symbol = prop.symbol;
BoundDagEvaluation evaluation;
switch (symbol)
{
case PropertySymbol property:
evaluation = new BoundDagPropertyEvaluation(prop.pattern.Syntax, property, input);
break;
case FieldSymbol field:
evaluation = new BoundDagFieldEvaluation(prop.pattern.Syntax, field, input);
break;
default:
throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
}
decisions.Add(evaluation);
var output = new BoundDagTemp(prop.pattern.Syntax, prop.symbol.GetTypeOrReturnType(), evaluation, 0);
MakeDecisionsAndBindings(output, prop.pattern, decisions, bindings, ref discardedUseSiteDiagnostics);
}
}
if (recursive.VariableAccess != null)
{
// we have a "variable" declaration
bindings.Add((recursive.VariableAccess, input));
}
}
private static BoundDecisionDag MakeDecisionDag(ImmutableArray<PartialCaseDecision> cases)
{
throw new NotImplementedException();
}
}
internal class PartialCaseDecision
{
public readonly int Index;
public readonly ImmutableArray<BoundDagDecision> Decisions;
public readonly ImmutableArray<(BoundExpression, BoundDagTemp)> Bindings;
public readonly BoundExpression WhereClause;
public readonly LabelSymbol CaseLabel;
public PartialCaseDecision(
int Index,
ImmutableArray<BoundDagDecision> Decisions,
ImmutableArray<(BoundExpression, BoundDagTemp)> Bindings,
BoundExpression WhereClause,
LabelSymbol CaseLabel)
{
this.Index = Index;
this.Decisions = Decisions;
this.Bindings = Bindings;
this.WhereClause = WhereClause;
this.CaseLabel = CaseLabel;
}
}
partial class BoundDagEvaluation
{
public override bool Equals(object obj) => obj is BoundDagEvaluation other && this.Equals(other);
public bool Equals(BoundDagEvaluation other)
{
return other != (object)null && this.Kind == other.Kind && this.Input.Equals(other.Input) && this.Symbol == other.Symbol;
}
private Symbol Symbol
{
get
{
switch (this)
{
case BoundDagFieldEvaluation e: return e.Field;
case BoundDagPropertyEvaluation e: return e.Property;
case BoundDagTypeEvaluation e: return e.Type;
case BoundDagDeconstructEvaluation e: return e.DeconstructMethod;
default: throw ExceptionUtilities.UnexpectedValue(this.Kind);
}
}
}
public override int GetHashCode()
{
return this.Input.GetHashCode() ^ (this.Symbol?.GetHashCode() ?? 0);
}
public static bool operator ==(BoundDagEvaluation left, BoundDagEvaluation right)
{
return (left == (object)null) ? right == (object)null : left.Equals(right);
}
public static bool operator !=(BoundDagEvaluation left, BoundDagEvaluation right)
{
return !left.Equals(right);
}
}
partial class BoundDagTemp
{
public override bool Equals(object obj) => obj is BoundDagTemp other && this.Equals(other);
public bool Equals(BoundDagTemp other)
{
return other != (object)null && this.Type == other.Type && object.Equals(this.Source, other.Source) && this.Index == other.Index;
}
public override int GetHashCode()
{
return this.Type.GetHashCode() ^ (this.Source?.GetHashCode() ?? 0) ^ this.Index;
}
public static bool operator ==(BoundDagTemp left, BoundDagTemp right)
{
return left.Equals(right);
}
public static bool operator !=(BoundDagTemp left, BoundDagTemp right)
{
return !left.Equals(right);
}
}
}
......@@ -200,7 +200,31 @@ public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
base.VisitDeclarationPattern(node);
}
public override void VisitDeconstructionPattern(DeconstructionPatternSyntax node)
{
var variable = MakePatternVariable(node, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
base.VisitDeconstructionPattern(node);
}
public override void VisitPropertyPattern(PropertyPatternSyntax node)
{
var variable = MakePatternVariable(node, _nodeToBind);
if ((object)variable != null)
{
_variablesBuilder.Add(variable);
}
base.VisitPropertyPattern(node);
}
protected abstract TFieldOrLocalSymbol MakePatternVariable(DeclarationPatternSyntax node, SyntaxNode nodeToBind);
protected abstract TFieldOrLocalSymbol MakePatternVariable(DeconstructionPatternSyntax node, SyntaxNode nodeToBind);
protected abstract TFieldOrLocalSymbol MakePatternVariable(PropertyPatternSyntax node, SyntaxNode nodeToBind);
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { }
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { }
......@@ -434,10 +458,25 @@ internal class ExpressionVariableFinder : ExpressionVariableFinder<LocalSymbol>
protected override LocalSymbol MakePatternVariable(DeclarationPatternSyntax node, SyntaxNode nodeToBind)
{
var designation = node.Designation as SingleVariableDesignationSyntax;
return MakePatternVariable(node.Type, node.Designation, nodeToBind);
}
protected override LocalSymbol MakePatternVariable(DeconstructionPatternSyntax node, SyntaxNode nodeToBind)
{
return MakePatternVariable(node.Type, node.Designation, nodeToBind);
}
protected override LocalSymbol MakePatternVariable(PropertyPatternSyntax node, SyntaxNode nodeToBind)
{
return MakePatternVariable(node.Type, node.Designation, nodeToBind);
}
private LocalSymbol MakePatternVariable(TypeSyntax type, VariableDesignationSyntax variableDesignation, SyntaxNode nodeToBind)
{
var designation = variableDesignation as SingleVariableDesignationSyntax;
if (designation == null)
{
Debug.Assert(node.Designation.Kind() == SyntaxKind.DiscardDesignation);
Debug.Assert(variableDesignation == null || variableDesignation.Kind() == SyntaxKind.DiscardDesignation);
return null;
}
......@@ -453,7 +492,7 @@ protected override LocalSymbol MakePatternVariable(DeclarationPatternSyntax node
_scopeBinder.ContainingMemberOrLambda,
scopeBinder: _scopeBinder,
nodeBinder: _enclosingBinder,
typeSyntax: node.Type,
typeSyntax: type,
identifierToken: designation.Identifier,
kind: LocalDeclarationKind.PatternVariable,
nodeToBind: nodeToBind,
......@@ -561,6 +600,34 @@ protected override Symbol MakePatternVariable(DeclarationPatternSyntax node, Syn
_containingFieldOpt, nodeToBind);
}
protected override Symbol MakePatternVariable(DeconstructionPatternSyntax node, SyntaxNode nodeToBind)
{
var designation = node.Designation as SingleVariableDesignationSyntax;
if (designation == null)
{
return null;
}
return GlobalExpressionVariable.Create(
_containingType, _modifiers, node.Type,
designation.Identifier.ValueText, designation, designation.GetLocation(),
_containingFieldOpt, nodeToBind);
}
protected override Symbol MakePatternVariable(PropertyPatternSyntax node, SyntaxNode nodeToBind)
{
var designation = node.Designation as SingleVariableDesignationSyntax;
if (designation == null)
{
return null;
}
return GlobalExpressionVariable.Create(
_containingType, _modifiers, node.Type,
designation.Identifier.ValueText, designation, designation.GetLocation(),
_containingFieldOpt, nodeToBind);
}
protected override Symbol MakeDeclarationExpressionVariable(DeclarationExpressionSyntax node, SingleVariableDesignationSyntax designation, BaseArgumentListSyntax argumentListSyntaxOpt, SyntaxNode nodeToBind)
{
return GlobalExpressionVariable.Create(
......
......@@ -54,9 +54,10 @@ internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatement
BindPatternSwitchSections(originalBinder, out defaultLabel, out isComplete, out var someCaseMatches, diagnostics);
var locals = GetDeclaredLocalsForScope(node);
var functions = GetDeclaredLocalFunctionsForScope(node);
BoundDecisionDag decisionDag = null; // not relevant to the C# 7 pattern binder
return new BoundPatternSwitchStatement(
node, boundSwitchExpression, someCaseMatches,
locals, functions, switchSections, defaultLabel, this.BreakLabel, this, isComplete);
locals, functions, switchSections, defaultLabel, this.BreakLabel, decisionDag, isComplete);
}
/// <summary>
......@@ -157,7 +158,7 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel
{
patternMatches = null;
}
else if (boundLabel.Pattern.Kind == BoundKind.WildcardPattern)
else if (boundLabel.Pattern.Kind == BoundKind.DiscardPattern)
{
// wildcard pattern matches anything
patternMatches = true;
......@@ -211,7 +212,7 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel
var caseLabelSyntax = (CaseSwitchLabelSyntax)node;
bool wasExpression;
var pattern = sectionBinder.BindConstantPattern(
node, SwitchGoverningType, caseLabelSyntax.Value, node.HasErrors, diagnostics, out wasExpression, wasSwitchCase: true);
node, SwitchGoverningType, caseLabelSyntax.Value, node.HasErrors, diagnostics, out wasExpression);
bool hasErrors = pattern.HasErrors;
var constantValue = pattern.ConstantValue;
if (!hasErrors &&
......@@ -237,7 +238,7 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel
case SyntaxKind.DefaultSwitchLabel:
{
var defaultLabelSyntax = (DefaultSwitchLabelSyntax)node;
var pattern = new BoundWildcardPattern(node);
var pattern = new BoundDiscardPattern(node);
bool hasErrors = pattern.HasErrors;
if (defaultLabel != null)
{
......@@ -258,7 +259,7 @@ internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabel
{
var matchLabelSyntax = (CasePatternSwitchLabelSyntax)node;
var pattern = sectionBinder.BindPattern(
matchLabelSyntax.Pattern, SwitchGoverningType, node.HasErrors, diagnostics, wasSwitchCase: true);
matchLabelSyntax.Pattern, SwitchGoverningType, node.HasErrors, diagnostics);
return new BoundPatternSwitchLabel(node, label, pattern,
matchLabelSyntax.WhenClause != null ? sectionBinder.BindBooleanExpression(matchLabelSyntax.WhenClause.Condition, diagnostics) : null,
true, node.HasErrors);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
// We use a subclass of SwitchBinder for the pattern-matching switch statement until we have completed
// a totally compatible implementation of switch that also accepts pattern-matching constructs.
internal partial class PatternSwitchBinder2 : SwitchBinder
{
internal PatternSwitchBinder2(Binder next, SwitchStatementSyntax switchSyntax) : base(next, switchSyntax)
{
}
/// <summary>
/// When pattern-matching is enabled, we use a completely different binder and binding
/// strategy for switch statements. Once we have confirmed that it is totally upward
/// compatible with the existing syntax and semantics, we will merge them completely.
/// However, until we have edit-and-continue working, we continue using the old binder
/// when we can.
/// </summary>
private bool UseV8SwitchBinder
{
get
{
var parseOptions = SwitchSyntax?.SyntaxTree?.Options as CSharpParseOptions;
return
parseOptions?.Features.ContainsKey("testV8SwitchBinder") == true ||
HasPatternSwitchSyntax(SwitchSyntax) ||
!SwitchGoverningType.IsValidV6SwitchGoverningType();
}
}
internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics)
{
// If it is a valid C# 6 switch statement, we use the old binder to bind it.
if (!UseV8SwitchBinder) return base.BindSwitchExpressionAndSections(node, originalBinder, diagnostics);
Debug.Assert(SwitchSyntax.Equals(node));
// Bind switch expression and set the switch governing type.
var boundSwitchExpression = SwitchGoverningExpression;
diagnostics.AddRange(SwitchGoverningDiagnostics);
BoundPatternSwitchLabel defaultLabel;
bool isComplete;
ImmutableArray<BoundPatternSwitchSection> switchSections =
BindPatternSwitchSections(originalBinder, out defaultLabel, out isComplete, out var someCaseMatches, diagnostics);
var locals = GetDeclaredLocalsForScope(node);
var functions = GetDeclaredLocalFunctionsForScope(node);
BoundDecisionDag decisionDag = null; // we'll compute it later
return new BoundPatternSwitchStatement(
syntax: node,
expression: boundSwitchExpression,
someLabelAlwaysMatches: someCaseMatches,
innerLocals: locals,
innerLocalFunctions: functions,
switchSections: switchSections,
defaultLabel: defaultLabel,
breakLabel: this.BreakLabel,
decisionDag: decisionDag,
isComplete: isComplete);
}
/// <summary>
/// Bind a pattern switch label in order to force inference of the type of pattern variables.
/// </summary>
internal override void BindPatternSwitchLabelForInference(CasePatternSwitchLabelSyntax node, DiagnosticBag diagnostics)
{
// node should be a label of this switch statement.
Debug.Assert(this.SwitchSyntax == node.Parent.Parent);
// This simulates enough of the normal binding path of a switch statement to cause
// the label's pattern variables to have their types inferred, if necessary.
// It also binds the when clause, and therefore any pattern and out variables there.
BoundPatternSwitchLabel defaultLabel = null;
BindPatternSwitchSectionLabel(
sectionBinder: GetBinder(node.Parent),
node: node,
label: LabelsByNode[node],
defaultLabel: ref defaultLabel,
diagnostics: diagnostics);
}
/// <summary>
/// Bind the pattern switch labels, reporting in the process which cases are subsumed. The strategy
/// implemented with the help of <see cref="SubsumptionDiagnosticBuilder"/>, is to start with an empty
/// decision tree, and for each case we visit the decision tree to see if the case is subsumed. If it
/// is, we report an error. If it is not subsumed and there is no guard expression, we then add it to
/// the decision tree.
/// </summary>
private ImmutableArray<BoundPatternSwitchSection> BindPatternSwitchSections(
Binder originalBinder,
out BoundPatternSwitchLabel defaultLabel,
out bool isComplete,
out bool someCaseMatches,
DiagnosticBag diagnostics)
{
defaultLabel = null;
// someCaseMatches will be set to true if some single case label would handle all inputs
someCaseMatches = false;
// Bind match sections
var boundPatternSwitchSectionsBuilder = ArrayBuilder<BoundPatternSwitchSection>.GetInstance();
SubsumptionDiagnosticBuilder subsumption = new SubsumptionDiagnosticBuilder(ContainingMemberOrLambda, SwitchSyntax, this.Conversions, SwitchGoverningType);
foreach (var sectionSyntax in SwitchSyntax.Sections)
{
var section = BindPatternSwitchSection(sectionSyntax, originalBinder, ref defaultLabel, ref someCaseMatches, subsumption, diagnostics);
boundPatternSwitchSectionsBuilder.Add(section);
}
isComplete = defaultLabel != null || subsumption.IsComplete || someCaseMatches;
return boundPatternSwitchSectionsBuilder.ToImmutableAndFree();
}
/// <summary>
/// Bind the pattern switch section, producing subsumption diagnostics.
/// </summary>
/// <param name="node"/>
/// <param name="originalBinder"/>
/// <param name="defaultLabel">If a default label is found in this section, assigned that label</param>
/// <param name="someCaseMatches">If a case is found that would always match the input, set to true</param>
/// <param name="subsumption">A helper class that uses a decision tree to produce subsumption diagnostics.</param>
/// <param name="diagnostics"></param>
/// <returns></returns>
private BoundPatternSwitchSection BindPatternSwitchSection(
SwitchSectionSyntax node,
Binder originalBinder,
ref BoundPatternSwitchLabel defaultLabel,
ref bool someCaseMatches,
SubsumptionDiagnosticBuilder subsumption,
DiagnosticBag diagnostics)
{
// Bind match section labels
var boundLabelsBuilder = ArrayBuilder<BoundPatternSwitchLabel>.GetInstance();
var sectionBinder = originalBinder.GetBinder(node); // this binder can bind pattern variables from the section.
Debug.Assert(sectionBinder != null);
var labelsByNode = LabelsByNode;
foreach (var labelSyntax in node.Labels)
{
LabelSymbol label = labelsByNode[labelSyntax];
BoundPatternSwitchLabel boundLabel = BindPatternSwitchSectionLabel(sectionBinder, labelSyntax, label, ref defaultLabel, diagnostics);
boundLabelsBuilder.Add(boundLabel);
}
// Bind switch section statements
var boundStatementsBuilder = ArrayBuilder<BoundStatement>.GetInstance();
foreach (var statement in node.Statements)
{
boundStatementsBuilder.Add(sectionBinder.BindStatement(statement, diagnostics));
}
return new BoundPatternSwitchSection(node, sectionBinder.GetDeclaredLocalsForScope(node), boundLabelsBuilder.ToImmutableAndFree(), boundStatementsBuilder.ToImmutableAndFree());
}
private BoundPatternSwitchLabel BindPatternSwitchSectionLabel(
Binder sectionBinder,
SwitchLabelSyntax node,
LabelSymbol label,
ref BoundPatternSwitchLabel defaultLabel,
DiagnosticBag diagnostics)
{
// Until we've determined whether or not the switch label is reachable, we assume it
// is. The caller updates isReachable after determining if the label is subsumed.
const bool isReachable = true;
switch (node.Kind())
{
case SyntaxKind.CaseSwitchLabel:
{
var caseLabelSyntax = (CaseSwitchLabelSyntax)node;
bool wasExpression;
var pattern = sectionBinder.BindConstantPattern(
node, SwitchGoverningType, caseLabelSyntax.Value, node.HasErrors, diagnostics, out wasExpression);
bool hasErrors = pattern.HasErrors;
var constantValue = pattern.ConstantValue;
if (!hasErrors &&
(object)constantValue != null &&
pattern.Value.Type == SwitchGoverningType &&
this.FindMatchingSwitchCaseLabel(constantValue, caseLabelSyntax) != label)
{
diagnostics.Add(ErrorCode.ERR_DuplicateCaseLabel, node.Location, pattern.ConstantValue.GetValueToDisplay() ?? label.Name);
hasErrors = true;
}
if (caseLabelSyntax.Value.Kind() == SyntaxKind.DefaultLiteralExpression)
{
diagnostics.Add(ErrorCode.WRN_DefaultInSwitch, caseLabelSyntax.Value.Location);
}
return new BoundPatternSwitchLabel(node, label, pattern, null, isReachable, hasErrors);
}
case SyntaxKind.DefaultSwitchLabel:
{
var defaultLabelSyntax = (DefaultSwitchLabelSyntax)node;
var pattern = new BoundDiscardPattern(node);
bool hasErrors = pattern.HasErrors;
if (defaultLabel != null)
{
diagnostics.Add(ErrorCode.ERR_DuplicateCaseLabel, node.Location, label.Name);
hasErrors = true;
}
// Note that this is semantically last! The caller will place it in the decision tree
// in the final position.
defaultLabel = new BoundPatternSwitchLabel(node, label, pattern, null, isReachable, hasErrors);
return defaultLabel;
}
case SyntaxKind.CasePatternSwitchLabel:
{
var matchLabelSyntax = (CasePatternSwitchLabelSyntax)node;
var pattern = sectionBinder.BindPattern(
matchLabelSyntax.Pattern, SwitchGoverningType, node.HasErrors, diagnostics);
return new BoundPatternSwitchLabel(node, label, pattern,
matchLabelSyntax.WhenClause != null ? sectionBinder.BindBooleanExpression(matchLabelSyntax.WhenClause.Condition, diagnostics) : null,
isReachable, node.HasErrors);
}
default:
throw ExceptionUtilities.UnexpectedValue(node);
}
}
}
}
......@@ -27,7 +27,7 @@ internal sealed class SubsumptionDiagnosticBuilder : DecisionTreeBuilder
{
// For the purpose of computing subsumption, we ignore the input expression's constant
// value. Therefore we create a fake expression here that doesn't contain the value.
var placeholderExpression = new BoundDup(syntax, RefKind.None, switchGoverningType);
var placeholderExpression = new BoundImplicitReceiver(syntax, switchGoverningType);
_subsumptionTree = CreateEmptyDecisionTree(placeholderExpression);
}
......@@ -271,7 +271,7 @@ private ErrorCode CheckSubsumed(BoundPattern pattern, DecisionTree decisionTree,
throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind);
}
}
case BoundKind.WildcardPattern:
case BoundKind.DiscardPattern:
// because we always handle `default:` last, and that is the only way to get a wildcard pattern,
// we should never need to see if it subsumes something else.
throw ExceptionUtilities.UnexpectedValue(decisionTree.Kind);
......
......@@ -40,10 +40,34 @@ internal static SwitchBinder Create(Binder next, SwitchStatementSyntax switchSyn
(parseOptions?.IsFeatureEnabled(MessageID.IDS_FeaturePatternMatching) != false ||
parseOptions?.Features.ContainsKey("testV7SwitchBinder") != false ||
switchSyntax.HasErrors && HasPatternSwitchSyntax(switchSyntax))
? new PatternSwitchBinder(next, switchSyntax)
? ( HasRecursivePatternSwitchSyntax(switchSyntax)
? new PatternSwitchBinder2(next, switchSyntax)
: (SwitchBinder)new PatternSwitchBinder(next, switchSyntax))
: new SwitchBinder(next, switchSyntax);
}
private static bool HasRecursivePatternSwitchSyntax(SwitchStatementSyntax switchSyntax)
{
foreach (var section in switchSyntax.Sections)
{
foreach (var label in section.Labels)
{
if (label is CasePatternSwitchLabelSyntax s)
{
switch (s.Pattern.Kind())
{
case SyntaxKind.DeconstructionPattern:
case SyntaxKind.PropertyPattern:
case SyntaxKind.DiscardPattern:
return true;
}
}
}
}
return false;
}
internal static bool HasPatternSwitchSyntax(SwitchStatementSyntax switchSyntax)
{
foreach (var section in switchSyntax.Sections)
......@@ -242,7 +266,7 @@ private void BuildSwitchLabels(SyntaxList<SwitchLabelSyntax> labelsSyntax, Binde
// bind the pattern, to cause its pattern variables to be inferred if necessary
var matchLabel = (CasePatternSwitchLabelSyntax)labelSyntax;
var pattern = sectionBinder.BindPattern(
matchLabel.Pattern, SwitchGoverningType, labelSyntax.HasErrors, tempDiagnosticBag, wasSwitchCase: true);
matchLabel.Pattern, SwitchGoverningType, labelSyntax.HasErrors, tempDiagnosticBag);
break;
default:
......
......@@ -799,16 +799,77 @@
<Field Name="SwitchSections" Type="ImmutableArray&lt;BoundPatternSwitchSection&gt;"/>
<Field Name="DefaultLabel" Type="BoundPatternSwitchLabel" Null="allow"/>
<Field Name="BreakLabel" Type="GeneratedLabelSymbol"/>
<!-- We store the pattern switch binder so we can perform some binding-like
operations during control flow analysis. -->
<Field Name="Binder" Type="PatternSwitchBinder"/>
<!-- A directed acyclic decision graph for the switch statement. It can be traversed
to determine if all labels are reachable, and if every input is handled. -->
<Field Name="DecisionDag" Type="BoundDecisionDag" Null="allow"/>
<!-- A switch is considered complete if every value of the switch expression's
type is handled by some switch label that is either not guarded by a when
clause, or for which the when clause expression has the constant value true. -->
<Field Name="IsComplete" Type="bool"/>
</Node>
<AbstractNode Name="BoundDecisionDag" Base="BoundNode">
<Field Name="Label" Type="LabelSymbol"/>
</AbstractNode>
<!-- This node is used to indicate a point in the decision dag where an evaluation is performed, such as invoking Deconstruct. -->
<Node Name="BoundEvaluationPoint" Base="BoundDecisionDag">
<Field Name="Evaluation" Type="BoundDagEvaluation" Null="disallow"/>
<Field Name="Next" Type="BoundDecisionDag" Null="disallow"/>
</Node>
<!-- This node is used to indicate a point in the decision dag where a test may change the path of the decision dag. -->
<Node Name="BoundDecisionPoint" Base="BoundDecisionDag">
<Field Name="Decision" Type="BoundDagDecision" Null="disallow"/>
<Field Name="WhenTrue" Type="BoundDecisionDag" Null="disallow"/>
<Field Name="WhenFalse" Type="BoundDecisionDag" Null="disallow"/>
</Node>
<Node Name="BoundWhereClause" Base="BoundDecisionDag">
<Field Name="Bindings" Type="ImmutableArray&lt;(BoundExpression,BoundDagTemp)&gt;" Null="disallow"/>
<Field Name="Where" Type="BoundExpression" Null="allow"/>
<Field Name="WhenTrue" Type="BoundDecisionDag" Null="disallow"/>
<Field Name="WhenFalse" Type="BoundDecisionDag" Null="allow"/>
</Node>
<!-- This node is used to indicate a point in the decision dag where the decision has been completed. -->
<Node Name="BoundDecision" Base="BoundDecisionDag">
</Node>
<AbstractNode Name="BoundDagDecision" Base="BoundNode">
Input is the input to the decision point.
<Field Name="Input" Type="BoundDagTemp"/>
</AbstractNode>
<Node Name="BoundDagTemp" Base="BoundNode">
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
<Field Name="Source" Type="BoundDagEvaluation" Null="allow"/>
<Field Name="Index" Type="int"/>
</Node>
<Node Name="BoundNonNullDecision" Base="BoundDagDecision">
<!--Check that the input is not null.-->
</Node>
<Node Name="BoundTypeDecision" Base="BoundDagDecision">
<!--Check that the input is of the given type. Null check is separate.-->
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
</Node>
<Node Name="BoundValueDecision" Base="BoundDagDecision">
<!--Check that the input is the same as the given constant value.-->
<Field Name="Value" Type="ConstantValue" Null="disallow"/>
</Node>
<!-- As a decision, an evaluation is considered to always succeed. -->
<AbstractNode Name="BoundDagEvaluation" Base="BoundDagDecision">
</AbstractNode>
<Node Name="BoundDagDeconstructEvaluation" Base="BoundDagEvaluation">
<Field Name="DeconstructMethod" Type="MethodSymbol" Null="disallow"/>
</Node>
<Node Name="BoundDagTypeEvaluation" Base="BoundDagEvaluation">
<!--Cast to the given type, assumed to be used after a successful type check.-->
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
</Node>
<Node Name="BoundDagFieldEvaluation" Base="BoundDagEvaluation">
<Field Name="Field" Type="FieldSymbol" Null="disallow"/>
</Node>
<Node Name="BoundDagPropertyEvaluation" Base="BoundDagEvaluation">
<Field Name="Property" Type="PropertySymbol" Null="disallow"/>
</Node>
<Node Name="BoundPatternSwitchSection" Base="BoundStatementList">
<Field Name="Locals" Type="ImmutableArray&lt;LocalSymbol&gt;"/>
<Field Name="SwitchLabels" Type="ImmutableArray&lt;BoundPatternSwitchLabel&gt;"/>
......@@ -1588,6 +1649,14 @@
<!-- CONSIDER: we might benefit from recording the input (matched value) type in the bound pattern. -->
</AbstractNode>
<Node Name="BoundConstantPattern" Base="BoundPattern">
<Field Name="Value" Type="BoundExpression"/>
<Field Name="ConstantValue" Type="ConstantValue" Null="allow"/>
</Node>
<Node Name="BoundDiscardPattern" Base="BoundPattern">
</Node>
<Node Name="BoundDeclarationPattern" Base="BoundPattern">
<!-- Variable is a local symbol, or in the case of top-level code in scripts and interactive,
a field that is a member of the script class. Variable is null if `_` is used. -->
......@@ -1599,17 +1668,28 @@
free. The necessity of this member is a consequence of a design issue documented in
https://github.com/dotnet/roslyn/issues/13960 . When that is fixed this field can be
removed. -->
<Field Name="VariableAccess" Type="BoundExpression" Null="disallow"/>
<Field Name="VariableAccess" Type="BoundExpression" Null="allow"/>
<Field Name="DeclaredType" Type="BoundTypeExpression" Null="allow"/>
<Field Name="IsVar" Type="bool"/>
</Node>
<Node Name="BoundConstantPattern" Base="BoundPattern">
<Field Name="Value" Type="BoundExpression"/>
<Field Name="ConstantValue" Type="ConstantValue" Null="allow"/>
</Node>
<Node Name="BoundWildcardPattern" Base="BoundPattern">
<Node Name="BoundRecursivePattern" Base="BoundPattern">
<Field Name="DeclaredType" Type="BoundTypeExpression" Null="allow"/>
<Field Name="InputType" Type="TypeSymbol"/>
<Field Name="DeconstructMethodOpt" Type="MethodSymbol" Null="allow"/>
<Field Name="Deconstruction" Type="ImmutableArray&lt;BoundPattern&gt;" Null="allow"/>
<Field Name="PropertiesOpt" Type="ImmutableArray&lt;(Symbol symbol, BoundPattern pattern)&gt;" Null="allow"/>
<!-- Variable is a local symbol, or in the case of top-level code in scripts and interactive,
a field that is a member of the script class. Variable is null if `_` is used or if the identifier is omitted. -->
<Field Name="Variable" Type="Symbol" Null="allow"/>
<!-- VariableAccess is an access to the declared variable, suitable for use
in the lowered form in either an lvalue or rvalue position. We maintain it separately
from the Symbol to facilitate lowerings in which the variable is no longer a simple
variable access (e.g. in the expression evaluator). It is expected to be logically side-effect
free. The necessity of this member is a consequence of a design issue documented in
https://github.com/dotnet/roslyn/issues/13960 . When that is fixed this field can be
removed. -->
<Field Name="VariableAccess" Type="BoundExpression" Null="allow"/>
</Node>
<Node Name="BoundDiscardExpression" Base="BoundExpression">
......
......@@ -555,12 +555,4 @@ public BoundTryStatement(SyntaxNode syntax, BoundBlock tryBlock, ImmutableArray<
{
}
}
internal partial class BoundDeclarationPattern
{
public BoundDeclarationPattern(SyntaxNode syntax, LocalSymbol localSymbol, BoundTypeExpression declaredType, bool isVar, bool hasErrors = false)
: this(syntax, localSymbol, localSymbol == null ? new BoundDiscardExpression(syntax, declaredType.Type) : (BoundExpression)new BoundLocal(syntax, localSymbol, null, declaredType.Type), declaredType, isVar, hasErrors)
{
}
}
}
......@@ -116,7 +116,12 @@ protected DecisionTree AddToDecisionTree(DecisionTree decisionTree, SyntaxNode s
{
var declarationPattern = (BoundDeclarationPattern)pattern;
DecisionMaker maker =
(e, t) => new DecisionTree.Guarded(e, t, ImmutableArray.Create(new KeyValuePair<BoundExpression, BoundExpression>(e, declarationPattern.VariableAccess)), sectionSyntax, guard, label);
(e, t) => new DecisionTree.Guarded(
e, t,
(declarationPattern.VariableAccess == null)
? ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>>.Empty
: ImmutableArray.Create(new KeyValuePair<BoundExpression, BoundExpression>(e, declarationPattern.VariableAccess)),
sectionSyntax, guard, label);
if (declarationPattern.IsVar)
{
return Add(decisionTree, maker);
......@@ -126,7 +131,7 @@ protected DecisionTree AddToDecisionTree(DecisionTree decisionTree, SyntaxNode s
return AddByType(decisionTree, declarationPattern.DeclaredType.Type, maker);
}
}
case BoundKind.WildcardPattern:
case BoundKind.DiscardPattern:
// We do not yet support a wildcard pattern syntax. It is used exclusively
// to model the "default:" case, which is handled specially in the caller.
default:
......
......@@ -5416,6 +5416,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to The type &apos;var&apos; is not permitted in recursive patterns. If you want the type inferred, just omit it..
/// </summary>
internal static string ERR_InferredRecursivePatternType {
get {
return ResourceManager.GetString("ERR_InferredRecursivePatternType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot initialize a by-reference variable with a value.
/// </summary>
......@@ -7981,6 +7990,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to A property subpattern requires a reference to the property or field to be matched, e.g. &apos;{{ Name: {0}}}&apos;.
/// </summary>
internal static string ERR_PropertyPatternNameMissing {
get {
return ResourceManager.GetString("ERR_PropertyPatternNameMissing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos;: property or indexer must have at least one accessor.
/// </summary>
......@@ -9961,6 +9979,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Matching the tuple type &apos;{0}&apos; requires &apos;{1}&apos; subpatterns, but &apos;{2}&apos; subpatterns are present..
/// </summary>
internal static string ERR_WrongNumberOfSubpatterns {
get {
return ResourceManager.GetString("ERR_WrongNumberOfSubpatterns", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The yield statement cannot be used inside an anonymous method or lambda expression.
/// </summary>
......
......@@ -5216,4 +5216,13 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_FeatureRecursivePatterns" xml:space="preserve">
<value>recursive patterns</value>
</data>
<data name="ERR_InferredRecursivePatternType" xml:space="preserve">
<value>The type 'var' is not permitted in recursive patterns. If you want the type inferred, just omit it.</value>
</data>
<data name="ERR_WrongNumberOfSubpatterns" xml:space="preserve">
<value>Matching the tuple type '{0}' requires '{1}' subpatterns, but '{2}' subpatterns are present.</value>
</data>
<data name="ERR_PropertyPatternNameMissing" xml:space="preserve">
<value>A property subpattern requires a reference to the property or field to be matched, e.g. '{{ Name: {0}}}'</value>
</data>
</root>
\ No newline at end of file
......@@ -1541,6 +1541,10 @@ internal enum ErrorCode
#region diagnostics introduced for recursive patterns
// PROTOTYPE(patterns2): renumber these before committing
ERR_MissingPattern = 8500,
ERR_InferredRecursivePatternType = 8501,
ERR_WrongNumberOfSubpatterns = 8502,
ERR_PropertyPatternNameMissing = 8503,
//ERR_FeatureIsUnimplemented = 8504,
#endregion diagnostics introduced for recursive patterns
}
......
......@@ -1189,6 +1189,22 @@ protected virtual void AssignImpl(BoundNode node, BoundExpression value, RefKind
break;
}
case BoundKind.RecursivePattern:
{
var pattern = (BoundRecursivePattern)node;
var symbol = pattern.Variable as LocalSymbol;
if ((object)symbol != null)
{
// we do not track definite assignment for pattern variables when they are
// promoted to fields for top-level code in scripts and interactive
int slot = GetOrCreateSlot(symbol);
SetSlotState(slot, assigned: written || !this.State.Reachable);
}
if (written) NoteWrite(pattern.VariableAccess, value, read);
break;
}
case BoundKind.LocalDeclaration:
{
var local = (BoundLocalDeclaration)node;
......@@ -1479,7 +1495,7 @@ private void AssignPatternVariables(BoundPattern pattern)
Assign(pat, null, RefKind.None, false);
break;
}
case BoundKind.WildcardPattern:
case BoundKind.DiscardPattern:
break;
case BoundKind.ConstantPattern:
{
......@@ -1487,6 +1503,26 @@ private void AssignPatternVariables(BoundPattern pattern)
this.VisitRvalue(pat.Value);
break;
}
case BoundKind.RecursivePattern:
{
var pat = (BoundRecursivePattern)pattern;
if (!pat.Deconstruction.IsDefaultOrEmpty)
{
foreach (var subpat in pat.Deconstruction)
{
AssignPatternVariables(subpat);
}
}
if (!pat.PropertiesOpt.IsDefaultOrEmpty)
{
foreach (var (_, subpat) in pat.PropertiesOpt)
{
AssignPatternVariables(subpat);
}
}
Assign(pat, null, RefKind.None, false);
break;
}
default:
break;
}
......
......@@ -109,6 +109,11 @@ private Symbol GetNodeSymbol(BoundNode node)
return ((BoundDeclarationPattern)node).Variable as LocalSymbol;
}
case BoundKind.RecursivePattern:
{
return ((BoundRecursivePattern)node).Variable as LocalSymbol;
}
case BoundKind.FieldAccess:
{
var fieldAccess = (BoundFieldAccess)node;
......
......@@ -64,15 +64,29 @@ protected override void VisitPatternSwitchSection(BoundPatternSwitchSection node
/// </summary>
private void NoteDeclaredPatternVariables(BoundPattern pattern)
{
if (IsInside && pattern.Kind == BoundKind.DeclarationPattern)
if (IsInside)
{
var decl = (BoundDeclarationPattern)pattern;
// The variable may be null if it is a discard designation `_`.
if (decl.Variable?.Kind == SymbolKind.Local)
switch (pattern)
{
// Because this API only returns local symbols and parameters,
// we exclude pattern variables that have become fields in scripts.
_variablesDeclared.Add(decl.Variable);
case BoundDeclarationPattern decl:
{
// The variable may be null if it is a discard designation `_`.
if (decl.Variable?.Kind == SymbolKind.Local)
{
// Because this API only returns local symbols and parameters,
// we exclude pattern variables that have become fields in scripts.
_variablesDeclared.Add(decl.Variable);
}
}
break;
case BoundRecursivePattern recur:
{
if (recur.Variable?.Kind == SymbolKind.Local)
{
_variablesDeclared.Add(recur.Variable);
}
}
break;
}
}
}
......
......@@ -10730,6 +10730,114 @@ protected PatternSyntax(ObjectReader reader)
}
}
internal sealed partial class DiscardPatternSyntax : PatternSyntax
{
internal readonly SyntaxToken underscoreToken;
internal DiscardPatternSyntax(SyntaxKind kind, SyntaxToken underscoreToken, DiagnosticInfo[] diagnostics, SyntaxAnnotation[] annotations)
: base(kind, diagnostics, annotations)
{
this.SlotCount = 1;
this.AdjustFlagsAndWidth(underscoreToken);
this.underscoreToken = underscoreToken;
}
internal DiscardPatternSyntax(SyntaxKind kind, SyntaxToken underscoreToken, SyntaxFactoryContext context)
: base(kind)
{
this.SetFactoryContext(context);
this.SlotCount = 1;
this.AdjustFlagsAndWidth(underscoreToken);
this.underscoreToken = underscoreToken;
}
internal DiscardPatternSyntax(SyntaxKind kind, SyntaxToken underscoreToken)
: base(kind)
{
this.SlotCount = 1;
this.AdjustFlagsAndWidth(underscoreToken);
this.underscoreToken = underscoreToken;
}
public SyntaxToken UnderscoreToken { get { return this.underscoreToken; } }
internal override GreenNode GetSlot(int index)
{
switch (index)
{
case 0: return this.underscoreToken;
default: return null;
}
}
internal override SyntaxNode CreateRed(SyntaxNode parent, int position)
{
return new CSharp.Syntax.DiscardPatternSyntax(this, parent, position);
}
public override TResult Accept<TResult>(CSharpSyntaxVisitor<TResult> visitor)
{
return visitor.VisitDiscardPattern(this);
}
public override void Accept(CSharpSyntaxVisitor visitor)
{
visitor.VisitDiscardPattern(this);
}
public DiscardPatternSyntax Update(SyntaxToken underscoreToken)
{
if (underscoreToken != this.UnderscoreToken)
{
var newNode = SyntaxFactory.DiscardPattern(underscoreToken);
var diags = this.GetDiagnostics();
if (diags != null && diags.Length > 0)
newNode = newNode.WithDiagnosticsGreen(diags);
var annotations = this.GetAnnotations();
if (annotations != null && annotations.Length > 0)
newNode = newNode.WithAnnotationsGreen(annotations);
return newNode;
}
return this;
}
internal override GreenNode SetDiagnostics(DiagnosticInfo[] diagnostics)
{
return new DiscardPatternSyntax(this.Kind, this.underscoreToken, diagnostics, GetAnnotations());
}
internal override GreenNode SetAnnotations(SyntaxAnnotation[] annotations)
{
return new DiscardPatternSyntax(this.Kind, this.underscoreToken, GetDiagnostics(), annotations);
}
internal DiscardPatternSyntax(ObjectReader reader)
: base(reader)
{
this.SlotCount = 1;
var underscoreToken = (SyntaxToken)reader.ReadValue();
if (underscoreToken != null)
{
AdjustFlagsAndWidth(underscoreToken);
this.underscoreToken = underscoreToken;
}
}
internal override void WriteTo(ObjectWriter writer)
{
base.WriteTo(writer);
writer.WriteValue(this.underscoreToken);
}
static DiscardPatternSyntax()
{
ObjectBinder.RegisterTypeReader(typeof(DiscardPatternSyntax), r => new DiscardPatternSyntax(r));
}
}
internal sealed partial class DeclarationPatternSyntax : PatternSyntax
{
internal readonly TypeSyntax type;
......@@ -15974,7 +16082,7 @@ internal ForEachVariableStatementSyntax(SyntaxKind kind, SyntaxToken forEachKeyw
/// <summary>
/// The variable(s) of the loop. In correct code this is a tuple
/// literal, declaration expression with a tuple designator, or
/// a wildcard syntax in the form of a simple identifier. In broken
/// a discard syntax in the form of a simple identifier. In broken
/// code it could be something else.
/// </summary>
public ExpressionSyntax Variable { get { return this.variable; } }
......@@ -34642,6 +34750,11 @@ public virtual TResult VisitWhenClause(WhenClauseSyntax node)
return this.DefaultVisit(node);
}
public virtual TResult VisitDiscardPattern(DiscardPatternSyntax node)
{
return this.DefaultVisit(node);
}
public virtual TResult VisitDeclarationPattern(DeclarationPatternSyntax node)
{
return this.DefaultVisit(node);
......@@ -35686,6 +35799,11 @@ public virtual void VisitWhenClause(WhenClauseSyntax node)
this.DefaultVisit(node);
}
public virtual void VisitDiscardPattern(DiscardPatternSyntax node)
{
this.DefaultVisit(node);
}
public virtual void VisitDeclarationPattern(DeclarationPatternSyntax node)
{
this.DefaultVisit(node);
......@@ -36936,6 +37054,12 @@ public override CSharpSyntaxNode VisitWhenClause(WhenClauseSyntax node)
return node.Update(whenKeyword, condition);
}
public override CSharpSyntaxNode VisitDiscardPattern(DiscardPatternSyntax node)
{
var underscoreToken = (SyntaxToken)this.Visit(node.UnderscoreToken);
return node.Update(underscoreToken);
}
public override CSharpSyntaxNode VisitDeclarationPattern(DeclarationPatternSyntax node)
{
var type = (TypeSyntax)this.Visit(node.Type);
......@@ -40606,6 +40730,33 @@ public WhenClauseSyntax WhenClause(SyntaxToken whenKeyword, ExpressionSyntax con
return result;
}
public DiscardPatternSyntax DiscardPattern(SyntaxToken underscoreToken)
{
#if DEBUG
if (underscoreToken == null)
throw new ArgumentNullException(nameof(underscoreToken));
switch (underscoreToken.Kind)
{
case SyntaxKind.IdentifierToken:
break;
default:
throw new ArgumentException("underscoreToken");
}
#endif
int hash;
var cached = CSharpSyntaxNodeCache.TryGetNode((int)SyntaxKind.DiscardPattern, underscoreToken, this.context, out hash);
if (cached != null) return (DiscardPatternSyntax)cached;
var result = new DiscardPatternSyntax(SyntaxKind.DiscardPattern, underscoreToken, this.context);
if (hash >= 0)
{
SyntaxNodeCache.AddNode(result, hash);
}
return result;
}
public DeclarationPatternSyntax DeclarationPattern(TypeSyntax type, VariableDesignationSyntax designation)
{
#if DEBUG
......@@ -47633,6 +47784,33 @@ public static WhenClauseSyntax WhenClause(SyntaxToken whenKeyword, ExpressionSyn
return result;
}
public static DiscardPatternSyntax DiscardPattern(SyntaxToken underscoreToken)
{
#if DEBUG
if (underscoreToken == null)
throw new ArgumentNullException(nameof(underscoreToken));
switch (underscoreToken.Kind)
{
case SyntaxKind.IdentifierToken:
break;
default:
throw new ArgumentException("underscoreToken");
}
#endif
int hash;
var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.DiscardPattern, underscoreToken, out hash);
if (cached != null) return (DiscardPatternSyntax)cached;
var result = new DiscardPatternSyntax(SyntaxKind.DiscardPattern, underscoreToken);
if (hash >= 0)
{
SyntaxNodeCache.AddNode(result, hash);
}
return result;
}
public static DeclarationPatternSyntax DeclarationPattern(TypeSyntax type, VariableDesignationSyntax designation)
{
#if DEBUG
......@@ -52283,6 +52461,7 @@ internal static IEnumerable<Type> GetNodeTypes()
typeof(IsPatternExpressionSyntax),
typeof(ThrowExpressionSyntax),
typeof(WhenClauseSyntax),
typeof(DiscardPatternSyntax),
typeof(DeclarationPatternSyntax),
typeof(DeconstructionPatternSyntax),
typeof(SubpatternElementSyntax),
......@@ -454,6 +454,12 @@ public virtual TResult VisitWhenClause(WhenClauseSyntax node)
return this.DefaultVisit(node);
}
/// <summary>Called when the visitor visits a DiscardPatternSyntax node.</summary>
public virtual TResult VisitDiscardPattern(DiscardPatternSyntax node)
{
return this.DefaultVisit(node);
}
/// <summary>Called when the visitor visits a DeclarationPatternSyntax node.</summary>
public virtual TResult VisitDeclarationPattern(DeclarationPatternSyntax node)
{
......@@ -1705,6 +1711,12 @@ public virtual void VisitWhenClause(WhenClauseSyntax node)
this.DefaultVisit(node);
}
/// <summary>Called when the visitor visits a DiscardPatternSyntax node.</summary>
public virtual void VisitDiscardPattern(DiscardPatternSyntax node)
{
this.DefaultVisit(node);
}
/// <summary>Called when the visitor visits a DeclarationPatternSyntax node.</summary>
public virtual void VisitDeclarationPattern(DeclarationPatternSyntax node)
{
......@@ -3090,6 +3102,12 @@ public override SyntaxNode VisitWhenClause(WhenClauseSyntax node)
return node.Update(whenKeyword, condition);
}
public override SyntaxNode VisitDiscardPattern(DiscardPatternSyntax node)
{
var underscoreToken = this.VisitToken(node.UnderscoreToken);
return node.Update(underscoreToken);
}
public override SyntaxNode VisitDeclarationPattern(DeclarationPatternSyntax node)
{
var type = (TypeSyntax)this.Visit(node.Type);
......@@ -6609,6 +6627,20 @@ public static WhenClauseSyntax WhenClause(ExpressionSyntax condition)
return SyntaxFactory.WhenClause(SyntaxFactory.Token(SyntaxKind.WhenKeyword), condition);
}
/// <summary>Creates a new DiscardPatternSyntax instance.</summary>
public static DiscardPatternSyntax DiscardPattern(SyntaxToken underscoreToken)
{
switch (underscoreToken.Kind())
{
case SyntaxKind.IdentifierToken:
break;
default:
throw new ArgumentException("underscoreToken");
}
return (DiscardPatternSyntax)Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.SyntaxFactory.DiscardPattern((Syntax.InternalSyntax.SyntaxToken)underscoreToken.Node).CreateRed();
}
/// <summary>Creates a new DeclarationPatternSyntax instance.</summary>
public static DeclarationPatternSyntax DeclarationPattern(TypeSyntax type, VariableDesignationSyntax designation)
{
......
......@@ -6711,6 +6711,63 @@ internal PatternSyntax(Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.CShar
}
}
public sealed partial class DiscardPatternSyntax : PatternSyntax
{
internal DiscardPatternSyntax(Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.CSharpSyntaxNode green, SyntaxNode parent, int position)
: base(green, parent, position)
{
}
public SyntaxToken UnderscoreToken
{
get { return new SyntaxToken(this, ((Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax.DiscardPatternSyntax)this.Green).underscoreToken, this.Position, 0); }
}
internal override SyntaxNode GetNodeSlot(int index)
{
switch (index)
{
default: return null;
}
}
internal override SyntaxNode GetCachedSlot(int index)
{
switch (index)
{
default: return null;
}
}
public override TResult Accept<TResult>(CSharpSyntaxVisitor<TResult> visitor)
{
return visitor.VisitDiscardPattern(this);
}
public override void Accept(CSharpSyntaxVisitor visitor)
{
visitor.VisitDiscardPattern(this);
}
public DiscardPatternSyntax Update(SyntaxToken underscoreToken)
{
if (underscoreToken != this.UnderscoreToken)
{
var newNode = SyntaxFactory.DiscardPattern(underscoreToken);
var annotations = this.GetAnnotations();
if (annotations != null && annotations.Length > 0)
return newNode.WithAnnotations(annotations);
return newNode;
}
return this;
}
public DiscardPatternSyntax WithUnderscoreToken(SyntaxToken underscoreToken)
{
return this.Update(underscoreToken);
}
}
public sealed partial class DeclarationPatternSyntax : PatternSyntax
{
private TypeSyntax type;
......@@ -9914,7 +9971,7 @@ public override SyntaxToken OpenParenToken
/// <summary>
/// The variable(s) of the loop. In correct code this is a tuple
/// literal, declaration expression with a tuple designator, or
/// a wildcard syntax in the form of a simple identifier. In broken
/// a discard syntax in the form of a simple identifier. In broken
/// code it could be something else.
/// </summary>
public ExpressionSyntax Variable
......
......@@ -381,6 +381,11 @@ public BoundAssignmentOperator AssignmentExpression(BoundExpression left, BoundE
return new BoundAssignmentOperator(Syntax, left, right, left.Type, refKind: refKind) { WasCompilerGenerated = true };
}
public BoundAssignmentOperator AssignmentExpression(LocalSymbol left, BoundExpression right, RefKind refKind = RefKind.None)
{
return new BoundAssignmentOperator(Syntax, this.Local(left), right, left.Type, refKind: refKind) { WasCompilerGenerated = true };
}
public BoundBlock Block()
{
return Block(ImmutableArray<BoundStatement>.Empty);
......@@ -553,6 +558,11 @@ public BoundLiteral Literal(uint value)
return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_UInt32)) { WasCompilerGenerated = true };
}
public BoundLiteral Literal(ConstantValue value, TypeSymbol type)
{
return new BoundLiteral(Syntax, value, type) { WasCompilerGenerated = true };
}
public BoundObjectCreationExpression New(NamedTypeSymbol type, params BoundExpression[] args)
{
// TODO: add diagnostics for when things fall apart
......
......@@ -224,8 +224,10 @@ private IOperation CreateInternal(BoundNode boundNode)
return CreateBoundConstantPatternOperation((BoundConstantPattern)boundNode);
case BoundKind.DeclarationPattern:
return CreateBoundDeclarationPatternOperation((BoundDeclarationPattern)boundNode);
case BoundKind.WildcardPattern:
throw ExceptionUtilities.Unreachable;
case BoundKind.RecursivePattern:
return CreateBoundRecursivePatternOperation((BoundRecursivePattern)boundNode);
case BoundKind.DiscardPattern:
return CreateBoundDiscardPatternOperation((BoundDiscardPattern)boundNode);
case BoundKind.PatternSwitchStatement:
return CreateBoundPatternSwitchStatementOperation((BoundPatternSwitchStatement)boundNode);
case BoundKind.PatternSwitchLabel:
......@@ -1631,12 +1633,12 @@ private IInterpolatedStringText CreateBoundInterpolatedStringTextOperation(Bound
private IConstantPattern CreateBoundConstantPatternOperation(BoundConstantPattern boundConstantPattern)
{
Lazy<IOperation> value = new Lazy<IOperation>(() => Create(boundConstantPattern.Value));
SyntaxNode syntax = boundConstantPattern.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundConstantPattern.WasCompilerGenerated;
return new LazyConstantPattern(value, _semanticModel, syntax, type, constantValue, isImplicit);
Lazy<IOperation> value = new Lazy<IOperation>(() => Create(boundConstantPattern.Value));
SyntaxNode syntax = boundConstantPattern.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundConstantPattern.WasCompilerGenerated;
return new LazyConstantPattern(value, _semanticModel, syntax, type, constantValue, isImplicit);
}
private IDeclarationPattern CreateBoundDeclarationPatternOperation(BoundDeclarationPattern boundDeclarationPattern)
......@@ -1649,6 +1651,25 @@ private IDeclarationPattern CreateBoundDeclarationPatternOperation(BoundDeclarat
return new DeclarationPattern(variable, _semanticModel, syntax, type, constantValue, isImplicit);
}
private IDiscardPattern CreateBoundDiscardPatternOperation(BoundDiscardPattern boundDiscardPattern)
{
SyntaxNode syntax = boundDiscardPattern.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundDiscardPattern.WasCompilerGenerated;
return new DiscardPattern(_semanticModel, syntax, type, constantValue, isImplicit);
}
private IRecursivePattern CreateBoundRecursivePatternOperation(BoundRecursivePattern boundRecursivePattern)
{
ISymbol variable = boundRecursivePattern.Variable;
SyntaxNode syntax = boundRecursivePattern.Syntax;
ITypeSymbol type = null;
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundRecursivePattern.WasCompilerGenerated;
return new RecursivePattern(variable, _semanticModel, syntax, type, constantValue, isImplicit);
}
private ISwitchStatement CreateBoundPatternSwitchStatementOperation(BoundPatternSwitchStatement boundPatternSwitchStatement)
{
Lazy<IOperation> value = new Lazy<IOperation>(() => Create(boundPatternSwitchStatement.Expression));
......@@ -1667,7 +1688,7 @@ private ICaseClause CreateBoundPatternSwitchLabelOperation(BoundPatternSwitchLab
Optional<object> constantValue = default(Optional<object>);
bool isImplicit = boundPatternSwitchLabel.WasCompilerGenerated;
if (boundPatternSwitchLabel.Pattern.Kind == BoundKind.WildcardPattern)
if (boundPatternSwitchLabel.Pattern.Kind == BoundKind.DiscardPattern)
{
// Default switch label in pattern switch statement is represented as a default case clause.
return new DefaultCaseClause(_semanticModel, syntax, type, constantValue, isImplicit);
......
......@@ -5146,25 +5146,6 @@ private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options)
// then assume it is unless we see specific tokens following it.
switch (this.CurrentToken.Kind)
{
case SyntaxKind.IdentifierToken:
// C#7: In certain contexts, we treat *identifier* as a disambiguating token. Those
// contexts are where the sequence of tokens being disambiguated is immediately preceded by one
// of the keywords is, case, or out, or arises while parsing the first element of a tuple literal
// (in which case the tokens are preceded by `(` and the identifier is followed by a `,`) or a
// subsequent element of a tuple literal (in which case the tokens are preceded by `,` and the
// identifier is followed by a `,` or `)`).
// Note that we treat query contextual keywords (which appear here as identifiers) as disambiguating tokens as well.
if ((options & (NameOptions.AfterIs | NameOptions.DefinitePattern | NameOptions.AfterOut)) != 0 ||
(options & NameOptions.AfterTupleComma) != 0 && (this.PeekToken(1).Kind == SyntaxKind.CommaToken || this.PeekToken(1).Kind == SyntaxKind.CloseParenToken) ||
(options & NameOptions.FirstElementOfPossibleTupleLiteral) != 0 && this.PeekToken(1).Kind == SyntaxKind.CommaToken
)
{
// we allow 'G<T,U> x' as a pattern-matching operation and a declaration expression in a tuple.
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
return ScanTypeArgumentListKind.PossibleTypeArgumentList;
case SyntaxKind.OpenParenToken:
case SyntaxKind.CloseParenToken:
case SyntaxKind.CloseBracketToken:
......@@ -5203,6 +5184,27 @@ private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options)
// `A<B>>C` elsewhere is parsed as `A < (B >> C)`
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
case SyntaxKind.IdentifierToken:
// C#7: In certain contexts, we treat *identifier* as a disambiguating token. Those
// contexts are where the sequence of tokens being disambiguated is immediately preceded by one
// of the keywords is, case, or out, or arises while parsing the first element of a tuple literal
// (in which case the tokens are preceded by `(` and the identifier is followed by a `,`) or a
// subsequent element of a tuple literal (in which case the tokens are preceded by `,` and the
// identifier is followed by a `,` or `)`).
// In C#8 (or whenever recursive patterns are introduced) we also treat an identifier as a
// disambiguating token if we're parsing the type of a pattern.
// Note that we treat query contextual keywords (which appear here as identifiers) as disambiguating tokens as well.
if ((options & (NameOptions.AfterIs | NameOptions.DefinitePattern | NameOptions.AfterOut)) != 0 ||
(options & NameOptions.AfterTupleComma) != 0 && (this.PeekToken(1).Kind == SyntaxKind.CommaToken || this.PeekToken(1).Kind == SyntaxKind.CloseParenToken) ||
(options & NameOptions.FirstElementOfPossibleTupleLiteral) != 0 && this.PeekToken(1).Kind == SyntaxKind.CommaToken
)
{
// we allow 'G<T,U> x' as a pattern-matching operation and a declaration expression in a tuple.
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
return ScanTypeArgumentListKind.PossibleTypeArgumentList;
case SyntaxKind.EndOfFileToken: // e.g. `e is A<B>`
// This is useful for parsing expressions in isolation
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
......
......@@ -18,6 +18,12 @@ private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
var tk = this.CurrentToken.Kind;
Precedence precedence = GetPrecedence(SyntaxKind.IsPatternExpression);
// We will parse a shift-expression ONLY (nothing looser) - i.e. not a relational expression
// So x is y < z should be parsed as (x is y) < z
// But x is y << z should be parsed as x is (y << z)
Debug.Assert(Precedence.Shift == precedence + 1);
// For totally broken syntax, parse a type for error recovery purposes
switch (tk)
{
case SyntaxKind.CloseParenToken:
......@@ -32,6 +38,17 @@ private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
break;
}
if (tk == SyntaxKind.IdentifierToken && this.CurrentToken.Text == "_")
{
// In a pattern, we reserve `_` as a wildcard. It cannot be used (with that spelling) as the
// type of a declaration or recursive pattern, nor as a type in an in-type expression starting
// in C# 7. The binder will give a diagnostic if
// there is a usable symbol in scope by that name. You can always escape it, using `@_`.
// TODO(patterns2): Should we use the "contextual keyword" infrastructure for this?
var node = _syntaxFactory.DiscardPattern(this.EatToken(SyntaxKind.IdentifierToken));
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
}
// If it starts with 'nameof(', skip the 'if' and parse as a constant pattern.
if (SyntaxFacts.IsPredefinedType(tk) ||
(tk == SyntaxKind.IdentifierToken &&
......@@ -42,16 +59,19 @@ private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
{
TypeSyntax type = this.ParseType(ParseTypeMode.AfterIs);
if (!type.IsMissing && this.IsTrueIdentifier())
if (!type.IsMissing)
{
var designation = ParseSimpleDesignation();
return _syntaxFactory.DeclarationPattern(type, designation);
PatternSyntax p = ParsePatternContinued(type, false);
if (p != null)
{
return p;
}
}
tk = this.CurrentToken.ContextualKind;
if ((!IsExpectedBinaryOperator(tk) || GetPrecedence(SyntaxFacts.GetBinaryExpression(tk)) <= precedence) &&
// member selection is not formally a binary operator but has higher precedence than relational
tk != SyntaxKind.DotToken)
tk != SyntaxKind.DotToken)
{
// it is a typical "is Type" operator.
// Note that we don't bother checking for primary expressions such as X[e], X(e), X++, and X--
......@@ -66,11 +86,26 @@ private CSharpSyntaxNode ParseTypeOrPatternForIsOperator()
this.Release(ref resetPoint);
}
}
// check to see if it looks like a recursive pattern.
else if (tk == SyntaxKind.OpenParenToken || tk == SyntaxKind.OpenBraceToken)
{
var resetPoint = this.GetResetPoint();
try
{
PatternSyntax p = ParsePatternContinued(null, false);
if (p != null)
{
return p;
}
// We parse a shift-expression ONLY (nothing looser) - i.e. not a relational expression
// So x is y < z should be parsed as (x is y) < z
// But x is y << z should be parsed as x is (y << z)
Debug.Assert(Precedence.Shift == precedence + 1);
// this can occur when we encounter a misplaced lambda expression.
this.Reset(ref resetPoint);
}
finally
{
this.Release(ref resetPoint);
}
}
// In places where a pattern is supported, we do not support tuple types
// due to both syntactic and semantic ambiguities between tuple types and positional patterns.
......@@ -221,14 +256,56 @@ private bool ScanDesignation(bool permitTuple)
}
}
// Priority is the ExpressionSyntax. It might return ExpressionSyntax which might be a constant pattern such as 'case 3:'
// All constant expressions are converted to the constant pattern in the switch binder if it is a match statement.
// It is used for parsing patterns in the switch cases. It never returns constant pattern, because for a `case` we
// need to use a pre-pattern-matching syntax node for a constant case.
/// <summary>
/// Here is the grammar being parsed:
/// ``` antlr
/// pattern
/// : declaration_pattern
/// | constant_pattern
/// | deconstruction_pattern
/// | property_pattern
/// | discard_pattern
/// ;
/// declaration_pattern
/// : type identifier
/// ;
/// constant_pattern
/// : expression
/// ;
/// deconstruction_pattern
/// : type? '(' subpatterns? ')' property_subpattern? identifier?
/// ;
/// subpatterns
/// : subpattern
/// | subpattern ',' subpatterns
/// ;
/// subpattern
/// : pattern
/// | identifier ':' pattern
/// ;
/// property_subpattern
/// : '{' subpatterns? '}'
/// ;
/// property_pattern
/// : property_subpattern identifier?
/// ;
/// discard_pattern
/// : '_'
/// ;
/// ```
///
/// Priority is the ExpressionSyntax. It might return ExpressionSyntax which might be a constant pattern such as 'case 3:'
/// All constant expressions are converted to the constant pattern in the switch binder if it is a match statement.
/// It is used for parsing patterns in the switch cases. It never returns constant pattern, because for a `case` we
/// need to use a pre-pattern-matching syntax node for a constant case.
/// </summary>
/// <param name="forCase">prevents the use of "when" for the identifier</param>
/// <returns></returns>
private CSharpSyntaxNode ParseExpressionOrPattern(bool forCase)
{
// handle common error recovery situations during typing
switch (this.CurrentToken.Kind)
var tk = this.CurrentToken.Kind;
switch (tk)
{
case SyntaxKind.CommaToken:
case SyntaxKind.SemicolonToken:
......@@ -239,11 +316,21 @@ private CSharpSyntaxNode ParseExpressionOrPattern(bool forCase)
return this.ParseIdentifierName(ErrorCode.ERR_MissingArgument);
}
if (tk == SyntaxKind.IdentifierToken && this.CurrentToken.Text == "_")
{
// In a pattern, we reserve `_` as a wildcard. It cannot be used (with that spelling) as the
// type of a declaration or recursive pattern, nor as a type in an in-type expression starting
// in C# 7. The binder will give a diagnostic if
// there is a usable symbol in scope by that name. You can always escape it, using `@_`.
// TODO(patterns2): Should we use the "contextual keyword" infrastructure for this?
var node = _syntaxFactory.DiscardPattern(this.EatToken(SyntaxKind.IdentifierToken));
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
}
var resetPoint = this.GetResetPoint();
try
{
TypeSyntax type = null;
var tk = this.CurrentToken.Kind;
if ((SyntaxFacts.IsPredefinedType(tk) || tk == SyntaxKind.IdentifierToken) &&
// If it is a nameof, skip the 'if' and parse as an expression.
(this.CurrentToken.ContextualKind != SyntaxKind.NameOfKeyword || this.PeekToken(1).Kind != SyntaxKind.OpenParenToken))
......@@ -257,97 +344,108 @@ private CSharpSyntaxNode ParseExpressionOrPattern(bool forCase)
}
}
PropertySubpatternSyntax propertySubpattern = null;
bool parsePropertySubpattern()
PatternSyntax p = ParsePatternContinued(type, forCase);
if (p != null)
{
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
propertySubpattern = ParsePropertySubpattern();
return true;
}
return false;
return (forCase && p is ConstantPatternSyntax c) ? c.expression : (CSharpSyntaxNode)p;
}
VariableDesignationSyntax designation = null;
bool parseDesignation()
{
if (this.IsTrueIdentifier() && (!forCase || this.CurrentToken.ContextualKind != SyntaxKind.WhenKeyword))
{
designation = ParseSimpleDesignation();
return true;
}
return false;
}
this.Reset(ref resetPoint);
return this.ParseSubExpression(Precedence.Expression);
}
finally
{
this.Release(ref resetPoint);
}
}
bool looksLikeCast()
private PatternSyntax ParsePatternContinued(TypeSyntax type, bool forCase)
{
PropertySubpatternSyntax propertySubpattern = null;
bool parsePropertySubpattern()
{
if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken)
{
var resetPoint2 = this.GetResetPoint();
try
{
return this.ScanCast(forPattern: true);
}
finally
{
this.Reset(ref resetPoint2);
this.Release(ref resetPoint2);
}
propertySubpattern = ParsePropertySubpattern();
return true;
}
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken && (type != null || !looksLikeCast()))
{
// It is possible this is a parenthesized (constant) expression.
// We normalize later.
ParseSubpatternList(out var openParenToken, out var subPatterns, out var closeParenToken, SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken);
if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken)
{
// Testers do the darndest things, in this case putting a lambda expression where a pattern is expected.
this.Reset(ref resetPoint);
return this.ParseSubExpression(Precedence.Expression);
}
return false;
}
parsePropertySubpattern();
parseDesignation();
VariableDesignationSyntax designation = null;
bool parseDesignation()
{
if (this.IsTrueIdentifier() && (!forCase || this.CurrentToken.ContextualKind != SyntaxKind.WhenKeyword))
{
designation = ParseSimpleDesignation();
return true;
}
if (type == null &&
propertySubpattern == null &&
designation == null &&
subPatterns.Count == 1 &&
subPatterns[0].NameColon == null &&
subPatterns[0].Pattern is ConstantPatternSyntax cp)
{
// There is an ambiguity between a deconstruction pattern `(` pattern `)`
// and a constant expression pattern than happens to be parenthesized.
// We normalize to the parenthesized expression, and semantic analysis
// will notice the parens and treat it whichever way is semantically sensible,
// giving priority to its treatment as a constant expression.
return _syntaxFactory.ParenthesizedExpression(openParenToken, cp.Expression, closeParenToken);
}
return false;
}
var node = _syntaxFactory.DeconstructionPattern(type, openParenToken, subPatterns, closeParenToken, propertySubpattern, designation);
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
bool looksLikeCast()
{
var resetPoint2 = this.GetResetPoint();
try
{
return this.ScanCast(forPattern: true);
}
if (parsePropertySubpattern())
finally
{
parseDesignation();
var node = _syntaxFactory.PropertyPattern(type, propertySubpattern, designation);
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
this.Reset(ref resetPoint2);
this.Release(ref resetPoint2);
}
}
if (type != null && parseDesignation())
if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken && (type != null || !looksLikeCast()))
{
// It is possible this is a parenthesized (constant) expression.
// We normalize later.
ParseSubpatternList(out var openParenToken, out var subPatterns, out var closeParenToken, SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken);
//if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken)
//{
// // Testers do the darndest things, in this case putting a lambda expression where a pattern is expected.
// return null;
//}
parsePropertySubpattern();
parseDesignation();
if (type == null &&
propertySubpattern == null &&
designation == null &&
subPatterns.Count == 1 &&
subPatterns[0].NameColon == null &&
subPatterns[0].Pattern is ConstantPatternSyntax cp)
{
return _syntaxFactory.DeclarationPattern(type, designation);
// There is an ambiguity between a deconstruction pattern `(` pattern `)`
// and a constant expression pattern than happens to be parenthesized.
// We normalize to the parenthesized expression, and semantic analysis
// will notice the parens and treat it whichever way is semantically sensible,
// giving priority to its treatment as a constant expression.
return _syntaxFactory.ConstantPattern(_syntaxFactory.ParenthesizedExpression(openParenToken, cp.Expression, closeParenToken));
}
this.Reset(ref resetPoint);
return this.ParseSubExpression(Precedence.Expression);
var node = _syntaxFactory.DeconstructionPattern(type, openParenToken, subPatterns, closeParenToken, propertySubpattern, designation);
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
}
finally
if (parsePropertySubpattern())
{
this.Release(ref resetPoint);
parseDesignation();
var node = _syntaxFactory.PropertyPattern(type, propertySubpattern, designation);
return this.CheckFeatureAvailability(node, MessageID.IDS_FeatureRecursivePatterns);
}
if (type != null && parseDesignation())
{
return _syntaxFactory.DeclarationPattern(type, designation);
}
// let the caller fall back to its default (expression or type)
return null;
}
private PatternSyntax ParsePattern()
......
......@@ -17,6 +17,10 @@ Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.WithOpenParenTo
Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.WithPropertySubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax propertySubpattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.WithSubPatterns(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax> subPatterns) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.UnderscoreToken.get -> Microsoft.CodeAnalysis.SyntaxToken
Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.WithUnderscoreToken(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax.AddPropertySubpatternSubPatterns(params Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax[] items) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax.Designation.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax
......@@ -45,15 +49,19 @@ Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax.Update(Microsoft.Co
Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax.WithNameColon(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax.WithPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax
Microsoft.CodeAnalysis.CSharp.SyntaxKind.DeconstructionPattern = 9023 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.DiscardPattern = 9024 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.PropertyPattern = 9020 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.PropertySubpattern = 9021 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
Microsoft.CodeAnalysis.CSharp.SyntaxKind.SubpatternElement = 9022 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDeconstructionPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitPropertyPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitPropertySubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode
override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitSubpatternElement(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode
override Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void
override Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax.Accept<TResult>(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult> visitor) -> TResult
override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void
override Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax.Accept<TResult>(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult> visitor) -> TResult
override Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void
override Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax.Accept<TResult>(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult> visitor) -> TResult
override Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void
......@@ -64,6 +72,7 @@ static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetConversion(this Microso
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DeconstructionPattern(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax> subPatterns, Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax propertySubpattern, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DeconstructionPattern(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken openParenToken, Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax> subPatterns, Microsoft.CodeAnalysis.SyntaxToken closeParenToken, Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax propertySubpattern, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DeconstructionPattern(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax> subPatterns = default(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax>)) -> Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.DiscardPattern(Microsoft.CodeAnalysis.SyntaxToken underscoreToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PropertyPattern() -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PropertyPattern(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax propertySubpattern, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDesignationSyntax designation) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.PropertySubpattern(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax> subPatterns = default(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax>)) -> Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax
......@@ -72,10 +81,12 @@ static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RefType(Microsoft.CodeAnalysi
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SubpatternElement(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SubpatternElement(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDeconstructionPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax node) -> void
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> void
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPropertyPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax node) -> void
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitPropertySubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax node) -> void
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitSubpatternElement(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax node) -> void
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.VisitDeconstructionPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DeconstructionPatternSyntax node) -> TResult
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.VisitDiscardPattern(Microsoft.CodeAnalysis.CSharp.Syntax.DiscardPatternSyntax node) -> TResult
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.VisitPropertyPattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertyPatternSyntax node) -> TResult
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.VisitPropertySubpattern(Microsoft.CodeAnalysis.CSharp.Syntax.PropertySubpatternSyntax node) -> TResult
virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>.VisitSubpatternElement(Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternElementSyntax node) -> TResult
\ No newline at end of file
......@@ -27,7 +27,7 @@ internal class GlobalExpressionVariable : SourceMemberFieldSymbol
: base(containingType, modifiers, name, syntax, location)
{
Debug.Assert(DeclaredAccessibility == Accessibility.Private);
_typeSyntax = typeSyntax.GetReference();
_typeSyntax = typeSyntax?.GetReference();
}
internal static GlobalExpressionVariable Create(
......@@ -43,14 +43,14 @@ internal class GlobalExpressionVariable : SourceMemberFieldSymbol
Debug.Assert(nodeToBind.Kind() == SyntaxKind.VariableDeclarator || nodeToBind is ExpressionSyntax);
var syntaxReference = syntax.GetReference();
return typeSyntax.IsVar
return (typeSyntax == null || typeSyntax.IsVar)
? new InferrableGlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, location, containingFieldOpt, nodeToBind)
: new GlobalExpressionVariable(containingType, modifiers, typeSyntax, name, syntaxReference, location);
}
protected override SyntaxList<AttributeListSyntax> AttributeDeclarationSyntaxList => default(SyntaxList<AttributeListSyntax>);
protected override TypeSyntax TypeSyntax => (TypeSyntax)_typeSyntax.GetSyntax();
protected override TypeSyntax TypeSyntax => (TypeSyntax)_typeSyntax?.GetSyntax();
protected override SyntaxTokenList ModifiersTokenList => default(SyntaxTokenList);
public override bool HasInitializer => false;
protected override ConstantValue MakeConstantValue(
......@@ -73,12 +73,21 @@ internal override TypeSymbol GetFieldType(ConsList<FieldSymbol> fieldsBeingBound
var diagnostics = DiagnosticBag.GetInstance();
TypeSymbol type;
bool isVar;
var binderFactory = compilation.GetBinderFactory(SyntaxTree);
var binder = binderFactory.GetBinder(typeSyntax);
var binder = binderFactory.GetBinder(typeSyntax ?? SyntaxNode);
bool isVar;
type = binder.BindType(typeSyntax, diagnostics, out isVar);
if (typeSyntax != null)
{
type = binder.BindType(typeSyntax, diagnostics, out isVar);
}
else
{
// Recursive patterns may omit the type syntax
isVar = true;
type = null;
}
Debug.Assert((object)type != null || isVar);
......@@ -124,6 +133,7 @@ private TypeSymbol SetType(CSharpCompilation compilation, DiagnosticBag diagnost
compilation.DeclarationDiagnostics.AddRange(diagnostics);
state.NotePartComplete(CompletionPart.Type);
}
return _lazyType;
}
......
......@@ -56,7 +56,7 @@ internal class SourceLocalSymbol : LocalSymbol
this._scopeBinder = scopeBinder;
this._containingSymbol = containingSymbol;
this._identifierToken = identifierToken;
this._typeSyntax = allowRefKind ? typeSyntax.SkipRef(out this._refKind) : typeSyntax;
this._typeSyntax = allowRefKind ? typeSyntax?.SkipRef(out this._refKind) : typeSyntax;
this._declarationKind = declarationKind;
// create this eagerly as it will always be needed for the EnsureSingleDefinition
......@@ -170,7 +170,7 @@ internal new string GetDebuggerDisplay()
new[] { SyntaxKind.LocalDeclarationStatement, SyntaxKind.ForStatement, SyntaxKind.UsingStatement, SyntaxKind.FixedStatement }.
Contains(nodeToBind.Ancestors().OfType<StatementSyntax>().First().Kind()) ||
nodeToBind is ExpressionSyntax);
return typeSyntax.IsVar && kind != LocalDeclarationKind.DeclarationExpressionVariable
return typeSyntax?.IsVar != false && kind != LocalDeclarationKind.DeclarationExpressionVariable
? new LocalSymbolWithEnclosingContext(containingSymbol, scopeBinder, nodeBinder, typeSyntax, identifierToken, kind, nodeToBind, forbiddenZone)
: new SourceLocalSymbol(containingSymbol, scopeBinder, false, typeSyntax, identifierToken, kind);
}
......@@ -303,14 +303,13 @@ public bool IsVar
{
if (_typeSyntax == null)
{
// in "let x = 1;" there is no syntax corresponding to the type.
// in "e is {} x" there is no syntax corresponding to the type.
return true;
}
if (_typeSyntax.IsVar)
{
bool isVar;
TypeSymbol declType = this.TypeSyntaxBinder.BindType(_typeSyntax, new DiagnosticBag(), out isVar);
TypeSymbol declType = this.TypeSyntaxBinder.BindType(_typeSyntax, new DiagnosticBag(), out var isVar);
return isVar;
}
......@@ -325,8 +324,16 @@ private TypeSymbol GetTypeSymbol()
Binder typeBinder = this.TypeSyntaxBinder;
bool isVar;
RefKind refKind;
TypeSymbol declType = typeBinder.BindType(_typeSyntax.SkipRef(out refKind), diagnostics, out isVar);
TypeSymbol declType;
if (_typeSyntax == null) // In recursive patterns the type may be omitted.
{
isVar = true;
declType = null;
}
else
{
declType = typeBinder.BindType(_typeSyntax.SkipRef(out var refKind), diagnostics, out isVar);
}
if (isVar)
{
......
......@@ -49,15 +49,15 @@ protected void TypeChecks(TypeSymbol type, DiagnosticBag diagnostics)
}
else if (type.SpecialType == SpecialType.System_Void)
{
diagnostics.Add(ErrorCode.ERR_FieldCantHaveVoidType, TypeSyntax.Location);
diagnostics.Add(ErrorCode.ERR_FieldCantHaveVoidType, TypeSyntax?.Location ?? this.Locations[0]);
}
else if (type.IsRestrictedType(ignoreSpanLikeTypes: true))
{
diagnostics.Add(ErrorCode.ERR_FieldCantBeRefAny, TypeSyntax.Location, type);
diagnostics.Add(ErrorCode.ERR_FieldCantBeRefAny, TypeSyntax?.Location ?? this.Locations[0], type);
}
else if (type.IsByRefLikeType && (this.IsStatic || !containingType.IsByRefLikeType))
{
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeSyntax.Location, type);
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeSyntax?.Location ?? this.Locations[0], type);
}
else if (IsConst && !type.CanBeConst())
{
......
......@@ -1718,6 +1718,12 @@
<Field Name="Condition" Type="ExpressionSyntax"/>
</Node>
<AbstractNode Name="PatternSyntax" Base="CSharpSyntaxNode" />
<Node Name="DiscardPatternSyntax" Base="PatternSyntax">
<Kind Name="DiscardPattern" />
<Field Name="UnderscoreToken" Type="SyntaxToken">
<Kind Name="IdentifierToken"/>
</Field>
</Node>
<Node Name="DeclarationPatternSyntax" Base="PatternSyntax">
<Kind Name="DeclarationPattern" />
<Field Name="Type" Type="TypeSyntax"/>
......@@ -2169,7 +2175,7 @@
<summary>
The variable(s) of the loop. In correct code this is a tuple
literal, declaration expression with a tuple designator, or
a wildcard syntax in the form of a simple identifier. In broken
a discard syntax in the form of a simple identifier. In broken
code it could be something else.
</summary>
</PropertyComment>
......
......@@ -560,6 +560,7 @@ public enum SyntaxKind : ushort
PropertySubpattern = 9021,
SubpatternElement = 9022,
DeconstructionPattern = 9023,
DiscardPattern = 9024,
// Kinds between 9000 and 9039 are "reserved" for pattern matching.
// Please start with 9040 if you add more kinds below.
......
......@@ -5906,6 +5906,12 @@ static void M(out int x)
// (8,18): error CS8059: Feature 'pattern matching' is not available in C# 6. Please use language version 7.0 or greater.
// bool b = 3 is int _;
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "3 is int _").WithArguments("pattern matching", "7.0").WithLocation(8, 18),
// (11,13): error CS8059: Feature 'pattern matching' is not available in C# 6. Please use language version 7.0 or greater.
// case _: // not a discard
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "case _:").WithArguments("pattern matching", "7.0").WithLocation(11, 13),
// (11,18): error CS8058: Feature 'recursive patterns' is experimental and unsupported; use '/features:patterns2' to enable.
// case _: // not a discard
Diagnostic(ErrorCode.ERR_FeatureIsExperimental, "_").WithArguments("recursive patterns", "patterns2").WithLocation(11, 18),
// (16,13): error CS8059: Feature 'pattern matching' is not available in C# 6. Please use language version 7.0 or greater.
// case int _:
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "case int _:").WithArguments("pattern matching", "7.0").WithLocation(16, 13),
......@@ -5918,15 +5924,9 @@ static void M(out int x)
// (6,10): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7.0 or greater.
// (_, var _, int _) = (1, 2, 3);
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "_").WithArguments("tuples", "7.0").WithLocation(6, 10),
// (11,18): error CS0103: The name '_' does not exist in the current context
// case _: // not a discard
Diagnostic(ErrorCode.ERR_NameNotInContext, "_").WithArguments("_").WithLocation(11, 18),
// (21,15): error CS8059: Feature 'tuples' is not available in C# 6. Please use language version 7.0 or greater.
// M(out _);
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "_").WithArguments("tuples", "7.0").WithLocation(21, 15),
// (12,17): warning CS0162: Unreachable code detected
// break;
Diagnostic(ErrorCode.WRN_UnreachableCode, "break").WithLocation(12, 17)
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion6, "_").WithArguments("tuples", "7.0").WithLocation(21, 15)
);
}
......
......@@ -11,9 +11,8 @@
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
public class CodeGenOperatorTests : CSharpTestBase
public class CodeGenOperators : CSharpTestBase
{
[Fact]
public void TestIsNullPattern()
{
......@@ -258,40 +257,34 @@ public static void Main()
// Release
var compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.ReleaseExe);
compilation.VerifyIL("C.Main", @"{
// Code size 20 (0x14)
.maxstack 2
IL_0000: ldnull
IL_0001: ldnull
IL_0002: ceq
IL_0004: call ""void System.Console.Write(bool)""
IL_0009: ldstr "" Branch taken""
IL_000e: call ""void System.Console.Write(string)""
IL_0013: ret
// Code size 17 (0x11)
.maxstack 1
IL_0000: ldc.i4.1
IL_0001: call ""void System.Console.Write(bool)""
IL_0006: ldstr "" Branch taken""
IL_000b: call ""void System.Console.Write(string)""
IL_0010: ret
}");
// Debug
compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.DebugExe);
compilation.VerifyIL("C.Main", @"{
// Code size 33 (0x21)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: ldnull
IL_0003: ceq
IL_0005: call ""void System.Console.Write(bool)""
IL_000a: nop
IL_000b: ldnull
IL_000c: ldnull
IL_000d: ceq
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: brfalse.s IL_0020
IL_0013: nop
IL_0014: ldstr "" Branch taken""
IL_0019: call ""void System.Console.Write(string)""
IL_001e: nop
IL_001f: nop
IL_0020: ret
// Code size 27 (0x1b)
.maxstack 1
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: call ""void System.Console.Write(bool)""
IL_0007: nop
IL_0008: ldc.i4.1
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_001a
IL_000d: nop
IL_000e: ldstr "" Branch taken""
IL_0013: call ""void System.Console.Write(string)""
IL_0018: nop
IL_0019: nop
IL_001a: ret
}");
}
......@@ -316,40 +309,34 @@ public static void Main()
// Release
var compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.ReleaseExe);
compilation.VerifyIL("C.Main", @"{
// Code size 20 (0x14)
.maxstack 2
IL_0000: ldnull
IL_0001: ldnull
IL_0002: ceq
IL_0004: call ""void System.Console.Write(bool)""
IL_0009: ldstr "" Branch taken""
IL_000e: call ""void System.Console.Write(string)""
IL_0013: ret
// Code size 17 (0x11)
.maxstack 1
IL_0000: ldc.i4.1
IL_0001: call ""void System.Console.Write(bool)""
IL_0006: ldstr "" Branch taken""
IL_000b: call ""void System.Console.Write(string)""
IL_0010: ret
}");
// Debug
compilation = CompileAndVerify(source, expectedOutput: "True Branch taken", options: TestOptions.DebugExe);
compilation.VerifyIL("C.Main", @"{
// Code size 33 (0x21)
.maxstack 2
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: ldnull
IL_0003: ceq
IL_0005: call ""void System.Console.Write(bool)""
IL_000a: nop
IL_000b: ldnull
IL_000c: ldnull
IL_000d: ceq
IL_000f: stloc.0
IL_0010: ldloc.0
IL_0011: brfalse.s IL_0020
IL_0013: nop
IL_0014: ldstr "" Branch taken""
IL_0019: call ""void System.Console.Write(string)""
IL_001e: nop
IL_001f: nop
IL_0020: ret
// Code size 27 (0x1b)
.maxstack 1
.locals init (bool V_0)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: call ""void System.Console.Write(bool)""
IL_0007: nop
IL_0008: ldc.i4.1
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: brfalse.s IL_001a
IL_000d: nop
IL_000e: ldstr "" Branch taken""
IL_0013: call ""void System.Console.Write(string)""
IL_0018: nop
IL_0019: nop
IL_001a: ret
}");
}
......
......@@ -97,9 +97,12 @@ static void M1(int? x)
{
case int i: break;
}").WithArguments("System.Nullable`1", "GetValueOrDefault").WithLocation(12, 9),
// (17,36): error CS0656: Missing compiler required member 'System.Nullable`1.GetValueOrDefault'
// (17,36): error CS0656: Missing compiler required member 'System.Nullable`1.get_HasValue'
// static bool M2(int? x) => x is int i;
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "get_HasValue").WithLocation(17, 36),
// (17,36): error CS0656: Missing compiler required member 'System.Nullable`1.get_Value'
// static bool M2(int? x) => x is int i;
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "GetValueOrDefault").WithLocation(17, 36)
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "get_Value").WithLocation(17, 36)
);
}
......@@ -138,11 +141,14 @@ static void M1(int? x)
}").WithArguments("System.Nullable`1", "get_HasValue").WithLocation(12, 9),
// (17,36): error CS0656: Missing compiler required member 'System.Nullable`1.get_HasValue'
// static bool M2(int? x) => x is int i;
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "get_HasValue").WithLocation(17, 36)
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "get_HasValue").WithLocation(17, 36),
// (17,36): error CS0656: Missing compiler required member 'System.Nullable`1.get_Value'
// static bool M2(int? x) => x is int i;
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "int i").WithArguments("System.Nullable`1", "get_Value").WithLocation(17, 36)
);
}
[Fact, WorkItem(17266, "https://github.com/dotnet/roslyn/issues/17266")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(17266, "https://github.com/dotnet/roslyn/issues/17266")]
public void DoubleEvaluation01()
{
var source =
......@@ -194,7 +200,7 @@ .maxstack 1
}");
}
[Fact, WorkItem(19122, "https://github.com/dotnet/roslyn/issues/19122")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(19122, "https://github.com/dotnet/roslyn/issues/19122")]
public void PatternCrash_01()
{
var source = @"using System;
......
......@@ -8370,7 +8370,7 @@ .maxstack 2
);
}
[Fact, WorkItem(18859, "https://github.com/dotnet/roslyn/issues/18859")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(18859, "https://github.com/dotnet/roslyn/issues/18859")]
public void BoxInPatternIf_02()
{
var source = @"using System;
......@@ -8428,7 +8428,7 @@ .maxstack 1
);
}
[Fact, WorkItem(16195, "https://github.com/dotnet/roslyn/issues/16195")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(16195, "https://github.com/dotnet/roslyn/issues/16195")]
public void TestMatchWithTypeParameter_01()
{
var source =
......@@ -8631,7 +8631,7 @@ .maxstack 2
);
}
[Fact, WorkItem(16195, "https://github.com/dotnet/roslyn/issues/16195")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(16195, "https://github.com/dotnet/roslyn/issues/16195")]
public void TestMatchWithTypeParameter_02()
{
var source =
......@@ -9250,7 +9250,7 @@ .maxstack 2
);
}
[Fact, WorkItem(16129, "https://github.com/dotnet/roslyn/issues/16129")]
[Fact(Skip = "PROTOTYPE(patterns2): code quality"), WorkItem(16129, "https://github.com/dotnet/roslyn/issues/16129")]
public void ExactPatternMatch()
{
var source =
......
......@@ -7186,7 +7186,7 @@ static void M()
#region Patterns
[Fact]
[Fact(Skip = "PROTOTYPE(patterns2): pdb details")]
public void SyntaxOffset_Pattern()
{
var source = @"class C { bool F(object o) => o is int i && o is 3 && o is bool; }";
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册