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

Draft implementation for list patterns #49080

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
204 changes: 106 additions & 98 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;

Expand Down Expand Up @@ -7854,19 +7855,53 @@ private bool TryBindIndexOrRangeIndexer(
var argument = arguments.Arguments[0];

var argType = argument.Type;
bool argIsIndex = TypeSymbol.Equals(argType,
Compilation.GetWellKnownType(WellKnownType.System_Index),
TypeCompareKind.ConsiderEverything);
bool argIsRange = !argIsIndex && TypeSymbol.Equals(argType,
Compilation.GetWellKnownType(WellKnownType.System_Range),
TypeCompareKind.ConsiderEverything);

if ((!argIsIndex && !argIsRange) ||
ThreeState argIsIndexNotRange =
TypeSymbol.Equals(argType, Compilation.GetWellKnownType(WellKnownType.System_Index), TypeCompareKind.ConsiderEverything) ? ThreeState.True :
TypeSymbol.Equals(argType, Compilation.GetWellKnownType(WellKnownType.System_Range), TypeCompareKind.ConsiderEverything) ? ThreeState.False :
ThreeState.Unknown;

if (!argIsIndexNotRange.HasValue() ||
!(receiverOpt?.Type is TypeSymbol receiverType))
{
return false;
}

if (!TryFindIndexOrRangeIndexerPattern(syntax, receiverOpt, receiverType, argIsIndex: argIsIndexNotRange.Value(),
out var lengthOrCountProperty, out var patternSymbol, out var returnType, diagnostics))
{
return false;
}

patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess(
syntax,
receiverOpt,
lengthOrCountProperty,
patternSymbol,
BindToNaturalType(argument, diagnostics),
returnType);

_ = MessageID.IDS_FeatureIndexOperator.CheckFeatureAvailability(diagnostics, syntax);
if (arguments.Names.Count > 0)
{
diagnostics.Add(
!argIsIndexNotRange.Value()
? ErrorCode.ERR_ImplicitRangeIndexerWithName
: ErrorCode.ERR_ImplicitIndexIndexerWithName,
arguments.Names[0].GetLocation());
}
return true;
}

#nullable enable
private bool TryFindIndexOrRangeIndexerPattern(
SyntaxNode syntax,
BoundExpression? receiverOpt,
TypeSymbol receiverType, bool argIsIndex,
[NotNullWhen(true)] out PropertySymbol? lengthOrCountProperty,
[NotNullWhen(true)] out Symbol? patternSymbol,
[NotNullWhen(true)] out TypeSymbol? returnType,
DiagnosticBag diagnostics)
{
// SPEC:

// An indexer invocation with a single argument of System.Index or System.Range will
Expand All @@ -7877,25 +7912,21 @@ private bool TryBindIndexOrRangeIndexer(
// 2. For Index: Has an accessible indexer with a single int parameter
// For Range: Has an accessible Slice method that takes two int parameters

PropertySymbol lengthOrCountProperty;

var lookupResult = LookupResult.GetInstance();
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
HashSet<DiagnosticInfo>? useSiteDiagnostics = null;

// Look for Length first

if (!tryLookupLengthOrCount(WellKnownMemberNames.LengthPropertyName, out lengthOrCountProperty) &&
!tryLookupLengthOrCount(WellKnownMemberNames.CountPropertyName, out lengthOrCountProperty))
if (!TryLookupLengthOrCount(WellKnownMemberNames.LengthPropertyName, receiverType, lookupResult, out lengthOrCountProperty, ref useSiteDiagnostics) &&
!TryLookupLengthOrCount(WellKnownMemberNames.CountPropertyName, receiverType, lookupResult, out lengthOrCountProperty, ref useSiteDiagnostics))
{
patternSymbol = null;
returnType = null;
return false;
}

Debug.Assert(lengthOrCountProperty is { });

if (argIsIndex)
{
// Look for `T this[int i]` indexer

LookupMembersInType(
lookupResult,
receiverType,
Expand All @@ -7917,43 +7948,32 @@ candidate is PropertySymbol property &&
property.OriginalDefinition is { ParameterCount: 1 } original &&
isIntNotByRef(original.Parameters[0]))
{
patternSymbol = property;
returnType = property.Type;
CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics);
// note: implicit copy check on the indexer accessor happens in CheckPropertyValueKind
patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess(
syntax,
receiverOpt,
lengthOrCountProperty,
property,
BindToNaturalType(argument, diagnostics),
property.Type);
break;
checkWellKnown(WellKnownMember.System_Index__GetOffset);
return true;
}
}
}
}
else if (receiverType.SpecialType == SpecialType.System_String)
{
Debug.Assert(argIsRange);
Debug.Assert(!argIsIndex);
// Look for Substring
var substring = (MethodSymbol)Compilation.GetSpecialTypeMember(SpecialMember.System_String__Substring);
if (substring is object)
{
patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess(
syntax,
receiverOpt,
lengthOrCountProperty,
substring,
BindToNaturalType(argument, diagnostics),
substring.ReturnType);
checkWellKnown(WellKnownMember.System_Range__get_Start);
checkWellKnown(WellKnownMember.System_Range__get_End);
patternSymbol = substring;
returnType = substring.ReturnType;
checkWellKnownForRange();
return true;
}
}
else
{
Debug.Assert(argIsRange);
Debug.Assert(!argIsIndex);
// Look for `T Slice(int, int)` indexer

LookupMembersInType(
lookupResult,
receiverType,
Expand All @@ -7977,50 +7997,35 @@ method.OriginalDefinition is var original &&
isIntNotByRef(original.Parameters[0]) &&
isIntNotByRef(original.Parameters[1]))
{
patternSymbol = method;
returnType = method.ReturnType;
CheckImplicitThisCopyInReadOnlyMember(receiverOpt, lengthOrCountProperty.GetMethod, diagnostics);
CheckImplicitThisCopyInReadOnlyMember(receiverOpt, method, diagnostics);
patternIndexerAccess = new BoundIndexOrRangePatternIndexerAccess(
syntax,
receiverOpt,
lengthOrCountProperty,
method,
BindToNaturalType(argument, diagnostics),
method.ReturnType);
checkWellKnown(WellKnownMember.System_Range__get_Start);
checkWellKnown(WellKnownMember.System_Range__get_End);
break;
checkWellKnownForRange();
return true;
}
}
}
}

