Browse Source

Merge pull request #3980 from sailro/fix-primary-constructor-field-dependency

Keep instance-dependent assignments in constructors
pull/3987/head
Siegfried Pammer 1 month ago committed by GitHub
parent
commit
544335c86f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 30
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
  2. 14
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 19
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

30
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs

@ -134,6 +134,36 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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

14
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2788,6 +2788,20 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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;
}
}

19
ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -356,6 +356,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -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 @@ -403,6 +404,24 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
bool ReferencesInstanceMember(Expression expression)
{
foreach (var node in expression.DescendantsAndSelf.OfType<Expression>())
{
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);

Loading…
Cancel
Save