Skip to content

Commit

Permalink
SONARPY-1803 Store type definition location in the new type model (#1780
Browse files Browse the repository at this point in the history
)
  • Loading branch information
guillaume-dequenne-sonarsource committed May 1, 2024
1 parent 5ef0ca4 commit d936bd4
Show file tree
Hide file tree
Showing 19 changed files with 127 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,14 @@

import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;

import static org.sonar.python.tree.TreeUtils.nameFromExpression;
import static org.sonar.python.types.InferredTypes.typeClassLocation;

@Rule(key = "S5756")
public class NonCallableCalledCheck extends PythonSubscriptionCheck {
Expand All @@ -41,15 +38,12 @@ public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
Expression callee = callExpression.callee();
InferredType calleeType = callee.type();
PythonType type = callee.typeV2();
if (isNonCallableType(type)) {
String name = nameFromExpression(callee);
PreciseIssue preciseIssue = ctx.addIssue(callee, message(type, name));
LocationInFile location = typeClassLocation(calleeType);
if (location != null) {
preciseIssue.secondary(location, "Definition.");
}
type.definitionLocation()
.ifPresent(location -> preciseIssue.secondary(location, "Definition."));
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,5 @@
*/
package org.sonar.plugins.python.api;

public class LocationInFile {
private final String fileId;
private final int startLine;
private final int startLineOffset;
private final int endLine;
private final int endLineOffset;

public LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
this.fileId = fileId;
this.startLine = startLine;
this.startLineOffset = startLineOffset;
this.endLine = endLine;
this.endLineOffset = endLineOffset;
}

public String fileId() {
return fileId;
}

public int startLine() {
return startLine;
}

public int startLineOffset() {
return startLineOffset;
}

public int endLine() {
return endLine;
}

public int endLineOffset() {
return endLineOffset;
}
public record LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
symbolTableBuilder.visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty())));
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile));
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -60,7 +60,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable)));
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -71,7 +71,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
SymbolTableBuilderV2 symbolTableBuilderV2 = new SymbolTableBuilderV2();
symbolTableBuilderV2.visitFileInput(rootTree);
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable)));
rootTree.accept(new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile));
}

