Skip to content

Commit

Permalink
SONARPY-2262 Introduce the TypesTable interface
Browse files Browse the repository at this point in the history
  • Loading branch information
maksim-grebeniuk-sonarsource committed Oct 25, 2024
1 parent 2babf83 commit 65d7e58
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* SonarQube Python Plugin
* Copyright (C) 2011-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.python.semantic.v2;

import java.util.List;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.UnknownType;

public class BasicTypeTable implements TypeTable {
@Override
public PythonType getBuiltinsModule() {
return new UnknownType.UnresolvedImportType("");
}

@Override
public PythonType getType(String typeFqn) {
return new UnknownType.UnresolvedImportType(typeFqn);
}

@Override
public PythonType getType(String... typeFqnParts) {
return new UnknownType.UnresolvedImportType(String.join(".", typeFqnParts));
}

@Override
public PythonType getType(List<String> typeFqnParts) {
return new UnknownType.UnresolvedImportType(String.join(".", typeFqnParts));
}

@Override
public PythonType getModuleType(List<String> typeFqnParts) {
return new UnknownType.UnresolvedImportType(String.join(".", typeFqnParts));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class FunctionTypeBuilder implements TypeBuilder<FunctionType> {
private static final String CLASS_METHOD_DECORATOR = "classmethod";
private static final String STATIC_METHOD_DECORATOR = "staticmethod";

public FunctionTypeBuilder fromFunctionDef(FunctionDef functionDef, @Nullable String fileId, ProjectLevelTypeTable projectLevelTypeTable) {
public FunctionTypeBuilder fromFunctionDef(FunctionDef functionDef, @Nullable String fileId, TypeTable projectLevelTypeTable) {
this.name = functionDef.name().name();
this.attributes = new ArrayList<>();
this.parameters = new ArrayList<>();
Expand Down Expand Up @@ -155,7 +155,7 @@ public FunctionTypeBuilder withOwner(PythonType owner) {
return this;
}

private void createParameterNames(List<AnyParameter> parameterTrees, @Nullable String fileId, ProjectLevelTypeTable projectLevelTypeTable) {
private void createParameterNames(List<AnyParameter> parameterTrees, @Nullable String fileId, TypeTable projectLevelTypeTable) {
ParameterState parameterState = new ParameterState();
parameterState.positionalOnly = parameterTrees.stream().anyMatch(param -> Optional.of(param)
.filter(p -> p.is(Tree.Kind.PARAMETER))
Expand All @@ -174,7 +174,7 @@ private void createParameterNames(List<AnyParameter> parameterTrees, @Nullable S
}
}

private void addParameter(Parameter parameter, @Nullable String fileId, ParameterState parameterState, ProjectLevelTypeTable projectLevelTypeTable) {
private void addParameter(Parameter parameter, @Nullable String fileId, ParameterState parameterState, TypeTable projectLevelTypeTable) {
Name parameterName = parameter.name();
Token starToken = parameter.starToken();
if (parameterName != null) {
Expand All @@ -197,7 +197,7 @@ private void addParameter(Parameter parameter, @Nullable String fileId, Paramete
}
}

private ParameterType getParameterType(Parameter parameter, ProjectLevelTypeTable projectLevelTypeTable) {
private ParameterType getParameterType(Parameter parameter, TypeTable projectLevelTypeTable) {
boolean isPositionalVariadic = false;
boolean isKeywordVariadic = false;
Token starToken = parameter.starToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@

public class LazyTypesContext {
private final Map<String, LazyType> lazyTypes;
private final ProjectLevelTypeTable projectLevelTypeTable;
private final TypeTable typeTable;

public LazyTypesContext(ProjectLevelTypeTable projectLevelTypeTable) {
public LazyTypesContext(ProjectLevelTypeTable typeTable) {
this.lazyTypes = new HashMap<>();
this.projectLevelTypeTable = projectLevelTypeTable;
this.typeTable = typeTable;
}

public TypeWrapper getOrCreateLazyTypeWrapper(String importPath) {
Expand All @@ -49,7 +49,7 @@ public LazyType getOrCreateLazyType(String importPath) {
}

public PythonType resolveLazyType(LazyType lazyType) {
PythonType resolved = projectLevelTypeTable.getType(lazyType.importPath());
PythonType resolved = typeTable.getType(lazyType.importPath());
lazyType.resolve(resolved);
lazyTypes.remove(lazyType.importPath());
return resolved;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TypeWrapper;

public class ProjectLevelTypeTable {
public class ProjectLevelTypeTable implements TypeTable {

private final SymbolsModuleTypeProvider symbolsModuleTypeProvider;
private final ModuleType rootModule;
Expand All @@ -41,18 +41,22 @@ public ProjectLevelTypeTable(ProjectLevelSymbolTable projectLevelSymbolTable) {
this.rootModule = this.symbolsModuleTypeProvider.createBuiltinModule();
}

public ModuleType getBuiltinsModule() {
@Override
public PythonType getBuiltinsModule() {
return rootModule;
}

@Override
public PythonType getType(String typeFqn) {
return getType(typeFqn.split("\\."));
}

@Override
public PythonType getType(String... typeFqnParts) {
return getType(List.of(typeFqnParts));
}

@Override
public PythonType getType(List<String> typeFqnParts) {
var parent = (PythonType) rootModule;
for (int i = 0; i < typeFqnParts.size(); i++) {
Expand Down Expand Up @@ -101,6 +105,7 @@ private static boolean shouldResolveImmediately(LazyTypeWrapper lazyTypeWrapper,
* It is to be used to retrieve modules referenced in the "from" clause of an "import from" statement,
* as it will only consider submodules over package members in case of name conflict.
*/
@Override
public PythonType getModuleType(List<String> typeFqnParts) {
var parent = (PythonType) rootModule;
for (int i = 0; i < typeFqnParts.size(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@

public class TypeInferenceV2 {

private final ProjectLevelTypeTable projectLevelTypeTable;
private final TypeTable projectLevelTypeTable;
private final SymbolTable symbolTable;
private final PythonFile pythonFile;

public TypeInferenceV2(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile, SymbolTable symbolTable) {
public TypeInferenceV2(TypeTable projectLevelTypeTable, PythonFile pythonFile, SymbolTable symbolTable) {
this.projectLevelTypeTable = projectLevelTypeTable;
this.symbolTable = symbolTable;
this.pythonFile = pythonFile;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SonarQube Python Plugin
* Copyright (C) 2011-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.python.semantic.v2;

import java.util.List;
import org.sonar.python.types.v2.PythonType;

public interface TypeTable {
PythonType getBuiltinsModule();

PythonType getType(String typeFqn);

PythonType getType(String... typeFqnParts);

PythonType getType(List<String> typeFqnParts);

PythonType getModuleType(List<String> typeFqnParts);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.sonar.python.cfg.fixpoint.ProgramState;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolV2;
import org.sonar.python.semantic.v2.TypeTable;
import org.sonar.python.types.v2.PythonType;

public class FlowSensitiveTypeInference extends ForwardAnalysis {
Expand All @@ -51,7 +52,7 @@ public class FlowSensitiveTypeInference extends ForwardAnalysis {
private final IsInstanceVisitor isInstanceVisitor;

public FlowSensitiveTypeInference(
ProjectLevelTypeTable projectLevelTypeTable, Set<SymbolV2> trackedVars,
TypeTable projectLevelTypeTable, Set<SymbolV2> trackedVars,
Map<Statement, Assignment> assignmentsByAssignmentStatement,
Map<Statement, Set<Definition>> definitionsByDefinitionStatement,
Map<String, PythonType> parameterTypesByName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.sonar.plugins.python.api.tree.RegularArgument;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolV2;
import org.sonar.python.semantic.v2.TypeTable;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TypeSource;
import org.sonar.python.types.v2.UnknownType;
Expand All @@ -36,7 +37,7 @@ public class IsInstanceVisitor extends BaseTreeVisitor {
private final PythonType isInstanceFunctionType;
private TypeInferenceProgramState state;

public IsInstanceVisitor(ProjectLevelTypeTable projectLevelTypeTable) {
public IsInstanceVisitor(TypeTable projectLevelTypeTable) {
isInstanceFunctionType = projectLevelTypeTable.getType("isinstance");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@
import org.sonar.plugins.python.api.tree.TypeAnnotation;
import org.sonar.python.semantic.v2.ClassTypeBuilder;
import org.sonar.python.semantic.v2.FunctionTypeBuilder;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolV2;
import org.sonar.python.semantic.v2.TypeTable;
import org.sonar.python.semantic.v2.UsageV2;
import org.sonar.python.tree.ComprehensionExpressionImpl;
import org.sonar.python.tree.DictCompExpressionImpl;
Expand Down Expand Up @@ -88,12 +88,12 @@

public class TrivialTypeInferenceVisitor extends BaseTreeVisitor {

private final ProjectLevelTypeTable projectLevelTypeTable;
private final TypeTable projectLevelTypeTable;
private final String fileId;

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

public TrivialTypeInferenceVisitor(ProjectLevelTypeTable projectLevelTypeTable, PythonFile pythonFile) {
public TrivialTypeInferenceVisitor(TypeTable projectLevelTypeTable, PythonFile pythonFile) {
this.projectLevelTypeTable = projectLevelTypeTable;
Path path = pathOf(pythonFile);
this.fileId = path != null ? path.toString() : pythonFile.toString();
Expand All @@ -108,7 +108,7 @@ public void visitFileInput(FileInput fileInput) {

@Override
public void visitStringLiteral(StringLiteral stringLiteral) {
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
// TODO: SONARPY-1867 multiple object types to represent str instance?
PythonType strType = builtins.resolveMember("str").orElse(PythonType.UNKNOWN);
((StringLiteralImpl) stringLiteral).typeV2(new ObjectType(strType, new ArrayList<>(), new ArrayList<>()));
Expand All @@ -122,30 +122,30 @@ public void visitTuple(Tuple tuple) {
if (contentTypes.size() == 1 && !contentTypes.get(0).equals(PythonType.UNKNOWN)) {
attributes = contentTypes;
}
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
PythonType tupleType = builtins.resolveMember("tuple").orElse(PythonType.UNKNOWN);
((TupleImpl) tuple).typeV2(new ObjectType(tupleType, attributes, new ArrayList<>()));
}

@Override
public void visitDictionaryLiteral(DictionaryLiteral dictionaryLiteral) {
super.visitDictionaryLiteral(dictionaryLiteral);
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
PythonType dictType = builtins.resolveMember("dict").orElse(PythonType.UNKNOWN);
((DictionaryLiteralImpl) dictionaryLiteral).typeV2(new ObjectType(dictType, new ArrayList<>(), new ArrayList<>()));
}

@Override
public void visitSetLiteral(SetLiteral setLiteral) {
super.visitSetLiteral(setLiteral);
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
PythonType setType = builtins.resolveMember("set").orElse(PythonType.UNKNOWN);
((SetLiteralImpl) setLiteral).typeV2(new ObjectType(setType, new ArrayList<>(), new ArrayList<>()));
}

@Override
public void visitNumericLiteral(NumericLiteral numericLiteral) {
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
NumericLiteralImpl numericLiteralImpl = (NumericLiteralImpl) numericLiteral;
NumericLiteralImpl.NumericKind numericKind = numericLiteralImpl.numericKind();
PythonType pythonType = builtins.resolveMember(numericKind.value()).orElse(PythonType.UNKNOWN);
Expand All @@ -154,7 +154,7 @@ public void visitNumericLiteral(NumericLiteral numericLiteral) {

@Override
public void visitNone(NoneExpression noneExpression) {
ModuleType builtins = this.projectLevelTypeTable.getBuiltinsModule();
var builtins = this.projectLevelTypeTable.getBuiltinsModule();
// TODO: SONARPY-1867 multiple object types to represent str instance?
PythonType noneType = builtins.resolveMember("NoneType").orElse(PythonType.UNKNOWN);
((NoneExpressionImpl) noneExpression).typeV2(new ObjectType(noneType, new ArrayList<>(), new ArrayList<>()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* SonarQube Python Plugin
* Copyright (C) 2011-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.python.semantic.v2;

import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.UnknownType;

import static org.assertj.core.api.Assertions.assertThat;

class BasicTypeTableTest {

private final BasicTypeTable basicTypeTable = new BasicTypeTable();

@Test
void testGetBuiltinsModule() {
PythonType builtinsModuleType = basicTypeTable.getBuiltinsModule();
assertThat(builtinsModuleType).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertResolvedMember(builtinsModuleType, "foo", "foo");
}

@Test
void testGetTypeFromTypeFqn() {
PythonType fooType = basicTypeTable.getType("foo");
assertThat(fooType).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertResolvedMember(fooType, "bar", "foo.bar");
}

@Test
void testGetTypeFromTypeFqnParts() {
PythonType fooType = basicTypeTable.getType("foo", "bar");
assertThat(fooType).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertResolvedMember(fooType, "qux", "foo.bar.qux");
}

@Test
void testGetTypeFromTypeFqnList() {
PythonType fooType = basicTypeTable.getType(List.of("foo", "bar"));
assertThat(fooType).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertResolvedMember(fooType, "qux", "foo.bar.qux");
}

@Test
void testGetModuleType() {
PythonType fooType = basicTypeTable.getModuleType(List.of("foo", "bar"));
assertThat(fooType).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertResolvedMember(fooType, "qux", "foo.bar.qux");
}

private static void assertResolvedMember(PythonType builtinsModuleType, String memberName, String resolvedMemberName) {
Optional<PythonType> resolvedMemberType = builtinsModuleType.resolveMember(memberName);
assertThat(resolvedMemberType).isPresent();
assertThat(resolvedMemberType.get()).isInstanceOf(UnknownType.UnresolvedImportType.class);
assertThat(((UnknownType.UnresolvedImportType) resolvedMemberType.get()).importPath()).isEqualTo(resolvedMemberName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
public class TypesTestUtils {

public static final ProjectLevelTypeTable PROJECT_LEVEL_TYPE_TABLE = new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty());
public static final ModuleType BUILTINS = PROJECT_LEVEL_TYPE_TABLE.getBuiltinsModule();
public static final PythonType BUILTINS = PROJECT_LEVEL_TYPE_TABLE.getBuiltinsModule();

public static final PythonType INT_TYPE = BUILTINS.resolveMember("int").get();
public static final PythonType FLOAT_TYPE = BUILTINS.resolveMember("float").get();
Expand Down

0 comments on commit 65d7e58

Please sign in to comment.