diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
index 58b908f00..7b60f3ad4 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
@@ -16,6 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
+using System;
+
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class ConstructorInitializers
@@ -55,6 +57,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
+ public class MethodCallInCtorInit
+ {
+ public MethodCallInCtorInit(ConsoleKey key)
+ : this(((int)key).ToString())
+ {
+ }
+
+ public MethodCallInCtorInit(string s)
+ {
+ }
+ }
+
public struct SimpleStruct
{
public int Field1;
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 236a1c023..e6a8584b7 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -29,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.IL;
+using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@@ -3776,9 +3777,19 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitAddressOf(AddressOf inst, TranslationContext context)
{
- // HACK: this is only correct if the argument is an R-value; otherwise we're missing the copy to the temporary
+ var classification = ILInlining.ClassifyExpression(inst.Value);
var value = Translate(inst.Value, inst.Type);
value = value.ConvertTo(inst.Type, this);
+ // ILAst AddressOf copies the value to a temporary, but when invoking a method in C#
+ // on a mutable lvalue, we would end up modifying the original lvalue, not just the copy.
+ // We solve this by introducing a "redundant" cast. Casts are classified as rvalue
+ // and ensure that the C# compiler will also create a copy.
+ if (classification == ExpressionClassification.MutableLValue && value.Expression is not CastExpression)
+ {
+ value = new CastExpression(ConvertType(inst.Type), value.Expression)
+ .WithoutILInstruction()
+ .WithRR(new ConversionResolveResult(inst.Type, value.ResolveResult, Conversion.IdentityConversion));
+ }
return new DirectionExpression(FieldDirection.Ref, value)
.WithILInstruction(inst)
.WithRR(new ByReferenceResolveResult(value.ResolveResult, ReferenceKind.Ref));
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CastResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CastResolveResult.cs
deleted file mode 100644
index 9ed3987a1..000000000
--- a/ICSharpCode.Decompiler/CSharp/Resolver/CastResolveResult.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy of this
-// software and associated documentation files (the "Software"), to deal in the Software
-// without restriction, including without limitation the rights to use, copy, modify, merge,
-// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
-// to whom the Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in all copies or
-// substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-// DEALINGS IN THE SOFTWARE.
-
-using ICSharpCode.Decompiler.Semantics;
-using ICSharpCode.Decompiler.TypeSystem;
-
-namespace ICSharpCode.Decompiler.CSharp.Resolver
-{
- ///
- /// Represents an explicitly applied conversion (CastExpression or AsExpression)
- /// (a result belonging to an AST node; not implicitly inserted 'between' nodes).
- ///
- class CastResolveResult : ConversionResolveResult
- {
- // The reason this class exists is that for code like this:
- // int i = ...;
- // long n = 0;
- // n = n + (long)i;
- // The resolver will produce (and process) an CastResolveResult for the cast,
- // (with Conversion = implicit numeric conversion)
- // and then pass it into CSharpResolver.ResolveBinaryOperator().
- // That method normally wraps input arguments into another conversion
- // (the implicit conversion applied by the operator).
- // However, identity conversions do not cause the creation of ConversionResolveResult instances,
- // so the OperatorResolveResult's argument will be the CastResolveResult
- // of the cast.
- // Without this class (and instead using ConversionResolveResult for both purposes),
- // it would be hard for the conversion-processing code
- // in the ResolveVisitor to distinguish the existing conversion from the CastExpression
- // from an implicit conversion introduced by the binary operator.
- // This would cause the conversion to be processed yet again.
- // The following unit tests would fail without this class:
- // * CastTests.ExplicitConversion_In_Assignment
- // * FindReferencesTest.FindReferencesForOpImplicitInAssignment_ExplicitCast
- // * CS0029InvalidConversionIssueTests.ExplicitConversionFromUnknownType
-
- public CastResolveResult(ConversionResolveResult rr)
- : base(rr.Type, rr.Input, rr.Conversion, rr.CheckForOverflow)
- {
- }
-
- public CastResolveResult(IType targetType, ResolveResult input, Conversion conversion, bool checkForOverflow)
- : base(targetType, input, conversion, checkForOverflow)
- {
- }
- }
-}
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 2f1d823bc..e168def6e 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -275,7 +275,6 @@
-
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 0b4657148..21c6e9d45 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -240,7 +240,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var loadInst = r.LoadInst;
if (loadInst.OpCode == OpCode.LdLoca)
{
- if (!IsGeneratedValueTypeTemporary((LdLoca)loadInst, v, inlinedExpression))
+ if (!IsGeneratedValueTypeTemporary((LdLoca)loadInst, v, inlinedExpression, options))
return false;
}
else
@@ -285,7 +285,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
/// The load instruction (a descendant within 'next')
/// The variable being inlined.
- static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression)
+ static bool IsGeneratedValueTypeTemporary(LdLoca loadInst, ILVariable v, ILInstruction inlinedExpression, InliningOptions options)
{
Debug.Assert(loadInst.Variable == v);
// Inlining a value type variable is allowed only if the resulting code will maintain the semantics
@@ -295,6 +295,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// of the rvalue (e.g. M(ref (MyStruct)obj); is invalid).
if (IsUsedAsThisPointerInCall(loadInst, out var method))
{
+ if (options.HasFlag(InliningOptions.Aggressive))
+ {
+ // Inlining might be required in ctor initializers (see #2714).
+ // expressionBuilder.VisitAddressOf will handle creating the copy for us.
+ return true;
+ }
+
switch (ClassifyExpression(inlinedExpression))
{
case ExpressionClassification.RValue:
@@ -397,13 +404,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return inst != ldloca && inst.Parent is LdObj;
}
- internal enum ExpressionClassification
- {
- RValue,
- MutableLValue,
- ReadonlyLValue,
- }
-
///
/// Gets whether the instruction, when converted into C#, turns into an l-value that can
/// be used to mutate a value-type.
@@ -828,4 +828,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return CanMoveInto(arg, stmt, arg);
}
}
+
+ internal enum ExpressionClassification
+ {
+ RValue,
+ MutableLValue,
+ ReadonlyLValue,
+ }
}