From 999884a613941040abe617cc016d16aaa0799b28 Mon Sep 17 00:00:00 2001 From: Guillaume Dequenne Date: Fri, 16 Feb 2024 13:58:51 +0100 Subject: [PATCH] SONARPY-1609 Fix FP on S5886 when returning optional unions of unknown symbols (#1707) --- .../resources/checks/functionReturnType.py | 18 ++++++++++++++++++ .../org/sonar/python/types/InferredTypes.java | 4 +++- .../sonar/python/types/InferredTypesTest.java | 4 +--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/python-checks/src/test/resources/checks/functionReturnType.py b/python-checks/src/test/resources/checks/functionReturnType.py index 117084ecb0..f956b15632 100644 --- a/python-checks/src/test/resources/checks/functionReturnType.py +++ b/python-checks/src/test/resources/checks/functionReturnType.py @@ -334,3 +334,21 @@ def echo_round() -> Generator[int, float, str]: @contextlib.contextmanager def test() -> contextlib.AbstractContextManager[str]: yield "this is an example" + + +def no_fp_on_optional_of_unknown_symbol(cond) -> Optional[Unknown]: + if cond == 42: + return 10 # OK + if cond == 24: + return "hello" # OK + if cond == 5: + return None + + +def no_fp_on_optional_union_of_unknown_symbol(cond) -> Optional[Union[str, Unknown]]: + if cond == 42: + return 10 # OK + if cond == 24: + return "hello" # OK + if cond == 5: + return None diff --git a/python-frontend/src/main/java/org/sonar/python/types/InferredTypes.java b/python-frontend/src/main/java/org/sonar/python/types/InferredTypes.java index d7b4af1bf3..024d61c617 100644 --- a/python-frontend/src/main/java/org/sonar/python/types/InferredTypes.java +++ b/python-frontend/src/main/java/org/sonar/python/types/InferredTypes.java @@ -298,7 +298,9 @@ private static DeclaredType declaredTypeFromTypeAnnotationSubscription(Subscript .map(exp -> declaredTypeFromTypeAnnotation(exp, builtinSymbols)) .collect(Collectors.toList()); if (args.stream().anyMatch(Objects::isNull)) { - args = Collections.emptyList(); + // null args indicate something was wrong in the resolution of some of the alternatives + // returning null here will ensure the resulting type will be AnyType, which will avoid potential FPs + return null; } String builtinFqn = ALIASED_ANNOTATIONS.get(symbol.fullyQualifiedName()); return builtinFqn != null ? new DeclaredType(builtinSymbols.get(builtinFqn), args) : new DeclaredType(symbol, args); diff --git a/python-frontend/src/test/java/org/sonar/python/types/InferredTypesTest.java b/python-frontend/src/test/java/org/sonar/python/types/InferredTypesTest.java index 1fa7c0b190..46d6fec101 100644 --- a/python-frontend/src/test/java/org/sonar/python/types/InferredTypesTest.java +++ b/python-frontend/src/test/java/org/sonar/python/types/InferredTypesTest.java @@ -301,9 +301,7 @@ void test_optional_type_annotations() { ); assertThat(fromTypeshedTypeAnnotation(typeAnnotation)).isEqualTo(InferredTypes.anyType()); declaredType = fromTypeAnnotation(typeAnnotation); - assertThat(declaredType).isInstanceOf(DeclaredType.class); - assertThat(((DeclaredType) declaredType).alternativeTypeSymbols()).extracting(Symbol::fullyQualifiedName) - .containsExactlyInAnyOrder("typing.Optional"); + assertThat(declaredType).isInstanceOf(AnyType.class); } @Test