Skip to content

Commit 000c244

Browse files
SONARPY-1803 Store type definition location in the new type model (#1780)
1 parent e7c6574 commit 000c244

File tree

19 files changed

+127
-78
lines changed

19 files changed

+127
-78
lines changed

python-checks/src/main/java/org/sonar/python/checks/NonCallableCalledCheck.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,14 @@
2121

2222
import javax.annotation.Nullable;
2323
import org.sonar.check.Rule;
24-
import org.sonar.plugins.python.api.LocationInFile;
2524
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
2625
import org.sonar.plugins.python.api.tree.CallExpression;
2726
import org.sonar.plugins.python.api.tree.Expression;
2827
import org.sonar.plugins.python.api.tree.Tree;
29-
import org.sonar.plugins.python.api.types.InferredType;
3028
import org.sonar.python.types.v2.PythonType;
3129
import org.sonar.python.types.v2.TriBool;
3230

3331
import static org.sonar.python.tree.TreeUtils.nameFromExpression;
34-
import static org.sonar.python.types.InferredTypes.typeClassLocation;
3532

3633
@Rule(key = "S5756")
3734
public class NonCallableCalledCheck extends PythonSubscriptionCheck {
@@ -41,15 +38,12 @@ public void initialize(Context context) {
4138
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
4239
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
4340
Expression callee = callExpression.callee();
44-
InferredType calleeType = callee.type();
4541
PythonType type = callee.typeV2();
4642
if (isNonCallableType(type)) {
4743
String name = nameFromExpression(callee);
4844
PreciseIssue preciseIssue = ctx.addIssue(callee, message(type, name));
49-
LocationInFile location = typeClassLocation(calleeType);
50-
if (location != null) {
51-
preciseIssue.secondary(location, "Definition.");
52-
}
45+
type.definitionLocation()
46+
.ifPresent(location -> preciseIssue.secondary(location, "Definition."));
5347
}
5448
});
5549
}

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

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,5 @@
1919
*/
2020
package org.sonar.plugins.python.api;
2121

22-
public class LocationInFile {
23-
private final String fileId;
24-
private final int startLine;
25-
private final int startLineOffset;
26-
private final int endLine;
27-
private final int endLineOffset;
28-
29-
public LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
30-
this.fileId = fileId;
31-
this.startLine = startLine;
32-
this.startLineOffset = startLineOffset;
33-
this.endLine = endLine;
34-
this.endLineOffset = endLineOffset;
35-
}
36-
37-
public String fileId() {
38-
return fileId;
39-
}
40-
41-
public int startLine() {
42-
return startLine;
43-
}
44-
45-
public int startLineOffset() {
46-
return startLineOffset;
47-
}
48-
49-
public int endLine() {
50-
return endLine;
51-
}
52-
53-
public int endLineOffset() {
54-
return endLineOffset;
55-
}
22+
public record LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
5623
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
4949
symbolTableBuilder.visitFileInput(rootTree);
5050
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
5151
symbolTableBuilderV2.visitFileInput(rootTree);
52-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty())));
52+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile));
5353
}
5454

5555
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
@@ -60,7 +60,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
6060
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
6161
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
6262
symbolTableBuilderV2.visitFileInput(rootTree);
63-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable)));
63+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
6464
}
6565

6666
public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
@@ -71,7 +71,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
7171
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
7272
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
7373
symbolTableBuilderV2.visitFileInput(rootTree);
74-
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable)));
74+
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
7575
}
7676

