Skip to content

Commit

Permalink
SONARPY-2306 Support annotated return type information conversion fro…
Browse files Browse the repository at this point in the history
…m PythonType to FunctionDescriptor
  • Loading branch information
maksim-grebeniuk-sonarsource committed Nov 6, 2024
1 parent 6c14b30 commit 7f686b5
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.sonar.python.index.ClassDescriptor;
import org.sonar.python.index.Descriptor;
import org.sonar.python.index.FunctionDescriptor;
import org.sonar.python.index.TypeAnnotationDescriptor;
import org.sonar.python.index.VariableDescriptor;
import org.sonar.python.semantic.v2.SymbolV2;
import org.sonar.python.semantic.v2.UsageV2;
Expand Down Expand Up @@ -96,6 +97,10 @@ private static Descriptor convert(String moduleFqn, FunctionType type) {
.filter(Objects::nonNull)
.toList();

var returnType = type.returnType();
var annotatedReturnTypeName = typeFqn(moduleFqn, returnType);
var typeAnnotationDescriptor = convertReturnType(moduleFqn, returnType);

// Using FunctionType#name and FunctionType#fullyQualifiedName instead of symbol is only accurate if the function has not been reassigned
// This logic should be revisited when tackling SONARPY-2285
return new FunctionDescriptor(type.name(), type.fullyQualifiedName(),
Expand All @@ -105,11 +110,20 @@ private static Descriptor convert(String moduleFqn, FunctionType type) {
decorators,
type.hasDecorators(),
type.definitionLocation().orElse(null),
null,
null
annotatedReturnTypeName,
typeAnnotationDescriptor
);
}

@CheckForNull
private static TypeAnnotationDescriptor convertReturnType(String moduleFqn, PythonType returnType) {
if (returnType.unwrappedType() != PythonType.UNKNOWN) {
var typeAnnotationDescriptor = new TypeAnnotationDescriptor(typeFqn(moduleFqn, returnType.unwrappedType()), TypeAnnotationDescriptor.TypeKind.INSTANCE, List.of(), typeFqn(moduleFqn, returnType.unwrappedType()));
return typeAnnotationDescriptor;
}
return null;
}

private static Descriptor convert(String moduleFqn, String parentFqn, String symbolName, ClassType type) {
var symbolFqn = symbolFqn(parentFqn, symbolName);
var memberDescriptors = type.members()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package org.sonar.python.semantic.v2;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.tree.ExpressionStatement;
Expand All @@ -30,6 +29,7 @@
import org.sonar.python.types.v2.FunctionType;
import org.sonar.python.types.v2.LazyTypeWrapper;
import org.sonar.python.types.v2.ModuleType;
import org.sonar.python.types.v2.ObjectType;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;
import org.sonar.python.types.v2.TypeChecker;
Expand Down Expand Up @@ -355,6 +355,28 @@ def foo(self): ...
assertThat(typeWrapper.hasImportPath("abstractmethod")).isTrue();
}

@Test
void importedFunctionReturnTypeTest() {
var projectLevelSymbolTable = new ProjectLevelSymbolTable();
var libTree = parseWithoutSymbols(
"""
def foo() -> int: ...
"""
);
projectLevelSymbolTable.addModule(libTree, "", pythonFile("lib.py"));

var projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
var mainFile = pythonFile("main.py");
var fileInput = parseAndInferTypes(projectLevelTypeTable, mainFile, """
from lib import foo
foo
"""
);
var fooType = (FunctionType) ((ExpressionStatement) fileInput.statements().statements().get(1)).expressions().get(0).typeV2();
assertThat(fooType.returnType()).isInstanceOf(ObjectType.class).extracting(PythonType::unwrappedType)
.isInstanceOfSatisfying(ClassType.class, returnClassType -> assertThat(returnClassType.name()).isEqualTo("int"));
}

@Test
void relativeImports() {
var projectLevelSymbolTable = new ProjectLevelSymbolTable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2398,7 +2398,7 @@ void type_origin_of_project_function() {
assertThat(fooType.typeOrigin()).isEqualTo(TypeOrigin.LOCAL);
PythonType xType = ((ExpressionStatement) fileInput.statements().statements().get(3)).expressions().get(0).typeV2();
// Declared return types of local functions are currently not stored in the project level symbol table
assertThat(xType).isEqualTo(PythonType.UNKNOWN);
assertThat(xType.unwrappedType()).isSameAs(projectLevelTypeTable.getBuiltinsModule().resolveMember("int").get());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.sonar.python.index.ClassDescriptor;
import org.sonar.python.index.Descriptor;
import org.sonar.python.index.FunctionDescriptor;
import org.sonar.python.index.TypeAnnotationDescriptor;
import org.sonar.python.index.VariableDescriptor;
import org.sonar.python.semantic.ProjectLevelSymbolTable;
import org.sonar.python.semantic.v2.LazyTypesContext;
Expand Down Expand Up @@ -85,11 +86,11 @@ void testConvertFunctionTypeWithoutDecorator() {
assertThat(functionDescriptor.isAsynchronous()).isTrue();
assertThat(functionDescriptor.isInstanceMethod()).isTrue();

// TODO SONARPY-2223 support for return type is missing in FunctionType
assertThat(functionDescriptor.annotatedReturnTypeName()).isNull();

// TODO SONARPY-2223 support for type annotation is missing in FunctionType
assertThat(functionDescriptor.typeAnnotationDescriptor()).isNull();
assertThat(functionDescriptor.annotatedReturnTypeName()).isEqualTo("float");
assertThat(functionDescriptor.typeAnnotationDescriptor())
.isNotNull()
.extracting(TypeAnnotationDescriptor::fullyQualifiedName)
.isEqualTo("float");

assertThat(functionDescriptor.hasDecorators()).isTrue();
assertThat(functionDescriptor.decorators()).isNotEmpty().containsOnly("abc.abstractmethod");
Expand Down

0 comments on commit 7f686b5

Please sign in to comment.