Skip to content

Commit

Permalink
Implements recursive pattern matching by
Browse files Browse the repository at this point in the history
reverse-engineering the relationship between positions and properties
by examining the names of constructor parameters.
See also #8415
  • Loading branch information
gafter committed Feb 23, 2016
1 parent e44c541 commit 936e40d
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 13 deletions.
112 changes: 110 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,116 @@ internal BoundPattern BindPattern(PatternSyntax node, BoundExpression operand, T

private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, BoundExpression operand, TypeSymbol operandType, bool hasErrors, DiagnosticBag diagnostics)
{
Error(diagnostics, ErrorCode.ERR_FeatureIsUnimplemented, node, "recursive pattern matching to a user-defined \"is\" operator");
return new BoundWildcardPattern(node, true);
var type = (NamedTypeSymbol)this.BindType(node.Type, diagnostics);
hasErrors = hasErrors || CheckValidPatternType(node.Type, operand, operandType, type, false, diagnostics);

// We intend that (positional) recursive pattern-matching should be defined in terms of
// a pattern of user-defined methods or operators. Tentatively, perhaps a method called
// GetValues that has an out parameter for each position of the recursive pattern. But
// for now we try to *infer* a positional pattern-matching operation from the presence of
// an accessible constructor.
var correspondingMembers = default(ImmutableArray<Symbol>);
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
var memberNames = type.MemberNames;
var correspondingMembersForCtor = ArrayBuilder<Symbol>.GetInstance();
foreach (var m in type.GetMembers(WellKnownMemberNames.InstanceConstructorName))
{
var ctor = m as MethodSymbol;
if (ctor?.ParameterCount != node.PatternList.SubPatterns.Count) continue;
if (!IsAccessible(ctor, useSiteDiagnostics: ref useSiteDiagnostics)) continue;
correspondingMembersForCtor.Clear();
foreach (var parameter in ctor.Parameters)
{
var name = CaseInsensitiveComparison.ToLower(parameter.Name);
Symbol correspondingMember = null;
foreach (var memberName in memberNames)
{
if (!name.Equals(CaseInsensitiveComparison.ToLower(memberName))) continue;
var candidate = LookupMatchableMemberInType(type, memberName, ref useSiteDiagnostics);
if (candidate?.IsStatic != false) continue;
if (candidate.Kind != SymbolKind.Property && candidate.Kind != SymbolKind.Field) continue;
if ((candidate as PropertySymbol)?.IsIndexedProperty == true) continue;
if (!IsAccessible(candidate, useSiteDiagnostics: ref useSiteDiagnostics)) continue;
if (correspondingMember != null)
{
// We have two candidates for this property. Cannot use the constructor.
goto tryAnotherConstructor;
}
correspondingMember = candidate;
}

if (correspondingMember == null) goto tryAnotherConstructor;
correspondingMembersForCtor.Add(correspondingMember);
}
Debug.Assert(correspondingMembersForCtor.Count == node.PatternList.SubPatterns.Count);
if (correspondingMembers.IsDefault)
{
correspondingMembers = correspondingMembersForCtor.ToImmutable();
}
else
{
if (!correspondingMembersForCtor.SequenceEqual(correspondingMembers, (s1, s2) => s1 == s2))
{
correspondingMembersForCtor.Free();
Error(diagnostics, ErrorCode.ERR_FeatureIsUnimplemented, node, "cannot infer a positional pattern from conflicting constructors");
diagnostics.Add(node, useSiteDiagnostics);
hasErrors = true;
return new BoundWildcardPattern(node, hasErrors);
}
}
tryAnotherConstructor:;
}

if (correspondingMembers == null)
{
Error(diagnostics, ErrorCode.ERR_FeatureIsUnimplemented, node, "cannot infer a positional pattern from any accessible constructor");
diagnostics.Add(node, useSiteDiagnostics);
correspondingMembersForCtor.Free();
hasErrors = true;
return new BoundWildcardPattern(node, hasErrors);
}

// Given that we infer a set of properties to match, we record the result as a BoundPropertyPattern.
// Once we translate recursive (positional) patterns into an invocation of GetValues, or "operator is", we'll
// use a dedicated bound node for that form.
var properties = correspondingMembers;
var boundPatterns = BindRecursiveSubPropertyPatterns(node, properties, type, diagnostics);
return new BoundPropertyPattern(node, type, boundPatterns, properties, hasErrors: hasErrors);
}

