diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index c0d10da6f..79116b15f 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -202,6 +202,9 @@
+
+
+
diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
index ea17c49f8..281b12ee4 100644
--- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
@@ -405,6 +405,12 @@ namespace ICSharpCode.Decompiler.Tests
await Run(settings: new DecompilerSettings { SortSwitchSections = true, FileScopedNamespaces = false });
}
+ [Test]
+ public async Task Issue3729()
+ {
+ await Run();
+ }
+
async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null,
AssemblerOptions assemblerOptions = AssemblerOptions.Library)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs
new file mode 100644
index 000000000..4ac1684c3
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Runtime.CompilerServices;
+using Library1;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
+{
+ public class Issue3729
+ {
+ private MyStruct structField;
+
+ public void TestSingleArgCtor()
+ {
+ //IL_0003: Unknown result type (might be due to invalid IL or missing references)
+ Console.WriteLine("Test new struct: " + ((object)new MyStruct(4)/*cast due to constrained. prefix*/).ToString());
+ }
+
+ public void TestParameterlessCtor()
+ {
+ //IL_0002: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0007: Unknown result type (might be due to invalid IL or missing references)
+ MyEmptyStruct val = new MyEmptyStruct();
+ Console.WriteLine(val);
+ }
+
+ public void TestMultiArgCtor()
+ {
+ //IL_0011: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0016: Unknown result type (might be due to invalid IL or missing references)
+ MyBigStruct val = new MyBigStruct(1, "hello", 3.14);
+ Console.WriteLine(val);
+ }
+
+ public void TestFieldCtor()
+ {
+ //IL_0007: Unknown result type (might be due to invalid IL or missing references)
+ ref MyStruct reference = ref structField;
+ reference = new MyStruct(5);
+ }
+
+ public unsafe static void TestPointerCtor(void* ptr)
+ {
+ //IL_0002: Unknown result type (might be due to invalid IL or missing references)
+ System.Runtime.CompilerServices.Unsafe.Write(ptr, new MyStruct(6));
+ }
+
+ public void TestArrayElemCtor()
+ {
+ //IL_000f: Unknown result type (might be due to invalid IL or missing references)
+ MyStruct[] array = (MyStruct[])(object)new MyStruct[1] {
+ new MyStruct(7)
+ };
+ }
+
+ public void TestGenericStructCtor()
+ {
+ //IL_0003: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0008: Unknown result type (might be due to invalid IL or missing references)
+ MyGenericStruct val = new MyGenericStruct(4);
+ Console.WriteLine(val);
+ }
+
+ public void TestRefTypeNewobj()
+ {
+ //IL_0000: Unknown result type (might be due to invalid IL or missing references)
+ //IL_0006: Expected O, but got Unknown
+ MyClass value = new MyClass();
+ Console.WriteLine(value);
+ }
+ }
+ public class Issue3729_DerivedFromUnknown : MissingBase
+ {
+ }
+ public class Issue3729_DerivedFromUnknownWithArgs : MissingBase
+ {
+ public Issue3729_DerivedFromUnknownWithArgs()
+ : base(42)
+ {
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.il
new file mode 100644
index 000000000..f468655e6
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.il
@@ -0,0 +1,228 @@
+// Reproducer for issue #3729:
+// Decompilation of a struct constructor call where the struct's defining
+// assembly (Library1) cannot be resolved at decompile time.
+// Mirrors what csc emits for `new Library1.MyStruct(4)` followed by
+// string concat with the struct value.
+
+.assembly extern Library1
+{
+}
+.assembly extern System.Runtime
+{
+ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
+ .ver 4:0:0:0
+}
+.assembly extern System.Console
+{
+ .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
+ .ver 4:0:0:0
+}
+.assembly Issue3729
+{
+ .ver 1:0:0:0
+}
+.module Issue3729.dll
+
+.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729
+ extends [System.Runtime]System.Object
+{
+ .field private valuetype [Library1]Library1.MyStruct structField
+
+ // single-int-arg struct ctor with subsequent constrained.callvirt ToString()
+ .method public hidebysig
+ instance void TestSingleArgCtor () cil managed
+ {
+ .maxstack 2
+ .locals init (
+ [0] valuetype [Library1]Library1.MyStruct
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0002: ldc.i4.4
+ IL_0003: call instance void [Library1]Library1.MyStruct::.ctor(int32)
+ IL_0008: ldstr "Test new struct: "
+ IL_000d: ldloca.s 0
+ IL_000f: constrained. [Library1]Library1.MyStruct
+ IL_0015: callvirt instance string [System.Runtime]System.Object::ToString()
+ IL_001a: call string [System.Runtime]System.String::Concat(string, string)
+ IL_001f: call void [System.Console]System.Console::WriteLine(string)
+ IL_0024: ret
+ }
+
+ // explicit parameterless struct ctor (C# 10+ feature in source)
+ // Single-argument ctor: the receiver sits directly below the argument.
+ .method public hidebysig
+ instance void TestParameterlessCtor () cil managed
+ {
+ .maxstack 1
+ .locals init (
+ [0] valuetype [Library1]Library1.MyEmptyStruct
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0002: call instance void [Library1]Library1.MyEmptyStruct::.ctor()
+ IL_0007: ldloc.0
+ IL_0008: box [Library1]Library1.MyEmptyStruct
+ IL_000d: call void [System.Console]System.Console::WriteLine(object)
+ IL_0012: ret
+ }
+
+ // Multi-argument ctor with mixed parameter types: the receiver sits below three arguments
+ .method public hidebysig
+ instance void TestMultiArgCtor () cil managed
+ {
+ .maxstack 5
+ .locals init (
+ [0] valuetype [Library1]Library1.MyBigStruct
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0002: ldc.i4.1
+ IL_0003: ldstr "hello"
+ IL_0008: ldc.r8 3.14
+ IL_0011: call instance void [Library1]Library1.MyBigStruct::.ctor(int32, string, float64)
+ IL_0016: ldloc.0
+ IL_0017: box [Library1]Library1.MyBigStruct
+ IL_001c: call void [System.Console]System.Console::WriteLine(object)
+ IL_0021: ret
+ }
+
+ // struct field init via `ldflda + call .ctor`. Receiver is the field
+ // address (StackType.Ref) - same shape as `ldloca`, just sourced from
+ // an instance field instead of a local.
+ .method public hidebysig
+ instance void TestFieldCtor () cil managed
+ {
+ .maxstack 2
+
+ IL_0000: ldarg.0
+ IL_0001: ldflda valuetype [Library1]Library1.MyStruct ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729::structField
+ IL_0006: ldc.i4.5
+ IL_0007: call instance void [Library1]Library1.MyStruct::.ctor(int32)
+ IL_000c: ret
+ }
+
+ // Unsafe code passes the target as a pointer, so the receiver is a native
+ // pointer (StackType.I) rather than a managed reference.
+ .method public hidebysig static
+ void TestPointerCtor (
+ void* ptr
+ ) cil managed
+ {
+ .maxstack 2
+
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.6
+ IL_0002: call instance void [Library1]Library1.MyStruct::.ctor(int32)
+ IL_0007: ret
+ }
+
+ // struct array element init via `ldelema + call .ctor`. Receiver is the
+ // element address (StackType.Ref) - again the same shape.
+ .method public hidebysig
+ instance void TestArrayElemCtor () cil managed
+ {
+ .maxstack 4
+ .locals init (
+ [0] valuetype [Library1]Library1.MyStruct[]
+ )
+
+ IL_0000: ldc.i4.1
+ IL_0001: newarr [Library1]Library1.MyStruct
+ IL_0006: stloc.0
+ IL_0007: ldloc.0
+ IL_0008: ldc.i4.0
+ IL_0009: ldelema [Library1]Library1.MyStruct
+ IL_000e: ldc.i4.7
+ IL_000f: call instance void [Library1]Library1.MyStruct::.ctor(int32)
+ IL_0014: ret
+ }
+
+ // generic struct instantiation `new MyGenericStruct(4)`. Declaring
+ // type at the call site is a ParameterizedType wrapping the unresolved
+ // generic def - its Kind delegates to the underlying UnknownType.Kind,
+ // so a constructed generic type is unresolved in the same way.
+ .method public hidebysig
+ instance void TestGenericStructCtor () cil managed
+ {
+ .maxstack 2
+ .locals init (
+ [0] valuetype [Library1]Library1.MyGenericStruct`1
+ )
+
+ IL_0000: ldloca.s 0
+ IL_0002: ldc.i4.4
+ IL_0003: call instance void valuetype [Library1]Library1.MyGenericStruct`1::.ctor(!0)
+ IL_0008: ldloc.0
+ IL_0009: box valuetype [Library1]Library1.MyGenericStruct`1
+ IL_000e: call void [System.Console]System.Console::WriteLine(object)
+ IL_0013: ret
+ }
+
+ // newobj on an unresolved reference type - uses OpCode.NewObj, a different
+ // IL path that already produces correct C# (HandleConstructorCall in CallBuilder).
+ // Included as a control case for the value-type ctor handling above.
+ .method public hidebysig
+ instance void TestRefTypeNewobj () cil managed
+ {
+ .maxstack 1
+ .locals init (
+ [0] class [Library1]Library1.MyClass
+ )
+
+ IL_0000: newobj instance void [Library1]Library1.MyClass::.ctor()
+ IL_0005: stloc.0
+ IL_0006: ldloc.0
+ IL_0007: call void [System.Console]System.Console::WriteLine(object)
+ IL_000c: ret
+ }
+
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor () cil managed
+ {
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance void [System.Runtime]System.Object::.ctor()
+ IL_0006: ret
+ }
+}
+
+// Classes deriving from an unresolved base class. The synthesized .ctor
+// calls `[Library1]Library1.MissingBase::.ctor(...)` with `this`
+// (StackType.O) as the receiver - exercises the StackType.Ref guard on
+// a reference-typed receiver, which has to keep falling through to the
+// default call path so the C# decompiler can render it as a normal
+// `: base(...)` constructor initializer.
+.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729_DerivedFromUnknown
+ extends [Library1]Library1.MissingBase
+{
+ // parameterless base ctor - the implicit ctor would normally be elided.
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor () cil managed
+ {
+ .maxstack 8
+
+ IL_0000: ldarg.0
+ IL_0001: call instance void [Library1]Library1.MissingBase::.ctor()
+ IL_0006: ret
+ }
+}
+
+// Same as above but the base ctor takes an argument - receiver is at
+// depth 1 (one parameter on the stack above it). Output should render
+// the explicit `: base()` initializer.
+.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue3729_DerivedFromUnknownWithArgs
+ extends [Library1]Library1.MissingBase
+{
+ .method public hidebysig specialname rtspecialname
+ instance void .ctor () cil managed
+ {
+ .maxstack 2
+
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.s 42
+ IL_0003: call instance void [Library1]Library1.MissingBase::.ctor(int32)
+ IL_0008: ret
+ }
+}