Skip to content

Commit

Permalink
Implement extended property patterns (#52139)
Browse files Browse the repository at this point in the history
  • Loading branch information
alrz committed May 5, 2021
1 parent 17761cf commit f24acfe
Show file tree
Hide file tree
Showing 43 changed files with 2,027 additions and 125 deletions.
3 changes: 2 additions & 1 deletion eng/targets/Settings.props
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@
and hence suppress this warning until we get closer to release and a more
thorough documentation story
-->
<NoWarn>$(NoWarn);1573;1591;1701</NoWarn>
<!-- PROTOTYPE(extended-property-patterns) PublicAPIs -->
<NoWarn>$(NoWarn);1573;1591;1701;RS0016</NoWarn>
</PropertyGroup>
<PropertyGroup>
<DefineConstants Condition="'$(InitialDefineConstants)' != ''">$(DefineConstants);$(InitialDefineConstants)</DefineConstants>
Expand Down
39 changes: 30 additions & 9 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ deconstructMethod is null &&
bool isError = hasErrors || outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length;
TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type;
ParameterSymbol? parameter = null;
// PROTOTYPE(extended-property-patterns) ExpressionColon
if (subPattern.NameColon != null && !isError)
{
// Check that the given name is the same as the corresponding parameter of the method.
Expand Down Expand Up @@ -818,6 +819,7 @@ private void BindITupleSubpatterns(
var objectType = Compilation.GetSpecialType(SpecialType.System_Object);
foreach (var subpatternSyntax in node.Subpatterns)
{
// PROTOTYPE(extended-property-patterns) ExpressionColon
if (subpatternSyntax.NameColon != null)
{
// error: name not permitted in ITuple deconstruction
Expand Down Expand Up @@ -874,6 +876,7 @@ private void BindValueTupleSubpatterns(
bool isError = i >= elementTypesWithAnnotations.Length;
TypeSymbol elementType = isError ? CreateErrorType() : elementTypesWithAnnotations[i].Type;
FieldSymbol? foundField = null;
// PROTOTYPE(extended-property-patterns) ExpressionColon
if (subpatternSyntax.NameColon != null && !isError)
{
string name = subpatternSyntax.NameColon.Name.Identifier.ValueText;
Expand Down Expand Up @@ -1163,34 +1166,52 @@ private ImmutableArray<BoundSubpattern> BindPropertyPatternClause(
var builder = ArrayBuilder<BoundSubpattern>.GetInstance(node.Subpatterns.Count);
foreach (SubpatternSyntax p in node.Subpatterns)
{
IdentifierNameSyntax? name = p.NameColon?.Name;
ExpressionSyntax? expr = p.ExpressionColon?.Expression;
PatternSyntax pattern = p.Pattern;
Symbol? member = null;
ImmutableArray<Symbol> members;
TypeSymbol memberType;
if (name == null)
if (expr == null)
{
if (!hasErrors)
diagnostics.Add(ErrorCode.ERR_PropertyPatternNameMissing, pattern.Location, pattern);

memberType = CreateErrorType();
members = ImmutableArray<Symbol>.Empty;
hasErrors = true;
}
else
{
member = LookupMemberForPropertyPattern(inputType, name, diagnostics, ref hasErrors, out memberType);
var memberBuilder = ArrayBuilder<Symbol>.GetInstance();
LookupMembersForPropertyPattern(inputType, expr, memberBuilder, diagnostics, ref hasErrors, out memberType);
members = memberBuilder.ToImmutableAndFree();
}

BoundPattern boundPattern = BindPattern(pattern, memberType, GetValEscape(memberType, inputValEscape), permitDesignations, hasErrors, diagnostics);
builder.Add(new BoundSubpattern(p, member, boundPattern));
builder.Add(new BoundSubpattern(p, members, boundPattern));
}

return builder.ToImmutableAndFree();
}

private Symbol? LookupMemberForPropertyPattern(
TypeSymbol inputType, IdentifierNameSyntax name, BindingDiagnosticBag diagnostics, ref bool hasErrors, out TypeSymbol memberType)
private void LookupMembersForPropertyPattern(
TypeSymbol inputType, ExpressionSyntax expr, ArrayBuilder<Symbol> builder, BindingDiagnosticBag diagnostics, ref bool hasErrors, out TypeSymbol memberType)
{
Symbol? symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics);
Symbol? symbol;
switch (expr)
{
case IdentifierNameSyntax name:
symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics);
break;
case MemberAccessExpressionSyntax { Name: IdentifierNameSyntax name } memberAccess when memberAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression):
LookupMembersForPropertyPattern(inputType, memberAccess.Expression, builder, diagnostics, ref hasErrors, out memberType);
symbol = BindPropertyPatternMember(memberType.StrippedType(), name, ref hasErrors, diagnostics);
break;
default:
Error(diagnostics, ErrorCode.ERR_InvalidNameInSubpattern, expr);
symbol = null;
hasErrors = true;
break;
}

memberType = symbol switch
{
Expand All @@ -1199,7 +1220,7 @@ private ImmutableArray<BoundSubpattern> BindPropertyPatternClause(
_ => CreateErrorType()
};

return symbol;
builder.AddIfNotNull(symbol);
}

private Symbol? BindPropertyPatternMember(
Expand Down
59 changes: 41 additions & 18 deletions src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -527,29 +527,46 @@ private Tests MakeTestsAndBindingsForRecursivePattern(
if (!recursive.Properties.IsDefault)
{
// we have a "property" form
for (int i = 0; i < recursive.Properties.Length; i++)
foreach (var subpattern in recursive.Properties)
{
var subPattern = recursive.Properties[i];
Symbol? symbol = subPattern.Symbol;
BoundPattern pattern = subPattern.Pattern;
BoundDagEvaluation evaluation;
switch (symbol)
BoundPattern pattern = subpattern.Pattern;
BoundDagTemp currentInput = input;
if (subpattern.Symbols.IsEmpty)
{
case PropertySymbol property:
evaluation = new BoundDagPropertyEvaluation(pattern.Syntax, property, OriginalInput(input, property));
break;
case FieldSymbol field:
evaluation = new BoundDagFieldEvaluation(pattern.Syntax, field, OriginalInput(input, field));
addErrorTest();
goto done;
}

for (int index = 0, count = subpattern.Symbols.Length; ;)
{
Symbol symbol = subpattern.Symbols[index];
BoundDagEvaluation evaluation;
switch (symbol)
{
case PropertySymbol property:
evaluation = new BoundDagPropertyEvaluation(pattern.Syntax, property, OriginalInput(currentInput, property));
break;
case FieldSymbol field:
evaluation = new BoundDagFieldEvaluation(pattern.Syntax, field, OriginalInput(currentInput, field));
break;
default:
addErrorTest();
goto done;
}

tests.Add(new Tests.One(evaluation));
TypeSymbol type = symbol.GetTypeOrReturnType().Type;
currentInput = new BoundDagTemp(pattern.Syntax, type, evaluation);

if (++index == count)
break;
default:
RoslynDebug.Assert(recursive.HasAnyErrors);
tests.Add(new Tests.One(new BoundDagTypeTest(recursive.Syntax, ErrorType(), input, hasErrors: true)));
continue;

// If this is not the last member, add null test, unwrap nullables, and continue.
currentInput = MakeConvertToType(currentInput, pattern.Syntax, type.StrippedType(), isExplicitTest: false, tests);
}

tests.Add(new Tests.One(evaluation));
var element = new BoundDagTemp(pattern.Syntax, symbol.GetTypeOrReturnType().Type, evaluation);
tests.Add(MakeTestsAndBindings(element, pattern, bindings));
done:
tests.Add(MakeTestsAndBindings(currentInput, pattern, bindings));
}
}

Expand All @@ -560,6 +577,12 @@ private Tests MakeTestsAndBindingsForRecursivePattern(
}

return Tests.AndSequence.Create(tests);

void addErrorTest()
{
Debug.Assert(recursive.HasAnyErrors);
tests.Add(new Tests.One(new BoundDagTypeTest(recursive.Syntax, ErrorType(), input, hasErrors: true)));
}
}

private Tests MakeTestsAndBindingsForNegatedPattern(BoundDagTemp input, BoundNegatedPattern neg, ArrayBuilder<BoundPatternBinding> bindings)
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2092,7 +2092,7 @@
<!-- A subpattern, either in a positional or property part of a recursive pattern. -->
<Node Name="BoundSubpattern" Base="BoundNode">
<!-- The tuple element or parameter in a positional pattern, or the property or field in a property pattern. -->
<Field Name="Symbol" Type="Symbol?"/>
<Field Name="Symbols" Type="ImmutableArray&lt;Symbol&gt;"/>
<Field Name="Pattern" Type="BoundPattern" Null="disallow"/>
</Node>

Expand Down
32 changes: 32 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundSubpattern.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Collections.Immutable;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
// PROTOTYPE(extended-property-patterns) Split BoundPropertySubpattern and remove this (requires IOperation changes)
partial class BoundSubpattern
{
internal BoundSubpattern(SyntaxNode syntax, Symbol? symbol, BoundPattern pattern, bool hasErrors = false)
: this(syntax, symbol is null ? ImmutableArray<Symbol>.Empty : ImmutableArray.Create(symbol), pattern, hasErrors)
{
}

internal Symbol? Symbol
{
get
{
if (this.Symbols.IsEmpty)
return null;
if (this.Symbols.Length == 1)
return this.Symbols[0];
return null;
}
}
}
}
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6609,4 +6609,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_HiddenPositionalMember" xml:space="preserve">
<value>The positional member '{0}' found corresponding to this parameter is hidden.</value>
</data>
<data name="ERR_InvalidNameInSubpattern" xml:space="preserve">
<value>Identifier or a simple member access expected.</value>
</data>
<data name="IDS_FeatureExtendedPropertyPatterns" xml:space="preserve">
<value>extended property patterns</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1934,6 +1934,9 @@ internal enum ErrorCode

#region diagnostics introduced for C# 10.0

// PROTOTYPE(extended-property-patterns) Temp values
ERR_InvalidNameInSubpattern = 9000,

ERR_InheritingFromRecordWithSealedToString = 8912,
ERR_HiddenPositionalMember = 8913,

Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/MessageID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ internal enum MessageID

IDS_FeatureDiscards = MessageBase + 12688,

// PROTOTYPE(extended-property-patterns) Move
IDS_FeatureExtendedPropertyPatterns = MessageBase + 12800,

IDS_FeatureDefaultTypeParameterConstraint = MessageBase + 12689,
IDS_FeatureNullPropagatingOperator = MessageBase + 12690,
IDS_FeatureExpressionBodiedMethod = MessageBase + 12691,
Expand Down Expand Up @@ -485,6 +488,10 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
case MessageID.IDS_FeatureSwitchOnBool: // Checked in the binder.
return LanguageVersion.CSharp2;

// PROTOTYPE(extended-property-patterns) Move
case MessageID.IDS_FeatureExtendedPropertyPatterns:
return LanguageVersion.Preview;

// Special C# 2 feature: only a warning in C# 1.
case MessageID.IDS_FeatureModuleAttrLoc:
return LanguageVersion.CSharp1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,17 @@ private void LearnFromAnyNullPatterns(
// for property part
if (!rp.Properties.IsDefault)
{
for (int i = 0, n = rp.Properties.Length; i < n; i++)
foreach (BoundSubpattern subpattern in rp.Properties)
{
BoundSubpattern item = rp.Properties[i];
Symbol symbol = item.Symbol;
if (symbol?.ContainingType.Equals(inputType, TypeCompareKind.AllIgnoreOptions) == true)
if (subpattern.Symbols.IsEmpty)
continue;

// Obtain the last symbol's slot which is actually the one being matched.
Symbol last = subpattern.Symbols.Last();
// PROTOTYPE(extended-property-patterns) For a chain of members, the input type is no longer `inputType`
if (last.ContainingType.Equals(inputType, TypeCompareKind.AllIgnoreOptions))
{
LearnFromAnyNullPatterns(GetOrCreateSlot(symbol, inputSlot), symbol.GetTypeOrReturnType().Type, item.Pattern);
LearnFromAnyNullPatterns(GetOrCreateSlot(last, inputSlot), last.GetTypeOrReturnType().Type, subpattern.Pattern);
}
}
}
Expand Down
21 changes: 11 additions & 10 deletions src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f24acfe

Please sign in to comment.