diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 8616d227c86..13a76337064 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -13,8 +13,8 @@ parameters: path: src/Analyser/AnalyserResultFinalizer.php - - rawMessage: PHPDoc tag @var with type int|string is not subtype of type string. - identifier: varTag.type + rawMessage: PHPDoc tag @var with type int|string is not subtype of native type string. + identifier: varTag.nativeType count: 1 path: src/Analyser/ArgumentsNormalizer.php @@ -1054,8 +1054,8 @@ parameters: path: src/Type/Constant/ConstantStringType.php - - rawMessage: PHPDoc tag @var with type int|string is not subtype of type string. - identifier: varTag.type + rawMessage: PHPDoc tag @var with type int|string is not subtype of native type string. + identifier: varTag.nativeType count: 1 path: src/Type/Constant/ConstantStringType.php diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index 920676948ab..21d9d09c1cb 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -750,9 +750,6 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type } $functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope); - if ($scope->nativeTypesPromoted) { - return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType(); - } if ($functionReflection->getName() === 'call_user_func') { $result = ArgumentsNormalizer::reorderCallUserFuncArguments($expr, $scope); @@ -810,6 +807,10 @@ public function resolveType(MutatingScope $scope, Expr $expr): Type } } + if ($scope->nativeTypesPromoted) { + return ParametersAcceptorSelector::combineAcceptors($functionReflection->getVariants())->getNativeReturnType(); + } + return VoidToNullTypeTransformer::transform($parametersAcceptor->getReturnType(), $expr); } diff --git a/tests/PHPStan/Analyser/Bug14522Test.php b/tests/PHPStan/Analyser/Bug14522Test.php new file mode 100644 index 00000000000..455894159a8 --- /dev/null +++ b/tests/PHPStan/Analyser/Bug14522Test.php @@ -0,0 +1,36 @@ +assertFileAsserts($assertType, $file, ...$args); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/bug-14522.neon', + ]; + } + +} diff --git a/tests/PHPStan/Analyser/bug-14522.neon b/tests/PHPStan/Analyser/bug-14522.neon new file mode 100644 index 00000000000..c551b84f1f6 --- /dev/null +++ b/tests/PHPStan/Analyser/bug-14522.neon @@ -0,0 +1,2 @@ +parameters: + treatPhpDocTypesAsCertain: false diff --git a/tests/PHPStan/Analyser/data/bug-9307.php b/tests/PHPStan/Analyser/data/bug-9307.php index 69158aa7591..0b6ce3becc5 100644 --- a/tests/PHPStan/Analyser/data/bug-9307.php +++ b/tests/PHPStan/Analyser/data/bug-9307.php @@ -31,7 +31,7 @@ public function test(): void } } - assertType('array<*ERROR*>', $objects); // could be array + assertType('array', $objects); $this->acceptObjects($objects); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-13273.php b/tests/PHPStan/Analyser/nsrt/bug-13273.php new file mode 100644 index 00000000000..c9b2497e33b --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13273.php @@ -0,0 +1,19 @@ + + */ +function getBackoffTime(int $retryCount, int $maxBackoff): int +{ + $retryCount = max(0, $retryCount); + assertNativeType('int<0, max>', $retryCount); + $maxBackoff = max(1, $maxBackoff); + assertNativeType('int<1, max>', $maxBackoff); + + $total = 0; + for ($i = 0; $i <= $retryCount; ++$i) { + $total += min(2 ** $i, $maxBackoff); + } + assertType('int<1, max>', $total); + return $total; +} + +/** @param int<-2, 2> $retryCount */ +function maxWithBoundedRange(int $retryCount): void +{ + $result = max(0, $retryCount); + assertType('int<0, 2>', $result); + assertNativeType('int<0, max>', $result); +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-8956.php b/tests/PHPStan/Analyser/nsrt/bug-8956.php index 15ba7b8dfdb..37781951755 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-8956.php +++ b/tests/PHPStan/Analyser/nsrt/bug-8956.php @@ -12,10 +12,10 @@ public function doFoo(): void { foreach (array_chunk(range(0, 10), 60) as $chunk) { assertType('array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}', $chunk); - assertNativeType('array', $chunk); + assertNativeType('array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}', $chunk); foreach ($chunk as $val) { assertType('0|1|2|3|4|5|6|7|8|9|10', $val); - assertNativeType('mixed', $val); + assertNativeType('0|1|2|3|4|5|6|7|8|9|10', $val); } } } @@ -23,7 +23,7 @@ public function doFoo(): void public function doBar(): void { assertType('array{array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}', array_chunk(range(0, 10), 60)); - assertNativeType('list', array_chunk(range(0, 10), 60)); + assertNativeType('array{array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}', array_chunk(range(0, 10), 60)); } } diff --git a/tests/PHPStan/Analyser/nsrt/native-types.php b/tests/PHPStan/Analyser/nsrt/native-types.php index e9b121b8130..8638049b04a 100644 --- a/tests/PHPStan/Analyser/nsrt/native-types.php +++ b/tests/PHPStan/Analyser/nsrt/native-types.php @@ -334,7 +334,7 @@ public function doFoo(): void { $a = array_replace([1, 2, 3], [4, 5, 6]); assertType('array{4, 5, 6}', $a); - assertNativeType('array', $a); + assertNativeType('array{4, 5, 6}', $a); } } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 2c63aec4647..7d6455150e6 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -198,17 +198,14 @@ public function testImpossibleCheckTypeFunctionCall(): void [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', 659, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'someAnother\' will always evaluate to true.', 662, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'unknown\' will always evaluate to false.', 665, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], [ 'Call to function method_exists() with \'CheckTypeFunctionCall\\\\MethodExistsWithTrait\' and \'method\' will always evaluate to true.', @@ -958,8 +955,6 @@ public function testBug4890b(): void public function testBug10502(): void { - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->treatPhpDocTypesAsCertain = true; $this->analyse([__DIR__ . '/data/bug-10502.php'], [ [ @@ -969,7 +964,6 @@ public function testBug10502(): void [ "Call to function is_callable() with array{1: 'count', 0: ArrayObject} will always evaluate to true.", 24, - $tipText, ], ]); } diff --git a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php index 9a057cd5392..ec9eb838f39 100644 --- a/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php @@ -199,7 +199,6 @@ public function testStrictComparison(): void [ 'Strict comparison using === between int<0, 1> and 100 will always evaluate to false.', 622, - $tipText, ], [ 'Strict comparison using === between 100 and \'foo\' will always evaluate to false.', @@ -444,7 +443,6 @@ public function testBug7555(): void [ 'Strict comparison using === between 2 and 2 will always evaluate to true.', 11, - 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.', ], ]); } @@ -493,13 +491,10 @@ public function testBug6181(): void public function testBug2851b(): void { - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; - $this->analyse([__DIR__ . '/data/bug-2851b.php'], [ [ 'Strict comparison using === between 0 and 0 will always evaluate to true.', 21, - $tipText, ], ]); } @@ -570,17 +565,14 @@ public function testBug4242(): void public function testBug3633(): void { - $tipText = 'Because the type is coming from a PHPDoc, you can turn off this check by setting treatPhpDocTypesAsCertain: false in your %configurationFile%.'; $this->analyse([__DIR__ . '/data/bug-3633.php'], [ [ 'Strict comparison using === between class-string<$this(Bug3633\HelloWorld)> and \'Bug3633\\\OtherClass\' will always evaluate to false.', 37, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\HelloWorld\' will always evaluate to true.', 41, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\HelloWorld\' and \'Bug3633\\\OtherClass\' will always evaluate to false.', @@ -589,47 +581,38 @@ public function testBug3633(): void [ 'Strict comparison using === between class-string<$this(Bug3633\OtherClass)> and \'Bug3633\\\HelloWorld\' will always evaluate to false.', 64, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.', 71, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\OtherClass\' and \'Bug3633\\\OtherClass\' will always evaluate to true.', 74, - $tipText, ], [ 'Strict comparison using === between class-string<$this(Bug3633\FinalClass)> and \'Bug3633\\\HelloWorld\' will always evaluate to false.', 93, - $tipText, ], [ 'Strict comparison using === between class-string<$this(Bug3633\FinalClass)> and \'Bug3633\\\OtherClass\' will always evaluate to false.', 96, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.', 102, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\HelloWorld\' will always evaluate to false.', 106, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\OtherClass\' will always evaluate to false.', 109, - $tipText, ], [ 'Strict comparison using !== between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to false.', 112, - $tipText, ], [ 'Strict comparison using === between \'Bug3633\\\FinalClass\' and \'Bug3633\\\FinalClass\' will always evaluate to true.', diff --git a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php index 300484a89b7..97ea2abc806 100644 --- a/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/TernaryOperatorConstantConditionRuleTest.php @@ -97,7 +97,16 @@ public function testReportPhpDoc(): void public function testBug7580(): void { $this->treatPhpDocTypesAsCertain = false; - $this->analyse([__DIR__ . '/data/bug-7580.php'], []); + $this->analyse([__DIR__ . '/data/bug-7580.php'], [ + [ + 'Ternary operator condition is always false.', + 6, + ], + [ + 'Ternary operator condition is always true.', + 9, + ], + ]); } public function testBug3370(): void diff --git a/tests/PHPStan/Rules/Debug/DumpNativeTypeRuleTest.php b/tests/PHPStan/Rules/Debug/DumpNativeTypeRuleTest.php index bfdd614b081..1fe7ba75acc 100644 --- a/tests/PHPStan/Rules/Debug/DumpNativeTypeRuleTest.php +++ b/tests/PHPStan/Rules/Debug/DumpNativeTypeRuleTest.php @@ -42,7 +42,7 @@ public function testBug14508(): void { $this->analyse([__DIR__ . '/data/bug-14508-native.php'], [ [ - 'Dumped type #1: int', + 'Dumped type #1: int<0, 100>', 10, ], [