Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions ICSharpCode.Decompiler.Tests/Output/TextTokenWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,22 @@ public T UseInGenericMethod<T>(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<bool> FoldStartDefaultCollapsed = new();
public int FoldEndCount;

Expand All @@ -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()
Expand Down Expand Up @@ -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<IMember>());
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)
Expand Down
7 changes: 4 additions & 3 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4660,9 +4660,10 @@ protected internal override TranslatedExpression VisitDynamicInvokeMemberInstruc
}
IEnumerable<ILInstruction> 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)
Expand Down Expand Up @@ -4737,12 +4738,12 @@ IMember CreateDynamicMemberSymbol(string name, CSharpArgumentInfo targetInfo)
/// 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, IReadOnlyList<IType> typeArguments)
IMember CreateDynamicInvokeMemberSymbol(string name, IType declaringType, IReadOnlyList<CSharpArgumentInfo> argumentInfo, IReadOnlyList<IType> typeArguments)
{
var method = new FakeMethod(compilation, SymbolKind.Method) {
Name = name,
ReturnType = SpecialType.Dynamic,
DeclaringType = DynamicArgumentType(targetInfo),
DeclaringType = declaringType,
};
if (argumentInfo.Count > 0)
{
Expand Down
10 changes: 8 additions & 2 deletions ICSharpCode.Decompiler/Output/TextTokenWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NewObj>()?.Method
?? node.Parent.Annotation<DynamicInvokeConstructorInstruction>()?.Type.GetDefinition()
?? node.Parent.GetSymbol();
if (ctorSymbol != null)
symbol = ctorSymbol;
}
Expand Down Expand Up @@ -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<ResolveResult>() is DynamicInvocationResolveResult)
{
output.Write(token);
}
else if (IsDynamicMemberReference(node))
output.WriteLocalReference(token, m, isHoverOnly: true);
else
output.WriteReference(m, token, false);
Expand Down
40 changes: 17 additions & 23 deletions ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion ILSpy/Languages/CSharpHighlightingTokenWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NewObj>()?.Method
?? node.Parent.Annotation<DynamicInvokeConstructorInstruction>()?.Type.GetDefinition()
?? node.Parent.GetSymbol();
if (ctorSymbol != null)
symbol = ctorSymbol;
}
Expand Down
Loading