Skip to content

Commit cb48768

Browse files
Fix #3610: Add more primary constructor test cases.
1 parent 46c99f1 commit cb48768

File tree

4 files changed

+176
-35
lines changed

4 files changed

+176
-35
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Threading;
45

@@ -226,6 +227,79 @@ private struct S2(object obj)
226227
}
227228
}
228229

230+
internal class Issue3610
231+
{
232+
private struct CtorDoubleAssignmentTest
233+
{
234+
#if EXPECTED_OUTPUT
235+
public bool Value = false;
236+
237+
public CtorDoubleAssignmentTest(string arg1, int arg2)
238+
{
239+
Value = true;
240+
}
241+
#else
242+
public bool Value;
243+
244+
public CtorDoubleAssignmentTest(string arg1, int arg2)
245+
{
246+
Value = false;
247+
Value = true;
248+
}
249+
#endif
250+
}
251+
252+
private struct CtorDoubleAssignmentTest2
253+
{
254+
public bool Value = true;
255+
256+
public CtorDoubleAssignmentTest2(string arg1, int arg2)
257+
{
258+
Value = false;
259+
}
260+
}
261+
262+
private class FieldInitTest
263+
{
264+
public bool Flag = true;
265+
public Func<int, int> Action = (int a) => a;
266+
public string Value;
267+
268+
public FieldInitTest(string value)
269+
{
270+
Value = value;
271+
}
272+
}
273+
274+
private abstract class PCFieldInitTest(StringComparison value)
275+
{
276+
private StringComparison _value = value;
277+
278+
public bool Func()
279+
{
280+
return value == StringComparison.Ordinal;
281+
}
282+
}
283+
284+
private class RecordTest<T>
285+
{
286+
private interface IInterface
287+
{
288+
T[] Objects { get; }
289+
}
290+
291+
protected record Record(T[] Objects) : IInterface
292+
{
293+
public Record(List<T> objects)
294+
: this(objects.ToArray())
295+
{
296+
}
297+
}
298+
}
299+
300+
private abstract record RecordTest2(Guid[] Guids);
301+
}
302+
229303
public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode);
230304

231305
[DebuggerDisplay("{DebuggerDisplay()}")]

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Threading;
45

@@ -201,6 +202,79 @@ private struct S2(object obj)
201202
}
202203
}
203204

205+
206+
internal class Issue3610
207+
{
208+
private struct CtorDoubleAssignmentTest
209+
{
210+
#if EXPECTED_OUTPUT
211+
public bool Value = false;
212+
213+
public CtorDoubleAssignmentTest(string arg1, int arg2)
214+
{
215+
Value = true;
216+
}
217+
#else
218+
public bool Value;
219+
220+
public CtorDoubleAssignmentTest(string arg1, int arg2)
221+
{
222+
Value = false;
223+
Value = true;
224+
}
225+
#endif
226+
}
227+
228+
private struct CtorDoubleAssignmentTest2
229+
{
230+
public bool Value = true;
231+
232+
public CtorDoubleAssignmentTest2(string arg1, int arg2)
233+
{
234+
Value = false;
235+
}
236+
}
237+
238+
private class FieldInitTest
239+
{
240+
public bool Flag = true;
241+
public Func<int, int> Action = (int a) => a;
242+
public string Value;
243+
244+
public FieldInitTest(string value)
245+
{
246+
Value = value;
247+
}
248+
}
249+
250+
private abstract class PCFieldInitTest(StringComparison value)
251+
{
252+
private StringComparison _value = value;
253+
254+
public bool Func()
255+
{
256+
return value == StringComparison.Ordinal;
257+
}
258+
}
259+
260+
private class RecordTest<T>
261+
{
262+
private interface IInterface
263+
{
264+
T[] Objects { get; }
265+
}
266+
267+
protected record Record(T[] Objects) : IInterface
268+
{
269+
public Record(List<T> objects)
270+
: this(objects.ToArray())
271+
{
272+
}
273+
}
274+
}
275+
276+
private abstract record RecordTest2(Guid[] Guids);
277+
}
204278
public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode);
205279

206280
[DebuggerDisplay("{DebuggerDisplay()}")]

ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ IMethod DetectPrimaryConstructor()
177177

178178
Dictionary<IMethod, IMethod> ctorCallChain = new Dictionary<IMethod, IMethod>();
179179

