Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement extended property patterns #52139

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
75bf6e7
WIP
alrz Mar 25, 2021
9f51bbb
WIP
alrz Mar 25, 2021
1e67ee2
WIP
alrz Mar 25, 2021
b4019e1
WIP
alrz Mar 25, 2021
d61c3c8
WIP
alrz Mar 25, 2021
5593114
WIP
alrz Mar 25, 2021
b384f78
WIP
alrz Mar 25, 2021
808ce1f
WIP
alrz Mar 25, 2021
675a57d
WIP
alrz Mar 25, 2021
a4cebf9
WIP
alrz Mar 25, 2021
bc7efe6
WIP
alrz Apr 7, 2021
c620d01
Update resources
alrz Apr 7, 2021
daea3f1
WIP
alrz Apr 7, 2021
411a6b5
Merge remote-tracking branch 'origin/features/extended-property-patte…
alrz Apr 7, 2021
4bc2dac
Update resources
alrz Apr 7, 2021
be4e20a
Address feedback on syntax tree
alrz Apr 25, 2021
49e151e
Revert
alrz Apr 25, 2021
6e5b186
Merge remote-tracking branch 'origin/features/extended-property-patte…
alrz Apr 25, 2021
4d0a51d
Address feedback
alrz Apr 27, 2021
371c4f4
Typo
alrz Apr 27, 2021
8e82fe7
Revert code
alrz Apr 29, 2021
13eb678
Remove ERR_ConditionalAccessInSubpattern
alrz Apr 29, 2021
359681f
Add PROTOTYPE comments
alrz Apr 29, 2021
8996089
Add suggested test
alrz Apr 29, 2021
7c487de
Add backcompat APIs
alrz Apr 29, 2021
81037c3
Typo
alrz Apr 29, 2021
eaa5c5d
Attempt to disable RS0016
alrz Apr 29, 2021
ffcb24e
Renamings
alrz Apr 29, 2021
6d5f488
Fix typo
alrz Apr 29, 2021
ae153fd
Add missing PROTOTYPE comment
alrz Apr 29, 2021
732486c
Create ExpressionColon when a NameColon.WithName is called with an ex…
alrz Apr 29, 2021
7d7bd53
Pass IOp verification
alrz Apr 29, 2021
11f1ea8
Add missing member
alrz Apr 29, 2021
a6ba2fe
Attempt to fix build
alrz Apr 29, 2021
35ea5b3
Typo
alrz May 4, 2021
b6b7bd3
Address feedback on syntax
alrz May 4, 2021
6f518d7
Add more parsing tests
alrz May 4, 2021
d0e80c4
Add suggested tests
alrz May 4, 2021
3a83ba0
Fix build
alrz May 4, 2021
4897191
Add test
alrz May 4, 2021
d3f1e7f
Move langversion check to the parser
alrz May 4, 2021
05f9a88
Add suggested tests
alrz May 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
alrz marked this conversation as resolved.
Show resolved Hide resolved
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