Browse Source

Give dynamic member accesses a symbol for hover tooltips

Dynamic member accesses and invocations carried only the member name
(DynamicMemberResolveResult / DynamicInvocationResolveResult), so
GetSymbol returned null and the editor emitted no reference or hover.

Synthesize a member on the target type - a dynamic field for a member
access, a dynamic-returning method for a member invocation - named after
the accessed member and typed from the callsite delegate: each argument
uses its recorded compile-time type when the binder set one (statically
typed or constant arguments), dynamic otherwise, and the declaring type
comes from the receiver's argument info. Route these through GetSymbol;
TextTokenWriter and the hover renderer already turn an IEntity into a
tooltip. The synthesized members have no metadata token, so they render
a signature on hover but are not navigation targets.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/3907/head
Siegfried Pammer 2 months ago committed by Siegfried Pammer
parent
commit
1c010a828e
  1. 57
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 10
      ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs
  3. 9
      ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs
  4. 8
      ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

57
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4356,7 +4356,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4356,7 +4356,7 @@ namespace ICSharpCode.Decompiler.CSharp
var target = TranslateDynamicTarget(inst.Target, inst.TargetArgumentInfo);
return new MemberReferenceExpression(target, inst.Name!)
.WithILInstruction(inst)
.WithRR(new DynamicMemberResolveResult(target.ResolveResult, inst.Name));
.WithRR(new DynamicMemberResolveResult(target.ResolveResult, inst.Name, CreateDynamicMemberSymbol(inst.Name!, inst.TargetArgumentInfo)));
}
protected internal override TranslatedExpression VisitDynamicInvokeConstructorInstruction(DynamicInvokeConstructorInstruction inst, TranslationContext context)
@ -4385,7 +4385,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4385,7 +4385,7 @@ namespace ICSharpCode.Decompiler.CSharp
var arguments = TranslateDynamicArguments(inst.Arguments.Skip(1), inst.ArgumentInfo.Skip(1)).ToList();
return new InvocationExpression(targetExpr, arguments.Select(a => a.Expression))
.WithILInstruction(inst)
.WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Invocation, arguments.Select(a => a.ResolveResult).ToArray()));
.WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Invocation, arguments.Select(a => a.ResolveResult).ToArray(), symbol: CreateDynamicInvokeMemberSymbol(inst.Name, inst.ArgumentInfo[0], inst.ArgumentInfo.Skip(1).ToArray())));
}
protected internal override TranslatedExpression VisitDynamicInvokeInstruction(DynamicInvokeInstruction inst, TranslationContext context)
@ -4426,6 +4426,57 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4426,6 +4426,57 @@ namespace ICSharpCode.Decompiler.CSharp
return translatedTarget;
}
/// <summary>
/// The type to show for a dynamic callsite argument: its recorded compile-time type when the runtime
/// binder was told to use it (statically-typed or constant arguments), otherwise <c>dynamic</c>.
/// </summary>
static IType DynamicArgumentType(CSharpArgumentInfo info)
{
if ((info.HasFlag(CSharpArgumentInfoFlags.UseCompileTimeType) || info.HasFlag(CSharpArgumentInfoFlags.Constant))
&& info.CompileTimeType != null)
{
return info.CompileTimeType;
}
return SpecialType.Dynamic;
}
/// <summary>
/// Synthesizes a member on the target type for a dynamic member access (a.Member), so the member
/// reference carries a navigable symbol / hover tooltip. Since the real member is unknown, this is a
/// <c>dynamic</c>-typed field named after the accessed member, declared on the target's compile-time type.
/// </summary>
IMember CreateDynamicMemberSymbol(string name, CSharpArgumentInfo targetInfo)
{
return new FakeField(compilation) {
Name = name,
ReturnType = SpecialType.Dynamic,
DeclaringType = DynamicArgumentType(targetInfo),
};
}
/// <summary>
/// Synthesizes a member on the target type for a dynamic member invocation (a.Method(b)): a
/// <c>dynamic</c>-returning method named after the invoked member, with one parameter per argument
/// typed by <see cref="DynamicArgumentType"/>, so the member reference carries a navigable symbol /
/// hover tooltip.
/// </summary>
IMember CreateDynamicInvokeMemberSymbol(string name, CSharpArgumentInfo targetInfo, IReadOnlyList<CSharpArgumentInfo> argumentInfo)
{
var method = new FakeMethod(compilation, SymbolKind.Method) {
Name = name,
ReturnType = SpecialType.Dynamic,
DeclaringType = DynamicArgumentType(targetInfo),
};
if (argumentInfo.Count > 0)
{
var parameters = new IParameter[argumentInfo.Count];
for (int i = 0; i < argumentInfo.Count; i++)
parameters[i] = new DefaultParameter(DynamicArgumentType(argumentInfo[i]), argumentInfo[i].Name ?? string.Empty);
method.Parameters = parameters;
}
return method;
}
IEnumerable<TranslatedExpression> TranslateDynamicArguments(IEnumerable<ILInstruction> arguments, IEnumerable<CSharpArgumentInfo> argumentInfo)
{
foreach (var (argument, info) in arguments.Zip(argumentInfo))
@ -4502,7 +4553,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -4502,7 +4553,7 @@ namespace ICSharpCode.Decompiler.CSharp
var value = TranslateDynamicArgument(inst.Value, inst.ValueArgumentInfo);
var member = new MemberReferenceExpression(target, inst.Name!)
.WithoutILInstruction()
.WithRR(new DynamicMemberResolveResult(target.ResolveResult, inst.Name));
.WithRR(new DynamicMemberResolveResult(target.ResolveResult, inst.Name, CreateDynamicMemberSymbol(inst.Name!, inst.TargetArgumentInfo)));
return Assignment(member, value).WithILInstruction(inst);
}