180+
Accessibility minAccessibility = recordTypeDef.IsAbstract ? Accessibility.Protected : Accessibility.Public;
181+
180182
foreach (var method in recordTypeDef.Methods)
181183
{
182184
cancellationToken.ThrowIfCancellationRequested();
@@ -185,10 +187,10 @@ IMethod DetectPrimaryConstructor()
185187
if (method.IsStatic || !method.IsConstructor)
186188
continue;
187189
var m = method.Specialize(subst);
188-
var body = DecompileBody(method, allTransforms: method.Accessibility == Accessibility.Public && !recordTypeDef.IsRecord);
190+
var body = DecompileBody(method, allTransforms: method.Accessibility >= minAccessibility && !recordTypeDef.IsRecord);
189191
if (body == null)
190192
continue;
191-
if (primaryCtor == null && method.Accessibility == Accessibility.Public && IsPrimaryConstructorBody(m, method, body))
193+
if (primaryCtor == null && method.Accessibility >= minAccessibility && IsPrimaryConstructorBody(m, method, body))
192194
{
193195
primaryCtor = method;
194196
}
@@ -199,7 +201,7 @@ IMethod DetectPrimaryConstructor()
199201
// if a chained to a constructor of a different type, give up
200202
if (calledCtor != null && calledCtor.DeclaringTypeDefinition?.Equals(method.DeclaringTypeDefinition) != true)
201203
return null;
202-
ctorCallChain[method] = calledCtor;
204+
ctorCallChain[method] = (IMethod)calledCtor?.MemberDefinition;
203205
}
204206
if (primaryCtor == null)
205207
{
@@ -272,6 +274,10 @@ bool IsPrimaryConstructorBody(IMethod method, IMethod unspecializedMethod, Block
272274
}
273275
if (offset < method.Parameters.Count)
274276
{
277+
if (primaryCtorParameterToAutoPropertyOrBackingField.ContainsKey(unspecializedMethod.Parameters[offset]))
278+
return false;
279+
if (autoPropertyOrBackingFieldToPrimaryCtorParameter.ContainsKey(backingMember))
280+
return false;
275281
primaryCtorParameterToAutoPropertyOrBackingField.Add(unspecializedMethod.Parameters[offset], backingMember);
276282
autoPropertyOrBackingFieldToPrimaryCtorParameter.Add(backingMember, unspecializedMethod.Parameters[offset]);
277283
}

ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ record = null;
264264
{
265265
bool referencesParameter = initializer.Annotations.OfType<ILInstruction>().Any(inst => inst.Descendants
266266
.OfType<IInstructionWithVariableOperand>()
267-
.Any(inst => inst.Variable.Kind == VariableKind.Parameter));
267+
.Any(inst => inst.Variable.Kind == VariableKind.Parameter && ctorMethodDef.Equals(inst.Variable.Function.Method)));
268268
if (referencesParameter)
269269
break;
270270
}
@@ -287,44 +287,33 @@ record = null;
287287
}
288288
if (allSame)
289289
{
290-
foreach (var ctor in instanceCtorsNotChainingWithThis)
291-
ctor.Body.First().Remove();
292-
if (fieldOrPropertyOrEventDecl == null)
293-
continue;
294-
if (ctorIsUnsafe && IntroduceUnsafeModifier.IsUnsafe(initializer))
290+
if (fieldOrPropertyOrEventDecl is PropertyDeclaration { Initializer.IsNull: true } pd)
295291
{
296-
fieldOrPropertyOrEventDecl.Modifiers |= Modifiers.Unsafe;
292+
pd.Initializer = initializer.Detach();
297293
}
298-
if (fieldOrPropertyOrEventDecl is PropertyDeclaration pd)
294+
else if (fieldOrPropertyOrEventDecl is FieldDeclaration or EventDeclaration)
299295
{
300-
pd.Initializer = initializer.Detach();
296+
var vdecl = fieldOrPropertyOrEventDecl.GetChildrenByRole(Roles.Variable).Single();
297+
if (vdecl.Initializer.IsNull)
298+
{
299+
vdecl.Initializer = initializer.Detach();
300+
}
301+
else
302+
{
303+
break;
304+
}
301305
}
302-
else
306+
if (ctorIsUnsafe && fieldOrPropertyOrEventDecl != null && IntroduceUnsafeModifier.IsUnsafe(initializer))
303307
{
304-
fieldOrPropertyOrEventDecl.GetChildrenByRole(Roles.Variable).Single().Initializer = initializer.Detach();
308+
fieldOrPropertyOrEventDecl.Modifiers |= Modifiers.Unsafe;
305309
}
310+
foreach (var ctor in instanceCtorsNotChainingWithThis)
311+
ctor.Body.First().Remove();
306312
}
307313
} while (allSame);
308314
}
309315
}
310316

311-
bool IsPropertyDeclaredByPrimaryCtor(IMember m, RecordDecompiler record)
312-
{
313-
if (record == null)
314-
return false;
315-
switch (m)
316-
{
317-
case IProperty p:
318-
return record.IsPropertyDeclaredByPrimaryConstructor(p);
319-
case IField f:
320-
return record.PrimaryConstructor != null;
321-
case IEvent e:
322-
return record.PrimaryConstructor != null;
323-
default:
324-
return false;
325-
}
326-
}
327-
328317
void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition)
329318
{
330319
// if we're outside of a type definition skip this altogether
@@ -390,9 +379,7 @@ void HandleStaticFieldInitializers(IEnumerable<AstNode> members, ITypeDefinition
390379
continue;
391380
}
392381
var fieldOrPropertyDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrProperty) as EntityDeclaration;
393-
if (fieldOrPropertyDecl == null)
394-
break;
395-
if (ctorIsUnsafe && IntroduceUnsafeModifier.IsUnsafe(assignment.Right))
382+
if (ctorIsUnsafe && fieldOrPropertyDecl != null && IntroduceUnsafeModifier.IsUnsafe(assignment.Right))
396383
{
397384
fieldOrPropertyDecl.Modifiers |= Modifiers.Unsafe;
398385
}
@@ -416,7 +403,7 @@ void HandleStaticFieldInitializers(IEnumerable<AstNode> members, ITypeDefinition
416403
}
417404
}
418405
}
419-
else if (fieldOrPropertyDecl is PropertyDeclaration { IsAutomaticProperty: true } pd)
406+
else if (fieldOrPropertyDecl is PropertyDeclaration { IsAutomaticProperty: true, Initializer.IsNull: true } pd)
420407
{
421408
pd.Initializer = assignment.Right.Detach();
422409
}

0 commit comments

Comments
 (0)