@openrewrite/rewrite 8.67.0-20251112-160335 → 8.67.0-20251113-092819

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.PatternMatchingComparator = void 0;
12
+ exports.DebugPatternMatchingComparator = exports.PatternMatchingComparator = void 0;
13
13
  /*
14
14
  * Copyright 2025 the original author or authors.
15
15
  * <p>
@@ -62,11 +62,14 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
62
62
  // Evaluate constraint with cursor at the captured node (always defined)
63
63
  // Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
64
64
  if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(p, cursorAtCapturedNode)) {
65
- return this.abort(j);
65
+ const captureName = captureMarker.captureName || 'unnamed';
66
+ const targetKind = p.kind || 'unknown';
67
+ return this.constraintFailed(captureName, targetKind);
66
68
  }
67
69
  const success = this.matcher.handleCapture(captureMarker, p, undefined);
68
70
  if (!success) {
69
- return this.abort(j);
71
+ const captureName = captureMarker.captureName || 'unnamed';
72
+ return this.captureConflict(captureName);
70
73
  }
71
74
  return j;
72
75
  }
@@ -85,6 +88,19 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
85
88
  return super.hasSameKind(j, other) ||
86
89
  (j.kind == java_1.J.Kind.Identifier && utils_1.PlaceholderUtils.isCapture(j));
87
90
  }
91
+ /**
92
+ * Additional specialized abort methods for pattern matching scenarios.
93
+ */
94
+ constraintFailed(captureName, targetKind) {
95
+ var _a;
96
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
97
+ return this.abort(pattern, 'constraint-failed', `capture[${captureName}]`, 'constraint satisfied', `constraint failed for ${targetKind}`);
98
+ }
99
+ captureConflict(captureName) {
100
+ var _a;
101
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
102
+ return this.abort(pattern, 'capture-conflict', `capture[${captureName}]`, 'compatible binding', 'conflicting binding');
103
+ }
88
104
  /**
89
105
  * Override visitRightPadded to check if this wrapper has a CaptureMarker.
90
106
  * If so, capture the entire wrapper (to preserve markers like semicolons).
@@ -115,12 +131,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
115
131
  // Evaluate constraint with cursor at the captured node (always defined)
116
132
  // Skip constraint for variadic captures - they're evaluated in matchSequence with the full array
117
133
  if (captureMarker.constraint && !captureMarker.variadicOptions && !captureMarker.constraint(targetElement, cursorAtCapturedNode)) {
118
- return this.abort(right);
134
+ const captureName = captureMarker.captureName || 'unnamed';
135
+ const targetKind = targetElement.kind || 'unknown';
136
+ return this.constraintFailed(captureName, targetKind);
119
137
  }
120
138
  // Handle the capture with the wrapper - use the element for pattern matching
121
139
  const success = this.matcher.handleCapture(captureMarker, targetElement, targetWrapper);
122
140
  if (!success) {
123
- return this.abort(right);
141
+ const captureName = captureMarker.captureName || 'unnamed';
142
+ return this.captureConflict(captureName);
124
143
  }
125
144
  return right;
126
145
  }
@@ -150,7 +169,18 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
150
169
  // Extract the other container
151
170
  const isContainer = p.kind === java_1.J.Kind.Container;
152
171
  if (!isContainer) {
153
- return this.abort(container);
172
+ // Set up cursors temporarily for kindMismatch to use
173
+ const savedCursor = this.cursor;
174
+ const savedTargetCursor = this.targetCursor;
175
+ this.cursor = new __1.Cursor(container, this.cursor);
176
+ this.targetCursor = new __1.Cursor(p, this.targetCursor);
177
+ try {
178
+ return this.kindMismatch();
179
+ }
180
+ finally {
181
+ this.cursor = savedCursor;
182
+ this.targetCursor = savedTargetCursor;
183
+ }
154
184
  }
155
185
  const otherContainer = p;
156
186
  // Push wrappers onto both cursors
@@ -162,7 +192,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
162
192
  // Use matchSequence for variadic matching
163
193
  // filterEmpty=true to skip J.Empty elements (they represent missing elements in destructuring)
164
194
  if (!(yield this.matchSequence(container.elements, otherContainer.elements, true))) {
165
- return this.abort(container);
195
+ return this.structuralMismatch('elements');
166
196
  }
167
197
  }
168
198
  finally {
@@ -172,6 +202,21 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
172
202
  return container;
173
203
  });
174
204
  }
205
+ /**
206
+ * Visit a single element in a container (for non-variadic matching).
207
+ * Extracted to allow debug subclass to add path tracking.
208
+ *
209
+ * @param element The pattern element
210
+ * @param otherElement The target element
211
+ * @param index The index in the container
212
+ * @returns true if matching should continue, false if it failed
213
+ */
214
+ visitContainerElement(element, otherElement, index) {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ yield this.visitRightPadded(element, otherElement);
217
+ return this.match;
218
+ });
219
+ }
175
220
  visitMethodInvocation(methodInvocation, other) {
176
221
  const _super = Object.create(null, {
177
222
  visitMethodInvocation: { get: () => super.visitMethodInvocation }
@@ -184,45 +229,71 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
184
229
  return _super.visitMethodInvocation.call(this, methodInvocation, other);
185
230
  }
186
231
  // Otherwise, handle variadic captures ourselves
187
- if (!this.match || other.kind !== java_1.J.Kind.MethodInvocation) {
188
- return this.abort(methodInvocation);
189
- }
190
- const otherMethodInvocation = other;
191
- // Compare select
192
- if ((methodInvocation.select === undefined) !== (otherMethodInvocation.select === undefined)) {
232
+ if (!this.match) {
193
233
  return this.abort(methodInvocation);
194
234
  }
195
- // Visit select if present
196
- if (methodInvocation.select && otherMethodInvocation.select) {
197
- yield this.visit(methodInvocation.select.element, otherMethodInvocation.select.element);
198
- if (!this.match)
199
- return methodInvocation;
200
- }
201
- // Compare typeParameters
202
- if ((methodInvocation.typeParameters === undefined) !== (otherMethodInvocation.typeParameters === undefined)) {
203
- return this.abort(methodInvocation);
235
+ if (other.kind !== java_1.J.Kind.MethodInvocation) {
236
+ // Set up cursors for kindMismatch
237
+ const savedCursor = this.cursor;
238
+ const savedTargetCursor = this.targetCursor;
239
+ this.cursor = new __1.Cursor(methodInvocation, this.cursor);
240
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
241
+ try {
242
+ return this.kindMismatch();
243
+ }
244
+ finally {
245
+ this.cursor = savedCursor;
246
+ this.targetCursor = savedTargetCursor;
247
+ }
204
248
  }
205
- // Visit typeParameters if present
206
- if (methodInvocation.typeParameters && otherMethodInvocation.typeParameters) {
207
- if (methodInvocation.typeParameters.elements.length !== otherMethodInvocation.typeParameters.elements.length) {
208
- return this.abort(methodInvocation);
249
+ const otherMethodInvocation = other;
250
+ // Set up cursors for the entire method
251
+ const savedCursor = this.cursor;
252
+ const savedTargetCursor = this.targetCursor;
253
+ this.cursor = new __1.Cursor(methodInvocation, this.cursor);
254
+ this.targetCursor = new __1.Cursor(otherMethodInvocation, this.targetCursor);
255
+ try {
256
+ // Compare select
257
+ if ((methodInvocation.select === undefined) !== (otherMethodInvocation.select === undefined)) {
258
+ return this.structuralMismatch('select');
209
259
  }
210
- // Visit each type parameter in lock step (visit RightPadded to check for markers)
211
- for (let i = 0; i < methodInvocation.typeParameters.elements.length; i++) {
212
- yield this.visitRightPadded(methodInvocation.typeParameters.elements[i], otherMethodInvocation.typeParameters.elements[i]);
260
+ // Visit select if present
261
+ if (methodInvocation.select && otherMethodInvocation.select) {
262
+ yield this.visit(methodInvocation.select.element, otherMethodInvocation.select.element);
213
263
  if (!this.match)
214
264
  return methodInvocation;
215
265
  }
216
- }
217
- // Visit name
218
- yield this.visit(methodInvocation.name, otherMethodInvocation.name);
219
- if (!this.match)
266
+ // Compare typeParameters
267
+ if ((methodInvocation.typeParameters === undefined) !== (otherMethodInvocation.typeParameters === undefined)) {
268
+ return this.structuralMismatch('typeParameters');
269
+ }
270
+ // Visit typeParameters if present
271
+ if (methodInvocation.typeParameters && otherMethodInvocation.typeParameters) {
272
+ if (methodInvocation.typeParameters.elements.length !== otherMethodInvocation.typeParameters.elements.length) {
273
+ return this.arrayLengthMismatch('typeParameters.elements');
274
+ }
275
+ // Visit each type parameter in lock step (visit RightPadded to check for markers)
276
+ for (let i = 0; i < methodInvocation.typeParameters.elements.length; i++) {
277
+ yield this.visitRightPadded(methodInvocation.typeParameters.elements[i], otherMethodInvocation.typeParameters.elements[i]);
278
+ if (!this.match)
279
+ return methodInvocation;
280
+ }
281
+ }
282
+ // Visit name
283
+ yield this.visit(methodInvocation.name, otherMethodInvocation.name);
284
+ if (!this.match) {
285
+ return methodInvocation;
286
+ }
287
+ // Special handling for variadic captures in arguments
288
+ if (!(yield this.matchArguments(methodInvocation.arguments.elements, otherMethodInvocation.arguments.elements))) {
289
+ return this.structuralMismatch('arguments');
290
+ }
220
291
  return methodInvocation;
221
- // Special handling for variadic captures in arguments
222
- if (!(yield this.matchArguments(methodInvocation.arguments.elements, otherMethodInvocation.arguments.elements))) {
223
- return this.abort(methodInvocation);
224
292
  }
225
- return methodInvocation;
293
+ finally {
294
+ this.cursor = savedCursor;
295
+ this.targetCursor = savedTargetCursor;
296
+ }
226
297
  });
227
298
  }
228
299
  visitBlock(block, other) {
@@ -240,15 +311,40 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
240
311
  return _super.visitBlock.call(this, block, other);
241
312
  }
242
313
  // Otherwise, handle variadic captures ourselves
243
- if (!this.match || other.kind !== java_1.J.Kind.Block) {
314
+ if (!this.match) {
244
315
  return this.abort(block);
245
316
  }
317
+ if (other.kind !== java_1.J.Kind.Block) {
318
+ // Set up cursors for kindMismatch
319
+ const savedCursor = this.cursor;
320
+ const savedTargetCursor = this.targetCursor;
321
+ this.cursor = new __1.Cursor(block, this.cursor);
322
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
323
+ try {
324
+ return this.kindMismatch();
325
+ }
326
+ finally {
327
+ this.cursor = savedCursor;
328
+ this.targetCursor = savedTargetCursor;
329
+ }
330
+ }
246
331
  const otherBlock = other;
247
- // Special handling for variadic captures in statements
248
- if (!(yield this.matchSequence(block.statements, otherBlock.statements, false))) {
249
- return this.abort(block);
332
+ // Set up cursors for structural comparison
333
+ const savedCursor = this.cursor;
334
+ const savedTargetCursor = this.targetCursor;
335
+ this.cursor = new __1.Cursor(block, this.cursor);
336
+ this.targetCursor = new __1.Cursor(otherBlock, this.targetCursor);
337
+ try {
338
+ // Special handling for variadic captures in statements
339
+ if (!(yield this.matchSequence(block.statements, otherBlock.statements, false))) {
340
+ return this.structuralMismatch('statements');
341
+ }
342
+ return block;
343
+ }
344
+ finally {
345
+ this.cursor = savedCursor;
346
+ this.targetCursor = savedTargetCursor;
250
347
  }
251
- return block;
252
348
  });
253
349
  }
254
350
  visitJsCompilationUnit(compilationUnit, other) {
@@ -265,15 +361,40 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
265
361
  return _super.visitJsCompilationUnit.call(this, compilationUnit, other);
266
362
  }
267
363
  // Otherwise, handle variadic captures ourselves
268
- if (!this.match || other.kind !== index_1.JS.Kind.CompilationUnit) {
364
+ if (!this.match) {
269
365
  return this.abort(compilationUnit);
270
366
  }
367
+ if (other.kind !== index_1.JS.Kind.CompilationUnit) {
368
+ // Set up cursors for kindMismatch
369
+ const savedCursor = this.cursor;
370
+ const savedTargetCursor = this.targetCursor;
371
+ this.cursor = new __1.Cursor(compilationUnit, this.cursor);
372
+ this.targetCursor = new __1.Cursor(other, this.targetCursor);
373
+ try {
374
+ return this.kindMismatch();
375
+ }
376
+ finally {
377
+ this.cursor = savedCursor;
378
+ this.targetCursor = savedTargetCursor;
379
+ }
380
+ }
271
381
  const otherCompilationUnit = other;
272
- // Special handling for variadic captures in top-level statements
273
- if (!(yield this.matchSequence(compilationUnit.statements, otherCompilationUnit.statements, false))) {
274
- return this.abort(compilationUnit);
382
+ // Set up cursors for structural comparison
383
+ const savedCursor = this.cursor;
384
+ const savedTargetCursor = this.targetCursor;
385
+ this.cursor = new __1.Cursor(compilationUnit, this.cursor);
386
+ this.targetCursor = new __1.Cursor(otherCompilationUnit, this.targetCursor);
387
+ try {
388
+ // Special handling for variadic captures in top-level statements
389
+ if (!(yield this.matchSequence(compilationUnit.statements, otherCompilationUnit.statements, false))) {
390
+ return this.structuralMismatch('statements');
391
+ }
392
+ return compilationUnit;
393
+ }
394
+ finally {
395
+ this.cursor = savedCursor;
396
+ this.targetCursor = savedTargetCursor;
275
397
  }
276
- return compilationUnit;
277
398
  });
278
399
  }
279
400
  /**
@@ -282,7 +403,7 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
282
403
  */
283
404
  matchArguments(patternArgs, targetArgs) {
284
405
  return __awaiter(this, void 0, void 0, function* () {
285
- return this.matchSequence(patternArgs, targetArgs, true);
406
+ return yield this.matchSequence(patternArgs, targetArgs, true);
286
407
  });
287
408
  }
288
409
  /**
@@ -330,14 +451,20 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
330
451
  const variadicOptions = captureMarker.variadicOptions;
331
452
  const min = (_a = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.min) !== null && _a !== void 0 ? _a : 0;
332
453
  const max = (_b = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.max) !== null && _b !== void 0 ? _b : Infinity;
333
- // Calculate maximum possible consumption
454
+ // Calculate maximum possible consumption and check if remaining patterns are deterministic
334
455
  let nonVariadicRemainingPatterns = 0;
456
+ let allRemainingPatternsAreDeterministic = true;
335
457
  for (let i = patternIdx + 1; i < patternElements.length; i++) {
336
458
  const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElements[i]);
337
459
  const nextIsVariadic = (nextCaptureMarker === null || nextCaptureMarker === void 0 ? void 0 : nextCaptureMarker.variadicOptions) !== undefined;
338
460
  if (!nextIsVariadic) {
339
461
  nonVariadicRemainingPatterns++;
340
462
  }
463
+ // A pattern is deterministic if it's not a capture at all (i.e., a literal/fixed structure)
464
+ // Variadic captures and non-variadic captures are both non-deterministic
465
+ if (nextCaptureMarker) {
466
+ allRemainingPatternsAreDeterministic = false;
467
+ }
341
468
  }
342
469
  const remainingTargetElements = targetElements.length - targetIdx;
343
470
  const maxPossible = Math.min(remainingTargetElements - nonVariadicRemainingPatterns, max);
@@ -345,7 +472,10 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
345
472
  // This avoids unnecessary backtracking when constraints make the split point obvious
346
473
  let pivotDetected = false;
347
474
  let pivotAt = -1;
348
- if (patternIdx + 1 < patternElements.length && min <= maxPossible) {
475
+ // Skip pivot detection if we're using deterministic optimization
476
+ // (when all remaining patterns are literals, there's only ONE valid consumption amount)
477
+ const useDeterministicOptimization = allRemainingPatternsAreDeterministic && maxPossible >= min && maxPossible <= max;
478
+ if (!useDeterministicOptimization && patternIdx + 1 < patternElements.length && min <= maxPossible) {
349
479
  const nextPattern = patternElements[patternIdx + 1];
350
480
  // Scan through possible consumption amounts starting from min
351
481
  for (let tryConsume = min; tryConsume <= maxPossible; tryConsume++) {
@@ -372,10 +502,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
372
502
  }
373
503
  }
374
504
  }
375
- // Try different consumption amounts
376
- // If pivot detected, try that first; otherwise use greedy approach (max to min)
505
+ // Determine consumption order
377
506
  const consumptionOrder = [];
378
- if (pivotDetected && pivotAt >= 0) {
507
+ // OPTIMIZATION: If all remaining patterns are deterministic (literals, not captures),
508
+ // there's only ONE mathematically valid consumption amount. Skip backtracking entirely.
509
+ // Example: foo(${args}, 999) matching foo(1,2,42) -> args MUST be [1,2], only try consume=2
510
+ if (useDeterministicOptimization) {
511
+ consumptionOrder.push(maxPossible);
512
+ }
513
+ else if (pivotDetected && pivotAt >= 0) {
379
514
  // Try pivot first, then others as fallback
380
515
  consumptionOrder.push(pivotAt);
381
516
  for (let c = maxPossible; c >= min; c--) {
@@ -392,21 +527,15 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
392
527
  }
393
528
  for (const consume of consumptionOrder) {
394
529
  // Capture elements for this consumption amount
395
- const capturedWrappers = [];
396
- for (let i = 0; i < consume; i++) {
397
- const wrapped = targetElements[targetIdx + i];
398
- const element = wrapped.element;
399
- // For arguments, filter out J.Empty as it represents an empty argument list
400
- // For statements, include all elements
401
- if (!filterEmpty || element.kind !== java_1.J.Kind.Empty) {
402
- capturedWrappers.push(wrapped);
403
- }
404
- }
405
- // Extract just the elements for the constraint check
530
+ // For empty argument lists, there will be a single J.Empty element that we need to filter out
531
+ const rawWrappers = targetElements.slice(targetIdx, targetIdx + consume);
532
+ const capturedWrappers = filterEmpty
533
+ ? rawWrappers.filter(w => w.element.kind !== java_1.J.Kind.Empty)
534
+ : rawWrappers;
406
535
  const capturedElements = capturedWrappers.map(w => w.element);
407
- // Re-check min/max constraints against actual captured elements (after filtering if applicable)
536
+ // Check min/max constraints against filtered elements
408
537
  if (capturedElements.length < min || capturedElements.length > max) {
409
- continue; // Try next consumption amount
538
+ continue;
410
539
  }
411
540
  // Evaluate constraint for variadic capture
412
541
  // For variadic captures, constraint receives the entire array of captured elements
@@ -447,27 +576,646 @@ class PatternMatchingComparator extends comparator_1.JavaScriptSemanticComparato
447
576
  if (filterEmpty && targetElement.kind === java_1.J.Kind.Empty) {
448
577
  return false;
449
578
  }
579
+ if (!(yield this.visitSequenceElement(patternWrapper, targetWrapper, targetIdx))) {
580
+ return false;
581
+ }
582
+ // Continue matching the rest
583
+ return yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
584
+ }
585
+ });
586
+ }
587
+ /**
588
+ * Visit a single element in a sequence during non-variadic matching.
589
+ * Extracted to allow debug subclass to add path tracking.
590
+ *
591
+ * @param patternWrapper The pattern element
592
+ * @param targetWrapper The target element
593
+ * @param targetIdx The index in the target sequence
594
+ * @returns true if matching succeeded, false otherwise
595
+ */
596
+ visitSequenceElement(patternWrapper, targetWrapper, targetIdx) {
597
+ return __awaiter(this, void 0, void 0, function* () {
598
+ // Save current state for backtracking (both match state and capture bindings)
599
+ const savedMatch = this.match;
600
+ const savedState = this.matcher.saveState();
601
+ yield this.visitRightPadded(patternWrapper, targetWrapper);
602
+ if (!this.match) {
603
+ // Restore state on match failure
604
+ this.match = savedMatch;
605
+ this.matcher.restoreState(savedState);
606
+ return false;
607
+ }
608
+ return true;
609
+ });
610
+ }
611
+ }
612
+ exports.PatternMatchingComparator = PatternMatchingComparator;
613
+ /**
614
+ * Debug-instrumented version of PatternMatchingComparator.
615
+ * Overrides methods to add path tracking, logging, and explanation capture.
616
+ * Zero cost when not instantiated - production code uses the base class.
617
+ */
618
+ class DebugPatternMatchingComparator extends PatternMatchingComparator {
619
+ get debug() {
620
+ return this.matcher.debug;
621
+ }
622
+ /**
623
+ * Extracts the last segment of a kind string (after the last dot).
624
+ * For example: "org.openrewrite.java.tree.J.MethodInvocation" -> "MethodInvocation"
625
+ */
626
+ formatKind(kind) {
627
+ return kind.substring(kind.lastIndexOf('.') + 1);
628
+ }
629
+ /**
630
+ * Formats a value for display in error messages.
631
+ */
632
+ formatValue(value) {
633
+ if (value === null)
634
+ return 'null';
635
+ if (value === undefined)
636
+ return 'undefined';
637
+ if (typeof value === 'string')
638
+ return `"${value}"`;
639
+ if (typeof value === 'number' || typeof value === 'boolean')
640
+ return String(value);
641
+ // For objects with a kind property (LST nodes)
642
+ if (value && typeof value === 'object' && value.kind) {
643
+ const kind = this.formatKind(value.kind);
644
+ // Show key identifying properties for common node types
645
+ if (value.simpleName)
646
+ return `${kind}("${value.simpleName}")`;
647
+ if (value.value !== undefined)
648
+ return `${kind}(${this.formatValue(value.value)})`;
649
+ return kind;
650
+ }
651
+ return String(value);
652
+ }
653
+ /**
654
+ * Override abort to capture explanation when debug is enabled.
655
+ * Only sets explanation on the first abort call (when this.match is still true).
656
+ * This preserves the most specific explanation closest to the actual mismatch.
657
+ */
658
+ abort(t, reason, propertyName, expected, actual) {
659
+ // If already aborted, don't overwrite the explanation
660
+ // The first abort is typically the most specific
661
+ if (!this.match) {
662
+ return t;
663
+ }
664
+ // If we have context about the mismatch, capture it
665
+ if (reason && this.debug && (expected !== undefined || actual !== undefined)) {
666
+ const expectedStr = this.formatValue(expected);
667
+ const actualStr = this.formatValue(actual);
668
+ this.debug.setExplanation(reason, expectedStr, actualStr, 'Property values do not match');
669
+ }
670
+ // Set `this.match = false`
671
+ return super.abort(t, reason, propertyName, expected, actual);
672
+ }
673
+ /**
674
+ * Override helper methods to extract detailed context from cursors.
675
+ */
676
+ kindMismatch() {
677
+ var _a, _b;
678
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
679
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
680
+ // Pass the full kind strings - formatValue() will detect and format them
681
+ return this.abort(pattern, 'kind-mismatch', 'kind', this.formatKind(pattern === null || pattern === void 0 ? void 0 : pattern.kind), this.formatKind(target === null || target === void 0 ? void 0 : target.kind));
682
+ }
683
+ structuralMismatch(propertyName) {
684
+ var _a, _b;
685
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
686
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
687
+ const expectedValue = pattern === null || pattern === void 0 ? void 0 : pattern[propertyName];
688
+ const actualValue = target === null || target === void 0 ? void 0 : target[propertyName];
689
+ return this.abort(pattern, 'structural-mismatch', propertyName, expectedValue, actualValue);
690
+ }
691
+ arrayLengthMismatch(propertyName) {
692
+ var _a, _b;
693
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
694
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
695
+ const expectedArray = pattern === null || pattern === void 0 ? void 0 : pattern[propertyName];
696
+ const actualArray = target === null || target === void 0 ? void 0 : target[propertyName];
697
+ const expectedLen = Array.isArray(expectedArray) ? expectedArray.length : 'not an array';
698
+ const actualLen = Array.isArray(actualArray) ? actualArray.length : 'not an array';
699
+ return this.abort(pattern, 'array-length-mismatch', propertyName, expectedLen, actualLen);
700
+ }
701
+ valueMismatch(propertyName, expected, actual) {
702
+ var _a, _b;
703
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
704
+ const target = (_b = this.targetCursor) === null || _b === void 0 ? void 0 : _b.value;
705
+ // Track number of paths pushed for cleanup
706
+ let pathsPushed = 0;
707
+ // Handle path tracking only if propertyName is provided
708
+ if (propertyName) {
709
+ // Split dotted property paths (e.g., "name.simpleName" → ["name", "simpleName"])
710
+ const pathParts = propertyName.split('.');
711
+ pathsPushed = pathParts.length;
712
+ // Add each property to path with kind information for nested objects
713
+ const kindStr = this.formatKind(pattern === null || pattern === void 0 ? void 0 : pattern.kind);
714
+ this.debug.pushPath(`${kindStr}#${pathParts[0]}`);
715
+ // For nested properties, try to get the kind of intermediate objects
716
+ let currentObj = pattern === null || pattern === void 0 ? void 0 : pattern[pathParts[0]];
717
+ for (let i = 1; i < pathParts.length; i++) {
718
+ if (currentObj && typeof currentObj === 'object' && currentObj.kind) {
719
+ // Include the kind of the nested object
720
+ const nestedKind = this.formatKind(currentObj.kind);
721
+ this.debug.pushPath(`${nestedKind}#${pathParts[i]}`);
722
+ }
723
+ else {
724
+ // Fallback to just the property name if no kind available
725
+ this.debug.pushPath(pathParts[i]);
726
+ }
727
+ currentObj = currentObj === null || currentObj === void 0 ? void 0 : currentObj[pathParts[i]];
728
+ }
729
+ }
730
+ try {
731
+ // If expected/actual provided, use them directly
732
+ if (expected !== undefined || actual !== undefined) {
733
+ return this.abort(pattern, 'value-mismatch', propertyName, expected, actual);
734
+ }
735
+ // Otherwise, try to extract from cursors (fallback for older code)
736
+ if (propertyName) {
737
+ // Navigate dotted property paths
738
+ const getNestedValue = (obj, path) => {
739
+ return path.split('.').reduce((current, prop) => current === null || current === void 0 ? void 0 : current[prop], obj);
740
+ };
741
+ const expectedValue = getNestedValue(pattern, propertyName);
742
+ const actualValue = getNestedValue(target, propertyName);
743
+ return this.abort(pattern, 'value-mismatch', propertyName, expectedValue, actualValue);
744
+ }
745
+ else {
746
+ // No property name - compare whole objects
747
+ return this.abort(pattern, 'value-mismatch', propertyName, pattern, target);
748
+ }
749
+ }
750
+ finally {
751
+ // Pop all the path components we pushed
752
+ for (let i = 0; i < pathsPushed; i++) {
753
+ this.debug.popPath();
754
+ }
755
+ }
756
+ }
757
+ visit(j, p, parent) {
758
+ const _super = Object.create(null, {
759
+ visit: { get: () => super.visit }
760
+ });
761
+ return __awaiter(this, void 0, void 0, function* () {
762
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(j);
763
+ if (captureMarker) {
764
+ const savedTargetCursor = this.targetCursor;
765
+ const cursorAtCapturedNode = this.targetCursor !== undefined
766
+ ? new __1.Cursor(p, this.targetCursor)
767
+ : new __1.Cursor(p);
768
+ this.targetCursor = cursorAtCapturedNode;
769
+ try {
770
+ if (captureMarker.constraint && !captureMarker.variadicOptions) {
771
+ this.debug.log('debug', 'constraint', `Evaluating constraint for capture: ${captureMarker.captureName}`);
772
+ const constraintResult = captureMarker.constraint(p, cursorAtCapturedNode);
773
+ if (!constraintResult) {
774
+ this.debug.log('info', 'constraint', `Constraint failed for capture: ${captureMarker.captureName}`);
775
+ this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${p.kind}`, `Constraint evaluation returned false`);
776
+ return this.abort(j);
777
+ }
778
+ this.debug.log('debug', 'constraint', `Constraint passed for capture: ${captureMarker.captureName}`);
779
+ }
780
+ const success = this.matcher.handleCapture(captureMarker, p, undefined);
781
+ if (!success) {
782
+ return this.abort(j);
783
+ }
784
+ return j;
785
+ }
786
+ finally {
787
+ this.targetCursor = savedTargetCursor;
788
+ }
789
+ }
790
+ return yield _super.visit.call(this, j, p, parent);
791
+ });
792
+ }
793
+ visitElement(j, other) {
794
+ return __awaiter(this, void 0, void 0, function* () {
795
+ if (!this.match) {
796
+ return j;
797
+ }
798
+ const kindStr = this.formatKind(j.kind);
799
+ if (j.kind !== other.kind) {
800
+ return this.abort(j, 'kind-mismatch', 'kind', kindStr, this.formatKind(other.kind));
801
+ }
802
+ for (const key of Object.keys(j)) {
803
+ if (key.startsWith('_') || key === 'kind' || key === 'id' || key === 'markers' || key === 'prefix') {
804
+ continue;
805
+ }
806
+ const jValue = j[key];
807
+ const otherValue = other[key];
808
+ if (Array.isArray(jValue)) {
809
+ if (!Array.isArray(otherValue) || jValue.length !== otherValue.length) {
810
+ this.debug.pushPath(`${kindStr}#${key}`);
811
+ const result = this.abort(j, 'array-length-mismatch', key, jValue.length, Array.isArray(otherValue) ? otherValue.length : otherValue);
812
+ this.debug.popPath();
813
+ return result;
814
+ }
815
+ for (let i = 0; i < jValue.length; i++) {
816
+ this.debug.pushPath(`${kindStr}#${key}`);
817
+ this.debug.pushPath(i.toString());
818
+ try {
819
+ yield this.visitProperty(jValue[i], otherValue[i]);
820
+ if (!this.match) {
821
+ return j;
822
+ }
823
+ }
824
+ finally {
825
+ this.debug.popPath();
826
+ this.debug.popPath();
827
+ }
828
+ }
829
+ }
830
+ else {
831
+ this.debug.pushPath(`${kindStr}#${key}`);
832
+ try {
833
+ yield this.visitProperty(jValue, otherValue);
834
+ if (!this.match) {
835
+ return j;
836
+ }
837
+ }
838
+ finally {
839
+ this.debug.popPath();
840
+ }
841
+ }
842
+ }
843
+ return j;
844
+ });
845
+ }
846
+ visitRightPadded(right, p) {
847
+ const _super = Object.create(null, {
848
+ visitRightPadded: { get: () => super.visitRightPadded }
849
+ });
850
+ return __awaiter(this, void 0, void 0, function* () {
851
+ if (!this.match) {
852
+ return right;
853
+ }
854
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(right);
855
+ if (captureMarker) {
856
+ const isRightPadded = p.kind === java_1.J.Kind.RightPadded;
857
+ const targetWrapper = isRightPadded ? p : undefined;
858
+ const targetElement = isRightPadded ? targetWrapper.element : p;
859
+ const savedTargetCursor = this.targetCursor;
860
+ const cursorAtCapturedNode = this.targetCursor !== undefined
861
+ ? (targetWrapper ? new __1.Cursor(targetWrapper, this.targetCursor) : new __1.Cursor(targetElement, this.targetCursor))
862
+ : (targetWrapper ? new __1.Cursor(targetWrapper) : new __1.Cursor(targetElement));
863
+ this.targetCursor = cursorAtCapturedNode;
864
+ try {
865
+ if (captureMarker.constraint && !captureMarker.variadicOptions) {
866
+ this.debug.log('debug', 'constraint', `Evaluating constraint for wrapped capture: ${captureMarker.captureName}`);
867
+ const constraintResult = captureMarker.constraint(targetElement, cursorAtCapturedNode);
868
+ if (!constraintResult) {
869
+ this.debug.log('info', 'constraint', `Constraint failed for wrapped capture: ${captureMarker.captureName}`);
870
+ this.debug.setExplanation('constraint-failed', `Capture ${captureMarker.captureName} with valid constraint`, `Constraint failed for ${targetElement.kind}`, `Constraint evaluation returned false`);
871
+ return this.abort(right);
872
+ }
873
+ this.debug.log('debug', 'constraint', `Constraint passed for wrapped capture: ${captureMarker.captureName}`);
874
+ }
875
+ const success = this.matcher.handleCapture(captureMarker, targetElement, targetWrapper);
876
+ if (!success) {
877
+ return this.abort(right);
878
+ }
879
+ return right;
880
+ }
881
+ finally {
882
+ this.targetCursor = savedTargetCursor;
883
+ }
884
+ }
885
+ return yield _super.visitRightPadded.call(this, right, p);
886
+ });
887
+ }
888
+ visitContainer(container, p) {
889
+ return __awaiter(this, void 0, void 0, function* () {
890
+ if (!this.match) {
891
+ return container;
892
+ }
893
+ const isContainer = p.kind === java_1.J.Kind.Container;
894
+ if (!isContainer) {
895
+ return this.abort(container);
896
+ }
897
+ const otherContainer = p;
898
+ const hasVariadicCapture = container.elements.some(elem => utils_1.PlaceholderUtils.isVariadicCapture(elem));
899
+ const savedCursor = this.cursor;
900
+ const savedTargetCursor = this.targetCursor;
901
+ this.cursor = new __1.Cursor(container, this.cursor);
902
+ this.targetCursor = new __1.Cursor(otherContainer, this.targetCursor);
903
+ try {
904
+ if (hasVariadicCapture) {
905
+ if (!(yield this.matchSequence(container.elements, otherContainer.elements, true))) {
906
+ return this.arrayLengthMismatch('elements');
907
+ }
908
+ }
909
+ else {
910
+ // Non-variadic path - track indices
911
+ if (container.elements.length !== otherContainer.elements.length) {
912
+ return this.arrayLengthMismatch('elements');
913
+ }
914
+ for (let i = 0; i < container.elements.length; i++) {
915
+ this.debug.pushPath(i.toString());
916
+ try {
917
+ if (!(yield this.visitContainerElement(container.elements[i], otherContainer.elements[i], i))) {
918
+ return container;
919
+ }
920
+ }
921
+ finally {
922
+ this.debug.popPath();
923
+ }
924
+ }
925
+ }
926
+ }
927
+ finally {
928
+ this.cursor = savedCursor;
929
+ this.targetCursor = savedTargetCursor;
930
+ }
931
+ return container;
932
+ });
933
+ }
934
+ /**
935
+ * Override visitContainerProperty to add path tracking with property context.
936
+ */
937
+ visitContainerProperty(propertyName, container, otherContainer) {
938
+ return __awaiter(this, void 0, void 0, function* () {
939
+ // Get parent from cursor
940
+ const parent = this.cursor.value;
941
+ // Push path for the property
942
+ const kindStr = this.formatKind(parent.kind);
943
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
944
+ try {
945
+ yield this.visitContainer(container, otherContainer);
946
+ return container;
947
+ }
948
+ finally {
949
+ this.debug.popPath();
950
+ }
951
+ });
952
+ }
953
+ /**
954
+ * Override visitRightPaddedProperty to add path tracking with property context.
955
+ */
956
+ visitRightPaddedProperty(propertyName, rightPadded, otherRightPadded) {
957
+ return __awaiter(this, void 0, void 0, function* () {
958
+ // Get parent from cursor
959
+ const parent = this.cursor.value;
960
+ // Push path for the property
961
+ const kindStr = this.formatKind(parent.kind);
962
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
963
+ try {
964
+ return yield this.visitRightPadded(rightPadded, otherRightPadded);
965
+ }
966
+ finally {
967
+ this.debug.popPath();
968
+ }
969
+ });
970
+ }
971
+ /**
972
+ * Override visitLeftPaddedProperty to add path tracking with property context.
973
+ */
974
+ visitLeftPaddedProperty(propertyName, leftPadded, otherLeftPadded) {
975
+ return __awaiter(this, void 0, void 0, function* () {
976
+ // Get parent from cursor
977
+ const parent = this.cursor.value;
978
+ // Push path for the property
979
+ const kindStr = this.formatKind(parent.kind);
980
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
981
+ try {
982
+ return yield this.visitLeftPadded(leftPadded, otherLeftPadded);
983
+ }
984
+ finally {
985
+ this.debug.popPath();
986
+ }
987
+ });
988
+ }
989
+ visitContainerElement(element, otherElement, index) {
990
+ const _super = Object.create(null, {
991
+ visitContainerElement: { get: () => super.visitContainerElement }
992
+ });
993
+ return __awaiter(this, void 0, void 0, function* () {
994
+ // Don't push index here - it should be handled by the caller with proper context
995
+ return yield _super.visitContainerElement.call(this, element, otherElement, index);
996
+ });
997
+ }
998
+ visitArrayProperty(parent, propertyName, array1, array2, visitor) {
999
+ return __awaiter(this, void 0, void 0, function* () {
1000
+ // Push path for the property
1001
+ const kindStr = this.formatKind(parent.kind);
1002
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
1003
+ try {
1004
+ // Check length mismatch (will have path context)
1005
+ if (array1.length !== array2.length) {
1006
+ this.arrayLengthMismatch(propertyName);
1007
+ return;
1008
+ }
1009
+ // Visit each element with index tracking
1010
+ for (let i = 0; i < array1.length; i++) {
1011
+ this.debug.pushPath(i.toString());
1012
+ try {
1013
+ yield visitor(array1[i], array2[i], i);
1014
+ if (!this.match) {
1015
+ return;
1016
+ }
1017
+ }
1018
+ finally {
1019
+ this.debug.popPath();
1020
+ }
1021
+ }
1022
+ }
1023
+ finally {
1024
+ this.debug.popPath();
1025
+ }
1026
+ });
1027
+ }
1028
+ matchSequence(patternElements, targetElements, filterEmpty) {
1029
+ const _super = Object.create(null, {
1030
+ matchSequence: { get: () => super.matchSequence }
1031
+ });
1032
+ return __awaiter(this, void 0, void 0, function* () {
1033
+ var _a, _b;
1034
+ // Push path component for the container
1035
+ // Extract kind from cursors if available
1036
+ const pattern = (_a = this.cursor) === null || _a === void 0 ? void 0 : _a.value;
1037
+ if (pattern && pattern.kind) {
1038
+ const kindStr = this.formatKind(pattern.kind);
1039
+ // Determine property name based on the kind
1040
+ let propertyName = 'elements';
1041
+ if (pattern.kind.includes('MethodInvocation')) {
1042
+ propertyName = 'arguments';
1043
+ }
1044
+ else if (pattern.kind.includes('Block')) {
1045
+ propertyName = 'statements';
1046
+ }
1047
+ this.debug.pushPath(`${kindStr}#${propertyName}`);
1048
+ }
1049
+ try {
1050
+ return yield _super.matchSequence.call(this, patternElements, targetElements, filterEmpty);
1051
+ }
1052
+ finally {
1053
+ if ((_b = this.cursor) === null || _b === void 0 ? void 0 : _b.value) {
1054
+ this.debug.popPath();
1055
+ }
1056
+ }
1057
+ });
1058
+ }
1059
+ visitSequenceElement(patternWrapper, targetWrapper, targetIdx) {
1060
+ return __awaiter(this, void 0, void 0, function* () {
1061
+ this.debug.pushPath(targetIdx.toString());
1062
+ try {
450
1063
  // Save current state for backtracking (both match state and capture bindings)
451
1064
  const savedMatch = this.match;
452
1065
  const savedState = this.matcher.saveState();
453
1066
  yield this.visitRightPadded(patternWrapper, targetWrapper);
454
1067
  if (!this.match) {
1068
+ // Preserve explanation before restoring state
1069
+ const explanation = this.debug.getExplanation();
455
1070
  // Restore state on match failure
456
1071
  this.match = savedMatch;
457
1072
  this.matcher.restoreState(savedState);
1073
+ // Restore the explanation if one was set during matching
1074
+ if (explanation) {
1075
+ this.debug.restoreExplanation(explanation);
1076
+ }
458
1077
  return false;
459
1078
  }
460
- // Continue matching the rest
461
- const restMatches = yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
462
- if (!restMatches) {
463
- // Restore full state on backtracking failure
464
- this.match = savedMatch;
1079
+ return true;
1080
+ }
1081
+ finally {
1082
+ this.debug.popPath();
1083
+ }
1084
+ });
1085
+ }
1086
+ matchSequenceOptimized(patternElements, targetElements, patternIdx, targetIdx, filterEmpty) {
1087
+ return __awaiter(this, void 0, void 0, function* () {
1088
+ var _a, _b;
1089
+ if (patternIdx >= patternElements.length) {
1090
+ return targetIdx >= targetElements.length;
1091
+ }
1092
+ const patternWrapper = patternElements[patternIdx];
1093
+ const captureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternWrapper);
1094
+ const isVariadic = (captureMarker === null || captureMarker === void 0 ? void 0 : captureMarker.variadicOptions) !== undefined;
1095
+ if (isVariadic) {
1096
+ const variadicOptions = captureMarker.variadicOptions;
1097
+ const min = (_a = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.min) !== null && _a !== void 0 ? _a : 0;
1098
+ const max = (_b = variadicOptions === null || variadicOptions === void 0 ? void 0 : variadicOptions.max) !== null && _b !== void 0 ? _b : Infinity;
1099
+ let nonVariadicRemainingPatterns = 0;
1100
+ let allRemainingPatternsAreDeterministic = true;
1101
+ for (let i = patternIdx + 1; i < patternElements.length; i++) {
1102
+ const nextCaptureMarker = utils_1.PlaceholderUtils.getCaptureMarker(patternElements[i]);
1103
+ const nextIsVariadic = (nextCaptureMarker === null || nextCaptureMarker === void 0 ? void 0 : nextCaptureMarker.variadicOptions) !== undefined;
1104
+ if (!nextIsVariadic) {
1105
+ nonVariadicRemainingPatterns++;
1106
+ }
1107
+ if (nextCaptureMarker) {
1108
+ allRemainingPatternsAreDeterministic = false;
1109
+ }
1110
+ }
1111
+ const remainingTargetElements = targetElements.length - targetIdx;
1112
+ const maxPossible = Math.min(remainingTargetElements - nonVariadicRemainingPatterns, max);
1113
+ let pivotDetected = false;
1114
+ let pivotAt = -1;
1115
+ // Skip pivot detection if we're using deterministic optimization
1116
+ // (when all remaining patterns are literals, there's only ONE valid consumption amount)
1117
+ const useDeterministicOptimization = allRemainingPatternsAreDeterministic && maxPossible >= min && maxPossible <= max;
1118
+ if (!useDeterministicOptimization && patternIdx + 1 < patternElements.length && min <= maxPossible) {
1119
+ const nextPattern = patternElements[patternIdx + 1];
1120
+ for (let tryConsume = min; tryConsume <= maxPossible; tryConsume++) {
1121
+ if (targetIdx + tryConsume < targetElements.length) {
1122
+ const candidateElement = targetElements[targetIdx + tryConsume];
1123
+ if (filterEmpty && candidateElement.element.kind === java_1.J.Kind.Empty) {
1124
+ continue;
1125
+ }
1126
+ const savedMatch = this.match;
1127
+ const savedState = this.matcher.saveState();
1128
+ yield this.visitRightPadded(nextPattern, candidateElement);
1129
+ const matchesNext = this.match;
1130
+ this.match = savedMatch;
1131
+ this.matcher.restoreState(savedState);
1132
+ if (matchesNext) {
1133
+ pivotDetected = true;
1134
+ pivotAt = tryConsume;
1135
+ break;
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ const consumptionOrder = [];
1141
+ // OPTIMIZATION: If all remaining patterns are deterministic (literals, not captures),
1142
+ // there's only ONE mathematically valid consumption amount. Skip backtracking entirely.
1143
+ // Example: foo(${args}, 999) matching foo(1,2,42) -> args MUST be [1,2], only try consume=2
1144
+ if (useDeterministicOptimization) {
1145
+ consumptionOrder.push(maxPossible);
1146
+ }
1147
+ else if (pivotDetected && pivotAt >= 0) {
1148
+ consumptionOrder.push(pivotAt);
1149
+ for (let c = maxPossible; c >= min; c--) {
1150
+ if (c !== pivotAt) {
1151
+ consumptionOrder.push(c);
1152
+ }
1153
+ }
1154
+ }
1155
+ else {
1156
+ for (let c = maxPossible; c >= min; c--) {
1157
+ consumptionOrder.push(c);
1158
+ }
1159
+ }
1160
+ for (const consume of consumptionOrder) {
1161
+ // Capture elements for this consumption amount
1162
+ // For empty argument lists, there will be a single J.Empty element that we need to filter out
1163
+ const rawWrappers = targetElements.slice(targetIdx, targetIdx + consume);
1164
+ const capturedWrappers = filterEmpty
1165
+ ? rawWrappers.filter(w => w.element.kind !== java_1.J.Kind.Empty)
1166
+ : rawWrappers;
1167
+ const capturedElements = capturedWrappers.map(w => w.element);
1168
+ // Check min/max constraints against filtered elements
1169
+ if (capturedElements.length < min || capturedElements.length > max) {
1170
+ continue;
1171
+ }
1172
+ if (captureMarker.constraint) {
1173
+ this.debug.log('debug', 'constraint', `Evaluating variadic constraint for capture: ${captureMarker.captureName} (${capturedElements.length} elements)`);
1174
+ const cursor = this.targetCursor || new __1.Cursor(targetElements[0]);
1175
+ const constraintResult = captureMarker.constraint(capturedElements, cursor);
1176
+ if (!constraintResult) {
1177
+ this.debug.log('info', 'constraint', `Variadic constraint failed for capture: ${captureMarker.captureName}`);
1178
+ continue;
1179
+ }
1180
+ this.debug.log('debug', 'constraint', `Variadic constraint passed for capture: ${captureMarker.captureName}`);
1181
+ }
1182
+ const savedState = this.matcher.saveState();
1183
+ const success = this.matcher.handleVariadicCapture(captureMarker, capturedElements, capturedWrappers);
1184
+ if (!success) {
1185
+ this.matcher.restoreState(savedState);
1186
+ continue;
1187
+ }
1188
+ const restMatches = yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + consume, filterEmpty);
1189
+ if (restMatches) {
1190
+ return true;
1191
+ }
1192
+ // Preserve explanation from this failed attempt before restoring state
1193
+ // This is especially important when using deterministic optimization (only one attempt)
1194
+ const currentExplanation = this.debug.getExplanation();
465
1195
  this.matcher.restoreState(savedState);
1196
+ // Restore the explanation if one was set during this attempt
1197
+ if (currentExplanation) {
1198
+ this.debug.restoreExplanation(currentExplanation);
1199
+ }
466
1200
  }
467
- return restMatches;
1201
+ return false;
1202
+ }
1203
+ else {
1204
+ if (targetIdx >= targetElements.length) {
1205
+ return false;
1206
+ }
1207
+ const targetWrapper = targetElements[targetIdx];
1208
+ const targetElement = targetWrapper.element;
1209
+ if (filterEmpty && targetElement.kind === java_1.J.Kind.Empty) {
1210
+ return false;
1211
+ }
1212
+ if (!(yield this.visitSequenceElement(patternWrapper, targetWrapper, targetIdx))) {
1213
+ return false;
1214
+ }
1215
+ return yield this.matchSequenceOptimized(patternElements, targetElements, patternIdx + 1, targetIdx + 1, filterEmpty);
468
1216
  }
469
1217
  });
470
1218
  }
471
1219
  }
472
- exports.PatternMatchingComparator = PatternMatchingComparator;
1220
+ exports.DebugPatternMatchingComparator = DebugPatternMatchingComparator;
473
1221
  //# sourceMappingURL=comparator.js.map