Skip to content

Commit f0c39c1

Browse files
committed
Fix nested type fallback decompilation
1 parent 8f36857 commit f0c39c1

7 files changed

Lines changed: 78 additions & 7 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ dotnet format DecompilerServer.sln
122122
- Cache decompiled C# source with line indexing
123123
- Support ranged retrieval without re-decompilation
124124
- Include proper headers/footers and maintain source document metadata
125+
- When decompiling nested types by name, prefer reflection names such as `Outer+Inner`; dotted names such as `Outer.Inner` can fail in ILSpy lookup paths even when the type exists in metadata.
125126

126127
### IL Handling
127128
- Use ICSharpCode.Decompiler.Disassembler for readable IL output

DecompilerServer.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8-
<Version>1.0.0</Version>
8+
<Version>1.0.1</Version>
99
</PropertyGroup>
1010

1111
<ItemGroup>
@@ -16,6 +16,7 @@
1616

1717
<ItemGroup>
1818
<Compile Remove="EmbeddedSourceTestLibrary/**" />
19+
<Compile Remove="NestedNoSymbolsTestLibrary/**" />
1920
<Compile Remove="Tests/**" />
2021
<Compile Remove="TestLibrary/**" />
2122
</ItemGroup>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<DebugType>none</DebugType>
8+
<DebugSymbols>false</DebugSymbols>
9+
</PropertyGroup>
10+
11+
</Project>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NestedNoSymbolsTestLibrary;
2+
3+
public class OuterContainer
4+
{
5+
public class NestedWorker
6+
{
7+
public int Compute(int value)
8+
{
9+
return value + 1;
10+
}
11+
}
12+
}

Services/DecompilerService.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ private string DecompileEntity(IEntity entity)
189189
var decompiler = _contextManager.GetDecompiler();
190190
return entity switch
191191
{
192-
ITypeDefinition type => decompiler.DecompileTypeAsString(type.FullTypeName),
192+
ITypeDefinition type => DecompileType(type, decompiler),
193193
IMethod method => DecompileMethod(method, decompiler),
194194
IField field => DecompileField(field, decompiler),
195195
IProperty property => DecompileProperty(property, decompiler),
@@ -199,6 +199,23 @@ private string DecompileEntity(IEntity entity)
199199
}
200200
}
201201

202+
private static string DecompileType(IType type, CSharpDecompiler decompiler)
203+
{
204+
return decompiler.DecompileTypeAsString(GetDecompilerTypeName(type));
205+
}
206+
207+
private static FullTypeName GetDecompilerTypeName(IType type)
208+
{
209+
if (!string.IsNullOrWhiteSpace(type.ReflectionName))
210+
return new FullTypeName(type.ReflectionName);
211+
212+
return type switch
213+
{
214+
ITypeDefinition typeDefinition => typeDefinition.FullTypeName,
215+
_ => new FullTypeName(type.FullName)
216+
};
217+
}
218+
202219
private static string StripHeader(string code)
203220
{
204221
var lines = code.Split('\n');
@@ -248,31 +265,31 @@ private string DecompileMethod(IMethod method, CSharpDecompiler decompiler)
248265
if (method.DeclaringType == null)
249266
return $"// Error: Method '{method.Name}' has no declaring type and cannot be decompiled";
250267

251-
return decompiler.DecompileTypeAsString(new ICSharpCode.Decompiler.TypeSystem.FullTypeName(method.DeclaringType.FullName));
268+
return DecompileType(method.DeclaringType, decompiler);
252269
}
253270

254271
private string DecompileField(IField field, CSharpDecompiler decompiler)
255272
{
256273
if (field.DeclaringType == null)
257274
return $"// Error: Field '{field.Name}' has no declaring type and cannot be decompiled";
258275

259-
return decompiler.DecompileTypeAsString(new ICSharpCode.Decompiler.TypeSystem.FullTypeName(field.DeclaringType.FullName));
276+
return DecompileType(field.DeclaringType, decompiler);
260277
}
261278

