Browse Source

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
pull/3907/head
Siegfried Pammer 2 months ago committed by Siegfried Pammer
parent
commit
cba8b0d7a5
  1. 34
      ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
  2. 35
      ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs

34
ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs

@ -323,6 +323,40 @@ namespace ICSharpCode.Decompiler.Tests.Output
} }
#endregion #endregion
#region Dynamic (synthesized member) tests
[Test]
public void DynamicType()
{
ambience.ConversionFlags = ConversionFlags.None;
Assert.That(ambience.ConvertType(SpecialType.Dynamic), Is.EqualTo("dynamic"));
}
[Test]
public void DynamicInvokeMember()
{
// The shape ExpressionBuilder synthesizes for a.Compute(1, "two", d): dynamic return,
// per-argument types from the callsite delegate (constants keep their compile-time type),
// declared on the dynamic type.
var method = new FakeMethod(compilation, SymbolKind.Method) {
Name = "Compute",
ReturnType = SpecialType.Dynamic,
DeclaringType = SpecialType.Dynamic,
Parameters = new IParameter[] {
new DefaultParameter(compilation.FindType(KnownTypeCode.Int32), string.Empty),
new DefaultParameter(compilation.FindType(KnownTypeCode.String), string.Empty),
new DefaultParameter(SpecialType.Dynamic, string.Empty),
},
};
ambience.ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList;
Assert.That(ambience.ConvertSymbol(method), Is.EqualTo("dynamic Compute(int, string, dynamic)"));
// The same member as DecompilerTextView.BuildHoverContent renders it (full hover form): the
// unnamed synthetic parameters collapse to their types, so there is no dangling-name artifact.
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
Assert.That(ambience.ConvertSymbol(method), Is.EqualTo("public dynamic dynamic.Compute(int, string, dynamic)"));
}
#endregion
#region Test types #region Test types
#pragma warning disable 169, 67 #pragma warning disable 169, 67

35
ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs

@ -24,6 +24,9 @@ using Avalonia.VisualTree;
using AwesomeAssertions; using AwesomeAssertions;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
@ -41,6 +44,11 @@ public class DynamicMemberSample
{ {
return d.Property; return d.Property;
} }
public object Call(dynamic d)
{
return d.Compute(1, "two", d);
}
} }
/// <summary> /// <summary>
@ -76,4 +84,31 @@ public class HoverOnlyReferenceTests
navigated.Should().BeFalse("clicking a hover-only reference must not navigate"); navigated.Should().BeFalse("clicking a hover-only reference must not navigate");
view.LocalReferenceMarks.Should().BeEmpty("a hover-only reference must not highlight occurrences"); view.LocalReferenceMarks.Should().BeEmpty("a hover-only reference must not highlight occurrences");
} }
[AvaloniaTest]
public async Task Dynamic_Invoke_Member_Hover_Renders_Its_Synthesized_Signature()
{
var (_, vm) = await TestHarness.BootAsync();
await vm.OpenAssemblyAsync(typeof(DynamicMemberSample).Assembly.Location);
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
"ILSpy.Tests",
"ICSharpCode.ILSpy.Tests.TextView",
"ICSharpCode.ILSpy.Tests.TextView.DynamicMemberSample");
vm.AssemblyTreeModel.SelectNode(typeNode);
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
// The 'Compute' hover target is the member synthesized for the dynamic call d.Compute(1, "two", d).
var compute = tab.References!
.Select(r => r.Reference)
.OfType<IMethod>()
.First(m => m.Name == "Compute");
var ambience = new CSharpAmbience {
ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList,
};
ambience.ConvertSymbol(compute).Should().Be(
"dynamic Compute(int, string, dynamic)",
"each argument is typed from the callsite: the constants keep their compile-time type, "
+ "the dynamic argument stays dynamic");
}
} }

Loading…
Cancel
Save