Skip to content

Commit 4b8713a

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3526 Enable using Matcher API in type inference (#669)
GitOrigin-RevId: b6a84f2ce034a79ad2b4a44dddb50e305aefa7e7
1 parent d48fffc commit 4b8713a

25 files changed

+337
-121
lines changed

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

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@
1616
*/
1717
package org.sonar.python.api.types.v2.matchers;
1818

19-
import com.google.common.annotations.VisibleForTesting;
20-
import java.util.Set;
2119
import org.sonar.api.Beta;
2220
import org.sonar.plugins.python.api.SubscriptionContext;
2321
import org.sonar.plugins.python.api.TriBool;
2422
import org.sonar.plugins.python.api.tree.Expression;
2523
import org.sonar.plugins.python.api.types.v2.PythonType;
26-
import org.sonar.plugins.python.api.types.v2.UnionType;
2724
import org.sonar.python.types.v2.matchers.TypePredicate;
25+
import org.sonar.python.types.v2.matchers.TypePredicateContext;
26+
import org.sonar.python.types.v2.matchers.TypePredicateUtils;
2827

2928
// This record is package-private
3029
@Beta
@@ -33,28 +32,13 @@ record TypeMatcherImpl(TypePredicate predicate) implements TypeMatcher {
3332
@Override
3433
public TriBool evaluateFor(Expression expr, SubscriptionContext ctx) {
3534
PythonType type = expr.typeV2();
36-
Set<PythonType> candidates = extractCandidates(type);
37-
38-
TriBool result = TriBool.TRUE;
39-
for (PythonType candidate : candidates) {
40-
result = result.conservativeAnd(predicate.check(candidate, ctx));
41-
if (result.isUnknown()) {
42-
break;
43-
}
44-
}
45-
return result;
35+
TypePredicateContext predicateContext = TypePredicateContext.of(ctx.typeTable());
36+
return TypePredicateUtils.evaluate(predicate, type, predicateContext);
4637
}
4738

4839
@Override
4940
public boolean isTrueFor(Expression expr, SubscriptionContext ctx) {
5041
return evaluateFor(expr, ctx).isTrue();
5142
}
5243

53-
@VisibleForTesting
54-
static Set<PythonType> extractCandidates(PythonType type) {
55-
if (type instanceof UnionType unionType) {
56-
return unionType.candidates();
57-
}
58-
return Set.of(type);
59-
}
6044
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.python.semantic.v2.types;
18+
19+
import org.sonar.plugins.python.api.TriBool;
20+
import org.sonar.plugins.python.api.types.v2.PythonType;
21+
import org.sonar.python.types.v2.matchers.TypePredicate;
22+
import org.sonar.python.types.v2.matchers.TypePredicateContext;
23+
import org.sonar.python.types.v2.matchers.TypePredicateUtils;
24+
25+
class TypeInferenceMatcher {
26+
private final TypePredicate predicate;
27+
28+
private TypeInferenceMatcher(TypePredicate predicate) {
29+
this.predicate = predicate;
30+
}
31+
32+
public TriBool evaluate(PythonType type, TypePredicateContext ctx) {
33+
return TypePredicateUtils.evaluate(predicate, type, ctx);
34+
}
35+
36+
public static TypeInferenceMatcher of(TypePredicate predicate) {
37+
return new TypeInferenceMatcher(predicate);
38+
}
39+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* SonarQube Python Plugin
3+
* Copyright (C) 2011-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.python.semantic.v2.types;
18+
19+
import org.sonar.plugins.python.api.types.v2.PythonType;
20+
import org.sonar.python.api.types.v2.matchers.TypeMatchers;
21+
import org.sonar.python.types.v2.matchers.IsObjectSatisfyingPredicate;
22+
import org.sonar.python.types.v2.matchers.IsTypePredicate;
23+
import org.sonar.python.types.v2.matchers.TypePredicate;
24+
25+
/**
26+
* This class is the entry point for the TypeMatcher API specifically for type inference.
27+
* Compared to the {@link TypeMatchers} class, this API can handle {@link PythonType} instances directly.
28+
*
29+
* In case of missing matchers, they should be added to this class, instead of using the constructor directly.
30+
*
31+
* @see TypeInferenceMatcher
32+
*/
33+
class TypeInferenceMatchers {
34+
private TypeInferenceMatchers() {
35+
}
36+
37+
public static TypePredicate isType(String fqn) {
38+
return new IsTypePredicate(fqn);
39+
}
40+
41+
public static TypePredicate isObjectSatisfying(TypePredicate matcher) {
42+
return new IsObjectSatisfyingPredicate(matcher);
43+
}
44+
45+
public static TypePredicate isObjectOfType(String fqn) {
46+
return isObjectSatisfying(isType(fqn));
47+
}
48+
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.sonar.python.types.v2.matchers;
1818

1919
import java.util.List;
20-
import org.sonar.plugins.python.api.SubscriptionContext;
2120
import org.sonar.plugins.python.api.TriBool;
2221
import org.sonar.plugins.python.api.types.v2.PythonType;
2322

@@ -34,7 +33,7 @@ public AllTypePredicate(List<TypePredicate> predicates) {
3433
}
3534

3635
@Override
37-
public TriBool check(PythonType type, SubscriptionContext ctx) {
36+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3837
TriBool result = TriBool.TRUE;
3938
for (TypePredicate predicate : predicates) {
4039
TriBool partialResult = predicate.check(type, ctx);

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.sonar.python.types.v2.matchers;
1818

1919
import java.util.List;
20-
import org.sonar.plugins.python.api.SubscriptionContext;
2120
import org.sonar.plugins.python.api.TriBool;
2221
import org.sonar.plugins.python.api.types.v2.PythonType;
2322

@@ -34,7 +33,7 @@ public AnyTypePredicate(List<TypePredicate> predicates) {
3433
}
3534

3635
@Override
37-
public TriBool check(PythonType type, SubscriptionContext ctx) {
36+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3837
TriBool result = TriBool.FALSE;
3938

4039
for (TypePredicate predicate : predicates) {

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,13 @@
1818

1919
import java.util.Objects;
2020
import java.util.Optional;
21-
import org.sonar.plugins.python.api.SubscriptionContext;
2221
import org.sonar.plugins.python.api.TriBool;
2322
import org.sonar.plugins.python.api.types.v2.FullyQualifiedNameHelper;
2423
import org.sonar.plugins.python.api.types.v2.PythonType;
2524

2625
public record HasFQNPredicate(String fullyQualifiedName) implements TypePredicate {
2726
@Override
28-
public TriBool check(PythonType type, SubscriptionContext ctx) {
27+
public TriBool check(PythonType type, TypePredicateContext ctx) {
2928
return Optional.of(type)
3029
.flatMap(FullyQualifiedNameHelper::getFullyQualifiedName)
3130
.map(typeFqn -> Objects.equals(fullyQualifiedName, typeFqn) ? TriBool.TRUE : TriBool.FALSE)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.sonar.python.types.v2.matchers;
1818

19-
import org.sonar.plugins.python.api.SubscriptionContext;
2019
import org.sonar.plugins.python.api.TriBool;
2120
import org.sonar.plugins.python.api.types.v2.PythonType;
2221

@@ -29,7 +28,7 @@ public HasMemberPredicate(String memberName) {
2928
}
3029

3130
@Override
32-
public TriBool check(PythonType type, SubscriptionContext ctx) {
31+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3332
return type.hasMember(memberName);
3433
}
3534
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.sonar.python.types.v2.matchers;
1818

19-
import org.sonar.plugins.python.api.SubscriptionContext;
2019
import org.sonar.plugins.python.api.TriBool;
2120
import org.sonar.plugins.python.api.types.v2.PythonType;
2221

@@ -31,7 +30,7 @@ public HasMemberSatisfyingPredicate(String memberName, TypePredicate memberTypeP
3130
}
3231

3332
@Override
34-
public TriBool check(PythonType type, SubscriptionContext ctx) {
33+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3534
return type.resolveMember(memberName)
3635
.map(member -> memberTypePredicate.check(member, ctx))
3736
.orElse(TriBool.FALSE);

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.sonar.python.types.v2.matchers;
1818

19-
import org.sonar.plugins.python.api.SubscriptionContext;
2019
import org.sonar.plugins.python.api.TriBool;
2120
import org.sonar.plugins.python.api.types.v2.ObjectType;
2221
import org.sonar.plugins.python.api.types.v2.PythonType;
@@ -30,7 +29,7 @@ public IsObjectSatisfyingPredicate(TypePredicate wrappedPredicate) {
3029
}
3130

3231
@Override
33-
public TriBool check(PythonType type, SubscriptionContext ctx) {
32+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3433
if (type instanceof UnknownType) {
3534
return TriBool.UNKNOWN;
3635
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.sonar.python.types.v2.matchers;
1818

1919
import java.util.Set;
20-
import org.sonar.plugins.python.api.SubscriptionContext;
2120
import org.sonar.plugins.python.api.TriBool;
2221
import org.sonar.plugins.python.api.types.v2.PythonType;
2322

@@ -32,7 +31,7 @@ public IsObjectSubtypeOfPredicate(String fullyQualifiedName) {
3231
}
3332

3433
@Override
35-
public TriBool check(PythonType type, SubscriptionContext ctx) {
34+
public TriBool check(PythonType type, TypePredicateContext ctx) {
3635
return isObjectSatisfyingPredicate.check(type, ctx);
3736
}
3837

@@ -44,7 +43,7 @@ public IsSubtypeOfPredicate(String fullyQualifiedName) {
4443
}
4544

4645
@Override
47-
public TriBool check(PythonType type, SubscriptionContext ctx) {
46+
public TriBool check(PythonType type, TypePredicateContext ctx) {
4847
PythonType expectedType = ctx.typeTable().getType(fullyQualifiedName);
4948
Set<PythonType> types = collectTypes(type);
5049
if (types.stream().anyMatch(t -> t.equals(expectedType))) {

0 commit comments

Comments
 (0)