Skip to content

Commit 516cedc

Browse files
Seppli11sonartech
authored andcommitted
SONARPY-3699 Add CLI tool for agents (#806)
GitOrigin-RevId: 2136b155a91f9acf77c7ef0b3d9fbda7d5fee260
1 parent c2f5c29 commit 516cedc

3 files changed

Lines changed: 89 additions & 2 deletions

File tree

override-dep-licenses.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44
com.google.code.gson--gson--2.11.0=apache_v2
55
commons-io--commons-io--2.17.0=apache_v2
6-
com.github.spotbugs=apache_v2
6+
com.github.spotbugs=apache_v2
7+
info.picocli--picocli--4.7.5=apache_v2

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
import java.util.HashSet;
2020
import java.util.Optional;
2121
import java.util.Set;
22+
import org.sonar.plugins.python.api.TriBool;
2223
import org.sonar.plugins.python.api.tree.CallExpression;
2324
import org.sonar.plugins.python.api.tree.Expression;
2425
import org.sonar.plugins.python.api.tree.Name;
2526
import org.sonar.plugins.python.api.tree.QualifiedExpression;
27+
import org.sonar.plugins.python.api.tree.RegularArgument;
2628
import org.sonar.plugins.python.api.types.v2.ClassType;
2729
import org.sonar.plugins.python.api.types.v2.FunctionType;
2830
import org.sonar.plugins.python.api.types.v2.ObjectType;
@@ -34,14 +36,21 @@
3436
import org.sonar.plugins.python.api.types.v2.UnknownType;
3537
import org.sonar.python.semantic.v2.types.TypeInferenceMatcher;
3638
import org.sonar.python.semantic.v2.types.TypeInferenceMatchers;
39+
import org.sonar.python.tree.TreeUtils;
3740
import org.sonar.python.types.v2.matchers.TypePredicateContext;
3841

3942
public final class CallReturnTypeCalculator {
4043

44+
private static final String REVEAL_TYPE_FQN = "typing.reveal_type";
45+
4146
private CallReturnTypeCalculator() {
4247
}
4348

4449
public static PythonType computeCallExpressionType(CallExpression callExpr, TypePredicateContext typePredicateContext) {
50+
if (isRevealTypeCall(callExpr, typePredicateContext)) {
51+
return getFirstArgumentType(callExpr);
52+
}
53+
4554
PythonType calleeType = callExpr.callee().typeV2();
4655
TypeSource typeSource = computeTypeSource(calleeType, callExpr);
4756
PythonType returnType = returnTypeOfCall(calleeType);
@@ -179,5 +188,28 @@ private static TypeSource calleeTypeSource(CallExpression callExpr) {
179188
}
180189
return callExpr.callee().typeV2().typeSource();
181190
}
182-
}
183191

192+
private static boolean isRevealTypeCall(CallExpression callExpr, TypePredicateContext typePredicateContext) {
193+
Expression callee = callExpr.callee();
194+
if (!(callee instanceof Name name) || !"reveal_type".equals(name.name())) {
195+
return false;
196+
}
197+
198+
PythonType calleeType = callee.typeV2();
199+
200+
TriBool hasRevealTypeFqn = TypeInferenceMatcher.of(TypeInferenceMatchers.withFQN(REVEAL_TYPE_FQN))
201+
.evaluate(calleeType, typePredicateContext);
202+
203+
boolean isLocallyDefined = calleeType != PythonType.UNKNOWN;
204+
205+
return hasRevealTypeFqn.isTrue() || !isLocallyDefined;
206+
}
207+
208+
private static PythonType getFirstArgumentType(CallExpression callExpr) {
209+
RegularArgument firstArg = TreeUtils.nthArgumentOrKeyword(0, "__obj", callExpr.arguments());
210+
if (firstArg != null) {
211+
return firstArg.expression().typeV2();
212+
}
213+
return PythonType.UNKNOWN;
214+
}
215+
}

python-frontend/src/test/java/org/sonar/python/semantic/v2/types/typecalculator/CallReturnTypeCalculatorTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,4 +480,58 @@ def __call__(self) -> Self: ...
480480
.extracting(PythonType::unwrappedType)
481481
.isEqualTo(classDef.name().typeV2());
482482
}
483+
484+
@Test
485+
void computeCallExpressionType_revealType_transparent() {
486+
FileInput fileInput = parseAndInferTypes("""
487+
x = 42
488+
reveal_type(x)
489+
""");
490+
491+
CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance);
492+
493+
PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext);
494+
assertThat(result).isInstanceOf(ObjectType.class);
495+
assertThat(((ObjectType) result).unwrappedType()).isEqualTo(TypesTestUtils.INT_TYPE);
496+
}
497+
498+
@Test
499+
void computeCallExpressionType_revealType_inTypeAnnotation() {
500+
FileInput fileInput = parseAndInferTypes("""
501+
def foo(x: reveal_type(int)): ...
502+
""");
503+
504+
CallExpressionImpl callExpr = getFirstDescendant(fileInput, CallExpression.class::isInstance);
505+
506+
PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext);
507+
assertThat(result).isEqualTo(TypesTestUtils.INT_TYPE);
508+
}
509+
510+
@Test
511+
void computeCallExpressionType_revealType_noArguments() {
512+
FileInput fileInput = parseAndInferTypes("""
513+
reveal_type()
514+
""");
515+
516+
CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance);
517+
518+
PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext);
519+
assertThat(result).isEqualTo(PythonType.UNKNOWN);
520+
}
521+
522+
@Test
523+
void computeCallExpressionType_revealType_locallyDefined_notTransparent() {
524+
FileInput fileInput = parseAndInferTypes("""
525+
def reveal_type(x) -> str:
526+
return str(type(x))
527+
x = 42
528+
reveal_type(x)
529+
""");
530+
531+
CallExpressionImpl callExpr = getLastDescendant(fileInput, CallExpression.class::isInstance);
532+
533+
PythonType result = CallReturnTypeCalculator.computeCallExpressionType(callExpr, typePredicateContext);
534+
assertThat(result).isInstanceOf(ObjectType.class);
535+
assertThat(((ObjectType) result).unwrappedType()).isEqualTo(TypesTestUtils.STR_TYPE);
536+
}
483537
}

0 commit comments

Comments
 (0)