Ah, sorry, still getting my head around what 'soundness' means.

Isn't the first option unsound in the same way that normal type variables are unsound, though? E.g. one could do the following:

T = TypeVar('T')

class Tensor(Generic[T]):
    def __getitem__(self, value):
        return 0  # Dummy value

def foo(x: Tensor):
    return x[0]

x: Tensor[()] = Tensor()

Mypy is fine with this, even though it wouldn't work at runtime if `Tensor` were given a complete implementation (since the argument `x` is zero-rank, so we shouldn't be able to index it).

Option one would be unsound, but at least it would be unsound in a consistent way.

On Tue, 2 Feb 2021 at 21:49, S Pradeep Kumar <gohanpra@gmail.com> wrote:

On Tue, Feb 2, 2021 at 1:08 PM Matthew Rahtz via Typing-sig <typing-sig@python.org> wrote:

**Unbounded tuples**

Pradeep, do you think we should include support for unbounded tuples in the PEP? I'd prefer to hold off

I'm ok with just allowing `Tensor` to be the only unbounded variadic allowed. It would be implicitly treated as `Tensor[*Tuple[Any, ...]]`. So, we wouldn't allow explicitly using `Tensor[*Tuple[int, ...]]`.

**Arbitrary-rank tensors**

Oh, man, super well-caught! You're right, committing to invariance by default does put us in a tricky situation.

But then - trying this with a regular `TypeVar`, mypy seems to be happy with the following:

from typing import Generic, TypeVar

T = TypeVar('T')

class Tensor(Generic[T]):

def expects_arbitrary_tensor(x: Tensor):

def expects_concrete_tensor(x: Tensor[int]):

x: Tensor = Tensor()

y: Tensor[int] = Tensor()

Any idea why that works?

A non-variadic generic class `Foo` without parameters resolves to `Foo[Any]`. As I'd mentioned, we consider `List[Any]` to be compatible with `List[int]` and vice versa, despite invariance.

To work around this, we could either
(i) allow Tuple[Any, ...] in general to be compatible with Tuple[int, str], or
(ii) special-case variadic classes like Tensor so that `Tensor` is compatible with `Tensor[int, str]` and vice versa.

> Both are unsound.

I'd actually be much more strongly in favour of the option where `Tuple[Any, ...]` is compatible with `Tuple[int, str]`. `TypeVar` is invariant by default too, but in order to support gradual typing doesn't it *have* to behave such that `Tuple[int]` is compatible with `Tuple[Any]`?

Could you expand on why that first option is unsound?

> Both are unsound. The tuple or tensor we pass in may have zero elements and may thus cause a runtime error. Or its element may be a type that can't be used as an `int` or `str`, which is again a runtime error.
Both are unsound for the same reasons. As I'd mentioned, we might pass an empty tuple to something that expects  `Tuple[int, str]`, which would be a runtime error. For example, `x: Tuple[Any, ...] = (); foo(x)` where `def foo(x: Tuple[int, str]) -> None: x[0] + 1`. Or it might be a tuple with a non-int class as the dimension, which again would be a runtime error if used as an `int` or a `str`. For example, `x: Tuple[Any, ...] = ("hello",); foo(x)`.

The first option is not backward-compatible because we would have to change existing errors about `Tuple[Any, ...]` not being compatible with `Tuple[int, str]`.

But, yeah, I'd appreciate opinions on the above choice.
S Pradeep Kumar