Skip to content

Commit

Permalink
Move pattern-related parsing into a separate source file.
Browse files Browse the repository at this point in the history
Implemented the match expression. dotnet#5154
Implemented the throw expression. dotnet#5143
  • Loading branch information
gafter committed Nov 1, 2015
1 parent 88d05d4 commit caa34b6
Show file tree
Hide file tree
Showing 37 changed files with 951 additions and 491 deletions.
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,12 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, Diagnostic
case SyntaxKind.IsPatternExpression:
return BindIsPatternExpression((IsPatternExpressionSyntax)node, diagnostics);

case SyntaxKind.MatchExpression:
return BindMatchExpression((MatchExpressionSyntax)node, diagnostics);

case SyntaxKind.ThrowExpression:
return BindThrowExpression((ThrowExpressionSyntax)node, diagnostics);

default:
// NOTE: We could probably throw an exception here, but it's conceivable
// that a non-parser syntax tree could reach this point with an unexpected
Expand Down
122 changes: 121 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ private BoundPattern BindConstantPattern(ConstantPatternSyntax node, BoundExpres

private bool CheckValidPatternType(CSharpSyntaxNode typeSyntax, BoundExpression operand, TypeSymbol operandType, TypeSymbol patternType, bool isVar, DiagnosticBag diagnostics)
{
if (patternType.IsNullableType() && !isVar)
if (operandType?.IsErrorType() == true || patternType?.IsErrorType() == true)
{
return false;
}
else if (patternType.IsNullableType() && !isVar)
{
// It is an error to use pattern-matching with a nullable type, because you'll never get null. Use the underlying type.
Error(diagnostics, ErrorCode.ERR_PatternNullableType, typeSyntax, patternType, patternType.GetNullableUnderlyingType());
Expand Down Expand Up @@ -225,5 +229,121 @@ private bool CheckValidPatternType(CSharpSyntaxNode typeSyntax, BoundExpression
DeclareLocalVariable(localSymbol, identifier, declType);
return new BoundDeclarationPattern(node, localSymbol, boundDeclType, isVar, hasErrors);
}

private TypeSymbol BestType(MatchExpressionSyntax node, ArrayBuilder<BoundMatchCase> cases, DiagnosticBag diagnostics)
{
var types = ArrayBuilder<TypeSymbol>.GetInstance();

int n = cases.Count;
for (int i = 0; i < n; i++)
{
var e = cases[i].Expression;
if (e.Type != null && !types.Contains(e.Type)) types.Add(e.Type);
}

var allTypes = types.ToImmutableAndFree();

TypeSymbol bestType;
if (allTypes.IsDefaultOrEmpty)
{
diagnostics.Add(ErrorCode.ERR_AmbigMatch0, node.MatchToken.GetLocation());
bestType = CreateErrorType();
}
else if (allTypes.Length == 1)
{
bestType = allTypes[0];
}
else
{
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
bestType = BestTypeInferrer.InferBestType(
allTypes,
Conversions,
ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
if ((object)bestType == null)
{
diagnostics.Add(ErrorCode.ERR_AmbigMatch1, node.MatchToken.GetLocation());
bestType = CreateErrorType();
}
}

for (int i = 0; i < n; i++)
{
var c = cases[i];
var e = c.Expression;
var converted = GenerateConversionForAssignment(bestType, e, diagnostics);
if (e != converted)
{
cases[i] = new BoundMatchCase(c.Syntax, c.Locals, c.Pattern, c.Guard, converted);
}
}

return bestType;
}

private BoundExpression BindMatchExpression(MatchExpressionSyntax node, DiagnosticBag diagnostics)
{
var expression = BindValue(node.Left, diagnostics, BindValueKind.RValue);
// TODO: any constraints on a switch expression must be enforced here. For example,
// it must have a type (not be target-typed, lambda, null, etc)

var sectionBuilder = ArrayBuilder<BoundMatchCase>.GetInstance();
foreach (var section in node.Sections)
{
var sectionBinder = new PatternVariableBinder(section, this); // each section has its own locals.
var pattern = sectionBinder.BindPattern(section.Pattern, expression, expression.Type, section.HasErrors, diagnostics);
var guard = (section.Condition != null) ? sectionBinder.BindBooleanExpression(section.Condition, diagnostics) : null;
var e = sectionBinder.BindExpression(section.Expression, diagnostics);
sectionBuilder.Add(new BoundMatchCase(section, sectionBinder.Locals, pattern, guard, e, section.HasErrors));
}

var resultType = BestType(node, sectionBuilder, diagnostics);
return new BoundMatchExpression(node, expression, sectionBuilder.ToImmutableAndFree(), resultType);
}

private BoundExpression BindThrowExpression(ThrowExpressionSyntax node, DiagnosticBag diagnostics)
{
bool hasErrors = false;
if (node.Parent != null && !node.HasErrors)
{
switch (node.Parent.Kind())
{
case SyntaxKind.ConditionalExpression:
{
var papa = (ConditionalExpressionSyntax)node.Parent;
if (node == papa.WhenTrue || node == papa.WhenFalse) goto syntaxOk;
break;
}
case SyntaxKind.CoalesceExpression:
{
var papa = (BinaryExpressionSyntax)node.Parent;
if (node == papa.Right) goto syntaxOk;
break;
}
case SyntaxKind.MatchSection:
{
var papa = (MatchSectionSyntax)node.Parent;
if (node == papa.Expression) goto syntaxOk;
break;
}
case SyntaxKind.ArrowExpressionClause:
{
var papa = (ArrowExpressionClauseSyntax)node.Parent;
if (node == papa.Expression) goto syntaxOk;
break;
}
default:
break;
}

diagnostics.Add(ErrorCode.ERR_ThrowMisplaced, node.ThrowKeyword.GetLocation());
hasErrors = true;
syntaxOk:;
}

var thrownExpression = BindThrownExpression(node.Expression, diagnostics, ref hasErrors);
return new BoundThrowExpression(node, thrownExpression, null, hasErrors);
}
}
}
53 changes: 30 additions & 23 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,37 +277,44 @@ internal BoundStatement BindPossibleEmbeddedStatement(StatementSyntax node, Diag
return BindStatement(node, diagnostics);
}

private BoundThrowStatement BindThrow(ThrowStatementSyntax node, DiagnosticBag diagnostics)
private BoundExpression BindThrownExpression(ExpressionSyntax exprSyntax, DiagnosticBag diagnostics, ref bool hasErrors)
{
BoundExpression boundExpr = null;
bool hasErrors = false;
var boundExpr = BindValue(exprSyntax, diagnostics, BindValueKind.RValue);

ExpressionSyntax exprSyntax = node.Expression;
if (exprSyntax != null)
// SPEC VIOLATION: The spec requires the thrown exception to have a type, and that the type
// be System.Exception or derived from System.Exception. (Or, if a type parameter, to have
// an effective base class that meets that criterion.) However, we allow the literal null
// to be thrown, even though it does not meet that criterion and will at runtime always
// produce a null reference exception.

if (!boundExpr.IsLiteralNull())
{
boundExpr = BindValue(exprSyntax, diagnostics, BindValueKind.RValue);
var type = boundExpr.Type;

// SPEC VIOLATION: The spec requires the thrown exception to have a type, and that the type
// be System.Exception or derived from System.Exception. (Or, if a type parameter, to have
// an effective base class that meets that criterion.) However, we allow the literal null
// to be thrown, even though it does not meet that criterion and will at runtime always
// produce a null reference exception.
// If the expression is a lambda, anonymous method, or method group then it will
// have no compile-time type; give the same error as if the type was wrong.
HashSet<DiagnosticInfo> useSiteDiagnostics = null;

if (!boundExpr.IsLiteralNull())
if ((object)type == null || !type.IsErrorType() && !Compilation.IsExceptionType(type.EffectiveType(ref useSiteDiagnostics), ref useSiteDiagnostics))
{
var type = boundExpr.Type;
diagnostics.Add(ErrorCode.ERR_BadExceptionType, exprSyntax.Location);
hasErrors = true;
diagnostics.Add(exprSyntax, useSiteDiagnostics);
}
}

// If the expression is a lambda, anonymous method, or method group then it will
// have no compile-time type; give the same error as if the type was wrong.
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
return boundExpr;
}

if ((object)type == null || !type.IsErrorType() && !Compilation.IsExceptionType(type.EffectiveType(ref useSiteDiagnostics), ref useSiteDiagnostics))
{
diagnostics.Add(ErrorCode.ERR_BadExceptionType, exprSyntax.Location);
hasErrors = true;
diagnostics.Add(exprSyntax, useSiteDiagnostics);
}
}
private BoundThrowStatement BindThrow(ThrowStatementSyntax node, DiagnosticBag diagnostics)
{
BoundExpression boundExpr = null;
bool hasErrors = false;

ExpressionSyntax exprSyntax = node.Expression;
if (exprSyntax != null)
{
boundExpr = BindThrownExpression(exprSyntax, diagnostics, ref hasErrors);
}
else if (!this.Flags.Includes(BinderFlags.InCatchBlock))
{
Expand Down
41 changes: 28 additions & 13 deletions src/Compilers/CSharp/Portable/Binder/PatternVariableBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed class PatternVariableBinder : LocalScopeBinder
{
private readonly ExpressionSyntax expression;
private readonly ImmutableArray<ExpressionSyntax> expressions;
private readonly ImmutableArray<PatternSyntax> patterns;
private readonly ExpressionSyntax Expression;
private readonly ImmutableArray<ExpressionSyntax> Expressions;
private readonly ImmutableArray<PatternSyntax> Patterns;
public readonly SyntaxNode Syntax;

internal PatternVariableBinder(SyntaxNode syntax, ImmutableArray<ExpressionSyntax> expressions, Binder next) : base(next)
{
this.Syntax = syntax;
this.expressions = expressions;
this.Expressions = expressions;
}

internal PatternVariableBinder(SyntaxNode syntax, IEnumerable<VariableDeclaratorSyntax> declarations, Binder next) : base(next)
Expand All @@ -31,7 +31,7 @@ internal PatternVariableBinder(SyntaxNode syntax, IEnumerable<VariableDeclarator
var value = decl.Initializer?.Value;
if (value != null) expressions.Add(value);
}
this.expressions = expressions.ToImmutableAndFree();
this.Expressions = expressions.ToImmutableAndFree();
}

internal PatternVariableBinder(SyntaxNode syntax, IEnumerable<ArgumentSyntax> arguments, Binder next) : base(next)
Expand All @@ -43,7 +43,7 @@ internal PatternVariableBinder(SyntaxNode syntax, IEnumerable<ArgumentSyntax> ar
var value = arg.Expression;
if (value != null) expressions.Add(value);
}
this.expressions = expressions.ToImmutableAndFree();
this.Expressions = expressions.ToImmutableAndFree();
}

internal PatternVariableBinder(SwitchSectionSyntax syntax, Binder next) : base(next)
Expand All @@ -53,7 +53,7 @@ internal PatternVariableBinder(SwitchSectionSyntax syntax, Binder next) : base(n
var patterns = ArrayBuilder<PatternSyntax>.GetInstance();
foreach (var label in syntax.Labels)
{
var match = label as CaseMatchLabelSyntax;
var match = label as CasePatternSwitchLabelSyntax;
if (match != null)
{
patterns.Add(match.Pattern);
Expand All @@ -64,8 +64,18 @@ internal PatternVariableBinder(SwitchSectionSyntax syntax, Binder next) : base(n
}
}

this.expressions = expressions.ToImmutableAndFree();
this.patterns = patterns.ToImmutableAndFree();
this.Expressions = expressions.ToImmutableAndFree();
this.Patterns = patterns.ToImmutableAndFree();
}

internal PatternVariableBinder(MatchSectionSyntax syntax, Binder next) : base(next)
{
this.Syntax = syntax;
this.Patterns = ImmutableArray.Create<PatternSyntax>(syntax.Pattern);
this.Expressions = syntax.Condition != null
? ImmutableArray.Create<ExpressionSyntax>(syntax.Expression, syntax.Condition)
: ImmutableArray.Create<ExpressionSyntax>(syntax.Expression)
;
}

internal PatternVariableBinder(ForStatementSyntax syntax, Binder next) : base(next)
Expand All @@ -77,21 +87,22 @@ internal PatternVariableBinder(ForStatementSyntax syntax, Binder next) : base(ne
var value = decl.Initializer?.Value;
if (value != null) expressions.Add(value);
}

if (syntax.Initializers != null) expressions.AddRange(syntax.Initializers);
if (syntax.Condition != null) expressions.Add(syntax.Condition);
if (syntax.Incrementors != null) expressions.AddRange(syntax.Incrementors);
this.expressions = expressions.ToImmutableAndFree();
this.Expressions = expressions.ToImmutableAndFree();
}

internal PatternVariableBinder(SyntaxNode syntax, ExpressionSyntax expression, Binder next) : base(next)
{
this.expression = expression;
this.Expression = expression;
this.Syntax = syntax;
}

protected override ImmutableArray<LocalSymbol> BuildLocals()
{
var patterns = PatternVariableFinder.FindPatternVariables(expression, expressions, this.patterns);
var patterns = PatternVariableFinder.FindPatternVariables(Expression, Expressions, this.Patterns);
var builder = ArrayBuilder<LocalSymbol>.GetInstance();
foreach (var pattern in patterns)
{
Expand Down Expand Up @@ -121,7 +132,7 @@ class PatternVariableFinder : CSharpSyntaxWalker
finder.Visit(expression);
if (!expressions.IsDefaultOrEmpty) foreach (var subExpression in expressions)
{
finder.Visit(subExpression);
if(subExpression != null) finder.Visit(subExpression);
}

var result = finder.declarationPatterns;
Expand All @@ -148,6 +159,10 @@ public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) { }
public override void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node) { }
public override void VisitQueryExpression(QueryExpressionSyntax node) { }
public override void VisitMatchExpression(MatchExpressionSyntax node)
{
Visit(node.Left);
}

#region pool
private static readonly ObjectPool<PatternVariableFinder> s_poolInstance = CreatePool();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public struct Conversion : IEquatable<Conversion>
internal static readonly Conversion ImplicitNullable = new Conversion(ConversionKind.ImplicitNullable);
internal static readonly Conversion ImplicitReference = new Conversion(ConversionKind.ImplicitReference);
internal static readonly Conversion ImplicitEnumeration = new Conversion(ConversionKind.ImplicitEnumeration);
internal static readonly Conversion ImplicitThrow = new Conversion(ConversionKind.ImplicitThrow);
internal static readonly Conversion AnonymousFunction = new Conversion(ConversionKind.AnonymousFunction);
internal static readonly Conversion Boxing = new Conversion(ConversionKind.Boxing);
internal static readonly Conversion NullLiteral = new Conversion(ConversionKind.NullLiteral);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ internal enum ConversionKind : byte
Identity,
ImplicitNumeric,
ImplicitEnumeration,
ImplicitThrow,
ImplicitNullable,
NullLiteral,
ImplicitReference,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind)
case ConversionKind.Identity:
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitEnumeration:
case ConversionKind.ImplicitThrow:
case ConversionKind.ImplicitNullable:
case ConversionKind.NullLiteral:
case ConversionKind.ImplicitReference:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi
return Conversion.ImplicitEnumeration;
}

if (HasImplicitThrowConversion(sourceExpression, destination))
{
return Conversion.ImplicitThrow;
}

var kind = ClassifyImplicitConstantExpressionConversion(sourceExpression, destination);
if (kind != ConversionKind.NoConversion)
{
Expand Down Expand Up @@ -175,6 +180,11 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi
return Conversion.NoConversion;
}

private bool HasImplicitThrowConversion(BoundExpression sourceExpression, TypeSymbol destination)
{
return sourceExpression.Kind == BoundKind.ThrowExpression;
}

public Conversion ClassifyImplicitConversionFromExpression(BoundExpression sourceExpression, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
Debug.Assert(sourceExpression != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@ private static bool IsEncompassingImplicitConversionKind(ConversionKind kind)
// Added to spec in Roslyn timeframe.
case ConversionKind.NullLiteral:
case ConversionKind.NullToPointer:

// Added for C# 7.
case ConversionKind.ImplicitThrow:
return true;

default:
Expand Down
Loading

0 comments on commit caa34b6

Please sign in to comment.