Skip to content

Commit

Permalink
Use single argument overload for [start..] of string or Span
Browse files Browse the repository at this point in the history
  • Loading branch information
tats-u committed Aug 4, 2024
1 parent 967907d commit 2fe693f
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,57 @@ private BoundExpression VisitRangePatternIndexerAccess(BoundImplicitIndexerAcces
AddPlaceholderReplacement(node.ArgumentPlaceholders[1], rangeSizeExpr);

var sliceCall = (BoundCall)node.IndexerOrSliceAccess;
var rewrittenIndexerAccess = VisitExpression(sliceCall);

// [start..] can be simplified to .Substring(start) or .Slice(start) for some built-in types
// e.g. string and (ReadOnly)Span

BoundExpression? moreEfficientIndexerAccess = null;
if (endMakeOffsetInput is null)
{
MethodSymbol? singleArgumentOverload = null;
NamedTypeSymbol? typeContainingElementType = null;

if (
TryGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__Substring, out var stringSubstring, isOptional: true)
&& sliceCall.Method.Equals(stringSubstring)
&& TryGetSpecialTypeMethod(node.Syntax, SpecialMember.System_String__SubstringInt, out var stringOverload, isOptional: true)
)
{
singleArgumentOverload = stringOverload;
}
// with single type parameter ((ReadOnly)Span)
else if (sliceCall is { Method: SubstitutedMethodSymbol { UnderlyingMethod: var generalizedMethod }, Type: NamedTypeSymbol typeWithElementType })
{
typeContainingElementType = typeWithElementType;
if (
TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Span_T__Slice_Int_Int, out MethodSymbol? spanSlice, isOptional: true)
&& generalizedMethod.Equals(spanSlice)
&& TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Span_T__Slice_Int, out MethodSymbol? spanOverload, isOptional: true)
)
{
singleArgumentOverload = spanOverload;
}
else if (
TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, out MethodSymbol? readOnlySpanSlice, isOptional: true)
&& generalizedMethod.Equals(readOnlySpanSlice)
&& TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__Slice_Int, out MethodSymbol? readOnlySpanOverLoad, isOptional: true)
)
{
singleArgumentOverload = readOnlySpanOverLoad;

}
}

if (singleArgumentOverload is not null)
{
var overloadWithMaybeT = typeContainingElementType is not null
? new SubstitutedMethodSymbol(typeContainingElementType, singleArgumentOverload)
: singleArgumentOverload;
moreEfficientIndexerAccess = F.Call(receiver, overloadWithMaybeT, startExpr);
}
}

var rewrittenIndexerAccess = moreEfficientIndexerAccess ?? VisitExpression(sliceCall);

RemovePlaceholderReplacement(node.ArgumentPlaceholders[0]);
RemovePlaceholderReplacement(node.ArgumentPlaceholders[1]);
Expand Down
89 changes: 89 additions & 0 deletions src/Compilers/CSharp/Test/Emit/CodeGen/IndexAndRangeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4236,5 +4236,94 @@ static void M(Span<int> s)
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70);
comp.VerifyDiagnostics();
}
[ConditionalFact(typeof(CoreClrOnly))]
public void SingleOverloadReadOnlySpan()
{
string source = """
using System;

ReadOnlySpan<char> s = "0123";
Console.Write(s[1..].ToString());
""";
var comp = CompileAndVerify(source, targetFramework: TargetFramework.Net70, expectedOutput: "123");
comp.VerifyDiagnostics();
comp.VerifyIL("<top-level-statements-entry-point>", """
{
// Code size 39 (0x27)
.maxstack 2
.locals init (System.ReadOnlySpan<char> V_0, //s
System.ReadOnlySpan<char> V_1)
IL_0000: ldstr "0123"
IL_0005: call "System.ReadOnlySpan<char> string.op_Implicit(string)"
IL_000a: stloc.0
IL_000b: ldloca.s V_0
IL_000d: ldc.i4.1
IL_000e: call "System.ReadOnlySpan<char> System.ReadOnlySpan<char>.Slice(int)"
IL_0013: stloc.1
IL_0014: ldloca.s V_1
IL_0016: constrained. "System.ReadOnlySpan<char>"
IL_001c: callvirt "string object.ToString()"
IL_0021: call "void System.Console.Write(string)"
IL_0026: ret
}
""");
}

