Skip to content

Commit efeeede

Browse files
SONARPY-951 Translate starred parameter types to descriptors (#1038)
1 parent b33dd7f commit efeeede

File tree

6 files changed

+117
-42
lines changed

6 files changed

+117
-42
lines changed

python-frontend/src/main/java/org/sonar/plugins/python/api/symbols/FunctionSymbol.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ interface Parameter {
6969
boolean isVariadic();
7070
boolean isKeywordOnly();
7171
boolean isPositionalOnly();
72+
73+
/**
74+
* Returns true for **kwargs
75+
*/
76+
boolean isKeywordVariadic();
77+
78+
/**
79+
* Returns true for *args
80+
*/
81+
boolean isPositionalVariadic();
7282
@CheckForNull
7383
LocationInFile location();
7484
}

python-frontend/src/main/java/org/sonar/python/index/DescriptorUtils.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.sonar.python.semantic.ProjectLevelSymbolTable;
3838
import org.sonar.python.semantic.SymbolImpl;
3939
import org.sonar.python.types.DeclaredType;
40+
import org.sonar.python.types.InferredTypes;
4041

4142
import static org.sonar.python.semantic.SymbolUtils.typeshedSymbolWithFQN;
4243
import static org.sonar.python.types.InferredTypes.anyType;
@@ -107,9 +108,10 @@ private static List<FunctionDescriptor.Parameter> parameters(List<FunctionSymbol
107108
parameter.name(),
108109
((FunctionSymbolImpl.ParameterImpl) parameter).annotatedTypeName(),
109110
parameter.hasDefaultValue(),
110-
parameter.isVariadic(),
111111
parameter.isKeywordOnly(),
112112
parameter.isPositionalOnly(),
113+
parameter.isPositionalVariadic(),
114+
parameter.isKeywordVariadic(),
113115
parameter.location()
114116
)).collect(Collectors.toList());
115117
}
@@ -185,18 +187,29 @@ private static FunctionSymbolImpl createFunctionSymbol(FunctionDescriptor functi
185187

186188
private static void addParameters(FunctionSymbolImpl functionSymbol, FunctionDescriptor functionDescriptor,
187189
ProjectLevelSymbolTable projectLevelSymbolTable, Map<String, Symbol> createdSymbols) {
188-
functionDescriptor.parameters().stream().map(p -> {
189-
FunctionSymbolImpl.ParameterImpl parameter = new FunctionSymbolImpl.ParameterImpl(p);
190-
Symbol existingSymbol = createdSymbols.get(p.annotatedType());
191-
Symbol typeSymbol = existingSymbol != null ? existingSymbol : projectLevelSymbolTable.getSymbol(p.annotatedType(), null, createdSymbols);
190+
functionDescriptor.parameters().stream().map(parameterDescriptor -> {
191+
FunctionSymbolImpl.ParameterImpl parameter = new FunctionSymbolImpl.ParameterImpl(parameterDescriptor);
192+
setParameterType(parameter, parameterDescriptor.annotatedType(), projectLevelSymbolTable, createdSymbols);
193+
return parameter;
194+
}).forEach(functionSymbol::addParameter);
195+
}
196+
197+
private static void setParameterType(FunctionSymbolImpl.ParameterImpl parameter, String annotatedType,
198+
ProjectLevelSymbolTable projectLevelSymbolTable, Map<String, Symbol> createdSymbols) {
199+
InferredType declaredType;
200+
if (parameter.isKeywordVariadic()) {
201+
declaredType = InferredTypes.DICT;
202+
} else if (parameter.isPositionalVariadic()) {
203+
declaredType = InferredTypes.TUPLE;
204+
} else {
205+
Symbol existingSymbol = createdSymbols.get(annotatedType);
206+
Symbol typeSymbol = existingSymbol != null ? existingSymbol : projectLevelSymbolTable.getSymbol(annotatedType, null, createdSymbols);
192207
String annotatedTypeName = parameter.annotatedTypeName();
193208
if (typeSymbol == null && annotatedTypeName != null) {
194209
typeSymbol = typeshedSymbolWithFQN(annotatedTypeName);
195210
}
196-
// TODO: SONARPY-951 starred parameters should be mapped to the appropriate runtime type
197-
InferredType declaredType = typeSymbol == null ? anyType() : new DeclaredType(typeSymbol, Collections.emptyList());
198-
parameter.setDeclaredType(declaredType);
199-
return parameter;
200-
}).forEach(functionSymbol::addParameter);
211+
declaredType = typeSymbol == null ? anyType() : new DeclaredType(typeSymbol, Collections.emptyList());
212+
}
213+
parameter.setDeclaredType(declaredType);
201214
}
202215
}

