python / typing-council

Decisions by the Python Typing Council
41 stars 3 forks source link

What are the subtyping rules for `tuple[T, ...]`? #2

Closed erictraut closed 9 months ago

erictraut commented 9 months ago

PEP 484 introduced a way to express "arbitrary-length homogeneous tuples", but it didn't specify the subtyping rules for these types.

Arbitrary-length homogeneous tuples can be expressed using one type and ellipsis, for example Tuple[int, ...].

There are two logical ways to treat such tuples from a type compatibility standpoint:

  1. As a gradual type. With this interpretation, tuple[T, ...] is compatible with a tuple of any length that contains homogeneous elements of type T, and the reverse is true as well. With this interpretation, tuple[T] is compatible with tuple[T, ...] and the converse is true as well.
  2. As a union. With this interpretation, tuple[T, ...] is shorthand for tuple[()] | tuple[T] | tuple[T, T] | .... With this interpretation, tuple[T] is compatible with (and a subtype of) tuple[T, ...], but the converse is not true.

Mypy implements interpretation 2. Pyright previously used interpretation 1, but about a year ago I switched it to use interpretation 2 for compatibility with mypy (which was the reference implementation for PEP 484 and therefore presumably the authority on the topic).

Pyre and pytype appear to use interpretation 1.

Code sample in pyright playground Code sample in mypy playground

def func1(p1: tuple[str, ...]):
    t1: tuple[()] = p1  # Type error under interpretation 2
    t2: tuple[str] = p1  # Type error under interpretation 2
    t3: tuple[str, ...] = p1  # OK

    p1 = ()  # OK
    p1 = ("",)  # OK
    p1 = ("", "")  # OK

I'll note that PEP 646 extended tuple to support ... within a non-homogenous tuple. Both mypy and pyright support this today. Pyre and pytype have not yet added support.

x: tuple[int, *tuple[str, ...], int]
x = (1, 2)
x = (1, "", 2)
x = (1, "", "", 2)

Presumably, this extension should use the same subtyping rules as a simple tuple[T, ...].

It may be useful to pick a standardized term for a tuple that contains a ... (or an unspecialized TypeVarTuple) somewhere within it. I find that it's cumbersome to talk about these things without such a term. If we update the typing spec to clarify the subtyping rules, that would be a good opportunity to also define a term.

JelleZijlstra commented 9 months ago

I agree we should address this area, but I think procedurally this should first be discussed on discuss.python.org, and once there's a rough consensus, a PR should be made to the spec and the Council should be asked to decide on that PR. This makes sure that on the one hand the community has a chance to weigh in, and on the other hand the Council has a more concrete decision to make.

erictraut commented 9 months ago

OK, so you're suggesting that the normal flow for an issue like this is:

  1. Open an issue in discuss.python.org.
  2. Collect feedback for some period of time and attempt to reach general consensus.
  3. Write a formal proposal with a recommended solution and post it here in this issue tracker.
  4. The TC can then weigh in with questions and concerns, ultimately approving or rejecting the issue.

That sounds good to me. I thought that steps 1 and 2 would happen in this forum, but I see how it would be preferable for them to occur in the discuss.python.org forum first.

JelleZijlstra commented 9 months ago

Yes! I think we should write that list in the README for this repo.

JelleZijlstra commented 9 months ago

I specifically want to avoid this repo being the site for any substantive discussions. We have enough fragmentation between the python/typing repo and Discuss. This repo should be very focused on just being for Council decisions.

erictraut commented 9 months ago

OK, I just posted to discuss.python.org. I'm going to close this issue. We can bring it back later.