From 7c65f17008e41ada04f8682f31f24b256b4fdb0a Mon Sep 17 00:00:00 2001 From: Guillaume Dequenne <guillaume.dequenne@sonarsource.com> Date: Mon, 27 Jan 2025 09:37:18 +0100 Subject: [PATCH] SONARPY-2537 Reproduce changes --- .../python/semantic/v2/ReadUsagesVisitor.java | 8 ++- .../python/semantic/v2/TypeInferenceV2.java | 4 +- .../org/sonar/python/semantic/v2/UsageV2.java | 1 + .../semantic/v2/TypeInferenceV2Test.java | 52 +++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/ReadUsagesVisitor.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/ReadUsagesVisitor.java index 40b2039353..c604fb44c7 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/ReadUsagesVisitor.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/ReadUsagesVisitor.java @@ -139,7 +139,13 @@ private void addSymbolUsage(Name name) { var scope = currentScope(); var symbol = scope.resolve(name.name()); if (symbol != null && symbol.usages().stream().noneMatch(usage -> usage.tree().equals(name))) { - symbol.addUsage(name, UsageV2.Kind.OTHER); + if (name.parent().is(Tree.Kind.GLOBAL_STMT)) { + symbol.addUsage(name, UsageV2.Kind.GLOBAL_DECLARATION); + } else if (name.parent().is(Tree.Kind.NONLOCAL_STMT)) { + symbol.addUsage(name, UsageV2.Kind.NONLOCAL_DECLARATION); + } else { + symbol.addUsage(name, UsageV2.Kind.OTHER); + } } } } 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 69bb9dde00..bdd357e0a9 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 @@ -173,8 +173,8 @@ private static Set<SymbolV2> getTrackedVars(Set<SymbolV2> localVariables, Set<Na boolean hasMissingBindingUsage = variable.usages().stream() .filter(UsageV2::isBindingUsage) .anyMatch(u -> !assignedNames.contains(u.tree())); - boolean isGlobal = variable.usages().stream().anyMatch(v -> v.kind().equals(UsageV2.Kind.GLOBAL_DECLARATION)); - if (!hasMissingBindingUsage && !isGlobal) { + boolean isGlobalOrNonLocal = variable.usages().stream().anyMatch(v -> v.kind().equals(UsageV2.Kind.GLOBAL_DECLARATION) || v.kind().equals(UsageV2.Kind.NONLOCAL_DECLARATION)); + if (!hasMissingBindingUsage && !isGlobalOrNonLocal) { trackedVars.add(variable); } } diff --git a/python-frontend/src/main/java/org/sonar/python/semantic/v2/UsageV2.java b/python-frontend/src/main/java/org/sonar/python/semantic/v2/UsageV2.java index cb213cb61e..79d488c823 100644 --- a/python-frontend/src/main/java/org/sonar/python/semantic/v2/UsageV2.java +++ b/python-frontend/src/main/java/org/sonar/python/semantic/v2/UsageV2.java @@ -40,6 +40,7 @@ public enum Kind { EXCEPTION_INSTANCE, WITH_INSTANCE, GLOBAL_DECLARATION, + NONLOCAL_DECLARATION, PATTERN_DECLARATION, TYPE_PARAM_DECLARATION, TYPE_ALIAS_DECLARATION, 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 9ba3f70839..d933b5dcb2 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 @@ -1337,6 +1337,58 @@ void global_variable_builtin() { """).typeV2().unwrappedType()).isEqualTo(PythonType.UNKNOWN); } + @Test + void global_variable_in_nested_function() { + FileInput fileInput = inferTypes(""" + def outer(): + a = 24 + def nested(): + global a + a + """); + var outerFunctionDef = (FunctionDef) fileInput.statements().statements().get(0); + SymbolV2 symbolV2 = ((Name) ((ExpressionStatement) outerFunctionDef.body().statements().get(2)).expressions().get(0)).symbolV2(); + assertThat(symbolV2.usages()).extracting(UsageV2::kind).containsExactlyInAnyOrder(UsageV2.Kind.ASSIGNMENT_LHS, UsageV2.Kind.GLOBAL_DECLARATION, UsageV2.Kind.OTHER); + } + + @Test + void nonlocal_variable_in_nested_function() { + FileInput fileInput = inferTypes(""" + def outer(): + a = 24 + def nested(): + nonlocal a + a + """); + var outerFunctionDef = (FunctionDef) fileInput.statements().statements().get(0); + SymbolV2 symbolV2 = ((Name) ((ExpressionStatement) outerFunctionDef.body().statements().get(2)).expressions().get(0)).symbolV2(); + assertThat(symbolV2.usages()).extracting(UsageV2::kind).containsExactlyInAnyOrder(UsageV2.Kind.ASSIGNMENT_LHS, UsageV2.Kind.NONLOCAL_DECLARATION, UsageV2.Kind.OTHER); + } + + @Test + void nonlocal_variable_try_except() { + FileInput fileInput = inferTypes(""" + def outer(): + contains_target = True + + def nested(item): + nonlocal contains_target + if cond(): + contains_target = contains_target and foo() + + try: + return contains_target + except Exception as e: + ... + contains_target + """); + var outerFunctionDef = (FunctionDef) fileInput.statements().statements().get(0); + SymbolV2 symbolV2 = ((Name) ((ExpressionStatement) outerFunctionDef.body().statements().get(3)).expressions().get(0)).symbolV2(); + assertThat(symbolV2.usages()).extracting(UsageV2::kind).containsExactlyInAnyOrder( + UsageV2.Kind.ASSIGNMENT_LHS, UsageV2.Kind.NONLOCAL_DECLARATION, UsageV2.Kind.ASSIGNMENT_LHS, UsageV2.Kind.OTHER, UsageV2.Kind.OTHER, UsageV2.Kind.OTHER + ); + } + @Test void conditional_assignment() { PythonType type = lastExpression("""