diff --git a/ICSharpCode.Decompiler.Tests/Output/TextTokenWriterTests.cs b/ICSharpCode.Decompiler.Tests/Output/TextTokenWriterTests.cs index 20d3109f373..ab90064c84a 100644 --- a/ICSharpCode.Decompiler.Tests/Output/TextTokenWriterTests.cs +++ b/ICSharpCode.Decompiler.Tests/Output/TextTokenWriterTests.cs @@ -76,13 +76,22 @@ public T UseInGenericMethod(T t) } } + internal class DynamicInvokeMemberHost + { + public object DynamicStaticMethod(Type type) + { + dynamic dynamicType = type; + return Activator.CreateInstance(dynamicType); + } + } + [TestFixture] public class TextTokenWriterTests { sealed class ReferenceRecordingOutput : ITextOutput { public readonly List<(string Text, IMember Member)> MemberReferences = new(); - public readonly List<(string Text, object Reference, bool IsDefinition)> LocalReferences = new(); + public readonly List<(string Text, object Reference, bool IsDefinition, bool IsHoverOnly)> LocalReferences = new(); public readonly List FoldStartDefaultCollapsed = new(); public int FoldEndCount; @@ -104,7 +113,7 @@ public void WriteReference(IMember member, string text, bool isDefinition = fals public void WriteLocalReference(string text, object reference, bool isDefinition = false, bool isHoverOnly = false) { - LocalReferences.Add((text, reference, isDefinition)); + LocalReferences.Add((text, reference, isDefinition, isHoverOnly)); } public void MarkDefinitionStart() @@ -248,6 +257,25 @@ public void GenericLocalFunctionDefinitionAndUsesShareEqualReferenceObjects() }); } + [Test] + public void DynamicStaticInvocationReferencesStaticTargetTypeOnMemberNameOnly() + { + using var module = OpenTestAssembly(); + var output = DecompileAndCollectReferences(module, typeof(DynamicInvokeMemberHost)); + + var references = output.LocalReferences + .Where(r => r.Text == "CreateInstance") + .ToList(); + + Assert.That(references, Has.Count.EqualTo(1)); + Assert.That(references[0].IsHoverOnly, Is.True); + Assert.That(references[0].Reference, Is.InstanceOf()); + var member = (IMember)references[0].Reference; + Assert.That(member.DeclaringType.FullName, Is.EqualTo("System.Activator")); + Assert.That(member.Name, Is.EqualTo("CreateInstance")); + Assert.That(output.LocalReferences.Where(r => r.Text is "(" or ")"), Is.Empty); + } + // Runs inside Assert.Multiple: after a recorded assertion failure, bail out of the checks // that would dereference the missing data instead of throwing. static void AssertLocalFunctionReferenceGroup(ReferenceRecordingOutput output, string name, int expectedUses) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 25a1a6c4492..8461e11f916 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -4660,9 +4660,10 @@ protected internal override TranslatedExpression VisitDynamicInvokeMemberInstruc } IEnumerable argumentValues = inst.StaticTargetType != null ? inst.Arguments : inst.Arguments.Skip(1); var arguments = TranslateDynamicArguments(argumentValues, inst.ArgumentInfo.Skip(1)).ToList(); + var symbolDeclaringType = inst.StaticTargetType ?? DynamicArgumentType(inst.ArgumentInfo[0]); return new InvocationExpression(targetExpr, arguments.Select(a => a.Expression)) .WithILInstruction(inst) - .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(), inst.TypeArguments))); + .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Invocation, arguments.Select(a => a.ResolveResult).ToArray(), symbol: CreateDynamicInvokeMemberSymbol(inst.Name, symbolDeclaringType, inst.ArgumentInfo.Skip(1).ToArray(), inst.TypeArguments))); } protected internal override TranslatedExpression VisitDynamicInvokeInstruction(DynamicInvokeInstruction inst, TranslationContext context) @@ -4737,12 +4738,12 @@ IMember CreateDynamicMemberSymbol(string name, CSharpArgumentInfo targetInfo) /// typed by , so the member reference carries a navigable symbol / /// hover tooltip. /// - IMember CreateDynamicInvokeMemberSymbol(string name, CSharpArgumentInfo targetInfo, IReadOnlyList argumentInfo, IReadOnlyList typeArguments) + IMember CreateDynamicInvokeMemberSymbol(string name, IType declaringType, IReadOnlyList argumentInfo, IReadOnlyList typeArguments) { var method = new FakeMethod(compilation, SymbolKind.Method) { Name = name, ReturnType = SpecialType.Dynamic, - DeclaringType = DynamicArgumentType(targetInfo), + DeclaringType = declaringType, }; if (argumentInfo.Count > 0) { diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs index 424387b2185..61d4656e85b 100644 --- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs +++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs @@ -124,7 +124,9 @@ ISymbol GetCurrentMemberReference() } if (symbol != null && node.Slot?.Kind == Slots.Type && node.Parent is ObjectCreateExpression) { - var ctorSymbol = node.Parent.GetSymbol(); + var ctorSymbol = node.Parent.Annotation()?.Method + ?? node.Parent.Annotation()?.Type.GetDefinition() + ?? node.Parent.GetSymbol(); if (ctorSymbol != null) symbol = ctorSymbol; } @@ -364,7 +366,11 @@ public override void WriteToken(string token) output.WriteReference(t, token, false); return; case IMember m: - if (IsDynamicMemberReference(node)) + if (node is InvocationExpression && node.Annotation() is DynamicInvocationResolveResult) + { + output.Write(token); + } + else if (IsDynamicMemberReference(node)) output.WriteLocalReference(token, m, isHoverOnly: true); else output.WriteReference(m, token, false); diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 3c879df6a1e..a8f23076d61 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -22,6 +22,7 @@ using System.Linq; using System.Reflection.Metadata; +using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Semantics; @@ -796,29 +797,22 @@ public static ITypeDefinition GetTypeDefinition(this IModule module, string name #region ResolveResult public static ISymbol GetSymbol(this ResolveResult rr) { - if (rr is LocalResolveResult) - { - return ((LocalResolveResult)rr).Variable; - } - else if (rr is MemberResolveResult) - { - return ((MemberResolveResult)rr).Member; - } - else if (rr is TypeResolveResult) - { - return ((TypeResolveResult)rr).Type.GetDefinition(); - } - else if (rr is ConversionResolveResult) - { - return ((ConversionResolveResult)rr).Input.GetSymbol(); - } - else if (rr is DynamicMemberResolveResult dynamicMember) - { - return dynamicMember.Symbol; - } - else if (rr is DynamicInvocationResolveResult dynamicInvocation) - { - return dynamicInvocation.Symbol; + switch (rr) + { + case LocalResolveResult lrr: + return lrr.Variable; + case MemberResolveResult mrr: + return mrr.Member; + case TypeResolveResult trr: + return trr.Type.GetDefinition(); + case ConversionResolveResult crr: + return crr.Input.GetSymbol(); + case MethodGroupResolveResult mgrr: + return mgrr.Methods.FirstOrDefault(); + case DynamicMemberResolveResult dynamicMember: + return dynamicMember.Symbol; + case DynamicInvocationResolveResult dynamicInvocation: + return dynamicInvocation.Symbol; } return null; diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs index 9b9f8529ff0..fe992d8b1c2 100644 --- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs +++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs @@ -486,7 +486,9 @@ public override void WritePrimitiveValue(object? value, ICSharpCode.Decompiler.C } if (symbol != null && node.Slot?.Kind == Slots.Type && node.Parent is ObjectCreateExpression) { - var ctorSymbol = node.Parent.GetSymbol(); + var ctorSymbol = node.Parent.Annotation()?.Method + ?? node.Parent.Annotation()?.Type.GetDefinition() + ?? node.Parent.GetSymbol(); if (ctorSymbol != null) symbol = ctorSymbol; }