1919 */
2020package org .sonar .python .checks ;
2121
22+ import java .util .Collection ;
2223import java .util .List ;
2324import javax .annotation .CheckForNull ;
2425import javax .annotation .Nullable ;
2526import org .sonar .check .Rule ;
2627import org .sonar .plugins .python .api .LocationInFile ;
2728import org .sonar .plugins .python .api .PythonSubscriptionCheck ;
2829import org .sonar .plugins .python .api .SubscriptionContext ;
30+ import org .sonar .plugins .python .api .symbols .ClassSymbol ;
2931import org .sonar .plugins .python .api .symbols .Symbol ;
3032import org .sonar .plugins .python .api .tree .Expression ;
33+ import org .sonar .plugins .python .api .tree .IsExpression ;
3134import org .sonar .plugins .python .api .tree .Name ;
3235import org .sonar .plugins .python .api .tree .RaiseStatement ;
3336import org .sonar .plugins .python .api .tree .Token ;
@@ -51,6 +54,7 @@ public void initialize(Context context) {
5154 new IterationOnNonIterableCheck ().initialize (context );
5255 new SillyEqualityCheck ().initialize (context );
5356 context .registerSyntaxNodeConsumer (Tree .Kind .RAISE_STMT , ConfusingTypeCheckingCheck ::checkIncorrectExceptionType );
57+ context .registerSyntaxNodeConsumer (Tree .Kind .IS , ConfusingTypeCheckingCheck ::checkSillyIdentity );
5458 }
5559
5660 private static class NonCallableCalledCheck extends NonCallableCalled {
@@ -175,12 +179,7 @@ private static class SillyEqualityCheck extends SillyEquality {
175179
176180 @ Override
177181 boolean areIdentityComparableOrNone (InferredType leftType , InferredType rightType ) {
178- if (!containsDeclaredType (leftType ) && !containsDeclaredType (rightType )) {
179- return true ;
180- }
181- return leftType .equals (rightType )
182- || (typeSymbols (leftType ).stream ().map (Symbol ::fullyQualifiedName ).allMatch ("NoneType" ::equals ))
183- || (typeSymbols (rightType ).stream ().map (Symbol ::fullyQualifiedName ).allMatch ("NoneType" ::equals ));
182+ return ConfusingTypeCheckingCheck .areIdentityComparableOrNone (leftType , rightType );
184183 }
185184
186185 @ Override
@@ -204,4 +203,27 @@ String message(String result) {
204203 return "Fix this equality check; Previous type checks suggest that operands have incompatible types." ;
205204 }
206205 }
206+
207+ private static boolean areIdentityComparableOrNone (InferredType leftType , InferredType rightType ) {
208+ if (!containsDeclaredType (leftType ) && !containsDeclaredType (rightType )) {
209+ return true ;
210+ }
211+ Collection <ClassSymbol > leftSymbols = typeSymbols (leftType );
212+ Collection <ClassSymbol > rightSymbols = typeSymbols (rightType );
213+ return leftSymbols .stream ().map (Symbol ::fullyQualifiedName ).anyMatch (fqn -> fqn == null || rightSymbols .stream ().anyMatch (rs -> rs .canBeOrExtend (fqn )))
214+ || rightSymbols .stream ().map (Symbol ::fullyQualifiedName ).anyMatch (fqn -> fqn == null || leftSymbols .stream ().anyMatch (ls -> ls .canBeOrExtend (fqn )))
215+ || (typeSymbols (leftType ).stream ().map (Symbol ::fullyQualifiedName ).allMatch ("NoneType" ::equals ))
216+ || (typeSymbols (rightType ).stream ().map (Symbol ::fullyQualifiedName ).allMatch ("NoneType" ::equals ));
217+ }
218+
219+ private static void checkSillyIdentity (SubscriptionContext ctx ) {
220+ IsExpression isExpression = (IsExpression ) ctx .syntaxNode ();
221+ InferredType left = isExpression .leftOperand ().type ();
222+ InferredType right = isExpression .rightOperand ().type ();
223+ if (!areIdentityComparableOrNone (left , right )) {
224+ Token notToken = isExpression .notToken ();
225+ Token lastToken = notToken == null ? isExpression .operator () : notToken ;
226+ ctx .addIssue (isExpression .operator (), lastToken , "Fix this identity check; Previous type checks suggest that operands have incompatible types." );
227+ }
228+ }
207229}
0 commit comments