[ConditionalFact(typeof(CoreClrOnly))]
public void SingleOverloadSpan()
{
string source = """
using System;

Console.Write("0123".ToCharArray().AsSpan()[1..].ToString());
""";
var comp = CompileAndVerify(source, targetFramework: TargetFramework.Net70, expectedOutput: "123");
comp.VerifyDiagnostics();
comp.VerifyIL("<top-level-statements-entry-point>", """
{
// Code size 44 (0x2c)
.maxstack 2
.locals init (System.ReadOnlySpan<char> V_0, //s
)
IL_0000: ldstr "0123"
IL_0005: call "char[] string.ToCharArray()"
IL_000a: call "System.ReadOnlySpan<char> string.op_Implicit(string)"
IL_000f: stloc.0 V_0
IL_0010: ldloca.s 0
IL_0012: ldc.i4.1
IL_0013: call "System.Span<char> System.Span<char>.Slice(int)"
IL_0018: stloc.0
IL_0019: ldloca.s V_0
IL_001b: constrained. "System.Span<char>"
IL_0021: callvirt "string object.ToString()"
IL_0026: call "void System.Console.Write(string)"
IL_002b: ret
}
""");
}

[ConditionalFact(typeof(CoreClrOnly))]
public void SingleOverloadString()
{
string source = """
using System;

Console.Write("0123"[1..]);
""";
var comp = CompileAndVerify(source, targetFramework: TargetFramework.Net70, expectedOutput: "123");
comp.VerifyDiagnostics();
comp.VerifyIL("<top-level-statements-entry-point>", """
{
// Code size 17 (0x11)
.maxstack 2

IL_0000: ldstr "0123"
IL_0005: ldc.i4.1
IL_0006: callvirt "string string.Substring(int)"
IL_000b: call "void System.Console.Write(string)"
IL_0010: ret
}
""");
}
}
}
2 changes: 2 additions & 0 deletions src/Compilers/Core/Portable/SpecialMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ internal enum SpecialMember

System_Type__GetTypeFromHandle,

System_String__SubstringInt,

Count
}
}
9 changes: 9 additions & 0 deletions src/Compilers/Core/Portable/SpecialMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,14 @@ static SpecialMembers()
1, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)InternalSpecialType.System_Type, // Return Type
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_RuntimeTypeHandle,

// System_String__SubstringInt
(byte)MemberFlags.Method, // Flags
(byte)SpecialType.System_String, // DeclaringTypeId
0, // Arity
1, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,
};

string[] allNames = new string[(int)SpecialMember.Count]
Expand Down Expand Up @@ -1474,6 +1482,7 @@ static SpecialMembers()
"Empty", // System_Array__Empty
"SetValue", // System_Array__SetValue
"GetTypeFromHandle", // System_Type__GetTypeFromHandle
"Substring", // System_String__SubstringInt
};

s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames);
Expand Down
3 changes: 3 additions & 0 deletions src/Compilers/Core/Portable/WellKnownMember.cs
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ internal enum WellKnownMember

System_Runtime_CompilerServices_ParamCollectionAttribute__ctor,

System_Span_T__Slice_Int,
System_ReadOnlySpan_T__Slice_Int,

Count,

// Remember to update the AllWellKnownTypeMembers tests when making changes here
Expand Down
24 changes: 24 additions & 0 deletions src/Compilers/Core/Portable/WellKnownMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4327,6 +4327,28 @@ static WellKnownMembers()
0, // Arity
0, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type

// System_Span_T__Slice_Int
(byte)(MemberFlags.Method), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
1, // Method Signature
(byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle,
(byte)WellKnownType.ExtSentinel, (WellKnownType.System_Span_T - WellKnownType.ExtSentinel),
1,
(byte)SignatureTypeCode.GenericTypeParameter, (byte)0, // Return Type
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,
// System_ReadOnlySpan_T__Slice_Int
(byte)(MemberFlags.Method), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId
0, // Arity
1, // Method Signature
(byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle,
(byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel),
1,
(byte)SignatureTypeCode.GenericTypeParameter, (byte)0, // Return Type
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,

};

string[] allNames = new string[(int)WellKnownMember.Count]
Expand Down Expand Up @@ -4861,6 +4883,8 @@ static WellKnownMembers()
"AsSpan", // System_Collections_Immutable_ImmutableArray_T__AsSpan
"AddRange", // System_Collections_Generic_List_T__AddRange
".ctor", // System_Runtime_CompilerServices_ParamCollectionAttribute__ctor
"Slice", // System_Span_T__Slice_Int
"Slice", // System_ReadOnlySpan_T__Slice_Int
};

s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames);
Expand Down

0 comments on commit 2fe693f

Please sign in to comment.