PY-76076 Acknowledge sys.version_info guards in flow-sensitive same-scope resolve

sys.version_info guards are processed at the level of ScopeImpl.collectDeclarations and
PyResolveUtil.scopeCrawlUp in PyReferenceImpl.resolveInner, as implemented in
3318ff79cdcc5ba0ce5e4feb65abad5ad0f4acfa.
However, once we collected all name definition candidates flow-insensitively this way, in
PyReferenceImpl.getResultsFromProcessor, if the reference and these candidates were located
in the same scope, we completely ignored these variants and gathered reachable definitions all
over again from CFG using PyDefUseUtil.getLatestDefs. And the latter didn't consider version
guards at all. I've added version guard checks directly in PyDefUseUtil.getLatestDefs.

GitOrigin-RevId: 9f92eecd1eb1812bfbd2bf54f8192f45f0cf0a1d
This commit is contained in:
Mikhail Golubev
2024-09-20 15:14:33 +03:00
committed by intellij-monorepo-bot
parent de0f128ac5
commit ffceeb161b
4 changed files with 105 additions and 2 deletions

View File

@@ -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<PyCallSiteExpression, ConditionalInstruction> 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) {

View File

@@ -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

View File

@@ -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]:
...

View File

@@ -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());