PY-86002 Disable "python.typing.strict.unions" and revert the tests updated for PY-24834 in 253

(cherry picked from commit 3df7847e2f94e8368e15e331f16a2254154e63fa)

IJ-MR-184619

GitOrigin-RevId: 213aedec5242143c1456450c214deb6f6e0fd60e
This commit is contained in:
Mikhail Golubev
2025-12-03 20:58:50 +00:00
committed by intellij-monorepo-bot
parent cd1307bcc5
commit fd524cbe67
30 changed files with 61 additions and 303 deletions
@@ -683,7 +683,7 @@
<registryKey key="python.activate.virtualenv.on.run" defaultValue="true"
description="Whether enabled activates virtualenv before script execution and passes environment to the process"/>
<registryKey key="python.typing.strict.unions" defaultValue="true" description="Enable strict union types semantics"/>
<registryKey key="python.typing.strict.unions" defaultValue="false" description="Enable strict union types semantics"/>
<!-- uv -->
<configurationType implementation="com.jetbrains.python.sdk.uv.run.UvRunConfigurationType"/>
@@ -5,7 +5,7 @@ def test(p1):
for x in <warning descr="Expected type 'collections.Iterable', got 'int' instead">42</warning>:
pass
for x in <warning descr="Expected type 'collections.Iterable', got 'Union[int, Any]' instead">f('foo', p1)</warning>:
for x in f('foo', p1):
pass
@@ -3,5 +3,5 @@ KWARGS = {
"little_list": ['WORLD_RET_BP_IMPALA_AB.Control', 'WORLD_RET_BP_IMPALA_AB.Impala_WS'],
}
for element in <warning descr="Expected type 'collections.Iterable', got 'Union[bool, List[str]]' instead">KWARGS["little_list"]</warning>:
for element in KWARGS["little_list"]:
print(element)
@@ -15,4 +15,5 @@ list_union_literal_incorrect: List[Union[L2, L1]] = <warning descr="Expected typ
list_union_literal_incorrect: List[Union[L2, L1]] = <warning descr="Expected type 'list[Literal['a', 'b', 5, 'test']]', got 'list[Literal['a', 'r']]' instead">['a', 'r']</warning>
list_tuple: List[Tuple[L2, L1]] = [('b', 'test'), (5, 'test')]
list_tuple_incorrect: List[Tuple[L2, L1]] = <warning descr="Expected type 'list[tuple[Literal['a', 'b', 5], Literal['test']]]', got 'list[tuple[Literal['a']] | tuple[Literal[5], Literal['test']]]' instead">[('a',), (5, 'test')]</warning>
# TODO false negative due to PY-24834
list_tuple_incorrect: List[Tuple[L2, L1]] = [('a',), (5, 'test')]
@@ -14,8 +14,8 @@ def test():
x1 = f1()
x2 = f2()
x3 = 1
f(<warning descr="Expected type 'Union[int, str, list]', got 'Optional[int]' instead">x1</warning>)
f(<warning descr="Expected type 'Union[int, str, list]', got 'Union[int, str, None]' instead">x2</warning>)
f(x1) # Weaker union types
f(x2) # Weaker union types
f(x3)
if x1:
f(x1)
@@ -15,4 +15,5 @@ set_union_literal_incorrect: Set[Union[L2, L1]] = <warning descr="Expected type
set_union_literal_incorrect2: Set[Union[L2, L1]] = <warning descr="Expected type 'set[Literal['a', 'b', 5, 'test']]', got 'set[Literal['a', 'r']]' instead">{'a', 'r'}</warning>
set_of_tuple_and_list: Set[Union[Tuple[L2, L1], List[L1]]] = {('b', 'test'), ['test', 'test'], (5, 'test')}
set_of_tuple_and_list_incorrect: Set[Union[Tuple[L2, L1], List[L1]]] = <warning descr="Expected type 'set[tuple[Literal['a', 'b', 5], Literal['test']] | list[Literal['test']]]', got 'set[tuple[Literal['b'], Literal['r']] | list[Literal['test']] | tuple[Literal[5], Literal['test']]]' instead">{('b', 'r'), ['test', 'test'], (5, 'test')}</warning>
# TODO false negative due to PY-24834
set_of_tuple_and_list_incorrect: Set[Union[Tuple[L2, L1], List[L1]]] = {('b', 'r'), ['test', 'test'], (5, 'test')}
@@ -1,51 +0,0 @@
from typing import Any
class A:
def __iter__(self):
return self
def __next__(self):
return 42
class B:
def __iter__(self):
return self
def __next__(self):
return 42
class C:
pass
def all_union_members_match_no_any(iterable: A | B):
for _ in iterable:
pass
def some_union_members_match_no_any(iterable: A | B | None):
for _ in <warning descr="Expected type 'collections.Iterable', got 'A | B | None' instead">iterable</warning>:
pass
def all_union_members_dont_match_no_any(iterable: C | None):
for _ in <warning descr="Expected type 'collections.Iterable', got 'C | None' instead">iterable</warning>:
pass
def all_union_members_match_with_any(iterable: A | B | Any):
for _ in iterable:
pass
def some_union_members_match_with_any(iterable: A | B | None | Any):
for _ in <warning descr="Expected type 'collections.Iterable', got 'A | B | None | Any' instead">iterable</warning>:
pass
def all_union_members_dont_match_with_any(iterable: C | None | Any):
for _ in <warning descr="Expected type 'collections.Iterable', got 'C | None | Any' instead">iterable</warning>:
pass
@@ -37,8 +37,8 @@ def test():
:type x: int or str
"""
x_1 = f_1()
print_int(<warning descr="Expected type 'int', got 'Union[int, str, None]' instead">x_1</warning>)
print_int_or_str(<warning descr="Expected type 'Union[int, str]', got 'Union[int, str, None]' instead">x_1</warning>)
print_int(x_1) # Weaker union types
print_int_or_str(x_1) # Weaker union types
if isinstance(x_1, int):
print_int(x_1)
if isinstance(x_1, str):
@@ -16,7 +16,7 @@ def test(c):
:type x: int
"""
x1 = f1(c)
f2(<warning descr="Expected type 'str', got 'Union[List[Any], str, None]' instead">x1</warning>)
f2(x1) # Weaker union types
f3(<warning descr="Expected type 'int', got 'Union[List[Any], str, None]' instead">x1</warning>)
f2(<warning descr="Expected type 'str', got 'int' instead">x1.count('')</warning>)
@@ -57,7 +57,7 @@ def f():
class MyTestCase(unittest.TestCase):
def test_something(self):
with self.assertRaises(Exception):
with self.assertRaises():
raise Foo
foo() # pass
@@ -6,7 +6,7 @@ async def expect_no_warning():
async def expect_new_warning():
await <weak_warning descr="Function 'fun_non_async()' neither declared as 'async' nor with 'Awaitable' as return type">fun_non_async()</weak_warning>
await <warning descr="Function 'fun_non_async()' neither declared as 'async' nor with 'Awaitable' as return type">fun_non_async()</warning>
def local_fun_non_async():
@@ -2,7 +2,7 @@ from b import fun_awaitable_imported, MyAwaitable
async def expect_false_positive_warning():
await <weak_warning descr="Function 'fun_awaitable_imported()' neither declared as 'async' nor with 'Awaitable' as return type">fun_awaitable_imported()</weak_warning>
await <warning descr="Function 'fun_awaitable_imported()' neither declared as 'async' nor with 'Awaitable' as return type">fun_awaitable_imported()</warning>
async def expect_pass_1():
@@ -1,6 +1,6 @@
def f(i):
xs = []
xs[i].<weak_warning descr="Member 'List[Any]' of 'Union[List[Any], Any]' does not have attribute 'items'">items</weak_warning>()
xs[i].items()
def g(index):
@@ -1,4 +1,4 @@
def foo(smth, param):
if smth:
param = ""
print(param.<weak_warning descr="Member 'str' of 'Union[{smth}, str]' does not have attribute 'smth'">smth</weak_warning>())
print(param.smth())
@@ -1,31 +0,0 @@
from typing import Any
class A:
def method(self):
pass
class B(A):
pass
class C:
def method(self):
pass
def union_with_all_compatible_types(x: A | B | C):
x.method()
def union_with_some_incompatible_types(x: A | None):
x.<weak_warning descr="Member 'None' of 'A | None' does not have attribute 'method'">method</weak_warning>()
def union_with_all_incompatible_types(x: object | None):
x.<weak_warning descr="Member 'object' of 'object | None' does not have attribute 'method'">method</weak_warning>()
def union_with_some_incompatible_types_and_any(x: Any | None):
x.<weak_warning descr="Member 'None' of 'Any | None' does not have attribute 'method'">method</weak_warning>()
def narrowing_union_with_some_incompatible_types_after(x: Any | None):
if isinstance(x, A):
x.method()
assert isinstance(x, B)
x.method()
@@ -1,13 +0,0 @@
from typing import Any
class A:
a = 1
class Anish(Any):
pass
a = A() if bool() else Anish()
_ = a.a
_ = a.<weak_warning descr="Member 'A' of 'A | Anish' does not have attribute 'b'">b</weak_warning>
@@ -1,63 +0,0 @@
from typing import Any
class A:
def __pos__(self):
pass
def __add__(self, other):
pass
def __getitem__(self, item):
pass
class B:
def __pos__(self):
pass
def __add__(self, other):
pass
def __getitem__(self, item):
pass
class C:
pass
def all_union_members_match_no_any(x: A | B):
print(+x)
print(x + 1)
print(x[42])
def some_union_members_match_no_any(x: A | B | None):
print(<weak_warning descr="Member 'None' of 'A | B | None' does not have attribute '__pos__'">+</weak_warning>x)
print(x <weak_warning descr="Member 'None' of 'A | B | None' does not have attribute '__add__'">+</weak_warning> 1)
print(x<weak_warning descr="Member 'None' of 'A | B | None' does not have attribute '__getitem__'">[</weak_warning>42])
def all_union_members_dont_match_no_any(x: C | None):
print(<weak_warning descr="Member 'C' of 'C | None' does not have attribute '__pos__'">+</weak_warning>x)
print(x <weak_warning descr="Member 'C' of 'C | None' does not have attribute '__add__'">+</weak_warning> 1)
print(x<weak_warning descr="Member 'C' of 'C | None' does not have attribute '__getitem__'">[</weak_warning>42])
def all_union_members_match_with_any(x: A | B | Any):
print(+x)
print(x + 1)
print(x[42])
def some_union_members_match_with_any(x: A | B | None | Any):
print(<weak_warning descr="Member 'None' of 'A | B | None | Any' does not have attribute '__pos__'">+</weak_warning>x)
print(x <weak_warning descr="Member 'None' of 'A | B | None | Any' does not have attribute '__add__'">+</weak_warning> 1)
print(x<weak_warning descr="Member 'None' of 'A | B | None | Any' does not have attribute '__getitem__'">[</weak_warning>42])
def all_union_members_dont_match_with_any(x: C | None | Any):
print(<weak_warning descr="Member 'C' of 'C | None | Any' does not have attribute '__pos__'">+</weak_warning>x)
print(x <weak_warning descr="Member 'C' of 'C | None | Any' does not have attribute '__add__'">+</weak_warning> 1)
print(x<weak_warning descr="Member 'C' of 'C | None | Any' does not have attribute '__getitem__'">[</weak_warning>42])
@@ -5,7 +5,7 @@ def f(x):
def f(x):
try:
from <error descr="Unresolved reference 'foo'">foo</error> import <weak_warning descr="Module 'StringIO' not found">StringIO</weak_warning>
from <error descr="Unresolved reference 'foo'">foo</error> import <warning descr="Module 'StringIO' not found">StringIO</warning>
except Exception:
pass
return x
@@ -28,14 +28,14 @@ def f(x):
try:
import foo as bar
except ImportError:
import <weak_warning descr="Module 'bar' not found">bar</weak_warning>
import <warning descr="Module 'bar' not found">bar</warning>
# PY-3678
def f():
try:
from foo import bar #pass
except ImportError:
import <weak_warning descr="Module 'bar' not found">bar</weak_warning> #fail
import <warning descr="Module 'bar' not found">bar</warning> #fail
finally:
pass
@@ -6,5 +6,5 @@ async def foo(x):
return y
async def bar(x_new) -> Any:
async def bar(x_new) -> int | Any:
return await x_new + 1
@@ -8,6 +8,6 @@ class Test:
y = extracted(c)
return y
def extracted(c_new) -> Any:
def extracted(c_new) -> int | Any:
y = self.a + b * c_new
return y
@@ -7,6 +7,6 @@ class Test:
y = extracted()
return y
def extracted() -> Any:
def extracted() -> int | Any:
y = x * 2
return y
@@ -7,6 +7,6 @@ class Test:
y = extracted(x)
return y
def extracted(x_new) -> Any:
def extracted(x_new) -> int | Any:
y = x_new * 2
return y
@@ -6,5 +6,5 @@ def compound_duplicate(p1, p2):
print(bar(p2))
def bar(p1_new) -> Any:
def bar(p1_new) -> int | Any:
return p1_new + 1
@@ -14,6 +14,6 @@ def f(p: Person, salutation: str, ageHolder: Ageholder):
return <caret>greeting(ageHolder, p, salutation)
def greeting(ageHolder_new: Ageholder, p_new: Person, salutation_new: str) -> str:
def greeting(ageHolder_new: Ageholder, p_new: Person, salutation_new: str) -> LiteralString | str | int:
return salutation_new + p_new.name + "(" + ageHolder_new.age + ")"
@@ -797,11 +797,11 @@ public class PyTypeTest extends PyTestCase {
""";
final PyExpression expr = parseExpr(text);
assertNotNull(expr);
doTest("UnsafeUnion[Union[int, str], Any]", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile()));
doTest("Union[Union[int, str], Any]", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile()));
}
public void testUpperBoundGeneric() {
doTest("UnsafeUnion[Union[int, str], Any]",
doTest("Union[Union[int, str], Any]",
"""
def foo(x):
'''
@@ -1641,7 +1641,7 @@ public class PyTypeTest extends PyTestCase {
doTest("List[Union[str, int]]", "expr = ['1', 1, 1]");
doTest("List[UnsafeUnion[Union[str, int], Any]]", "expr = ['1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]");
doTest("List[Union[Union[str, int], Any]]", "expr = ['1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]");
}
public void testSetLiteral() {
@@ -1649,7 +1649,7 @@ public class PyTypeTest extends PyTestCase {
doTest("Set[Union[str, int]]", "expr = {'1', 1, 1}");
doTest("Set[UnsafeUnion[Union[str, int], Any]]", "expr = {'1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}");
doTest("Set[Union[Union[str, int], Any]]", "expr = {'1', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}");
}
public void testDictLiteral() {
@@ -2773,23 +2773,23 @@ public class PyTypeTest extends PyTestCase {
runWithLanguageLevel(
LanguageLevel.PYTHON35,
() -> {
doTest("UnsafeUnion[int, Any]",
doTest("Union[int, Any]",
"""
from typing import Any
x: Any
expr = x * 2""");
doTest("UnsafeUnion[int, Any]",
doTest("Union[int, Any]",
"""
from typing import Any
x: Any
expr = 2 * x""");
doTest("UnsafeUnion[int, Any]",
doTest("Union[int, Any]",
"def f(x):\n" +
" expr = x * 2");
doTest("UnsafeUnion[int, Any]",
doTest("Union[int, Any]",
"def f(x):\n" +
" expr = 2 * x");
}
@@ -18,7 +18,6 @@ package com.jetbrains.python;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.util.PsiTreeUtil;
@@ -248,7 +247,7 @@ public class PyTypingTest extends PyTestCase {
}
public void testAnyStrForUnknown() {
doTest("UnsafeUnion[str | bytes, Any]",
doTest("str | bytes | Any",
"""
from typing import AnyStr
@@ -3614,52 +3613,6 @@ public class PyTypingTest extends PyTestCase {
expr = receiver.get()""");
}
// PY-24834
// It works incorrectly due to PY-83119 (the information about unresolved union member attributes
// being lost during type inference).
public void testGenericUnionMemberMethodCallSomeMembersDoNotOwnIt() {
doTest("str", // Should be `str | Any`
"""
class Box[T]:
def get(self) -> T:
pass
r: int | Box[str] = ...
expr = r.get()
""");
}
// PY-24834
// This version doesn't work now properly because of lacking constraint solving.
// We can't match `Box[T]` for `self` with `Box[int] | Box[str]`.
public void testGenericUnionMemberCallAllMembersAreSameClassParameterizations() {
doTest("Any", // Should be `int | str`
"""
class Box[T]:
def get(self) -> T:
pass
r: Box[int] | Box[str] = ...
expr = r.get()
""");
}
// PY-24834
public void testGenericUnionMemberCallAllMembersOwnIt() {
doTest("int | str",
"""
class Box1[T]:
def get(self) -> T:
pass
class Box2[T]:
def get(self) -> T:
pass
r: Box1[int] | Box2[str] = ...
expr = r.get()
""");
}
public void testGenericClassTypeHintedInDocstrings() {
doTest("int",
"""
@@ -6784,26 +6737,6 @@ public class PyTypingTest extends PyTestCase {
""");
}
// See com.jetbrains.python.refactoring.PyExtractMethodTest.testTypedStatements
//
// This scenario changes depending on whether the strict unions are enabled.
// Without them, the inferred type is LiteralString | str | int, because due to special handling
// of unions containing literal types in PyTypeChecker, none of the candidate methods fully matches:
// `LiteralString | str | int` receiver is compatible with neither `LiteralString`, `str` nor `int` for `self`,
// so we infer a union of all possible return types.
// With strict unions, due to special handling of self in #processSelfParameter, only
// `__add__(self: str, other: str) -> str` overload remains.
// PY-24834 PY-83313
public void testUnionStrConcat() {
//Registry.get("python.typing.strict.unions").setValue(false, myFixture.getTestRootDisposable());
doTest("str", """
from typing import LiteralString
x: LiteralString | str | int
expr = x + "foo"
""");
}
private void doTestNoInjectedText(@NotNull String text) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject());
@@ -2895,28 +2895,6 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
""");
}
// PY-74277
public void testPassingTypeIsCallable() {
runWithLanguageLevel(
LanguageLevel.PYTHON312,
() -> doTestByText("""
from typing_extensions import TypeIs, Callable
def takes_narrower(x: int | str, narrower: Callable[[object], TypeIs[int]]):
if narrower(x):
expr1: int = x
# └─ should be of `int` type
else:
expr2: str = x
# └─ should be of `str` type
def is_bool(x: object) -> TypeIs[bool]:
return isinstance(x, bool)
takes_narrower(42, <warning descr="Expected type '(object) -> TypeIs[int]', got '(x: object) -> TypeIs[bool]' instead">is_bool</warning>)
"""));
}
// PY-75556
public void testLiteralTypeOnKwargs() {
doTestByText("""
@@ -3095,11 +3073,6 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase {
""");
}
// PY-24834
public void testStrictUnionImplicitProtocolMatching() {
doTest();
}
// PY-76822
public void testProtocolWithAssignedPropertyInMethod() {
doTestByText("""
@@ -477,21 +477,6 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase
}
// PY-24834
public void testStrictUnionMemberAttributeAccess() {
doTest();
}
// PY-24834
public void testStrictUnionMemberOperatorAccess() {
doTest();
}
// PY-24834
public void testStrictUnionMemberExtendingAny() {
doTest();
}
// PY-83529
public void testPackageAttributeInPresenceOfBinarySkeleton() {
runWithAdditionalClassEntryInSdkRoots(getTestDirectoryPath() + "/site-packages", () -> {
@@ -1534,10 +1534,10 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
title: str
year: int
movies1: list[Movie] = <warning descr="Expected type 'list[Movie]', got 'list[Movie | dict[str, str]]' instead">[
movies1: list[Movie] = [
{"title": "Blade Runner", "year": 1982}, # OK
{"title": "The Matrix"},
]</warning>
]
movies2: list[Movie] = <warning descr="Expected type 'list[Movie]', got 'list[dict[str, str]]' instead">[
{"title": "The Matrix"},
]</warning>
@@ -1609,6 +1609,29 @@ public class PyTypeCheckerInspectionTest extends PyInspectionTestCase {
);
}
// PY-74277
public void testPassingTypeIsCallable() {
runWithLanguageLevel(
LanguageLevel.PYTHON312,
() -> doTestByText("""
from typing_extensions import TypeIs
def takes_narrower(x: int | str, narrower: Callable[[object], TypeIs[int]]):
if narrower(x):
expr1: int = x
# └─ should be of `int` type
else:
expr2: str = x
# └─ should be of `str` type
def is_bool(x: object) -> TypeIs[bool]:
return isinstance(x, bool)
takes_narrower(42, is_bool)
"""));
}
public void testGeneratorTypeHint() {
runWithLanguageLevel(LanguageLevel.getLatest(), this::doTest);
}
@@ -106,7 +106,7 @@ public class PyUnusedImportTest extends PyTestCase {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureFromTempProjectFile(filename);
myFixture.enableInspections(PyUnusedImportsInspection.class, PyUnresolvedReferencesInspection.class);
myFixture.checkHighlighting(true, false, true);
myFixture.checkHighlighting(true, false, false);
}
@Override