From c4bbadd8e29697c0d9bef87cfcf578f91b1b60f7 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Sat, 20 Jan 2024 07:36:47 -0300 Subject: [PATCH] Fixes in semantic analysis to better determine if strings in annotations should be checked for symbols or not. --- .../analysis/visitors/OccurrencesVisitor.java | 36 +++++++++++++++++-- .../python/pydev/analysis/visitors/Scope.java | 12 +++++++ .../package_manager/CondaPackageManager.java | 4 +++ .../OccurrencesAnalyzerPy310Test.java | 26 ++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/OccurrencesVisitor.java b/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/OccurrencesVisitor.java index faff4c3b95..dc15d3b8a1 100644 --- a/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/OccurrencesVisitor.java +++ b/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/OccurrencesVisitor.java @@ -10,9 +10,11 @@ package com.python.pydev.analysis.visitors; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.Document; @@ -53,6 +55,7 @@ import org.python.pydev.parser.jython.ast.Raise; import org.python.pydev.parser.jython.ast.Return; import org.python.pydev.parser.jython.ast.Str; +import org.python.pydev.parser.jython.ast.Subscript; import org.python.pydev.parser.jython.ast.While; import org.python.pydev.parser.jython.ast.Yield; import org.python.pydev.parser.jython.ast.decoratorsType; @@ -249,15 +252,34 @@ public Object visitAssign(Assign node) throws Exception { return r; } + private static Set CONTAINER_CLASSES = new HashSet(); + static { + CONTAINER_CLASSES.add("list"); + CONTAINER_CLASSES.add("List"); + CONTAINER_CLASSES.add("typing.List"); + CONTAINER_CLASSES.add("tuple"); + CONTAINER_CLASSES.add("Tuple"); + CONTAINER_CLASSES.add("typing.Tuple"); + CONTAINER_CLASSES.add("set"); + CONTAINER_CLASSES.add("Set"); + CONTAINER_CLASSES.add("typing.Set"); + // This means that it's just a str directly in the type annotation. + CONTAINER_CLASSES.add("str"); + } + @Override public Object visitStr(Str node) throws Exception { if (this.scope.isVisitingTypeAnnotation()) { - String fullRepresentationString = NodeUtils.getFullRepresentationString(scope.currentScope().scopeNode); + String fullRepresentationString = null; // If a string is found inside a typing.Literal, don't parse it for type definitions // (i.e.: those are considered constants in this case and not class references as in // generic classes). - if (!"typing.Literal".equals(fullRepresentationString) && !"Literal".equals(fullRepresentationString)) { + if (this.scope.subscripts.size() > 0) { + Subscript lastSubscript = this.scope.subscripts.peek(); + fullRepresentationString = NodeUtils.getFullRepresentationString(lastSubscript.value); + } + if (fullRepresentationString == null || CONTAINER_CLASSES.contains(fullRepresentationString)) { String s = node.s; IGrammar grammar = PyParser.createGrammar(true, this.nature.getGrammarVersion(), s.toCharArray()); Throwable errorOnParsing = null; @@ -453,6 +475,16 @@ public Object visitLambda(Lambda node) throws Exception { return ret; } + @Override + public Object visitSubscript(Subscript node) throws Exception { + this.scope.pushSubscript(node); + try { + return super.visitSubscript(node); + } finally { + this.scope.popSubscript(node); + } + } + @Override public void traverse(SimpleNode node) throws Exception { if (node instanceof If) { diff --git a/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/Scope.java b/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/Scope.java index 4ee4d47c71..a01f18d68d 100644 --- a/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/Scope.java +++ b/plugins/com.python.pydev.analysis/src/com/python/pydev/analysis/visitors/Scope.java @@ -21,6 +21,7 @@ import org.python.pydev.core.TokensList; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.If; +import org.python.pydev.parser.jython.ast.Subscript; import org.python.pydev.parser.jython.ast.TryExcept; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.structure.FastStack; @@ -522,4 +523,15 @@ public ScopeItems getPrevScopeItems() { return scope.get(scope.size() - 2); } + public final FastStack subscripts = new FastStack<>(3); + + public void pushSubscript(Subscript node) { + subscripts.push(node); + + } + + public void popSubscript(Subscript node) { + subscripts.pop(); + } + } diff --git a/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java b/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java index b65f33a93b..63f04e5a7c 100644 --- a/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java +++ b/plugins/org.python.pydev/src/org/python/pydev/ui/pythonpathconf/package_manager/CondaPackageManager.java @@ -40,6 +40,10 @@ public static List listCondaEnvironments(File condaExecutable) { null, encoding); Log.logInfo(output.o1); + if (output.o2 != null && output.o2.length() > 0) { + Log.logInfo("STDERR when listing conda environments:\n" + output.o2); + + } JsonObject jsonOutput = JsonValue.readFrom(output.o1).asObject(); JsonArray envs = jsonOutput.get("envs").asArray(); Set set = new HashSet<>(); diff --git a/plugins/org.python.pydev/tests_analysis/com/python/pydev/analysis/OccurrencesAnalyzerPy310Test.java b/plugins/org.python.pydev/tests_analysis/com/python/pydev/analysis/OccurrencesAnalyzerPy310Test.java index 53d9bd5010..220c1b43de 100644 --- a/plugins/org.python.pydev/tests_analysis/com/python/pydev/analysis/OccurrencesAnalyzerPy310Test.java +++ b/plugins/org.python.pydev/tests_analysis/com/python/pydev/analysis/OccurrencesAnalyzerPy310Test.java @@ -708,6 +708,32 @@ public void testTypingWithLiterals() { checkNoError(); } + public void testTypingWithLiteralsShowError() { + doc = new Document("def method() -> 'err':\n" + + " return 'a'\n" + + ""); + checkError("Undefined variable: err"); + } + + public void testTypingWithLiterals2() { + doc = new Document("from typing import Annotated, List\n" + + "class Param:\n" + + " pass\n" + + "\n" + + "def method() -> Annotated[List[str],Param(description=\"\"\"Provides a list (i.e.: ['output.txt', 'gen/my.pdf'])\"\"\")]:\n" + + " return 'a'\n" + + ""); + checkNoError(); + } + + public void testTypingWithLiterals3() { + doc = new Document("from typing import Tuple, Literal\n" + + "def method() -> Tuple[Literal['line', 'call', 'return'], ...]:\n" + + " return ()\n" + + ""); + checkNoError(); + } + public void testOverload() { doc = new Document("from typing import overload\n" + "\n"