python-frontend/src/main/java/org/sonar/python/index/FunctionDescriptor.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,19 @@ public static class Parameter {
104104
private final String name;
105105
private final String annotatedType;
106106
private final boolean hasDefaultValue;
107-
private final boolean isVariadic;
107+
private final boolean isKeywordVariadic;
108+
private final boolean isPositionalVariadic;
108109
private final boolean isKeywordOnly;
109110
private final boolean isPositionalOnly;
110111
private final LocationInFile location;
111112

112113
public Parameter(@Nullable String name, @Nullable String annotatedType, boolean hasDefaultValue,
113-
boolean isVariadic, boolean isKeywordOnly, boolean isPositionalOnly, @Nullable LocationInFile location) {
114+
boolean isKeywordOnly, boolean isPositionalOnly, boolean isPositionalVariadic, boolean isKeywordVariadic, @Nullable LocationInFile location) {
114115
this.name = name;
115116
this.annotatedType = annotatedType;
116117
this.hasDefaultValue = hasDefaultValue;
117-
this.isVariadic = isVariadic;
118+
this.isKeywordVariadic = isKeywordVariadic;
119+
this.isPositionalVariadic = isPositionalVariadic;
118120
this.isKeywordOnly = isKeywordOnly;
119121
this.isPositionalOnly = isPositionalOnly;
120122
this.location = location;
@@ -134,7 +136,7 @@ public boolean hasDefaultValue() {
134136
}
135137

136138
public boolean isVariadic() {
137-
return isVariadic;
139+
return isKeywordVariadic || isPositionalVariadic;
138140
}
139141

140142
public boolean isKeywordOnly() {
@@ -145,6 +147,14 @@ public boolean isPositionalOnly() {
145147
return isPositionalOnly;
146148
}
147149

150+
public boolean isKeywordVariadic() {
151+
return isKeywordVariadic;
152+
}
153+
154+
public boolean isPositionalVariadic() {
155+
return isPositionalVariadic;
156+
}
157+
148158
@CheckForNull
149159
public LocationInFile location() {
150160
return location;

python-frontend/src/main/java/org/sonar/python/semantic/FunctionSymbolImpl.java

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,19 @@ public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, bool
110110
ParameterState parameterState = new ParameterState();
111111
parameterState.positionalOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.POSITIONAL_ONLY;
112112
parameterState.keywordOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.KEYWORD_ONLY;
113-
boolean isVariadic = (parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_KEYWORD) || parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_POSITIONAL;
114-
hasVariadicParameter |= isVariadic;
115-
ParameterImpl parameter = new ParameterImpl(
116-
parameterSymbol.getName(), anyType(), null, parameterSymbol.getHasDefault(), isVariadic, parameterState, null, parameterSymbol.getTypeAnnotation());
113+
boolean isKeywordVariadic = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_KEYWORD;
114+
boolean isPositionalVariadic = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_POSITIONAL;
115+
hasVariadicParameter |= isKeywordVariadic || isPositionalVariadic;
116+
InferredType declaredType;
117+
if (isPositionalVariadic) {
118+
declaredType = InferredTypes.TUPLE;
119+
} else if (isKeywordVariadic) {
120+
declaredType = InferredTypes.DICT;
121+
} else {
122+
declaredType = anyType();
123+
}
124+
ParameterImpl parameter = new ParameterImpl(parameterSymbol.getName(), declaredType, null, parameterSymbol.getHasDefault(), parameterState,
125+
isKeywordVariadic, isPositionalVariadic, parameterSymbol.getTypeAnnotation(), null);
117126
parameters.add(parameter);
118127
}
119128
functionDefinitionLocation = null;
@@ -209,7 +218,7 @@ private void createParameterNames(List<AnyParameter> parameterTrees, @Nullable S
209218
if (anyParameter.is(Tree.Kind.PARAMETER)) {
210219
addParameter((org.sonar.plugins.python.api.tree.Parameter) anyParameter, fileId, parameterState);
211220
} else {
212-
parameters.add(new ParameterImpl(null, InferredTypes.anyType(), null, false, false, parameterState, locationInFile(anyParameter, fileId), null));
221+
parameters.add(new ParameterImpl(null, InferredTypes.anyType(), null, false, parameterState, false, false, null, locationInFile(anyParameter, fileId)));
213222
}
214223
}
215224
}
@@ -218,9 +227,9 @@ private void addParameter(org.sonar.plugins.python.api.tree.Parameter parameter,
218227
Name parameterName = parameter.name();
219228
Token starToken = parameter.starToken();
220229
if (parameterName != null) {
221-
InferredType declaredType = getParameterType(parameter, starToken);
222-
this.parameters.add(new ParameterImpl(parameterName.name(), declaredType, annotatedTypeName(parameter.typeAnnotation()), parameter.defaultValue() != null,
223-
starToken != null, parameterState, locationInFile(parameter, fileId), null));
230+
ParameterType parameterType = getParameterType(parameter);
231+
this.parameters.add(new ParameterImpl(parameterName.name(), parameterType.inferredType, annotatedTypeName(parameter.typeAnnotation()), parameter.defaultValue() != null,
232+
parameterState, parameterType.isKeywordVariadic, parameterType.isPositionalVariadic, null, locationInFile(parameter, fileId)));
224233
if (starToken != null) {
225234
hasVariadicParameter = true;
226235
parameterState.keywordOnly = true;
@@ -237,24 +246,31 @@ private void addParameter(org.sonar.plugins.python.api.tree.Parameter parameter,
237246
}
238247
}
239248

240-
private InferredType getParameterType(org.sonar.plugins.python.api.tree.Parameter parameter, @Nullable Token starToken) {
249+
private ParameterType getParameterType(org.sonar.plugins.python.api.tree.Parameter parameter) {
250+
InferredType inferredType = InferredTypes.anyType();
251+
boolean isPositionalVariadic = false;
252+
boolean isKeywordVariadic = false;
253+
Token starToken = parameter.starToken();
241254
if (starToken != null) {
242255
// https://docs.python.org/3/reference/compound_stmts.html#function-definitions
256+
hasVariadicParameter = true;
243257
if ("*".equals(starToken.value())) {
244258
// if the form “*identifier” is present, it is initialized to a tuple receiving any excess positional parameters
245-
return InferredTypes.TUPLE;
259+
isPositionalVariadic = true;
260+
inferredType = InferredTypes.TUPLE;
246261
}
247262
if ("**".equals(starToken.value())) {
248263
// If the form “**identifier” is present, it is initialized to a new ordered mapping receiving any excess keyword arguments
249-
return InferredTypes.DICT;
264+
isKeywordVariadic = true;
265+
inferredType = InferredTypes.DICT;
266+
}
267+
} else {
268+
TypeAnnotation typeAnnotation = parameter.typeAnnotation();
269+
if (typeAnnotation != null) {
270+
inferredType = isStub ? fromTypeshedTypeAnnotation(typeAnnotation) : fromTypeAnnotation(typeAnnotation);
250271
}
251272
}
252-
InferredType declaredType = InferredTypes.anyType();
253-
TypeAnnotation typeAnnotation = parameter.typeAnnotation();
254-
if (typeAnnotation != null) {
255-
declaredType = isStub ? fromTypeshedTypeAnnotation(typeAnnotation) : fromTypeAnnotation(typeAnnotation);
256-
}
257-
return declaredType;
273+
return new ParameterType(inferredType, isKeywordVariadic, isPositionalVariadic);
258274
}
259275

260276
@Override
@@ -309,6 +325,18 @@ public InferredType declaredReturnType() {
309325
}
310326
return declaredReturnType;
311327
}
328+
329+
static class ParameterType {
330+
InferredType inferredType;
331+
boolean isPositionalVariadic;
332+
boolean isKeywordVariadic;
333+
334+
public ParameterType(InferredType inferredType, boolean isKeywordVariadic, boolean isPositionalVariadic) {
335+
this.inferredType = inferredType;
336+
this.isKeywordVariadic = isKeywordVariadic;
337+
this.isPositionalVariadic = isPositionalVariadic;
338+
}
339+
}
312340

313341
public void setAnnotatedReturnTypeName(@Nullable TypeAnnotation returnTypeAnnotation) {
314342
annotatedReturnTypeName = annotatedTypeName(returnTypeAnnotation);
@@ -354,18 +382,20 @@ public static class ParameterImpl implements Parameter {
354382
private SymbolsProtos.Type protobufType;
355383
private final String annotatedTypeName;
356384
private final boolean hasDefaultValue;
357-
private final boolean isVariadic;
385+
private final boolean isKeywordVariadic;
386+
private final boolean isPositionalVariadic;
358387
private final boolean isKeywordOnly;
359388
private final boolean isPositionalOnly;
360389
private final LocationInFile location;
361390
private boolean hasReadDeclaredType = false;
362391

363392
ParameterImpl(@Nullable String name, InferredType declaredType, @Nullable String annotatedTypeName, boolean hasDefaultValue,
364-
boolean isVariadic, ParameterState parameterState, @Nullable LocationInFile location, @Nullable SymbolsProtos.Type protobufType) {
393+
ParameterState parameterState, boolean isKeywordVariadic, boolean isPositionalVariadic, @Nullable SymbolsProtos.Type protobufType, @Nullable LocationInFile location) {
365394
this.name = name;
366395
this.declaredType = declaredType;
367396
this.hasDefaultValue = hasDefaultValue;
368-
this.isVariadic = isVariadic;
397+
this.isKeywordVariadic = isKeywordVariadic;
398+
this.isPositionalVariadic = isPositionalVariadic;
369399
this.isKeywordOnly = parameterState.keywordOnly;
370400
this.isPositionalOnly = parameterState.positionalOnly;
371401
this.location = location;
@@ -376,7 +406,8 @@ public static class ParameterImpl implements Parameter {
376406
public ParameterImpl(FunctionDescriptor.Parameter parameterDescriptor) {
377407
this.name = parameterDescriptor.name();
378408
this.hasDefaultValue = parameterDescriptor.hasDefaultValue();
379-
this.isVariadic = parameterDescriptor.isVariadic();
409+
this.isPositionalVariadic = parameterDescriptor.isPositionalVariadic();
410+
this.isKeywordVariadic = parameterDescriptor.isKeywordVariadic();
380411
this.isKeywordOnly = parameterDescriptor.isKeywordOnly();
381412
this.isPositionalOnly = parameterDescriptor.isPositionalOnly();
382413
this.location = parameterDescriptor.location();
@@ -414,7 +445,7 @@ public boolean hasDefaultValue() {
414445

415446
@Override
416447
public boolean isVariadic() {
417-
return isVariadic;
448+
return isKeywordVariadic || isPositionalVariadic;
418449
}
419450

420451
@Override
@@ -427,6 +458,16 @@ public boolean isPositionalOnly() {
427458
return isPositionalOnly;
428459
}
429460

461+
@Override
462+
public boolean isKeywordVariadic() {
463+
return isKeywordVariadic;
464+
}
465+
466+
@Override
467+
public boolean isPositionalVariadic() {
468+
return isPositionalVariadic;
469+
}
470+
430471
@CheckForNull
431472
@Override
432473
public LocationInFile location() {

python-frontend/src/test/java/org/sonar/python/index/FunctionDescriptorTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,11 @@ public void parameterWithPositional() {
8888

8989
@Test
9090
public void variadicParameter() {
91-
FunctionDescriptor functionDescriptor = lastFunctionDescriptor("def foo(*x): ...");
92-
FunctionDescriptor.Parameter parameter = functionDescriptor.parameters().get(0);
93-
assertThat(parameter.isVariadic()).isTrue();
91+
FunctionDescriptor functionDescriptor = lastFunctionDescriptor("def foo(*x, **y): ...");
92+
FunctionDescriptor.Parameter parameter1 = functionDescriptor.parameters().get(0);
93+
FunctionDescriptor.Parameter parameter2 = functionDescriptor.parameters().get(1);
94+
assertThat(parameter1.isVariadic()).isTrue();
95+
assertThat(parameter2.isVariadic()).isTrue();
9496
}
9597

9698
@Test

python-frontend/src/test/java/org/sonar/python/semantic/ProjectLevelSymbolTableTest.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,13 +689,12 @@ public void loop_in_class_inheritance() {
689689
@Test
690690
public void annotated_parameter_is_translated_correctly() {
691691
FileInput tree = parseWithoutSymbols(
692-
"def fn(param: str, **my_dict): ..."
692+
"def fn(param: str, *my_tuple, **my_dict): ..."
693693
);
694694
Set<Symbol> globalSymbols = globalSymbols(tree, "");
695695
assertThat(globalSymbols).extracting(Symbol::name).containsExactly("fn");
696696
FunctionSymbol functionSymbol = ((FunctionSymbol) globalSymbols.stream().findFirst().get());
697-
// TODO: SONARPY-951 starred parameters should be mapped to the appropriate runtime type (InferredTypes.DICT instead of anyType)
698-
assertThat(functionSymbol.parameters()).extracting(FunctionSymbol.Parameter::declaredType).containsExactly(InferredTypes.DECL_STR, InferredTypes.anyType());
697+
assertThat(functionSymbol.parameters()).extracting(FunctionSymbol.Parameter::declaredType).containsExactly(InferredTypes.DECL_STR, InferredTypes.TUPLE, InferredTypes.DICT);
699698
assertThat(globalSymbols).extracting(Symbol::usages).allSatisfy(usages -> assertThat(usages).isEmpty());
700699
}
701700

0 commit comments

Comments
 (0)