Browse Source

Fix #3452: More bugs with primary constructors.

pull/3613/head
Siegfried Pammer 2 months ago
parent
commit
46c99f1a21
  1. 50
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs
  2. 50
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs
  3. 8
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  4. 16
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

50
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs

@ -4,6 +4,8 @@ using System.Threading;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Playstation namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Playstation
{ {
#pragma warning disable CS0414, CS9113, CS9124
public record struct CopilotContextId public record struct CopilotContextId
{ {
public Guid Id { get; } public Guid Id { get; }
@ -176,6 +178,54 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Playstation
Leet = (int)Math.Min(dummy1, dummy2); Leet = (int)Math.Min(dummy1, dummy2);
} }
} }
internal class Issue3452
{
private struct Data
{
public object Obj;
}
private class C1(object obj)
{
internal Data d = new Data {
Obj = obj
};
}
private class C2(object obj)
{
public object Obj => obj;
}
private class C3(StringComparison comparison)
{
private StringComparison _comparison = comparison;
internal StringComparison Test()
{
return comparison;
}
}
private struct S1
{
internal Data d;
public S1(object obj)
{
d = new Data {
Obj = obj
};
}
}
private struct S2(object obj)
{
public object Obj => obj;
}
}
public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode); public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode);
[DebuggerDisplay("{DebuggerDisplay()}")] [DebuggerDisplay("{DebuggerDisplay()}")]

50
ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs

@ -4,6 +4,8 @@ using System.Threading;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.PlaystationPreferPrimary namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.PlaystationPreferPrimary
{ {
#pragma warning disable CS0414, CS9113, CS9124
public record struct CopilotContextId public record struct CopilotContextId
{ {
public Guid Id { get; } public Guid Id { get; }
@ -157,6 +159,48 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.PlaystationPreferPrimary
public int Leet = (int)Math.Min(dummy1, dummy2); public int Leet = (int)Math.Min(dummy1, dummy2);
} }
internal class Issue3452
{
private struct Data
{
public object Obj;
}
private class C1(object obj)
{
internal Data d = new Data {
Obj = obj
};
}
private class C2(object obj)
{
public object Obj => obj;
}
private class C3(StringComparison comparison)
{
private StringComparison _comparison = comparison;
internal StringComparison Test()
{
return comparison;
}
}
private struct S1(object obj)
{
internal Data d = new Data {
Obj = obj
};
}
private struct S2(object obj)
{
public object Obj => obj;
}
}
public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode); public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode);
[DebuggerDisplay("{DebuggerDisplay()}")] [DebuggerDisplay("{DebuggerDisplay()}")]
@ -238,14 +282,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.PlaystationPreferPrimary
} }
} }
public class PersonRegular2 public class PersonRegular2(string name, int age)
{ {
private readonly string _name = "name" + Environment.GetEnvironmentVariable("Path"); private readonly string _name = "name" + Environment.GetEnvironmentVariable("Path");
private readonly int _age = Environment.GetEnvironmentVariable("Path")?.Length ?? (-1); private readonly int _age = Environment.GetEnvironmentVariable("Path")?.Length ?? (-1);
public PersonRegular2(string name, int age)
{
}
} }
public record QueryParameter(string name, object? value, bool encode = true) : NamedParameter(name, value, encode); public record QueryParameter(string name, object? value, bool encode = true) : NamedParameter(name, value, encode);

