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; }