diff --git a/python/python-psi-impl/src/com/jetbrains/python/refactoring/PyDefUseUtil.java b/python/python-psi-impl/src/com/jetbrains/python/refactoring/PyDefUseUtil.java index 83cb0a635ee2..8268e5968737 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/refactoring/PyDefUseUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/refactoring/PyDefUseUtil.java @@ -21,6 +21,7 @@ import com.intellij.codeInsight.controlflow.ControlFlowUtil; import com.intellij.codeInsight.controlflow.Instruction; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.Version; import com.intellij.psi.PsiElement; import com.intellij.psi.util.QualifiedName; import com.jetbrains.python.codeInsight.controlflow.CallInstruction; @@ -37,6 +38,8 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import static com.jetbrains.python.psi.impl.stubs.PyVersionSpecificStubBaseKt.evaluateVersionsForElement; + /** * @author Dennis.Ushakov */ @@ -82,6 +85,9 @@ public final class PyDefUseUtil { final HashMap pendingTypeGuard = new HashMap<>(); ControlFlowUtil.iteratePrev(startNum, instructions, instruction -> { + if (unreachableDueToVersionGuard(instruction)) { + return ControlFlowUtil.Operation.CONTINUE; + } if (acceptTypeAssertions && instruction instanceof CallInstruction callInstruction) { var typeGuardInstruction = pendingTypeGuard.get(instruction.getElement()); if (typeGuardInstruction != null) { @@ -97,7 +103,6 @@ public final class PyDefUseUtil { } } final PsiElement element = instruction.getElement(); - final PyImplicitImportNameDefiner implicit = PyUtil.as(element, PyImplicitImportNameDefiner.class); if (acceptTypeAssertions && instruction instanceof ConditionalInstruction conditionalInstruction && instruction.num() < startNum) { @@ -122,7 +127,7 @@ public final class PyDefUseUtil { } } } - else if (acceptImplicitImports && implicit != null) { + else if (acceptImplicitImports && element instanceof PyImplicitImportNameDefiner implicit) { if (!implicit.multiResolveName(varName).isEmpty()) { result.add(instruction); return ControlFlowUtil.Operation.CONTINUE; @@ -133,6 +138,14 @@ public final class PyDefUseUtil { return result; } + private static boolean unreachableDueToVersionGuard(@NotNull Instruction instruction) { + PsiElement element = instruction.getElement(); + if (element == null) return false; + LanguageLevel languageLevel = LanguageLevel.forElement(element); + Version version = new Version(languageLevel.getMajorVersion(), languageLevel.getMinorVersion(), 0); + return !evaluateVersionsForElement(element).contains(version); + } + @Nullable private static String elementName(PsiElement element) { if (element instanceof PyImportElement) { diff --git a/python/testData/types/GenericAliasUnderVersionGuard/mod.py b/python/testData/types/GenericAliasUnderVersionGuard/mod.py new file mode 100644 index 000000000000..4f661660f80a --- /dev/null +++ b/python/testData/types/GenericAliasUnderVersionGuard/mod.py @@ -0,0 +1,12 @@ +import sys +from typing import TypeAlias, TypeVar + +T = TypeVar("T") + +if sys.version_info >= (3,): + Alias: TypeAlias = list[T] +else: + Alias: TypeAlias = set[T] + +def f(x: T) -> Alias[T]: + pass \ No newline at end of file diff --git a/python/testData/types/GenericTypeImportedUnderVersionGuard/mod.py b/python/testData/types/GenericTypeImportedUnderVersionGuard/mod.py new file mode 100644 index 000000000000..47816edcd5d5 --- /dev/null +++ b/python/testData/types/GenericTypeImportedUnderVersionGuard/mod.py @@ -0,0 +1,11 @@ +import sys + +if sys.version_info >= (3,): + from builtins import list as Container +else: + from builtins import set as Container + + +class C: + def m(self) -> Container[str]: + ... \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 835a952e0484..f25dc3157bd1 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -5712,6 +5712,73 @@ public class PyTypingTest extends PyTestCase { """); } + // PY-76076 PY-60968 + public void testGenericAliasUnderVersionGuard() { + doMultiFileStubAwareTest("list[str]", """ + from mod import f + + expr = f("foo") + """); + } + + // PY-76076 PY-60968 + public void testGenericTypeImportedUnderVersionGuard() { + doMultiFileStubAwareTest("list[str]", """ + from mod import C + expr = C().m() + """); + } + + // PY-76076 + public void testFunctionDefinitionUnderVersionGuard() { + doTest("list[str]", """ + import sys + from typing import TypeVar + + T = TypeVar("T") + + if sys.version_info >= (3,): + def f(x: T) -> list[T]: ... + else: + def f(x: T) -> set[T]: ... + + expr = f("foo") + """); + } + + // PY-76076 + public void testClassDefinitionUnderVersionGuard() { + doTest("list[str]", """ + import sys + from typing import TypeVar + + T = TypeVar("T") + + if sys.version_info >= (3,): + class C: + def m(self, x: T) -> list[T]: ... + else: + class C: + def m(self, x: T) -> set[T]: ... + + expr = C().m("foo") + """); + } + + // PY-76076 + public void testVariableDefinitionUnderVersionGuard() { + doTest("int", """ + import sys + + if sys.version_info < (3, 0): + x: str = "foo" + else: + x: int = 42 + + expr = x + """); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());