From 3814348ce763526a9ff18f4ad663eecc8a4b1f76 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 09:19:19 +0100 Subject: [PATCH 1/6] Fix slow analysis on return with a big OR condition --- src/Analyser/SpecifiedTypes.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index ccb2c5e8a36..69cae95bf51 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -3,13 +3,16 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; +use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\UnionType; use function array_key_exists; use function array_merge; final class SpecifiedTypes { + private const MAX_SURE_NOT_TYPES = 8; private bool $overwrite = false; @@ -179,6 +182,10 @@ public function unionWith(SpecifiedTypes $other): self continue; } + if ($type instanceof UnionType && count($type->getTypes()) >= self::MAX_SURE_NOT_TYPES) { + continue; + } + $sureNotTypeUnion[$exprString] = [ $exprNode, TypeCombinator::union($type, $other->sureNotTypes[$exprString][1]), From 90ccd5e3ce76cb40de0a80d3b8ad04c691e9de82 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 09:21:11 +0100 Subject: [PATCH 2/6] regression test --- .../Analyser/AnalyserIntegrationTest.php | 6 + tests/PHPStan/Analyser/data/bug-14207.php | 151 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-14207.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 4e7036c6862..f9dfbbc5203 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1632,6 +1632,12 @@ public function testBug13980(): void $this->assertNoErrors($errors); } + public function testBug14207(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-14207.php'); + $this->assertNoErrors($errors); + } + public function testBug13945(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-13945.php'); diff --git a/tests/PHPStan/Analyser/data/bug-14207.php b/tests/PHPStan/Analyser/data/bug-14207.php new file mode 100644 index 00000000000..63b718a3d08 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-14207.php @@ -0,0 +1,151 @@ +namespace + ? strtoupper( $tag_name->node_name ) + : "{$tag_name->namespace} {$tag_name->node_name}"; + } + + assertType('non-falsy-string|uppercase-string', $tag_name); + + $x = ( + 'ADDRESS' === $tag_name || + 'APPLET' === $tag_name || + 'AREA' === $tag_name || + 'ARTICLE' === $tag_name || + 'ASIDE' === $tag_name || + 'BASE' === $tag_name || + 'BASEFONT' === $tag_name || + 'BGSOUND' === $tag_name || + 'BLOCKQUOTE' === $tag_name || + 'BODY' === $tag_name || + 'BR' === $tag_name || + 'BUTTON' === $tag_name || + 'CAPTION' === $tag_name || + 'CENTER' === $tag_name || + 'COL' === $tag_name || + 'COLGROUP' === $tag_name || + 'DD' === $tag_name || + 'DETAILS' === $tag_name || + 'DIR' === $tag_name || + 'DIV' === $tag_name || + 'DL' === $tag_name || + 'DT' === $tag_name || + 'EMBED' === $tag_name || + 'FIELDSET' === $tag_name || + 'FIGCAPTION' === $tag_name || + 'FIGURE' === $tag_name || + 'FOOTER' === $tag_name || + 'FORM' === $tag_name || + 'FRAME' === $tag_name || + 'FRAMESET' === $tag_name || + 'H1' === $tag_name || + 'H2' === $tag_name || + 'H3' === $tag_name || + 'H4' === $tag_name || + 'H5' === $tag_name || + 'H6' === $tag_name || + 'HEAD' === $tag_name || + 'HEADER' === $tag_name || + 'HGROUP' === $tag_name || + 'HR' === $tag_name || + 'HTML' === $tag_name || + 'IFRAME' === $tag_name || + 'IMG' === $tag_name || + 'INPUT' === $tag_name || + 'KEYGEN' === $tag_name || + 'LI' === $tag_name || + 'LINK' === $tag_name || + 'LISTING' === $tag_name || + 'MAIN' === $tag_name || + 'MARQUEE' === $tag_name || + 'MENU' === $tag_name || + 'META' === $tag_name || + 'NAV' === $tag_name || + 'NOEMBED' === $tag_name || + 'NOFRAMES' === $tag_name || + 'NOSCRIPT' === $tag_name || + 'OBJECT' === $tag_name || + 'OL' === $tag_name || + 'P' === $tag_name || + 'PARAM' === $tag_name || + 'PLAINTEXT' === $tag_name || + 'PRE' === $tag_name || + 'SCRIPT' === $tag_name || + 'SEARCH' === $tag_name || + 'SECTION' === $tag_name || + 'SELECT' === $tag_name || + 'SOURCE' === $tag_name || + 'STYLE' === $tag_name || + 'SUMMARY' === $tag_name || + 'TABLE' === $tag_name || + 'TBODY' === $tag_name || + 'TD' === $tag_name || + 'TEMPLATE' === $tag_name || + 'TEXTAREA' === $tag_name || + 'TFOOT' === $tag_name || + 'TH' === $tag_name || + 'THEAD' === $tag_name || + 'TITLE' === $tag_name || + 'TR' === $tag_name || + 'TRACK' === $tag_name || + 'UL' === $tag_name || + 'WBR' === $tag_name || + 'XMP' === $tag_name || + + // MathML. + 'math MI' === $tag_name || + 'math MO' === $tag_name || + 'math MN' === $tag_name || + 'math MS' === $tag_name || + 'math MTEXT' === $tag_name || + 'math ANNOTATION-XML' === $tag_name || + + // SVG. + 'svg DESC' === $tag_name || + 'svg FOREIGNOBJECT' === $tag_name || + 'svg TITLE' === $tag_name || + + // some random stuff + 'a1' === $tag_name || + 'a2' === $tag_name || + 'a3' === $tag_name || + 'a4' === $tag_name || + 'a5' === $tag_name || + 'a6' === $tag_name || + 'a7' === $tag_name || + 'a8' === $tag_name || + 'a9' === $tag_name + ); + + assertType('bool', $x); + assertType('non-falsy-string|uppercase-string', $tag_name); + + return $x; + } +} From 1cf5dadcb7d5615a27262ca73aa32ff263213a30 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 09:31:24 +0100 Subject: [PATCH 3/6] cs --- src/Analyser/SpecifiedTypes.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index 69cae95bf51..58378aa4059 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -3,15 +3,16 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; -use PHPStan\Type\GeneralizePrecision; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\UnionType; use function array_key_exists; use function array_merge; +use function count; final class SpecifiedTypes { + private const MAX_SURE_NOT_TYPES = 8; private bool $overwrite = false; From df9536f47c57d5b5cdd68dc86c726865ecae271e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 09:36:20 +0100 Subject: [PATCH 4/6] more asserts --- tests/PHPStan/Analyser/data/bug-14207.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/bug-14207.php b/tests/PHPStan/Analyser/data/bug-14207.php index 63b718a3d08..9579792b07b 100644 --- a/tests/PHPStan/Analyser/data/bug-14207.php +++ b/tests/PHPStan/Analyser/data/bug-14207.php @@ -31,7 +31,7 @@ public static function is_special( $tag_name ): bool { : "{$tag_name->namespace} {$tag_name->node_name}"; } - assertType('non-falsy-string|uppercase-string', $tag_name); + assertType('non-falsy-string|uppercase-string', $tag_name); // should be string $x = ( 'ADDRESS' === $tag_name || @@ -144,7 +144,9 @@ public static function is_special( $tag_name ): bool { ); assertType('bool', $x); - assertType('non-falsy-string|uppercase-string', $tag_name); + if ($x) { + assertType("'a1'|'a2'|'a3'|'a4'|'a5'|'a6'|'a7'|'a8'|'a9'|'ADDRESS'|'APPLET'|'AREA'|'ARTICLE'|'ASIDE'|'BASE'|'BASEFONT'|'BGSOUND'|'BLOCKQUOTE'|'BODY'|'BR'|'BUTTON'|'CAPTION'|'CENTER'|'COL'|'COLGROUP'|'DD'|'DETAILS'|'DIR'|'DIV'|'DL'|'DT'|'EMBED'|'FIELDSET'|'FIGCAPTION'|'FIGURE'|'FOOTER'|'FORM'|'FRAME'|'FRAMESET'|'H1'|'H2'|'H3'|'H4'|'H5'|'H6'|'HEAD'|'HEADER'|'HGROUP'|'HR'|'HTML'|'IFRAME'|'IMG'|'INPUT'|'KEYGEN'|'LI'|'LINK'|'LISTING'|'MAIN'|'MARQUEE'|'math ANNOTATION-XML'|'math MI'|'math MN'|'math MO'|'math MS'|'math MTEXT'|'MENU'|'META'|'NAV'|'NOEMBED'|'NOFRAMES'|'NOSCRIPT'|'OBJECT'|'OL'|'P'|'PARAM'|'PLAINTEXT'|'PRE'|'SCRIPT'|'SEARCH'|'SECTION'|'SELECT'|'SOURCE'|'STYLE'|'SUMMARY'|'svg DESC'|'svg FOREIGNOBJECT'|'svg TITLE'|'TABLE'|'TBODY'|'TD'|'TEMPLATE'|'TEXTAREA'|'TFOOT'|'TH'|'THEAD'|'TITLE'|'TR'|'TRACK'|'UL'|'WBR'|'XMP'", $tag_name); + } return $x; } From 2301b9e9f44e885afd83d1b67774b1b50836a783 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 09:56:53 +0100 Subject: [PATCH 5/6] drop wrong comment --- tests/PHPStan/Analyser/data/bug-14207.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/bug-14207.php b/tests/PHPStan/Analyser/data/bug-14207.php index 9579792b07b..a378443b790 100644 --- a/tests/PHPStan/Analyser/data/bug-14207.php +++ b/tests/PHPStan/Analyser/data/bug-14207.php @@ -31,7 +31,7 @@ public static function is_special( $tag_name ): bool { : "{$tag_name->namespace} {$tag_name->node_name}"; } - assertType('non-falsy-string|uppercase-string', $tag_name); // should be string + assertType('non-falsy-string|uppercase-string', $tag_name); $x = ( 'ADDRESS' === $tag_name || From a16f84c7886ab21c236584c2b5fe4f5d400adaa4 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 27 Feb 2026 14:42:46 +0100 Subject: [PATCH 6/6] Update SpecifiedTypes.php --- src/Analyser/SpecifiedTypes.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Analyser/SpecifiedTypes.php b/src/Analyser/SpecifiedTypes.php index 58378aa4059..cbaa5180107 100644 --- a/src/Analyser/SpecifiedTypes.php +++ b/src/Analyser/SpecifiedTypes.php @@ -13,7 +13,7 @@ final class SpecifiedTypes { - private const MAX_SURE_NOT_TYPES = 8; + private const MAX_ADDED_TYPES = 8; private bool $overwrite = false; @@ -172,6 +172,10 @@ public function unionWith(SpecifiedTypes $other): self continue; } + if ($type instanceof UnionType && count($type->getTypes()) >= self::MAX_ADDED_TYPES) { + continue; + } + $sureTypeUnion[$exprString] = [ $exprNode, TypeCombinator::intersect($type, $other->sureTypes[$exprString][1]), @@ -183,7 +187,7 @@ public function unionWith(SpecifiedTypes $other): self continue; } - if ($type instanceof UnionType && count($type->getTypes()) >= self::MAX_SURE_NOT_TYPES) { + if ($type instanceof UnionType && count($type->getTypes()) >= self::MAX_ADDED_TYPES) { continue; }