diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/BasicTypeTable.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/BasicTypeTable.java new file mode 100644 index 0000000000..e1deafdad6 --- /dev/null +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/BasicTypeTable.java @@ -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 typeFqnParts) { + return new UnknownType.UnresolvedImportType(String.join(".", typeFqnParts)); + } + + @Override + public PythonType getModuleType(List typeFqnParts) { + return new UnknownType.UnresolvedImportType(String.join(".", typeFqnParts)); + } +} diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/FunctionTypeBuilder.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/FunctionTypeBuilder.java index 99e0d0f2f4..7eeaee6604 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/FunctionTypeBuilder.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/FunctionTypeBuilder.java @@ -60,7 +60,7 @@ public class FunctionTypeBuilder implements TypeBuilder { 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<>(); @@ -155,7 +155,7 @@ public FunctionTypeBuilder withOwner(PythonType owner) { return this; } - private void createParameterNames(List parameterTrees, @Nullable String fileId, ProjectLevelTypeTable projectLevelTypeTable) { + private void createParameterNames(List 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)) @@ -174,7 +174,7 @@ private void createParameterNames(List 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) { @@ -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(); diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/LazyTypesContext.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/LazyTypesContext.java index 63d9c46751..352da5253c 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/LazyTypesContext.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/LazyTypesContext.java @@ -28,11 +28,11 @@ public class LazyTypesContext { private final Map 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) { @@ -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; diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java index 377f21957f..65fb20d4b5 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/ProjectLevelTypeTable.java @@ -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; @@ -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 typeFqnParts) { var parent = (PythonType) rootModule; for (int i = 0; i < typeFqnParts.size(); i++) { @@ -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 typeFqnParts) { var parent = (PythonType) rootModule; for (int i = 0; i < typeFqnParts.size(); i++) { diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeInferenceV2.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeInferenceV2.java index b626094d67..085838128c 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeInferenceV2.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeInferenceV2.java @@ -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; diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeTable.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeTable.java new file mode 100644 index 0000000000..0707bd88a5 --- /dev/null +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/TypeTable.java @@ -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 typeFqnParts); + + PythonType getModuleType(List typeFqnParts); +} diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/FlowSensitiveTypeInference.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/FlowSensitiveTypeInference.java index d528e66942..4dfee21e7b 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/FlowSensitiveTypeInference.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/FlowSensitiveTypeInference.java @@ -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 { @@ -51,7 +52,7 @@ public class FlowSensitiveTypeInference extends ForwardAnalysis { private final IsInstanceVisitor isInstanceVisitor; public FlowSensitiveTypeInference( - ProjectLevelTypeTable projectLevelTypeTable, Set trackedVars, + TypeTable projectLevelTypeTable, Set trackedVars, Map assignmentsByAssignmentStatement, Map> definitionsByDefinitionStatement, Map parameterTypesByName diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/IsInstanceVisitor.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/IsInstanceVisitor.java index 803d76b384..e9016dafb1 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/IsInstanceVisitor.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/IsInstanceVisitor.java @@ -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; @@ -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"); } diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TrivialTypeInferenceVisitor.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TrivialTypeInferenceVisitor.java index d88cdfd2ff..54473422de 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TrivialTypeInferenceVisitor.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/types/TrivialTypeInferenceVisitor.java @@ -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; @@ -88,12 +88,12 @@ public class TrivialTypeInferenceVisitor extends BaseTreeVisitor { - private final ProjectLevelTypeTable projectLevelTypeTable; + private final TypeTable projectLevelTypeTable; private final String fileId; private final Deque 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(); @@ -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<>())); @@ -122,7 +122,7 @@ 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<>())); } @@ -130,7 +130,7 @@ public void visitTuple(Tuple tuple) { @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<>())); } @@ -138,14 +138,14 @@ public void visitDictionaryLiteral(DictionaryLiteral dictionaryLiteral) { @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); @@ -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<>())); diff --git a/python-frontend/src/test/java/org/sonar/python/semantic/v2/BasicTypeTableTest.java b/python-frontend/src/test/java/org/sonar/python/semantic/v2/BasicTypeTableTest.java new file mode 100644 index 0000000000..26e23eb7d6 --- /dev/null +++ b/python-frontend/src/test/java/org/sonar/python/semantic/v2/BasicTypeTableTest.java @@ -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 resolvedMemberType = builtinsModuleType.resolveMember(memberName); + assertThat(resolvedMemberType).isPresent(); + assertThat(resolvedMemberType.get()).isInstanceOf(UnknownType.UnresolvedImportType.class); + assertThat(((UnknownType.UnresolvedImportType) resolvedMemberType.get()).importPath()).isEqualTo(resolvedMemberName); + } +} diff --git a/python-frontend/src/test/java/org/sonar/python/types/v2/TypesTestUtils.java b/python-frontend/src/test/java/org/sonar/python/types/v2/TypesTestUtils.java index 2ff5edcdea..d839b08774 100644 --- a/python-frontend/src/test/java/org/sonar/python/types/v2/TypesTestUtils.java +++ b/python-frontend/src/test/java/org/sonar/python/types/v2/TypesTestUtils.java @@ -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();