diff --git a/python-frontend/src/main/java/org/sonar/python/index/VariableDescriptor.java b/python-frontend/src/main/java/org/sonar/python/index/VariableDescriptor.java index 32a3dd6d72..25f5242887 100644 --- a/python-frontend/src/main/java/org/sonar/python/index/VariableDescriptor.java +++ b/python-frontend/src/main/java/org/sonar/python/index/VariableDescriptor.java @@ -26,11 +26,17 @@ public class VariableDescriptor implements Descriptor { private final String name; private final String fullyQualifiedName; private final String annotatedType; + private final boolean isImportedModule; - public VariableDescriptor(String name, @Nullable String fullyQualifiedName, @Nullable String annotatedType) { + public VariableDescriptor(String name, @Nullable String fullyQualifiedName, @Nullable String annotatedType, boolean isImportedModule) { this.name = name; this.fullyQualifiedName = fullyQualifiedName; this.annotatedType = annotatedType; + this.isImportedModule = isImportedModule; + } + + public VariableDescriptor(String name, @Nullable String fullyQualifiedName, @Nullable String annotatedType) { + this(name, fullyQualifiedName, annotatedType, false); } @Override @@ -52,4 +58,8 @@ public Kind kind() { public String annotatedType() { return annotatedType; } + + public boolean isImportedModule() { + return isImportedModule; + } } 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 23afe2dcc4..377f21957f 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 @@ -67,10 +67,12 @@ public PythonType getType(List typeFqnParts) { if (parent instanceof ModuleType moduleType) { TypeWrapper typeWrapper = moduleType.members().get(part); if (typeWrapper instanceof LazyTypeWrapper lazyTypeWrapper && !lazyTypeWrapper.isResolved()) { - if (i == typeFqnParts.size() - 1) { - // this is the name we are looking for, resolve it + if (shouldResolveImmediately(lazyTypeWrapper, typeFqnParts, i)) { + // We try to resolve the type of the member if it points to a different module. + // If it points to the same module, we try to resolve the submodule of the same name return typeWrapper.type(); } + // The member of the module is a LazyType, which means it's a re-exported type from a submodule // We try to resolve the submodule instead Optional subModule = moduleType.resolveSubmodule(part); @@ -90,6 +92,10 @@ public PythonType getType(List typeFqnParts) { return parent; } + private static boolean shouldResolveImmediately(LazyTypeWrapper lazyTypeWrapper, List typeFqnParts, int i) { + return i == typeFqnParts.size() - 1 && !(lazyTypeWrapper.hasImportPath(String.join(".", typeFqnParts))); + } + /** * This method returns a module type for a given FQN, or unknown if it cannot be resolved. * It is to be used to retrieve modules referenced in the "from" clause of an "import from" statement, diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/VariableDescriptorToPythonTypeConverter.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/VariableDescriptorToPythonTypeConverter.java index 1cc7753ca3..fdf6971bf9 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/VariableDescriptorToPythonTypeConverter.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/converter/VariableDescriptorToPythonTypeConverter.java @@ -28,6 +28,12 @@ public class VariableDescriptorToPythonTypeConverter implements DescriptorToPythonTypeConverter { public PythonType convert(ConversionContext ctx, VariableDescriptor from) { + if (from.isImportedModule()) { + var fqn = from.fullyQualifiedName(); + if (fqn != null) { + return ctx.lazyTypesContext().getOrCreateLazyType(fqn); + } + } return Optional.ofNullable(from.annotatedType()) .map(fqn -> ctx.lazyTypesContext().getOrCreateLazyTypeWrapper(fqn)) .map(t -> (PythonType) new ObjectType(t)) diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/typeshed/VarSymbolToDescriptorConverter.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/typeshed/VarSymbolToDescriptorConverter.java index 8a373b5ca1..b3882bf592 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/typeshed/VarSymbolToDescriptorConverter.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/typeshed/VarSymbolToDescriptorConverter.java @@ -28,10 +28,11 @@ public class VarSymbolToDescriptorConverter { public Descriptor convert(SymbolsProtos.VarSymbol varSymbol) { var fullyQualifiedName = TypeShedUtils.normalizedFqn(varSymbol.getFullyQualifiedName()); var typeAnnotation = TypeShedUtils.getTypesNormalizedFqn(varSymbol.getTypeAnnotation()); + var isImportedModule = varSymbol.getIsImportedModule(); if (isTypeAnnotationKnownToBeIncorrect(fullyQualifiedName)) { - return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, null); + return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, null, isImportedModule); } - return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, typeAnnotation); + return new VariableDescriptor(varSymbol.getName(), fullyQualifiedName, typeAnnotation, isImportedModule); } private static boolean isTypeAnnotationKnownToBeIncorrect(String fullyQualifiedName) { diff --git a/python-frontend/src/main/java/org/sonar/python/types/v2/LazyTypeWrapper.java b/python-frontend/src/main/java/org/sonar/python/types/v2/LazyTypeWrapper.java index 725737bc09..f869c46137 100644 --- a/python-frontend/src/main/java/org/sonar/python/types/v2/LazyTypeWrapper.java +++ b/python-frontend/src/main/java/org/sonar/python/types/v2/LazyTypeWrapper.java @@ -46,6 +46,10 @@ public boolean isResolved() { return !(type instanceof LazyType); } + public boolean hasImportPath(String importPath) { + return ((LazyType) type).importPath().equals(importPath); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/python-frontend/src/test/java/org/sonar/python/semantic/v2/ProjectLevelTypeTableTest.java b/python-frontend/src/test/java/org/sonar/python/semantic/v2/ProjectLevelTypeTableTest.java index 9696545878..a3af556958 100644 --- a/python-frontend/src/test/java/org/sonar/python/semantic/v2/ProjectLevelTypeTableTest.java +++ b/python-frontend/src/test/java/org/sonar/python/semantic/v2/ProjectLevelTypeTableTest.java @@ -253,4 +253,14 @@ class lib: ... // SONARPY-2176 lib should be resolved as the renamed class "A" here assertThat(aType.name()).isEqualTo("A"); } + + @Test + void resolveStubsWithImportedModuleVariableDescriptor() { + var symbolTable = ProjectLevelSymbolTable.empty(); + var table = new ProjectLevelTypeTable(symbolTable); + + var nnModuleType = table.getType("torch.nn"); + + Assertions.assertThat(nnModuleType).isNotNull().isInstanceOf(ModuleType.class); + } }