Skip to content

Use (simplified) unions instead of joins for tuple fallbacks #17408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use (simplified) unions instead of joins for tuple fallbacks
  • Loading branch information
ilevkivskyi committed Jun 20, 2024
commit 66ce50145d058abdc140d8c92c02372ae4e7478a
23 changes: 23 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2605,6 +2605,9 @@ def format_literal_value(typ: LiteralType) -> str:
elif isinstance(typ, LiteralType):
return f"Literal[{format_literal_value(typ)}]"
elif isinstance(typ, UnionType):
typ = get_proper_type(ignore_last_known_values(typ))
if not isinstance(typ, UnionType):
return format(typ)
literal_items, union_items = separate_union_literals(typ)

# Coalesce multiple Literal[] members. This also changes output order.
Expand Down Expand Up @@ -3235,3 +3238,23 @@ def format_key_list(keys: list[str], *, short: bool = False) -> str:
return f"{td}key {formatted_keys[0]}"
else:
return f"{td}keys ({', '.join(formatted_keys)})"


def ignore_last_known_values(t: UnionType) -> Type:
"""This will avoid types like str | str in error messages.

last_known_values are kept during union simplification, but may cause
weird formatting for e.g. tuples of literals.
"""
union_items: list[Type] = []
seen_instances = set()
for item in t.items:
if isinstance(item, ProperType) and isinstance(item, Instance):
erased = item.copy_modified(last_known_value=None)
if erased in seen_instances:
continue
seen_instances.add(erased)
union_items.append(erased)
else:
union_items.append(item)
return UnionType.make_union(union_items, t.line, t.column)
6 changes: 3 additions & 3 deletions mypy/semanal_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from mypy_extensions import trait

from mypy import join
from mypy.errorcodes import LITERAL_REQ, ErrorCode
from mypy.nodes import (
CallExpr,
Expand All @@ -30,6 +29,7 @@
from mypy.plugin import SemanticAnalyzerPluginInterface
from mypy.tvar_scope import TypeVarLikeScope
from mypy.type_visitor import ANY_STRATEGY, BoolTypeQuery
from mypy.typeops import make_simplified_union
from mypy.types import (
TPDICT_FB_NAMES,
AnyType,
Expand Down Expand Up @@ -58,7 +58,7 @@
# Priorities for ordering of patches within the "patch" phase of semantic analysis
# (after the main pass):

# Fix fallbacks (does joins)
# Fix fallbacks (does subtype checks).
PRIORITY_FALLBACKS: Final = 1


Expand Down Expand Up @@ -304,7 +304,7 @@ def calculate_tuple_fallback(typ: TupleType) -> None:
raise NotImplementedError
else:
items.append(item)
fallback.args = (join.join_type_list(items),)
fallback.args = (make_simplified_union(items),)


class _NamedTypeCallback(Protocol):
Expand Down
7 changes: 3 additions & 4 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ def is_recursive_pair(s: Type, t: Type) -> bool:

def tuple_fallback(typ: TupleType) -> Instance:
"""Return fallback type for a tuple."""
from mypy.join import join_type_list

info = typ.partial_fallback.type
if info.fullname != "builtins.tuple":
return typ.partial_fallback
Expand All @@ -115,8 +113,9 @@ def tuple_fallback(typ: TupleType) -> Instance:
raise NotImplementedError
else:
items.append(item)
# TODO: we should really use a union here, tuple types are special.
return Instance(info, [join_type_list(items)], extra_attrs=typ.partial_fallback.extra_attrs)
return Instance(
info, [make_simplified_union(items)], extra_attrs=typ.partial_fallback.extra_attrs
)


def get_self_type(func: CallableType, default_self: Instance | TupleType) -> Type | None:
Expand Down
4 changes: 3 additions & 1 deletion test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1640,10 +1640,12 @@ from typing import Generator
def g() -> Generator[int, None, None]:
x = yield from () # E: Function does not return a value (it only ever returns None)
x = yield from (0, 1, 2) # E: Function does not return a value (it only ever returns None)
x = yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "object", expected type "int") \
x = yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "Union[int, str]", expected type "int") \
# E: Function does not return a value (it only ever returns None)
x = yield from ("ERROR",) # E: Incompatible types in "yield from" (actual type "str", expected type "int") \
# E: Function does not return a value (it only ever returns None)


[builtins fixtures/tuple.pyi]

-- dict(...)
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ nti: NT[int]
reveal_type(nti * x) # N: Revealed type is "builtins.tuple[builtins.int, ...]"

nts: NT[str]
reveal_type(nts * x) # N: Revealed type is "builtins.tuple[builtins.object, ...]"
reveal_type(nts * x) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]"
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

Expand Down Expand Up @@ -1310,9 +1310,9 @@ reveal_type(foo(nti, nts)) # N: Revealed type is "Tuple[builtins.int, builtins.
reveal_type(foo(nts, nti)) # N: Revealed type is "Tuple[builtins.int, builtins.object, fallback=__main__.NT[builtins.object]]"

reveal_type(foo(nti, x)) # N: Revealed type is "builtins.tuple[builtins.int, ...]"
reveal_type(foo(nts, x)) # N: Revealed type is "builtins.tuple[builtins.object, ...]"
reveal_type(foo(nts, x)) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]"
reveal_type(foo(x, nti)) # N: Revealed type is "builtins.tuple[builtins.int, ...]"
reveal_type(foo(x, nts)) # N: Revealed type is "builtins.tuple[builtins.object, ...]"
reveal_type(foo(x, nts)) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]"
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

Expand Down
16 changes: 9 additions & 7 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -1947,7 +1947,7 @@ class NTStr(NamedTuple):
y: str

