Skip to content

Commit 9776160

Browse files
SONARPY-1796 Infer types for set, dict and tuple literals (#1773)
1 parent 9b3cd26 commit 9776160

File tree

6 files changed

+161
-8
lines changed

6 files changed

+161
-8
lines changed

python-checks/src/test/resources/checks/nonCallableCalled.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ def call_noncallable(p):
2222
list_var() # Noncompliant
2323

2424
tuple_var = ()
25-
tuple_var() # FN
25+
tuple_var() # Noncompliant
2626

2727
dict_var = {}
28-
dict_var() # FN
28+
dict_var() # Noncompliant
29+
30+
set_literal = {1, 2}
31+
set_literal() # Noncompliant
2932

3033
set_var = set()
3134
set_var() # FN

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.sonar.plugins.python.api.tree.AssignmentStatement;
3131
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
3232
import org.sonar.plugins.python.api.tree.ClassDef;
33+
import org.sonar.plugins.python.api.tree.DictionaryLiteral;
3334
import org.sonar.plugins.python.api.tree.DottedName;
3435
import org.sonar.plugins.python.api.tree.Expression;
3536
import org.sonar.plugins.python.api.tree.ExpressionList;
@@ -43,14 +44,19 @@
4344
import org.sonar.plugins.python.api.tree.NumericLiteral;
4445
import org.sonar.plugins.python.api.tree.QualifiedExpression;
4546
import org.sonar.plugins.python.api.tree.RegularArgument;
47+
import org.sonar.plugins.python.api.tree.SetLiteral;
4648
import org.sonar.plugins.python.api.tree.StringLiteral;
4749
import org.sonar.plugins.python.api.tree.Tree;
50+
import org.sonar.plugins.python.api.tree.Tuple;
4851
import org.sonar.plugins.python.api.types.InferredType;
52+
import org.sonar.python.tree.DictionaryLiteralImpl;
4953
import org.sonar.python.tree.ListLiteralImpl;
5054
import org.sonar.python.tree.NameImpl;
5155
import org.sonar.python.tree.NoneExpressionImpl;
5256
import org.sonar.python.tree.NumericLiteralImpl;
57+
import org.sonar.python.tree.SetLiteralImpl;
5358
import org.sonar.python.tree.StringLiteralImpl;
59+
import org.sonar.python.tree.TupleImpl;
5460
import org.sonar.python.types.RuntimeType;
5561
import org.sonar.python.types.v2.ClassType;
5662
import org.sonar.python.types.v2.FunctionType;
@@ -82,7 +88,36 @@ public void visitStringLiteral(StringLiteral stringLiteral) {
8288
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
8389
// TODO: multiple object types to represent str instance?
8490
PythonType strType = builtins.resolveMember("str").orElse(PythonType.UNKNOWN);
85-
((StringLiteralImpl) stringLiteral).typeV2(new ObjectType(strType, List.of(), List.of()));
91+
((StringLiteralImpl) stringLiteral).typeV2(new ObjectType(strType, new ArrayList<>(), new ArrayList<>()));
92+
}
93+
94+
@Override
95+
public void visitTuple(Tuple tuple) {
96+
super.visitTuple(tuple);
97+
List<PythonType> contentTypes = tuple.elements().stream().map(Expression::typeV2).distinct().toList();
98+
List<PythonType> attributes = new ArrayList<>();
99+
if (contentTypes.size() == 1 && !contentTypes.get(0).equals(PythonType.UNKNOWN)) {
100+
attributes = contentTypes;
101+
}
102+
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
103+
PythonType tupleType = builtins.resolveMember("tuple").orElse(PythonType.UNKNOWN);
104+
((TupleImpl) tuple).typeV2(new ObjectType(tupleType, attributes, new ArrayList<>()));
105+
}
106+
107+
@Override
108+
public void visitDictionaryLiteral(DictionaryLiteral dictionaryLiteral) {
109+
super.visitDictionaryLiteral(dictionaryLiteral);
110+
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
111+
PythonType dictType = builtins.resolveMember("dict").orElse(PythonType.UNKNOWN);
112+
((DictionaryLiteralImpl) dictionaryLiteral).typeV2(new ObjectType(dictType, new ArrayList<>(), new ArrayList<>()));
113+
}
114+
115+
@Override
116+
public void visitSetLiteral(SetLiteral setLiteral) {
117+
super.visitSetLiteral(setLiteral);
118+
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
119+
PythonType setType = builtins.resolveMember("set").orElse(PythonType.UNKNOWN);
120+
((SetLiteralImpl) setLiteral).typeV2(new ObjectType(setType, new ArrayList<>(), new ArrayList<>()));
86121
}
87122

88123
@Override
@@ -92,7 +127,7 @@ public void visitNumericLiteral(NumericLiteral numericLiteral) {
92127
String memberName = ((RuntimeType) type).getTypeClass().fullyQualifiedName();
93128
if (memberName != null) {
94129
PythonType pythonType = builtins.resolveMember(memberName).orElse(PythonType.UNKNOWN);
95-
((NumericLiteralImpl) numericLiteral).typeV2(new ObjectType(pythonType, List.of(), List.of()));
130+
((NumericLiteralImpl) numericLiteral).typeV2(new ObjectType(pythonType, new ArrayList<>(), new ArrayList<>()));
96131
}
97132
}
98133

@@ -101,7 +136,7 @@ public void visitNone(NoneExpression noneExpression) {
101136
ModuleType builtins = this.projectLevelTypeTable.getModule(BUILTINS);
102137
// TODO: multiple object types to represent str instance?
103138
PythonType noneType = builtins.resolveMember("NoneType").orElse(PythonType.UNKNOWN);
104-
((NoneExpressionImpl) noneExpression).typeV2(new ObjectType(noneType, List.of(), List.of()));
139+
((NoneExpressionImpl) noneExpression).typeV2(new ObjectType(noneType, new ArrayList<>(), new ArrayList<>()));
105140
}
106141

107142
@Override
@@ -111,7 +146,7 @@ public void visitListLiteral(ListLiteral listLiteral) {
111146
List<PythonType> pythonTypes = listLiteral.elements().expressions().stream().map(Expression::typeV2).distinct().toList();
112147
// TODO: cleanly reduce attributes
113148
PythonType listType = builtins.resolveMember("list").orElse(PythonType.UNKNOWN);
114-
((ListLiteralImpl) listLiteral).typeV2(new ObjectType(listType, pythonTypes, List.of()));
149+
((ListLiteralImpl) listLiteral).typeV2(new ObjectType(listType, pythonTypes, new ArrayList<>()));
115150
}
116151

117152
@Override

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
import org.sonar.plugins.python.api.tree.TreeVisitor;
2727
import org.sonar.plugins.python.api.types.InferredType;
2828
import org.sonar.python.types.InferredTypes;
29+
import org.sonar.python.types.v2.PythonType;
2930

3031
public class DictionaryLiteralImpl extends DictOrSetLiteralImpl<DictionaryLiteralElement> implements DictionaryLiteral {
3132

33+
private PythonType pythonType = PythonType.UNKNOWN;
34+
3235
public DictionaryLiteralImpl(Token lCurlyBrace, List<Token> commas, List<DictionaryLiteralElement> elements, Token rCurlyBrace) {
3336
super(lCurlyBrace, commas, elements, rCurlyBrace);
3437
}
@@ -46,4 +49,14 @@ public Kind getKind() {
4649
public InferredType type() {
4750
return InferredTypes.DICT;
4851
}
52+
53+
@Override
54+
public PythonType typeV2() {
55+
return pythonType;
56+
}
57+
58+
public DictionaryLiteral typeV2(PythonType pythonType) {
59+
this.pythonType = pythonType;
60+
return this;
61+
}
4962
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
import org.sonar.plugins.python.api.tree.TreeVisitor;
2727
import org.sonar.plugins.python.api.types.InferredType;
2828
import org.sonar.python.types.InferredTypes;
29+
import org.sonar.python.types.v2.PythonType;
2930

3031
public class SetLiteralImpl extends DictOrSetLiteralImpl<Expression> implements SetLiteral {
3132

33+
private PythonType pythonType = PythonType.UNKNOWN;
34+
3235
public SetLiteralImpl(Token lCurlyBrace, List<Expression> elements, List<Token> commas, Token rCurlyBrace) {
3336
super(lCurlyBrace, commas, elements, rCurlyBrace);
3437
}
@@ -46,4 +49,14 @@ public Kind getKind() {
4649
public InferredType type() {
4750
return InferredTypes.SET;
4851
}
52+
53+
@Override
54+
public PythonType typeV2() {
55+
return pythonType;
56+
}
57+
58+
public SetLiteralImpl typeV2(PythonType pythonType) {
59+
this.pythonType = pythonType;
60+
return this;
61+
}
4962
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@
3030
import org.sonar.plugins.python.api.tree.Tuple;
3131
import org.sonar.plugins.python.api.types.InferredType;
3232
import org.sonar.python.types.InferredTypes;
33+
import org.sonar.python.types.v2.PythonType;
3334

3435
public class TupleImpl extends PyTree implements Tuple {
3536

3637
private final Token leftParenthesis;
3738
private final List<Expression> elements;
3839
private final List<Token> commas;
3940
private final Token rightParenthesis;
41+
private PythonType pythonType = PythonType.UNKNOWN;
4042

4143
public TupleImpl(@Nullable Token leftParenthesis, List<Expression> elements, List<Token> commas, @Nullable Token rightParenthesis) {
4244
this.leftParenthesis = leftParenthesis;
@@ -101,4 +103,14 @@ public Kind getKind() {
101103
public InferredType type() {
102104
return InferredTypes.TUPLE;
103105
}
106+
107+
@Override
108+
public PythonType typeV2() {
109+
return pythonType;
110+
}
111+
112+
public TupleImpl typeV2(PythonType pythonType) {
113+
this.pythonType = pythonType;
114+
return this;
115+
}
104116
}

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

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,24 @@
2222
import java.util.List;
2323
import org.junit.jupiter.api.Test;
2424
import org.sonar.plugins.python.api.tree.ClassDef;
25+
import org.sonar.plugins.python.api.tree.DictionaryLiteral;
2526
import org.sonar.plugins.python.api.tree.ExpressionStatement;
2627
import org.sonar.plugins.python.api.tree.FileInput;
28+
import org.sonar.plugins.python.api.tree.Name;
29+
import org.sonar.plugins.python.api.tree.NoneExpression;
30+
import org.sonar.plugins.python.api.tree.NumericLiteral;
31+
import org.sonar.plugins.python.api.tree.SetLiteral;
32+
import org.sonar.plugins.python.api.tree.StringLiteral;
33+
import org.sonar.plugins.python.api.tree.Tree;
34+
import org.sonar.plugins.python.api.tree.Tuple;
2735
import org.sonar.python.semantic.ProjectLevelSymbolTable;
2836
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
2937
import org.sonar.python.semantic.v2.SymbolTableBuilderV2;
3038
import org.sonar.python.semantic.v2.TypeInferenceV2;
31-
32-
import static org.sonar.python.PythonTestUtils.parseWithoutSymbols;
39+
import org.sonar.python.tree.TreeUtils;
3340

3441
import static org.assertj.core.api.Assertions.assertThat;
42+
import static org.sonar.python.PythonTestUtils.parseWithoutSymbols;
3543

3644

3745
public class ObjectTypeTest {
@@ -87,6 +95,75 @@ def foo(self): ...
8795
assertThat(aType.hasMember("foo")).isEqualTo(TriBool.UNKNOWN);
8896
}
8997

98+
@Test
99+
void literalTypes() {
100+
FileInput fileInput = parseAndInferTypes("\"hello\"");
101+
StringLiteral stringLiteral = (StringLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.STRING_LITERAL)).get();
102+
ObjectType stringLiteralType = (ObjectType) stringLiteral.typeV2();
103+
assertThat(stringLiteralType.displayName()).isEqualTo("str");
104+
105+
fileInput = parseAndInferTypes("(1, 2, 3)");
106+
Tuple intTuple = (Tuple) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.TUPLE)).get();
107+
ObjectType intTupleType = (ObjectType) intTuple.typeV2();
108+
assertThat(intTupleType.displayName()).isEqualTo("tuple");
109+
assertThat(intTupleType.attributes()).extracting(PythonType::displayName).containsExactly("int");
110+
111+
fileInput = parseAndInferTypes("(1, \"hello\")");
112+
Tuple intStrTuple = (Tuple) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.TUPLE)).get();
113+
ObjectType intStrTupleType = (ObjectType) intStrTuple.typeV2();
114+
assertThat(intStrTupleType.displayName()).isEqualTo("tuple");
115+
assertThat(intStrTupleType.attributes()).isEmpty();
116+
117+
fileInput = parseAndInferTypes("(foo(),)");
118+
Tuple unknownTuple = (Tuple) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.TUPLE)).get();
119+
ObjectType unknownTupleType = (ObjectType) unknownTuple.typeV2();
120+
assertThat(unknownTupleType.displayName()).isEqualTo("tuple");
121+
assertThat(unknownTupleType.attributes()).isEmpty();
122+
123+
fileInput = parseAndInferTypes("{1, 2, 3}");
124+
SetLiteral setLiteral = (SetLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.SET_LITERAL)).get();
125+
ObjectType setLiteralType = (ObjectType) setLiteral.typeV2();
126+
assertThat(setLiteralType.displayName()).isEqualTo("set");
127+
128+
fileInput = parseAndInferTypes("{\"my_key\": 42}");
129+
DictionaryLiteral dictionaryLiteral = (DictionaryLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.DICTIONARY_LITERAL)).get();
130+
ObjectType dictionaryLiteralType = (ObjectType) dictionaryLiteral.typeV2();
131+
assertThat(dictionaryLiteralType.displayName()).isEqualTo("dict");
132+
133+
fileInput = parseAndInferTypes("None");
134+
NoneExpression noneExpression = (NoneExpression) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NONE)).get();
135+
ObjectType noneExpressionType = (ObjectType) noneExpression.typeV2();
136+
assertThat(noneExpressionType.displayName()).isEqualTo("NoneType");
137+
138+
fileInput = parseAndInferTypes("unknown");
139+
Name name = (Name) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NAME)).get();
140+
UnknownType nameType = (UnknownType) name.typeV2();
141+
assertThat(nameType.displayName()).isEqualTo("UnknownType");
142+
}
143+
144+
@Test
145+
void numericLiteralTypes() {
146+
FileInput fileInput = parseAndInferTypes("42");
147+
NumericLiteral intLiteral = (NumericLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NUMERIC_LITERAL)).get();
148+
ObjectType intLiteralType = (ObjectType) intLiteral.typeV2();
149+
assertThat(intLiteralType.displayName()).isEqualTo("int");
150+
151+
fileInput = parseAndInferTypes("42.5");
152+
NumericLiteral floatLiteral = (NumericLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NUMERIC_LITERAL)).get();
153+
ObjectType floatLiteralType = (ObjectType) floatLiteral.typeV2();
154+
assertThat(floatLiteralType.displayName()).isEqualTo("float");
155+
156+
fileInput = parseAndInferTypes("4e2");
157+
NumericLiteral exponentLiteral = (NumericLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NUMERIC_LITERAL)).get();
158+
ObjectType exponentLiteralType = (ObjectType) exponentLiteral.typeV2();
159+
assertThat(exponentLiteralType.displayName()).isEqualTo("float");
160+
161+
fileInput = parseAndInferTypes("42j");
162+
NumericLiteral complexLiteral = (NumericLiteral) TreeUtils.firstChild(fileInput, t -> t.is(Tree.Kind.NUMERIC_LITERAL)).get();
163+
ObjectType complexLiteralType = (ObjectType) complexLiteral.typeV2();
164+
assertThat(complexLiteralType.displayName()).isEqualTo("complex");
165+
}
166+
90167
@Test
91168
void objectType_of_unknown() {
92169
// TODO: Ensure this is the behavior we want (do we even want it possible to have object of unknown? Maybe replace with UnionType when implemented

0 commit comments

Comments
 (0)