private ImmutableArray<BoundPattern> BindRecursiveSubPropertyPatterns(RecursivePatternSyntax node, ImmutableArray<Symbol> properties, NamedTypeSymbol type, DiagnosticBag diagnostics)
{
var boundPatternsBuilder = ArrayBuilder<BoundPattern>.GetInstance();
for (int i = 0; i < properties.Length; i++)
{
var syntax = node.PatternList.SubPatterns[i];
var property = properties[i];
var pattern = syntax.Pattern;
bool hasErrors = false;
Debug.Assert(!property.IsStatic);
var boundPattern = this.BindPattern(pattern, null, property.GetTypeOrReturnType(), hasErrors, diagnostics);
boundPatternsBuilder.Add(boundPattern);
}

return boundPatternsBuilder.ToImmutableAndFree();
}

private Symbol LookupMatchableMemberInType(TypeSymbol operandType, string name, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
var lookupResult = LookupResult.GetInstance();
this.LookupMembersInType(
lookupResult,
operandType,
name,
arity: 0,
basesBeingResolved: null,
options: LookupOptions.Default,
originalBinder: this,
diagnose: false,
useSiteDiagnostics: ref useSiteDiagnostics);
var result = lookupResult.SingleSymbolOrDefault;
lookupResult.Free();
return result;
}

private BoundPattern BindPropertyPattern(PropertyPatternSyntax node, BoundExpression operand, TypeSymbol operandType, bool hasErrors, DiagnosticBag diagnostics)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,6 @@ public Constant(int Value)
{
this.Value = Value;
}
//public static bool operator is(Constant self, out int Value)
//{
// Value = self.Value;
// return true;
//}
}
public class Plus : Expression
{
Expand All @@ -151,12 +146,6 @@ public Plus(Expression Left, Expression Right)
this.Left = Left;
this.Right = Right;
}
//public static bool operator is(Plus self, out Expression Left, out Expression Right)
//{
// Left = self.Left;
// Right = self.Right;
// return true;
//}
}
public class X
{
Expand Down Expand Up @@ -185,6 +174,57 @@ public static void Main()
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact]
public void InferredPositionalPatternTest()
{
var source =
@"using System;
public class Expression {}
public class Constant : Expression
{
public readonly int Value;
public Constant(int Value)
{
this.Value = Value;
}
}
public class Plus : Expression
{
public readonly Expression Left, Right;
public Plus(Expression Left, Expression Right)
{
this.Left = Left;
this.Right = Right;
}
}
public class X
{
public static void Main()
{
// ((1 + (2 + 3)) + 6)
Expression expr = new Plus(new Plus(new Constant(1), new Plus(new Constant(2), new Constant(3))), new Constant(6));
// The recursive form of this pattern would be
if (expr is Plus(Plus(Constant(int x1), Plus(Constant(int x2), Constant(int x3))), Constant(int x6)))
{
Console.WriteLine(""{0} {1} {2} {3}"", x1, x2, x3, x6);
}
else
{
Console.WriteLine(""wrong"");
}
Console.WriteLine(expr is Plus(Plus(Constant(1), Plus(Constant(2), Constant(3))), Constant(6)));
Console.WriteLine(expr is Plus(Plus(Constant(1), Plus(Constant(2), Constant(4))), Constant(6)));
}
}";
var expectedOutput =
@"1 2 3 6
True
False";
var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: patternParseOptions);
compilation.VerifyDiagnostics();
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact]
public void PatternErrors()
{
Expand Down

0 comments on commit 936e40d

Please sign in to comment.