t1: T
reveal_type(t1.__iter__) # N: Revealed type is "def () -> typing.Iterator[__main__.A]"
reveal_type(t1.__iter__) # N: Revealed type is "def () -> typing.Iterator[Union[__main__.B, __main__.C]]"

t2: NTInt
reveal_type(t2.__iter__) # N: Revealed type is "def () -> typing.Iterator[builtins.int]"
Expand All @@ -1960,7 +1960,6 @@ t: Union[Tuple[int, int], Tuple[str, str]]
for x in t:
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
[builtins fixtures/for.pyi]
[out]

[case testNewAnalyzerFallbackUpperBoundCheckAndFallbacks]
from typing import TypeVar, Generic, Tuple
Expand All @@ -1973,18 +1972,21 @@ S = TypeVar('S', bound='Tuple[G[A], ...]')

class GG(Generic[S]): pass

g: GG[Tuple[G[B], G[C]]] \
# E: Type argument "Tuple[G[B], G[C]]" of "GG" must be a subtype of "Tuple[G[A], ...]" \
# E: Type argument "B" of "G" must be a subtype of "A" \
# E: Type argument "C" of "G" must be a subtype of "A"
g: GG[Tuple[G[B], G[C]]] # E: Type argument "Tuple[G[B], G[C]]" of "GG" must be a subtype of "Tuple[G[A], ...]" \
# E: Type argument "B" of "G" must be a subtype of "A" \
# E: Type argument "C" of "G" must be a subtype of "A"

T = TypeVar('T', bound=A, covariant=True)

class G(Generic[T]): pass

t: Tuple[G[B], G[C]] # E: Type argument "B" of "G" must be a subtype of "A" \
# E: Type argument "C" of "G" must be a subtype of "A"
reveal_type(t.__iter__) # N: Revealed type is "def () -> typing.Iterator[builtins.object]"
reveal_type(t.__iter__) # N: Revealed type is "def () -> typing.Iterator[__main__.G[__main__.B]]"




[builtins fixtures/tuple.pyi]

[case testNewAnalyzerClassKeywordsForward]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-statements.test
Original file line number Diff line number Diff line change
Expand Up @@ -1339,7 +1339,7 @@ from typing import Generator
def g() -> Generator[int, None, None]:
yield from ()
yield from (0, 1, 2)
yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "object", expected type "int")
yield from (0, "ERROR") # E: Incompatible types in "yield from" (actual type "Union[int, str]", expected type "int")
yield from ("ERROR",) # E: Incompatible types in "yield from" (actual type "str", expected type "int")
[builtins fixtures/tuple.pyi]

Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -1408,8 +1408,8 @@ y = ""
reveal_type(t[x]) # N: Revealed type is "Union[builtins.int, builtins.str]"
t[y] # E: No overload variant of "__getitem__" of "tuple" matches argument type "str" \
# N: Possible overload variants: \
# N: def __getitem__(self, int, /) -> object \
# N: def __getitem__(self, slice, /) -> Tuple[object, ...]
# N: def __getitem__(self, int, /) -> Union[int, str] \
# N: def __getitem__(self, slice, /) -> Tuple[Union[int, str], ...]

[builtins fixtures/tuple.pyi]

Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def g(a: Tuple[Unpack[Ts]], b: Tuple[Unpack[Ts]]) -> Tuple[Unpack[Ts]]:

reveal_type(g(args, args)) # N: Revealed type is "Tuple[builtins.int, builtins.str]"
reveal_type(g(args, args2)) # N: Revealed type is "Tuple[builtins.int, builtins.str]"
reveal_type(g(args, args3)) # N: Revealed type is "builtins.tuple[builtins.object, ...]"
reveal_type(g(args, args3)) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.str], ...]"
reveal_type(g(any, any)) # N: Revealed type is "builtins.tuple[Any, ...]"
[builtins fixtures/tuple.pyi]

Expand Down Expand Up @@ -989,7 +989,7 @@ from typing_extensions import Unpack

def pipeline(*xs: Unpack[Tuple[int, Unpack[Tuple[float, ...]], bool]]) -> None:
for x in xs:
reveal_type(x) # N: Revealed type is "builtins.float"
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.float]"
[builtins fixtures/tuple.pyi]

[case testFixedUnpackItemInInstanceArguments]
Expand Down Expand Up @@ -1715,7 +1715,7 @@ vt: Tuple[int, Unpack[Tuple[float, ...]], int]

reveal_type(vt + (1, 2)) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int, Literal[1]?, Literal[2]?]"
reveal_type((1, 2) + vt) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, builtins.int, Unpack[builtins.tuple[builtins.float, ...]], builtins.int]"
reveal_type(vt + vt) # N: Revealed type is "builtins.tuple[builtins.float, ...]"
reveal_type(vt + vt) # N: Revealed type is "builtins.tuple[Union[builtins.int, builtins.float], ...]"
reveal_type(vtf + (1, 2)) # N: Revealed type is "Tuple[Unpack[builtins.tuple[builtins.float, ...]], Literal[1]?, Literal[2]?]"
reveal_type((1, 2) + vtf) # N: Revealed type is "Tuple[Literal[1]?, Literal[2]?, Unpack[builtins.tuple[builtins.float, ...]]]"

Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/semanal-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ MypyFile:1(
TupleType(
Tuple[builtins.int, builtins.str])
BaseType(
builtins.tuple[builtins.object, ...])
builtins.tuple[Union[builtins.int, builtins.str], ...])
PassStmt:2()))

[case testBaseClassFromIgnoredModule]
Expand Down
Loading