Bug report
Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history: analysing deep !== chains got ~+27–30 percentage points slower on 2026-06-09, affecting two bench files:
tests/bench/data/bug-14207-and.php — 90 × 'X' !== $tag_name && … in one expression (currently +53–70 % over the committed baseline of Mo 262 ms, fails the ±25 % tolerance on all but the fastest runner class)
tests/bench/data/and-chain-truthy-blowup.php — if with 100 × $x !== "val_n" && … (currently +46–65 % over Mo ≈ 179 ms)
When it started
The regression window is exactly the TypeSpecifier→ExprHandler dispatch batch, merged between the 15:52 and 15:54 bench runs on 2026-06-09:
bb18f722eb Dispatch specifyTypesInCondition through ExprHandler::specifyTypes
9ae05a4ded Move single-consumer TypeSpecifier helpers into their handlers
3a70970e7a Extract ConditionalExpressionHolderHelper from TypeSpecifier
c128b84527 Extract EqualityTypeSpecifyingHelper from TypeSpecifier
Evidence from the Test job (mode vs. the committed baseline.xml of 2026-05-27):
| bench |
15:52 run (5997afc9e9) |
15:54 run (c128b84527) |
| bug-14207-and.php |
+8.68 % |
+39.38 % |
| and-chain-truthy-blowup.php |
+10.93 % |
+37.87 % |
| bug-10538.php (control) |
+11.83 % |
+12.02 % |
The control row shows this is not runner noise: a 3-second array-shape bench on the same two runners stayed flat while both !==-chain benches jumped ~+28 pp. Both files have failed on every non-fastest-class runner since (the 2026-06-10 green run was a fastest-class runner, which masks these two files — that's why the job only went permanently red a day later when phpstan/phpstan-src#5848 landed on top).
Independent from the June 11/June 20 MutatingScope regressions
Local in-process A/B on current 2.2.x HEAD (median of 5 steady-state iterations, PHP 8.5.5): reverting the MutatingScope changes from phpstan/phpstan-src#5848 and phpstan/phpstan-src#5876 — which recovers −19…−21 % on bug-7581.php / bug-10538.php / bug-14462.php — moves these two files not at all:
So their entire regression lives in the dispatch batch above. Likely the same root cause as the deep BooleanOr resolveType() slowdown for which 035f580764 added the or-chain-resolve-type.php bench.
Reproduce
# in phpstan-src, 2.2.x
cp tests/bench/data/bug-14207-and.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/and-chain-truthy-blowup.php (abbreviated)
<?php declare(strict_types = 1);
namespace BenchAndChainTruthyBlowup;
/**
* Regression test for O(N²) in deep BooleanAnd chains.
* Without the flattening optimization, each level recursed through
* specifyTypesInCondition and filterByTruthyValue, creating O(N²) scope operations.
* Slow at the original BOOLEAN_EXPRESSION_MAX_PROCESS_DEPTH = 4.
*/
function test(string $x): void {
if ($x !== "val_1" && $x !== "val_2" && /* … */ && $x !== "val_100") {
echo $x;
}
}
tests/bench/data/bug-14207-and.php (abbreviated)
<?php declare(strict_types = 1);
namespace Bug14207And;
use function PHPStan\Testing\assertType;
class HelloWorld
{
public static function is_not_special(string $tag_name): bool {
$x = (
'ADDRESS' !== $tag_name &&
'APPLET' !== $tag_name &&
// … 86 more !== comparisons …
'a9' !== $tag_name
);
assertType('bool', $x);
if ($x) {
assertType('string', $tag_name);
}
return $x;
}
}
Code snippet that reproduces the problem
https://github.com/phpstan/phpstan-src/blob/2.2.x/tests/bench/data/bug-14207-and.php
Expected output
Analysis time of both benches back near the committed CI baseline (Mo 262 ms / Mo ≈ 179 ms on GitHub runners, within the ±25 % tolerance) with no change in inferred types — moving specifyTypesInCondition dispatch into ExprHandlers is a refactor and should be performance-neutral on deep boolean chains.
Related bench regressions keeping the Test job red since 2026-06-11: #14918 (type "reasons" commits, same day), #14920 (phpstan-src#5848 guard check), #14921 (phpstan-src#5876 conditional targets).
Bug report
Performance regression on the 2.2.x dev branch, found by auditing the bench.yml Test job history: analysing deep
!==chains got ~+27–30 percentage points slower on 2026-06-09, affecting two bench files:tests/bench/data/bug-14207-and.php— 90 ×'X' !== $tag_name && …in one expression (currently +53–70 % over the committed baseline ofMo 262 ms, fails the ±25 % tolerance on all but the fastest runner class)tests/bench/data/and-chain-truthy-blowup.php—ifwith 100 ×$x !== "val_n" && …(currently +46–65 % overMo ≈ 179 ms)When it started
The regression window is exactly the TypeSpecifier→ExprHandler dispatch batch, merged between the 15:52 and 15:54 bench runs on 2026-06-09:
bb18f722ebDispatch specifyTypesInCondition through ExprHandler::specifyTypes9ae05a4dedMove single-consumer TypeSpecifier helpers into their handlers3a70970e7aExtract ConditionalExpressionHolderHelper from TypeSpecifierc128b84527Extract EqualityTypeSpecifyingHelper from TypeSpecifierEvidence from the Test job (mode vs. the committed
baseline.xmlof 2026-05-27):5997afc9e9)c128b84527)The control row shows this is not runner noise: a 3-second array-shape bench on the same two runners stayed flat while both
!==-chain benches jumped ~+28 pp. Both files have failed on every non-fastest-class runner since (the 2026-06-10 green run was a fastest-class runner, which masks these two files — that's why the job only went permanently red a day later when phpstan/phpstan-src#5848 landed on top).Independent from the June 11/June 20 MutatingScope regressions
Local in-process A/B on current 2.2.x HEAD (median of 5 steady-state iterations, PHP 8.5.5): reverting the
MutatingScopechanges from phpstan/phpstan-src#5848 and phpstan/phpstan-src#5876 — which recovers −19…−21 % onbug-7581.php/bug-10538.php/bug-14462.php— moves these two files not at all:So their entire regression lives in the dispatch batch above. Likely the same root cause as the deep
BooleanOrresolveType()slowdown for which035f580764added theor-chain-resolve-type.phpbench.Reproduce
tests/bench/data/and-chain-truthy-blowup.php (abbreviated)
tests/bench/data/bug-14207-and.php (abbreviated)
Code snippet that reproduces the problem
https://github.com/phpstan/phpstan-src/blob/2.2.x/tests/bench/data/bug-14207-and.php
Expected output
Analysis time of both benches back near the committed CI baseline (
Mo 262 ms/Mo ≈ 179 mson GitHub runners, within the ±25 % tolerance) with no change in inferred types — movingspecifyTypesInConditiondispatch into ExprHandlers is a refactor and should be performance-neutral on deep boolean chains.Related bench regressions keeping the Test job red since 2026-06-11: #14918 (type "reasons" commits, same day), #14920 (phpstan-src#5848 guard check), #14921 (phpstan-src#5876 conditional targets).