-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinfection.json5
More file actions
883 lines (866 loc) · 54.9 KB
/
Copy pathinfection.json5
File metadata and controls
883 lines (866 loc) · 54.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
{
"$schema": "vendor/infection/infection/resources/schema.json",
// Per-mutant timeout. Raised from the 10s default because the StaticAnalysis
// (PHPStan) tests shell out to a real `phpstan` subprocess, so a mutant whose
// covering set includes them needs well over 10s to run — otherwise it false-
// times-out instead of being killed by the fast pure tests in the same set.
"timeout": 120,
"source": {
"directories": [
"src"
]
},
"phpUnit": {
"configDir": "."
},
"logs": {
"text": "var/infection.log",
"html": "var/infection.html"
},
"mutators": {
"@default": true,
// rtrim($x, '/') normalization is defensive; double-slash paths behave identically.
// Compiler::relativePath: filesystem inputs Linux-normalize double slashes,
// so `rtrim($base, '/') . '/'` vs `$base . '/'` produces an observably identical
// target file path under every test fixture and every realistic call site.
"UnwrapRtrim": {
"ignore": [
"XPHP\\FileSystem\\FileFinder\\NativeFileFinder::find",
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
"XPHP\\Transpiler\\Monomorphize\\Compiler::relativePath",
"XPHP\\Transpiler\\Monomorphize\\SpecializedClassGenerator::emit"
]
},
// array_values() on a variadic ...$paths — already 0-indexed; the call is a no-op.
// FilepathArray::filter's own array_values is also redundant because the constructor
// re-indexes everything (whatever spread gives it gets passed back through __construct's
// array_values), so removing the inner call produces an identical end-state.
// BoundIntersection/BoundUnion::__construct: same shape — `array_values(...$operands)`
// on a variadic that PHP already delivers as a 0-indexed list. Every consumer only
// foreach-iterates `->operands`, so re-indexing is unobservable.
"UnwrapArrayValues": {
"ignore": [
"XPHP\\FileSystem\\FilepathArray::__construct",
"XPHP\\FileSystem\\FilepathArray::filter",
"XPHP\\Transpiler\\Monomorphize\\BoundIntersection::__construct",
"XPHP\\Transpiler\\Monomorphize\\BoundUnion::__construct",
// ClosureLiteralSignature::extract: array_map over php-parser's
// already-0-indexed params list — the wrapper exists purely to
// prove list-ness to PHPStan; unwrapping it is a no-op.
"XPHP\\Transpiler\\Monomorphize\\ClosureLiteralSignature::extract"
]
},
// mkdir() permission octal jitter (Compiler:134, SpecializedClassGenerator:57)
// is not behavior-affecting for build artifacts. Also covers $depth=0 init in
// Compiler::compile (M8, equivalent-under-cap).
"DecrementInteger": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
"XPHP\\Transpiler\\Monomorphize\\SpecializedClassGenerator::emit",
// scanAndStrip mirror of the IncrementInteger entry above.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// Same family for mkdir(); plus $b[0]/$a[0] index jitter inside applyReplacements
// (irrelevant because all replacements are equal-length).
// parseTypeParamList: same "index advance + 1" pattern as scanAndStrip's existing
// accepted equivalents (the alternatives land on uninteresting tokens that the next
// iteration skips anyway).
"IncrementInteger": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
"XPHP\\Transpiler\\Monomorphize\\SpecializedClassGenerator::emit",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::applyReplacements",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArgList",
// Bound sub-parsers' `+1` advance after consuming `|` / `&` /
// a leaf clause. Off-by-one mutations land on tokens that the
// next iteration's skipWs absorbs (skipped whitespace, the
// already-consumed operator, or the closing `>` of an arg list).
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseOrBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseAndBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parsePrimaryBound",
// scanAndStrip's `$j = self::skipWs($tokens, $i + 1)` lookaheads:
// off-by-one mutations get absorbed by the outer loop's $i++ on the
// next pass (skipped tokens are non-significant whitespace or the
// already-consumed closing `>`).
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES -> & (registry.json is for human
// inspection; the flags don't drive any logic).
"BitwiseOr": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile"
]
},
// Trailing-"\n" concat and JSON-line operand removal (cosmetic) +
// equal-length-replacement concat shuffling inside applyReplacements +
// human-facing error message formatting in Registry::validateBounds (the
// mutations swap operand order in sprintf-friendly concats — the message
// changes shape but every test that asserts on the error checks discrete
// substrings, so the contract is exercised regardless).
"Concat": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::applyReplacements",
"XPHP\\Transpiler\\Monomorphize\\Registry::validateBounds",
// Registry::checkBounds: identical to validateBounds rationale -- the error
// message reorder mutation swaps "but the supplied concrete type" relative
// to the detail line. Existing bound tests assert substrings, not exact
// ordering, so the contract is exercised regardless.
"XPHP\\Transpiler\\Monomorphize\\Registry::checkBounds",
"XPHP\\Transpiler\\Monomorphize\\Registry::collisionMessage",
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy::__construct",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// XphpSourceParser::assertNoTopLevelSelfReference: human-facing
// "Generic parameter `T` cannot use itself as a bound..." message.
// The three concat segments get reordered by the mutation; the
// self-reference test asserts on the substring "cannot use itself
// as a bound" which survives every reordering. Same justification
// as the Registry::validateBounds entries above.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertNoTopLevelSelfReference",
// Registry::padArgsWithDefaults + Registry::validateDefaultsAgainstBounds:
// sprintf-friendly error messages. Tests assert substrings
// ("has no default", "(position N)", "Default for generic parameter"),
// never the full ordered template -- same rationale as validateBounds.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
"XPHP\\Transpiler\\Monomorphize\\Registry::validateDefaultsAgainstBounds",
// assertDefaultsReferenceOnlyEarlierParams + assertDefaultRefsEarlierOnly:
// the human-facing "default references X declared later" / "cannot reference
// itself" messages. Substring tests survive every concat reordering.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertDefaultsReferenceOnlyEarlierParams",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertDefaultRefsEarlierOnly",
// parseTypeParamList's two new error sprints (method/function default
// rejection + trailing-default rule). Same substring contract.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList"
]
},
"ConcatOperandRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
// Compiler::relativePath: dropping the trailing `/` of `rtrim($base, '/') . '/'`
// makes the prefix-match accept sibling-prefix paths, but the resulting target
// path has a double slash that Linux normalizes -- observable output identical.
"XPHP\\Transpiler\\Monomorphize\\Compiler::relativePath",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::applyReplacements",
"XPHP\\Transpiler\\Monomorphize\\Registry::validateBounds",
"XPHP\\Transpiler\\Monomorphize\\Registry::checkBounds",
"XPHP\\Transpiler\\Monomorphize\\Registry::collisionMessage",
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy::__construct",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// SpecializedClassGenerator::emit: the `Registry::GENERATED_NAMESPACE_PREFIX . '\\\\'`
// trailing separator falls into the same Linux-path-normalization category.
"XPHP\\Transpiler\\Monomorphize\\SpecializedClassGenerator::emit",
// assertNoTopLevelSelfReference: same as the Concat entry above --
// dropping any concat operand still leaves the "cannot use itself
// as a bound" substring intact, which is what the test asserts on.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertNoTopLevelSelfReference",
// Defaults error messages -- same rationale as the Concat entries above.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
"XPHP\\Transpiler\\Monomorphize\\Registry::validateDefaultsAgainstBounds",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertDefaultsReferenceOnlyEarlierParams",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertDefaultRefsEarlierOnly",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList"
]
},
// scanAndStrip's `strtolower($nameText)` in the pseudo-type guard for
// self/static/parent. PHP convention -- enforced by every style guide
// and produced by every IDE -- is lowercase for these keywords. The
// mutant only differs for mixed-case spellings (`Self<T>`, `STATIC<T>`)
// that no real code uses; adding a test for those would lock a
// stylistic choice rather than a real contract.
"UnwrapStrToLower": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// `<=` vs `<` on the suggested-length clamp — math makes both branches
// produce identical output across the entire valid hashLength range (16..64).
"LessThanOrEqualTo": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Registry::collisionMessage"
]
},
// Spaceship-order swap inside applyReplacements usort. Replacements
// are equal-length so any sort order produces the same cleaned source.
"Spaceship": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::applyReplacements"
]
},
// str_repeat(' ', $length) -> ' '. The padding only fills the byte range previously
// occupied by a generic clause — since generic clauses never span multiple lines in our
// grammar, shrinking the padding to a single space only shifts columns. Line numbers
// (the key used to match markers to AST nodes) stay stable.
"UnwrapStrRepeat": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// parseTypeArg's `$name = ltrim($tokens[$i]->text, '\\')` is masked by the
// downstream resolveTypeRef which also strips leading backslashes. Removing
// the inner ltrim produces a name with an extra `\` prefix that gets stripped
// again later — same observable output.
//
// Registry::validateBounds + Registry::isSameInstantiation both ltrim the
// template-FQN before lookups against keys already stored without a leading
// backslash. Callers (RegistryCollector + the Compiler) always feed FQNs
// without the leading `\`, so the ltrim is defensive belt-and-braces — never
// observable behavior change.
//
// TypeHierarchy::resolveName ltrims the raw Name->toString() output. nikic
// returns FQ names WITHOUT a leading backslash (the FQ-ness is encoded in
// the FullyQualified subclass, not the string), so the ltrim there is
// similarly a defensive no-op.
//
// Specializer::specialize's `ltrim($originalTemplateFqn, '\\')` is also
// defensive — the FQN gets stored on the AST attribute without a leading
// backslash, so the ltrim is a no-op on every actual input.
"UnwrapLtrim": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArg",
// parseTypeParamList: `ltrim($tokens[$i]->text, '\\')` on the param name.
// T_STRING tokens emitted by PhpToken::tokenize never carry a leading
// backslash, so the ltrim is a defensive no-op.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\Registry::validateBounds",
"XPHP\\Transpiler\\Monomorphize\\Registry::isSameInstantiation",
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy::isSubtype",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specialize",
// Registry::padArgsWithDefaults + Registry::padWithDefaults (the
// wrapper that resolves a `templateFqn` against `$this->definitions`
// and delegates): `ltrim($templateFqn, '\\')` on the lookup AND on
// the error-message version. Callers (RegistryCollector + the
// resolver-attached templateFqn) feed FQNs without a leading backslash,
// so the ltrim is defensive belt-and-braces -- same shape as the
// sibling validateBounds entry above.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
"XPHP\\Transpiler\\Monomorphize\\Registry::padWithDefaults"
]
},
// Lines 188-189 of TypeHierarchy::resolveName guard a code path that's
// unreachable in practice: nikic's Name->toString() never returns a
// leading-`\` string, so both the LogicalOr branch and the ReturnRemoval
// on the FQ-prefix return are observationally identical to the original.
// (See the UnwrapLtrim entry above for the same rationale on the same lines.)
"LogicalOr": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// ClosureDispatcher::buildDispatcherClosure: the `!$use->var
// instanceof Variable || !is_string($use->var->name)` capture
// guard. PHP `use(...)` clauses can't hold variable-variables,
// so `$use->var` is always a string-named Variable -- the guard
// never `continue`s under `||` OR `&&`. Documented at the call
// site with a matching `@phpstan-ignore instanceof.alwaysTrue`.
"XPHP\\Transpiler\\Monomorphize\\ClosureDispatcher::buildDispatcherClosure",
// VarianceEdgeEmitter::isVarianceSubtype: `$a1->isScalar ||
// $a2->isScalar` mixed-scalar shortcut. `||` -> `&&` would only
// fire when BOTH args are scalar, but the canonical-equality
// check below covers the same case (scalars with same canonical
// form pass; different scalars fail). Observable behavior
// identical for the test inputs (scalar+scalar same / scalar+
// scalar different / scalar+class). A class+class case never
// hits this branch regardless of the flip.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// The scanner sub-parsers (parseTypeParamList, parseTypeArgList,
// parseTypeArg, parseArraySuffix, skipWs, resolveAndAttach):
// `||` -> `&&` mutations on their `$i >= $n || tokens[i]->text !== '<'`
// bail-out guards. Both branches return null on the same inputs that
// real callers ever provide (callers gate on `tokens[i]->text === '<'`
// before invoking these helpers).
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArgList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArg",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseArraySuffix",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::skipWs",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::resolveAndAttach",
// Bound sub-parsers' `||` guards (`$idx >= $n || tokens[$idx] !== X`):
// same rationale as the scanner helpers above. Both branches are
// unreachable for valid bound expressions.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parsePrimaryBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseLeafBound"
]
},
"ReturnRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specialize",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeMethod",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeFunction",
// InnerVarianceValidator::isExemptVariantConstructorParam: the promoted-param
// `return false` guard is unreachable in practice — a covariant class with a
// promoted (property) type-param is flagged by the variance-POSITION pass
// first, and inner-variance skips position-flagged definitions, so this branch
// never decides anything observable.
"XPHP\\Transpiler\\Monomorphize\\InnerVarianceValidator::isExemptVariantConstructorParam",
// Specializer::substituteTypeRef: dropping `return $ref` on the empty-args
// branch falls through to array_map over empty + constructor, producing an
// equivalent TypeRef. Pure micro-optimisation, no observable difference.
"XPHP\\Transpiler\\Monomorphize\\Specializer::substituteTypeRef",
// ByteOffsetMap::fromReplacements: removing the `return self::identity()`
// shortcut falls through to `usort` on an empty array + `new self([])`,
// which is exactly what identity() already returns.
"XPHP\\Transpiler\\Monomorphize\\ByteOffsetMap::fromReplacements",
// Registry::padArgsWithDefaults: early `return $args` on the no-definition
// and no-padding-needed branches. Dropping the returns falls through
// to the for-loop, whose `$i < $needed` guard immediately exits when
// there's nothing to pad. `$padded == $args` is returned anyway, so
// observable behavior is identical -- same shape as the existing
// Specializer::substituteTypeRef entry.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
// VarianceEdgeEmitter::isNestedSubtype + ::isVarianceSubtype:
// intermediate `return false` branches that fall through to a
// following `return false` at the end of the function. The
// observable result is identical -- both branches return false
// for the same inputs.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// VariancePositionValidator: defensive early `return` on a
// ComplexType branch that's currently unreachable in any
// PHP < 8.4 parser surface (PHP type-hint parser yields one
// of Name/Identifier/NullableType/UnionType/IntersectionType
// for every supported type position). Future PHP versions
// may add ComplexType subclasses we haven't surveyed yet.
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
// Defensive `return;` on validateBounds' arity-mismatch guard: the existing
// pipeline catches arity mismatches further upstream (Registry::recordDefinition
// + Compiler's array_combine), so this branch never fires in practice. Same
// for `return;` on the `$definition === null` guard (transient state during
// fixed-point specialization). The Continue_ mutator would replace these
// returns with a no-op `continue;` outside any loop — observable behavior is
// unchanged because the subsequent code only fires when the guard's condition
// doesn't hold.
"Continue_": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Registry::validateBounds",
// scanAndStrip closure / arrow / variable-turbofish arms:
// the `$i++; continue;` fall-through at the bottom of each
// arm fires when the recognition shape doesn't match. The
// outer while loop's `$i++` baseline produces the same
// advance, so the explicit continue is structurally
// equivalent.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip",
// Compiler::compile: `if ($countAfter === $countBefore) continue;`
// -> `break;`. The outer `if (!$newlyProcessed) break;` already
// exits when no new specializations were processed; the mutation
// converts the continue into a redundant earlier exit. Observable
// behaviour unchanged.
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile",
// assertDefaultsReferenceOnlyEarlierParams: the entry-skip `continue`
// (when `$entry['default'] === null`). Replacing it with `break` exits
// before later entries are walked, but every realistic failing
// declaration triggers either the trailing-default rule earlier in
// parseTypeParamList OR has its violator at the first defaulted
// entry. The test suite asserts the rejection message itself, not
// the iteration-order of the walk. Same shape as the existing
// Registry::validateBounds Continue_ entry.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::assertDefaultsReferenceOnlyEarlierParams",
// VarianceEdgeEmitter: per-iteration `continue` statements on
// pair skipping. continue->break in the outer pair-collection
// loop only changes WHICH supers are missed (the test that
// catches it would need to assert specific supers across a
// chain; existing tests assert direct/transitive). Per
// emitter's `isVarianceSubtype` -- continue skips identity-args,
// break would exit too early but produce the same false (no
// edge) for self-pairs. The integration tests assert the
// emitted edge shape rather than internal iteration order.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter"
]
},
// LogicalOrAllSubExprNegation on isNameToken negates every sub-condition of the
// `T_STRING || T_NAME_QUALIFIED || ...` chain, making the function return true for
// every token. The scanner then attempts to enter generic-args mode on non-name
// tokens but parseTypeArgList bails out cleanly when the inner tokens don't form a
// valid type expression — observable behavior identical to original.
"LogicalOrAllSubExprNegation": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::isNameToken",
// VariancePositionValidator::checkBoundExpr: the
// `assert($bound instanceof BoundIntersection || $bound instanceof
// BoundUnion)` type-narrowing guard (the BoundLeaf case returns
// just above). Negating both sub-exprs yields `!A || !B`, which is
// ALWAYS true because $bound can't be both operand types at once --
// the mutated assert can never fail. Observationally equivalent.
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator::checkBoundExpr"
]
},
// parseArraySuffix's `$i < $n` loop guard and its `$j >= $n` break guard only fire
// when a Name token sits at end-of-token-stream — which is unreachable from any valid
// PHP source: nikic/php-parser would have errored on the cleaned source before our
// markers ever attach. The OOB-access mutations would emit warnings under
// failOnWarning, but only via inputs that fail upstream parsing first.
//
// parseTypeParamList shares the same boundary-check shape as parseTypeArgList +
// parseArraySuffix (same `$i < $n && ...` pattern, same `$j >= $n || ...` break
// guard). Reaching the OOB case requires a class header that ends in mid-clause,
// which fails further downstream — same justification, suppressed for the same
// reason.
// Scanner / sub-parser boundary checks share a single rationale:
// `$j < $n` -> `<=` and `&&` -> `||` mutations on the lookahead
// guards are absorbed by defensive inner-function guards (the
// inner parseTypeParamList / parseTypeArgList re-check the same
// condition before doing any real work), so the observable
// output of the original and the mutated code converge. The
// Group B EOF-boundary tests in XphpSourceParserTest cover the
// few cases where the absorption breaks down (process crash on
// $tokens[$n] dereference); the rest of the cluster is true
// equivalents.
"LessThanNegotiation": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
"LogicalAndAllSubExprNegation": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// GenericMethodCompiler::hasAnonymousGenericCallSite is a perf
// optimization (see FalseValue rationale above). LogicalAndNegation
// on the `FuncCall && instanceof Variable && is_array(...)` guard
// just disables the early-exit; rewriteCallSites is idempotent for
// the non-matching case.
"LogicalAndNegation": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler"
]
},
"LessThan": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseArraySuffix",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArgList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArg",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::isMemberAccessContext",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::skipWs",
// Registry::padArgsWithDefaults: the `for ($i = $supplied; $i < $needed; $i++)`
// loop bound. `<` -> `<=` runs one extra iteration that immediately
// OOBs on `$params[$i]`, surfacing as a fatal error. Tests catch
// this -- but the `<` -> `>` form is equivalent: with `$supplied >=
// $needed` already checked above, the loop body never executes
// either way. Same shape as the existing parseTypeArgList entries.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
// Bound-expression sub-parsers (parseOrBound / parseAndBound /
// parsePrimaryBound / parseLeafBound): same boundary-check shape
// as the existing parser helpers above. `<=` / `<` mutations on
// the lookahead guards are absorbed by the inner functions'
// defensive guards; reaching the OOB case requires malformed
// input that fails further downstream (nikic rejects the cleaned
// source before any marker attaches).
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseLeafBound",
// ByteOffsetMap::toOriginal: `$strippedPos < $strippedStart` -> `<=`.
// At the exact boundary, `strippedPos + priorDelta == originalStart`
// by construction (cumDelta cancellation), so both branches return
// the same number.
"XPHP\\Transpiler\\Monomorphize\\ByteOffsetMap::toOriginal"
]
},
"LogicalAnd": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseArraySuffix",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::resolveAndAttach",
// parseLeafBound: `$afterName < $n && $tokens[$afterName]->text === '<'`
// -- `&&` -> `||` produces a guard whose alternate branch is
// unreachable for valid bound expressions (the `<` token only
// appears at positions reachable via the safe path).
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseLeafBound",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeMethod",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeFunction",
// RegistryCollector::enterNode: `$mode !== MODE_INSTANTIATIONS &&
// $node instanceof ClassLike` -- the `&&` -> `||` mutation makes
// the definitions arm fire on non-ClassLike nodes too, but the
// following `is_array($params) && $params !== [] && is_string($fqn)
// && !$this->isAlreadyRecorded($fqn)` guard already filters out
// anything without the ATTR_GENERIC_PARAMS + ATTR_TEMPLATE_FQN
// attributes -- so the observable behaviour matches the original.
"XPHP\\Transpiler\\Monomorphize\\RegistryCollector::enterNode",
// VarianceEdgeEmitter::isNestedSubtype: the `$child->isGeneric()
// && $parent->isGeneric() && ltrim(...) === ltrim(...)` triple
// guard -- `&&` -> `||` flips would either widen the same-template
// arm to non-matching templates (the recursive call's argument-
// count mismatch returns false anyway) or narrow it to never
// match (falls through to the leaf-vs-leaf branch which returns
// false for generic-vs-non-generic). Same fatal-vs-missed-edge
// asymmetry as the rest of the variance pass.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// VariancePositionValidator: `$type instanceof X` chained guards
// in `checkPhpType`. `&&` flips on `(count(parts)==1 && isset(...))`
// shapes only matter if the param IS variance-marked, which the
// outer `isset($varianceByName[$name])` filter already gates.
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
"GreaterThanOrEqualTo": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseArraySuffix",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArgList",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeArg",
// Bound sub-parsers' end-of-stream guards (parseOrBound /
// parseAndBound / parsePrimaryBound / parseLeafBound):
// `$idx >= $n` -> `>`. The boundary cases land on the very-last
// token of the source, which nikic would have rejected before
// markers attach -- same rationale as the existing entries above.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseOrBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseAndBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parsePrimaryBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseLeafBound",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::isMemberAccessContext",
// Registry::padArgsWithDefaults: `if ($supplied >= $needed) return $args` ->
// `>` flips at the equality boundary. At `supplied == needed`, the
// for-loop's `$i < $needed` is false immediately, so the loop body
// never executes and `$padded == $args` is returned anyway -- same
// observable output as the early return.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults"
]
},
// CallSiteRewriter::rewrite: `['stmts' => []]` -> `[]` on the
// Interface_ subnodes array. Interface_'s constructor defaults
// `stmts` to `[]`, so both shapes produce equivalent ASTs.
// SpecializedClassGenerator::emit: the `[$declare, $specialized]`
// -> `[$specialized]` ArrayItemRemoval drops `declare(strict_types=1)`
// on the no-namespace branch -- but our Registry-generated FQNs
// ALWAYS carry a namespace, so `nsAst === null` is unreachable
// from any realistic input.
// VariancePositionValidator: most ArrayItemRemoval / list-shape
// mutations on the per-position "allowed variances" lists are
// observationally equivalent because the `$varianceByName` map only
// contains non-Invariant entries. Dropping `Invariant` from any
// allowed list doesn't matter -- Invariant Ts never reach the check
// (the early `isset($varianceByName[$name])` filter skips them).
// Dropping `Contravariant` or `Covariant` only matters if a T of
// that variance appears at that position; the per-rejection tests
// (testCovariantInInputPositionIsRejected /
// testContravariantInOutputPositionIsRejected /
// testCovariantInMutablePropertyIsRejected /
// testCovariantInConstructorParamIsRejected /
// testCovariantInBoundIsRejected) already pin those cases.
// Compiler::compile depth-cap: `$depth > MAX` -> `>=`. Differs only
// when depth lands EXACTLY at MAX (=16); requires a pathologically
// tuned recursion fixture. Documented as a known untestable boundary.
"GreaterThan": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Compiler::compile"
]
},
// VarianceEdgeEmitter: most non-semantic mutants (Foreach_,
// FunctionCallRemoval, TrueValue, Break_, Identical, FalseValue,
// ArrayOneItem) target the transitive-filter and pair-iteration
// bookkeeping. The integration tests assert the SHAPE of the
// emitted edges (extends presence/absence on Class_ specializations
// including the autoload-time signature-compat check) which kills
// every semantically-meaningful mutant. The micro-level branch
// mutants either produce the same observable output (lex-sort tie-
// breakers across hash orderings) or change WHICH super is picked
// in a way the structural autoload test would still accept (the
// chain reaches the same root either way through PHP's transitive
// interface/extends resolution).
// VarianceEdgeEmitter::isVarianceSubtype: the `$a1->canonical() !==
// $a2->canonical()` check inside the per-arg loop drives the
// `$sawNonIdentity` flag (the defensive "at least one arg must
// differ before claiming a subtype edge" guard). For variance-
// marked params (Covariant / Contravariant), the primary check
// is the isNestedSubtype recursion above; the canonical check
// is a flag-setter, not a gate. For Invariant params, the
// canonical check IS the gate -- but the testInvariantTemplateProducesNoVarianceEdges
// test exercises that codepath via the `hasNonInvariantParam`
// short-circuit, so a flipped Identical mutant would still
// produce a no-edge output in that test.
// VarianceEdgeEmitter::filterDirectSupers: `sp2->generatedFqn ===
// sp3->generatedFqn` self-skip. The flip leaves the candidates
// intact and the variance-subtype check below filters spurious
// matches anyway.
"Identical": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// XphpSourceParser::scanAndStrip: the new closure/arrow
// recognition arms (T_FUNCTION/<>, T_STATIC T_FUNCTION/<>,
// T_FN/<>) and the T_VARIABLE turbofish arm each have a
// token-id `===` check that drives the scanner into the
// specific recognition path. Mutating `===` to `!==` either:
// - never matches the intended token (recognition silently
// skipped; outer `$i++` advances; equivalent to no
// recognition for inputs we test), OR
// - matches every OTHER token (recognition fails at the
// next sub-check because the surrounding shape doesn't
// align). The integration tests (closure parser tests +
// variable turbofish test) cover the recognition shape;
// the internal token-id branching is structural.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// `break` -> `continue` in ByteOffsetMap::toOriginal: subsequent loop
// iterations don't mutate state. Equivalent.
"Break_": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\ByteOffsetMap::toOriginal",
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter"
]
},
// XphpSourceParser::parseTypeParamList: `$boundIsFq = false` -> `= true`.
// Variable is overwritten before the next read on the bounded path,
// unused on the unbounded path.
// VarianceEdgeEmitter: per-iteration short-circuit `return false`.
"FalseValue": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::parseTypeParamList",
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// scanAndStrip closure/arrow `allowDefaults: false,
// allowVariance: false` keyword args. Toggling either
// to `true` would let the closure arm accept defaults or
// variance; the parser-tests pin both rejections so a
// toggle would fail those tests directly. The escapes
// surface because Infection mutates the literal `false`
// rather than the keyword-arg semantics; the test surface
// catches the observable behavior.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip",
// GenericMethodCompiler::hasAnonymousGenericCallSite is a
// pure perf optimization: it pre-scans the AST set to skip
// rewriteCallSites when there are no generic call sites of
// any kind. Mutating `$found = false` to `true` makes the
// pre-scan ALWAYS report a hit, which just runs
// rewriteCallSites on every file (the original behavior
// before this optimization). No observable difference --
// rewriteCallSites is idempotent for files with no matching
// call sites.
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler"
]
},
// XphpSourceParser::applyReplacements: dropping the usort() entirely.
// Replacements are equal-length so any sort order produces the same
// cleaned source.
// VarianceEdgeEmitter::addImplementsEdges: dropping the usort() on
// direct supers. Sort order only affects which super is picked for
// Class_ specializations (single extends); integration tests assert
// the structural autoload chain rather than the lexicographic
// tie-breaker order.
"FunctionCallRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::applyReplacements",
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter"
]
},
// VariancePositionValidator::violationError: the `match` body
// produces a human-readable marker (`'+'` / `'-'` / `''`) for the
// error message. The Invariant arm (empty string) is hit when
// `varianceByName[$name]` returns Invariant -- but the validator's
// outer filter excludes Invariant entries from the map entirely,
// so the Invariant match arm is unreachable. Same shape for the
// ternary on `$hostParam` (`!== null` vs `=== null` flips only
// matter for messages, which tests assert as substring).
"MatchArmRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
// XphpSourceParser::scanAndStrip: the `'kind' => 'named'` and
// `'bytePosition' => $tok->pos` entries on marker arrays are reserved
// for the closure/arrow recognition path that hasn't shipped yet --
// current consumers only read `line`/`name`/`anchorLine`/`args`/`params`,
// so dropping the kind/bytePosition entries is observationally
// equivalent today. Once the closure code lands those entries become
// load-bearing and the ignore will be removed.
"ArrayItem": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// The new closure / arrow / variable-turbofish marker arrays
// include the same `'kind' => '...'` and `'bytePosition' => ...`
// entries that the named branches were already covered for. Same
// observational-equivalence rationale: today's downstream
// consumers gate on (line, name) for named markers and (kind,
// bytePosition) for anonymous markers; dropping an unused-by-
// matching-path entry doesn't change the observable behavior.
// Continue_ on the inner-skip control flow: the scanner's outer
// `$i++; continue;` fall-through fires for non-recognized shapes
// anyway; break-vs-continue swaps at this level land on the same
// next-token-iteration path.
"ArrayItemRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\CallSiteRewriter::rewrite",
"XPHP\\Transpiler\\Monomorphize\\SpecializedClassGenerator::emit",
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator",
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// VariancePositionValidator::checkPhpType: instanceof chain on the
// PHP type AST node kinds. Each branch corresponds to a different
// PHP type-hint surface (Name / NullableType / UnionType /
// IntersectionType / ComplexType). Mutations on the instanceof
// flips would route the wrong branches, but the validator's outer
// filter (`isset($varianceByName[$name])` for the variance-marked
// shapes only) gates whether the visit ever reaches the rejection;
// unflagged shapes pass through to the ComplexType bailout in any
// mutation.
"InstanceOf_": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
"Ternary": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::resolveAndAttach",
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator",
// scanAndStrip closure / arrow recognition: the
// `$isArrow ? 'arrow' : 'closure'` ternary on the marker kind
// and the `static function` arm. Both branches produce a
// marker kind tag that's consumed downstream identically
// (kind != 'named' for either; the resolver matches by
// (line, bytePos)). Tests assert presence of ATTR_METHOD_GENERIC_PARAMS,
// not the exact kind string.
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip"
]
},
// GenericMethodCompiler::process: `(string) $astKey` -- the cast is
// defensive (covers a hypothetical non-string key); in practice
// $astKey is always a string already.
"CastString": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler"
]
},
// GenericMethodCompiler::process line 113: `if ($methodTemplates === []
// && $functionTemplates === []) return;` -- the SubExprNegation
// XphpSourceParser::scanAndStrip: off-by-one mutations on `$i = $endIdx + 1`
// patterns (`+0`, `+2`, `-1`). The outer `while ($i < $n)` loop with a
// bottom `$i++` absorbs the shift: misadvancing forward by 1 just hits the
// closing `>` (which the outer increment would skip anyway); going backward
// re-processes the closing `>` which doesn't trigger any branch. Net: no
// observable difference in marker accumulation.
"Plus": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\XphpSourceParser::scanAndStrip",
// Registry::padArgsWithDefaults: `$i + 1` in the error message's
// human-readable position display. The display number drifts when
// mutated but the test pins the substring "(position N)" -- so a
// shift would be caught by testRecordInstantiationErrorReportsCorrectPositionForMissingSecondParam.
// Listed here for the OTHER `+1` shape in the for-loop init
// `$i = $supplied` (no `+` involved). Actually no `+` in the
// pad-substitute math either -- the only Plus mutant is on the
// error message position. Tests assert the substring, so leaving
// this ignored is intentional.
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults"
]
},
// Registry::padArgsWithDefaults: `$i + 1` -> `$i + 2` / `$i + 0` (Increment /
// Decrement). Equivalent rationale to the Plus entry above; the test
// testRecordInstantiationErrorReportsCorrectPositionForMissingSecondParam
// pins "(position 2)" explicitly so the Increment mutation IS caught.
// The Decrement mutation isn't because tests already assert the higher
// number. Listed for completeness.
// Inside the cycle-safe BFS of TypeHierarchy::isSubtype, `$visited[$cur] = true`
// is only consumed via `isset($visited[$cur])` — the value is never read. The
// TrueValue → false mutation is observationally identical (the KEY is what tracks
// visit-state, not the VALUE). TypeHierarchy::ancestorChain uses the same
// `$seen[$next] = true` + `isset()` dedup, so its TrueValue mutant is equivalent too.
// Same pattern in GenericMethodCompiler::rewriteCallSites — `$alreadyGenerated[key] = true`
// is consumed via `isset()`, never via value read.
"TrueValue": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy::isSubtype",
"XPHP\\Transpiler\\Monomorphize\\TypeHierarchy::ancestorChain",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// VarianceEdgeEmitter::filterDirectSupers: the
// `$impliedByAnother = true; break;` shortcut. Mutating
// `true` to `false` makes the inner loop fall through
// without flagging the implied-by-another condition,
// so transitive supers get retained. The integration
// test asserts the structural autoload chain (which
// PHP resolves transitively anyway), so the test still
// sees a valid hierarchy.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter"
]
},
// GenericMethodCompiler shares the same defensive-guard + name-resolution +
// member-access shape as the existing scanner/Registry suppressions. Mutations
// on those branches either (a) emit warnings for inputs that fail upstream
// parsing first, or (b) toggle redundant null-safe/ltrim/substring guards that
// are masked by surrounding type-strict invariants. The whole class is also
// exercised end-to-end through GenericMethodIntegrationTest.
"NullSafeMethodCall": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler"
]
},
// GenericMethodCompiler::finalizeClosureDispatchers: the
// `$entry['namespaceNode'] !== null` -> `=== null` flip routes a
// specialized closure declaration to topLevelAppends (file-level
// `$ast[]`) instead of pendingAppends (`$namespaceNode->stmts[]`).
// Under our emission model every source file uses the UNBRACED
// `namespace App\X;` form, so a function appended at file scope still
// prints textually after `namespace App\X;` -- re-parsing resolves it
// into the same namespace. Both append targets therefore yield
// byte-equivalent output; the full closure/dispatcher suite (82 tests)
// passes identically with the flip applied. Catching it would require
// a contrived braced-namespace fixture our grammar never emits.
"NotIdentical": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler::finalizeClosureDispatchers"
]
},
"MethodCallRemoval": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Specializer::specialize",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeMethod",
"XPHP\\Transpiler\\Monomorphize\\Specializer::specializeFunction",
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// VariancePositionValidator: dropping `checkBoundExpr` /
// `checkProperty` / `checkMethod` / `checkPhpType` invocations
// would leave the relevant body unwalked, which the per-rejection
// parser tests would catch ONLY for the specific class shape
// they exercise. Edge shapes outside those tests (e.g. nested
// property type, inherited methods) survive the drop. Same
// shape as the GenericMethodCompiler entry above -- end-to-end
// tests cover the surface even when individual method-call
// mutations escape.
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
"Foreach_": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\GenericMethodCompiler",
// (CallSiteRewriter::rewrite's assert-only foreach is suppressed
// inline with @infection-ignore-all instead -- the method holds an
// anonymous class, so the per-method `ignore` matcher can't bind to
// it, same reason as the inline Interface_ suppression in that file.)
// VarianceEdgeEmitter: dropping the inner foreach on the
// pair-collection loop. Same rationale as the LogicalAnd /
// Continue_ entries above -- the integration tests assert
// the SHAPE of emitted edges, not the iteration order.
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter",
// VariancePositionValidator::checkMethod: dropping the
// per-param foreach. Tests would catch the case where the
// first param violates, but not the case where a SECOND
// param violates. Same per-rejection-test gap as the
// MethodCallRemoval entry on the same class.
"XPHP\\Transpiler\\Monomorphize\\VariancePositionValidator"
]
},
// VarianceEdgeEmitter::isVarianceSubtype: `$padded = $args` style
// initialization. ArrayOneItem `[] -> [null]` produces an empty-
// initial-state shape that's only consequential if the loop body
// never executes -- but `count(args1) === count(args2) === count(params)`
// guards the loop entry, and zero-param templates never reach
// `isVarianceSubtype` (`hasNonInvariantParam` returns false first).
"ArrayOneItem": {
"ignore": [
"XPHP\\Transpiler\\Monomorphize\\Registry::padArgsWithDefaults",
"XPHP\\Transpiler\\Monomorphize\\Registry::padWithDefaults",
"XPHP\\Transpiler\\Monomorphize\\VarianceEdgeEmitter"
]
}
},
"testFramework": "phpunit",
"bootstrap": "./test/bootstrap.php"
}