Skip to content

Commit 7a3bb32

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3679 Merge isSubtypeOf with isOrExtendsType (#790)
GitOrigin-RevId: 8e1c7a72509a2b559e99b29a082e3ace840be0c3
1 parent cfd35db commit 7a3bb32

7 files changed

Lines changed: 60 additions & 206 deletions

File tree

.claude/skills/type-matchers.md

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,13 @@ TypeMatchers.isObjectOfType("builtins.str")
130130
// TypeMatchers.isObjectSatisfying(TypeMatchers.isType("builtins.str"))
131131
```
132132

133-
### Subtype Matching
134-
135-
#### `isSubtypeOf(String fqn)`
136-
Checks if the type is a subtype of the type resolved from the FQN.
137-
138-
```java
139-
TypeMatchers.isSubtypeOf("typing.Mapping")
140-
```
141-
142-
#### `isObjectOfSubType(String fqn)`
143-
Convenience method for object instances of subtypes.
133+
#### `isObjectInstanceOf(String fqn)`
134+
Checks if the type is an object type and if its nested type or supertypes match the given type by equality. Useful for checking if an instance is of a type or extends it.
144135

145136
```java
146-
TypeMatchers.isObjectOfSubType("typing.Mapping")
137+
TypeMatchers.isObjectInstanceOf("builtins.Exception")
147138
// Equivalent to:
148-
// TypeMatchers.isObjectSatisfying(TypeMatchers.isSubtypeOf("typing.Mapping"))
139+
// TypeMatchers.isObjectSatisfying(TypeMatchers.isOrExtendsType("builtins.Exception"))
149140
```
150141

151142
### Type Hierarchy Matching
@@ -225,9 +216,7 @@ TypeMatchers.any(
225216
### Checking for Dict-like Types
226217

227218
```java
228-
TypeMatchers.isObjectSatisfying(
229-
TypeMatchers.isSubtypeOf("typing.Mapping")
230-
)
219+
TypeMatchers.isObjectInstanceOf("typing.Mapping")
231220
```
232221

233222
### Checking Method Owner

python-frontend/src/main/java/org/sonar/plugins/python/api/types/v2/matchers/TypeMatchers.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.sonar.python.types.v2.matchers.HasMemberSatisfyingPredicate;
2929
import org.sonar.python.types.v2.matchers.IsFunctionOwnerSatisfyingPredicate;
3030
import org.sonar.python.types.v2.matchers.IsObjectSatisfyingPredicate;
31-
import org.sonar.python.types.v2.matchers.IsSubtypeOfPredicate;
3231
import org.sonar.python.types.v2.matchers.IsTypeOrSuperTypeSatisfyingPredicate;
3332
import org.sonar.python.types.v2.matchers.IsTypePredicate;
3433
import org.sonar.python.types.v2.matchers.TypePredicate;
@@ -95,12 +94,13 @@ public static TypeMatcher isObjectOfType(String fqn) {
9594
return isObjectSatisfying(isType(fqn));
9695
}
9796

98-
public static TypeMatcher isSubtypeOf(String fqn) {
99-
return new TypeMatcherImpl(new IsSubtypeOfPredicate(fqn));
100-
}
101-
102-
public static TypeMatcher isObjectOfSubType(String fqn) {
103-
return isObjectSatisfying(isSubtypeOf(fqn));
97+
/**
98+
* Checks if the type of the expression is an object type and if its nested type, or its supertypes, matches the given type by equality.
99+
* @param fqn The FQN of the type to match by equality
100+
* @return a type matcher
101+
*/
102+
public static TypeMatcher isObjectInstanceOf(String fqn) {
103+
return isObjectSatisfying(isOrExtendsType(fqn));
104104
}
105105

106106
public static TypeMatcher isOrExtendsType(String fqn) {

python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TypeInferenceMatchers.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.sonar.python.types.v2.matchers.HasMemberPredicate;
2525
import org.sonar.python.types.v2.matchers.IsObjectSatisfyingPredicate;
2626
import org.sonar.python.types.v2.matchers.IsSelfTypePredicate;
27-
import org.sonar.python.types.v2.matchers.IsSubtypeOfPredicate;
27+
import org.sonar.python.types.v2.matchers.IsTypeOrSuperTypeSatisfyingPredicate;
2828
import org.sonar.python.types.v2.matchers.IsTypePredicate;
2929
import org.sonar.python.types.v2.matchers.TypePredicate;
3030

@@ -56,8 +56,8 @@ public static TypePredicate isObjectOfType(String fqn) {
5656
return isObjectSatisfying(isType(fqn));
5757
}
5858

59-
public static TypePredicate isSubtypeOf(String fqn) {
60-
return new IsSubtypeOfPredicate(fqn);
59+
public static TypePredicate isOrExtendsType(String fqn) {
60+
return new IsTypeOrSuperTypeSatisfyingPredicate(isType(fqn));
6161
}
6262

6363
public static TypePredicate hasMember(String memberName) {

python-frontend/src/main/java/org/sonar/python/semantic/v2/types/typecalculator/QualifiedExpressionCalculator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
public class QualifiedExpressionCalculator {
3030
private static final TypeInferenceMatcher IS_PROPERTY_TYPE = TypeInferenceMatcher.of(
31-
TypeInferenceMatchers.isSubtypeOf("property"));
31+
TypeInferenceMatchers.isOrExtendsType("property"));
3232

3333
private final TypePredicateContext typePredicateContext;
3434

python-frontend/src/main/java/org/sonar/python/types/v2/matchers/IsSubtypeOfPredicate.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

python-frontend/src/test/java/org/sonar/python/types/v2/matchers/IsSubtypeOfPredicateTest.java

Lines changed: 0 additions & 131 deletions
This file was deleted.

python-frontend/src/test/java/org/sonar/python/types/v2/matchers/IsTypeOrSuperTypeSatisfyingPredicateTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,50 @@
3131

3232
class IsTypeOrSuperTypeSatisfyingPredicateTest {
3333

34+
@Test
35+
void testObjectInstanceOf() {
36+
var project = new TestProject();
37+
project.addModule("my_file.py", """
38+
class A:
39+
pass
40+
""");
41+
Expression objectTypeExpression = project.lastExpression("""
42+
from my_file import A
43+
A()
44+
""");
45+
46+
SubscriptionContext subscriptionContext = Mockito.mock(SubscriptionContext.class);
47+
Mockito.when(subscriptionContext.typeTable()).thenReturn(project.projectLevelTypeTable());
48+
49+
assertThat(TypeMatchers.isObjectInstanceOf("my_file.A").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.TRUE);
50+
assertThat(TypeMatchers.isObjectInstanceOf("str").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.FALSE);
51+
}
52+
53+
@Test
54+
void testObjectInstanceOfWithSubclasses() {
55+
var project = new TestProject();
56+
project.addModule("my_file.py", """
57+
class A:
58+
pass
59+
class B(A):
60+
pass
61+
class C(A):
62+
pass
63+
""");
64+
Expression objectTypeExpression = project.lastExpression("""
65+
from my_file import B
66+
B()
67+
""");
68+
69+
SubscriptionContext subscriptionContext = Mockito.mock(SubscriptionContext.class);
70+
Mockito.when(subscriptionContext.typeTable()).thenReturn(project.projectLevelTypeTable());
71+
72+
assertThat(TypeMatchers.isObjectInstanceOf("my_file.A").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.TRUE);
73+
assertThat(TypeMatchers.isObjectInstanceOf("my_file.B").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.TRUE);
74+
assertThat(TypeMatchers.isObjectInstanceOf("my_file.C").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.FALSE);
75+
assertThat(TypeMatchers.isObjectInstanceOf("str").evaluateFor(objectTypeExpression, subscriptionContext)).isEqualTo(TriBool.FALSE);
76+
}
77+
3478
@Test
3579
void testDirectTypeMatchWithIsTypePredicate() {
3680
var project = new TestProject();

0 commit comments

Comments
 (0)