44
55use PhpParser \Node ;
66use PHPStan \Analyser \Scope ;
7+ use PHPStan \DependencyInjection \AutowiredParameter ;
78use PHPStan \DependencyInjection \RegisteredRule ;
9+ use PHPStan \DependencyInjection \Type \OperatorTypeSpecifyingExtensionRegistryProvider ;
10+ use PHPStan \Rules \IdentifierRuleError ;
811use PHPStan \Rules \Rule ;
912use PHPStan \Rules \RuleErrorBuilder ;
1013use PHPStan \Rules \RuleLevelHelper ;
2831final class InvalidComparisonOperationRule implements Rule
2932{
3033
31- public function __construct (private RuleLevelHelper $ ruleLevelHelper )
34+ public function __construct (
35+ private RuleLevelHelper $ ruleLevelHelper ,
36+ private OperatorTypeSpecifyingExtensionRegistryProvider $ operatorTypeSpecifyingExtensionRegistryProvider ,
37+ #[AutowiredParameter(ref: '%featureToggles.checkExtensionsForComparisonOperators% ' )]
38+ private bool $ checkExtensionsForComparisonOperators ,
39+ )
3240 {
3341 }
3442
@@ -55,6 +63,22 @@ public function processNode(Node $node, Scope $scope): array
5563 return [];
5664 }
5765
66+ $ result = $ this ->operatorTypeSpecifyingExtensionRegistryProvider ->getRegistry ()->callOperatorTypeSpecifyingExtensions (
67+ $ node ,
68+ $ scope ->getType ($ node ->left ),
69+ $ scope ->getType ($ node ->right ),
70+ );
71+
72+ if ($ result !== null ) {
73+ if (! $ result instanceof ErrorType) {
74+ return [];
75+ }
76+
77+ if ($ this ->checkExtensionsForComparisonOperators ) {
78+ return $ this ->createError ($ node , $ scope );
79+ }
80+ }
81+
5882 if (
5983 ($ this ->isNumberType ($ scope , $ node ->left ) && (
6084 $ this ->isPossiblyNullableObjectType ($ scope , $ node ->right ) || $ this ->isPossiblyNullableArrayType ($ scope , $ node ->right )
@@ -63,43 +87,7 @@ public function processNode(Node $node, Scope $scope): array
6387 $ this ->isPossiblyNullableObjectType ($ scope , $ node ->left ) || $ this ->isPossiblyNullableArrayType ($ scope , $ node ->left )
6488 ))
6589 ) {
66- switch (get_class ($ node )) {
67- case Node \Expr \BinaryOp \Equal::class:
68- $ nodeType = 'equal ' ;
69- break ;
70- case Node \Expr \BinaryOp \NotEqual::class:
71- $ nodeType = 'notEqual ' ;
72- break ;
73- case Node \Expr \BinaryOp \Greater::class:
74- $ nodeType = 'greater ' ;
75- break ;
76- case Node \Expr \BinaryOp \GreaterOrEqual::class:
77- $ nodeType = 'greaterOrEqual ' ;
78- break ;
79- case Node \Expr \BinaryOp \Smaller::class:
80- $ nodeType = 'smaller ' ;
81- break ;
82- case Node \Expr \BinaryOp \SmallerOrEqual::class:
83- $ nodeType = 'smallerOrEqual ' ;
84- break ;
85- case Node \Expr \BinaryOp \Spaceship::class:
86- $ nodeType = 'spaceship ' ;
87- break ;
88- default :
89- throw new ShouldNotHappenException ();
90- }
91-
92- return [
93- RuleErrorBuilder::message (sprintf (
94- 'Comparison operation "%s" between %s and %s results in an error. ' ,
95- $ node ->getOperatorSigil (),
96- $ scope ->getType ($ node ->left )->describe (VerbosityLevel::value ()),
97- $ scope ->getType ($ node ->right )->describe (VerbosityLevel::value ()),
98- ))
99- ->line ($ node ->left ->getStartLine ())
100- ->identifier (sprintf ('%s.invalid ' , $ nodeType ))
101- ->build (),
102- ];
90+ return $ this ->createError ($ node , $ scope );
10391 }
10492
10593 return [];
@@ -166,4 +154,46 @@ private function isPossiblyNullableArrayType(Scope $scope, Node\Expr $expr): boo
166154 return !($ type instanceof ErrorType) && $ type ->isArray ()->yes ();
167155 }
168156
157+ /** @return list<IdentifierRuleError> */
158+ private function createError (Node \Expr \BinaryOp $ node , Scope $ scope ): array
159+ {
160+ switch (get_class ($ node )) {
161+ case Node \Expr \BinaryOp \Equal::class:
162+ $ nodeType = 'equal ' ;
163+ break ;
164+ case Node \Expr \BinaryOp \NotEqual::class:
165+ $ nodeType = 'notEqual ' ;
166+ break ;
167+ case Node \Expr \BinaryOp \Greater::class:
168+ $ nodeType = 'greater ' ;
169+ break ;
170+ case Node \Expr \BinaryOp \GreaterOrEqual::class:
171+ $ nodeType = 'greaterOrEqual ' ;
172+ break ;
173+ case Node \Expr \BinaryOp \Smaller::class:
174+ $ nodeType = 'smaller ' ;
175+ break ;
176+ case Node \Expr \BinaryOp \SmallerOrEqual::class:
177+ $ nodeType = 'smallerOrEqual ' ;
178+ break ;
179+ case Node \Expr \BinaryOp \Spaceship::class:
180+ $ nodeType = 'spaceship ' ;
181+ break ;
182+ default :
183+ throw new ShouldNotHappenException ();
184+ }
185+
186+ return [
187+ RuleErrorBuilder::message (sprintf (
188+ 'Comparison operation "%s" between %s and %s results in an error. ' ,
189+ $ node ->getOperatorSigil (),
190+ $ scope ->getType ($ node ->left )->describe (VerbosityLevel::value ()),
191+ $ scope ->getType ($ node ->right )->describe (VerbosityLevel::value ()),
192+ ))
193+ ->line ($ node ->left ->getStartLine ())
194+ ->identifier (sprintf ('%s.invalid ' , $ nodeType ))
195+ ->build (),
196+ ];
197+ }
198+
169199}
0 commit comments