diff --git a/src/Rules/RuleLevelHelper.php b/src/Rules/RuleLevelHelper.php index 43f57d7223..cba600b029 100644 --- a/src/Rules/RuleLevelHelper.php +++ b/src/Rules/RuleLevelHelper.php @@ -6,7 +6,6 @@ use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\BenevolentUnionType; -use PHPStan\Type\CompoundType; use PHPStan\Type\ErrorType; use PHPStan\Type\Generic\TemplateMixedType; use PHPStan\Type\MixedType; @@ -98,7 +97,7 @@ public function accepts(Type $acceptingType, Type $acceptedType, bool $strictTyp } $accepts = $acceptingType->accepts($acceptedType, $strictTypes); - if (!$accepts->yes() && $acceptingType instanceof UnionType && !$acceptedType instanceof CompoundType) { + if (!$accepts->yes() && $acceptingType instanceof UnionType) { foreach ($acceptingType->getTypes() as $innerType) { if (self::accepts($innerType, $acceptedType, $strictTypes)) { return true; diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index af0a1ece0d..292d219563 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -13,16 +13,19 @@ class ReturnTypeRuleTest extends RuleTestCase { - private bool $checkExplicitMixed = false; + private bool $checkNullables; + + private bool $checkExplicitMixed; protected function getRule(): Rule { - return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), true, false, true, $this->checkExplicitMixed, false))); + return new ReturnTypeRule(new FunctionReturnTypeCheck(new RuleLevelHelper($this->createReflectionProvider(), $this->checkNullables, false, true, $this->checkExplicitMixed, false))); } public function testReturnTypeRule(): void { require_once __DIR__ . '/data/returnTypes.php'; + $this->checkNullables = true; $this->checkExplicitMixed = false; $this->analyse([__DIR__ . '/data/returnTypes.php'], [ [ @@ -71,6 +74,7 @@ public function testReturnTypeRule(): void public function testReturnTypeRulePhp70(): void { $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/returnTypes-7.0.php'], [ [ 'Function ReturnTypes\Php70\returnInteger() should return int but empty return statement found.', @@ -82,6 +86,7 @@ public function testReturnTypeRulePhp70(): void public function testIsGenerator(): void { $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/is-generator.php'], []); } @@ -89,6 +94,7 @@ public function testBug2568(): void { require_once __DIR__ . '/data/bug-2568.php'; $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-2568.php'], []); } @@ -96,6 +102,7 @@ public function testBug2723(): void { require_once __DIR__ . '/data/bug-2723.php'; $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-2723.php'], [ [ 'Function Bug2723\baz() should return Bug2723\Bar> but returns Bug2723\BarOfFoo.', @@ -107,36 +114,42 @@ public function testBug2723(): void public function testBug5706(): void { $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-5706.php'], []); } public function testBug5844(): void { $this->checkExplicitMixed = false; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-5844.php'], []); } public function testBug7218(): void { $this->checkExplicitMixed = true; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-7218.php'], []); } public function testBug5751(): void { $this->checkExplicitMixed = true; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-5751.php'], []); } public function testBug3931(): void { $this->checkExplicitMixed = true; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-3931.php'], []); } public function testBug3801(): void { $this->checkExplicitMixed = true; + $this->checkNullables = true; $this->analyse([__DIR__ . '/data/bug-3801.php'], [ [ 'Function Bug3801\do_foo() should return array{bool, null}|array{null, bool} but returns array{false, true}.', @@ -149,4 +162,23 @@ public function testBug3801(): void ]); } + public function testListWithNullablesChecked(): void + { + $this->checkExplicitMixed = false; + $this->checkNullables = true; + $this->analyse([__DIR__ . '/data/return-list-nullables.php'], [ + [ + 'Function ReturnListNullables\doFoo() should return array|null but returns array.', + 16, + ], + ]); + } + + public function testListWithNullablesUnchecked(): void + { + $this->checkExplicitMixed = false; + $this->checkNullables = false; + $this->analyse([__DIR__ . '/data/return-list-nullables.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/return-list-nullables.php b/tests/PHPStan/Rules/Functions/data/return-list-nullables.php new file mode 100644 index 0000000000..ae6762061f --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/return-list-nullables.php @@ -0,0 +1,17 @@ + $x + * @return array|null + */ +function doFoo(array $x): ?array +{ + $list = []; + foreach ($x as $v) { + $list[] = $v; + } + + return $list; +}