diff --git a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
index 08632d998..b81065949 100644
--- a/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
+++ b/ICSharpCode.Decompiler.Tests/Output/CSharpAmbienceTests.cs
@@ -355,6 +355,25 @@ namespace ICSharpCode.Decompiler.Tests.Output
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
Assert.That(ambience.ConvertSymbol(method), Is.EqualTo("public dynamic dynamic.Compute(int, string, dynamic)"));
}
+ [Test]
+ public void DynamicIndexer()
+ {
+ // The shape ExpressionBuilder synthesizes for a[b]: an indexer with dynamic return, index
+ // parameters typed from the callsite delegate, declared on the dynamic type. It carries no
+ // accessors, which the ambience renders cleanly (no empty { } artifact).
+ var indexer = new FakeProperty(compilation) {
+ Name = "Item",
+ IsIndexer = true,
+ ReturnType = SpecialType.Dynamic,
+ DeclaringType = SpecialType.Dynamic,
+ Parameters = new IParameter[] { new DefaultParameter(compilation.FindType(KnownTypeCode.Int32), string.Empty) },
+ };
+ ambience.ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList;
+ Assert.That(ambience.ConvertSymbol(indexer), Is.EqualTo("dynamic this[int]"));
+
+ ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.ShowBody | ConversionFlags.PlaceReturnTypeAfterParameterList);
+ Assert.That(ambience.ConvertSymbol(indexer), Is.EqualTo("public dynamic dynamic.this[int]"));
+ }
#endregion
#region Test types
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 1e364d5da..f2504ac9c 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -4348,7 +4348,8 @@ namespace ICSharpCode.Decompiler.CSharp
var arguments = TranslateDynamicArguments(inst.Arguments.Skip(1), inst.ArgumentInfo.Skip(1)).ToList();
return new IndexerExpression(target, arguments.Select(a => a.Expression))
.WithILInstruction(inst)
- .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Indexing, arguments.Select(a => a.ResolveResult).ToArray()));
+ .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Indexing, arguments.Select(a => a.ResolveResult).ToArray(),
+ symbol: CreateDynamicIndexerSymbol(DynamicArgumentType(inst.ArgumentInfo[0]), inst.ArgumentInfo.Skip(1).ToArray())));
}
protected internal override TranslatedExpression VisitDynamicGetMemberInstruction(DynamicGetMemberInstruction inst, TranslationContext context)
@@ -4509,6 +4510,24 @@ namespace ICSharpCode.Decompiler.CSharp
return constructor;
}
+ ///
+ /// Synthesizes the indexer for a dynamic index access (a[b]): an indexer on the target type whose
+ /// parameters are typed from the callsite delegate, so the brackets carry a hover tooltip.
+ ///
+ IMember CreateDynamicIndexerSymbol(IType declaringType, IReadOnlyList argumentInfo)
+ {
+ 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);
+ return new FakeProperty(compilation) {
+ Name = "Item",
+ IsIndexer = true,
+ ReturnType = SpecialType.Dynamic,
+ DeclaringType = declaringType,
+ Parameters = parameters,
+ };
+ }
+
IEnumerable TranslateDynamicArguments(IEnumerable arguments, IEnumerable argumentInfo)
{
foreach (var (argument, info) in arguments.Zip(argumentInfo))
@@ -4575,7 +4594,8 @@ namespace ICSharpCode.Decompiler.CSharp
var value = new TranslatedExpression(arguments.Last());
var indexer = new IndexerExpression(target, arguments.SkipLast(1).Select(a => a.Expression))
.WithoutILInstruction()
- .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Indexing, arguments.SkipLast(1).Select(a => a.ResolveResult).ToArray()));
+ .WithRR(new DynamicInvocationResolveResult(target.ResolveResult, DynamicInvocationType.Indexing, arguments.SkipLast(1).Select(a => a.ResolveResult).ToArray(),
+ symbol: CreateDynamicIndexerSymbol(DynamicArgumentType(inst.ArgumentInfo[0]), inst.ArgumentInfo.Skip(1).Take(inst.ArgumentInfo.Count - 2).ToArray())));
return Assignment(indexer, value).WithILInstruction(inst);
}
diff --git a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
index 2ea45c712..e9d796078 100644
--- a/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
+++ b/ICSharpCode.Decompiler/Output/TextTokenWriter.cs
@@ -159,6 +159,10 @@ namespace ICSharpCode.Decompiler
{
if (node.Annotation() is CSharp.Resolver.DynamicMemberResolveResult)
return true;
+ // The node itself is a dynamic invocation/indexing (a.Method(b), a[b]): its parentheses/brackets
+ // carry the synthesized member.
+ if (node.Annotation() is CSharp.Resolver.DynamicInvocationResolveResult)
+ return true;
if (node.Slot?.Kind == Slots.TargetExpression && node.Parent is InvocationExpression
&& node.Parent.Annotation() is CSharp.Resolver.DynamicInvocationResolveResult)
return true;
diff --git a/ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs b/ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs
index 756342be5..2f7fc75a8 100644
--- a/ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs
+++ b/ILSpy.Tests/Editor/HoverOnlyReferenceTests.cs
@@ -49,6 +49,11 @@ public class DynamicMemberSample
{
return d.Compute(1, "two", d);
}
+
+ public object Index(dynamic d)
+ {
+ return d[0];
+ }
}
///
@@ -111,4 +116,26 @@ public class HoverOnlyReferenceTests
"each argument is typed from the callsite: the constants keep their compile-time type, "
+ "the dynamic argument stays dynamic");
}
+
+ [AvaloniaTest]
+ public async Task Dynamic_Index_Access_Is_A_HoverOnly_Indexer()
+ {
+ var (_, vm) = await TestHarness.BootAsync();
+ await vm.OpenAssemblyAsync(typeof(DynamicMemberSample).Assembly.Location);
+ var typeNode = vm.AssemblyTreeModel.FindNode(
+ "ILSpy.Tests",
+ "ICSharpCode.ILSpy.Tests.TextView",
+ "ICSharpCode.ILSpy.Tests.TextView.DynamicMemberSample");
+ vm.AssemblyTreeModel.SelectNode(typeNode);
+ var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync();
+
+ // The brackets of d[0] carry a synthesized indexer.
+ var bracket = tab.References!.First(r => r.Reference is IProperty { IsIndexer: true });
+ bracket.Kind.Should().Be(ReferenceMode.HoverOnly, "the synthesized indexer has no metadata to jump to");
+
+ var ambience = new CSharpAmbience {
+ ConversionFlags = ConversionFlags.ShowReturnType | ConversionFlags.ShowParameterList,
+ };
+ ambience.ConvertSymbol((IProperty)bracket.Reference!).Should().Be("dynamic this[int]");
+ }
}