On Wed, Jan 27, 2021 at 4:43 AM Matthew Rahtz <mrahtz@google.com> wrote:
**Reply to Guido**

> Why do you care about the result of unpacking?

Thinking this through...I guess my main concern would be the issue that I think Eric brought up, concatenation. I'm reasonably convinced it could be done in a way that was sound, but it would be another detail to add to the PEP, which I'm trying to keep minimal.

Sure. But you do have to state explicitly that this is not expected to work.
Also, now that I think about it, if we did allow concatenation, I'm not sure the ellipsis notation would remain intuitive: if we had e.g. `Tuple[int, ..., str]`, my personal intuition is that it would mean "An `int`, zero of more arbitrary types, then a `str`" rather than "An arbitrary number of `int`s, then a `str`".

But then your intuition would be wrong for the meaning of `Tuple[int, ...]` as well. (In two ways actually -- the empty tuple is a member of that type, and the `...` stands for 0 or more times `int`.)
> ISTM that you can define all important operations on this just fine

Eric pointed out at https://mail.python.org/archives/list/typing-sig@python.org/message/25INUZ5KPEKG6V62VBNHDKVSTHXSEABR/ that we'd need 2 rules: if `Ts == Tuple[T, ...]`, then `Union[*Ts]` collapses to `T`, whereas if `Ts == Tuple[T1, T2, T3]` then `Union[*Ts]` collapses to a union of the individual subtypes with literals stripped. Here too, although this seems sound, it's more just that I don't want to add too many of these kinds of subtle details to at least this initial PEP.

Subtle details is why we have PEPs. :-) The weirdness is really that literals are stripped (which I presume means widened to their base type, i.e. `Literal[1]` -> `int` and `Literal[1, ""]` -> `Union[int, str]`).
> Did you just change the subject?

Oh, sorry, I see how that was unclear. The connection was that, if we did allow 'open-ended' type variables, then it's possible for Eric's example

def foo(a: Tuple[*Ts], b: Tuple[*Ts]): ...

foo((3, "hi"), ("hi", ))


to type-check fine with `Ts == Tuple[int | str, ...]`.

> but I'm basically arguing that the tuples need to have the same length

Cool, I agree :)

> Or (as I said last night) we could return NoReturn.

*shrug* Sounds fine.

> But see my remark above -- I think it could use `Tuple[object, object]` or perhaps a tuple of two unions.

Oh, I see what you mean.

I'm not sure how to phrase this - I'm a bit of my depth when it comes to constraint-solving - but what I'd intuitively expect to happen is for type-checker to see the first argument, bind `Ts` to `Tuple[int, str]`, then see the second argument, realise its type is different than what `Ts` has already been bound to, and throw an error. Is it possible to set things up like this? Or are there reasons that in general we should try and solve for the most general type possible?

No, that's not how type variables work at all! When a type checker sees multiple arguments using the same type variable it does a "solve" operation which tries to find a common type. That common type may well be object, and in some type checkers (not mypy) it may be a union. The solve operation is important for class hierarchies, e.g.
class A: ...
class B(A): ...
class C(A): ...
T = TypeVar("T")
def f(a: T, b: T) -> T: ...
x = f(B(), C())  # inferred type is A
To avoid this you have to use `TypeVar(bound=...)` .

This is another reason why I'm not too happy with leaving out all function bodies in examples -- if you have a function like f() in this example, there's not much you can do with the arguments other than print them. E.g. if you wanted to return a+b, you would have to specify as the bound a protocol or concrete type that defines an `__add__` operation, and then a call like `f(1, "")` would be rejected.
Actually, gosh, this is really worth clarifying. In your example:

def foo(a: T, b: T): ...

foo(3, "hi")  # T becomes object

I see what you mean - that `T` being `object` is a valid solution to this - but at the same time it horrifies me - it definitely doesn't feel like that should be a solution. If I had written that code, I'd be trying to enforce that `a` and `b` are the same type. It looks like pytype complains about this example, but Mypy doesn't (`reveal_type` says `a` and `b` are both 'T`-1'; not sure what that means).

Mypy's reveal_type() acts on the function before type variables are expanded, so it just says that they have the same type. The index is to avoid ambiguities in case there are somehow distinct type variables with the same name. (I believe type variables used for class definitions have positive indexes and those defined in function definitions have negative indexes.)

--Guido van Rossum (python.org/~guido)