提交 26923316 编写于 作者: A Andy Gocke

Handle fake indexers for Index and Range on string

上级 3e9297b7
......@@ -2806,7 +2806,7 @@ private BoundExpression BindArrayCreationExpression(ArrayCreationExpressionSynta
var size = BindValue(arg, diagnostics, BindValueKind.RValue);
if (!size.HasAnyErrors)
{
size = ConvertToArrayIndex(size, node, diagnostics);
size = ConvertToArrayIndex(size, node, diagnostics, allowIndexAndRange: false);
if (IsNegativeConstantForArraySize(size))
{
Error(diagnostics, ErrorCode.ERR_NegativeArraySize, arg);
......@@ -6788,7 +6788,7 @@ private BoundExpression BindArrayAccess(ExpressionSyntax node, BoundExpression e
{
BoundExpression argument = arguments.Arguments[i];
BoundExpression index = ConvertToArrayIndex(argument, node, diagnostics);
BoundExpression index = ConvertToArrayIndex(argument, node, diagnostics, allowIndexAndRange: rank == 1);
convertedArguments[i] = index;
// NOTE: Dev10 only warns if rank == 1
......@@ -6813,7 +6813,7 @@ private BoundExpression BindArrayAccess(ExpressionSyntax node, BoundExpression e
return new BoundArrayAccess(node, expr, convertedArguments.AsImmutableOrNull(), resultType, hasErrors);
}
private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode node, DiagnosticBag diagnostics)
private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode node, DiagnosticBag diagnostics, bool allowIndexAndRange)
{
Debug.Assert(index != null);
......@@ -6830,9 +6830,14 @@ private BoundExpression ConvertToArrayIndex(BoundExpression index, SyntaxNode no
TryImplicitConversionToArrayIndex(index, SpecialType.System_Int32, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, SpecialType.System_UInt32, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, SpecialType.System_Int64, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, SpecialType.System_UInt64, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, WellKnownType.System_Index, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, WellKnownType.System_Range, node, diagnostics);
TryImplicitConversionToArrayIndex(index, SpecialType.System_UInt64, node, diagnostics);
if (result is null && allowIndexAndRange)
{
result =
TryImplicitConversionToArrayIndex(index, WellKnownType.System_Index, node, diagnostics) ??
TryImplicitConversionToArrayIndex(index, WellKnownType.System_Range, node, diagnostics);
}
if (result == null)
{
......@@ -6952,7 +6957,7 @@ private BoundExpression BindPointerElementAccess(ExpressionSyntax node, BoundExp
BoundExpression index = arguments[0];
index = ConvertToArrayIndex(index, index.Syntax, diagnostics);
index = ConvertToArrayIndex(index, index.Syntax, diagnostics, allowIndexAndRange: false);
return new BoundPointerElementAccess(node, expr, index, CheckOverflowAtRuntime, pointedAtType, hasErrors);
}
......@@ -7152,6 +7157,43 @@ private BoundExpression BindIndexedPropertyAccess(SyntaxNode syntax, BoundExpres
if (!analyzedArguments.HasErrors)
{
// PROTOTYPE: REMOVE BEFORE SHIPPING DEV16
// Pretend like there are indexers that support range and index
if (receiverOpt?.Type.SpecialType == SpecialType.System_String &&
analyzedArguments.Arguments.Count == 1)
{
HashSet<DiagnosticInfo> diags = null;
var argType = analyzedArguments.Arguments[0].Type;
TypeSymbol resultType = null;
if (argType == GetWellKnownType(WellKnownType.System_Index, ref diags))
{
resultType = GetSpecialType(SpecialType.System_Char, diagnostics, syntax);
}
else if (argType == GetWellKnownType(WellKnownType.System_Range, ref diags))
{
resultType = GetSpecialType(SpecialType.System_String, diagnostics, syntax);
}
if (!(resultType is null))
{
var args = analyzedArguments.Arguments.ToImmutable();
return new BoundIndexerAccess(
syntax,
receiverOpt,
candidates[0],
args,
argumentNames,
argumentRefKinds,
expanded: false,
argsToParamsOpt: default,
binderOpt: this,
useSetterForDefaultArgumentGeneration: false,
resultType,
hasErrors: false);
}
}
// Dev10 uses the "this" keyword as the method name for indexers.
var candidate = candidates[0];
var name = candidate.IsIndexer ? SyntaxFacts.GetText(SyntaxKind.ThisKeyword) : candidate.Name;
......
......@@ -3673,7 +3673,16 @@ public override BoundNode VisitIndexerAccess(BoundIndexerAccess node)
// https://github.com/dotnet/roslyn/issues/29964 Update indexer based on inferred receiver type.
VisitArguments(node, node.Arguments, node.ArgumentRefKindsOpt, node.Indexer, node.ArgsToParamsOpt, node.Expanded);
_resultType = node.Indexer.Type;
// PROTOTYPE: REMOVE BEFORE SHIPPING DEV16
if (node.Arguments.Length == 1 &&
node.Arguments[0].Type == compilation.GetWellKnownType(WellKnownType.System_Range))
{
_resultType = TypeSymbolWithAnnotations.Create(node.Type);
}
else
{
_resultType = node.Indexer.Type;
}
return null;
}
......
......@@ -553,11 +553,8 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node)
out BoundAssignmentOperator arrayAssign);
var indexType = VisitType(node.Indices[0].Type);
if (!TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Index__Value, out PropertySymbol indexValueSymbol) ||
!TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Index__FromEnd, out PropertySymbol indexFromEndSymbol))
{
return node;
}
var indexValueSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__Value);
var indexFromEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__FromEnd);
BoundExpression resultExpr;
if (indexType == _compilation.GetWellKnownType(WellKnownType.System_Index))
......@@ -595,12 +592,9 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node)
// Array.Copy(array, start, newArr, 0, length);
// push newArray
if (!TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Range__Start, out PropertySymbol rangeStartSymbol) ||
!TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Range__End, out PropertySymbol rangeEndSymbol) ||
!TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Array__Copy, out MethodSymbol arrayCopySymbol))
{
return node;
}
var rangeStartSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__Start);
var rangeEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__End);
var arrayCopySymbol = F.WellKnownMethod(WellKnownMember.System_Array__Copy);
var startLocal = F.StoreToTemp(
F.Conditional(
......
......@@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
......@@ -82,6 +83,101 @@ private BoundExpression VisitIndexerAccess(BoundIndexerAccess node, bool isLeftO
// NOTE: This is done later by MakeArguments, for now we just lower each argument.
ImmutableArray<BoundExpression> rewrittenArguments = VisitList(node.Arguments);
// PROTOTYPE: REMOVE BEFORE SHIPPING DEV16
if (rewrittenReceiver?.Type.SpecialType == SpecialType.System_String &&
rewrittenArguments.Length == 1 && rewrittenArguments[0].Type.SpecialType == SpecialType.None)
{
var F = _factory;
var indexLocal = F.StoreToTemp(rewrittenArguments[0], out BoundAssignmentOperator indexAssign);
var stringLocal = F.StoreToTemp(rewrittenReceiver, out BoundAssignmentOperator stringAssign);
var indexValueSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__Value);
var indexFromEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Index__FromEnd);
var argType = rewrittenArguments[0].Type;
if (argType == _compilation.GetWellKnownType(WellKnownType.System_Index))
{
// string[Index] is rewritten as:
// index.FromEnd ? s[s.Length - index.Value] : s[index.Value];
var indexValueExpr = F.Property(indexLocal, indexValueSymbol);
return F.Sequence(
ImmutableArray.Create<LocalSymbol>(
indexLocal.LocalSymbol,
stringLocal.LocalSymbol),
ImmutableArray.Create<BoundExpression>(
indexAssign,
stringAssign),
F.Conditional(
F.Property(indexLocal, indexFromEndSymbol),
F.Property(stringLocal, node.Indexer,
F.Binary(
BinaryOperatorKind.Subtraction,
F.SpecialType(SpecialType.System_Int32),
F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
indexValueExpr)),
F.Property(stringLocal, node.Indexer, indexValueExpr),
F.SpecialType(SpecialType.System_Char)));
}
else if (argType == _compilation.GetWellKnownType(WellKnownType.System_Range))
{
// string[Range] is translated to:
// var start = range.Start.FromEnd ? array.Length - range.Start.Value : range.Start.Value;
// var end = range.End.FromEnd ? array.Length - range.End.Value : range.End.Value;
// string.Substring(start, end - start)
var rangeStartSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__Start);
var rangeEndSymbol = (PropertySymbol)F.WellKnownMember(WellKnownMember.System_Range__End);
var arrayCopySymbol = F.WellKnownMethod(WellKnownMember.System_Array__Copy);
var startLocal = F.StoreToTemp(
F.Conditional(
F.Property(F.Property(indexLocal, rangeStartSymbol), indexFromEndSymbol),
F.Binary(
BinaryOperatorKind.Subtraction,
F.SpecialType(SpecialType.System_Int32),
F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
F.Property(F.Property(indexLocal, rangeEndSymbol), indexValueSymbol)),
F.Property(F.Property(indexLocal, rangeStartSymbol), indexValueSymbol),
F.SpecialType(SpecialType.System_Int32)),
out BoundAssignmentOperator startAssign);
var endLocal = F.StoreToTemp(
F.Conditional(
F.Property(F.Property(indexLocal, rangeEndSymbol), indexFromEndSymbol),
F.Binary(
BinaryOperatorKind.Subtraction,
F.SpecialType(SpecialType.System_Int32),
F.Call(stringLocal, F.SpecialMethod(SpecialMember.System_String__Length)),
F.Property(F.Property(indexLocal, rangeEndSymbol), indexValueSymbol)),
F.Property(F.Property(indexLocal, rangeEndSymbol), indexValueSymbol),
F.SpecialType(SpecialType.System_Int32)),
out BoundAssignmentOperator endAssign);
var substringExpr = F.Call(
stringLocal,
F.WellKnownMethod(WellKnownMember.System_String__Substring),
startLocal,
F.Binary(BinaryOperatorKind.Subtraction, F.SpecialType(SpecialType.System_Int32), endLocal, startLocal));
return F.Sequence(
ImmutableArray.Create(
indexLocal.LocalSymbol,
stringLocal.LocalSymbol,
startLocal.LocalSymbol,
endLocal.LocalSymbol),
ImmutableArray.Create<BoundExpression>(
indexAssign,
stringAssign,
startAssign,
endAssign),
substringExpr);
}
else
{
throw ExceptionUtilities.Unreachable;
}
}
return MakeIndexerAccess(
node.Syntax,
rewrittenReceiver,
......
......@@ -288,6 +288,12 @@ public BoundExpression Property(BoundExpression receiverOpt, PropertySymbol prop
return Call(receiverOpt, property.GetMethod); // TODO: should we use property.GetBaseProperty().GetMethod to ensure we generate a call to the overridden method?
}
public BoundExpression Property(BoundExpression receiverOpt, PropertySymbol property, BoundExpression arg0)
{
Debug.Assert((receiverOpt == null) == property.IsStatic);
return Call(receiverOpt, property.GetMethod, arg0); // TODO: should we use property.GetBaseProperty().GetMethod to ensure we generate a call to the overridden method?
}
public NamedTypeSymbol SpecialType(SpecialType st)
{
NamedTypeSymbol specialType = Compilation.GetSpecialType(st);
......
......@@ -8,6 +8,251 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
public class IndexAndRangeTests : CSharpTestBase
{
[Fact]
public void IndexIndexerStringTwoArgs()
{
var comp = CreateCompilationWithIndex(@"
using System;
class C
{
public static void Main()
{
var s = ""abcdef"";
M(s);
}
public static void M(string s)
{
Console.WriteLine(s[new Index(1, false)]);
Console.WriteLine(s[new Index(1, false), ^1]);
}
}");
comp.VerifyDiagnostics(
// (13,27): error CS1501: No overload for method 'this' takes 2 arguments
// Console.WriteLine(s[new Index(1, false), ^1]);
Diagnostic(ErrorCode.ERR_BadArgCount, "s[new Index(1, false), ^1]").WithArguments("this", "2").WithLocation(13, 27));
}
[Fact]
public void IndexIndexerArrayTwoArgs()
{
var comp = CreateCompilationWithIndex(@"
using System;
class C
{
public static void Main()
{
var x = new int[1,1];
M(x);
}
public static void M(int[,] s)
{
Console.WriteLine(s[new Index(1, false), ^1]);
}
}");
comp.VerifyDiagnostics(
// (12,27): error CS0029: Cannot implicitly convert type 'System.Index' to 'int'
// Console.WriteLine(s[new Index(1, false), ^1]);
Diagnostic(ErrorCode.ERR_NoImplicitConv, "s[new Index(1, false), ^1]").WithArguments("System.Index", "int").WithLocation(12, 27),
// (12,27): error CS0029: Cannot implicitly convert type 'System.Index' to 'int'
// Console.WriteLine(s[new Index(1, false), ^1]);
Diagnostic(ErrorCode.ERR_NoImplicitConv, "s[new Index(1, false), ^1]").WithArguments("System.Index", "int").WithLocation(12, 27));
}
[Fact]
public void FakeIndexIndexerString()
{
var comp = CreateCompilationWithIndex(@"
using System;
class C
{
public static void Main()
{
var s = ""abcdef"";
M(s);
}
public static void M(string s)
{
Console.WriteLine(s[new Index(1, false)]);
Console.WriteLine(s[^1]);
}
}", TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: @"b
f");
verifier.VerifyIL("C.M", @"
{
// Code size 121 (0x79)
.maxstack 3
.locals init (System.Index V_0,
string V_1)
IL_0000: ldloca.s V_0
IL_0002: ldc.i4.1
IL_0003: ldc.i4.0
IL_0004: call ""System.Index..ctor(int, bool)""
IL_0009: ldarg.0
IL_000a: stloc.1
IL_000b: ldloca.s V_0
IL_000d: call ""bool System.Index.FromEnd.get""
IL_0012: brtrue.s IL_0023
IL_0014: ldloc.1
IL_0015: ldloca.s V_0
IL_0017: call ""int System.Index.Value.get""
IL_001c: callvirt ""char string.this[int].get""
IL_0021: br.s IL_0037
IL_0023: ldloc.1
IL_0024: ldloc.1
IL_0025: callvirt ""int string.Length.get""
IL_002a: ldloca.s V_0
IL_002c: call ""int System.Index.Value.get""
IL_0031: sub
IL_0032: callvirt ""char string.this[int].get""
IL_0037: call ""void System.Console.WriteLine(char)""
IL_003c: ldloca.s V_0
IL_003e: ldc.i4.1
IL_003f: ldc.i4.1
IL_0040: call ""System.Index..ctor(int, bool)""
IL_0045: ldarg.0
IL_0046: stloc.1
IL_0047: ldloca.s V_0
IL_0049: call ""bool System.Index.FromEnd.get""
IL_004e: brtrue.s IL_005f
IL_0050: ldloc.1
IL_0051: ldloca.s V_0
IL_0053: call ""int System.Index.Value.get""
IL_0058: callvirt ""char string.this[int].get""
IL_005d: br.s IL_0073
IL_005f: ldloc.1
IL_0060: ldloc.1
IL_0061: callvirt ""int string.Length.get""
IL_0066: ldloca.s V_0
IL_0068: call ""int System.Index.Value.get""
IL_006d: sub
IL_006e: callvirt ""char string.this[int].get""
IL_0073: call ""void System.Console.WriteLine(char)""
IL_0078: ret
}");
}
[Fact]
public void FakeRangeIndexerString()
{
var comp = CreateCompilationWithIndexAndRange(@"
using System;
class C
{
public static void Main()
{
var s = ""abcdef"";
var result = M(s);
Console.WriteLine(result);
}
public static string M(string s) => s[1..3];
}", TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "bc");
verifier.VerifyIL("C.M", @"
{
// Code size 151 (0x97)
.maxstack 4
.locals init (System.Range V_0,
string V_1,
int V_2,
int V_3,
System.Index V_4)
IL_0000: ldc.i4.1
IL_0001: call ""System.Index System.Index.op_Implicit(int)""
IL_0006: ldc.i4.3
IL_0007: call ""System.Index System.Index.op_Implicit(int)""
IL_000c: call ""System.Range System.Range.Create(System.Index, System.Index)""
IL_0011: stloc.0
IL_0012: ldarg.0
IL_0013: stloc.1
IL_0014: ldloca.s V_0
IL_0016: call ""System.Index System.Range.Start.get""
IL_001b: stloc.s V_4
IL_001d: ldloca.s V_4
IL_001f: call ""bool System.Index.FromEnd.get""
IL_0024: brtrue.s IL_0038
IL_0026: ldloca.s V_0
IL_0028: call ""System.Index System.Range.Start.get""
IL_002d: stloc.s V_4
IL_002f: ldloca.s V_4
IL_0031: call ""int System.Index.Value.get""
IL_0036: br.s IL_004f
IL_0038: ldloc.1
IL_0039: callvirt ""int string.Length.get""
IL_003e: ldloca.s V_0
IL_0040: call ""System.Index System.Range.End.get""
IL_0045: stloc.s V_4
IL_0047: ldloca.s V_4
IL_0049: call ""int System.Index.Value.get""
IL_004e: sub
IL_004f: stloc.2
IL_0050: ldloca.s V_0
IL_0052: call ""System.Index System.Range.End.get""
IL_0057: stloc.s V_4
IL_0059: ldloca.s V_4
IL_005b: call ""bool System.Index.FromEnd.get""
IL_0060: brtrue.s IL_0074
IL_0062: ldloca.s V_0
IL_0064: call ""System.Index System.Range.End.get""
IL_0069: stloc.s V_4
IL_006b: ldloca.s V_4
IL_006d: call ""int System.Index.Value.get""
IL_0072: br.s IL_008b
IL_0074: ldloc.1
IL_0075: callvirt ""int string.Length.get""
IL_007a: ldloca.s V_0
IL_007c: call ""System.Index System.Range.End.get""
IL_0081: stloc.s V_4
IL_0083: ldloca.s V_4
IL_0085: call ""int System.Index.Value.get""
IL_008a: sub
IL_008b: stloc.3
IL_008c: ldloc.1
IL_008d: ldloc.2
IL_008e: ldloc.3
IL_008f: ldloc.2
IL_0090: sub
IL_0091: callvirt ""string string.Substring(int, int)""
IL_0096: ret
}");
}
[Fact]
public void FakeRangeIndexerStringOpenEnd()
{
var comp = CreateCompilationWithIndexAndRange(@"
using System;
class C
{
public static void Main()
{
var s = ""abcdef"";
var result = M(s);
Console.WriteLine(result);
}
public static string M(string s) => s[1..];
}", TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "bcdef");
}
[Fact]
public void FakeRangeIndexerStringOpenStart()
{
var comp = CreateCompilationWithIndexAndRange(@"
using System;
class C
{
public static void Main()
{
var s = ""abcdef"";
var result = M(s);
Console.WriteLine(result);
}
public static string M(string s) => s[..^2];
}", TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "abcd");
}
[Fact]
public void FakeIndexIndexerArray()
{
......@@ -118,15 +363,20 @@ public static void Main()
{
var x = new[] { 11 };
Console.WriteLine(x[^1]);
Console.WriteLine(""test""[^1]);
}
}", options: TestOptions.ReleaseExe);
comp.VerifyDiagnostics();
// We check for the well-known member in lowering, so you won't normally see this
// error during binding. This is fine for a preview-only feature.
comp.VerifyEmitDiagnostics(
// (30,27): error CS0656: Missing compiler required member 'System.Index.Value'
// Console.WriteLine(x[^1]);
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x[^1]").WithArguments("System.Index", "Value").WithLocation(30, 27));
// (28,5): error CS0656: Missing compiler required member 'System.Index.Value'
// {
Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"{
var x = new[] { 11 };
Console.WriteLine(x[^1]);
Console.WriteLine(""test""[^1]);
}").WithArguments("System.Index", "Value").WithLocation(28, 5));
}
[Fact]
......
......@@ -414,6 +414,7 @@ internal enum WellKnownMember
System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames,
System_String__Format_IFormatProvider,
System_String__Substring,
Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile,
Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles,
......
......@@ -2878,6 +2878,15 @@ static WellKnownMembers()
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String,
(byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object,
// System_String__Substring
(byte)MemberFlags.Method, // Flags
(byte)SpecialType.System_String, // DeclaringTypeId
0, // Arity
2, // Method Signature
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,
(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,
// Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId
......@@ -3655,6 +3664,8 @@ static WellKnownMembers()
".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames
"Format", // System_String__Format_IFormatProvider
"Substring", // System_string__Substring
"CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile
"CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册