python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.46k stars 2.83k forks source link

Support for Union[*TypeVarTuple] #16720

Open pwuertz opened 10 months ago

pwuertz commented 10 months ago

(Forwarded from https://github.com/python/typing/issues/1523) I'm trying to annotate a method that only accepts a union of specific types, with the types specified via variadic generic. Although mypy supports variadic unpacking like Tuple[*Ts], unpacking Union[*Ts] doesn't seem to work yet:

import typing

Ts = typing.TypeVarTuple("Ts")

class Accepts(typing.Generic[*Ts]):
    # error: Unpack is only valid in a variadic position  [valid-type]
    def foo(self, x: typing.Union[*Ts]): ...

class A: ...
class B: ...
class C: ...
class Test(Accepts[A, B]): ...

Test().foo(A())  # ok
Test().foo(B())  # ok
Test().foo(C())  # should raise [arg-type] error

Mypy 1.7.1, Python 3.11

erictraut commented 10 months ago

The Union[*Ts] feature was included in an early draft of PEP 646, but it was removed before the PEP was finalized and accepted. I don't know if there's much appetite to add it back to the typing spec.

benedikt-bartscher commented 10 months ago

@erictraut are you sure PEP646 forbids the Union[*Ts] syntax? Unions are only mentioned in this block about multiple instances of one TypeVarTuple in the same function signature/class: https://peps.python.org/pep-0646/#type-variable-tuple-equality IIRC Pyright does support this syntax as well.

erictraut commented 10 months ago

@benedikt-bartscher, the reference to Union that you cite here has nothing to do with the Union[*Ts] notation.

Pyright currently has support for Union[*Ts], but I plan to remove this support now that I noticed it was removed from the final PEP. If it's not part of the standard, I don't plan to support it.

The facility was added in this draft PR for PEP 646. It was removed in this draft. The justification for removal was:

Remove support for type variable tuples in Union. Apparently the implementation would be tricky in Pyre because of special-casing around Union. That could be evidence that it would be tricky in Mypy and pytype. Since we don't have a specific use-case in mind, I think this is fine.

I can attest to the fact that it was very tricky (and somewhat hacky) to implement in pyright. It would also prevent us from deprecating the Union special form in favor of the newer | operator for specifying unions.

mantasu commented 2 weeks ago

Could we make Ts (without the asterisk) be union by default? This would scale well with additional unions, e.g., T | Ts