diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a43ba35dda..a941e3799b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3217,13 +3217,36 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self continue; } foreach ($conditionalExpressions as $conditionalExpression) { + $subtypeMatch = false; foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { - if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) { + if (!array_key_exists($holderExprString, $specifiedExpressions)) { continue 2; } + $specifiedHolder = $specifiedExpressions[$holderExprString]; + if ($specifiedHolder->equals($conditionalTypeHolder)) { + continue; + } + + $guardType = $conditionalTypeHolder->getType(); + $specifiedType = $specifiedHolder->getType(); + if ( + $conditionalExpression->getTypeHolder()->getCertainty()->yes() + && $specifiedHolder->getCertainty()->yes() + && $conditionalTypeHolder->getCertainty()->yes() + && $guardType->isOffsetAccessible()->no() + && $guardType->isSuperTypeOf($specifiedType)->yes() + ) { + $subtypeMatch = true; + continue; + } + continue 2; } $conditions[$conditionalExprString][] = $conditionalExpression; + if ($subtypeMatch) { + continue; + } + $specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder(); } } diff --git a/tests/PHPStan/Analyser/nsrt/bug-10422.php b/tests/PHPStan/Analyser/nsrt/bug-10422.php new file mode 100644 index 0000000000..3b910a253b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-10422.php @@ -0,0 +1,42 @@ +something()) { + $error = 'another'; + } + if ($error) { + die('Done'); + } + assertType(Foo::class, $test); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-11218.php b/tests/PHPStan/Analyser/nsrt/bug-11218.php new file mode 100644 index 0000000000..c538164edd --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11218.php @@ -0,0 +1,23 @@ + 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 6c3e80dce1..94ffc4711c 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/pr-5379.php b/tests/PHPStan/Analyser/nsrt/pr-5379.php new file mode 100644 index 0000000000..e349626134 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pr-5379.php @@ -0,0 +1,70 @@ + $aggregation + */ + private function parseAggregation(array $aggregation) + { + $type = $aggregation['type'] ?? null; + if (!\is_string($type) || empty($type) || is_numeric($type)) { + return null; + } + + if (empty($aggregation['field']) && $type !== 'filter') { + return null; + } + + $field = ''; + if ($type !== 'filter') { + $field = self::buildFieldName(); + } + + assertType('non-falsy-string', $type); + } + + private static function buildFieldName(): string + { + return 'field'; + } +} + +class AggregationParser2 +{ + private function parseAggregation(string $aggregation, string $type) + { + if (empty($aggregation[1]) && $type !== 'filter') { + return null; + } + assertType('string', $type); + + if ($type !== 'filter') { + assertType('string', $type); + } + + assertType('string', $type); + } + + private function parseAggregation2(ArrayAccess $aggregation, string $type) + { + if (empty($aggregation['foo']) && $type !== 'filter') { + return null; + } + assertType('string', $type); + + if ($type !== 'filter') { + assertType('string', $type); + } + + assertType('string', $type); + } +} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index b6161df233..21e0a6dd74 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1476,6 +1476,25 @@ public function testBug14227(): void $this->analyse([__DIR__ . '/data/bug-14227.php'], []); } + public function testBug12597(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-12597.php'], []); + } + + #[RequiresPhp('>= 8.0')] + public function testBug12597NonFinite(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = false; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-12597-non-finite.php'], []); + } + public function testBug14117(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-12597-non-finite.php b/tests/PHPStan/Rules/Variables/data/bug-12597-non-finite.php new file mode 100644 index 0000000000..3b42da95f0 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12597-non-finite.php @@ -0,0 +1,51 @@ +message($message); + } + } + + public function message(string $message): void {} +} + +class Foo {} +class Bar {} + +class HelloWorld2 +{ + public function test(mixed $type): void + { + if (is_int($type) || is_object($type)) { + $message = 'Hello!'; + } + + if (is_int($type)) { + $this->message($message); + } + } + + public function test2(mixed $type): void + { + if ($type instanceof Foo || $type instanceof Bar) { + $message = 'Hello!'; + } + + if ($type instanceof Foo) { + $this->message($message); + } + } + + public function message(string $message): void + { + } +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-12597.php b/tests/PHPStan/Rules/Variables/data/bug-12597.php new file mode 100644 index 0000000000..2cfdf45974 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12597.php @@ -0,0 +1,22 @@ +message($message); + } + } + + public function message(string $message): void {} +}