Skip to content

Commit 1adc874

Browse files
authored
SONARPY-2382 fix unary propagation problem (#2198)
1 parent 393af24 commit 1adc874

File tree

3 files changed

+64
-18
lines changed

3 files changed

+64
-18
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11

22
def param_with_type_hint(a: int):
3-
a() # OK
3+
a() # OK
4+
5+
def call_unknown_value(unknown_value):
6+
(~unknown_value)(X) # Ok

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

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

1919
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
20-
import org.sonar.plugins.python.api.tree.Token;
2120
import org.sonar.plugins.python.api.tree.UnaryExpression;
2221
import org.sonar.plugins.python.api.types.BuiltinTypes;
2322
import org.sonar.python.semantic.v2.TypeTable;
@@ -52,22 +51,32 @@ public TrivialTypePropagationVisitor(TypeTable typeTable) {
5251
public void visitUnaryExpression(UnaryExpression unaryExpr) {
5352
super.visitUnaryExpression(unaryExpr);
5453

55-
Token operator = unaryExpr.operator();
56-
PythonType exprType = switch (operator.value()) {
57-
case "~" -> intType;
58-
case "not" -> boolType;
59-
case "+", "-" -> getTypeWhenUnaryPlusMinus(unaryExpr);
60-
default -> PythonType.UNKNOWN;
61-
};
62-
54+
PythonType exprType = calculateUnaryExprType(unaryExpr);
6355
if (unaryExpr instanceof UnaryExpressionImpl unaryExprImpl) {
6456
unaryExprImpl.typeV2(toObjectType(exprType));
6557
}
6658
}
6759

68-
private PythonType getTypeWhenUnaryPlusMinus(UnaryExpression unaryExpr) {
69-
var innerExprType = unaryExpr.expression().typeV2();
70-
return TypeUtils.map(innerExprType, this::mapUnaryPlusMinusType);
60+
private PythonType calculateUnaryExprType(UnaryExpression unaryExpr) {
61+
String operator = unaryExpr.operator().value();
62+
return TypeUtils.map(unaryExpr.expression().typeV2(), type -> mapUnaryExprType(operator, type));
63+
}
64+
65+
private PythonType mapUnaryExprType(String operator, PythonType type) {
66+
return switch (operator) {
67+
case "~" -> mapInvertExprType(type);
68+
// not cannot be overloaded and always returns a boolean
69+
case "not" -> boolType;
70+
case "+", "-" -> mapUnaryPlusMinusType(type);
71+
default -> PythonType.UNKNOWN;
72+
};
73+
}
74+
75+
private PythonType mapInvertExprType(PythonType type) {
76+
if(isIntTypeCheck.check(type) == TriBool.TRUE || isBooleanTypeCheck.check(type) == TriBool.TRUE) {
77+
return intType;
78+
}
79+
return PythonType.UNKNOWN;
7180
}
7281

7382
private PythonType mapUnaryPlusMinusType(PythonType type) {

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

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
2222
import org.junit.jupiter.params.ParameterizedTest;
2323
import org.junit.jupiter.params.provider.Arguments;
2424
import org.junit.jupiter.params.provider.MethodSource;
25+
import org.sonar.python.tree.TokenImpl;
26+
import org.sonar.python.tree.UnaryExpressionImpl;
2527
import org.sonar.python.types.v2.ObjectType;
2628
import org.sonar.python.types.v2.PythonType;
2729
import org.sonar.python.types.v2.TypesTestUtils;
2830

2931
import static org.assertj.core.api.Assertions.assertThat;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.when;
3034
import static org.sonar.python.PythonTestUtils.lastExpression;
3135
import static org.sonar.python.PythonTestUtils.pythonFile;
3236
import static org.sonar.python.types.v2.TypesTestUtils.PROJECT_LEVEL_TYPE_TABLE;
@@ -54,15 +58,13 @@ static Stream<Arguments> testSources() {
5458
Arguments.of("+(True)", TypesTestUtils.INT_TYPE),
5559

5660
Arguments.of("~1", TypesTestUtils.INT_TYPE),
57-
Arguments.of("~1.0", TypesTestUtils.INT_TYPE),
58-
Arguments.of("~(3+2j)", TypesTestUtils.INT_TYPE),
59-
Arguments.of("~(1j)", TypesTestUtils.INT_TYPE),
6061
Arguments.of("~(True)", TypesTestUtils.INT_TYPE),
6162

6263
Arguments.of("not 1", TypesTestUtils.BOOL_TYPE),
6364
Arguments.of("not 1.0", TypesTestUtils.BOOL_TYPE),
6465
Arguments.of("not (2j)", TypesTestUtils.BOOL_TYPE),
65-
Arguments.of("not (True)", TypesTestUtils.BOOL_TYPE)
66+
Arguments.of("not (True)", TypesTestUtils.BOOL_TYPE),
67+
Arguments.of("not x", TypesTestUtils.BOOL_TYPE)
6668
);
6769
}
6870

@@ -77,12 +79,32 @@ void test(String code, PythonType expectedType) {
7779
assertThat(objectType.type()).isEqualTo(expectedType));
7880
}
7981

82+
static Stream<Arguments> testUnknownReturnSources() {
83+
return Stream.of(
84+
Arguments.of("~x"),
85+
Arguments.of("~1.0"),
86+
Arguments.of("~(1j)"),
87+
Arguments.of("~(3+2j)"),
88+
Arguments.of("-x"),
89+
Arguments.of("+x")
90+
);
91+
}
92+
93+
@ParameterizedTest
94+
@MethodSource("testUnknownReturnSources")
95+
void testUnknownReturn(String code) {
96+
var expr = lastExpression(code);
97+
expr.accept(trivialTypeInferenceVisitor);
98+
expr.accept(trivialTypePropagationVisitor);
99+
assertThat(expr.typeV2()).isEqualTo(PythonType.UNKNOWN);
100+
}
101+
80102
static Stream<Arguments> customNumberClassTestSource() {
81103
return Stream.of(
82104
Arguments.of("+(MyNum())", PythonType.UNKNOWN),
83105
Arguments.of("-(MyNum())", PythonType.UNKNOWN),
84106
Arguments.of("not (MyNum())", new ObjectType(TypesTestUtils.BOOL_TYPE)),
85-
Arguments.of("~(MyNum())", new ObjectType(TypesTestUtils.INT_TYPE))
107+
Arguments.of("~(MyNum())", PythonType.UNKNOWN)
86108
);
87109
}
88110

@@ -105,4 +127,16 @@ void testNotOfCustomClass() {
105127
assertThat(expr.typeV2()).isInstanceOfSatisfying(ObjectType.class, objectType ->
106128
assertThat(objectType.type()).isEqualTo(TypesTestUtils.BOOL_TYPE));
107129
}
130+
131+
@Test
132+
void testUnknownOperator() {
133+
var operator = mock(TokenImpl.class);
134+
when(operator.value()).thenReturn("invalid_operator");
135+
UnaryExpressionImpl expr = new UnaryExpressionImpl(operator, lastExpression("1"));
136+
expr.typeV2(TypesTestUtils.INT_TYPE);
137+
138+
expr.accept(trivialTypePropagationVisitor);
139+
assertThat(expr.typeV2()).isEqualTo(PythonType.UNKNOWN);
140+
}
141+
108142
}

0 commit comments

Comments
 (0)