Skip to content

Commit 870af20

Browse files
SONARPY-2182 Fix FP on S5756 when calling TypedDict (#2036)
1 parent 457a6bc commit 870af20

File tree

4 files changed

+59
-7
lines changed

4 files changed

+59
-7
lines changed

its/ruling/src/test/resources/expected/python-S5756.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,13 +383,19 @@ def no_fp_on_decorated_classes():
383383
employee() # FN
384384

385385

386-
def fp_typed_dict():
386+
def typing_extensions_typed_dict():
387387
from typing_extensions import TypedDict
388388
# TypedDict is defined as "TypedDict: object" in typing_extensions.pyi
389389
# Despite actually being a function
390-
x = TypedDict('x', {'a': int, 'b': str}) # Noncompliant
390+
x = TypedDict('x', {'a': int, 'b': str}) # OK
391391

392392

393+
def typing_typed_dict():
394+
from typing_extensions import TypedDict
395+
# TypedDict is defined as "TypedDict: object" in typing.pyi
396+
# Despite actually being a function
397+
x = TypedDict('x', {'a': int, 'b': str}) # OK
398+
393399
def function_type_is_callable():
394400
import unittest
395401
# unittest.skip() returns a Callable

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@ public class VarSymbolToDescriptorConverter {
2828
public Descriptor convert(SymbolsProtos.VarSymbol varSymbol) {
2929
var fullyQualifiedName = TypeShedUtils.normalizedFqn(varSymbol.getFullyQualifiedName());
3030
var typeAnnotation = TypeShedUtils.getTypesNormalizedFqn(varSymbol.getTypeAnnotation());
31+
if (isTypeAnnotationKnownToBeIncorrect(fullyQualifiedName)) {
32+
return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, null);
33+
}
3134
return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, typeAnnotation);
3235
}
3336

37+
private static boolean isTypeAnnotationKnownToBeIncorrect(String fullyQualifiedName) {
38+
// TypedDict is defined to have type "object" in Typeshed, which is incorrect and leads to FPs
39+
return "typing.TypedDict".equals(fullyQualifiedName) || "typing_extensions.TypedDict".equals(fullyQualifiedName);
40+
}
41+
3442
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,47 @@ void builtinVarTest() {
6060
Assertions.assertThat(descriptor.annotatedType()).isEqualTo("int");
6161
}
6262

63+
@Test
64+
void test_typed_dict_exception() {
65+
var converter = new VarSymbolToDescriptorConverter();
66+
var symbol = SymbolsProtos.VarSymbol.newBuilder()
67+
.setName("TypedDict")
68+
.setFullyQualifiedName("typing.TypedDict")
69+
.setTypeAnnotation(SymbolsProtos.Type.newBuilder()
70+
.setFullyQualifiedName("something")
71+
.build())
72+
.build();
73+
74+
var descriptor = (VariableDescriptor) converter.convert(symbol);
75+
Assertions.assertThat(descriptor.name()).isEqualTo("TypedDict");
76+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("typing.TypedDict");
77+
Assertions.assertThat(descriptor.annotatedType()).isNull();
78+
79+
symbol = SymbolsProtos.VarSymbol.newBuilder()
80+
.setName("TypedDict")
81+
.setFullyQualifiedName("typing_extensions.TypedDict")
82+
.setTypeAnnotation(SymbolsProtos.Type.newBuilder()
83+
.setFullyQualifiedName("something")
84+
.build())
85+
.build();
86+
87+
descriptor = (VariableDescriptor) converter.convert(symbol);
88+
Assertions.assertThat(descriptor.name()).isEqualTo("TypedDict");
89+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("typing_extensions.TypedDict");
90+
Assertions.assertThat(descriptor.annotatedType()).isNull();
91+
92+
symbol = SymbolsProtos.VarSymbol.newBuilder()
93+
.setName("TypedDict")
94+
.setFullyQualifiedName("unrelated.TypedDict")
95+
.setTypeAnnotation(SymbolsProtos.Type.newBuilder()
96+
.setFullyQualifiedName("something")
97+
.build())
98+
.build();
99+
100+
descriptor = (VariableDescriptor) converter.convert(symbol);
101+
Assertions.assertThat(descriptor.name()).isEqualTo("TypedDict");
102+
Assertions.assertThat(descriptor.fullyQualifiedName()).isEqualTo("unrelated.TypedDict");
103+
Assertions.assertThat(descriptor.annotatedType()).isEqualTo("something");
104+
}
105+
63106
}

0 commit comments

Comments
 (0)