Bug report
Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history. c53a7b8a5f (phpstan/phpstan-src#5876, Keep subtype-absorbed variables as conditional-expression targets when merging branches) added a second ~+15 percentage-point step on top of the already-regressed tests/bench/data/bug-10538.php bench (nested foreach over a ~100-entry constant array of string array shapes).
Where the previous unset($newVariableTypes[$exprString]) dropped a subtype-absorbed variable entirely, it now stays as a conditional-expression target — so more conditional-expression holders are created and carried through every subsequent branch merge in loop convergence.
When it started
Consistent step between the 2026-06-19 and 2026-06-20 bench runs, across all runner speed classes (mode vs. the committed baseline.xml of 2026-05-27; classes identified via the stable bug-7581.php percentage as a runner-speed proxy):
| runner class |
before Jun 20 |
after Jun 20 |
| fast |
passing (< +10 %) |
+15.5–16.7 % |
| medium |
~+19 % |
+33–36 % |
| slow |
+26–29 % |
+42–46 % |
c53a7b8a5f is the only src/ commit between the last pre-step observation (2026-06-19 13:33, fast-class runner, bug-10538.php passing) and the first post-step observation (2026-06-20 09:03 — the run for c53a7b8a5f itself, medium-class runner, bug-10538.php at +33.74 %). Since the bench's tolerance tier is ±10 % (baseline 2.61 s), the file now fails on every runner class except the very fastest.
Local confirmation
In-process bench on current 2.2.x HEAD (median of 5 steady-state iterations, same setup as PHPStan\Benchmark\RegressionBench, PHP 8.5.5), restoring the pre-phpstan/phpstan-src#5876 unset behavior:
The bug-14462.php row shows the interaction with ad34ca7395 (phpstan/phpstan-src#5848, tracked separately): the extra targets kept by this change are exactly the holders on which phpstan/phpstan-src#5848's extra reverse isSuperTypeOf() guard check then runs, so the two compound — reverting either one alone recovers nothing on that file, reverting both recovers −21 %.
Reproduce
# in phpstan-src, 2.2.x
cp tests/bench/data/bug-10538.php test.php
bin/phpstan analyse -l 8 test.php --debug
# or the bench itself:
tests/vendor/bin/phpbench run --file=tests/bench/storage/baseline.xml --report=my-report
tests/bench/data/bug-10538.php (abbreviated — full file linked below)
<?php
namespace Bug10538;
class SomeClass {
protected const STATES = [
'BW' => 1, 'BY' => 2, 'BE' => 3, 'BB' => 4,
'HB' => 5, 'HH' => 6, 'HE' => 7, 'MV' => 8,
'NI' => 9, 'NW' => 10, 'RP' => 11, 'SL' => 12,
'ST' => 13, 'SN' => 14, 'SH' => 15, 'TH' => 16,
];
protected static function test(): void
{
for ($i = 0; $i < 10; $i++) {
foreach (self::CHANGESET as $stateCode => $changesets) {
$stateId = self::STATES[$stateCode];
foreach ($changesets as $changeset) {
echo sprintf(
'%s %s %s %s',
$changeset['new']['Gemarkung'],
$changeset['old']['Gemeinde'],
$changeset['old']['Gemarkung'],
$stateId
);
}
}
}
}
// CHANGESET: ~100-entry constant array of nested string array shapes
protected const CHANGESET = [ /* … */ ];
}
Code snippet that reproduces the problem
https://github.com/phpstan/phpstan-src/blob/2.2.x/tests/bench/data/bug-10538.php
Expected output
The inference improvement from phpstan/phpstan-src#5876 kept, but without the ~15 % analysis-time cost on constant-array-heavy loop code — e.g. by bounding which subtype-absorbed variables are worth keeping as conditional targets, or making the extra holders cheap for the downstream merge/guard machinery (see the interaction with phpstan/phpstan-src#5848). bug-10538.php back within the ±10 % bench tolerance on typical runners.
Related bench regressions keeping the Test job red since 2026-06-11: #14920 (phpstan-src#5848 — compounds with this change on bug-14462.php), #14918 (type "reasons" commits), #14919 (ExprHandler dispatch refactor).
Bug report
Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history.
c53a7b8a5f(phpstan/phpstan-src#5876, Keep subtype-absorbed variables as conditional-expression targets when merging branches) added a second ~+15 percentage-point step on top of the already-regressedtests/bench/data/bug-10538.phpbench (nestedforeachover a ~100-entry constant array of string array shapes).Where the previous
unset($newVariableTypes[$exprString])dropped a subtype-absorbed variable entirely, it now stays as a conditional-expression target — so more conditional-expression holders are created and carried through every subsequent branch merge in loop convergence.When it started
Consistent step between the 2026-06-19 and 2026-06-20 bench runs, across all runner speed classes (mode vs. the committed
baseline.xmlof 2026-05-27; classes identified via the stablebug-7581.phppercentage as a runner-speed proxy):c53a7b8a5fis the onlysrc/commit between the last pre-step observation (2026-06-19 13:33, fast-class runner,bug-10538.phppassing) and the first post-step observation (2026-06-20 09:03 — the run forc53a7b8a5fitself, medium-class runner,bug-10538.phpat +33.74 %). Since the bench's tolerance tier is ±10 % (baseline 2.61 s), the file now fails on every runner class except the very fastest.Local confirmation
In-process bench on current 2.2.x HEAD (median of 5 steady-state iterations, same setup as
PHPStan\Benchmark\RegressionBench, PHP 8.5.5), restoring the pre-phpstan/phpstan-src#5876unsetbehavior:The
bug-14462.phprow shows the interaction withad34ca7395(phpstan/phpstan-src#5848, tracked separately): the extra targets kept by this change are exactly the holders on which phpstan/phpstan-src#5848's extra reverseisSuperTypeOf()guard check then runs, so the two compound — reverting either one alone recovers nothing on that file, reverting both recovers −21 %.Reproduce
tests/bench/data/bug-10538.php (abbreviated — full file linked below)
Code snippet that reproduces the problem
https://github.com/phpstan/phpstan-src/blob/2.2.x/tests/bench/data/bug-10538.php
Expected output
The inference improvement from phpstan/phpstan-src#5876 kept, but without the ~15 % analysis-time cost on constant-array-heavy loop code — e.g. by bounding which subtype-absorbed variables are worth keeping as conditional targets, or making the extra holders cheap for the downstream merge/guard machinery (see the interaction with phpstan/phpstan-src#5848).
bug-10538.phpback within the ±10 % bench tolerance on typical runners.Related bench regressions keeping the Test job red since 2026-06-11: #14920 (phpstan-src#5848 — compounds with this change on bug-14462.php), #14918 (type "reasons" commits), #14919 (ExprHandler dispatch refactor).