cleanup(lookupResult, ref useSiteDiagnostics);
if (patternIndexerAccess is null)
{
return false;
}

_ = MessageID.IDS_FeatureIndexOperator.CheckFeatureAvailability(diagnostics, syntax);
checkWellKnown(WellKnownMember.System_Index__GetOffset);
if (arguments.Names.Count > 0)
{
diagnostics.Add(
argIsRange
? ErrorCode.ERR_ImplicitRangeIndexerWithName
: ErrorCode.ERR_ImplicitIndexIndexerWithName,
arguments.Names[0].GetLocation());
}
return true;

static void cleanup(LookupResult lookupResult, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
lookupResult.Free();
useSiteDiagnostics = null;
}
lookupResult.Free();
useSiteDiagnostics = null;
patternSymbol = null;
returnType = null;
return false;

#pragma warning disable CS8762 // Parameter may not have a null value when exiting in some condition.
static bool isIntNotByRef(ParameterSymbol param)
=> param.Type.SpecialType == SpecialType.System_Int32 &&
param.RefKind == RefKind.None;
#pragma warning restore CS8762 // Parameter may not have a null value when exiting in some condition.

void checkWellKnownForRange()
{
checkWellKnown(WellKnownMember.System_Range__get_Start);
checkWellKnown(WellKnownMember.System_Range__get_End);
checkWellKnown(WellKnownMember.System_Index__GetOffset);
}

void checkWellKnown(WellKnownMember member)
{
Expand All @@ -8029,39 +8034,42 @@ void checkWellKnown(WellKnownMember member)
// the user from getting surprising errors when optimizations fail
_ = GetWellKnownTypeMember(Compilation, member, diagnostics, syntax: syntax);
}
}

private bool TryLookupLengthOrCount(
string propertyName, TypeSymbol receiverType, LookupResult lookupResult,
[NotNullWhen(true)] out PropertySymbol? valid, ref HashSet<DiagnosticInfo>? useSiteDiagnostics)
{
LookupMembersInType(
lookupResult,
receiverType,
propertyName,
arity: 0,
basesBeingResolved: null,
LookupOptions.Default,
originalBinder: this,
diagnose: false,
useSiteDiagnostics: ref useSiteDiagnostics);

bool tryLookupLengthOrCount(string propertyName, out PropertySymbol valid)
if (lookupResult.IsSingleViable &&
lookupResult.Symbols[0] is PropertySymbol property &&
property.GetOwnOrInheritedGetMethod()?.OriginalDefinition is MethodSymbol getMethod &&
getMethod.ReturnType.SpecialType == SpecialType.System_Int32 &&
getMethod.RefKind == RefKind.None &&
!getMethod.IsStatic &&
IsAccessible(getMethod, ref useSiteDiagnostics))
{
LookupMembersInType(
lookupResult,
receiverType,
propertyName,
arity: 0,
basesBeingResolved: null,
LookupOptions.Default,
originalBinder: this,
diagnose: false,
useSiteDiagnostics: ref useSiteDiagnostics);

if (lookupResult.IsSingleViable &&
lookupResult.Symbols[0] is PropertySymbol property &&
property.GetOwnOrInheritedGetMethod()?.OriginalDefinition is MethodSymbol getMethod &&
getMethod.ReturnType.SpecialType == SpecialType.System_Int32 &&
getMethod.RefKind == RefKind.None &&
!getMethod.IsStatic &&
IsAccessible(getMethod, ref useSiteDiagnostics))
{
lookupResult.Clear();
useSiteDiagnostics = null;
valid = property;
return true;
}
lookupResult.Clear();
useSiteDiagnostics = null;
valid = null;
return false;
valid = property;
return true;
}
lookupResult.Clear();
useSiteDiagnostics = null;
valid = null;
return false;
}
#nullable restore

private ErrorPropertySymbol CreateErrorPropertySymbol(ImmutableArray<PropertySymbol> propertyGroup)
{
Expand Down
Loading