From a897d3a4a0bc1cc8a385eab34da399a75969a538 Mon Sep 17 00:00:00 2001 From: Matthias Noback Date: Thu, 10 Mar 2022 10:30:49 +0100 Subject: [PATCH 1/5] Allow null as a return value when resolving return types --- src/Analyser/MutatingScope.php | 38 +++++++++++++++++-- .../DynamicFunctionReturnTypeExtension.php | 2 +- src/Type/DynamicMethodReturnTypeExtension.php | 2 +- ...DynamicStaticMethodReturnTypeExtension.php | 2 +- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7bdd515031..ffe61015bc 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2660,7 +2660,14 @@ private function resolveType(Expr $node): Type continue; } - return $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall($functionReflection, $node, $this); + $resolvedType = $dynamicFunctionReturnTypeExtension->getTypeFromFunctionCall( + $functionReflection, + $node, + $this, + ); + if ($resolvedType !== null) { + return $resolvedType; + } } return ParametersAcceptorSelector::selectFromArgs( @@ -5659,7 +5666,16 @@ private function exactInstantiation(New_ $node, string $className): ?Type continue; } - $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($constructorMethod, $methodCall, $this); + $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( + $constructorMethod, + $methodCall, + $this, + ); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; } if (count($resolvedTypes) > 0) { @@ -5835,7 +5851,12 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, continue; } - $resolvedTypes[] = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $methodCall, $this); + $resolvedType = $dynamicMethodReturnTypeExtension->getTypeFromMethodCall($methodReflection, $methodCall, $this); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; } } else { foreach ($this->dynamicReturnTypeExtensionRegistry->getDynamicStaticMethodReturnTypeExtensionsForClass($className) as $dynamicStaticMethodReturnTypeExtension) { @@ -5843,7 +5864,16 @@ private function methodCallReturnType(Type $typeWithMethod, string $methodName, continue; } - $resolvedTypes[] = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall($methodReflection, $methodCall, $this); + $resolvedType = $dynamicStaticMethodReturnTypeExtension->getTypeFromStaticMethodCall( + $methodReflection, + $methodCall, + $this, + ); + if ($resolvedType === null) { + continue; + } + + $resolvedTypes[] = $resolvedType; } } } diff --git a/src/Type/DynamicFunctionReturnTypeExtension.php b/src/Type/DynamicFunctionReturnTypeExtension.php index ac4750ce53..92941735a3 100644 --- a/src/Type/DynamicFunctionReturnTypeExtension.php +++ b/src/Type/DynamicFunctionReturnTypeExtension.php @@ -12,6 +12,6 @@ interface DynamicFunctionReturnTypeExtension public function isFunctionSupported(FunctionReflection $functionReflection): bool; - public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type; + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type; } diff --git a/src/Type/DynamicMethodReturnTypeExtension.php b/src/Type/DynamicMethodReturnTypeExtension.php index 58635acca7..35f5b505ca 100644 --- a/src/Type/DynamicMethodReturnTypeExtension.php +++ b/src/Type/DynamicMethodReturnTypeExtension.php @@ -14,6 +14,6 @@ public function getClass(): string; public function isMethodSupported(MethodReflection $methodReflection): bool; - public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type; + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type; } diff --git a/src/Type/DynamicStaticMethodReturnTypeExtension.php b/src/Type/DynamicStaticMethodReturnTypeExtension.php index e2de530f9f..94560039a6 100644 --- a/src/Type/DynamicStaticMethodReturnTypeExtension.php +++ b/src/Type/DynamicStaticMethodReturnTypeExtension.php @@ -14,6 +14,6 @@ public function getClass(): string; public function isStaticMethodSupported(MethodReflection $methodReflection): bool; - public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type; + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type; } From c0c91756c03fbd9d50c2e84805fce69b45139ae4 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 10 Mar 2022 10:49:37 +0100 Subject: [PATCH 2/5] Open 1.5-dev --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/compiler-tests.yml | 2 +- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- composer.json | 2 +- composer.lock | 112 +++++++++---------- 8 files changed, 63 insertions(+), 63 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index c0bdcb9480..9c33485175 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: backward-compatibility: diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index 65e1c2e58d..784e22c7ed 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: compiler-tests: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 136ef4969e..1ef9d753dc 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -13,7 +13,7 @@ on: - 'compiler/**' env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: result-cache-e2e-tests: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6c7afe5f5a..e81abed510 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ on: - "master" env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: lint: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 543254e867..6d7cdde793 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -13,7 +13,7 @@ on: - 'compiler/**' env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: static-analysis: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f9f0e8b07f..70316b7fb8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ on: - 'compiler/**' env: - COMPOSER_ROOT_VERSION: "1.4.x-dev" + COMPOSER_ROOT_VERSION: "1.5.x-dev" jobs: tests: diff --git a/composer.json b/composer.json index f9943dc0ee..e4e2452107 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" }, "patcher": { "search": "patches" diff --git a/composer.lock b/composer.lock index 3497da7b42..2398628d1a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ede0704e050d946882afd2613bdbb349", + "content-hash": "1d2be9000b8b91802f4b6d667fa6267d", "packages": [ { "name": "clue/block-react", @@ -2907,12 +2907,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "React\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2962,12 +2962,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "React\\Promise\\Stream\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "React\\Promise\\Stream\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3045,12 +3045,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "React\\Promise\\Timer\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "React\\Promise\\Timer\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3301,12 +3301,12 @@ } }, "autoload": { - "psr-4": { - "RingCentral\\Psr7\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "RingCentral\\Psr7\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3594,12 +3594,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3673,12 +3673,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3754,12 +3754,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -3841,12 +3841,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3918,12 +3918,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3994,12 +3994,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -4073,12 +4073,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php74\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Php74\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4153,12 +4153,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -4386,12 +4386,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -4670,12 +4670,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, "files": [ "src/DeepCopy/deep_copy.php" - ] + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5799,11 +5799,11 @@ } }, "autoload": { - "classmap": [ - "src/" - ], "files": [ "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", From 84d2dccb69dd19f5441e5d34b439e1de824937c6 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Thu, 10 Mar 2022 16:57:44 +0100 Subject: [PATCH 3/5] Output elapsed time per file in debug mode --- src/Command/AnalyseApplication.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Command/AnalyseApplication.php b/src/Command/AnalyseApplication.php index 8e175c7725..73aab3de3e 100644 --- a/src/Command/AnalyseApplication.php +++ b/src/Command/AnalyseApplication.php @@ -7,11 +7,13 @@ use PHPStan\Analyser\ResultCache\ResultCacheManagerFactory; use PHPStan\Internal\BytesHelper; use PHPStan\PhpDoc\StubValidator; +use PHPStan\ShouldNotHappenException; use Symfony\Component\Console\Input\InputInterface; use function array_merge; use function count; use function is_string; use function memory_get_peak_usage; +use function microtime; use function sprintf; class AnalyseApplication @@ -148,15 +150,21 @@ private function runAnalyser( $errorOutput->getStyle()->progressAdvance($step); }; } else { - $preFileCallback = static function (string $file) use ($stdOutput): void { + $startTime = null; + $preFileCallback = static function (string $file) use ($stdOutput, &$startTime): void { $stdOutput->writeLineFormatted($file); + $startTime = microtime(true); }; $postFileCallback = null; if ($stdOutput->isDebug()) { $previousMemory = memory_get_peak_usage(true); - $postFileCallback = static function () use ($stdOutput, &$previousMemory): void { + $postFileCallback = static function () use ($stdOutput, &$previousMemory, &$startTime): void { + if ($startTime === null) { + throw new ShouldNotHappenException(); + } $currentTotalMemory = memory_get_peak_usage(true); - $stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory))); + $elapsedTime = microtime(true) - $startTime; + $stdOutput->writeLineFormatted(sprintf('--- consumed %s, total %s, took %.2f s', BytesHelper::bytes($currentTotalMemory - $previousMemory), BytesHelper::bytes($currentTotalMemory), $elapsedTime)); $previousMemory = $currentTotalMemory; }; } From 3abb905c2b79d9ff9bd5486c457a2c0162a86713 Mon Sep 17 00:00:00 2001 From: Yohta Kimura Date: Fri, 11 Mar 2022 21:53:42 +0900 Subject: [PATCH 4/5] add failing test case --- .../CatchWithUnthrownExceptionRuleTest.php | 22 ++++++++++++ .../Rules/Exceptions/data/bug-6791.php | 34 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/PHPStan/Rules/Exceptions/data/bug-6791.php diff --git a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php index 97540e5a4b..38efef3f77 100644 --- a/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php @@ -267,4 +267,26 @@ public function testBug6256(): void ]); } + public function testBug6791(): void + { + if (PHP_VERSION_ID < 70400) { + self::markTestSkipped('Test requires PHP 7.4.'); + } + + $this->analyse([__DIR__ . '/data/bug-6791.php'], [ + [ + 'Dead catch - TypeError is never thrown in the try block.', + 17, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 29, + ], + [ + 'Dead catch - TypeError is never thrown in the try block.', + 33, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-6791.php b/tests/PHPStan/Rules/Exceptions/data/bug-6791.php new file mode 100644 index 0000000000..84a2eacfcd --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-6791.php @@ -0,0 +1,34 @@ += 7.4 + +namespace Bug6791; + +class Foo { + /** @var int[] */ + public array $intArray; + /** @var \Ds\Set */ + public \Ds\Set $set; + /** @var int[] */ + public $array; +} + +$foo = new Foo(); +try { + $foo->intArray = ["a"]; +} catch (\TypeError $e) {} + +try { + $foo->set = ["a"]; +} catch (\TypeError $e) {} + +try { + $foo->set = new \Ds\Set; +} catch (\TypeError $e) {} + +try { + $foo->array = ["a"]; +} catch (\TypeError $e) {} + +try { + $foo->array = "non-array"; +} catch (\TypeError $e) {} + From f991c88834141abe685841c19de2e243ad694161 Mon Sep 17 00:00:00 2001 From: Yohta Kimura Date: Fri, 11 Mar 2022 22:02:22 +0900 Subject: [PATCH 5/5] check property assign `TypeError` throw point by native type --- src/Analyser/NodeScopeResolver.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ba3be2994d..e72ffe3a90 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3325,7 +3325,11 @@ static function (): void { if ($propertyReflection->canChangeTypeAfterAssignment()) { $scope = $scope->assignExpression($var, $assignedExprType); } - if (!$propertyReflection->getWritableType()->isSuperTypeOf($assignedExprType)->yes()) { + $declaringClass = $propertyReflection->getDeclaringClass(); + if ( + $declaringClass->hasNativeProperty($propertyName) + && !$declaringClass->getNativeProperty($propertyName)->getNativeType()->accepts($assignedExprType, true)->yes() + ) { $throwPoints[] = ThrowPoint::createExplicit($scope, new ObjectType(TypeError::class), $assignedExpr, false); } } else {