From a5cece4370fccf04814fd2e6d8c3d7c9c9878cef Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 19:34:07 +0100 Subject: [PATCH 01/11] Fix error reported on the wrong line --- src/Analyser/RuleErrorTransformer.php | 20 ++++++++++++- .../Rules/Methods/CallMethodsRuleTest.php | 13 +++++++++ .../PHPStan/Rules/Methods/data/bug-14150.php | 23 +++++++++++++++ .../TypesAssignedToPropertiesRuleTest.php | 16 ++++++++++ .../Rules/Properties/data/bug-14150.php | 29 +++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-14150.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-14150.php diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 12ad504320..1984d33e69 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -14,6 +14,7 @@ use PHPStan\Fixable\PhpPrinter; use PHPStan\Fixable\PhpPrinterIndentationDetectorVisitor; use PHPStan\Fixable\ReplacingNodeVisitor; +use PHPStan\Node\PropertyAssignNode; use PHPStan\Node\VirtualNode; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\FixableNodeRuleError; @@ -55,7 +56,6 @@ public function transform( Node $node, ): Error { - $line = $node->getStartLine(); $canBeIgnored = true; $fileName = $scope->getFileDescription(); $filePath = $scope->getFile(); @@ -75,6 +75,8 @@ public function transform( && $ruleError->getLine() !== -1 ) { $line = $ruleError->getLine(); + } else { + $line = $this->getLineFromNode($node); } if ( $ruleError instanceof FileRuleError @@ -166,4 +168,20 @@ public function transform( ); } + private function getLineFromNode(Node $node): int + { + if ($node instanceof PropertyAssignNode) { + return $this->getLineFromNode($node->getPropertyFetch()); + } + + if ( + $node instanceof Node\Expr\PropertyFetch + || $node instanceof Node\Expr\MethodCall + ) { + return $node->name->getStartLine(); + } + + return $node->getStartLine(); + } + } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 1c56de9b57..ac8e1ad170 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3844,6 +3844,19 @@ public function testBug12875(): void $this->analyse([__DIR__ . '/data/bug-12875.php'], []); } + public function testBug14150(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-14150.php'], [ + [ + 'Call to an undefined method Bug14150Method\HelloWorld::y().', + 21, + ], + ]); + } + #[RequiresPhp('>= 8.1')] public function testBug13805(): void { diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150.php b/tests/PHPStan/Rules/Methods/data/bug-14150.php new file mode 100644 index 0000000000..36964c07c6 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14150.php @@ -0,0 +1,23 @@ +x() + ->y(); + } +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 29c2c32aca..5f3578f946 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -986,6 +986,22 @@ public function testBug13654(): void $this->analyse([__DIR__ . '/data/bug-13654.php'], []); } + public function testBug14150(): void + { + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-14150.php'], [ + [ + 'Property Bug14150Properties\HelloWorld::$x (int) does not accept null.', + 20, + ], + [ + 'Property Bug14150Properties\HelloWorld::$x (int) does not accept null.', + 27, + ], + ]); + } + #[RequiresPhp('>= 8.5')] public function testCloneWith(): void { diff --git a/tests/PHPStan/Rules/Properties/data/bug-14150.php b/tests/PHPStan/Rules/Properties/data/bug-14150.php new file mode 100644 index 0000000000..a71f09f67f --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14150.php @@ -0,0 +1,29 @@ +x = null; + } + + public function test4(): void + { + $this + ->x() + ->x = null; + } +} From c720728d38e024c343c7cf689e891504c1ac54d3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 20:01:45 +0100 Subject: [PATCH 02/11] Refactor --- src/Analyser/RuleErrorTransformer.php | 6 +++--- src/Rules/FunctionCallParametersCheck.php | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 1984d33e69..d2bdcfccf4 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -76,7 +76,7 @@ public function transform( ) { $line = $ruleError->getLine(); } else { - $line = $this->getLineFromNode($node); + $line = self::getLineFromNode($node); } if ( $ruleError instanceof FileRuleError @@ -168,10 +168,10 @@ public function transform( ); } - private function getLineFromNode(Node $node): int + public static function getLineFromNode(Node $node): int { if ($node instanceof PropertyAssignNode) { - return $this->getLineFromNode($node->getPropertyFetch()); + return self::getLineFromNode($node->getPropertyFetch()); } if ( diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 16c0938b06..57b469f9fa 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -5,6 +5,7 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PHPStan\Analyser\MutatingScope; +use PHPStan\Analyser\RuleErrorTransformer; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; @@ -89,11 +90,7 @@ public function check( string $namedArgumentMessage, ): array { - if ($funcCall instanceof Node\Expr\MethodCall || $funcCall instanceof Node\Expr\StaticCall || $funcCall instanceof Node\Expr\FuncCall) { - $funcCallLine = $funcCall->name->getStartLine(); - } else { - $funcCallLine = $funcCall->getStartLine(); - } + $funcCallLine = RuleErrorTransformer::getLineFromNode($funcCall); $functionParametersMinCount = 0; $functionParametersMaxCount = 0; From 42bf0a3f1215f72e88e26889a20bbb7dcb6ddd51 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 20:31:07 +0100 Subject: [PATCH 03/11] Rework --- src/Analyser/RuleErrorTransformer.php | 24 ++++++++----------- src/Rules/FunctionCallParametersCheck.php | 2 +- .../Rules/Properties/data/bug-14150.php | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index d2bdcfccf4..ac93c65c66 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -76,7 +76,7 @@ public function transform( ) { $line = $ruleError->getLine(); } else { - $line = self::getLineFromNode($node); + $line = self::getStartLineFromNode($node); } if ( $ruleError instanceof FileRuleError @@ -168,20 +168,16 @@ public function transform( ); } - public static function getLineFromNode(Node $node): int + public static function getStartLineFromNode(Node $node): int { - if ($node instanceof PropertyAssignNode) { - return self::getLineFromNode($node->getPropertyFetch()); - } - - if ( - $node instanceof Node\Expr\PropertyFetch - || $node instanceof Node\Expr\MethodCall - ) { - return $node->name->getStartLine(); - } - - return $node->getStartLine(); + $line = match (true) { + $node instanceof PropertyAssignNode => self::getStartLineFromNode($node->getPropertyFetch()), + $node instanceof Node\Expr\PropertyFetch, + $node instanceof Node\Expr\MethodCall => $node->name->getStartLine(), + default => -1, + }; + + return $line !== -1 ? $line : $node->getStartLine(); } } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index 57b469f9fa..f85a48967a 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -90,7 +90,7 @@ public function check( string $namedArgumentMessage, ): array { - $funcCallLine = RuleErrorTransformer::getLineFromNode($funcCall); + $funcCallLine = RuleErrorTransformer::getStartLineFromNode($funcCall); $functionParametersMinCount = 0; $functionParametersMaxCount = 0; diff --git a/tests/PHPStan/Rules/Properties/data/bug-14150.php b/tests/PHPStan/Rules/Properties/data/bug-14150.php index a71f09f67f..43fd8ce37c 100644 --- a/tests/PHPStan/Rules/Properties/data/bug-14150.php +++ b/tests/PHPStan/Rules/Properties/data/bug-14150.php @@ -9,7 +9,7 @@ class HelloWorld /** * @return $this */ - public function x(): static + public function x() { return $this; } From 6adc439a4942fc8221a021d34c151e24a67a6e59 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 20:38:59 +0100 Subject: [PATCH 04/11] Fix --- src/Analyser/RuleErrorTransformer.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index ac93c65c66..872aac2e5a 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -170,12 +170,16 @@ public function transform( public static function getStartLineFromNode(Node $node): int { - $line = match (true) { - $node instanceof PropertyAssignNode => self::getStartLineFromNode($node->getPropertyFetch()), - $node instanceof Node\Expr\PropertyFetch, - $node instanceof Node\Expr\MethodCall => $node->name->getStartLine(), - default => -1, - }; + if ( + $node instanceof Node\Expr\PropertyFetch + || $node instanceof Node\Expr\MethodCall + ) { + $line = $node->name->getStartLine(); + } elseif ($node instanceof PropertyAssignNode) { + $line = self::getStartLineFromNode($node->getPropertyFetch()); + } else { + return $node->getStartLine(); + } return $line !== -1 ? $line : $node->getStartLine(); } From c3da32a3a180cf79297b2d4ff8c2371bc39496da Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 22:43:40 +0100 Subject: [PATCH 05/11] Handle more cases --- src/Analyser/RuleErrorTransformer.php | 2 ++ .../Methods/NullsafeMethodCallRuleTest.php | 10 +++++++ .../Rules/Methods/data/bug-14150-nullsafe.php | 23 +++++++++++++++ .../NullsafePropertyFetchRuleTest.php | 14 +++++++++ .../Properties/data/bug-14150-nullsafe.php | 29 +++++++++++++++++++ 5 files changed, 78 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 872aac2e5a..020796d4fe 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -172,7 +172,9 @@ public static function getStartLineFromNode(Node $node): int { if ( $node instanceof Node\Expr\PropertyFetch + || $node instanceof Node\Expr\NullsafePropertyFetch || $node instanceof Node\Expr\MethodCall + || $node instanceof Node\Expr\NullsafeMethodCall ) { $line = $node->name->getStartLine(); } elseif ($node instanceof PropertyAssignNode) { diff --git a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php index 80696dfe6b..a83d5429b8 100644 --- a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php @@ -37,6 +37,16 @@ public function testBug8664(): void $this->analyse([__DIR__ . '/../../Analyser/data/bug-8664.php'], []); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [ + [ + 'Using nullsafe method call on non-nullable type $this(Bug14150NullsafeMethod\HelloWorld). Use -> instead.', + 21, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testBug9293(): void { diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php new file mode 100644 index 0000000000..f5520e6c7a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php @@ -0,0 +1,23 @@ += 8.0 + +namespace Bug14150NullsafeMethod; + +class HelloWorld +{ + public int $x = 5; + + /** + * @return $this + */ + public function x(): static + { + return $this; + } + + public function testUnknownMethod(): void + { + $this + ->x() + ?->y(); + } +} diff --git a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php index 259f973c22..98d40e2e23 100644 --- a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php +++ b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php @@ -55,6 +55,20 @@ public function testBug8517(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8517.php'], []); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [ + [ + 'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.', + 20, + ], + [ + 'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.', + 27, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testBug9105(): void { diff --git a/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php b/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php new file mode 100644 index 0000000000..0b78120183 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php @@ -0,0 +1,29 @@ += 8.0 + +namespace Bug14150NullsafeProperty; + +class HelloWorld +{ + public int $x = 5; + + /** + * @return $this + */ + public function x() + { + return $this; + } + + public function test3(): void + { + $this + ?->x; + } + + public function test4(): void + { + $this + ->x() + ?->x; + } +} From 154a83e3acc63509208f94ed00a1eda511aa6f1e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 18 Feb 2026 23:24:40 +0100 Subject: [PATCH 06/11] More --- src/Analyser/RuleErrorTransformer.php | 2 ++ .../Methods/CallStaticMethodsRuleTest.php | 13 +++++++++++ .../Rules/Methods/data/bug-14150-nullsafe.php | 2 +- .../Rules/Methods/data/bug-14150-static.php | 23 +++++++++++++++++++ .../PHPStan/Rules/Methods/data/bug-14150.php | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Rules/Methods/data/bug-14150-static.php diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 020796d4fe..8da6cd2cc1 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -172,9 +172,11 @@ public static function getStartLineFromNode(Node $node): int { if ( $node instanceof Node\Expr\PropertyFetch + || $node instanceof Node\Expr\StaticPropertyFetch || $node instanceof Node\Expr\NullsafePropertyFetch || $node instanceof Node\Expr\MethodCall || $node instanceof Node\Expr\NullsafeMethodCall + || $node instanceof Node\Expr\StaticCall ) { $line = $node->name->getStartLine(); } elseif ($node instanceof PropertyAssignNode) { diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 9c28da3416..3ecba00c33 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -909,6 +909,19 @@ public function testRestrictedInternalClassNameUsage(): void ]); } + public function testBug14150(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-14150-static.php'], [ + [ + 'Call to an undefined static method Bug14150MethodStatic\HelloWorld::y().', + 21, + ], + ]); + } + public function testBug13556(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php index f5520e6c7a..77b55ee4b4 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php +++ b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php @@ -9,7 +9,7 @@ class HelloWorld /** * @return $this */ - public function x(): static + public function x() { return $this; } diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150-static.php b/tests/PHPStan/Rules/Methods/data/bug-14150-static.php new file mode 100644 index 0000000000..765252fb4a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14150-static.php @@ -0,0 +1,23 @@ + Date: Thu, 19 Feb 2026 11:30:27 +0100 Subject: [PATCH 07/11] Another strategy --- src/Analyser/RuleErrorTransformer.php | 23 +--------- src/Rules/FunctionCallParametersCheck.php | 6 ++- src/Rules/Methods/CallStaticMethodsRule.php | 2 +- src/Rules/Methods/MethodCallCheck.php | 21 +++++++-- src/Rules/Methods/NullsafeMethodCallRule.php | 1 + src/Rules/Methods/StaticMethodCallCheck.php | 42 +++++++++++++---- .../Methods/StaticMethodCallableRule.php | 2 +- .../Operators/InvalidBinaryOperationRule.php | 2 +- src/Rules/Operators/PipeOperatorRule.php | 4 +- .../Properties/AccessPropertiesCheck.php | 26 +++++++++-- .../AccessStaticPropertiesCheck.php | 46 +++++++++++++++---- .../Properties/NullsafePropertyFetchRule.php | 1 + .../ReadOnlyByPhpDocPropertyAssignRule.php | 4 ++ .../Properties/ReadOnlyPropertyAssignRule.php | 4 ++ .../TypesAssignedToPropertiesRule.php | 1 + .../WritingToReadOnlyPropertiesRule.php | 5 +- .../InvalidBinaryOperationRuleTest.php | 10 ++++ .../Rules/Operators/PipeOperatorRuleTest.php | 10 ++++ .../Rules/Operators/data/bug-14150-binary.php | 15 ++++++ .../Rules/Operators/data/bug-14150-pipe.php | 25 ++++++++++ 20 files changed, 196 insertions(+), 54 deletions(-) create mode 100644 tests/PHPStan/Rules/Operators/data/bug-14150-binary.php create mode 100644 tests/PHPStan/Rules/Operators/data/bug-14150-pipe.php diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 8da6cd2cc1..38ccfb0a12 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -56,6 +56,7 @@ public function transform( Node $node, ): Error { + $line = $node->getStartLine(); $canBeIgnored = true; $fileName = $scope->getFileDescription(); $filePath = $scope->getFile(); @@ -75,8 +76,6 @@ public function transform( && $ruleError->getLine() !== -1 ) { $line = $ruleError->getLine(); - } else { - $line = self::getStartLineFromNode($node); } if ( $ruleError instanceof FileRuleError @@ -168,24 +167,4 @@ public function transform( ); } - public static function getStartLineFromNode(Node $node): int - { - if ( - $node instanceof Node\Expr\PropertyFetch - || $node instanceof Node\Expr\StaticPropertyFetch - || $node instanceof Node\Expr\NullsafePropertyFetch - || $node instanceof Node\Expr\MethodCall - || $node instanceof Node\Expr\NullsafeMethodCall - || $node instanceof Node\Expr\StaticCall - ) { - $line = $node->name->getStartLine(); - } elseif ($node instanceof PropertyAssignNode) { - $line = self::getStartLineFromNode($node->getPropertyFetch()); - } else { - return $node->getStartLine(); - } - - return $line !== -1 ? $line : $node->getStartLine(); - } - } diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index f85a48967a..f35c515c47 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -90,7 +90,11 @@ public function check( string $namedArgumentMessage, ): array { - $funcCallLine = RuleErrorTransformer::getStartLineFromNode($funcCall); + if ($funcCall instanceof Node\Expr\MethodCall || $funcCall instanceof Node\Expr\StaticCall || $funcCall instanceof Node\Expr\FuncCall) { + $funcCallLine = $funcCall->name->getStartLine(); + } else { + $funcCallLine = $funcCall->getStartLine(); + } $functionParametersMinCount = 0; $functionParametersMaxCount = 0; diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 55537f84f0..166ad74ace 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -65,7 +65,7 @@ public function processNode(Node $node, Scope $scope): array */ private function processSingleMethodCall(Scope $scope, StaticCall $node, string $methodName): array { - [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class); + [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class, $node->name); if ($method === null) { return $errors; } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index 3be2032165..0a5c11b454 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -72,7 +72,10 @@ public function check( 'Cannot call method %s() on %s.', $methodName, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('method.nonObject')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.nonObject') + ->build(), ], null, ]; @@ -106,7 +109,10 @@ public function check( 'Call to private method %s() of parent class %s.', $methodReflection->getName(), $parentClassReflection->getDisplayName(), - ))->identifier('method.private')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.private') + ->build(), ], $methodReflection, ]; @@ -133,7 +139,10 @@ public function check( 'Call to an undefined method %s::%s().', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $methodName, - ))->identifier('method.notFound')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.notFound') + ->build(), ], null, ]; @@ -150,6 +159,7 @@ public function check( $methodReflection->getName(), $declaringClass->getDisplayName(), )) + ->line($astName->getStartLine()) ->identifier(sprintf('method.%s', $methodReflection->isPrivate() ? 'private' : 'protected')) ->build(); } @@ -161,7 +171,10 @@ public function check( ) { $errors[] = RuleErrorBuilder::message( sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $methodName), - )->identifier('method.nameCase')->build(); + ) + ->line($astName->getStartLine()) + ->identifier('method.nameCase') + ->build(); } return [$errors, $methodReflection]; diff --git a/src/Rules/Methods/NullsafeMethodCallRule.php b/src/Rules/Methods/NullsafeMethodCallRule.php index a6750d58a3..12ac117ce0 100644 --- a/src/Rules/Methods/NullsafeMethodCallRule.php +++ b/src/Rules/Methods/NullsafeMethodCallRule.php @@ -31,6 +31,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf('Using nullsafe method call on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly()))) + ->line($node->name->getStartLine()) ->identifier('nullsafe.neverNull') ->build(), ]; diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index b0a6f057e5..a3f80a6c22 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -4,6 +4,7 @@ use DOMDocument; use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; @@ -61,6 +62,7 @@ public function check( Scope $scope, string $methodName, $class, + Identifier|Expr $astName ): array { $errors = []; @@ -81,7 +83,9 @@ public function check( 'Calling %s::%s() outside of class scope.', $className, $methodName, - ))->identifier(sprintf('outOfClass.%s', $lowercasedClassName))->build(), + )) + ->line($astName->getStartLine()) + ->identifier(sprintf('outOfClass.%s', $lowercasedClassName))->build(), ], null, ]; @@ -95,7 +99,10 @@ public function check( 'Calling %s::%s() outside of class scope.', $className, $methodName, - ))->identifier(sprintf('outOfClass.parent'))->build(), + )) + ->line($astName->getStartLine()) + ->identifier(sprintf('outOfClass.parent')) + ->build(), ], null, ]; @@ -110,7 +117,10 @@ public function check( $scope->getFunctionName(), $methodName, $scope->getClassReflection()->getDisplayName(), - ))->identifier('class.noParent')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('class.noParent') + ->build(), ], null, ]; @@ -132,6 +142,7 @@ public function check( $methodName, $className, )) + ->line($astName->getStartLine()) ->identifier('class.notFound'); if ($this->discoveringSymbolsTip) { @@ -206,7 +217,10 @@ public function check( 'Cannot call static method %s() on %s.', $methodName, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('staticMethod.nonObject')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.nonObject') + ->build(), ]), null, ]; @@ -232,7 +246,10 @@ public function check( 'Call to an undefined static method %s::%s().', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $methodName, - ))->identifier('staticMethod.notFound')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.notFound') + ->build(), ]), null, ]; @@ -265,7 +282,10 @@ public function check( 'Static call to instance method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier('method.staticCall')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.staticCall') + ->build(), ]), $method, ]; @@ -281,6 +301,7 @@ public function check( $method->getName(), $method->getDeclaringClass()->getDisplayName(), )) + ->line($astName->getStartLine()) ->identifier(sprintf('staticMethod.%s', $method->isPrivate() ? 'private' : 'protected')) ->build(), ]); @@ -294,7 +315,9 @@ public function check( $method->isStatic() ? ' static' : '', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier(sprintf( + )) + ->line($astName->getStartLine()) + ->identifier(sprintf( '%s.callToAbstract', $method->isStatic() ? 'staticMethod' : 'method', ))->build(), @@ -317,7 +340,10 @@ public function check( 'Call to %s with incorrect case: %s', $lowercasedMethodName, $methodName, - ))->identifier('staticMethod.nameCase')->build(); + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.nameCase') + ->build(); } return [$errors, $method]; diff --git a/src/Rules/Methods/StaticMethodCallableRule.php b/src/Rules/Methods/StaticMethodCallableRule.php index e1b33d1321..223fb582b1 100644 --- a/src/Rules/Methods/StaticMethodCallableRule.php +++ b/src/Rules/Methods/StaticMethodCallableRule.php @@ -46,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array $methodNameName = $methodName->toString(); - [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass()); + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass(), $node->getName()->getStartLine()); if ($methodReflection === null) { return $errors; } diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index 606da6f43a..a8aaeccad9 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -121,7 +121,7 @@ public function processNode(Node $node, Scope $scope): array $scope->getType($left)->describe(VerbosityLevel::value()), $scope->getType($right)->describe(VerbosityLevel::value()), )) - ->line($left->getStartLine()) + ->line($right->getStartLine()) ->identifier(sprintf('%s.invalid', $identifier)) ->build(), ]; diff --git a/src/Rules/Operators/PipeOperatorRule.php b/src/Rules/Operators/PipeOperatorRule.php index 07cfedeb7e..908141b8fd 100644 --- a/src/Rules/Operators/PipeOperatorRule.php +++ b/src/Rules/Operators/PipeOperatorRule.php @@ -54,7 +54,9 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message(sprintf( 'Parameter #1%s of callable on the right side of pipe operator is passed by reference.', $parameter->getName() !== '' ? ' $' . $parameter->getName() : '', - ))->identifier('pipe.byRef') + )) + ->line($node->right->getStartLine()) + ->identifier('pipe.byRef') ->build(), ]; } diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index e9af849df6..b1e0514e8e 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -78,6 +78,7 @@ public function check(PropertyFetch $node, Scope $scope, bool $write): array $originalNameType = $scope->getType($node->name); $className = $scope->getType($node->var)->describe(VerbosityLevel::typeOnly()); $errors[] = RuleErrorBuilder::message(sprintf('Property name for %s must be a string, but %s was given.', $className, $originalNameType->describe(VerbosityLevel::precise()))) + ->line($node->name->getStartLine()) ->identifier('property.nameNotString') ->build(); } @@ -124,7 +125,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Cannot access property $%s on %s.', $name, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('property.nonObject')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.nonObject') + ->build(), ]; } @@ -183,7 +187,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Access to private property $%s of parent class %s.', $name, $parentClassReflection->getDisplayName(), - ))->identifier('property.private')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.private') + ->build(), ]; } @@ -208,7 +215,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Non-static access to static property %s::$%s.', $type->getStaticProperty($name, $scope)->getDeclaringClass()->getDisplayName(), $name, - ))->identifier('staticProperty.nonStaticAccess')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.nonStaticAccess') + ->build(), ]; } @@ -255,7 +265,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $propertyReflection->isPrivate() ? 'private' : 'protected', $type->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected')) + ->build(), ]; } @@ -265,7 +278,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $propertyReflection->isPrivateSet() ? 'private(set)' : 'protected(set)', $type->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier(sprintf('assign.property%s', $propertyReflection->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('assign.property%s', $propertyReflection->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet')) + ->build(), ]; } diff --git a/src/Rules/Properties/AccessStaticPropertiesCheck.php b/src/Rules/Properties/AccessStaticPropertiesCheck.php index 487453f428..7ad441ffed 100644 --- a/src/Rules/Properties/AccessStaticPropertiesCheck.php +++ b/src/Rules/Properties/AccessStaticPropertiesCheck.php @@ -83,7 +83,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Accessing %s::$%s outside of class scope.', $class, $name, - ))->identifier(sprintf('outOfClass.%s', $lowercasedClass))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('outOfClass.%s', $lowercasedClass)) + ->build(), ]; } $classType = $scope->resolveTypeByName($node->class); @@ -94,7 +97,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Accessing %s::$%s outside of class scope.', $class, $name, - ))->identifier('outOfClass.parent')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('outOfClass.parent') + ->build(), ]; } if ($scope->getClassReflection()->getParentClass() === null) { @@ -105,7 +111,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $scope->getFunctionName(), $name, $scope->getClassReflection()->getDisplayName(), - ))->identifier('class.noParent')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('class.noParent') + ->build(), ]; } @@ -121,6 +130,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $name, $class, )) + ->line($node->name->getStartLine()) ->identifier('class.notFound'); if ($this->discoveringSymbolsTip) { @@ -179,7 +189,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Cannot access static property $%s on %s.', $name, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('staticProperty.nonObject')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.nonObject') + ->build(), ]); } @@ -212,7 +225,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Access to private static property $%s of parent class %s.', $name, $parentClassReflection->getDisplayName(), - ))->identifier('staticProperty.private')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.private') + ->build(), ]; } @@ -233,7 +249,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Static access to instance property %s::$%s.', $classType->getInstanceProperty($name, $scope)->getDeclaringClass()->getDisplayName(), $name, - ))->identifier('property.staticAccess')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.staticAccess') + ->build(), ]); } @@ -242,7 +261,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Access to an undefined static property %s::$%s.', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier('staticProperty.notFound')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.notFound') + ->build(), ]); } @@ -266,7 +288,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $property->isPrivate() ? 'private' : 'protected', $name, $property->getDeclaringClass()->getDisplayName(), - ))->identifier(sprintf('staticProperty.%s', $property->isPrivate() ? 'private' : 'protected'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('staticProperty.%s', $property->isPrivate() ? 'private' : 'protected')) + ->build(), ]); } @@ -276,7 +301,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $property->isPrivateSet() ? 'private(set)' : 'protected(set)', $name, $property->getDeclaringClass()->getDisplayName(), - ))->identifier(sprintf('assign.staticProperty%s', $property->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('assign.staticProperty%s', $property->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet')) + ->build(), ]); } diff --git a/src/Rules/Properties/NullsafePropertyFetchRule.php b/src/Rules/Properties/NullsafePropertyFetchRule.php index c8718cddde..c1d2233736 100644 --- a/src/Rules/Properties/NullsafePropertyFetchRule.php +++ b/src/Rules/Properties/NullsafePropertyFetchRule.php @@ -39,6 +39,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly()))) + ->line($node->name->getStartLine()) ->identifier('nullsafe.neverNull') ->build(), ]; diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index 7c6f3d7159..e61a9a2426 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -82,6 +82,7 @@ public function processNode(Node $node, Scope $scope): array if (!$scope->isInClass()) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignOutOfClass') ->build(); continue; @@ -90,6 +91,7 @@ public function processNode(Node $node, Scope $scope): array $scopeClassReflection = $scope->getClassReflection(); if ($scopeClassReflection->getName() !== $declaringClass->getName()) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignOutOfClass') ->build(); continue; @@ -106,6 +108,7 @@ public function processNode(Node $node, Scope $scope): array ) { if (TypeUtils::findThisType($scope->getType($propertyFetch->var)) === null) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is not assigned on $this.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignNotOnThis') ->build(); } @@ -126,6 +129,7 @@ public function processNode(Node $node, Scope $scope): array } $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignNotInConstructor') ->build(); } diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php index 796f900868..987e000256 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php @@ -69,6 +69,7 @@ public function processNode(Node $node, Scope $scope): array if (!$scope->isInClass()) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignOutOfClass') ->build(); continue; @@ -77,6 +78,7 @@ public function processNode(Node $node, Scope $scope): array $scopeClassReflection = $scope->getClassReflection(); if ($scopeClassReflection->getName() !== $declaringClass->getName()) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignOutOfClass') ->build(); continue; @@ -93,6 +95,7 @@ public function processNode(Node $node, Scope $scope): array ) { if (TypeUtils::findThisType($scope->getType($propertyFetch->var)) === null) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is not assigned on $this.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignNotOnThis') ->build(); } @@ -109,6 +112,7 @@ public function processNode(Node $node, Scope $scope): array } $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignNotInConstructor') ->build(); } diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index 472ce3568e..6e464bdd1d 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -99,6 +99,7 @@ private function processSingleProperty( $propertyType->describe($verbosityLevel), $assignedValueType->describe($verbosityLevel), )) + ->line($assignedExpr->getStartLine()) ->identifier('assign.propertyType') ->acceptsReasonsTip($accepts->reasons) ->build(), diff --git a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php index 8ac259be31..00617f1398 100644 --- a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php +++ b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php @@ -61,7 +61,10 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message(sprintf( '%s is not writable.', $propertyDescription, - ))->identifier('assign.propertyReadOnly')->build(), + )) + ->line($propertyFetch->name->getStartLine()) + ->identifier('assign.propertyReadOnly') + ->build(), ]; } diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index fb1b119745..0fdd7d2641 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -605,6 +605,16 @@ public function testBug10440(): void ]); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-binary.php'], [ + [ + 'Binary operation "+" between 3 and \'e\' results in an error.', + 12, + ], + ]); + } + public function testBenevolentUnion(): void { $this->analyse([__DIR__ . '/data/binary-op-benevolent-union.php'], [ diff --git a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php index cf01a546f1..5efff4c68e 100644 --- a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php +++ b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php @@ -35,4 +35,14 @@ public function testRule(): void ]); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-pipe.php'], [ + [ + 'Parameter #1 $s of callable on the right side of pipe operator is passed by reference.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php b/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php new file mode 100644 index 0000000000..5489920fa6 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php @@ -0,0 +1,15 @@ += 8.5 + +namespace Bug14150PipeOperator; + +class Foo +{ + + /** + * @param callable(string): string $cb + */ + public function doFoo(callable $cb): void + { + $a = 'hello'; + $a |> $cb + |> $cb + |> $cb + |> self::doBar(...); + } + + public static function doBar(string &$s): void + { + + } + +} From 13fdcd9513f493d64a5cdb046c915aed8fc0ea28 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 19 Feb 2026 11:42:11 +0100 Subject: [PATCH 08/11] Fix --- src/Analyser/RuleErrorTransformer.php | 1 - src/Rules/FunctionCallParametersCheck.php | 1 - src/Rules/Methods/StaticMethodCallCheck.php | 8 ++++---- src/Rules/Methods/StaticMethodCallableRule.php | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Analyser/RuleErrorTransformer.php b/src/Analyser/RuleErrorTransformer.php index 38ccfb0a12..12ad504320 100644 --- a/src/Analyser/RuleErrorTransformer.php +++ b/src/Analyser/RuleErrorTransformer.php @@ -14,7 +14,6 @@ use PHPStan\Fixable\PhpPrinter; use PHPStan\Fixable\PhpPrinterIndentationDetectorVisitor; use PHPStan\Fixable\ReplacingNodeVisitor; -use PHPStan\Node\PropertyAssignNode; use PHPStan\Node\VirtualNode; use PHPStan\Rules\FileRuleError; use PHPStan\Rules\FixableNodeRuleError; diff --git a/src/Rules/FunctionCallParametersCheck.php b/src/Rules/FunctionCallParametersCheck.php index f35c515c47..16c0938b06 100644 --- a/src/Rules/FunctionCallParametersCheck.php +++ b/src/Rules/FunctionCallParametersCheck.php @@ -5,7 +5,6 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PHPStan\Analyser\MutatingScope; -use PHPStan\Analyser\RuleErrorTransformer; use PHPStan\Analyser\Scope; use PHPStan\DependencyInjection\AutowiredParameter; use PHPStan\DependencyInjection\AutowiredService; diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index a3f80a6c22..aaabe3d1e7 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -62,7 +62,7 @@ public function check( Scope $scope, string $methodName, $class, - Identifier|Expr $astName + Identifier|Expr $astName, ): array { $errors = []; @@ -318,9 +318,9 @@ public function check( )) ->line($astName->getStartLine()) ->identifier(sprintf( - '%s.callToAbstract', - $method->isStatic() ? 'staticMethod' : 'method', - ))->build(), + '%s.callToAbstract', + $method->isStatic() ? 'staticMethod' : 'method', + ))->build(), ], $method, ]; diff --git a/src/Rules/Methods/StaticMethodCallableRule.php b/src/Rules/Methods/StaticMethodCallableRule.php index 223fb582b1..5f10168c19 100644 --- a/src/Rules/Methods/StaticMethodCallableRule.php +++ b/src/Rules/Methods/StaticMethodCallableRule.php @@ -46,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array $methodNameName = $methodName->toString(); - [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass(), $node->getName()->getStartLine()); + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass(), $node->getName()); if ($methodReflection === null) { return $errors; } From 23963bec9e80097425e6f5e9b7ccc0bef6036771 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 19 Feb 2026 11:44:56 +0100 Subject: [PATCH 09/11] Revert for binary --- .../Operators/InvalidBinaryOperationRule.php | 2 +- .../Operators/InvalidBinaryOperationRuleTest.php | 10 ---------- .../Rules/Operators/data/bug-14150-binary.php | 15 --------------- 3 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 tests/PHPStan/Rules/Operators/data/bug-14150-binary.php diff --git a/src/Rules/Operators/InvalidBinaryOperationRule.php b/src/Rules/Operators/InvalidBinaryOperationRule.php index a8aaeccad9..606da6f43a 100644 --- a/src/Rules/Operators/InvalidBinaryOperationRule.php +++ b/src/Rules/Operators/InvalidBinaryOperationRule.php @@ -121,7 +121,7 @@ public function processNode(Node $node, Scope $scope): array $scope->getType($left)->describe(VerbosityLevel::value()), $scope->getType($right)->describe(VerbosityLevel::value()), )) - ->line($right->getStartLine()) + ->line($left->getStartLine()) ->identifier(sprintf('%s.invalid', $identifier)) ->build(), ]; diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index 0fdd7d2641..fb1b119745 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -605,16 +605,6 @@ public function testBug10440(): void ]); } - public function testBug14150(): void - { - $this->analyse([__DIR__ . '/data/bug-14150-binary.php'], [ - [ - 'Binary operation "+" between 3 and \'e\' results in an error.', - 12, - ], - ]); - } - public function testBenevolentUnion(): void { $this->analyse([__DIR__ . '/data/binary-op-benevolent-union.php'], [ diff --git a/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php b/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php deleted file mode 100644 index 5489920fa6..0000000000 --- a/tests/PHPStan/Rules/Operators/data/bug-14150-binary.php +++ /dev/null @@ -1,15 +0,0 @@ - Date: Thu, 19 Feb 2026 11:52:34 +0100 Subject: [PATCH 10/11] More --- src/Rules/Methods/CallPrivateMethodThroughStaticRule.php | 5 ++++- .../Properties/AccessPrivatePropertyThroughStaticRule.php | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php index 05cdafe663..f28b0b182c 100644 --- a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php +++ b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php @@ -57,7 +57,10 @@ public function processNode(Node $node, Scope $scope): array 'Unsafe call to private method %s::%s() through static::.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier('staticClassAccess.privateMethod')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticClassAccess.privateMethod') + ->build(), ]; } diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php index 44773b9275..6a0b1318b6 100644 --- a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php +++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php @@ -56,7 +56,10 @@ public function processNode(Node $node, Scope $scope): array 'Unsafe access to private property %s::$%s through static::.', $property->getDeclaringClass()->getDisplayName(), $propertyName, - ))->identifier('staticClassAccess.privateProperty')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticClassAccess.privateProperty') + ->build(), ]; } From 37eeadedcc67700c6feb3543362ff4e68697cc4c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 19 Feb 2026 12:21:48 +0100 Subject: [PATCH 11/11] Fix --- tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php index 5efff4c68e..a1873163d6 100644 --- a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php +++ b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php @@ -35,6 +35,7 @@ public function testRule(): void ]); } + #[RequiresPhp('>= 8.5')] public function testBug14150(): void { $this->analyse([__DIR__ . '/data/bug-14150-pipe.php'], [