diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs index e399012f96..45eaaceb72 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/ValueTypeCall.cs @@ -16,6 +16,7 @@ public void Increment() public void Dispose() { Console.WriteLine("MutValueType disposed on {0}", val); + val = val + 1; } public override string ToString() @@ -67,6 +68,10 @@ public static void Main() gvt.Call(ref gvt); new ValueTypeCall().InstanceFieldTests(); ForEach(); +#if CS73 + DisposeMultipleTimes(ref m, in m); + ToStringGeneric(ref m, in m); +#endif } static void RefParameter(ref MutValueType m) @@ -213,5 +218,37 @@ static void ForEachArray1(MutValueType[] list) } Console.WriteLine("after: " + list[0].val); } + +#if CS73 + static void DisposeMultipleTimes(ref T mutRef, in T immutableRef) where T : struct, IDisposable + { + Console.WriteLine("DisposeMultipleTimes:"); + mutRef.Dispose(); + mutRef.Dispose(); + T copyFromMut = mutRef; + copyFromMut.Dispose(); + immutableRef.Dispose(); + immutableRef.Dispose(); + T copyFromImmutable = immutableRef; + copyFromImmutable.Dispose(); + mutRef.Dispose(); + immutableRef.Dispose(); + } + + static void ToStringGeneric(ref T mutRef, in T immutableRef) where T : struct + { + Console.WriteLine("ToStringGeneric:"); + mutRef.ToString(); + mutRef.ToString(); + T copyFromMut = mutRef; + copyFromMut.ToString(); + immutableRef.ToString(); + immutableRef.ToString(); + T copyFromImmutable = immutableRef; + copyFromImmutable.ToString(); + mutRef.ToString(); + immutableRef.ToString(); + } +#endif } } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index dac595efac..b967cf067d 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -378,6 +378,7 @@ public bool ScopedRef { } [Obsolete("Renamed to ScopedRef. This property will be removed in a future version of the decompiler.")] + [Browsable(false)] public bool LifetimeAnnotations { get { return ScopedRef; } set { ScopedRef = value; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index e357dfa443..fc15ebc3ae 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -301,7 +301,7 @@ static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstr // Thus, we have to ensure we're operating on an r-value. // Additionally, we cannot inline in cases where the C# compiler prohibits the direct use // of the rvalue (e.g. M(ref (MyStruct)obj); is invalid). - if (IsUsedAsThisPointerInCall(loadInst, out var method)) + if (IsUsedAsThisPointerInCall(loadInst, out var method, out var constrainedTo)) { if (options.HasFlag(InliningOptions.Aggressive)) { @@ -321,7 +321,7 @@ static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstr case ExpressionClassification.ReadonlyLValue: // For struct method calls on readonly lvalues, the C# compiler // only generates a temporary if it isn't a "readonly struct" - return MethodRequiresCopyForReadonlyLValue(method); + return MethodRequiresCopyForReadonlyLValue(method, constrainedTo); default: throw new InvalidOperationException("invalid expression classification"); } @@ -337,11 +337,11 @@ static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstr } } - internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method) + internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method, IType constrainedTo = null) { if (method == null) return true; - var type = method.DeclaringType; + var type = constrainedTo ?? method.DeclaringType; if (type.IsReferenceType == true) return false; // reference types are never implicitly copied if (method.ThisIsRefReadOnly) @@ -351,12 +351,13 @@ internal static bool MethodRequiresCopyForReadonlyLValue(IMethod method) internal static bool IsUsedAsThisPointerInCall(LdLoca ldloca) { - return IsUsedAsThisPointerInCall(ldloca, out _); + return IsUsedAsThisPointerInCall(ldloca, out _, out _); } - static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method) + static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method, out IType constrainedType) { method = null; + constrainedType = null; if (ldloca.Variable.Type.IsReferenceType ?? false) return false; ILInstruction inst = ldloca; @@ -370,7 +371,9 @@ static bool IsUsedAsThisPointerInCall(LdLoca ldloca, out IMethod method) { case OpCode.Call: case OpCode.CallVirt: - method = ((CallInstruction)inst.Parent).Method; + var callInst = (CallInstruction)inst.Parent; + method = callInst.Method; + constrainedType = callInst.ConstrainedTo; if (method.IsAccessor) { if (method.AccessorKind == MethodSemanticsAttributes.Getter)