Skip to content

Commit cba8b0d

Browse files
Test dynamic member tooltip rendering
Lock the hover content the dynamic-tooltip work produces. Ambience-level cases (CSharpAmbienceTests) pin that SpecialType.Dynamic renders as "dynamic" and that a synthetic dynamic method renders its return and per-argument types - including the full hover form, confirming the unnamed synthetic parameters collapse to their types with no dangling name. An end-to-end case (HoverOnlyReferenceTests) decompiles a dynamic call and renders the symbol GetSymbol hands back, exercising the actual synthesis (argument typing from the callsite delegate), not a hand-built stand-in. Assisted-by: Claude:claude-fable-5:Claude Code
1 parent 4f9014f commit cba8b0d

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,40 @@ public void DestructorTests(ConversionFlags flags, string expectedOutput)
323323
}
324324
#endregion
325325

326+
#region Dynamic (synthesized member) tests
327+
[Test]
328+
public void DynamicType()
329+
{
330+
ambience.ConversionFlags = ConversionFlags.None;
331+
Assert.That(ambience.ConvertType(SpecialType.Dynamic), Is.EqualTo("dynamic"));
332+
}
333+
334+
[Test]
335+
public void DynamicInvokeMember()
336+
{
337+
// The shape ExpressionBuilder synthesizes for a.Compute(1, "two", d): dynamic return,
338+
// per-argument types from the callsite delegate (constants keep their compile-time type),
339+
// declared on the dynamic type.
340+
var method = new FakeMethod(compilation, SymbolKind.Method) {
341+
Name = "Compute",
342+
ReturnType = SpecialType.Dynamic,
343+
DeclaringType = SpecialType.Dynamic,
344+
Parameters = new IParameter[] {
345+
new DefaultParameter(compilation.FindType(KnownTypeCode.Int32), string.Empty),
346+
new DefaultParameter(compilation.FindType(KnownTypeCode.String), string.Empty),
347+
new DefaultParameter(SpecialType.Dynamic, string.Empty),
348+
},
349+
};
350+
ambience.ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList;
351+
Assert.That(ambience.ConvertSymbol(method), Is.EqualTo("dynamic Compute(int, string, dynamic)"));
352+
353+
// The same member as DecompilerTextView.BuildHoverContent renders it (full hover form): the
354+
// unnamed synthetic parameters collapse to their types, so there is no dangling-name artifact.
355+
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
356+
Assert.That(ambience.ConvertSymbol(method), Is.EqualTo("public dynamic dynamic.Compute(int, string, dynamic)"));
357+
}
358+
#endregion
359+
326360
#region Test types
327361
#pragma warning disable 169, 67
328362

ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

2525
using AwesomeAssertions;
2626

27+
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
28+
using ICSharpCode.Decompiler.Output;
29+
using ICSharpCode.Decompiler.TypeSystem;
2730
using ICSharpCode.ILSpy.TextView;
2831
using ICSharpCode.ILSpy.TreeNodes;
2932

@@ -41,6 +44,11 @@ public object Get(dynamic d)
4144
{
4245
return d.Property;
4346
}
47+
48+
public object Call(dynamic d)
49+
{
50+
return d.Compute(1, "two", d);
51+
}
4452
}
4553

4654
/// <summary>
@@ -76,4 +84,31 @@ public async Task Dynamic_Member_Is_HoverOnly_And_A_Click_Neither_Navigates_Nor_
7684
navigated.Should().BeFalse("clicking a hover-only reference must not navigate");
7785
view.LocalReferenceMarks.Should().BeEmpty("a hover-only reference must not highlight occurrences");
7886
}
87+
88+
[AvaloniaTest]
89+
public async Task Dynamic_Invoke_Member_Hover_Renders_Its_Synthesized_Signature()
90+
{
91+
var (_, vm) = await TestHarness.BootAsync();
92+
await vm.OpenAssemblyAsync(typeof(DynamicMemberSample).Assembly.Location);
93+
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
94+
"ILSpy.Tests",
95+
"ICSharpCode.ILSpy.Tests.TextView",
96+
"ICSharpCode.ILSpy.Tests.TextView.DynamicMemberSample");
97+
vm.AssemblyTreeModel.SelectNode(typeNode);
98+
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
99+
100+
// The 'Compute' hover target is the member synthesized for the dynamic call d.Compute(1, "two", d).
101+
var compute = tab.References!
102+
.Select(r => r.Reference)
103+
.OfType<IMethod>()
104+
.First(m => m.Name == "Compute");
105+
106+
var ambience = new CSharpAmbience {
107+
ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList,
108+
};
109+
ambience.ConvertSymbol(compute).Should().Be(
110+
"dynamic Compute(int, string, dynamic)",
111+
"each argument is typed from the callsite: the constants keep their compile-time type, "
112+
+ "the dynamic argument stays dynamic");
113+
}
79114
}

0 commit comments

Comments
 (0)