262279
private string DecompileProperty(IProperty property, CSharpDecompiler decompiler)
263280
{
264281
if (property.DeclaringType == null)
265282
return $"// Error: Property '{property.Name}' has no declaring type and cannot be decompiled";
266283

267-
return decompiler.DecompileTypeAsString(new ICSharpCode.Decompiler.TypeSystem.FullTypeName(property.DeclaringType.FullName));
284+
return DecompileType(property.DeclaringType, decompiler);
268285
}
269286

270287
private string DecompileEvent(IEvent evt, CSharpDecompiler decompiler)
271288
{
272289
if (evt.DeclaringType == null)
273290
return $"// Error: Event '{evt.Name}' has no declaring type and cannot be decompiled";
274291

275-
return decompiler.DecompileTypeAsString(new ICSharpCode.Decompiler.TypeSystem.FullTypeName(evt.DeclaringType.FullName));
292+
return DecompileType(evt.DeclaringType, decompiler);
276293
}
277294

278295
private string ComputeHash(string content)

Tests/DecompilationFunctionalityTests.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,34 @@ public void DecompileInterface_ShouldContainInterfaceDeclaration()
6060
Assert.Contains("InterfaceProperty", sourceCode);
6161
}
6262

63+
[Fact]
64+
public void DecompileNestedTypeWithoutSymbols_ShouldUseFallbackDecompiler()
65+
{
66+
using var contextManager = new AssemblyContextManager();
67+
var memberResolver = new MemberResolver(contextManager);
68+
var decompilerService = new DecompilerService(contextManager, memberResolver);
69+
70+
var assemblyPath = typeof(global::NestedNoSymbolsTestLibrary.OuterContainer.NestedWorker).Assembly.Location;
71+
contextManager.LoadAssemblyDirect(assemblyPath);
72+
73+
var nestedType = contextManager.GetAllTypes()
74+
.Single(type => type.ReflectionName == "NestedNoSymbolsTestLibrary.OuterContainer+NestedWorker");
75+
76+
var typeDocument = decompilerService.DecompileMember(memberResolver.GenerateMemberId(nestedType));
77+
var typeSource = string.Join("\n", typeDocument.Lines);
78+
79+
Assert.Equal(SourceKinds.Decompiled, typeDocument.SourceKind);
80+
Assert.Contains("class NestedWorker", typeSource);
81+
82+
var method = nestedType.Methods.Single(m => m.Name == "Compute");
83+
var methodDocument = decompilerService.DecompileMember(memberResolver.GenerateMemberId(method));
84+
var methodSource = string.Join("\n", methodDocument.Lines);
85+
86+
Assert.Equal(SourceKinds.Decompiled, methodDocument.SourceKind);
87+
Assert.Contains("class NestedWorker", methodSource);
88+
Assert.Contains("Compute", methodSource);
89+
}
90+
6391
[Fact]
6492
public void DecompileDerivedClass_ShouldShowInheritance()
6593
{
@@ -177,4 +205,4 @@ public void ResponseFormatter_ProducesValidJsonForDecompilerResults()
177205
Assert.Contains("\"language\":\"C#\"", jsonResponse);
178206
Assert.Contains("\"totalLines\":", jsonResponse);
179207
}
180-
}
208+
}

Tests/Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<ItemGroup>
3030
<ProjectReference Include="../DecompilerServer.csproj" />
3131
<ProjectReference Include="../EmbeddedSourceTestLibrary/EmbeddedSourceTestLibrary.csproj" />
32+
<ProjectReference Include="../NestedNoSymbolsTestLibrary/NestedNoSymbolsTestLibrary.csproj" />
3233
<ProjectReference Include="../TestLibrary/TestLibrary.csproj" />
3334
</ItemGroup>
3435

0 commit comments

Comments
 (0)