diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 398181075..4f524124f 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -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
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
return translatedTarget;
}
+ ///
+ /// 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 dynamic.
+ ///
+ static IType DynamicArgumentType(CSharpArgumentInfo info)
+ {
+ if ((info.HasFlag(CSharpArgumentInfoFlags.UseCompileTimeType) || info.HasFlag(CSharpArgumentInfoFlags.Constant))
+ && info.CompileTimeType != null)
+ {
+ return info.CompileTimeType;
+ }
+ return SpecialType.Dynamic;
+ }
+
+ ///
+ /// 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
+ /// dynamic-typed field named after the accessed member, declared on the target's compile-time type.
+ ///
+ IMember CreateDynamicMemberSymbol(string name, CSharpArgumentInfo targetInfo)
+ {
+ return new FakeField(compilation) {
+ Name = name,
+ ReturnType = SpecialType.Dynamic,
+ DeclaringType = DynamicArgumentType(targetInfo),
+ };
+ }
+
+ ///
+ /// Synthesizes a member on the target type for a dynamic member invocation (a.Method(b)): a
+ /// dynamic-returning method named after the invoked member, with one parameter per argument
+ /// typed by , so the member reference carries a navigable symbol /
+ /// hover tooltip.
+ ///
+ IMember CreateDynamicInvokeMemberSymbol(string name, CSharpArgumentInfo targetInfo, IReadOnlyList 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 TranslateDynamicArguments(IEnumerable arguments, IEnumerable argumentInfo)
{
foreach (var (argument, info) in arguments.Zip(argumentInfo))
@@ -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);
}
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs
index 280d0719e..0cac1b0b0 100644
--- a/ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs
+++ b/ICSharpCode.Decompiler/CSharp/Resolver/DynamicInvocationResolveResult.cs
@@ -72,12 +72,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
public readonly IList InitializerStatements;
- public DynamicInvocationResolveResult(ResolveResult target, DynamicInvocationType invocationType, IList arguments, IList initializerStatements = null) : base(SpecialType.Dynamic)
+ ///
+ /// Synthesized member (a dynamic method on the dynamic type) representing the invoked
+ /// member, so the member reference carries a navigable symbol / hover tooltip. Only set for an
+ /// invoke-member (a.Method(b)); null for a plain invoke or an indexer. May be null.
+ ///
+ public readonly IMember Symbol;
+
+ public DynamicInvocationResolveResult(ResolveResult target, DynamicInvocationType invocationType, IList arguments, IList initializerStatements = null, IMember symbol = null) : base(SpecialType.Dynamic)
{
this.Target = target;
this.InvocationType = invocationType;
this.Arguments = arguments ?? EmptyList.Instance;
this.InitializerStatements = initializerStatements ?? EmptyList.Instance;
+ this.Symbol = symbol;
}
public override string ToString()
diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs b/ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs
index 2f0bcd523..c943fd283 100644
--- a/ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs
+++ b/ICSharpCode.Decompiler/CSharp/Resolver/DynamicMemberResolveResult.cs
@@ -39,10 +39,17 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
///
public readonly string Member;
- public DynamicMemberResolveResult(ResolveResult target, string member) : base(SpecialType.Dynamic)
+ ///
+ /// Synthesized member (a dynamic field on the dynamic type) representing the accessed
+ /// member, so the member reference carries a navigable symbol / hover tooltip. May be null.
+ ///
+ 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()
diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
index 90bf02ce6..1b624c739 100644
--- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
@@ -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;
}