mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
PY-76243 Don't build implicit union types for conditional definitions and names imported from stub packages
Also fixes PY-59014, PY-39761. PyResolveImportUtil returns both .pyi stubs and the corresponding .py files for stub packages to support partial stub packages. See the line: ``` groupedResults.topResultIs(Priority.STUB_PACKAGE) -> firstResultWithFallback(groupedResults, Priority.STUB_PACKAGE) ``` in PyResolveImportUtil.filterTopPriorityResults. It means that, for instance, resolving the QuerySet name in type hints led to QuerySet definitions from both places. Then, PyTypingTypeProvider.getType() for the reference expression "QuerySet" returned a union type containing PyClassTypes for both of them, we couldn't parameterize it in PyTypingTypeProvider.getParameterizedType and returned Any. It's wrong that while evaluating type hints, we interpret multiple declarations as a union type. Those should only be explicitly expressed with typing.Union or "|" operator. This behavior was originally added in PY-18427 as an ad-hoc way to support version checks for type hints, but now it seems detrimental because it's unclear how to parameterize such implicit unions of generic types then. Other type checkers also don't treat conditional definitions like that. For instance, for conditional type aliases, Mypy complains about the name being defined twice and then uses only the first definition, and Pyright doesn't consider names under conditions other than version checks as valid type aliases at all. Both type checkers also support partial stub packages properly. GitOrigin-RevId: 1ecc7ab5d09625d10850ddc0e1f7761332ccddd5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
28f446816d
commit
e2d7d259e9
@@ -765,20 +765,13 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Ref<PyType> getType(@NotNull PyExpression expression, @NotNull Context context) {
|
private static Ref<PyType> getType(@NotNull PyExpression expression, @NotNull Context context) {
|
||||||
final List<PyType> members = new ArrayList<>();
|
|
||||||
boolean foundAny = false;
|
|
||||||
for (Pair<PyQualifiedNameOwner, PsiElement> pair : tryResolvingWithAliases(expression, context.getTypeContext())) {
|
for (Pair<PyQualifiedNameOwner, PsiElement> pair : tryResolvingWithAliases(expression, context.getTypeContext())) {
|
||||||
final Ref<PyType> typeRef = getTypeForResolvedElement(expression, pair.getFirst(), pair.getSecond(), context);
|
final Ref<PyType> typeRef = getTypeForResolvedElement(expression, pair.getFirst(), pair.getSecond(), context);
|
||||||
if (typeRef != null) {
|
if (typeRef != null) {
|
||||||
final PyType type = typeRef.get();
|
return typeRef;
|
||||||
if (type == null) {
|
|
||||||
foundAny = true;
|
|
||||||
}
|
|
||||||
members.add(type);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final PyType union = PyUnionType.union(members);
|
return null;
|
||||||
return union != null || foundAny ? Ref.create(union) : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
class MyClass[T]:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
class MyClass:
|
||||||
|
pass
|
||||||
@@ -593,17 +593,55 @@ public class PyTypingTest extends PyTestCase {
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
// PY-18427
|
// PY-18427 PY-76243
|
||||||
public void testConditionalType() {
|
public void testConditionalTypeAlias() {
|
||||||
doTest("int | str",
|
doTest("int",
|
||||||
"""
|
"""
|
||||||
if something:
|
if something:
|
||||||
Type = int
|
Type = int
|
||||||
else:
|
else:
|
||||||
Type = str
|
Type = str
|
||||||
|
|
||||||
def f(expr: Type):
|
expr: Type
|
||||||
pass
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testConditionalGenericTypeAlias() {
|
||||||
|
doTest("list[str]",
|
||||||
|
"""
|
||||||
|
if something:
|
||||||
|
Type = list
|
||||||
|
else:
|
||||||
|
Type = set
|
||||||
|
|
||||||
|
expr: Type[str]
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTypeAliasOfUnionOfGenericTypes() {
|
||||||
|
doTest("list[str] | set[str]",
|
||||||
|
"""
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
Type = list[T] | set[T]
|
||||||
|
|
||||||
|
expr: Type[str]
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTypeAliasOfUnionOfGenericTypesWithDifferentArity() {
|
||||||
|
doTest("dict[str, int] | set[int]",
|
||||||
|
"""
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T1 = TypeVar("T1")
|
||||||
|
T2 = TypeVar("T2")
|
||||||
|
|
||||||
|
Type = dict[T1, T2] | set[T2]
|
||||||
|
|
||||||
|
expr: Type[str, int]
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1950,28 +1988,24 @@ public class PyTypingTest extends PyTestCase {
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
if something:
|
if something:
|
||||||
Type = int
|
x: int
|
||||||
else:
|
else:
|
||||||
Type = str
|
x: str
|
||||||
|
expr = x
|
||||||
def f(expr: Type):
|
|
||||||
pass
|
|
||||||
""");
|
""");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// PY-44974
|
// PY-44974
|
||||||
public void testWithoutFromFutureImport() {
|
public void testBitwiseOrUnionWithoutFromFutureImport() {
|
||||||
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
|
runWithLanguageLevel(LanguageLevel.PYTHON39, () -> {
|
||||||
doTest("Union[int, str]",
|
doTest("Union[int, str]",
|
||||||
"""
|
"""
|
||||||
if something:
|
if something:
|
||||||
Type = int
|
x: int
|
||||||
else:
|
else:
|
||||||
Type = str
|
x: str
|
||||||
|
expr = x
|
||||||
def f(expr: Type):
|
|
||||||
pass
|
|
||||||
""");
|
""");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -5787,6 +5821,17 @@ public class PyTypingTest extends PyTestCase {
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PY-76243
|
||||||
|
public void testGenericClassDeclaredInStubPackage() {
|
||||||
|
runWithAdditionalClassEntryInSdkRoots("types/" + getTestName(false) + "/site-packages", () -> {
|
||||||
|
doTest("MyClass[int]",
|
||||||
|
"""
|
||||||
|
from pkg.mod import MyClass
|
||||||
|
expr: MyClass[int]
|
||||||
|
""");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void doTestNoInjectedText(@NotNull String text) {
|
private void doTestNoInjectedText(@NotNull String text) {
|
||||||
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
myFixture.configureByText(PythonFileType.INSTANCE, text);
|
||||||
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
|
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
|
||||||
|
|||||||
Reference in New Issue
Block a user