diff --git a/src/Analyser/ExprHandler/StaticCallHandler.php b/src/Analyser/ExprHandler/StaticCallHandler.php index ee0a758d491..2869dcee52f 100644 --- a/src/Analyser/ExprHandler/StaticCallHandler.php +++ b/src/Analyser/ExprHandler/StaticCallHandler.php @@ -144,6 +144,18 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } else { $throwPoints[] = InternalThrowPoint::createImplicit($scope, $expr); } + } elseif ($expr->class instanceof Expr) { + $classType = $scope->getType($expr->class)->getObjectTypeOrClassStringObjectType(); + $methodName = $expr->name->name; + $methodReflection = $scope->getMethodReflection($classType, $methodName); + if ($methodReflection !== null) { + $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( + $scope, + $expr->getArgs(), + $methodReflection->getVariants(), + $methodReflection->getNamedArgumentsVariants(), + ); + } } } else { $nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep()); @@ -202,7 +214,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } if ( - $methodReflection !== null + $expr->class instanceof Name + && $methodReflection !== null && ( ( !$methodReflection->isStatic() @@ -215,7 +228,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex ) { $scope = $scope->invalidateExpression(new Variable('this'), true, $methodReflection->getDeclaringClass()); } elseif ( - $methodReflection !== null + $expr->class instanceof Name + && $methodReflection !== null && $this->rememberPossiblyImpureFunctionValues && $scope->isInClass() && $scope->getClassReflection()->is($methodReflection->getDeclaringClass()->getName()) @@ -230,7 +244,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex } if ( - $methodReflection !== null + $expr->class instanceof Name + && $methodReflection !== null && !$methodReflection->isStatic() && $methodReflection->getName() === '__construct' && $scopeFunction instanceof MethodReflection diff --git a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php index ac9c2d3329d..22f9d9577c0 100644 --- a/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/IfConstantConditionRuleTest.php @@ -237,4 +237,10 @@ public function testBug6822(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-6822.php'], []); } + public function testBug5020(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-5020.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-5020.php b/tests/PHPStan/Rules/Comparison/data/bug-5020.php new file mode 100644 index 00000000000..816269eb90a --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-5020.php @@ -0,0 +1,33 @@ + $transformer + */ +function foo(string $transformer): void +{ + $input = ' asdasda asdasd '; + $error = false; + $output = $transformer::Transform($input, $error); + if ($error) { + + } +} diff --git a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php index 9fc94aa711f..4f3696de3ec 100644 --- a/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallToStaticMethodStatementWithoutSideEffectsRuleTest.php @@ -51,6 +51,10 @@ public function testRulePhp7(): void 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', 12, ], + [ + 'Call to static method DateTimeImmutable::createFromFormat() on a separate line has no effect.', + 13, + ], [ 'Call to method DateTime::format() on a separate line has no effect.', 23, @@ -132,7 +136,20 @@ public function testBug10819(): void public function testDynamicStaticCall(): void { - $this->analyse([__DIR__ . '/data/dynamic-static-call.php'], []); + $this->analyse([__DIR__ . '/data/dynamic-static-call.php'], [ + [ + 'Call to static method DynamicStaticCall\Foo::doFoo() on a separate line has no effect.', + 32, + ], + [ + 'Call to static method DynamicStaticCall\FinalFoo::doFoo() on a separate line has no effect.', + 33, + ], + [ + 'Call to static method DynamicStaticCall\Bar::finalFoo() on a separate line has no effect.', + 34, + ], + ]); } #[RequiresPhp('>= 8.5.0')] diff --git a/tests/PHPStan/Rules/Methods/data/dynamic-static-call.php b/tests/PHPStan/Rules/Methods/data/dynamic-static-call.php index b52e5df44d7..57db55a66ee 100644 --- a/tests/PHPStan/Rules/Methods/data/dynamic-static-call.php +++ b/tests/PHPStan/Rules/Methods/data/dynamic-static-call.php @@ -29,8 +29,8 @@ final static public function finalFoo():int class Baz { function doBaz(Foo $foo, FinalFoo $finalFoo, Bar $bar):void { - $foo::doFoo(); // no error, subclass could override static method with impure impl - $finalFoo::doFoo(); // could be "Call to static method .. on a separate line has no effect", because final class - $bar::finalFoo(); // could be "Call to static method .. on a separate line has no effect", because final method + $foo::doFoo(); + $finalFoo::doFoo(); + $bar::finalFoo(); } } diff --git a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php index 8e172d035a0..d4fd0d07518 100644 --- a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php @@ -219,4 +219,10 @@ public function testBug14511(): void $this->analyse([__DIR__ . '/data/bug-14511.php'], []); } + #[RequiresPhp('>= 8.1.0')] + public function testBug14557(): void + { + $this->analyse([__DIR__ . '/data/bug-14557-function.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 88bb38c5d40..8287809407a 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -389,4 +389,16 @@ public function testBug14511(): void $this->analyse([__DIR__ . '/data/bug-14511-method.php'], []); } + #[RequiresPhp('>= 8.1.0')] + public function testBug14557(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-14557.php'], [ + [ + 'Impure call to method Bug14557\SomeClass::impureStaticMethod() in pure method Bug14557\Foo::impureViaClassString().', + 93, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-14557-function.php b/tests/PHPStan/Rules/Pure/data/bug-14557-function.php new file mode 100644 index 00000000000..f79309e1641 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-14557-function.php @@ -0,0 +1,55 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14557Function; + +enum MyEnum: string +{ + case Foo = 'foo'; + case Bar = 'bar'; +} + +/** + * @param enum-string $enum + * @phpstan-pure + */ +function fromEnumString(string $enum): MyEnum +{ + return $enum::from('foo'); +} + +/** + * @param enum-string $enum + * @phpstan-pure + */ +function tryFromEnumString(string $enum): ?MyEnum +{ + return $enum::tryFrom('foo'); +} + +/** + * @param class-string $enum + * @phpstan-pure + */ +function fromClassString(string $enum): MyEnum +{ + return $enum::from('foo'); +} + +/** + * @param class-string $enum + * @phpstan-pure + */ +function tryFromClassString(string $enum): ?MyEnum +{ + return $enum::tryFrom('foo'); +} + +/** + * @phpstan-pure + */ +function fromEnum(MyEnum $enum): MyEnum +{ + return $enum::from('foo'); +} diff --git a/tests/PHPStan/Rules/Pure/data/bug-14557.php b/tests/PHPStan/Rules/Pure/data/bug-14557.php new file mode 100644 index 00000000000..bcc9d020234 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-14557.php @@ -0,0 +1,96 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14557; + +enum MyEnum: string +{ + case Foo = 'foo'; + case Bar = 'bar'; +} + +class SomeClass +{ + + /** @phpstan-pure */ + public static function pureStaticMethod(): int + { + return 1; + } + + /** @phpstan-impure */ + public static function impureStaticMethod(): int + { + echo 'hello'; + return 1; + } + +} + +class Foo +{ + + /** + * @param enum-string $enum + * @phpstan-pure + */ + public function doFoo(string $enum): MyEnum + { + return $enum::from('foo'); + } + + /** + * @param enum-string $enum + * @phpstan-pure + */ + public function doBar(string $enum): ?MyEnum + { + return $enum::tryFrom('foo'); + } + + /** + * @param class-string $enum + * @phpstan-pure + */ + public function doBaz(string $enum): MyEnum + { + return $enum::from('foo'); + } + + /** + * @param class-string $enum + * @phpstan-pure + */ + public function doLorem(string $enum): ?MyEnum + { + return $enum::tryFrom('foo'); + } + + /** + * @phpstan-pure + */ + public function fromEnum(MyEnum $enum): MyEnum + { + return $enum::from('foo'); + } + + /** + * @param class-string $class + * @phpstan-pure + */ + public function pureViaClassString(string $class): int + { + return $class::pureStaticMethod(); + } + + /** + * @param class-string $class + * @phpstan-pure + */ + public function impureViaClassString(string $class): int + { + return $class::impureStaticMethod(); // error + } + +}