10
ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs

@ -72,12 +72,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -72,12 +72,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
/// </summary>
public readonly IList<ResolveResult> InitializerStatements;
public DynamicInvocationResolveResult(ResolveResult target, DynamicInvocationType invocationType, IList<ResolveResult> arguments, IList<ResolveResult> initializerStatements = null) : base(SpecialType.Dynamic)
/// <summary>
/// Synthesized member (a <c>dynamic</c> method on the <c>dynamic</c> type) representing the invoked
/// member, so the member reference carries a navigable symbol / hover tooltip. Only set for an
/// invoke-member (<c>a.Method(b)</c>); null for a plain invoke or an indexer. May be null.
/// </summary>
public readonly IMember Symbol;
public DynamicInvocationResolveResult(ResolveResult target, DynamicInvocationType invocationType, IList<ResolveResult> arguments, IList<ResolveResult> initializerStatements = null, IMember symbol = null) : base(SpecialType.Dynamic)
{
this.Target = target;
this.InvocationType = invocationType;
this.Arguments = arguments ?? EmptyList<ResolveResult>.Instance;
this.InitializerStatements = initializerStatements ?? EmptyList<ResolveResult>.Instance;
this.Symbol = symbol;
}
public override string ToString()

9
ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs

@ -39,10 +39,17 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -39,10 +39,17 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
/// </summary>
public readonly string Member;
public DynamicMemberResolveResult(ResolveResult target, string member) : base(SpecialType.Dynamic)
/// <summary>
/// Synthesized member (a <c>dynamic</c> field on the <c>dynamic</c> type) representing the accessed
/// member, so the member reference carries a navigable symbol / hover tooltip. May be null.
/// </summary>
public readonly IMember Symbol;
public DynamicMemberResolveResult(ResolveResult target, string member, IMember symbol = null) : base(SpecialType.Dynamic)
{
this.Target = target;
this.Member = member;
this.Symbol = symbol;
}
public override string ToString()

8
ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs

@ -732,6 +732,14 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -732,6 +732,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
return ((ConversionResolveResult)rr).Input.GetSymbol();
}
else if (rr is CSharp.Resolver.DynamicMemberResolveResult dynamicMember)
{
return dynamicMember.Symbol;
}
else if (rr is CSharp.Resolver.DynamicInvocationResolveResult dynamicInvocation)
{
return dynamicInvocation.Symbol;
}
return null;
}

Loading…
Cancel
Save