public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.python.types.v2.ClassType;
import org.sonar.python.types.v2.Member;
import org.sonar.python.types.v2.PythonType;
Expand All @@ -35,17 +37,24 @@ public class ClassTypeBuilder implements TypeBuilder<ClassType> {
List<PythonType> attributes = new ArrayList<>();
List<PythonType> superClasses = new ArrayList<>();
List<PythonType> metaClasses = new ArrayList<>();
LocationInFile definitionLocation;

@Override
public ClassType build() {
return new ClassType(name, members, attributes, superClasses, metaClasses);
return new ClassType(name, members, attributes, superClasses, metaClasses, definitionLocation);
}

public ClassTypeBuilder setName(String name) {
public ClassTypeBuilder withName(String name) {
this.name = name;
return this;
}

@Override
public ClassTypeBuilder withDefinitionLocation(@Nullable LocationInFile definitionLocation) {
this.definitionLocation = definitionLocation;
return this;
}

public List<PythonType> superClasses() {
return superClasses;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.plugins.python.api.tree.AnyParameter;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Name;
Expand All @@ -48,6 +49,7 @@ public class FunctionTypeBuilder implements TypeBuilder<FunctionType> {
private boolean isInstanceMethod;
private PythonType owner;
private PythonType returnType = PythonType.UNKNOWN;
private LocationInFile definitionLocation;

private static final String CLASS_METHOD_DECORATOR = "classmethod";
private static final String STATIC_METHOD_DECORATOR = "staticmethod";
Expand Down Expand Up @@ -108,8 +110,16 @@ public FunctionTypeBuilder withReturnType(PythonType returnType) {
return this;
}

@Override
public FunctionTypeBuilder withDefinitionLocation(@Nullable LocationInFile definitionLocation) {
this.definitionLocation = definitionLocation;
return this;
}

public FunctionType build() {
return new FunctionType(name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter, owner);
return new FunctionType(
name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter, owner, definitionLocation
);
}

private static boolean isInstanceMethod(FunctionDef functionDef) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ private static PythonType convertToFunctionType(FunctionSymbol symbol, Map<Symbo
.withAsynchronous(symbol.isAsynchronous())
.withHasDecorators(symbol.hasDecorators())
.withInstanceMethod(symbol.isInstanceMethod())
.withHasVariadicParameter(symbol.hasVariadicParameter());
.withHasVariadicParameter(symbol.hasVariadicParameter())
.withDefinitionLocation(symbol.definitionLocation());
FunctionType functionType = functionTypeBuilder.build();
createdTypesBySymbol.put(symbol, functionType);
return functionType;
Expand All @@ -142,7 +143,7 @@ private PythonType convertToClassType(ClassSymbol symbol, Map<Symbol, PythonType
if (createdTypesBySymbol.containsKey(symbol)) {
return createdTypesBySymbol.get(symbol);
}
ClassType classType = new ClassType(symbol.name());
ClassType classType = new ClassType(symbol.name(), symbol.definitionLocation());
createdTypesBySymbol.put(symbol, classType);
Set<Member> members =
symbol.declaredMembers().stream().map(m -> new Member(m.name(), convertToType(m, createdTypesBySymbol))).collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
*/
package org.sonar.python.semantic.v2;

import org.sonar.plugins.python.api.LocationInFile;
import org.sonar.python.types.v2.PythonType;

public interface TypeBuilder<T extends PythonType> {

T build();

TypeBuilder<T> withDefinitionLocation(LocationInFile definitionLocation);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
*/
package org.sonar.python.semantic.v2;

import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.tree.ArgList;
import org.sonar.plugins.python.api.tree.AssignmentStatement;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
Expand Down Expand Up @@ -65,14 +67,20 @@
import org.sonar.python.types.v2.ObjectType;
import org.sonar.python.types.v2.PythonType;

import static org.sonar.python.semantic.SymbolUtils.pathOf;
import static org.sonar.python.tree.TreeUtils.locationInFile;

public class TypeInferenceV2 extends BaseTreeVisitor {

private final ProjectLevelTypeTable projectLevelTypeTable;
private final String fileId;

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

public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable) {
public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile) {
this.projectLevelTypeTable = projectLevelTypeTable;
Path path = pathOf(pythonFile);
this.fileId = path != null ? path.toString() : pythonFile.toString();
}

@Override
Expand Down Expand Up @@ -151,7 +159,9 @@ public void visitListLiteral(ListLiteral listLiteral) {
public void visitClassDef(ClassDef classDef) {
scan(classDef.args());
Name name = classDef.name();
ClassTypeBuilder classTypeBuilder = new ClassTypeBuilder().setName(name.name());
ClassTypeBuilder classTypeBuilder = new ClassTypeBuilder()
.withName(name.name())
.withDefinitionLocation(locationInFile(classDef.name(), fileId));
resolveTypeHierarchy(classDef, classTypeBuilder);
ClassType type = classTypeBuilder.build();
((NameImpl) name).typeV2(type);
Expand Down Expand Up @@ -212,7 +222,9 @@ public void visitFunctionDef(FunctionDef functionDef) {
}

private FunctionType buildFunctionType(FunctionDef functionDef) {
FunctionTypeBuilder functionTypeBuilder = new FunctionTypeBuilder().fromFunctionDef(functionDef);
FunctionTypeBuilder functionTypeBuilder = new FunctionTypeBuilder()
.fromFunctionDef(functionDef)
.withDefinitionLocation(locationInFile(functionDef.name(), fileId));
ClassType owner = null;
if (currentType() instanceof ClassType classType) {
owner = classType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;

/**
* ClassType
Expand All @@ -36,14 +38,15 @@ public record ClassType(
Set<Member> members,
List<PythonType> attributes,
List<PythonType> superClasses,
List<PythonType> metaClasses) implements PythonType {
List<PythonType> metaClasses,
@Nullable LocationInFile locationInFile) implements PythonType {

public ClassType(String name) {
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), null);
}

public ClassType(String name, List<PythonType> attributes) {
this(name, new HashSet<>(), attributes, new ArrayList<>(), new ArrayList<>());
public ClassType(String name, @Nullable LocationInFile locationInFile) {
this(name, new HashSet<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), locationInFile);
}

@Override
Expand Down Expand Up @@ -159,6 +162,11 @@ public TriBool instancesHaveMember(String memberName) {
return resolveMember(memberName).isPresent() ? TriBool.TRUE : TriBool.FALSE;
}

@Override
public Optional<LocationInFile> definitionLocation() {
return Optional.ofNullable(this.locationInFile);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.LocationInFile;

/**
* FunctionType
Expand All @@ -36,7 +37,8 @@ public record FunctionType(
boolean hasDecorators,
boolean isInstanceMethod,
boolean hasVariadicParameter,
@Nullable PythonType owner
@Nullable PythonType owner,
@Nullable LocationInFile locationInFile
) implements PythonType {

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

@Override
public Optional<LocationInFile> definitionLocation() {
return Optional.ofNullable(this.locationInFile);
}

@Override
public int hashCode() {
return Objects.hash(name, attributes, parameters, returnType, isAsynchronous, hasDecorators, isInstanceMethod, hasVariadicParameter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.sonar.plugins.python.api.LocationInFile;

public record ObjectType(PythonType type, List<PythonType> attributes, List<Member> members) implements PythonType {

Expand Down Expand Up @@ -59,4 +60,9 @@ public TriBool hasMember(String memberName) {
}
return TriBool.UNKNOWN;
}

@Override
public Optional<LocationInFile> definitionLocation() {
return type.definitionLocation();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.sonar.python.types.v2;

import java.util.Optional;
import org.sonar.plugins.python.api.LocationInFile;

/**
* PythonType
Expand Down Expand Up @@ -54,4 +55,8 @@ default Optional<PythonType> resolveMember(String memberName) {
default TriBool hasMember(String memberName) {
return TriBool.UNKNOWN;
}

default Optional<LocationInFile> definitionLocation() {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ void initModuleFullyQualifiedName() {
void globalSymbols() {
String code = "from mod import a, b";
FileInput fileInput = new PythonTreeMaker().fileInput(PythonParser.create().parse(code));
PythonFile pythonFile = mock(PythonFile.class, "my_module.py");
Mockito.when(pythonFile.fileName()).thenReturn("my_module.py");
PythonFile pythonFile = pythonFile("my_module.py");
List<Symbol> modSymbols = Arrays.asList(new SymbolImpl("a", null), new SymbolImpl("b", null));
Map<String, Set<Symbol>> globalSymbols = Collections.singletonMap("mod", new HashSet<>(modSymbols));
new PythonVisitorContext(fileInput, pythonFile, null, "my_package", ProjectLevelSymbolTable.from(globalSymbols), null);
Expand All @@ -98,8 +97,7 @@ void sonar_product() {
ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty();
String myPackage = "my_package";
File workingDirectory = null;
PythonFile pythonFile = mock(PythonFile.class, "my_module.py");
Mockito.when(pythonFile.fileName()).thenReturn("my_module.py");
PythonFile pythonFile = pythonFile("my_module.py");
FileInput fileInput = mock(FileInputImpl.class);

PythonVisitorContext pythonVisitorContext = new PythonVisitorContext(fileInput, pythonFile, workingDirectory, myPackage, projectLevelSymbolTable, cacheContext, SonarProduct.SONARLINT);
Expand Down

0 comments on commit d936bd4

Please sign in to comment.