diff --git a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs index e1e74632ef382..3c845a63b0060 100644 --- a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs +++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UseAutoProperty; @@ -2999,4 +3000,62 @@ class Class readonly ref int P => ref i; } """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77011")] + public Task TestRemoveThisIfPreferredCodeStyle() + => TestInRegularAndScriptAsync( + """ + class C + { + [|private readonly string a;|] + + public C(string a) + { + this.a = a; + } + + public string A => a; + } + """, + """ + class C + { + public C(string a) + { + A = a; + } + + public string A { get; } + } + """, + options: Option(CodeStyleOptions2.QualifyPropertyAccess, false, NotificationOption2.Error)); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77011")] + public Task TestKeepThisIfPreferredCodeStyle() + => TestInRegularAndScriptAsync( + """ + class C + { + [|private readonly string a;|] + + public C(string a) + { + this.a = a; + } + + public string A => a; + } + """, + """ + class C + { + public C(string a) + { + this.A = a; + } + + public string A { get; } + } + """, + options: Option(CodeStyleOptions2.QualifyPropertyAccess, true, NotificationOption2.Error)); } diff --git a/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator_RefSafety.cs b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator_RefSafety.cs new file mode 100644 index 0000000000000..07ea26d9bc8f0 --- /dev/null +++ b/src/Compilers/CSharp/Portable/CodeGen/CodeGenerator_RefSafety.cs @@ -0,0 +1,114 @@ +// 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. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Symbols; + +namespace Microsoft.CodeAnalysis.CSharp.CodeGen; + +internal partial class CodeGenerator +{ + /// + private static bool MightEscapeTemporaryRefs(BoundCall node, bool used) + { + return MightEscapeTemporaryRefs( + used: used, + returnType: node.Type, + returnRefKind: node.Method.RefKind, + thisParameterSymbol: node.Method.TryGetThisParameter(out var thisParameter) ? thisParameter : null, + parameters: node.Method.Parameters); + } + + /// + private static bool MightEscapeTemporaryRefs(BoundObjectCreationExpression node, bool used) + { + return MightEscapeTemporaryRefs( + used: used, + returnType: node.Type, + returnRefKind: RefKind.None, + thisParameterSymbol: null, + parameters: node.Constructor.Parameters); + } + + /// + private static bool MightEscapeTemporaryRefs(BoundFunctionPointerInvocation node, bool used) + { + FunctionPointerMethodSymbol method = node.FunctionPointer.Signature; + return MightEscapeTemporaryRefs( + used: used, + returnType: node.Type, + returnRefKind: method.RefKind, + thisParameterSymbol: null, + parameters: method.Parameters); + } + + /// + /// Determines whether a 'ref' can be captured by the call (considering only its signature). + /// + /// + /// + /// The emit layer consults this to avoid reusing temporaries that are passed by ref to such methods. + /// + /// + /// This is a heuristic which might have false positives, i.e., + /// it might not recognize that a call cannot capture a 'ref' + /// even if the binding-time ref safety analysis would recognize that. + /// That is fine, the IL will be correct, albeit less optimal (it might use more temps). + /// But we still want some heuristic to avoid regressing IL of common safe cases. + /// + /// + private static bool MightEscapeTemporaryRefs( + bool used, + TypeSymbol returnType, + RefKind returnRefKind, + ParameterSymbol? thisParameterSymbol, + ImmutableArray parameters) + { + // whether we have any outputs that can capture `ref`s + bool anyRefTargets = false; + // whether we have any inputs that can contain `ref`s + bool anyRefSources = false; + + if (used && (returnRefKind != RefKind.None || returnType.IsRefLikeOrAllowsRefLikeType())) + { + // If returning by ref or returning a ref struct, the result might capture `ref`s. + anyRefTargets = true; + } + + if (thisParameterSymbol is not null && processParameter(thisParameterSymbol, anyRefSources: ref anyRefSources, anyRefTargets: ref anyRefTargets)) + { + return true; + } + + foreach (var parameter in parameters) + { + if (processParameter(parameter, anyRefSources: ref anyRefSources, anyRefTargets: ref anyRefTargets)) + { + return true; + } + } + + return false; + + // Returns true if we can return 'true' early. + static bool processParameter(ParameterSymbol parameter, ref bool anyRefSources, ref bool anyRefTargets) + { + if (parameter.Type.IsRefLikeOrAllowsRefLikeType() && parameter.EffectiveScope != ScopedKind.ScopedValue) + { + anyRefSources = true; + if (parameter.RefKind.IsWritableReference()) + { + anyRefTargets = true; + } + } + else if (parameter.RefKind != RefKind.None && parameter.EffectiveScope == ScopedKind.None) + { + anyRefSources = true; + } + + // If there is at least one output and at least one input, a `ref` can be captured. + return anyRefTargets && anyRefSources; + } + } +} diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index 86b3e463535e1..127509552c312 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -1660,7 +1660,13 @@ private void EmitStaticCallExpression(BoundCall call, UseKind useKind) Debug.Assert(method.IsStatic); + var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + EmitArguments(arguments, method.Parameters, call.ArgumentRefKindsOpt); + + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, + MightEscapeTemporaryRefs(call, used: useKind != UseKind.Unused)); + int stackBehavior = GetCallStackBehavior(method, arguments); if (method.IsAbstract || method.IsVirtual) @@ -1690,6 +1696,8 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind) bool box; LocalDefinition tempOpt; + var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + if (receiverIsInstanceCall(call, out BoundCall nested)) { var calls = ArrayBuilder.GetInstance(); @@ -1755,6 +1763,11 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind) } emitArgumentsAndCallEpilogue(call, callKind, receiverUseKind); + + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, MightEscapeTemporaryRefs(call, used: true)); + + countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + FreeOptTemp(tempOpt); tempOpt = null; @@ -1815,6 +1828,9 @@ private void EmitInstanceCallExpression(BoundCall call, UseKind useKind) } emitArgumentsAndCallEpilogue(call, callKind, useKind); + + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, MightEscapeTemporaryRefs(call, used: useKind != UseKind.Unused)); + FreeOptTemp(tempOpt); return; @@ -2446,8 +2462,14 @@ private void EmitObjectCreationExpression(BoundObjectCreationExpression expressi } // none of the above cases, so just create an instance + + var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + EmitArguments(expression.Arguments, constructor.Parameters, expression.ArgumentRefKindsOpt); + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, + MightEscapeTemporaryRefs(expression, used)); + var stackAdjustment = GetObjCreationStackBehavior(expression); _builder.EmitOpCode(ILOpCode.Newobj, stackAdjustment); @@ -2572,8 +2594,7 @@ private void EmitAssignmentExpression(BoundAssignmentOperator assignmentOperator private bool TryEmitAssignmentInPlace(BoundAssignmentOperator assignmentOperator, bool used) { // If the left hand is itself a ref, then we can't use in-place assignment - // because we need to spill the creation. This code can't be written in C#, but - // can be produced by lowering. + // because we need to spill the creation. if (assignmentOperator.IsRef) { return false; @@ -2704,7 +2725,14 @@ private bool TryInPlaceCtorCall(BoundExpression target, BoundObjectCreationExpre Debug.Assert(temp == null, "in-place ctor target should not create temps"); var constructor = objCreation.Constructor; + + var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + EmitArguments(objCreation.Arguments, constructor.Parameters, objCreation.ArgumentRefKindsOpt); + + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, + MightEscapeTemporaryRefs(objCreation, used)); + // -2 to adjust for consumed target address and not produced value. var stackAdjustment = GetObjCreationStackBehavior(objCreation) - 2; _builder.EmitOpCode(ILOpCode.Call, stackAdjustment); @@ -4026,7 +4054,14 @@ private void EmitCalli(BoundFunctionPointerInvocation ptrInvocation, UseKind use } FunctionPointerMethodSymbol method = ptrInvocation.FunctionPointer.Signature; + + var countBefore = _builder.LocalSlotManager.StartScopeOfTrackingAddressedLocals(); + EmitArguments(ptrInvocation.Arguments, method.Parameters, ptrInvocation.ArgumentRefKindsOpt); + + _builder.LocalSlotManager.EndScopeOfTrackingAddressedLocals(countBefore, + MightEscapeTemporaryRefs(ptrInvocation, used: useKind != UseKind.Unused)); + var stackBehavior = GetCallStackBehavior(ptrInvocation.FunctionPointer.Signature, ptrInvocation.Arguments); if (temp is object) diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs index 365cd44a08702..b3af222698d72 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs @@ -991,13 +991,23 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) // Special Case: If the RHS is a pointer conversion, then the assignment functions as // a conversion (because the RHS will actually be typed as a native u/int in IL), so // we should not optimize away the local (i.e. schedule it on the stack). - if (CanScheduleToStack(localSymbol) && + else if (CanScheduleToStack(localSymbol) && assignmentLocal.Type.IsPointerOrFunctionPointer() && right.Kind == BoundKind.Conversion && ((BoundConversion)right).ConversionKind.IsPointerConversion()) { ShouldNotSchedule(localSymbol); } + // If this is a pointer-to-ref assignment, keep the local so GC knows to re-track it. + // We don't need to do this for implicitly synthesized locals because working with pointers in an unsafe context does not guarantee any GC tracking, + // but when a pointer is converted to a user-defined ref local, it becomes a use of a "safe" feature where we should guarantee the ref is tracked by GC. + else if (localSymbol.RefKind != RefKind.None && + localSymbol.SynthesizedKind == SynthesizedLocalKind.UserDefined && + right.Kind == BoundKind.PointerIndirectionOperator) + { + ShouldNotSchedule(localSymbol); + } + RecordVarWrite(localSymbol); assignmentLocal = null; } diff --git a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs index b3359712b498d..87cd14d63032f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/NativeIntegerTypeSymbol.cs @@ -368,6 +368,20 @@ public override ImmutableArray Parameters } } + internal override bool TryGetThisParameter(out ParameterSymbol? thisParameter) + { + if (UnderlyingMethod.TryGetThisParameter(out ParameterSymbol? underlyingThisParameter)) + { + thisParameter = underlyingThisParameter != null + ? new ThisParameterSymbol(this, _container) + : null; + return true; + } + + thisParameter = null; + return false; + } + public override ImmutableArray ExplicitInterfaceImplementations => ImmutableArray.Empty; public override ImmutableArray RefCustomModifiers => UnderlyingMethod.RefCustomModifiers; diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs index 1f87e26e45231..b34b097c130ad 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenInParametersTests.cs @@ -861,7 +861,8 @@ static ref readonly int M(in int x) // Code size 72 (0x48) .maxstack 3 .locals init (int V_0, - int V_1) + int V_1, + int V_2) IL_0000: ldc.i4.s 42 IL_0002: stloc.0 IL_0003: ldloca.s V_0 @@ -870,22 +871,22 @@ .locals init (int V_0, IL_000b: call ""void System.Console.WriteLine(int)"" IL_0010: newobj ""Program..ctor()"" IL_0015: ldc.i4.5 - IL_0016: stloc.0 - IL_0017: ldloca.s V_0 + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 IL_0019: ldc.i4.6 - IL_001a: stloc.1 - IL_001b: ldloca.s V_1 + IL_001a: stloc.2 + IL_001b: ldloca.s V_2 IL_001d: call ""int Program.this[in int, in int].get"" IL_0022: call ""void System.Console.WriteLine(int)"" IL_0027: ldc.i4.s 42 - IL_0029: stloc.0 - IL_002a: ldloca.s V_0 + IL_0029: stloc.1 + IL_002a: ldloca.s V_1 IL_002c: call ""ref readonly int Program.M(in int)"" IL_0031: ldind.i4 IL_0032: call ""void System.Console.WriteLine(int)"" IL_0037: ldc.i4.s 42 - IL_0039: stloc.0 - IL_003a: ldloca.s V_0 + IL_0039: stloc.2 + IL_003a: ldloca.s V_2 IL_003c: call ""ref readonly int Program.M(in int)"" IL_0041: ldind.i4 IL_0042: call ""void System.Console.WriteLine(int)"" diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs index 5e6809f136b60..989fda6428452 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs @@ -95,31 +95,40 @@ unsafe static void Main() 11"); verifier.VerifyIL("C.Main", @" { - // Code size 34 (0x22) + // Code size 43 (0x2b) .maxstack 2 .locals init (int V_0, //x int V_1, //y - pinned int& V_2) + int& V_2, //rx + pinned int& V_3) IL_0000: ldc.i4.5 IL_0001: stloc.0 IL_0002: ldc.i4.s 11 IL_0004: stloc.1 - IL_0005: ldloca.s V_1 + IL_0005: ldloca.s V_0 IL_0007: stloc.2 - IL_0008: ldloc.2 - IL_0009: conv.u + IL_0008: ldloca.s V_1 IL_000a: dup - IL_000b: ldind.i4 - IL_000c: call ""void System.Console.WriteLine(int)"" - IL_0011: dup - IL_0012: ldind.i4 - IL_0013: call ""void System.Console.WriteLine(int)"" - IL_0018: ldind.i4 - IL_0019: call ""void System.Console.WriteLine(int)"" - IL_001e: ldc.i4.0 - IL_001f: conv.u - IL_0020: stloc.2 - IL_0021: ret + IL_000b: stloc.2 + IL_000c: stloc.3 + IL_000d: ldloc.3 + IL_000e: conv.u + IL_000f: dup + IL_0010: ldind.i4 + IL_0011: call ""void System.Console.WriteLine(int)"" + IL_0016: dup + IL_0017: stloc.2 + IL_0018: ldloc.2 + IL_0019: ldind.i4 + IL_001a: call ""void System.Console.WriteLine(int)"" + IL_001f: stloc.2 + IL_0020: ldloc.2 + IL_0021: ldind.i4 + IL_0022: call ""void System.Console.WriteLine(int)"" + IL_0027: ldc.i4.0 + IL_0028: conv.u + IL_0029: stloc.3 + IL_002a: ret }"); } @@ -4204,13 +4213,14 @@ public void TestRefOnPointerIndirection_01() } "; - verify(TestOptions.UnsafeReleaseExe, Verification.Passes, @" + verify(TestOptions.UnsafeReleaseExe, Verification.Fails, @" { // Code size 14 (0xe) .maxstack 1 + .locals init (int& V_0) //x IL_0000: ldc.i4.0 IL_0001: conv.i - IL_0002: pop + IL_0002: stloc.0 IL_0003: ldstr ""run"" IL_0008: call ""void System.Console.WriteLine(string)"" IL_000d: ret @@ -4275,14 +4285,17 @@ .maxstack 1 verify(TestOptions.UnsafeReleaseExe, @" { - // Code size 14 (0xe) + // Code size 16 (0x10) .maxstack 1 + .locals init (int& V_0) //x IL_0000: ldc.i4.0 IL_0001: conv.i - IL_0002: call ""void* Unsafe.AsPointer(ref int)"" - IL_0007: conv.i4 - IL_0008: call ""void System.Console.WriteLine(int)"" - IL_000d: ret + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: call ""void* Unsafe.AsPointer(ref int)"" + IL_0009: conv.i4 + IL_000a: call ""void System.Console.WriteLine(int)"" + IL_000f: ret } "); @@ -4329,19 +4342,22 @@ public void TestRefOnPointerIndirection_03() } "; - verify(TestOptions.UnsafeReleaseExe, Verification.Passes, @" + verify(TestOptions.UnsafeReleaseExe, Verification.Fails, @" { - // Code size 16 (0x10) + // Code size 19 (0x13) .maxstack 1 - .locals init (int V_0) //i1 + .locals init (int V_0, //i1 + int& V_1) //i2 IL_0000: ldc.i4.0 IL_0001: stloc.0 - IL_0002: ldc.i4.0 - IL_0003: conv.i - IL_0004: pop - IL_0005: ldstr ""run"" - IL_000a: call ""void System.Console.WriteLine(string)"" - IL_000f: ret + IL_0002: ldloca.s V_0 + IL_0004: stloc.1 + IL_0005: ldc.i4.0 + IL_0006: conv.i + IL_0007: stloc.1 + IL_0008: ldstr ""run"" + IL_000d: call ""void System.Console.WriteLine(string)"" + IL_0012: ret } "); @@ -4392,13 +4408,14 @@ public void TestRefOnPointerArrayAccess_01() } "; - verify(TestOptions.UnsafeReleaseExe, Verification.Passes, @" + verify(TestOptions.UnsafeReleaseExe, Verification.Fails, @" { // Code size 14 (0xe) .maxstack 1 + .locals init (int& V_0) //x IL_0000: ldc.i4.0 IL_0001: conv.i - IL_0002: pop + IL_0002: stloc.0 IL_0003: ldstr ""run"" IL_0008: call ""void System.Console.WriteLine(string)"" IL_000d: ret @@ -4444,15 +4461,16 @@ public void TestRefOnPointerArrayAccess_02() } "; - verify(TestOptions.UnsafeReleaseExe, Verification.Passes, @" + verify(TestOptions.UnsafeReleaseExe, Verification.Fails, @" { // Code size 16 (0x10) .maxstack 2 + .locals init (int& V_0) //x IL_0000: ldc.i4.0 IL_0001: conv.i IL_0002: ldc.i4.4 IL_0003: add - IL_0004: pop + IL_0004: stloc.0 IL_0005: ldstr ""run"" IL_000a: call ""void System.Console.WriteLine(string)"" IL_000f: ret diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/FixedSizeBufferTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/FixedSizeBufferTests.cs index 6f709ef6d664d..a6896e2bf31c9 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/FixedSizeBufferTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/FixedSizeBufferTests.cs @@ -176,9 +176,10 @@ unsafe static void Main() CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: "12", verify: Verification.Passes) .VerifyIL("Program.Main", @" { - // Code size 52 (0x34) + // Code size 54 (0x36) .maxstack 2 - .locals init (S1 V_0) //c + .locals init (S1 V_0, //c + int& V_1) //i IL_0000: ldloca.s V_0 IL_0002: initobj ""S1"" IL_0008: ldloca.s V_0 @@ -191,9 +192,11 @@ .locals init (S1 V_0) //c IL_001e: ldflda ""S S1.field"" IL_0023: ldflda ""int* S.x"" IL_0028: ldflda ""int S.e__FixedBuffer.FixedElementField"" - IL_002d: ldind.i4 - IL_002e: call ""void System.Console.WriteLine(int)"" - IL_0033: ret + IL_002d: stloc.1 + IL_002e: ldloc.1 + IL_002f: ldind.i4 + IL_0030: call ""void System.Console.WriteLine(int)"" + IL_0035: ret }"); } @@ -227,8 +230,9 @@ unsafe static void Main() CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: "12", verify: Verification.Passes) .VerifyIL("Program.Main", @" { - // Code size 46 (0x2e) + // Code size 48 (0x30) .maxstack 3 + .locals init (int& V_0) //i IL_0000: newobj ""C1..ctor()"" IL_0005: dup IL_0006: ldflda ""S C1.field"" @@ -239,9 +243,11 @@ .maxstack 3 IL_0018: ldflda ""S C1.field"" IL_001d: ldflda ""int* S.x"" IL_0022: ldflda ""int S.e__FixedBuffer.FixedElementField"" - IL_0027: ldind.i4 - IL_0028: call ""void System.Console.WriteLine(int)"" - IL_002d: ret + IL_0027: stloc.0 + IL_0028: ldloc.0 + IL_0029: ldind.i4 + IL_002a: call ""void System.Console.WriteLine(int)"" + IL_002f: ret }"); } @@ -278,8 +284,9 @@ unsafe static void Main() CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: "12", verify: Verification.Passes) .VerifyIL("Program.Main", @" { - // Code size 58 (0x3a) + // Code size 60 (0x3c) .maxstack 3 + .locals init (int& V_0) //i IL_0000: newobj ""C1..ctor()"" IL_0005: dup IL_0006: ldflda ""S C1.field"" @@ -293,9 +300,11 @@ .maxstack 3 IL_0024: ldflda ""S C1.field"" IL_0029: ldflda ""int* S.x"" IL_002e: ldflda ""int S.e__FixedBuffer.FixedElementField"" - IL_0033: ldind.i4 - IL_0034: call ""void System.Console.WriteLine(int)"" - IL_0039: ret + IL_0033: stloc.0 + IL_0034: ldloc.0 + IL_0035: ldind.i4 + IL_0036: call ""void System.Console.WriteLine(int)"" + IL_003b: ret }"); } @@ -439,16 +448,17 @@ unsafe static void Main() CompileAndVerify(text, options: TestOptions.UnsafeReleaseExe, expectedOutput: "12", verify: Verification.Fails) .VerifyIL("Program.Main", @" { - // Code size 62 (0x3e) + // Code size 64 (0x40) .maxstack 4 - .locals init (pinned int& V_0) + .locals init (int& V_0, //i + pinned int& V_1) IL_0000: newobj ""S1..ctor()"" IL_0005: dup IL_0006: ldflda ""S S1.field"" IL_000b: ldflda ""int* S.x"" IL_0010: ldflda ""int S.e__FixedBuffer.FixedElementField"" - IL_0015: stloc.0 - IL_0016: ldloc.0 + IL_0015: stloc.1 + IL_0016: ldloc.1 IL_0017: conv.u IL_0018: ldc.i4.3 IL_0019: conv.i @@ -459,7 +469,7 @@ .locals init (pinned int& V_0) IL_001f: stind.i4 IL_0020: ldc.i4.0 IL_0021: conv.u - IL_0022: stloc.0 + IL_0022: stloc.1 IL_0023: ldflda ""S S1.field"" IL_0028: ldflda ""int* S.x"" IL_002d: ldflda ""int S.e__FixedBuffer.FixedElementField"" @@ -468,9 +478,11 @@ .locals init (pinned int& V_0) IL_0034: ldc.i4.4 IL_0035: mul IL_0036: add - IL_0037: ldind.i4 - IL_0038: call ""void System.Console.WriteLine(int)"" - IL_003d: ret + IL_0037: stloc.0 + IL_0038: ldloc.0 + IL_0039: ldind.i4 + IL_003a: call ""void System.Console.WriteLine(int)"" + IL_003f: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs index d36e398902681..ac044fc67d7a4 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/UnsafeTests.cs @@ -668,6 +668,282 @@ .locals init (S V_0) //s "); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_PointerToRefLocal() + { + // When converting from a pointer to a ref, we must avoid eliding the ref local in IL, so the GC knows to track the ref. + var source = """ + class C + { + unsafe void M(byte* p) + { + ref byte b = ref *p; + b.ToString(); + } + } + """; + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeDebugDll).VerifyIL("C.M", """ + { + // Code size 11 (0xb) + .maxstack 1 + .locals init (byte& V_0) //b + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: call "string byte.ToString()" + IL_0009: pop + IL_000a: ret + } + """); + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeReleaseDll).VerifyIL("C.M", """ + { + // Code size 10 (0xa) + .maxstack 1 + .locals init (byte& V_0) //b + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: call "string byte.ToString()" + IL_0008: pop + IL_0009: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_PointerToRefLocal_Nested() + { + var source = """ + class C + { + unsafe void M(ref byte b1, byte* p) + { + ref byte local = ref b1; + { + ref byte b2 = ref *p; + local = ref b2; + } + local.ToString(); + } + } + """; + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeDebugDll).VerifyIL("C.M", """ + { + // Code size 17 (0x11) + .maxstack 1 + .locals init (byte& V_0, //local + byte& V_1) //b2 + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: stloc.0 + IL_0003: nop + IL_0004: ldarg.2 + IL_0005: stloc.1 + IL_0006: ldloc.1 + IL_0007: stloc.0 + IL_0008: nop + IL_0009: ldloc.0 + IL_000a: call "string byte.ToString()" + IL_000f: pop + IL_0010: ret + } + """); + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeReleaseDll).VerifyIL("C.M", """ + { + // Code size 10 (0xa) + .maxstack 1 + .locals init (byte& V_0) //b2 + IL_0000: ldarg.2 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: call "string byte.ToString()" + IL_0008: pop + IL_0009: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_PointerToRefLocal_Arg() + { + var source = """ + class C + { + unsafe void M1(byte* p) + { + ref byte b = ref M2(ref *p); + b.ToString(); + } + ref byte M2(ref byte b) => ref b; + } + """; + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeDebugDll).VerifyIL("C.M1", """ + { + // Code size 17 (0x11) + .maxstack 2 + .locals init (byte& V_0) //b + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "ref byte C.M2(ref byte)" + IL_0008: stloc.0 + IL_0009: ldloc.0 + IL_000a: call "string byte.ToString()" + IL_000f: pop + IL_0010: ret + } + """); + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeReleaseDll).VerifyIL("C.M1", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "ref byte C.M2(ref byte)" + IL_0007: call "string byte.ToString()" + IL_000c: pop + IL_000d: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_PointerToRefLocal_Arg_EvaluationOrder() + { + // Perhaps the pointer-to-ref conversion should be preserved in the IL (via a ref local) + // so GC can re-track it before evaulating other arguments (which can have side effects), + // but there is no ref local in the C# source, so the current behavior might be expected. + var source = """ + class C + { + unsafe void M1(byte* p) + { + ref byte b = ref M2(ref *p, M3()); + b.ToString(); + } + ref byte M2(ref byte b, int i) => ref b; + int M3() => 42; + } + """; + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeDebugDll).VerifyIL("C.M1", """ + { + // Code size 23 (0x17) + .maxstack 3 + .locals init (byte& V_0) //b + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: ldarg.0 + IL_0004: call "int C.M3()" + IL_0009: call "ref byte C.M2(ref byte, int)" + IL_000e: stloc.0 + IL_000f: ldloc.0 + IL_0010: call "string byte.ToString()" + IL_0015: pop + IL_0016: ret + } + """); + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeReleaseDll).VerifyIL("C.M1", """ + { + // Code size 20 (0x14) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.0 + IL_0003: call "int C.M3()" + IL_0008: call "ref byte C.M2(ref byte, int)" + IL_000d: call "string byte.ToString()" + IL_0012: pop + IL_0013: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_PointerToRefLocal_Synthesized() + { + // Implicit (synthesized) ref locals can be elided. + var source = """ + class C + { + unsafe void M(byte* p) + { + (*p)++; + } + } + """; + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeDebugDll).VerifyIL("C.M", """ + { + // Code size 9 (0x9) + .maxstack 3 + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: dup + IL_0003: ldind.u1 + IL_0004: ldc.i4.1 + IL_0005: add + IL_0006: conv.u1 + IL_0007: stind.i1 + IL_0008: ret + } + """); + CompileAndVerify(source, verify: Verification.Fails, options: TestOptions.UnsafeReleaseDll).VerifyIL("C.M", """ + { + // Code size 8 (0x8) + .maxstack 3 + IL_0000: ldarg.1 + IL_0001: dup + IL_0002: ldind.u1 + IL_0003: ldc.i4.1 + IL_0004: add + IL_0005: conv.u1 + IL_0006: stind.i1 + IL_0007: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79051")] + public void Retrack_RefLocalToRefLocal() + { + // There is no pointer-to-ref conversion, so the ref local b2 can be elided in Release mode. + var source = """ + class C + { + void M(ref byte b1) + { + ref byte b2 = ref b1; + b2.ToString(); + } + } + """; + CompileAndVerify(source, options: TestOptions.DebugDll).VerifyIL("C.M", """ + { + // Code size 11 (0xb) + .maxstack 1 + .locals init (byte& V_0) //b2 + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: call "string byte.ToString()" + IL_0009: pop + IL_000a: ret + } + """); + CompileAndVerify(source, options: TestOptions.ReleaseDll).VerifyIL("C.M", """ + { + // Code size 8 (0x8) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: call "string byte.ToString()" + IL_0006: pop + IL_0007: ret + } + """); + } + #endregion Dereference tests #region Pointer member access tests diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 292b8336a9ee2..41b3ab5677a5b 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -9525,7 +9525,8 @@ .locals init (System.ReadOnlySpan V_0, //y System.ReadOnlySpan V_3, int V_4, int[] V_5, - System.Span V_6) + System.Span V_6, + System.Span V_7) IL_0000: ldtoken ".__StaticArrayInitTypeSize=8_Align=4 .34FB5C825DE7CA4AEA6E712F19D439C1DA0C92C37B423936C5F618545CA4FA1F4" IL_0005: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" IL_000a: stloc.0 @@ -9561,8 +9562,8 @@ .locals init (System.ReadOnlySpan V_0, //y IL_0056: ldloca.s V_3 IL_0058: ldloc.s V_5 IL_005a: newobj "System.Span..ctor(int[])" - IL_005f: stloc.s V_6 - IL_0061: ldloca.s V_6 + IL_005f: stloc.s V_7 + IL_0061: ldloca.s V_7 IL_0063: ldloc.s V_4 IL_0065: ldloca.s V_3 IL_0067: call "int System.ReadOnlySpan.Length.get" @@ -11541,7 +11542,8 @@ .locals init (System.Collections.Generic.List V_0, T[] V_2, System.Span V_3, System.Span V_4, - System.Span V_5) + System.Span V_5, + System.Span V_6) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stloc.0 @@ -11577,8 +11579,8 @@ .locals init (System.Collections.Generic.List V_0, IL_004e: ldloca.s V_4 IL_0050: ldloc.2 IL_0051: newobj "System.Span..ctor(T[])" - IL_0056: stloc.s V_5 - IL_0058: ldloca.s V_5 + IL_0056: stloc.s V_6 + IL_0058: ldloca.s V_6 IL_005a: ldloc.1 IL_005b: ldloca.s V_4 IL_005d: call "int System.Span.Length.get" @@ -11645,7 +11647,8 @@ .locals init (int[] V_0, int V_10, int V_11, int V_12, - int V_13) + int V_13, + System.Span V_14) IL_0000: ldstr "A" IL_0005: ldc.i4.2 IL_0006: newarr "int" @@ -11762,8 +11765,8 @@ .locals init (int[] V_0, IL_0105: ldloca.s V_6 IL_0107: ldloc.s V_4 IL_0109: newobj "System.Span..ctor(int[])" - IL_010e: stloc.s V_7 - IL_0110: ldloca.s V_7 + IL_010e: stloc.s V_14 + IL_0110: ldloca.s V_14 IL_0112: ldloc.3 IL_0113: ldloca.s V_6 IL_0115: call "int System.Span.Length.get" @@ -33712,7 +33715,8 @@ .locals init (System.ReadOnlySpan V_0, //li1 System.ReadOnlySpan V_3, int V_4, int[] V_5, - System.Span V_6) + System.Span V_6, + System.Span V_7) IL_0000: ldtoken ".__StaticArrayInitTypeSize=12_Align=4 .4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4" IL_0005: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" IL_000a: stloc.0 @@ -33754,8 +33758,8 @@ .locals init (System.ReadOnlySpan V_0, //li1 IL_006d: ldloca.s V_3 IL_006f: ldloc.s V_5 IL_0071: newobj "System.Span..ctor(int[])" - IL_0076: stloc.s V_6 - IL_0078: ldloca.s V_6 + IL_0076: stloc.s V_7 + IL_0078: ldloca.s V_7 IL_007a: ldloc.s V_4 IL_007c: ldloca.s V_3 IL_007e: call "int System.ReadOnlySpan.Length.get" @@ -33811,7 +33815,8 @@ .locals init (System.ReadOnlySpan V_0, //li1 C[] V_6, System.ReadOnlySpan.Enumerator V_7, D V_8, - System.ReadOnlySpan V_9) + System.ReadOnlySpan.Enumerator V_9, + System.ReadOnlySpan V_10) IL_0000: newobj "D..ctor()" IL_0005: stloc.1 IL_0006: ldloca.s V_1 @@ -33854,9 +33859,9 @@ .locals init (System.ReadOnlySpan V_0, //li1 IL_0061: brtrue.s IL_0043 IL_0063: ldloca.s V_4 IL_0065: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_006a: stloc.s V_7 + IL_006a: stloc.s V_9 IL_006c: br.s IL_0085 - IL_006e: ldloca.s V_7 + IL_006e: ldloca.s V_9 IL_0070: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" IL_0075: ldind.ref IL_0076: stloc.s V_8 @@ -33868,13 +33873,13 @@ .locals init (System.ReadOnlySpan V_0, //li1 IL_0081: ldc.i4.1 IL_0082: add IL_0083: stloc.s V_5 - IL_0085: ldloca.s V_7 + IL_0085: ldloca.s V_9 IL_0087: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" IL_008c: brtrue.s IL_006e IL_008e: ldloc.s V_6 IL_0090: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(C[])" - IL_0095: stloc.s V_9 - IL_0097: ldloca.s V_9 + IL_0095: stloc.s V_10 + IL_0097: ldloca.s V_10 IL_0099: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" IL_009e: ret } @@ -34231,7 +34236,8 @@ .locals init (Base[] V_0, int V_3, Base[] V_4, System.ReadOnlySpan V_5, - System.Span V_6) + System.Span V_6, + System.Span V_7) IL_0000: ldc.i4.1 IL_0001: newarr "Derived" IL_0006: dup @@ -34283,8 +34289,8 @@ .locals init (Base[] V_0, IL_006a: ldloca.s V_5 IL_006c: ldloc.s V_4 IL_006e: newobj "System.Span..ctor(Base[])" - IL_0073: stloc.s V_6 - IL_0075: ldloca.s V_6 + IL_0073: stloc.s V_7 + IL_0075: ldloca.s V_7 IL_0077: ldloc.3 IL_0078: ldloca.s V_5 IL_007a: call "int System.ReadOnlySpan.Length.get" @@ -35283,7 +35289,8 @@ .locals init (int[] V_0, int[] V_3, System.ReadOnlySpan V_4, System.ReadOnlySpan V_5, - System.Span V_6) + System.Span V_6, + System.Span V_7) IL_0000: ldc.i4.2 IL_0001: newarr "int" IL_0006: dup @@ -35335,8 +35342,8 @@ .locals init (int[] V_0, IL_005f: ldloca.s V_5 IL_0061: ldloc.3 IL_0062: newobj "System.Span..ctor(int[])" - IL_0067: stloc.s V_6 - IL_0069: ldloca.s V_6 + IL_0067: stloc.s V_7 + IL_0069: ldloca.s V_7 IL_006b: ldloc.2 IL_006c: ldloca.s V_5 IL_006e: call "int System.ReadOnlySpan.Length.get" @@ -35564,7 +35571,8 @@ .locals init (int V_0, System.ReadOnlySpan V_6, System.Runtime.CompilerServices.TaskAwaiter V_7, System.Span V_8, - System.Exception V_9) + System.Span V_9, + System.Exception V_10) IL_0000: ldarg.0 IL_0001: ldfld "int C.
d__0.<>1__state" IL_0006: stloc.0 @@ -35659,8 +35667,8 @@ .locals init (int V_0, IL_00ea: ldarg.0 IL_00eb: ldfld "int[] C.
d__0.<>7__wrap4" IL_00f0: newobj "System.Span..ctor(int[])" - IL_00f5: stloc.s V_8 - IL_00f7: ldloca.s V_8 + IL_00f5: stloc.s V_9 + IL_00f7: ldloca.s V_9 IL_00f9: ldloc.s V_4 IL_00fb: ldloca.s V_6 IL_00fd: call "int System.ReadOnlySpan.Length.get" @@ -35739,13 +35747,13 @@ .locals init (int V_0, } catch System.Exception { - IL_01c5: stloc.s V_9 + IL_01c5: stloc.s V_10 IL_01c7: ldarg.0 IL_01c8: ldc.i4.s -2 IL_01ca: stfld "int C.
d__0.<>1__state" IL_01cf: ldarg.0 IL_01d0: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" - IL_01d5: ldloc.s V_9 + IL_01d5: ldloc.s V_10 IL_01d7: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)" IL_01dc: leave.s IL_01f1 } @@ -35872,13 +35880,14 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { - // Code size 146 (0x92) + // Code size 147 (0x93) .maxstack 4 .locals init (int V_0, System.Span V_1, int V_2, System.Collections.Generic.List V_3, - System.Span V_4) + System.Span V_4, + System.Span V_5) IL_0000: ldc.i4.3 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -35928,27 +35937,27 @@ .locals init (int V_0, IL_0055: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" IL_005a: ldloc.3 IL_005b: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0060: stloc.1 - IL_0061: ldc.i4.0 - IL_0062: stloc.0 - IL_0063: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0068: stloc.s V_4 - IL_006a: ldloca.s V_4 - IL_006c: ldloca.s V_1 - IL_006e: ldloc.0 - IL_006f: ldloca.s V_4 - IL_0071: call "int System.Span.Length.get" - IL_0076: call "System.Span System.Span.Slice(int, int)" - IL_007b: call "void System.Span.CopyTo(System.Span)" - IL_0080: ldloc.0 - IL_0081: ldloca.s V_4 - IL_0083: call "int System.Span.Length.get" - IL_0088: add - IL_0089: stloc.0 - IL_008a: ldloc.3 - IL_008b: ldc.i4.0 - IL_008c: call "void CollectionExtensions.Report(object, bool)" - IL_0091: ret + IL_0060: stloc.s V_4 + IL_0062: ldc.i4.0 + IL_0063: stloc.0 + IL_0064: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0069: stloc.s V_5 + IL_006b: ldloca.s V_5 + IL_006d: ldloca.s V_4 + IL_006f: ldloc.0 + IL_0070: ldloca.s V_5 + IL_0072: call "int System.Span.Length.get" + IL_0077: call "System.Span System.Span.Slice(int, int)" + IL_007c: call "void System.Span.CopyTo(System.Span)" + IL_0081: ldloc.0 + IL_0082: ldloca.s V_5 + IL_0084: call "int System.Span.Length.get" + IL_0089: add + IL_008a: stloc.0 + IL_008b: ldloc.3 + IL_008c: ldc.i4.0 + IL_008d: call "void CollectionExtensions.Report(object, bool)" + IL_0092: ret } """); } @@ -37859,15 +37868,16 @@ static void Main() // Ideally we'd like to be able to use *both* something like AddRange, *and* AsSpan/CopyTo/etc. while building the same target collection verifier.VerifyIL("C.Main", """ { - // Code size 181 (0xb5) + // Code size 182 (0xb6) .maxstack 3 .locals init (int V_0, System.Span V_1, int V_2, System.Collections.Generic.ICollection V_3, System.Collections.Generic.List V_4, - System.Collections.Generic.IEnumerator V_5, - int V_6) + System.Span V_5, + System.Collections.Generic.IEnumerator V_6, + int V_7) IL_0000: ldc.i4.3 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -37920,49 +37930,49 @@ .locals init (int V_0, IL_005a: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" IL_005f: ldloc.s V_4 IL_0061: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0066: stloc.1 - IL_0067: ldc.i4.0 - IL_0068: stloc.0 - IL_0069: ldloc.3 - IL_006a: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" - IL_006f: stloc.s V_5 + IL_0066: stloc.s V_5 + IL_0068: ldc.i4.0 + IL_0069: stloc.0 + IL_006a: ldloc.3 + IL_006b: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" + IL_0070: stloc.s V_6 .try { - IL_0071: br.s IL_008b - IL_0073: ldloc.s V_5 - IL_0075: callvirt "int System.Collections.Generic.IEnumerator.Current.get" - IL_007a: stloc.s V_6 - IL_007c: ldloca.s V_1 - IL_007e: ldloc.0 - IL_007f: call "ref int System.Span.this[int].get" - IL_0084: ldloc.s V_6 - IL_0086: stind.i4 - IL_0087: ldloc.0 - IL_0088: ldc.i4.1 - IL_0089: add - IL_008a: stloc.0 - IL_008b: ldloc.s V_5 - IL_008d: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_0092: brtrue.s IL_0073 - IL_0094: leave.s IL_00a2 + IL_0072: br.s IL_008c + IL_0074: ldloc.s V_6 + IL_0076: callvirt "int System.Collections.Generic.IEnumerator.Current.get" + IL_007b: stloc.s V_7 + IL_007d: ldloca.s V_5 + IL_007f: ldloc.0 + IL_0080: call "ref int System.Span.this[int].get" + IL_0085: ldloc.s V_7 + IL_0087: stind.i4 + IL_0088: ldloc.0 + IL_0089: ldc.i4.1 + IL_008a: add + IL_008b: stloc.0 + IL_008c: ldloc.s V_6 + IL_008e: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_0093: brtrue.s IL_0074 + IL_0095: leave.s IL_00a3 } finally { - IL_0096: ldloc.s V_5 - IL_0098: brfalse.s IL_00a1 - IL_009a: ldloc.s V_5 - IL_009c: callvirt "void System.IDisposable.Dispose()" - IL_00a1: endfinally + IL_0097: ldloc.s V_6 + IL_0099: brfalse.s IL_00a2 + IL_009b: ldloc.s V_6 + IL_009d: callvirt "void System.IDisposable.Dispose()" + IL_00a2: endfinally } - IL_00a2: ldloca.s V_1 - IL_00a4: ldloc.0 - IL_00a5: call "ref int System.Span.this[int].get" - IL_00aa: ldc.i4.4 - IL_00ab: stind.i4 - IL_00ac: ldloc.s V_4 - IL_00ae: ldc.i4.0 - IL_00af: call "void CollectionExtensions.Report(object, bool)" - IL_00b4: ret + IL_00a3: ldloca.s V_5 + IL_00a5: ldloc.0 + IL_00a6: call "ref int System.Span.this[int].get" + IL_00ab: ldc.i4.4 + IL_00ac: stind.i4 + IL_00ad: ldloc.s V_4 + IL_00af: ldc.i4.0 + IL_00b0: call "void CollectionExtensions.Report(object, bool)" + IL_00b5: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/InlineArrayTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/InlineArrayTests.cs index 027e23174b049..a9621b5999619 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/InlineArrayTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/InlineArrayTests.cs @@ -7339,13 +7339,14 @@ static async Task FromResult(T r) verifier.VerifyIL("Program.d__2.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" { - // Code size 208 (0xd0) + // Code size 209 (0xd1) .maxstack 3 .locals init (int V_0, int V_1, System.Runtime.CompilerServices.TaskAwaiter V_2, System.Span V_3, - System.Exception V_4) + System.Span V_4, + System.Exception V_5) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.d__2.<>1__state"" IL_0006: stloc.0 @@ -7377,7 +7378,7 @@ .locals init (int V_0, IL_0041: ldloca.s V_2 IL_0043: ldarg.0 IL_0044: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Program.d__2>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Program.d__2)"" - IL_0049: leave IL_00cf + IL_0049: leave IL_00d0 IL_004e: ldarg.0 IL_004f: ldfld ""System.Runtime.CompilerServices.TaskAwaiter Program.d__2.<>u__1"" IL_0054: stloc.2 @@ -7402,36 +7403,36 @@ .locals init (int V_0, IL_0087: ldc.i4.0 IL_0088: ldloc.1 IL_0089: call ""System.Span System.Span.Slice(int, int)"" - IL_008e: stloc.3 - IL_008f: ldloca.s V_3 - IL_0091: ldc.i4.0 - IL_0092: call ""ref int System.Span.this[int].get"" - IL_0097: ldc.i4.s 111 - IL_0099: stind.i4 - IL_009a: ldarg.0 - IL_009b: ldnull - IL_009c: stfld ""C Program.d__2.<>7__wrap1"" - IL_00a1: leave.s IL_00bc + IL_008e: stloc.s V_4 + IL_0090: ldloca.s V_4 + IL_0092: ldc.i4.0 + IL_0093: call ""ref int System.Span.this[int].get"" + IL_0098: ldc.i4.s 111 + IL_009a: stind.i4 + IL_009b: ldarg.0 + IL_009c: ldnull + IL_009d: stfld ""C Program.d__2.<>7__wrap1"" + IL_00a2: leave.s IL_00bd } catch System.Exception { - IL_00a3: stloc.s V_4 - IL_00a5: ldarg.0 - IL_00a6: ldc.i4.s -2 - IL_00a8: stfld ""int Program.d__2.<>1__state"" - IL_00ad: ldarg.0 - IL_00ae: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" - IL_00b3: ldloc.s V_4 - IL_00b5: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" - IL_00ba: leave.s IL_00cf + IL_00a4: stloc.s V_5 + IL_00a6: ldarg.0 + IL_00a7: ldc.i4.s -2 + IL_00a9: stfld ""int Program.d__2.<>1__state"" + IL_00ae: ldarg.0 + IL_00af: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" + IL_00b4: ldloc.s V_5 + IL_00b6: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00bb: leave.s IL_00d0 } - IL_00bc: ldarg.0 - IL_00bd: ldc.i4.s -2 - IL_00bf: stfld ""int Program.d__2.<>1__state"" - IL_00c4: ldarg.0 - IL_00c5: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" - IL_00ca: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" - IL_00cf: ret + IL_00bd: ldarg.0 + IL_00be: ldc.i4.s -2 + IL_00c0: stfld ""int Program.d__2.<>1__state"" + IL_00c5: ldarg.0 + IL_00c6: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" + IL_00cb: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_00d0: ret } "); } @@ -7482,7 +7483,8 @@ .locals init (int V_0, int V_2, System.Runtime.CompilerServices.TaskAwaiter V_3, System.Span V_4, - System.Exception V_5) + System.Span V_5, + System.Exception V_6) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.d__2.<>1__state"" IL_0006: stloc.0 @@ -7547,8 +7549,8 @@ .locals init (int V_0, IL_0098: ldloc.2 IL_0099: sub IL_009a: call ""System.Span System.Span.Slice(int, int)"" - IL_009f: stloc.s V_4 - IL_00a1: ldloca.s V_4 + IL_009f: stloc.s V_5 + IL_00a1: ldloca.s V_5 IL_00a3: ldc.i4.0 IL_00a4: call ""ref int System.Span.this[int].get"" IL_00a9: ldc.i4.s 111 @@ -7560,13 +7562,13 @@ .locals init (int V_0, } catch System.Exception { - IL_00b5: stloc.s V_5 + IL_00b5: stloc.s V_6 IL_00b7: ldarg.0 IL_00b8: ldc.i4.s -2 IL_00ba: stfld ""int Program.d__2.<>1__state"" IL_00bf: ldarg.0 IL_00c0: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" - IL_00c5: ldloc.s V_5 + IL_00c5: ldloc.s V_6 IL_00c7: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" IL_00cc: leave.s IL_00e1 } @@ -7630,7 +7632,8 @@ .locals init (int V_0, System.Runtime.CompilerServices.TaskAwaiter V_5, System.Index V_6, System.Span V_7, - System.Exception V_8) + System.Span V_8, + System.Exception V_9) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.d__2.<>1__state"" IL_0006: stloc.0 @@ -7711,8 +7714,8 @@ .locals init (int V_0, IL_00cc: ldloc.3 IL_00cd: ldloc.s V_4 IL_00cf: call ""System.Span System.Span.Slice(int, int)"" - IL_00d4: stloc.s V_7 - IL_00d6: ldloca.s V_7 + IL_00d4: stloc.s V_8 + IL_00d6: ldloca.s V_8 IL_00d8: ldc.i4.0 IL_00d9: call ""ref int System.Span.this[int].get"" IL_00de: ldc.i4.s 111 @@ -7724,13 +7727,13 @@ .locals init (int V_0, } catch System.Exception { - IL_00ea: stloc.s V_8 + IL_00ea: stloc.s V_9 IL_00ec: ldarg.0 IL_00ed: ldc.i4.s -2 IL_00ef: stfld ""int Program.d__2.<>1__state"" IL_00f4: ldarg.0 IL_00f5: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" - IL_00fa: ldloc.s V_8 + IL_00fa: ldloc.s V_9 IL_00fc: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" IL_0101: leave.s IL_0116 } @@ -7795,7 +7798,8 @@ .locals init (int V_0, int V_2, System.Runtime.CompilerServices.TaskAwaiter V_3, System.Span V_4, - System.Exception V_5) + System.Span V_5, + System.Exception V_6) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.d__2.<>1__state"" IL_0006: stloc.0 @@ -7872,8 +7876,8 @@ .locals init (int V_0, IL_00c5: ldarg.0 IL_00c6: ldfld ""int Program.d__2.<>7__wrap2"" IL_00cb: call ""System.Span System.Span.Slice(int, int)"" - IL_00d0: stloc.s V_4 - IL_00d2: ldloca.s V_4 + IL_00d0: stloc.s V_5 + IL_00d2: ldloca.s V_5 IL_00d4: ldarg.0 IL_00d5: ldfld ""int Program.d__2.<>7__wrap3"" IL_00da: call ""ref int System.Span.this[int].get"" @@ -7886,13 +7890,13 @@ .locals init (int V_0, } catch System.Exception { - IL_00ea: stloc.s V_5 + IL_00ea: stloc.s V_6 IL_00ec: ldarg.0 IL_00ed: ldc.i4.s -2 IL_00ef: stfld ""int Program.d__2.<>1__state"" IL_00f4: ldarg.0 IL_00f5: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__2.<>t__builder"" - IL_00fa: ldloc.s V_5 + IL_00fa: ldloc.s V_6 IL_00fc: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" IL_0101: leave.s IL_0116 } @@ -7961,7 +7965,8 @@ .locals init (int V_0, System.Index V_6, System.Runtime.CompilerServices.TaskAwaiter V_7, System.ReadOnlySpan V_8, - System.Exception V_9) + System.ReadOnlySpan V_9, + System.Exception V_10) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.d__1.<>1__state"" IL_0006: stloc.0 @@ -8051,8 +8056,8 @@ .locals init (int V_0, IL_00ee: ldarg.0 IL_00ef: ldfld ""int Program.d__1.<>7__wrap2"" IL_00f4: call ""System.ReadOnlySpan System.ReadOnlySpan.Slice(int, int)"" - IL_00f9: stloc.s V_8 - IL_00fb: ldloca.s V_8 + IL_00f9: stloc.s V_9 + IL_00fb: ldloca.s V_9 IL_00fd: ldloc.s V_5 IL_00ff: call ""ref readonly int System.ReadOnlySpan.this[int].get"" IL_0104: ldind.i4 @@ -8061,13 +8066,13 @@ .locals init (int V_0, } catch System.Exception { - IL_0108: stloc.s V_9 + IL_0108: stloc.s V_10 IL_010a: ldarg.0 IL_010b: ldc.i4.s -2 IL_010d: stfld ""int Program.d__1.<>1__state"" IL_0112: ldarg.0 IL_0113: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.d__1.<>t__builder"" - IL_0118: ldloc.s V_9 + IL_0118: ldloc.s V_10 IL_011a: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" IL_011f: leave.s IL_0135 } @@ -8813,7 +8818,8 @@ static int M4(in int x, Buffer10 y) // Code size 32 (0x20) .maxstack 2 .locals init (Buffer10 V_0, - int V_1) + int V_1, + Buffer10 V_2) IL_0000: call ""Buffer10 Program.M3()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 @@ -8821,9 +8827,9 @@ .locals init (Buffer10 V_0, IL_000d: ldind.i4 IL_000e: stloc.1 IL_000f: ldloca.s V_1 - IL_0011: ldloca.s V_0 + IL_0011: ldloca.s V_2 IL_0013: initobj ""Buffer10"" - IL_0019: ldloc.0 + IL_0019: ldloc.2 IL_001a: call ""int Program.M4(in int, Buffer10)"" IL_001f: ret } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 1c2864d66eac5..7ae0f73ffafb3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -3822,7 +3822,8 @@ public void NestedInterpolatedStrings_02(string expression) .maxstack 4 .locals init (int V_0, //i1 int V_1, //i2 - System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_2) + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_2, + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_3) IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldc.i4.2 @@ -3836,14 +3837,14 @@ .locals init (int V_0, //i1 IL_0010: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" IL_0015: ldloca.s V_2 IL_0017: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_001c: ldloca.s V_2 + IL_001c: ldloca.s V_3 IL_001e: ldc.i4.0 IL_001f: ldc.i4.1 IL_0020: call ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" - IL_0025: ldloca.s V_2 + IL_0025: ldloca.s V_3 IL_0027: ldloc.1 IL_0028: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" - IL_002d: ldloca.s V_2 + IL_002d: ldloca.s V_3 IL_002f: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" IL_0034: call ""string string.Concat(string, string)"" IL_0039: call ""void System.Console.WriteLine(string)"" @@ -10151,14 +10152,15 @@ Creating DummyHandler from StructLogger#2 verifier.VerifyIL("", @" { - // Code size 175 (0xaf) + // Code size 177 (0xb1) .maxstack 4 .locals init (int V_0, //i StructLogger V_1, //s StructLogger V_2, DummyHandler V_3, bool V_4, - StructLogger& V_5) + StructLogger& V_5, + DummyHandler V_6) IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldloca.s V_2 @@ -10207,32 +10209,32 @@ .locals init (int V_0, //i IL_0064: ldobj ""StructLogger"" IL_0069: ldloca.s V_4 IL_006b: newobj ""DummyHandler..ctor(int, int, StructLogger, out bool)"" - IL_0070: stloc.3 - IL_0071: ldloc.s V_4 - IL_0073: brfalse.s IL_008f - IL_0075: ldloca.s V_3 - IL_0077: ldstr ""log:"" - IL_007c: call ""void DummyHandler.AppendLiteral(string)"" - IL_0081: nop - IL_0082: ldloca.s V_3 - IL_0084: ldloc.0 - IL_0085: dup - IL_0086: ldc.i4.1 - IL_0087: add - IL_0088: stloc.0 - IL_0089: call ""void DummyHandler.AppendFormatted(int)"" - IL_008e: nop - IL_008f: ldloc.s V_5 - IL_0091: ldloc.3 - IL_0092: call ""void StructLogger.Log(DummyHandler)"" - IL_0097: nop - IL_0098: ldstr ""(2) i={0}"" - IL_009d: ldloc.0 - IL_009e: box ""int"" - IL_00a3: call ""string string.Format(string, object)"" - IL_00a8: call ""void System.Console.WriteLine(string)"" - IL_00ad: nop - IL_00ae: ret + IL_0070: stloc.s V_6 + IL_0072: ldloc.s V_4 + IL_0074: brfalse.s IL_0090 + IL_0076: ldloca.s V_6 + IL_0078: ldstr ""log:"" + IL_007d: call ""void DummyHandler.AppendLiteral(string)"" + IL_0082: nop + IL_0083: ldloca.s V_6 + IL_0085: ldloc.0 + IL_0086: dup + IL_0087: ldc.i4.1 + IL_0088: add + IL_0089: stloc.0 + IL_008a: call ""void DummyHandler.AppendFormatted(int)"" + IL_008f: nop + IL_0090: ldloc.s V_5 + IL_0092: ldloc.s V_6 + IL_0094: call ""void StructLogger.Log(DummyHandler)"" + IL_0099: nop + IL_009a: ldstr ""(2) i={0}"" + IL_009f: ldloc.0 + IL_00a0: box ""int"" + IL_00a5: call ""string string.Format(string, object)"" + IL_00aa: call ""void System.Console.WriteLine(string)"" + IL_00af: nop + IL_00b0: ret } "); } @@ -16800,13 +16802,14 @@ public void ParenthesizedAdditiveExpression_04() verifier.VerifyIL("Program.<
$>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 244 (0xf4) + // Code size 245 (0xf5) .maxstack 4 .locals init (int V_0, int V_1, System.Runtime.CompilerServices.TaskAwaiter V_2, System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_3, - System.Exception V_4) + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_4, + System.Exception V_5) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.<
$>d__0.<>1__state"" IL_0006: stloc.0 @@ -16839,7 +16842,7 @@ .locals init (int V_0, IL_0042: ldloca.s V_2 IL_0044: ldarg.0 IL_0045: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Program.<
$>d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Program.<
$>d__0)"" - IL_004a: leave IL_00f3 + IL_004a: leave IL_00f4 IL_004f: ldarg.0 IL_0050: ldfld ""System.Runtime.CompilerServices.TaskAwaiter Program.<
$>d__0.<>u__1"" IL_0055: stloc.2 @@ -16871,36 +16874,36 @@ .locals init (int V_0, IL_009f: ldc.i4.0 IL_00a0: ldc.i4.1 IL_00a1: newobj ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" - IL_00a6: stloc.3 - IL_00a7: ldloca.s V_3 - IL_00a9: ldarg.0 - IL_00aa: ldfld ""int Program.<
$>d__0.5__3"" - IL_00af: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" - IL_00b4: ldloca.s V_3 - IL_00b6: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_00bb: call ""string string.Concat(string, string, string)"" - IL_00c0: call ""void System.Console.WriteLine(string)"" - IL_00c5: leave.s IL_00e0 + IL_00a6: stloc.s V_4 + IL_00a8: ldloca.s V_4 + IL_00aa: ldarg.0 + IL_00ab: ldfld ""int Program.<
$>d__0.5__3"" + IL_00b0: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" + IL_00b5: ldloca.s V_4 + IL_00b7: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" + IL_00bc: call ""string string.Concat(string, string, string)"" + IL_00c1: call ""void System.Console.WriteLine(string)"" + IL_00c6: leave.s IL_00e1 } catch System.Exception { - IL_00c7: stloc.s V_4 - IL_00c9: ldarg.0 - IL_00ca: ldc.i4.s -2 - IL_00cc: stfld ""int Program.<
$>d__0.<>1__state"" - IL_00d1: ldarg.0 - IL_00d2: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" - IL_00d7: ldloc.s V_4 - IL_00d9: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" - IL_00de: leave.s IL_00f3 + IL_00c8: stloc.s V_5 + IL_00ca: ldarg.0 + IL_00cb: ldc.i4.s -2 + IL_00cd: stfld ""int Program.<
$>d__0.<>1__state"" + IL_00d2: ldarg.0 + IL_00d3: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" + IL_00d8: ldloc.s V_5 + IL_00da: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00df: leave.s IL_00f4 } - IL_00e0: ldarg.0 - IL_00e1: ldc.i4.s -2 - IL_00e3: stfld ""int Program.<
$>d__0.<>1__state"" - IL_00e8: ldarg.0 - IL_00e9: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" - IL_00ee: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" - IL_00f3: ret + IL_00e1: ldarg.0 + IL_00e2: ldc.i4.s -2 + IL_00e4: stfld ""int Program.<
$>d__0.<>1__state"" + IL_00e9: ldarg.0 + IL_00ea: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" + IL_00ef: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_00f4: ret }"); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs index 9770e5eb3930b..81c060e72a02b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs @@ -2223,7 +2223,8 @@ public void NestedInterpolatedStrings_02(string expression) .maxstack 4 .locals init (int V_0, //i1 int V_1, //i2 - System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_2) + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_2, + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_3) IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldc.i4.2 @@ -2237,14 +2238,14 @@ .locals init (int V_0, //i1 IL_0010: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" IL_0015: ldloca.s V_2 IL_0017: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_001c: ldloca.s V_2 + IL_001c: ldloca.s V_3 IL_001e: ldc.i4.0 IL_001f: ldc.i4.1 IL_0020: call ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" - IL_0025: ldloca.s V_2 + IL_0025: ldloca.s V_3 IL_0027: ldloc.1 IL_0028: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" - IL_002d: ldloca.s V_2 + IL_002d: ldloca.s V_3 IL_002f: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" IL_0034: call ""string string.Concat(string, string)"" IL_0039: call ""void System.Console.WriteLine(string)"" @@ -12228,13 +12229,14 @@ public void ParenthesizedAdditiveExpression_04() verifier.VerifyIL("Program.<
$>d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 244 (0xf4) + // Code size 245 (0xf5) .maxstack 4 .locals init (int V_0, int V_1, System.Runtime.CompilerServices.TaskAwaiter V_2, System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_3, - System.Exception V_4) + System.Runtime.CompilerServices.DefaultInterpolatedStringHandler V_4, + System.Exception V_5) IL_0000: ldarg.0 IL_0001: ldfld ""int Program.<
$>d__0.<>1__state"" IL_0006: stloc.0 @@ -12267,7 +12269,7 @@ .locals init (int V_0, IL_0042: ldloca.s V_2 IL_0044: ldarg.0 IL_0045: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Program.<
$>d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Program.<
$>d__0)"" - IL_004a: leave IL_00f3 + IL_004a: leave IL_00f4 IL_004f: ldarg.0 IL_0050: ldfld ""System.Runtime.CompilerServices.TaskAwaiter Program.<
$>d__0.<>u__1"" IL_0055: stloc.2 @@ -12299,36 +12301,36 @@ .locals init (int V_0, IL_009f: ldc.i4.0 IL_00a0: ldc.i4.1 IL_00a1: newobj ""System.Runtime.CompilerServices.DefaultInterpolatedStringHandler..ctor(int, int)"" - IL_00a6: stloc.3 - IL_00a7: ldloca.s V_3 - IL_00a9: ldarg.0 - IL_00aa: ldfld ""int Program.<
$>d__0.5__3"" - IL_00af: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" - IL_00b4: ldloca.s V_3 - IL_00b6: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" - IL_00bb: call ""string string.Concat(string, string, string)"" - IL_00c0: call ""void System.Console.WriteLine(string)"" - IL_00c5: leave.s IL_00e0 + IL_00a6: stloc.s V_4 + IL_00a8: ldloca.s V_4 + IL_00aa: ldarg.0 + IL_00ab: ldfld ""int Program.<
$>d__0.5__3"" + IL_00b0: call ""void System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.AppendFormatted(int)"" + IL_00b5: ldloca.s V_4 + IL_00b7: call ""string System.Runtime.CompilerServices.DefaultInterpolatedStringHandler.ToStringAndClear()"" + IL_00bc: call ""string string.Concat(string, string, string)"" + IL_00c1: call ""void System.Console.WriteLine(string)"" + IL_00c6: leave.s IL_00e1 } catch System.Exception { - IL_00c7: stloc.s V_4 - IL_00c9: ldarg.0 - IL_00ca: ldc.i4.s -2 - IL_00cc: stfld ""int Program.<
$>d__0.<>1__state"" - IL_00d1: ldarg.0 - IL_00d2: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" - IL_00d7: ldloc.s V_4 - IL_00d9: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" - IL_00de: leave.s IL_00f3 + IL_00c8: stloc.s V_5 + IL_00ca: ldarg.0 + IL_00cb: ldc.i4.s -2 + IL_00cd: stfld ""int Program.<
$>d__0.<>1__state"" + IL_00d2: ldarg.0 + IL_00d3: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" + IL_00d8: ldloc.s V_5 + IL_00da: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00df: leave.s IL_00f4 } - IL_00e0: ldarg.0 - IL_00e1: ldc.i4.s -2 - IL_00e3: stfld ""int Program.<
$>d__0.<>1__state"" - IL_00e8: ldarg.0 - IL_00e9: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" - IL_00ee: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" - IL_00f3: ret + IL_00e1: ldarg.0 + IL_00e2: ldc.i4.s -2 + IL_00e4: stfld ""int Program.<
$>d__0.<>1__state"" + IL_00e9: ldarg.0 + IL_00ea: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder Program.<
$>d__0.<>t__builder"" + IL_00ef: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_00f4: ret }"); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 0b3a373b843a2..4ab1f964b7031 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -11009,6 +11009,1099 @@ static R F2() Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes() + { + var source = """ + var r1 = new R(111); + var r2 = new R(222); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // Needs two int temps. + .VerifyIL("", """ + { + // Code size 43 (0x2b) + .maxstack 2 + .locals init (R V_0, //r2 + int V_1, + int V_2) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.1 + IL_0003: ldloca.s V_1 + IL_0005: newobj "R..ctor(in int)" + IL_000a: ldc.i4 0xde + IL_000f: stloc.2 + IL_0010: ldloca.s V_2 + IL_0012: newobj "R..ctor(in int)" + IL_0017: stloc.0 + IL_0018: ldfld "ref readonly int R.F" + IL_001d: ldind.i4 + IL_001e: ldloc.0 + IL_001f: ldfld "ref readonly int R.F" + IL_0024: ldind.i4 + IL_0025: call "void Program.<
$>g__Report|0_0(int, int)" + IL_002a: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_NonRefStruct() + { + var source = """ + var r1 = new R(111); + var r2 = new R(222); + + struct R(in int x) + { + public readonly int F = x; + } + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp is enough. + .VerifyIL("", """ + { + // Code size 26 (0x1a) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "R..ctor(in int)" + IL_000a: pop + IL_000b: ldc.i4 0xde + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "R..ctor(in int)" + IL_0018: pop + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_UnusedResult() + { + var source = """ + new R(111); + new R(222); + + ref struct R + { + public R(in int x) { } + } + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp is enough. + .VerifyIL("", """ + { + // Code size 26 (0x1a) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "R..ctor(in int)" + IL_000a: pop + IL_000b: ldc.i4 0xde + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "R..ctor(in int)" + IL_0018: pop + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_ScopedParameter() + { + var source = """ + var r1 = new R(111); + var r2 = new R(222); + Use(r1, r2); + + static void Use(R x, R y) { } + + ref struct R + { + public R(scoped in int x) { } + } + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp is enough. + .VerifyIL("", """ + { + // Code size 31 (0x1f) + .maxstack 2 + .locals init (R V_0, //r2 + int V_1) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.1 + IL_0003: ldloca.s V_1 + IL_0005: newobj "R..ctor(scoped in int)" + IL_000a: ldc.i4 0xde + IL_000f: stloc.1 + IL_0010: ldloca.s V_1 + IL_0012: newobj "R..ctor(scoped in int)" + IL_0017: stloc.0 + IL_0018: ldloc.0 + IL_0019: call "void Program.<
$>g__Use|0_0(R, R)" + IL_001e: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_NestedBlock() + { + var source = """ + { + var r1 = new R(111); + var r2 = new R(222); + Report(r1.F, r2.F); + } + { + var r1 = new R(333); + var r2 = new R(444); + Report(r1.F, r2.F); + } + + static void Report(int x, int y) => System.Console.Write($"{x} {y} "); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222 333 444"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // Two int and two R temps would be enough, but the emit layer currently does not take blocks into account. + .VerifyIL("", """ + { + // Code size 90 (0x5a) + .maxstack 2 + .locals init (R V_0, //r2 + int V_1, + int V_2, + R V_3, //r2 + int V_4, + int V_5) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.1 + IL_0003: ldloca.s V_1 + IL_0005: newobj "R..ctor(in int)" + IL_000a: ldc.i4 0xde + IL_000f: stloc.2 + IL_0010: ldloca.s V_2 + IL_0012: newobj "R..ctor(in int)" + IL_0017: stloc.0 + IL_0018: ldfld "ref readonly int R.F" + IL_001d: ldind.i4 + IL_001e: ldloc.0 + IL_001f: ldfld "ref readonly int R.F" + IL_0024: ldind.i4 + IL_0025: call "void Program.<
$>g__Report|0_0(int, int)" + IL_002a: ldc.i4 0x14d + IL_002f: stloc.s V_4 + IL_0031: ldloca.s V_4 + IL_0033: newobj "R..ctor(in int)" + IL_0038: ldc.i4 0x1bc + IL_003d: stloc.s V_5 + IL_003f: ldloca.s V_5 + IL_0041: newobj "R..ctor(in int)" + IL_0046: stloc.3 + IL_0047: ldfld "ref readonly int R.F" + IL_004c: ldind.i4 + IL_004d: ldloc.3 + IL_004e: ldfld "ref readonly int R.F" + IL_0053: ldind.i4 + IL_0054: call "void Program.<
$>g__Report|0_0(int, int)" + IL_0059: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_ComCall() + { + var source = """ + using System.Runtime.InteropServices; + + I i = new C(); + i.M(111); + i.M(222); + + [ComImport, Guid("96A2DE64-6D44-4DA5-BBA4-25F5F07E0E6B")] + interface I + { + void M(ref int i); + } + + class C : I + { + void I.M(ref int i) { } + } + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp is enough. + .VerifyIL("", """ + { + // Code size 30 (0x1e) + .maxstack 3 + .locals init (int V_0) + IL_0000: newobj "C..ctor()" + IL_0005: dup + IL_0006: ldc.i4.s 111 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: callvirt "void I.M(ref int)" + IL_0010: ldc.i4 0xde + IL_0015: stloc.0 + IL_0016: ldloca.s V_0 + IL_0018: callvirt "void I.M(ref int)" + IL_001d: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_ComCall() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + I i = new C(); + var r1 = i.M(111); + var r2 = i.M(222); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + [ComImport, Guid("96A2DE64-6D44-4DA5-BBA4-25F5F07E0E6B")] + interface I + { + R M([UnscopedRef] ref int i); + } + + class C : I + { + R I.M([UnscopedRef] ref int i) + { + var r = new R(); + r.F = ref i; + return r; + } + } + + ref struct R + { + public ref int F; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics() + // Needs two int temps. + .VerifyIL("", """ + { + // Code size 51 (0x33) + .maxstack 3 + .locals init (R V_0, //r1 + R V_1, //r2 + int V_2, + int V_3) + IL_0000: newobj "C..ctor()" + IL_0005: dup + IL_0006: ldc.i4.s 111 + IL_0008: stloc.2 + IL_0009: ldloca.s V_2 + IL_000b: callvirt "R I.M(ref int)" + IL_0010: stloc.0 + IL_0011: ldc.i4 0xde + IL_0016: stloc.3 + IL_0017: ldloca.s V_3 + IL_0019: callvirt "R I.M(ref int)" + IL_001e: stloc.1 + IL_001f: ldloc.0 + IL_0020: ldfld "ref int R.F" + IL_0025: ldind.i4 + IL_0026: ldloc.1 + IL_0027: ldfld "ref int R.F" + IL_002c: ldind.i4 + IL_002d: call "void Program.<
$>g__Report|0_0(int, int)" + IL_0032: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_NoTemp() + { + var source = """ + var x1 = 111; + var r1 = new R(x1); + var x2 = 222; + var r2 = new R(x2); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // No int temps needed. + .VerifyIL("", """ + { + // Code size 43 (0x2b) + .maxstack 2 + .locals init (int V_0, //x1 + int V_1, //x2 + R V_2) //r2 + IL_0000: ldc.i4.s 111 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "R..ctor(in int)" + IL_000a: ldc.i4 0xde + IL_000f: stloc.1 + IL_0010: ldloca.s V_1 + IL_0012: newobj "R..ctor(in int)" + IL_0017: stloc.2 + IL_0018: ldfld "ref readonly int R.F" + IL_001d: ldind.i4 + IL_001e: ldloc.2 + IL_001f: ldfld "ref readonly int R.F" + IL_0024: ldind.i4 + IL_0025: call "void Program.<
$>g__Report|0_0(int, int)" + IL_002a: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_RValue() + { + var source = """ + var r1 = new R(F1()); + var r2 = new R(F2()); + Report(r1.F, r2.F); + + static int F1() => 111; + static int F2() => 222; + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // Needs two int temps. + .VerifyIL("", """ + { + // Code size 46 (0x2e) + .maxstack 2 + .locals init (R V_0, //r2 + int V_1, + int V_2) + IL_0000: call "int Program.<
$>g__F1|0_0()" + IL_0005: stloc.1 + IL_0006: ldloca.s V_1 + IL_0008: newobj "R..ctor(in int)" + IL_000d: call "int Program.<
$>g__F2|0_1()" + IL_0012: stloc.2 + IL_0013: ldloca.s V_2 + IL_0015: newobj "R..ctor(in int)" + IL_001a: stloc.0 + IL_001b: ldfld "ref readonly int R.F" + IL_0020: ldind.i4 + IL_0021: ldloc.0 + IL_0022: ldfld "ref readonly int R.F" + IL_0027: ldind.i4 + IL_0028: call "void Program.<
$>g__Report|0_2(int, int)" + IL_002d: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_Assignment() + { + var source = """ + var r1 = new R(100); + r1 = new R(101); + var r2 = new R(200); + r2 = new R(201); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("101 201"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // Needs at least two int temps. + .VerifyIL("", """ + { + // Code size 68 (0x44) + .maxstack 2 + .locals init (R V_0, //r2 + int V_1, + int V_2, + int V_3) + IL_0000: ldc.i4.s 100 + IL_0002: stloc.1 + IL_0003: ldloca.s V_1 + IL_0005: newobj "R..ctor(in int)" + IL_000a: pop + IL_000b: ldc.i4.s 101 + IL_000d: stloc.1 + IL_000e: ldloca.s V_1 + IL_0010: newobj "R..ctor(in int)" + IL_0015: ldc.i4 0xc8 + IL_001a: stloc.2 + IL_001b: ldloca.s V_2 + IL_001d: newobj "R..ctor(in int)" + IL_0022: stloc.0 + IL_0023: ldc.i4 0xc9 + IL_0028: stloc.3 + IL_0029: ldloca.s V_3 + IL_002b: newobj "R..ctor(in int)" + IL_0030: stloc.0 + IL_0031: ldfld "ref readonly int R.F" + IL_0036: ldind.i4 + IL_0037: ldloc.0 + IL_0038: ldfld "ref readonly int R.F" + IL_003d: ldind.i4 + IL_003e: call "void Program.<
$>g__Report|0_0(int, int)" + IL_0043: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_PatternMatch() + { + var source = """ + if (new R(111) is { } r1 && new R(222) is { } r2) + { + Report(r1.F, r2.F); + } + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R(in int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics() + // Needs two int temps. + .VerifyIL("", """ + { + // Code size 45 (0x2d) + .maxstack 2 + .locals init (R V_0, //r1 + R V_1, //r2 + int V_2, + int V_3) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.2 + IL_0003: ldloca.s V_2 + IL_0005: newobj "R..ctor(in int)" + IL_000a: stloc.0 + IL_000b: ldc.i4 0xde + IL_0010: stloc.3 + IL_0011: ldloca.s V_3 + IL_0013: newobj "R..ctor(in int)" + IL_0018: stloc.1 + IL_0019: ldloc.0 + IL_001a: ldfld "ref readonly int R.F" + IL_001f: ldind.i4 + IL_0020: ldloc.1 + IL_0021: ldfld "ref readonly int R.F" + IL_0026: ldind.i4 + IL_0027: call "void Program.<
$>g__Report|0_0(int, int)" + IL_002c: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_Initializer() + { + var source = """ + var r1 = new R() { F = ref M(111) }; + var r2 = new R() { F = ref M(222) }; + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static ref readonly int M(in int x) => ref x; + + ref struct R + { + public ref readonly int F; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_ViaOutParameter( + [CombinatorialValues("", "scoped")] string scoped, + [CombinatorialValues("", "readonly")] string ro) + { + var source = $$""" + scoped R r1, r2; + M(111, out r1); + M(222, out r2); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static void M(in int x, {{scoped}} out R r) => r = new R(x); + + {{ro}} ref struct R(in int x) + { + public {{ro}} ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_ViaOutParameter() + { + var source = """ + scoped R r1, r2; + M(111, out r1); + M(222, out r2); + + static void M(scoped in int x, scoped out R r) { } + + ref struct R; + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp would be enough, but currently the scope difference between input/output is not recognized by the heuristic. + .VerifyIL("", """ + { + // Code size 28 (0x1c) + .maxstack 2 + .locals init (R V_0, //r1 + R V_1, //r2 + int V_2, + int V_3) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.2 + IL_0003: ldloca.s V_2 + IL_0005: ldloca.s V_0 + IL_0007: call "void Program.<
$>g__M|0_0(scoped in int, out R)" + IL_000c: ldc.i4 0xde + IL_0011: stloc.3 + IL_0012: ldloca.s V_3 + IL_0014: ldloca.s V_1 + IL_0016: call "void Program.<
$>g__M|0_0(scoped in int, out R)" + IL_001b: ret + } + """); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_ViaRefParameter( + [CombinatorialValues("", "scoped")] string scoped) + { + var source = $$""" + using System.Diagnostics.CodeAnalysis; + + scoped var r1 = new R(); + scoped var r2 = new R(); + M(111, ref r1); + M(222, ref r2); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static void M([UnscopedRef] in int x, {{scoped}} ref R r) + { + r.F = ref x; + } + + ref struct R + { + public ref readonly int F; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_ViaRefParameter_Readonly( + [CombinatorialValues("", "scoped")] string scoped) + { + var source = $$""" + using System.Diagnostics.CodeAnalysis; + + scoped var r1 = new R(); + scoped var r2 = new R(); + M(111, ref r1); + M(222, ref r2); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static void M([UnscopedRef] in int x, {{scoped}} ref R r) + { + r = new R(in x); + } + + readonly ref struct R(ref readonly int x) + { + public readonly ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_ViaRefParameter() + { + var source = """ + scoped R r1 = new(), r2 = new(); + M(111, ref r1); + M(222, ref r2); + + static void M(in int x, ref R r) { } + + ref struct R; + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp would be enough, but currently the scope difference between input/output is not recognized by the heuristic. + .VerifyIL("", """ + { + // Code size 44 (0x2c) + .maxstack 2 + .locals init (R V_0, //r1 + R V_1, //r2 + int V_2, + int V_3) + IL_0000: ldloca.s V_0 + IL_0002: initobj "R" + IL_0008: ldloca.s V_1 + IL_000a: initobj "R" + IL_0010: ldc.i4.s 111 + IL_0012: stloc.2 + IL_0013: ldloca.s V_2 + IL_0015: ldloca.s V_0 + IL_0017: call "void Program.<
$>g__M|0_0(in int, ref R)" + IL_001c: ldc.i4 0xde + IL_0021: stloc.3 + IL_0022: ldloca.s V_3 + IL_0024: ldloca.s V_1 + IL_0026: call "void Program.<
$>g__M|0_0(in int, ref R)" + IL_002b: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_ViaOutDiscard() + { + var source = """ + M(111, out _); + M(222, out _); + + static void M(in int x, out R r) => r = default; + + ref struct R; + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int and one R temp would be enough. But currently the emit layer does not see the argument is a discard. + .VerifyIL("", """ + { + // Code size 28 (0x1c) + .maxstack 2 + .locals init (R V_0, + int V_1, + R V_2, + int V_3) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.1 + IL_0003: ldloca.s V_1 + IL_0005: ldloca.s V_0 + IL_0007: call "void Program.<
$>g__M|0_0(in int, out R)" + IL_000c: ldc.i4 0xde + IL_0011: stloc.3 + IL_0012: ldloca.s V_3 + IL_0014: ldloca.s V_2 + IL_0016: call "void Program.<
$>g__M|0_0(in int, out R)" + IL_001b: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_Scoped() + { + var source = """ + var r1 = new R(111); + var r2 = new R(222); + + ref struct R + { + public R(scoped in int x) { } + } + """; + CompileAndVerify(source) + .VerifyDiagnostics() + // One int temp is enough. + .VerifyIL("", """ + { + // Code size 26 (0x1a) + .maxstack 1 + .locals init (int V_0) + IL_0000: ldc.i4.s 111 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "R..ctor(scoped in int)" + IL_000a: pop + IL_000b: ldc.i4 0xde + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "R..ctor(scoped in int)" + IL_0018: pop + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_InstanceMethod() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + scoped var r1 = new R(); + scoped var r2 = new R(); + r1.Set(111); + r2.Set(222); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + ref struct R + { + public ref readonly int F; + + public void Set([UnscopedRef] in int x) => F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_InstanceMethod_Chained() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + scoped var r1 = new R(); + scoped var r2 = new R(); + new C().Set(111).To(ref r1); + new C().Set(222).To(ref r2); + Report(r1.F, r2.F); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + class C + { + public R Set([UnscopedRef] in int x) + { + var r = new R(); + r.F = ref x; + return r; + } + } + + ref struct R + { + public ref readonly int F; + public void To(ref R r) + { + r.F = ref F; + } + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_FunctionPointer() + { + var source = """ + unsafe + { + delegate* f = &M; + var r1 = f(111); + var r2 = f(222); + Report(r1.F, r2.F); + } + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static R M(in int x) => new R(in x); + + ref struct R(ref readonly int x) + { + public ref readonly int F = ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + options: TestOptions.UnsafeReleaseExe, + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_RefAssignment() + { + var source = """ + ref readonly int x = ref 111.M(); + ref readonly int y = ref 222.M(); + + Report(x, y); + + static void Report(int x, int y) => System.Console.WriteLine($"{x} {y}"); + + static class E + { + public static ref readonly int M(this in int x) => ref x; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_Receiver() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + class C + { + static S M1() => new S { F = 111 }; + + static void M2(ref int x, ref int y) => System.Console.WriteLine($"{x} {y}"); + + static void Main() + { + M2(ref M1().Ref(), ref new S { F = 222 }.Ref()); + } + } + + struct S + { + public int F; + + [UnscopedRef] public ref int Ref() => ref F; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_Receiver_ReadOnlyContext_01() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + new S { F = 111 }.M3(); + + struct S + { + public int F; + + [UnscopedRef] public ref int Ref() => ref F; + + public readonly void M3() + { + M2(ref Ref(), ref new S { F = 222 }.Ref()); + } + + static void M2(ref int x, ref int y) => System.Console.WriteLine($"{x} {y}"); + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics( + // (13,16): warning CS8656: Call to non-readonly member 'S.Ref()' from a 'readonly' member results in an implicit copy of 'this'. + // M2(ref Ref(), ref new S { F = 222 }.Ref()); + Diagnostic(ErrorCode.WRN_ImplicitCopyInReadOnlyMember, "Ref").WithArguments("S.Ref()", "this").WithLocation(13, 16)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_CannotEscape_Receiver_ReadOnlyContext_02() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + static S M1() => new S { F = 111 }; + + M1().M3(); + + struct S + { + public int F; + + [UnscopedRef] public readonly ref readonly int Ref() => ref F; + + public readonly void M3() + { + M2(in Ref(), new S { F = 222 }); + } + + static void M2(in int x, S y) => System.Console.WriteLine($"{x} {y.F}"); + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics() + // One S temp is enough. + .VerifyIL("S.M3", """ + { + // Code size 33 (0x21) + .maxstack 3 + .locals init (S V_0) + IL_0000: ldarg.0 + IL_0001: call "readonly ref readonly int S.Ref()" + IL_0006: ldloca.s V_0 + IL_0008: initobj "S" + IL_000e: ldloca.s V_0 + IL_0010: ldc.i4 0xde + IL_0015: stfld "int S.F" + IL_001a: ldloc.0 + IL_001b: call "void S.M2(in int, S)" + IL_0020: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67435")] + public void RefTemp_Escapes_Receiver_ReadOnlyContext_03() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + class C + { + static S M1() => new S { F = 111 }; + + static void M2(in int x, in int y) => System.Console.WriteLine($"{x} {y}"); + + static void Main() + { + M2(in M1().Ref(), in new S { F = 222 }.Ref()); + } + } + + public struct S + { + public int F; + + [UnscopedRef] public readonly ref readonly int Ref() => ref F; + } + """; + CompileAndVerify(source, + expectedOutput: RefFieldTests.IncludeExpectedOutput("111 222"), + targetFramework: TargetFramework.Net70, + verify: Verification.Fails) + .VerifyDiagnostics(); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72873")] public void Utf8Addition() { diff --git a/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs b/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs index 80e5bb29a3e59..c883dca5b9869 100644 --- a/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs +++ b/src/Compilers/Core/Portable/CodeGen/ILBuilderEmit.cs @@ -486,6 +486,9 @@ internal void EmitLocalStore(LocalDefinition local) internal void EmitLocalAddress(LocalDefinition local) { + Debug.Assert(LocalSlotManager != null); + LocalSlotManager.AddAddressedLocal(local, _optimizations); + if (local.IsReference) { EmitLocalLoad(local); diff --git a/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs b/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs index 6ab3b15df4910..991e7de5e3f20 100644 --- a/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs +++ b/src/Compilers/Core/Portable/CodeGen/LocalSlotManager.cs @@ -66,6 +66,13 @@ public override bool Equals(object? obj) // pool of free slots partitioned by their signature. private KeyedStack? _freeSlots; + // these locals cannot be added to "FreeSlots" + private HashSet? _nonReusableLocals; + + // locals whose address has been taken; excludes non-reusable local kinds + private ArrayBuilder? _addressedLocals; + private int _addressedLocalScopes; + // all locals in order private ArrayBuilder? _lazyAllLocals; @@ -154,7 +161,8 @@ internal LocalDefinition GetLocal(ILocalSymbolInternal symbol) internal void FreeLocal(ILocalSymbolInternal symbol) { var slot = GetLocal(symbol); - LocalMap.Remove(symbol); + var removed = LocalMap.Remove(symbol); + Debug.Assert(removed, $"Attempted to free '{symbol}' more than once."); FreeSlot(slot); } @@ -245,7 +253,55 @@ private LocalDefinition DeclareLocalImpl( internal void FreeSlot(LocalDefinition slot) { Debug.Assert(slot.Name == null); - FreeSlots.Push(new LocalSignature(slot.Type, slot.Constraints), slot); + + if (_nonReusableLocals?.Remove(slot) != true) + { + FreeSlots.Push(new LocalSignature(slot.Type, slot.Constraints), slot); + } + } + + internal int StartScopeOfTrackingAddressedLocals() + { + Debug.Assert((_addressedLocals == null) == (_addressedLocalScopes == 0)); + _addressedLocals ??= ArrayBuilder.GetInstance(); + _addressedLocalScopes++; + return _addressedLocals.Count; + } + + internal void AddAddressedLocal(LocalDefinition localDef, OptimizationLevel optimizations) + { + // No need to add non-reusable local kinds to `_addressedLocals` because that list + // only contains locals with reusable kinds to mark them as actually non-reusable. + if (localDef != null && localDef.SymbolOpt?.SynthesizedKind.IsSlotReusable(optimizations) != false) + { + _addressedLocals?.Add(localDef); + } + } + + internal void EndScopeOfTrackingAddressedLocals(int countBefore, bool markAsNotReusable) + { + Debug.Assert(_addressedLocals != null); + + if (markAsNotReusable && countBefore < _addressedLocals.Count) + { + _nonReusableLocals ??= new HashSet(ReferenceEqualityComparer.Instance); + for (var i = countBefore; i < _addressedLocals.Count; i++) + { + _nonReusableLocals.Add(_addressedLocals[i]); + } + } + + _addressedLocalScopes--; + if (_addressedLocalScopes > 0) + { + _addressedLocals.Count = countBefore; + } + else + { + Debug.Assert(_addressedLocalScopes == 0 && countBefore == 0); + _addressedLocals.Free(); + _addressedLocals = null; + } } public ImmutableArray LocalsInOrder() diff --git a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs index 361632cd64417..844b07058feb9 100644 --- a/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs +++ b/src/EditorFeatures/Core/InlineRename/AbstractEditorInlineRenameService.InlineRenameLocationSet.cs @@ -44,7 +44,7 @@ public async Task GetReplacementsAsync( CancellationToken cancellationToken) { var conflicts = await _renameLocationSet.ResolveConflictsAsync( - _renameInfo.RenameSymbol, _renameInfo.GetFinalSymbolName(replacementText), nonConflictSymbolKeys: default, cancellationToken).ConfigureAwait(false); + _renameInfo.RenameSymbol, _renameInfo.GetFinalSymbolName(replacementText), cancellationToken).ConfigureAwait(false); return new InlineRenameReplacementInfo(conflicts); } diff --git a/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb b/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb index c83f5b4f70cab..9b30c5643306f 100644 --- a/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/UseAutoProperty/UseAutoPropertyTests.vb @@ -131,7 +131,7 @@ partial class C public C() { - this.P = 0; + P = 0; } }".Trim() diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 203590a2e9807..a9c2be0cd3ca1 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -136,14 +136,13 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim locations = Renamer.FindRenameLocationsAsync( solution, symbol, renameOptions, CancellationToken.None).GetAwaiter().GetResult() - Return locations.ResolveConflictsAsync(symbol, renameTo, nonConflictSymbolKeys:=Nothing, CancellationToken.None).GetAwaiter().GetResult() + Return locations.ResolveConflictsAsync(symbol, renameTo, CancellationToken.None).GetAwaiter().GetResult() Else ' This tests that rename properly works when the entire call is remoted to OOP and the final result is ' marshaled back. Return Renamer.RenameSymbolAsync( - solution, symbol, renameTo, renameOptions, - nonConflictSymbolKeys:=Nothing, CancellationToken.None).GetAwaiter().GetResult() + solution, symbol, renameTo, renameOptions, CancellationToken.None).GetAwaiter().GetResult() End If End Function diff --git a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs index a14ed3349d2bb..a79515976f9b3 100644 --- a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs @@ -14,10 +14,10 @@ using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.UseAutoProperty; using Roslyn.Utilities; @@ -59,7 +59,7 @@ protected override SyntaxNode GetNodeToRemove(VariableDeclaratorSyntax declarato protected override PropertyDeclarationSyntax RewriteFieldReferencesInProperty( PropertyDeclarationSyntax property, - LightweightRenameLocations fieldLocations, + ImmutableArray fieldLocations, CancellationToken cancellationToken) { // We're going to walk this property body, converting most reference of the field to use the `field` keyword @@ -67,8 +67,7 @@ protected override PropertyDeclarationSyntax RewriteFieldReferencesInProperty( // we update to point at the property instead. So we grab that property name here to use in the rewriter. var propertyIdentifierName = IdentifierName(property.Identifier.WithoutTrivia()); - var identifierNames = fieldLocations.Locations - .Select(loc => loc.Location.FindNode(getInnermostNodeForTie: true, cancellationToken) as IdentifierNameSyntax) + var identifierNames = fieldLocations.SelectMany(loc => loc.Locations.Select(loc => loc.Location.FindNode(getInnermostNodeForTie: true, cancellationToken) as IdentifierNameSyntax)) .WhereNotNull() .ToSet(); diff --git a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs index b0dabbaddca9f..04f0fa67f3295 100644 --- a/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs +++ b/src/Features/Core/Portable/EncapsulateField/AbstractEncapsulateFieldService.cs @@ -287,7 +287,7 @@ private static async Task RenameAsync( // edit the files in the current project var resolution = await initialLocations .Filter((documentId, span) => !linkedProjectIds.Contains(documentId.ProjectId) && filter(documentId, span)) - .ResolveConflictsAsync(field, finalName, nonConflictSymbolKeys: default, cancellationToken).ConfigureAwait(false); + .ResolveConflictsAsync(field, finalName, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(resolution.IsSuccessful); diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs index afd63a14d0831..c651b4ba6d303 100644 --- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs @@ -16,11 +16,12 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.LanguageService; -using Microsoft.CodeAnalysis.Rename; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -28,9 +29,21 @@ namespace Microsoft.CodeAnalysis.UseAutoProperty; using static UseAutoPropertiesHelpers; -internal abstract partial class AbstractUseAutoPropertyCodeFixProvider +internal abstract partial class AbstractUseAutoPropertyCodeFixProvider< + TProvider, + TTypeDeclarationSyntax, + TPropertyDeclaration, + TVariableDeclarator, + TConstructorDeclaration, + TExpression> : CodeFixProvider - where TProvider : AbstractUseAutoPropertyCodeFixProvider + where TProvider : AbstractUseAutoPropertyCodeFixProvider< + TProvider, + TTypeDeclarationSyntax, + TPropertyDeclaration, + TVariableDeclarator, + TConstructorDeclaration, + TExpression> where TTypeDeclarationSyntax : SyntaxNode where TPropertyDeclaration : SyntaxNode where TVariableDeclarator : SyntaxNode @@ -48,7 +61,7 @@ public sealed override FixAllProvider GetFixAllProvider() protected abstract TPropertyDeclaration GetPropertyDeclaration(SyntaxNode node); protected abstract SyntaxNode GetNodeToRemove(TVariableDeclarator declarator); protected abstract TPropertyDeclaration RewriteFieldReferencesInProperty( - TPropertyDeclaration property, LightweightRenameLocations fieldLocations, CancellationToken cancellationToken); + TPropertyDeclaration property, ImmutableArray fieldLocations, CancellationToken cancellationToken); protected abstract ImmutableArray GetFormattingRules( Document document, SyntaxNode finalPropertyDeclaration); @@ -108,8 +121,6 @@ private async Task ProcessResultWorkerAsync( if (field == null || property == null) return currentSolution; - var locations = diagnostic.AdditionalLocations; - var fieldDocument = currentSolution.GetRequiredDocument(field.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken).SyntaxTree); var propertyDocument = currentSolution.GetRequiredDocument(property.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken).SyntaxTree); @@ -120,10 +131,8 @@ private async Task ProcessResultWorkerAsync( var project = fieldDocument.Project; var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - var renameOptions = new SymbolRenameOptions(); - - var fieldLocations = await Renamer.FindRenameLocationsAsync( - currentSolution, field, renameOptions, cancellationToken).ConfigureAwait(false); + var fieldLocations = await SymbolFinder.FindReferencesAsync( + field, currentSolution, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); var declarator = (TVariableDeclarator)field.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken); var propertyDeclaration = GetPropertyDeclaration(property.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken)); @@ -168,31 +177,9 @@ private async Task ProcessResultWorkerAsync( linkedFiles.AddRange(fieldDocument.GetLinkedDocumentIds()); linkedFiles.AddRange(propertyDocument.GetLinkedDocumentIds()); - var canEdit = new Dictionary(); - - // Now, rename all usages of the field to point at the property. Except don't actually - // rename the field itself. We want to be able to find it again post rename. - // - // We're asking the rename API to update a bunch of references to an existing field to the same name as an - // existing property. Rename will often flag this situation as an unresolvable conflict because the new - // name won't bind to the field anymore. - // - // To address this, we let rename know that there is no conflict if the new symbol it resolves to is the - // same as the property we're trying to get the references pointing to. - - var filteredLocations = fieldLocations.Filter( - (documentId, span) => - fieldDocument.Id == documentId ? !span.IntersectsWith(declarator.Span) : true && // The span check only makes sense if we are in the same file - CanEditDocument(currentSolution, documentId, linkedFiles, canEdit)); - - var resolution = await filteredLocations.ResolveConflictsAsync( - field, property.Name, - nonConflictSymbolKeys: [property.GetSymbolKey(cancellationToken)], - cancellationToken).ConfigureAwait(false); - - Contract.ThrowIfFalse(resolution.IsSuccessful); - - currentSolution = resolution.NewSolution; + // Now, rename all usages of the field to point at the property. + currentSolution = await UpdateReferencesAsync( + currentSolution, linkedFiles, fieldLocations, property, cancellationToken).ConfigureAwait(false); // Now find the field and property again post rename. fieldDocument = currentSolution.GetRequiredDocument(fieldDocument.Id); @@ -288,6 +275,85 @@ private async Task ProcessResultWorkerAsync( } } + private static async Task UpdateReferencesAsync( + Solution solution, + HashSet linkedDocuments, + ImmutableArray fieldLocations, + IPropertySymbol property, + CancellationToken cancellationToken) + { + var solutionEditor = new SolutionEditor(solution); + var canEditMap = new Dictionary(); + + foreach (var group in fieldLocations.SelectMany(loc => loc.Locations).GroupBy(loc => loc.Document)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var document = group.Key; + + if (!CanEditDocument(document.Id)) + continue; + + var syntaxFacts = document.GetRequiredLanguageService(); + var editor = await solutionEditor.GetDocumentEditorAsync(document.Id, cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var generator = editor.Generator; + var newNameNode = generator.IdentifierName(property.Name); + + foreach (var location in group.Distinct(LinkedFileReferenceLocationEqualityComparer.Instance)) + { + if (location.IsImplicit) + continue; + + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); + if (syntaxFacts.GetRootStandaloneExpression(node) == node) + { + // We're referencing the field as a trivial name (like `fieldName`). In this case, we might run into + // problems with symbol collisions if we just change the name to `propertyName`. So instead, we check + // if that name is in scope and isn't a reference to the new property. If that's the case, then we + // qualify with `this.fieldName` or `ClassName.FieldName` to avoid any collisions. + var symbols = semanticModel.LookupSymbols(node.SpanStart, name: property.Name); + if (symbols.Length > 0 && symbols.All(s => !s.OriginalDefinition.Equals(property.OriginalDefinition))) + { + var qualifiedName = generator.MemberAccessExpression( + property.IsStatic ? generator.TypeExpression(property.ContainingType) : generator.ThisExpression(), + newNameNode); + editor.ReplaceNode(node, qualifiedName.WithTriviaFrom(node)); + } + else + { + // The name was standing alone and didn't bind to any other symbol. Just do the trivial rename here. + editor.ReplaceNode(node, newNameNode.WithTriviaFrom(node)); + } + } + else + { + // Otherwise, we're referencing the field in a complex way (like `this.fieldName`). In this case, we can just + // trivially replace `fieldName` with `propertyName` and have it work. Note: we add the simplifier annotation + // here as well. That way we can attempt to simplify the code if the user does not prefer `this` qualifiers + // for properties. + editor.ReplaceNode(node, newNameNode.WithTriviaFrom(node)); + editor.ReplaceNode(node.GetRequiredParent(), (current, _) => current.WithAdditionalAnnotations(Simplifier.Annotation)); + } + } + } + + return solutionEditor.GetChangedSolution(); + + bool CanEditDocument(DocumentId documentId) + { + if (!canEditMap.TryGetValue(documentId, out var canEditDocument)) + { + var document = solution.GetDocument(documentId); + canEditDocument = document != null && !linkedDocuments.Contains(document.Id); + canEditMap[documentId] = canEditDocument; + } + + return canEditDocument; + } + } + private async Task<(IFieldSymbol? fieldSymbol, IPropertySymbol? propertySymbol)> MapDiagnosticToCurrentSolutionAsync( Diagnostic diagnostic, Solution originalSolution, @@ -350,22 +416,6 @@ private static bool WillRemoveFirstFieldInTypeDirectlyAboveProperty( return false; } - private static bool CanEditDocument( - Solution solution, - DocumentId documentId, - HashSet linkedDocuments, - Dictionary canEdit) - { - if (!canEdit.TryGetValue(documentId, out var canEditDocument)) - { - var document = solution.GetDocument(documentId); - canEditDocument = document != null && !linkedDocuments.Contains(document.Id); - canEdit[documentId] = canEditDocument; - } - - return canEditDocument; - } - private async Task FormatAsync( Document document, SyntaxNode finalPropertyDeclaration, @@ -388,39 +438,51 @@ private async Task FormatAsync( } private static bool IsWrittenToOutsideOfConstructorOrProperty( - IFieldSymbol field, LightweightRenameLocations renameLocations, TPropertyDeclaration propertyDeclaration, CancellationToken cancellationToken) + IFieldSymbol field, + ImmutableArray referencedSymbols, + TPropertyDeclaration propertyDeclaration, + CancellationToken cancellationToken) { - var constructorSpans = field.ContainingType.GetMembers() - .Where(m => m.IsConstructor()) - .SelectMany(c => c.DeclaringSyntaxReferences) - .Select(s => s.GetSyntax(cancellationToken)) - .Select(n => n.FirstAncestorOrSelf()) - .WhereNotNull() - .Select(d => (d.SyntaxTree.FilePath, d.Span)) - .ToSet(); - return renameLocations.Locations.Any( - loc => IsWrittenToOutsideOfConstructorOrProperty( - renameLocations.Solution, loc, propertyDeclaration, constructorSpans, cancellationToken)); + var constructorSpans = field.ContainingType + .GetMembers() + .Where(m => m.IsConstructor()) + .SelectMany(c => c.DeclaringSyntaxReferences) + .Select(s => s.GetSyntax(cancellationToken)) + .Select(n => n.FirstAncestorOrSelf()) + .WhereNotNull() + .Select(d => (d.SyntaxTree.FilePath, d.Span)) + .ToSet(); + + foreach (var referencedSymbol in referencedSymbols) + { + foreach (var location in referencedSymbol.LocationsArray) + { + if (IsWrittenToOutsideOfConstructorOrProperty(location, propertyDeclaration, constructorSpans, cancellationToken)) + return true; + } + } + + return false; } private static bool IsWrittenToOutsideOfConstructorOrProperty( - Solution solution, - RenameLocation location, + ReferenceLocation location, TPropertyDeclaration propertyDeclaration, ISet<(string filePath, TextSpan span)> constructorSpans, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); + // We don't need a setter if we're not writing to this field. if (!location.IsWrittenTo) - { - // We don't need a setter if we're not writing to this field. return false; - } - var syntaxFacts = solution.GetRequiredDocument(location.DocumentId).GetRequiredLanguageService(); - var node = location.Location.FindToken(cancellationToken).Parent; + if (location.IsImplicit) + return false; + + var syntaxFacts = location.Document.GetRequiredLanguageService(); + var node = location.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); while (node != null && !syntaxFacts.IsAnonymousOrLocalFunction(node)) { if (node == propertyDeclaration) diff --git a/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs b/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs index 3b8ef0d21672e..9137f800bd783 100644 --- a/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs +++ b/src/Features/Core/Portable/UseAutoProperty/UseAutoPropertyFixAllProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CodeAnalysis.CodeFixesAndRefactorings; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.UseAutoProperty; diff --git a/src/Features/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs b/src/Features/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs index 22a094867849d..29c496411e17f 100644 --- a/src/Features/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs +++ b/src/Features/ExternalAccess/OmniSharp/Rename/OmniSharpRenamer.cs @@ -23,7 +23,7 @@ public static async Task RenameSymbolAsync( CancellationToken cancellationToken) { var nonConflictSymbolsKeys = nonConflictSymbols is null ? default : nonConflictSymbols.SelectAsArray(s => s.GetSymbolKey(cancellationToken)); - var resolution = await Renamer.RenameSymbolAsync(solution, symbol, newName, options.ToRenameOptions(), nonConflictSymbolsKeys, cancellationToken).ConfigureAwait(false); + var resolution = await Renamer.RenameSymbolAsync(solution, symbol, newName, options.ToRenameOptions(), cancellationToken).ConfigureAwait(false); return new RenameResult(resolution.NewSolution, resolution.ErrorMessage); } } diff --git a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb index ddabdd4e4c3e3..99e0b405980af 100644 --- a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb @@ -8,6 +8,7 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.Rename Imports Microsoft.CodeAnalysis.UseAutoProperty @@ -39,7 +40,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty Return Nothing End Function - Protected Overrides Function RewriteFieldReferencesInProperty([property] As PropertyBlockSyntax, fieldLocations As LightweightRenameLocations, cancellationToken As CancellationToken) As PropertyBlockSyntax + Protected Overrides Function RewriteFieldReferencesInProperty([property] As PropertyBlockSyntax, fieldLocations As ImmutableArray(Of ReferencedSymbol), cancellationToken As CancellationToken) As PropertyBlockSyntax ' Only called to rewrite to `field` (which VB does not support). Return [property] End Function diff --git a/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs b/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs index 5f4062dfd243a..186c0218cfda4 100644 --- a/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Rename/RenameHandler.cs @@ -54,9 +54,7 @@ internal sealed class RenameHandler() : ILspServiceDocumentRequestHandler _nonConflictSymbolKeys; private readonly CancellationToken _cancellationToken; private readonly RenameAnnotation _renamedSymbolDeclarationAnnotation = new(); @@ -50,14 +49,12 @@ public Session( SymbolicRenameLocations renameLocationSet, Location renameSymbolDeclarationLocation, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { _renameLocationSet = renameLocationSet; _renameSymbolDeclarationLocation = renameSymbolDeclarationLocation; _originalText = renameLocationSet.Symbol.Name; _replacementText = replacementText; - _nonConflictSymbolKeys = nonConflictSymbolKeys; _cancellationToken = cancellationToken; _replacementTextValid = true; @@ -292,7 +289,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe // fixed them because of rename). Also, don't bother checking if a custom // callback was provided. The caller might be ok with a rename that introduces // errors. - if (!documentIdErrorStateLookup[documentId] && _nonConflictSymbolKeys.IsDefault) + if (!documentIdErrorStateLookup[documentId]) { var changeDoc = await conflictResolution.CurrentSolution.GetRequiredDocumentAsync( documentId, RenameOptions.RenameInSourceGeneratedDocuments, _cancellationToken).ConfigureAwait(false); @@ -349,7 +346,6 @@ private async Task IdentifyConflictsAsync( // If we were giving any non-conflict-symbols then ensure that we know what those symbols are in // the current project post after our edits so far. var currentProject = conflictResolution.CurrentSolution.GetRequiredProject(projectId); - var nonConflictSymbols = await GetNonConflictSymbolsAsync(currentProject).ConfigureAwait(false); foreach (var documentId in documentIdsForConflictResolution) { @@ -392,9 +388,8 @@ private async Task IdentifyConflictsAsync( // the spans would have been modified and so we need to adjust the old position // to the new position for which we use the renameSpanTracker, which was tracking // & mapping the old span -> new span during rename - hasConflict = - !IsConflictFreeChange(newReferencedSymbols, nonConflictSymbols) && - await CheckForConflictAsync(conflictResolution, renamedSymbolInNewSolution, conflictAnnotation, newReferencedSymbols).ConfigureAwait(false); + hasConflict = await CheckForConflictAsync( + conflictResolution, renamedSymbolInNewSolution, conflictAnnotation, newReferencedSymbols).ConfigureAwait(false); if (!hasConflict && !conflictAnnotation.IsInvocationExpression) hasConflict = LocalVariableConflictPerLanguage((SyntaxToken)tokenOrNode, newDocument, newReferencedSymbols); @@ -475,32 +470,6 @@ await AddDeclarationConflictsAsync( } } - private async Task?> GetNonConflictSymbolsAsync(Project currentProject) - { - if (_nonConflictSymbolKeys.IsDefault) - return null; - - var compilation = await currentProject.GetRequiredCompilationAsync(_cancellationToken).ConfigureAwait(false); - return [.. _nonConflictSymbolKeys.Select(s => s.Resolve(compilation).GetAnySymbol()).WhereNotNull()]; - } - - private static bool IsConflictFreeChange( - ImmutableArray symbols, ImmutableHashSet? nonConflictSymbols) - { - if (nonConflictSymbols != null) - { - foreach (var symbol in symbols) - { - // Reference not points at a symbol in the conflict-free list. This is a conflict-free change. - if (nonConflictSymbols.Contains(symbol)) - return true; - } - } - - // Just do the default check. - return false; - } - /// /// Gets the list of the nodes that were annotated for a conflict check /// diff --git a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs index d4a2a4a81d33c..6b9e792324bad 100644 --- a/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs +++ b/src/Workspaces/Core/Portable/Rename/ConflictEngine/ConflictResolver.cs @@ -42,17 +42,12 @@ internal static partial class ConflictResolver /// resolves them where possible. /// /// The new name of the identifier - /// Used after renaming references. References that now bind to any of these - /// symbols are not considered to be in conflict. Useful for features that want to rename existing references to - /// point at some existing symbol. Normally this would be a conflict, but this can be used to override that - /// behavior. /// The cancellation token. /// A conflict resolution containing the new solution. internal static async Task ResolveLightweightConflictsAsync( ISymbol symbol, LightweightRenameLocations lightweightRenameLocations, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -68,7 +63,8 @@ internal static async Task ResolveLightweightConflictsAsync( var result = await client.TryInvokeAsync( solution, - (service, solutionInfo, cancellationToken) => service.ResolveConflictsAsync(solutionInfo, serializableSymbol, serializableLocationSet, replacementText, nonConflictSymbolKeys, cancellationToken), + (service, solutionInfo, cancellationToken) => service.ResolveConflictsAsync( + solutionInfo, serializableSymbol, serializableLocationSet, replacementText, cancellationToken), cancellationToken).ConfigureAwait(false); if (result.HasValue && result.Value != null) @@ -83,7 +79,7 @@ internal static async Task ResolveLightweightConflictsAsync( return new ConflictResolution(WorkspacesResources.Failed_to_resolve_rename_conflicts); return await ResolveSymbolicLocationConflictsInCurrentProcessAsync( - heavyweightLocations, replacementText, nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + heavyweightLocations, replacementText, cancellationToken).ConfigureAwait(false); } /// @@ -93,7 +89,6 @@ internal static async Task ResolveLightweightConflictsAsync( internal static async Task ResolveSymbolicLocationConflictsInCurrentProcessAsync( SymbolicRenameLocations renameLocations, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { // when someone e.g. renames a symbol from metadata through the API (IDE blocks this), we need to return @@ -105,7 +100,7 @@ internal static async Task ResolveSymbolicLocationConflictsI } var resolution = await ResolveMutableConflictsAsync( - renameLocations, renameSymbolDeclarationLocation, replacementText, nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + renameLocations, renameSymbolDeclarationLocation, replacementText, cancellationToken).ConfigureAwait(false); return resolution.ToConflictResolution(); } @@ -114,12 +109,11 @@ private static Task ResolveMutableConflictsAsync( SymbolicRenameLocations renameLocationSet, Location renameSymbolDeclarationLocation, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var session = new Session( - renameLocationSet, renameSymbolDeclarationLocation, replacementText, nonConflictSymbolKeys, cancellationToken); + renameLocationSet, renameSymbolDeclarationLocation, replacementText, cancellationToken); return session.ResolveConflictsAsync(); } diff --git a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs index 953abf73dcbaa..c07b54c6bc07c 100644 --- a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs +++ b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs @@ -27,7 +27,6 @@ internal interface IRemoteRenamerService SerializableSymbolAndProjectId symbolAndProjectId, string replacementText, SymbolRenameOptions options, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken); ValueTask FindRenameLocationsAsync( @@ -41,7 +40,6 @@ internal interface IRemoteRenamerService SerializableSymbolAndProjectId symbolAndProjectId, SerializableRenameLocations renameLocationSet, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs b/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs index 7df6929dcabe9..ad9b98cce7fda 100644 --- a/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs +++ b/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs @@ -114,8 +114,8 @@ public static async Task FindRenameLocationsAsync( renameLocations.ReferencedSymbols.SelectAsArray(sym => SerializableSymbolAndProjectId.Dehydrate(solution, sym, cancellationToken))); } - public Task ResolveConflictsAsync(ISymbol symbol, string replacementText, ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) - => ConflictResolver.ResolveLightweightConflictsAsync(symbol, this, replacementText, nonConflictSymbolKeys, cancellationToken); + public Task ResolveConflictsAsync(ISymbol symbol, string replacementText, CancellationToken cancellationToken) + => ConflictResolver.ResolveLightweightConflictsAsync(symbol, this, replacementText, cancellationToken); public LightweightRenameLocations Filter(Func filter) => new( diff --git a/src/Workspaces/Core/Portable/Rename/Renamer.cs b/src/Workspaces/Core/Portable/Rename/Renamer.cs index 7e79da036c022..545bf3d986d4f 100644 --- a/src/Workspaces/Core/Portable/Rename/Renamer.cs +++ b/src/Workspaces/Core/Portable/Rename/Renamer.cs @@ -50,7 +50,7 @@ public static async Task RenameSymbolAsync( if (string.IsNullOrEmpty(newName)) throw new ArgumentException(WorkspacesResources._0_must_be_a_non_null_and_non_empty_string, nameof(newName)); - var resolution = await RenameSymbolAsync(solution, symbol, newName, options, nonConflictSymbolKeys: default, cancellationToken).ConfigureAwait(false); + var resolution = await RenameSymbolAsync(solution, symbol, newName, options, cancellationToken).ConfigureAwait(false); if (resolution.IsSuccessful) { @@ -144,7 +144,6 @@ internal static async Task RenameSymbolAsync( ISymbol symbol, string newName, SymbolRenameOptions options, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { Contract.ThrowIfNull(solution); @@ -167,7 +166,6 @@ internal static async Task RenameSymbolAsync( serializedSymbol, newName, options, - nonConflictSymbolKeys, cancellationToken), cancellationToken).ConfigureAwait(false); @@ -180,8 +178,7 @@ internal static async Task RenameSymbolAsync( } return await RenameSymbolInCurrentProcessAsync( - solution, symbol, newName, options, - nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + solution, symbol, newName, options, cancellationToken).ConfigureAwait(false); } private static async Task RenameSymbolInCurrentProcessAsync( @@ -189,7 +186,6 @@ private static async Task RenameSymbolInCurrentProcessAsync( ISymbol symbol, string newName, SymbolRenameOptions options, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { Contract.ThrowIfNull(solution); @@ -203,6 +199,6 @@ private static async Task RenameSymbolInCurrentProcessAsync( // without having to go through any intermediary LightweightTypes. var renameLocations = await SymbolicRenameLocations.FindLocationsInCurrentProcessAsync(symbol, solution, options, cancellationToken).ConfigureAwait(false); return await ConflictResolver.ResolveSymbolicLocationConflictsInCurrentProcessAsync( - renameLocations, newName, nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + renameLocations, newName, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Renamer/RemoteRenamerService.cs b/src/Workspaces/Remote/ServiceHub/Services/Renamer/RemoteRenamerService.cs index 3d146b0a4ef91..5d899266b8cdf 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Renamer/RemoteRenamerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Renamer/RemoteRenamerService.cs @@ -24,7 +24,6 @@ protected override IRemoteRenamerService CreateService(in ServiceConstructionArg SerializableSymbolAndProjectId symbolAndProjectId, string newName, SymbolRenameOptions options, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => @@ -36,7 +35,7 @@ protected override IRemoteRenamerService CreateService(in ServiceConstructionArg return null; var result = await Renamer.RenameSymbolAsync( - solution, symbol, newName, options, nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + solution, symbol, newName, options, cancellationToken).ConfigureAwait(false); return await result.DehydrateAsync(cancellationToken).ConfigureAwait(false); }, cancellationToken); @@ -72,7 +71,6 @@ protected override IRemoteRenamerService CreateService(in ServiceConstructionArg SerializableSymbolAndProjectId symbolAndProjectId, SerializableRenameLocations serializableLocations, string replacementText, - ImmutableArray nonConflictSymbolKeys, CancellationToken cancellationToken) { return RunServiceAsync(solutionChecksum, async solution => @@ -87,7 +85,7 @@ protected override IRemoteRenamerService CreateService(in ServiceConstructionArg return null; var result = await ConflictResolver.ResolveSymbolicLocationConflictsInCurrentProcessAsync( - locations, replacementText, nonConflictSymbolKeys, cancellationToken).ConfigureAwait(false); + locations, replacementText, cancellationToken).ConfigureAwait(false); return await result.DehydrateAsync(cancellationToken).ConfigureAwait(false); }, cancellationToken); }