diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index de3c4179b22..94510cd3a66 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3216,9 +3216,34 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self if (array_key_exists($conditionalExprString, $conditions)) { continue; } + + // Pass 1: Prefer exact matches + foreach ($conditionalExpressions as $conditionalExpression) { + foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { + if ( + !array_key_exists($holderExprString, $specifiedExpressions) + || !$conditionalTypeHolder->equals($specifiedExpressions[$holderExprString]) + ) { + continue 2; + } + } + + $conditions[$conditionalExprString][] = $conditionalExpression; + $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder(); + } + + if (array_key_exists($conditionalExprString, $conditions)) { + continue; + } + + // Pass 2: Supertype match. Only runs when Pass 1 found no exact match for this expression. foreach ($conditionalExpressions as $conditionalExpression) { foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { - if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) { + if ( + !array_key_exists($holderExprString, $specifiedExpressions) + || !$conditionalTypeHolder->getCertainty()->equals($specifiedExpressions[$holderExprString]->getCertainty()) + || !$conditionalTypeHolder->getType()->isSuperTypeOf($specifiedExpressions[$holderExprString]->getType())->yes() + ) { continue 2; } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10055.php b/tests/PHPStan/Analyser/nsrt/bug-10055.php new file mode 100644 index 00000000000..2464880ffcc --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10055.php @@ -0,0 +1,32 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug10055; + +use function PHPStan\Testing\assertType; + +/** + * @param 'value1'|'value2'|'value3' $param1 + * @param ($param1 is 'value3' ? bool : int) $param2 + */ +function test(string $param1, int|bool $param2): void +{ + match ($param1) { + 'value1' => assertType('int', $param2), + 'value2' => assertType('int', $param2), + 'value3' => assertType('bool', $param2), + }; +} + +function testScopeMerging(mixed $foo): void +{ + $a = 0; + if (\is_string($foo) || \is_int($foo)) { + $a = 1; + } + + if (\is_int($foo)) { + assertType('1', $a); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-10422.php b/tests/PHPStan/Analyser/nsrt/bug-10422.php new file mode 100644 index 00000000000..5a161575cc6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10422.php @@ -0,0 +1,28 @@ +something()) { + $error = 'another'; + } + if ($error) { + die('Done'); + } + assertType('Bug10422\TestClass', $test); + $test->test(); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-13591.php b/tests/PHPStan/Analyser/nsrt/bug-13591.php new file mode 100644 index 00000000000..4bdc227cd53 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13591.php @@ -0,0 +1,24 @@ + 1) { + echo implode(',', $a); + } elseif (count($a) === 1) { + assertType('string', current($a)); + echo trim(current($a)); + } +} + +/** @param string[] $a */ +function bar(array $a): void +{ + $count = count($a); + if ($count > 1) { + echo implode(',', $a); + } elseif ($count === 1) { + assertType('string', current($a)); + echo trim(current($a)); + } +} + +/** @param string[] $a */ +function qux(array $a): void +{ + switch (count($a)) { + case 0: + break; + case 1: + assertType('string', current($a)); + echo trim(current($a)); + break; + default: + echo implode(',', $a); + break; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-5051.php b/tests/PHPStan/Analyser/nsrt/bug-5051.php index 6c3e80dce11..94ffc4711c8 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-5051.php +++ b/tests/PHPStan/Analyser/nsrt/bug-5051.php @@ -60,27 +60,27 @@ public function testWithBooleans($data): void assertType('bool', $update); } else { assertType('1|2', $data); - assertType('bool', $update); + assertType('false', $update); } if ($data === 1) { - assertType('bool', $update); - assertType('bool', $foo); + assertType('false', $update); + assertType('false', $foo); } else { assertType('bool', $update); assertType('bool', $foo); } if ($data === 2) { - assertType('bool', $update); - assertType('bool', $foo); + assertType('false', $update); + assertType('false', $foo); } else { assertType('bool', $update); assertType('bool', $foo); } if ($data === 3) { - assertType('bool', $update); + assertType('false', $update); assertType('true', $foo); } else { assertType('bool', $update); @@ -88,7 +88,7 @@ public function testWithBooleans($data): void } if ($data === 1 || $data === 2) { - assertType('bool', $update); + assertType('false', $update); assertType('false', $foo); } else { assertType('bool', $update); diff --git a/tests/PHPStan/Analyser/nsrt/bug-6663.php b/tests/PHPStan/Analyser/nsrt/bug-6663.php new file mode 100644 index 00000000000..228a70b821a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-6663.php @@ -0,0 +1,24 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug6663; + +use function PHPStan\Testing\assertType; + +class X {} +class Y {} + +function test(mixed $xy, mixed $ab): void +{ + if ($xy instanceof X || $ab instanceof X) { + if ($xy instanceof Y) { + assertType('Bug6663\Y', $xy); + assertType('Bug6663\X', $ab); + } + if ($ab instanceof Y) { + assertType('Bug6663\X', $xy); + assertType('Bug6663\Y', $ab); + } + } +} diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 7def992a257..94ba55317c9 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -1277,4 +1277,11 @@ public function testBug14308(): void $this->analyse([__DIR__ . '/data/bug-14308.php'], []); } + public function testBug11218(): void + { + $this->reportPossiblyNonexistentConstantArrayOffset = true; + + $this->analyse([__DIR__ . '/data/bug-11218.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Arrays/data/bug-11218.php b/tests/PHPStan/Rules/Arrays/data/bug-11218.php new file mode 100644 index 00000000000..e2062abdd66 --- /dev/null +++ b/tests/PHPStan/Rules/Arrays/data/bug-11218.php @@ -0,0 +1,18 @@ +analyse([__DIR__ . '/../../Analyser/nsrt/bug-13591.php'], []); + } + + public function testBug4090(): void + { + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-4090.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 7aca3e79a0b..597b4154b57 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -4001,4 +4001,12 @@ public function testBug11978(): void ]); } + public function testBug10422(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10422.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index b6161df2333..207070c7627 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1499,4 +1499,22 @@ public function testBug14117(): void ]); } + public function testBug12597(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-12597.php'], []); + } + + public function testBug11218(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-11218.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-11218.php b/tests/PHPStan/Rules/Variables/data/bug-11218.php new file mode 100644 index 00000000000..aadd9d0cbcf --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-11218.php @@ -0,0 +1,16 @@ +message($message); + } + } + + public function message(string $message): void {} +}