8
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -185,7 +185,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (method.IsStatic || !method.IsConstructor) if (method.IsStatic || !method.IsConstructor)
continue; continue;
var m = method.Specialize(subst); var m = method.Specialize(subst);
var body = DecompileBody(method); var body = DecompileBody(method, allTransforms: method.Accessibility == Accessibility.Public && !recordTypeDef.IsRecord);
if (body == null) if (body == null)
continue; continue;
if (primaryCtor == null && method.Accessibility == Accessibility.Public && IsPrimaryConstructorBody(m, method, body)) if (primaryCtor == null && method.Accessibility == Accessibility.Public && IsPrimaryConstructorBody(m, method, body))
@ -268,7 +268,7 @@ namespace ICSharpCode.Decompiler.CSharp
else else
{ {
backingMember = field; backingMember = field;
referencesMembersDeclaredByPrimaryConstructor |= valueReferencesParameter && recordTypeDef.IsReferenceType == true; referencesMembersDeclaredByPrimaryConstructor |= valueReferencesParameter && (FieldIsGenerated(field) || recordTypeDef.IsReferenceType == true);
} }
if (offset < method.Parameters.Count) if (offset < method.Parameters.Count)
{ {
@ -1206,7 +1206,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
Block DecompileBody(IMethod method) Block DecompileBody(IMethod method, bool allTransforms = false)
{ {
if (method == null || method.MetadataToken.IsNil) if (method == null || method.MetadataToken.IsNil)
return null; return null;
@ -1223,7 +1223,7 @@ namespace ICSharpCode.Decompiler.CSharp
var body = typeSystem.MainModule.MetadataFile.GetMethodBody(methodDef.RelativeVirtualAddress); var body = typeSystem.MainModule.MetadataFile.GetMethodBody(methodDef.RelativeVirtualAddress);
var ilReader = new ILReader(typeSystem.MainModule); var ilReader = new ILReader(typeSystem.MainModule);
var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken); var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken);
var settings = new DecompilerSettings(LanguageVersion.CSharp1); var settings = allTransforms ? this.settings : new DecompilerSettings(LanguageVersion.CSharp1);
var transforms = CSharpDecompiler.GetILTransforms(); var transforms = CSharpDecompiler.GetILTransforms();
// Remove the last couple transforms -- we don't need variable names etc. here // Remove the last couple transforms -- we don't need variable names etc. here
int lastBlockTransform = transforms.FindLastIndex(t => t is BlockILTransform); int lastBlockTransform = transforms.FindLastIndex(t => t is BlockILTransform);

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

@ -227,14 +227,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// Recognize field or property initializers: // Recognize field or property initializers:
// Translate first statement in all ctors (if all ctors have the same statement) into an initializer. // Translate first statement in all ctors (if all ctors have the same statement) into an initializer.
bool allSame; bool allSame;
bool isStructPrimaryCtor = false;
do do
{ {
Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault()); Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault());
if (!m.Success) if (!m.Success)
break; break;
IMember fieldOrPropertyOrEvent = (m.Get<AstNode>("fieldAccess").Single().GetSymbol() as IMember)?.MemberDefinition; IMember fieldOrPropertyOrEvent = (m.Get<AstNode>("fieldAccess").Single().GetSymbol() as IMember)?.MemberDefinition;
if (!(fieldOrPropertyOrEvent is IField) && !(fieldOrPropertyOrEvent is IProperty) && !(fieldOrPropertyOrEvent is IEvent)) if (fieldOrPropertyOrEvent is not (IField or IProperty or IEvent))
break; break;
var fieldOrPropertyOrEventDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrPropertyOrEvent) as EntityDeclaration; var fieldOrPropertyOrEventDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrPropertyOrEvent) as EntityDeclaration;
// Cannot transform if it is a custom event. // Cannot transform if it is a custom event.
@ -245,12 +244,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// 'this'/'base' cannot be used in initializers // 'this'/'base' cannot be used in initializers
if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression)) if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression))
break; break;
var v = initializer.Annotation<ILVariableResolveResult>()?.Variable;
if (v?.Kind == IL.VariableKind.Parameter && IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent, record)) if (record != null && ctorMethodDef.Equals(record.PrimaryConstructor))
{ {
// remove record ctor parameter assignments // remove record ctor parameter assignments
isStructPrimaryCtor = true; if (fieldOrPropertyOrEvent is IField f && initializer.Annotation<ILVariableResolveResult>()?.Variable is ILVariable v)
if (fieldOrPropertyOrEvent is IField f)
fieldToVariableMap.Add(f, v); fieldToVariableMap.Add(f, v);
} }
else else
@ -259,14 +257,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (fieldOrPropertyOrEventDecl == null) if (fieldOrPropertyOrEventDecl == null)
break; break;
// or if this is a struct record, but not the primary ctor // or if this is a struct record, but not the primary ctor
if (declaringTypeDefinition.IsRecord && declaringTypeDefinition.IsReferenceType == false && !isStructPrimaryCtor) if (declaringTypeDefinition.IsRecord && declaringTypeDefinition.IsReferenceType == false)
break; break;
// or if this is not a primary constructor and the initializer contains any parameter reference, we have to abort // or if this is not a primary constructor and the initializer contains any parameter reference, we have to abort
if (!ctorMethodDef.Equals(record?.PrimaryConstructor)) if (!ctorMethodDef.Equals(record?.PrimaryConstructor))
{ {
bool referencesParameter = initializer.Annotation<ILInstruction>()?.Descendants bool referencesParameter = initializer.Annotations.OfType<ILInstruction>().Any(inst => inst.Descendants
.OfType<IInstructionWithVariableOperand>() .OfType<IInstructionWithVariableOperand>()
.Any(inst => inst.Variable.Kind == VariableKind.Parameter) ?? false; .Any(inst => inst.Variable.Kind == VariableKind.Parameter));
if (referencesParameter) if (referencesParameter)
break; break;
} }

Loading…
Cancel
Save