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 8a4027928f..ec6783a358 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 @@ -471,10 +471,17 @@ public void visitName(Name name) { .map(Expression.class::cast) .map(Expression::typeV2) // TODO: classes (SONARPY-1829) and functions should be propagated like other types - .filter(t -> (t instanceof ClassType) || (t instanceof FunctionType) || (t instanceof ModuleType)) + .filter(TrivialTypeInferenceVisitor::shouldTypeBeEagerlyPropagated) .ifPresent(type -> setTypeToName(name, type)); } + private static boolean shouldTypeBeEagerlyPropagated(PythonType t) { + return (t instanceof ClassType) + || (t instanceof FunctionType) + || (t instanceof ModuleType) + || (t instanceof UnknownType.UnresolvedImportType); + } + private PythonType currentType() { return typeStack.peek(); } diff --git a/python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java b/python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java index 6b1222d38e..1f88b39369 100644 --- a/python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java +++ b/python-frontend/src/test/java/org/sonar/python/semantic/v2/TypeInferenceV2Test.java @@ -154,6 +154,38 @@ void testUnresolvedImports() { .isEqualTo("something.unknown"); } + @Test + void testInheritanceFromUnresolvedImports() { + FileInput root = inferTypes(""" + from unknown import Parent + class A(Parent): ... + """); + + var classDef = (ClassDef) root.statements().statements().get(1); + assertThat(classDef.args().arguments().get(0)) + .extracting(RegularArgument.class::cast) + .extracting(RegularArgument::expression) + .extracting(Expression::typeV2) + .extracting(UnresolvedImportType.class::cast) + .extracting(UnresolvedImportType::importPath) + .isEqualTo("unknown.Parent"); + } + + @Test + void testUnresolvedImportTypePropagationInsideFunctions() { + var fileInput = inferTypes(""" + from a import b + def function(): + f(b) + """); + var functionDef = (FunctionDef) fileInput.statements().statements().get(1); + var funcCall = ((ExpressionStatement) functionDef.body().statements().get(0)).expressions().get(0); + var arg = ((RegularArgument) ((CallExpression) funcCall).arguments().get(0)); + var argType = arg.expression().typeV2(); + + assertThat(argType).isInstanceOfSatisfying(UnresolvedImportType.class, a -> assertThat(a.importPath()).isEqualTo("a.b")); + } + @Test void testProjectLevelSymbolTableImports() { var classSymbol = new ClassSymbolImpl("C", "something.known.C");