There are likely many algorithmic variations that produce equivalent results. I think it’s important for us to document one algorithm — preferably the simplest one to explain, and individual type checkers can then choose to implement the “base” algorithm or an equivalent algorithm that is more optimal.
I think your algorithm is equivalent to what I documented as long as there are no overlapping overloads present. We could say that when overlapping overloads are present, the behavior is indeterminate. I’m OK with that stance. However, I see overlapping overloads appear frequently in libraries and stub files. (Search for # type: ignore[overload-overlap]
in typeshed, and you’ll find more than 200 instances.) So it might be preferable not to leave the behavior indeterminate in this case.
This rule comes from mypy. I implemented it in pyright when I was attempting to match mypy’s behaviors. It is not well described in the mypy documentation for overloads. The phrase “taking into consideration both the argument types and arity” leaves some room for interpretation.
I don’t think this rule applies only where overloads overlap unsafely. It comes up in situations like the zip
constructor in builtins.pyi
. Similar patterns show up frequently in the pandas
stubs.
from typing import Iterable
def func(a: list[Iterable[int]]):
v1 = zip(*a)
# Without Step 4, this evaluates to zip[tuple[Iterable[int]]]
# With Step 4, this evaluates to zip[Any]
reveal_type(v1)
Yes, my examples were incorrect. I’ll work on better examples.
“Need not be” or “should not be”? If we want consistent behavior from type checkers, I think we need to be more prescriptive in the spec. I’m a bit uncomfortable saying that tuples shouldn’t be expanded to their equivalent subtypes.
It’s interesting that you thought “it feels wrong if they behave differently” for bool
but not for tuple
. After all, tuple[int | str, int | str]
is equivalent to tuple[int, int] | tuple[int, str] | tuple[str, int] | tuple[str, str]
, and the typing spec spells this out in the tuples chapter.
Use of context: I’m not sure we should specify that type checkers should use context to evaluate a type. It should be allowed, for example, to evaluate {'x': 1}
to an internal type that is assignable to both dict[str, int]
and a TypedDict with x: int
.
Hmm, allowing a type to be assigned to two different (incompatible) types doesn’t sound type safe to me. That would imply that one could assign the same value to both of those types, and that could lead to type violations.
I admit that I’m uneasy bringing up the topic of “context” in the overload chapter because there’s no other mention of it (currently) in the typing spec. However, without mentioning it here, I couldn’t figure out how to make the spec clear enough such that we can achieve consistent results between type checkers.