Skip to content

Commit 62afd92

Browse files
C#: Allow Capture objects as MatchResult keys (#7074)
Add ICapture interface so MatchResult.Of() can accept Capture objects as keys instead of only strings. This enables working with unnamed (auto-generated) captures without manually synchronizing string names between template creation and value binding.
1 parent 4a6b106 commit 62afd92

3 files changed

Lines changed: 63 additions & 1 deletion

File tree

rewrite-csharp/csharp/OpenRewrite/CSharp/Template/Capture.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,23 @@ internal enum CaptureKind
3232
Name,
3333
}
3434

35+
/// <summary>
36+
/// Non-generic interface for captures, providing access to the capture name
37+
/// without requiring knowledge of the captured type.
38+
/// </summary>
39+
public interface ICapture
40+
{
41+
string Name { get; }
42+
}
43+
3544
/// <summary>
3645
/// A named placeholder for pattern matching and template substitution.
3746
/// When used in an interpolated string passed to <see cref="CSharpTemplate.Create"/>
3847
/// or <see cref="CSharpPattern.Create"/>, the <see cref="TemplateStringHandler"/>
3948
/// intercepts it and registers the capture automatically.
4049
/// </summary>
4150
/// <typeparam name="T">The type of AST node this capture matches.</typeparam>
42-
public sealed class Capture<T> where T : J
51+
public sealed class Capture<T> : ICapture where T : J
4352
{
4453
public string Name { get; }
4554
public bool IsVariadic { get; }

rewrite-csharp/csharp/OpenRewrite/CSharp/Template/MatchResult.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,31 @@ public static MatchResult Of(params (string name, J value)[] captures)
5252
return new MatchResult(dict);
5353
}
5454

55+
/// <summary>
56+
/// Create a <see cref="MatchResult"/> from <see cref="ICapture"/> keys.
57+
/// This allows using unnamed captures (with auto-generated names) as keys,
58+
/// avoiding the need to manually synchronize string names between template
59+
/// creation and value binding.
60+
/// </summary>
61+
/// <example>
62+
/// <code>
63+
/// var left = Capture.Expression();
64+
/// var right = Capture.Expression();
65+
/// var template = CSharpTemplate.Expression($"{left} &amp;&amp; {right}");
66+
/// var values = MatchResult.Of((left, outerCond), (right, innerCond));
67+
/// var result = template.Apply(cursor, values: values);
68+
/// </code>
69+
/// </example>
70+
public static MatchResult Of(params (ICapture capture, J value)[] captures)
71+
{
72+
var dict = new Dictionary<string, object>(captures.Length);
73+
foreach (var (capture, value) in captures)
74+
{
75+
dict[capture.Name] = value;
76+
}
77+
return new MatchResult(dict);
78+
}
79+
5580
/// <summary>
5681
/// Get a captured value by name.
5782
/// </summary>
@@ -64,6 +89,13 @@ public static MatchResult Of(params (string name, J value)[] captures)
6489
public T? Get<T>(Capture<T> capture) where T : class, J
6590
=> Get<T>(capture.Name);
6691

92+
/// <summary>
93+
/// Get a captured value by its <see cref="ICapture"/> object.
94+
/// Useful when the capture's type parameter doesn't match the desired return type.
95+
/// </summary>
96+
public T? Get<T>(ICapture capture) where T : class, J
97+
=> Get<T>(capture.Name);
98+
6799
/// <summary>
68100
/// Get a variadic capture result as a list.
69101
/// Handles both IReadOnlyList&lt;T&gt; and IReadOnlyList&lt;object&gt; (from pattern matcher).
@@ -84,5 +116,10 @@ public IReadOnlyList<T> GetList<T>(string name) where T : class, J
84116
/// </summary>
85117
public bool Has(string name) => _captures.ContainsKey(name);
86118

119+
/// <summary>
120+
/// Check if a capture was bound.
121+
/// </summary>
122+
public bool Has(ICapture capture) => _captures.ContainsKey(capture.Name);
123+
87124
internal Dictionary<string, object> AsDict() => _captures;
88125
}

rewrite-csharp/csharp/OpenRewrite/Tests/Template/ScaffoldStrategyTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,22 @@ public void MatchResultOfCreatesManualBindings()
236236
Assert.Equal(42, lit!.Value);
237237
}
238238

239+
[Fact]
240+
public void MatchResultOfWithUnnamedCaptures()
241+
{
242+
var x = Capture.Expression();
243+
var y = Capture.Expression();
244+
var lit1 = new Literal(Guid.NewGuid(), Space.Empty, Markers.Empty, 1, "1", null, null);
245+
var lit2 = new Literal(Guid.NewGuid(), Space.Empty, Markers.Empty, 2, "2", null, null);
246+
247+
var values = MatchResult.Of((x, lit1), (y, lit2));
248+
249+
Assert.True(values.Has(x));
250+
Assert.True(values.Has(y));
251+
Assert.Equal(1, values.Get<Literal>(x)!.Value);
252+
Assert.Equal(2, values.Get<Literal>(y)!.Value);
253+
}
254+
239255
// === Cache isolation ===
240256

241257
[Fact]

0 commit comments

Comments
 (0)