Skip to content

Commit ced7da5

Browse files
marc-jasper-sonarsourcesonartech
authored andcommitted
SONARPY-2258 Ensure that all Expression which implement type() also implement typeV2() (#717)
GitOrigin-RevId: ab016e41e9fe29fa469ef014a4f7a92cf3282497
1 parent d52bf69 commit ced7da5

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.sonar.plugins.python.api.tree.CallExpression;
2525
import org.sonar.plugins.python.api.tree.Expression;
2626
import org.sonar.plugins.python.api.tree.QualifiedExpression;
27+
import org.sonar.plugins.python.api.tree.ConditionalExpression;
28+
import org.sonar.plugins.python.api.tree.SliceExpression;
2729
import org.sonar.plugins.python.api.tree.Tree;
2830
import org.sonar.plugins.python.api.tree.UnaryExpression;
2931
import org.sonar.plugins.python.api.types.BuiltinTypes;
@@ -38,12 +40,20 @@
3840
import org.sonar.python.tree.BinaryExpressionImpl;
3941
import org.sonar.python.tree.CallExpressionImpl;
4042
import org.sonar.python.tree.NameImpl;
43+
import org.sonar.python.tree.ConditionalExpressionImpl;
44+
import org.sonar.python.tree.SliceExpressionImpl;
4145
import org.sonar.python.tree.UnaryExpressionImpl;
4246
import org.sonar.python.types.v2.TypeCheckBuilder;
4347
import org.sonar.python.types.v2.TypeUtils;
4448
import org.sonar.python.types.v2.matchers.TypePredicateContext;
4549

4650
public class TrivialTypePropagationVisitor extends BaseTreeVisitor {
51+
private static final TypeInferenceMatcher IS_SLICEABLE_TYPE = TypeInferenceMatcher.of(
52+
TypeInferenceMatchers.any(
53+
TypeInferenceMatchers.isObjectOfType(BuiltinTypes.LIST),
54+
TypeInferenceMatchers.isObjectOfType(BuiltinTypes.TUPLE),
55+
TypeInferenceMatchers.isObjectOfType(BuiltinTypes.STR)));
56+
4757
private final TypeCheckBuilder isBooleanTypeCheck;
4858
private final TypeCheckBuilder isIntTypeCheck;
4959
private final TypeCheckBuilder isFloatTypeCheck;
@@ -132,6 +142,32 @@ public void visitAwaitExpression(AwaitExpression awaitExpression) {
132142
}
133143
}
134144

145+
@Override
146+
public void visitConditionalExpression(ConditionalExpression conditionalExpression) {
147+
super.visitConditionalExpression(conditionalExpression);
148+
if (conditionalExpression instanceof ConditionalExpressionImpl conditionalExpressionImpl) {
149+
PythonType trueType = conditionalExpression.trueExpression().typeV2();
150+
PythonType falseType = conditionalExpression.falseExpression().typeV2();
151+
conditionalExpressionImpl.typeV2(UnionType.or(trueType, falseType));
152+
}
153+
}
154+
155+
@Override
156+
public void visitSliceExpression(SliceExpression sliceExpression) {
157+
super.visitSliceExpression(sliceExpression);
158+
if (sliceExpression instanceof SliceExpressionImpl sliceExpressionImpl) {
159+
PythonType objectType = sliceExpression.object().typeV2();
160+
sliceExpressionImpl.typeV2(calculateSliceExpressionType(objectType));
161+
}
162+
}
163+
164+
private PythonType calculateSliceExpressionType(PythonType objectType) {
165+
if (IS_SLICEABLE_TYPE.evaluate(objectType, typePredicateContext) == TriBool.TRUE) {
166+
return objectType;
167+
}
168+
return PythonType.UNKNOWN;
169+
}
170+
135171
private static PythonType calculateBinaryExpressionType(BinaryExpression binaryExpression) {
136172
var kind = binaryExpression.getKind();
137173
var leftOperand = binaryExpression.leftOperand();

python-frontend/src/main/java/org/sonar/python/tree/ConditionalExpressionImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.sonar.plugins.python.api.tree.Tree;
2727
import org.sonar.plugins.python.api.tree.TreeVisitor;
2828
import org.sonar.plugins.python.api.types.InferredType;
29+
import org.sonar.plugins.python.api.types.v2.PythonType;
2930
import org.sonar.python.types.HasTypeDependencies;
3031
import org.sonar.python.types.InferredTypes;
3132

@@ -35,6 +36,7 @@ public class ConditionalExpressionImpl extends PyTree implements ConditionalExpr
3536
private final Expression condition;
3637
private final Token elseToken;
3738
private final Expression falseExpression;
39+
private PythonType typeV2 = PythonType.UNKNOWN;
3840

3941
public ConditionalExpressionImpl(Expression trueExpression,
4042
Token ifToken, Expression condition, Token elseToken, Expression falseExpression) {
@@ -90,6 +92,15 @@ public InferredType type() {
9092
return InferredTypes.or(trueExpression.type(), falseExpression.type());
9193
}
9294

95+
@Override
96+
public PythonType typeV2() {
97+
return typeV2;
98+
}
99+
100+
public void typeV2(PythonType type) {
101+
this.typeV2 = type;
102+
}
103+
93104
@Override
94105
public List<Expression> typeDependencies() {
95106
return Arrays.asList(trueExpression, falseExpression);

python-frontend/src/main/java/org/sonar/python/tree/SliceExpressionImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.sonar.plugins.python.api.tree.TreeVisitor;
2929
import org.sonar.plugins.python.api.types.BuiltinTypes;
3030
import org.sonar.plugins.python.api.types.InferredType;
31+
import org.sonar.plugins.python.api.types.v2.PythonType;
3132
import org.sonar.python.types.HasTypeDependencies;
3233
import org.sonar.python.types.InferredTypes;
3334

@@ -41,6 +42,7 @@ public class SliceExpressionImpl extends PyTree implements SliceExpression, HasT
4142
private final Token leftBracket;
4243
private final SliceList sliceList;
4344
private final Token rightBracket;
45+
private PythonType typeV2 = PythonType.UNKNOWN;
4446

4547
public SliceExpressionImpl(Expression object, Token leftBracket, SliceList sliceList, Token rightBracket) {
4648
this.object = object;
@@ -102,6 +104,15 @@ public InferredType type() {
102104
return InferredTypes.anyType();
103105
}
104106

107+
@Override
108+
public PythonType typeV2() {
109+
return typeV2;
110+
}
111+
112+
public void typeV2(PythonType type) {
113+
this.typeV2 = type;
114+
}
115+
105116
@Override
106117
public List<Expression> typeDependencies() {
107118
return Collections.singletonList(object);

python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.sonar.plugins.python.api.tree.BinaryExpression;
4444
import org.sonar.plugins.python.api.tree.CallExpression;
4545
import org.sonar.plugins.python.api.tree.ClassDef;
46+
import org.sonar.plugins.python.api.tree.ConditionalExpression;
4647
import org.sonar.plugins.python.api.tree.Expression;
4748
import org.sonar.plugins.python.api.tree.ExpressionStatement;
4849
import org.sonar.plugins.python.api.tree.FileInput;
@@ -53,6 +54,7 @@
5354
import org.sonar.plugins.python.api.tree.Name;
5455
import org.sonar.plugins.python.api.tree.QualifiedExpression;
5556
import org.sonar.plugins.python.api.tree.RegularArgument;
57+
import org.sonar.plugins.python.api.tree.SliceExpression;
5658
import org.sonar.plugins.python.api.tree.Statement;
5759
import org.sonar.plugins.python.api.tree.StatementList;
5860
import org.sonar.plugins.python.api.tree.StringLiteral;
@@ -4786,4 +4788,80 @@ async def foo():
47864788
.isInstanceOfSatisfying(ClassType.class, classType -> assertThat(classType.fullyQualifiedName()).isEqualTo("typing.Coroutine"));
47874789
}
47884790

4791+
@Test
4792+
void conditionalExpressionTypeV2_sameTypes() {
4793+
ConditionalExpression conditionalExpression = PythonTestUtils.getFirstChild(
4794+
inferTypes("1 if True else 2"),
4795+
t -> t.is(Tree.Kind.CONDITIONAL_EXPR)
4796+
);
4797+
4798+
assertThat(conditionalExpression.typeV2())
4799+
.isInstanceOf(ObjectType.class)
4800+
.extracting(PythonType::unwrappedType)
4801+
.isEqualTo(INT_TYPE);
4802+
}
4803+
4804+
@Test
4805+
void conditionalExpressionTypeV2_differentTypes() {
4806+
ConditionalExpression conditionalExpression = PythonTestUtils.getFirstChild(
4807+
inferTypes("1 if True else 'hello'"),
4808+
t -> t.is(Tree.Kind.CONDITIONAL_EXPR)
4809+
);
4810+
4811+
assertThat(conditionalExpression.typeV2()).isInstanceOfSatisfying(UnionType.class, unionType ->
4812+
assertThat(unionType.candidates())
4813+
.extracting(PythonType::unwrappedType)
4814+
.containsExactlyInAnyOrder(INT_TYPE, STR_TYPE)
4815+
);
4816+
}
4817+
4818+
@Test
4819+
void sliceExpressionTypeV2_list() {
4820+
SliceExpression sliceExpression = PythonTestUtils.getFirstChild(
4821+
inferTypes("[1, 2, 3][0:2]"),
4822+
t -> t.is(Tree.Kind.SLICE_EXPR)
4823+
);
4824+
4825+
assertThat(sliceExpression.typeV2())
4826+
.isInstanceOf(ObjectType.class)
4827+
.extracting(PythonType::unwrappedType)
4828+
.isEqualTo(LIST_TYPE);
4829+
}
4830+
4831+
@Test
4832+
void sliceExpressionTypeV2_tuple() {
4833+
SliceExpression sliceExpression = PythonTestUtils.getFirstChild(
4834+
inferTypes("(1, 2, 3)[0:2]"),
4835+
t -> t.is(Tree.Kind.SLICE_EXPR)
4836+
);
4837+
4838+
assertThat(sliceExpression.typeV2())
4839+
.isInstanceOf(ObjectType.class)
4840+
.extracting(PythonType::unwrappedType)
4841+
.isEqualTo(TUPLE_TYPE);
4842+
}
4843+
4844+
@Test
4845+
void sliceExpressionTypeV2_string() {
4846+
SliceExpression sliceExpression = PythonTestUtils.getFirstChild(
4847+
inferTypes("'hello'[0:2]"),
4848+
t -> t.is(Tree.Kind.SLICE_EXPR)
4849+
);
4850+
4851+
assertThat(sliceExpression.typeV2())
4852+
.isInstanceOf(ObjectType.class)
4853+
.extracting(PythonType::unwrappedType)
4854+
.isEqualTo(STR_TYPE);
4855+
}
4856+
4857+
@Test
4858+
void sliceExpressionTypeV2_unknown() {
4859+
SliceExpression sliceExpression = PythonTestUtils.getFirstChild(
4860+
inferTypes("unknown_var[0:2]"),
4861+
t -> t.is(Tree.Kind.SLICE_EXPR)
4862+
);
4863+
4864+
assertThat(sliceExpression.typeV2()).isEqualTo(PythonType.UNKNOWN);
4865+
}
4866+
47894867
}

0 commit comments

Comments
 (0)