diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs index 6d2d4041a..0be38a28c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs @@ -134,6 +134,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int B = b; } + + private struct StructInitializerDependsOnField + { + public int A; + + public int B; + + public StructInitializerDependsOnField(int value) + { + A = value; + B = A + 1; + } + } + + private struct StructInitializerDependsOnQualifiedField + { + public int value; + + public int b; + + // The parameter shadows the field, so reading the field keeps its qualifier. A + // field initializer cannot name the instance, so this has to stay a constructor + // rather than becoming a primary one - the same rule as the fixture above, which + // only differs in how the read happens to be spelled. + public StructInitializerDependsOnQualifiedField(int value) + { + this.value = value; + b = this.value + 1; + } + } #endif public class ClassWithConstant diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 3df219962..6d1e9ef7a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2788,6 +2788,20 @@ namespace ICSharpCode.Decompiler.CSharp .WithoutILInstruction(); } translatedTarget = EnsureTargetNotNullable(translatedTarget, target); + if (translatedTarget.Expression is ThisReferenceExpression) + { + // Give an explicit `this` the same resolve result the base-reference branch + // above gives `base`, and that the resolver gives the unqualified spelling of + // the same access. ConvertVariable annotates it as an ordinary local, so + // without this a consumer asking "does this expression reach instance state" + // gets a different answer depending on whether the qualifier happened to be + // printed - and the qualifier is printed for reasons (a parameter of the same + // name, AlwaysQualifyMemberReferences) that have nothing to do with the + // question being asked. + translatedTarget = new ThisReferenceExpression() + .WithILInstruction(target) + .WithRR(new ThisResolveResult(translatedTarget.Type, nonVirtualInvocation)); + } return translatedTarget; } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index 63da3e010..09af9bbfc 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -356,6 +356,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms var initializer = InitializerSequence.Analyze(this, ctor, ctorMethod); if (initializer is { CoversFullBody: true, HasDuplicateAssignments: false, Statements.Count: > 0 } + && !initializer.Statements.Any(statement => ReferencesInstanceMember(statement.Initializer)) && ctorMethod.Accessibility == expectedCtorAccessibility) { bool transformToPrimaryConstructor = MetadataTokens.GetRowNumber(ctorMethod.MetadataToken) == firstMethodRowNumber; @@ -403,6 +404,24 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + bool ReferencesInstanceMember(Expression expression) + { + foreach (var node in expression.DescendantsAndSelf.OfType()) + { + switch (node.GetResolveResult()) + { + case ThisResolveResult: + return true; + case MemberResolveResult { + TargetResult: ThisResolveResult, + Member: { IsStatic: false } member + } when member is not IField field || !IsGeneratedPrimaryConstructorBackingField(field): + return true; + } + } + return false; + } + if (StaticConstructor != null) { StaticInitializers = InitializerSequence.Analyze(this, StaticConstructorDecl!, StaticConstructor);