Browse Source

Bridge only the debug-step marker in NodeLookup

NodeLookup.AddNode indexed every annotation of every rendered node by reference
identity, but the debug-step highlighter only ever looks up the DebugStepMarker;
the rest were dead keys, and a shared annotation (ResolveResult and friends,
copied across nodes) would resolve to whichever node rendered last. Make
DebugStepMarker public and bridge only it -- behaviour-preserving for resolution
while dropping the per-annotation dictionary churn on every rendered node.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3847/head
Siegfried Pammer 7 days ago committed by Siegfried Pammer
parent
commit
2bd41edfaf
  1. 12
      ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs
  2. 12
      ILSpy.Tests/Views/DebugStepsTests.cs
  3. 20
      ILSpy/TextView/NodeLookup.cs

12
ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs

@ -144,9 +144,15 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -144,9 +144,15 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
else
step.ModifiedNodeCandidates.Add(candidate);
}
}
sealed class DebugStepMarker
{
}
/// <summary>
/// Annotation attached to a step's modified node so the debug-step highlighter can still locate
/// that node's rendered range after a later transform copies its annotations onto a replacement
/// (via CopyAnnotationsFrom). This is the only annotation <c>NodeLookup</c> bridges to a text
/// range; indexing every annotation would add dead keys and let shared ones collide by identity.
/// </summary>
public sealed class DebugStepMarker
{
}
}

12
ILSpy.Tests/Views/DebugStepsTests.cs

@ -32,6 +32,7 @@ using AwesomeAssertions; @@ -32,6 +32,7 @@ using AwesomeAssertions;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.ILSpy;
using ICSharpCode.ILSpy.AppEnv;
@ -275,7 +276,7 @@ public class DebugStepsTests @@ -275,7 +276,7 @@ public class DebugStepsTests
[AvaloniaTest]
public Task NodeLookup_Resolves_Copied_Ast_Annotations()
{
var marker = new object();
var marker = new DebugStepMarker();
var original = new IdentifierExpression("old");
original.AddAnnotation(marker);
var replacement = new IdentifierExpression("new").CopyAnnotationsFrom(original);
@ -287,6 +288,15 @@ public class DebugStepsTests @@ -287,6 +288,15 @@ public class DebugStepsTests
"C# debug-step markers copied by AST replacements must still resolve to emitted text");
range.Start.Should().Be(12);
range.Length.Should().Be(3);
// A non-marker annotation is not bridged: only the debug-step marker is queried by the
// resolver, and indexing arbitrary shared annotations would collide by reference identity.
var otherAnnotation = new object();
var otherNode = new IdentifierExpression("x");
otherNode.AddAnnotation(otherAnnotation);
lookup.AddNode(otherNode, 30, 1);
lookup.TryGetRange(otherAnnotation, out _).Should().BeFalse(
"only DebugStepMarker annotations are bridged to a text range");
return Task.CompletedTask;
}

20
ILSpy/TextView/NodeLookup.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System.Collections.Generic;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
namespace ICSharpCode.ILSpy.TextView
{
@ -34,15 +35,20 @@ namespace ICSharpCode.ILSpy.TextView @@ -34,15 +35,20 @@ namespace ICSharpCode.ILSpy.TextView
public void AddNode(object node, int start, int length)
{
if (length > 0)
if (length <= 0)
return;
var range = new TextRange(start, length);
nodes[node] = range;
// Bridge only the debug-step marker: it is the sole annotation the resolver ever looks up
// (it rides CopyAnnotationsFrom so a replaced node's step still resolves to the emitted
// text). Indexing every annotation would add dead keys never queried, and would let a
// shared annotation resolve to whichever node rendered last.
if (node is IAnnotatable annotatable)
{
nodes[node] = new TextRange(start, length);
if (node is IAnnotatable annotatable)
foreach (var annotation in annotatable.Annotations)
{
foreach (var annotation in annotatable.Annotations)
{
nodes[annotation] = new TextRange(start, length);
}
if (annotation is DebugStepMarker)
nodes[annotation] = range;
}
}
}

Loading…
Cancel
Save