7777
public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.HashSet;
2424
import java.util.List;
2525
import java.util.Set;
26+
import javax.annotation.Nullable;
27+
import org.sonar.plugins.python.api.LocationInFile;
2628
import org.sonar.python.types.v2.ClassType;
2729
import org.sonar.python.types.v2.Member;
2830
import org.sonar.python.types.v2.PythonType;
@@ -35,17 +37,24 @@ public class ClassTypeBuilder implements TypeBuilder<ClassType> {
3537
List<PythonType> attributes = new ArrayList<>();
3638
List<PythonType> superClasses = new ArrayList<>();
3739
List<PythonType> metaClasses = new ArrayList<>();
40+
LocationInFile definitionLocation;
3841

3942
@Override
4043
public ClassType build() {
41-
return new ClassType(name, members, attributes, superClasses, metaClasses);
44+
return new ClassType(name, members, attributes, superClasses, metaClasses, definitionLocation);
4245
}
4346

44-
public ClassTypeBuilder setName(String name) {
47+
public ClassTypeBuilder withName(String name) {
4548
this.name = name;
4649
return this;
4750
}
4851

52+
@Override
53+
public ClassTypeBuilder withDefinitionLocation(@Nullable LocationInFile definitionLocation) {
54+
this.definitionLocation = definitionLocation;
55+
return this;
56+
}
57+
4958
public List<PythonType> superClasses() {
5059
return superClasses;
5160
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Objects;
2525
import java.util.Optional;
2626
import javax.annotation.Nullable;
27+
import org.sonar.plugins.python.api.LocationInFile;
2728
import org.sonar.plugins.python.api.tree.AnyParameter;
2829
import org.sonar.plugins.python.api.tree.FunctionDef;
2930
import org.sonar.plugins.python.api.tree.Name;
@@ -48,6 +49,7 @@ public class FunctionTypeBuilder implements TypeBuilder<FunctionType> {
4849
private boolean isInstanceMethod;
4950
private PythonType owner;
5051
private PythonType returnType = PythonType.UNKNOWN;
52+
private LocationInFile definitionLocation;
5153

5254
private static final String CLASS_METHOD_DECORATOR = "classmethod";
5355
private static final String STATIC_METHOD_DECORATOR = "staticmethod";
@@ -108,8 +110,16 @@ public FunctionTypeBuilder withReturnType(PythonType returnType) {
108110
return this;
109111
}
110112

113+
@Override
114+
public FunctionTypeBuilder withDefinitionLocation(@Nullable LocationInFile definitionLocation) {
115+
this.definitionLocation = definitionLocation;
116+
return this;
117+
}
118+
111119
public FunctionType build() {
112-
return new FunctionType(name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter, owner);
120+
return new FunctionType(
121+
name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter, owner, definitionLocation
122+
);
113123
}
114124

115125
private static boolean isInstanceMethod(FunctionDef functionDef) {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ private static PythonType convertToFunctionType(FunctionSymbol symbol, Map<Symbo
121121
.withAsynchronous(symbol.isAsynchronous())
122122
.withHasDecorators(symbol.hasDecorators())
123123
.withInstanceMethod(symbol.isInstanceMethod())
124-
.withHasVariadicParameter(symbol.hasVariadicParameter());
124+
.withHasVariadicParameter(symbol.hasVariadicParameter())
125+
.withDefinitionLocation(symbol.definitionLocation());
125126
FunctionType functionType = functionTypeBuilder.build();
126127
createdTypesBySymbol.put(symbol, functionType);
127128
return functionType;
@@ -142,7 +143,7 @@ private PythonType convertToClassType(ClassSymbol symbol, Map<Symbol, PythonType
142143
if (createdTypesBySymbol.containsKey(symbol)) {
143144
return createdTypesBySymbol.get(symbol);
144145
}
145-
ClassType classType = new ClassType(symbol.name());
146+
ClassType classType = new ClassType(symbol.name(), symbol.definitionLocation());
146147
createdTypesBySymbol.put(symbol, classType);
147148
Set<Member> members =
148149
symbol.declaredMembers().stream().map(m -> new Member(m.name(), convertToType(m, createdTypesBySymbol))).collect(Collectors.toSet());

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
*/
2020
package org.sonar.python.semantic.v2;
2121

22+
import org.sonar.plugins.python.api.LocationInFile;
2223
import org.sonar.python.types.v2.PythonType;
2324

2425
public interface TypeBuilder<T extends PythonType> {
2526

2627
T build();
28+
29+
TypeBuilder<T> withDefinitionLocation(LocationInFile definitionLocation);
2730
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
*/
2020
package org.sonar.python.semantic.v2;
2121

22+
import java.nio.file.Path;
2223
import java.util.ArrayDeque;
2324
import java.util.ArrayList;
2425
import java.util.Collection;
2526
import java.util.Deque;
2627
import java.util.List;
2728
import java.util.Optional;
2829
import javax.annotation.Nullable;
30+
import org.sonar.plugins.python.api.PythonFile;
2931
import org.sonar.plugins.python.api.tree.ArgList;
3032
import org.sonar.plugins.python.api.tree.AssignmentStatement;
3133
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
@@ -65,14 +67,20 @@
6567
import org.sonar.python.types.v2.ObjectType;
6668
import org.sonar.python.types.v2.PythonType;
6769

70+
import static org.sonar.python.semantic.SymbolUtils.pathOf;
71+
import static org.sonar.python.tree.TreeUtils.locationInFile;
72+
6873
public class TypeInferenceV2 extends BaseTreeVisitor {
6974

7075
private final ProjectLevelTypeTable projectLevelTypeTable;
76+
private final String fileId;
7177

7278
private final Deque<PythonType> typeStack = new ArrayDeque<>();
7379

74-
public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable) {
80+
public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile) {
7581
this.projectLevelTypeTable = projectLevelTypeTable;
82+
Path path = pathOf(pythonFile);
83+
this.fileId = path != null ? path.toString() : pythonFile.toString();
7684
}
7785

7886
@Override
@@ -151,7 +159,9 @@ public void visitListLiteral(ListLiteral listLiteral) {
151159
public void visitClassDef(ClassDef classDef) {
152160
scan(classDef.args());
153161
Name name = classDef.name();
154-
ClassTypeBuilder classTypeBuilder = new ClassTypeBuilder().setName(name.name());
162+
ClassTypeBuilder classTypeBuilder = new ClassTypeBuilder()
163+
.withName(name.name())
164+
.withDefinitionLocation(locationInFile(classDef.name(), fileId));
155165
resolveTypeHierarchy(classDef, classTypeBuilder);
156166
ClassType type = classTypeBuilder.build();
157167
((NameImpl) name).typeV2(type);
@@ -212,7 +222,9 @@ public void visitFunctionDef(FunctionDef functionDef) {
212222
}
213223

214224
private FunctionType buildFunctionType(FunctionDef functionDef) {
215-
FunctionTypeBuilder functionTypeBuilder = new FunctionTypeBuilder().fromFunctionDef(functionDef);
225+
FunctionTypeBuilder functionTypeBuilder = new FunctionTypeBuilder()
226+
.fromFunctionDef(functionDef)
227+
.withDefinitionLocation(locationInFile(functionDef.name(), fileId));
216228
ClassType owner = null;
217229
if (currentType() instanceof ClassType classType) {
218230
owner = classType;

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.Optional;
2828
import java.util.Set;
2929
import java.util.stream.Collectors;
30+
import javax.annotation.Nullable;
31+
import org.sonar.plugins.python.api.LocationInFile;
3032

3133
/**
3234
* ClassType
@@ -36,14 +38,15 @@ public record ClassType(
3638
Set<Member> members,
3739
List<PythonType> attributes,
3840
List<PythonType> superClasses,
39-
List<PythonType> metaClasses) implements PythonType {
41+
List<PythonType> metaClasses,
42+
@Nullable LocationInFile locationInFile) implements PythonType {
4043

4144
public ClassType(String name) {
42-
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
45+
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null);
4346
}
4447

45-
public ClassType(String name, List<PythonType> attributes) {
46-
this(name, new HashSet<>(), attributes, new ArrayList<>(), new ArrayList<>());
48+
public ClassType(String name, @Nullable LocationInFile locationInFile) {
49+
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), locationInFile);
4750
}
4851

4952
@Override
@@ -159,6 +162,11 @@ public TriBool instancesHaveMember(String memberName) {
159162
return resolveMember(memberName).isPresent() ? TriBool.TRUE : TriBool.FALSE;
160163
}
161164

165+
@Override
166+
public Optional<LocationInFile> definitionLocation() {
167+
return Optional.ofNullable(this.locationInFile);
168+
}
169+
162170
@Override
163171
public boolean equals(Object o) {
164172
if (this == o) return true;

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Objects;
2424
import java.util.Optional;
2525
import javax.annotation.Nullable;
26+
import org.sonar.plugins.python.api.LocationInFile;
2627

2728
/**
2829
* FunctionType
@@ -36,7 +37,8 @@ public record FunctionType(
3637
boolean hasDecorators,
3738
boolean isInstanceMethod,
3839
boolean hasVariadicParameter,
39-
@Nullable PythonType owner
40+
@Nullable PythonType owner,
41+
@Nullable LocationInFile locationInFile
4042
) implements PythonType {
4143

4244
@Override
@@ -59,6 +61,11 @@ public Optional<String> displayName() {
5961
return Optional.of("Callable");
6062
}
6163

64+
@Override
65+
public Optional<LocationInFile> definitionLocation() {
66+
return Optional.ofNullable(this.locationInFile);
67+
}
68+
6269
@Override
6370
public int hashCode() {
6471
return Objects.hash(name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter);

0 commit comments

Comments
 (0)