From 761c8be02aa6fa9e200a56a303c25d3e650f4a68 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 11 Sep 2025 17:29:04 +0300 Subject: [PATCH] PY-80002 Don't report non-quoted forward references in Python 3.14+ (PEP 649) GitOrigin-RevId: b16fffe886d350a54434adb57519eb9c1992e217 --- .../jetbrains/python/psi/resolve/PyResolveUtil.java | 12 +++++++----- .../ForwardReferenceInAnnotationsInPython314.py | 6 ++++++ ...tionsWithFromFutureImportAnnotationsBefore314.py} | 0 .../python/inspections/PyTypingConformanceTest.kt | 8 +++++++- .../PyUnboundLocalVariableInspectionTest.java | 7 ++++++- 5 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsInPython314.py rename python/testData/inspections/PyUnboundLocalVariableInspection/{ForwardReferenceInAnnotations.py => ForwardReferenceInAnnotationsWithFromFutureImportAnnotationsBefore314.py} (100%) diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index dbb632e7c668..6683e3d06162 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -424,12 +424,14 @@ public final class PyResolveUtil { // Forward references are allowed in annotations according to PEP 563 PsiFile file = element.getContainingFile(); if (file instanceof PyFile pyFile) { - boolean isAnnotation = pyFile.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON37) && - pyFile.hasImportFromFuture(FutureFeature.ANNOTATIONS) && - PsiTreeUtil.getParentOfType(element, PyAnnotation.class) != null; - if (isAnnotation) return true; + LanguageLevel languageLevel = pyFile.getLanguageLevel(); + boolean nonEagerEvaluationEnabled = + (languageLevel.isAtLeast(LanguageLevel.PYTHON37) && pyFile.hasImportFromFuture(FutureFeature.ANNOTATIONS)) + || languageLevel.isAtLeast(LanguageLevel.PYTHON314); + boolean insideAnnotation = PsiTreeUtil.getParentOfType(element, PyAnnotation.class) != null; + if (insideAnnotation && nonEagerEvaluationEnabled) return true; - boolean isReferenceFromTypeParameterList = pyFile.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON312) && + boolean isReferenceFromTypeParameterList = languageLevel.isAtLeast(LanguageLevel.PYTHON312) && PsiTreeUtil.getParentOfType(element, PyTypeParameter.class, true) != null; if (isReferenceFromTypeParameterList) return true; } diff --git a/python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsInPython314.py b/python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsInPython314.py new file mode 100644 index 000000000000..09e53f9b8434 --- /dev/null +++ b/python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsInPython314.py @@ -0,0 +1,6 @@ +def f(a: A): + pass + + +class A: + pass \ No newline at end of file diff --git a/python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotations.py b/python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsWithFromFutureImportAnnotationsBefore314.py similarity index 100% rename from python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotations.py rename to python/testData/inspections/PyUnboundLocalVariableInspection/ForwardReferenceInAnnotationsWithFromFutureImportAnnotationsBefore314.py diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt b/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt index 77038f9e0814..74b45735efa9 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypingConformanceTest.kt @@ -11,6 +11,7 @@ import com.intellij.util.io.URLUtil import com.jetbrains.python.PythonTestUtil import com.jetbrains.python.fixtures.PyTestCase import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection +import com.jetbrains.python.psi.LanguageLevel import com.jetbrains.python.psi.PyRecursiveElementVisitor import org.junit.AfterClass import org.junit.Test @@ -51,7 +52,12 @@ class PyTypingConformanceTest(private val testFileName: String) : PyTestCase() { fun test() { myFixture.configureByFiles(*getFilePaths()) myFixture.enableInspections(*inspections) - checkHighlighting() + // The test suite has not been updated for 3.14 by default. + // In particular, forward references are not enabled by default + // and require an explicit `from __future__ import annotations`. + runWithLanguageLevel(LanguageLevel.PYTHON313, { + checkHighlighting() + }) } private fun getFilePaths(): Array { diff --git a/python/testSrc/com/jetbrains/python/inspections/PyUnboundLocalVariableInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyUnboundLocalVariableInspectionTest.java index 6dd626341ac5..9b7f0c77a1cb 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyUnboundLocalVariableInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyUnboundLocalVariableInspectionTest.java @@ -251,7 +251,12 @@ public class PyUnboundLocalVariableInspectionTest extends PyInspectionTestCase { doTest(); } - public void testForwardReferenceInAnnotations() { + public void testForwardReferenceInAnnotationsWithFromFutureImportAnnotationsBefore314() { + runWithLanguageLevel(LanguageLevel.PYTHON313, this::doTest); + } + + // PY-80002 + public void testForwardReferenceInAnnotationsInPython314() { doTest(); }