Skip to content

Commit df67e1b

Browse files
tats-ukasperk81
authored andcommitted
Use single argument overload for [start..] of string or Span
1 parent 32b04d9 commit df67e1b

File tree

13 files changed

+593
-145
lines changed

13 files changed

+593
-145
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10540,7 +10540,7 @@ candidate is PropertySymbol property &&
1054010540
{
1054110541
Debug.Assert(!argIsIndex);
1054210542
// Look for Substring
10543-
var substring = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_String__Substring, diagnostics, syntax);
10543+
var substring = (MethodSymbol)GetSpecialTypeMember(SpecialMember.System_String__SubstringIntInt, diagnostics, syntax);
1054410544
if (substring is object)
1054510545
{
1054610546
makeCall(syntax, receiver, substring, out indexerOrSliceAccess, out argumentPlaceholders);

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IndexerAccess.cs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Immutable;
67
using System.Diagnostics;
78
using Microsoft.CodeAnalysis.CSharp.CodeGen;
@@ -1024,13 +1025,67 @@ private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAcces
10241025
AddPlaceholderReplacement(node.ArgumentPlaceholders[1], rangeSizeExpr);
10251026

10261027
var sliceCall = (BoundCall)node.IndexerOrSliceAccess;
1027-
var rewrittenIndexerAccess = VisitExpression(sliceCall);
1028+
1029+
// [start..] can be simplified to .Substring(start) or .Slice(start) for some built-in types
1030+
// e.g. string and (ReadOnly)Span
1031+
1032+
BoundExpression? moreEfficientIndexerAccess = null;
1033+
if (endMakeOffsetInput is null)
1034+
{
1035+
MethodSymbol? singleArgumentOverload = null;
1036+
1037+
if (TryGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__SubstringIntInt, out var stringSubstring, isOptional: true)
1038+
&& sliceCall.Method.Equals(stringSubstring, TypeCompareKind.ConsiderEverything))
1039+
{
1040+
if (TryGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__SubstringInt, out var stringOverload, isOptional: true))
1041+
{
1042+
singleArgumentOverload = stringOverload;
1043+
}
1044+
}
1045+
// with single typeWithElementType parameter ((ReadOnly)(Span|Memory))
1046+
// Method name is always "Slice"
1047+
else if (sliceCall is { Method: SubstitutedMethodSymbol { UnderlyingMethod: var generalizedMethod, Name: WellKnownMemberNames.SliceMethodName }, Type: NamedTypeSymbol { Name: var typeName } typeWithElementType })
1048+
{
1049+
// We use the return typeWithElementType of the current twoArgumentOverloadSymbol to simplify the logic
1050+
singleArgumentOverload = (typeName switch
1051+
{
1052+
// <int> will be ignored by nameof
1053+
nameof(Span<int>) => tryGetCorrespondingOneArgumentOverload(WellKnownMember.System_Span_T__Slice_Int_Int, WellKnownMember.System_Span_T__Slice_Int, generalizedMethod, node.Syntax),
1054+
nameof(ReadOnlySpan<int>) => tryGetCorrespondingOneArgumentOverload(WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, WellKnownMember.System_ReadOnlySpan_T__Slice_Int, generalizedMethod, node.Syntax),
1055+
nameof(Memory<int>) => tryGetCorrespondingOneArgumentOverload(WellKnownMember.System_Memory_T__Slice_Int_Int, WellKnownMember.System_Memory_T__Slice_Int, generalizedMethod, node.Syntax),
1056+
nameof(ReadOnlyMemory<int>) => tryGetCorrespondingOneArgumentOverload(WellKnownMember.System_ReadOnlyMemory_T__Slice_Int_Int, WellKnownMember.System_ReadOnlyMemory_T__Slice_Int, generalizedMethod, node.Syntax),
1057+
_ => null,
1058+
// The returned typeWithElementType of the above twoArgumentOverloadSymbol has not had a concrete element typeWithElementType yet.
1059+
})?.AsMember(typeWithElementType);
1060+
}
1061+
1062+
if (singleArgumentOverload is not null)
1063+
{
1064+
moreEfficientIndexerAccess = F.Call(VisitExpression(receiver), singleArgumentOverload, VisitExpression(startExpr));
1065+
}
1066+
}
1067+
1068+
var rewrittenIndexerAccess = moreEfficientIndexerAccess ?? VisitExpression(sliceCall);
10281069

10291070
RemovePlaceholderReplacement(node.ArgumentPlaceholders[0]);
10301071
RemovePlaceholderReplacement(node.ArgumentPlaceholders[1]);
10311072
RemovePlaceholderReplacement(node.ReceiverPlaceholder);
10321073

10331074
return rewrittenIndexerAccess;
1075+
1076+
MethodSymbol? tryGetCorrespondingOneArgumentOverload(WellKnownMember twoArgumentOverloadMember, WellKnownMember oneArgumentOverloadMember, MethodSymbol method, SyntaxNode syntax)
1077+
{
1078+
if (!TryGetWellKnownTypeMember(syntax, twoArgumentOverloadMember, out MethodSymbol? twoArgumentOverloadSymbol, isOptional: true)
1079+
|| !method.Equals(twoArgumentOverloadSymbol)
1080+
|| !TryGetWellKnownTypeMember(syntax, oneArgumentOverloadMember, out MethodSymbol? oneArgumentOverloadSymbol, isOptional: true))
1081+
{
1082+
return null;
1083+
}
1084+
Debug.Assert(twoArgumentOverloadSymbol.Name == oneArgumentOverloadSymbol.Name
1085+
&& twoArgumentOverloadSymbol.ReturnType.Equals(oneArgumentOverloadSymbol.ReturnType, TypeCompareKind.ConsiderEverything));
1086+
return oneArgumentOverloadSymbol;
1087+
1088+
}
10341089
}
10351090

10361091
private BoundExpression MakeRangeSize(ref BoundExpression startExpr, BoundExpression endExpr, ArrayBuilder<LocalSymbol> localsBuilder, ArrayBuilder<BoundExpression> sideEffectsBuilder)

0 commit comments

Comments
 (0)