I see some value in adding an "AnyOf" construct for return types, but a type checker won't be able to do much with this information because (as others have pointed out), it would be unsafe to make many assumptions about the type. The most significant benefit is that language servers could provide completion suggestions — something developers would definitely appreciate.
That said, I don't think we need to introduce a new "AnyOf" type. As Zac pointed out, there's already a construct in the Python type system that has the desired properties of "AnyOf". Rather than invent a new construct, I'd prefer to leverage the constrained TypeVar.
Adding support for a new "AnyOf" type would likely be a lot of work in Pyright, but leveraging constrained TypeVars would be nearly free. In fact, it mostly works already, and I was able to implement completion suggestions and type narrowing with just a few additional lines of code. I'm guessing this it would be easy for most existing type checkers to add this support.
Mypy might require some small changes because it appears to use the first type in a constrained TypeVar rather than retaining the full list of constraints. I'm assuming that's not the intended behavior and is just a bug.
Zac said:
> I don't believe you can currently have a return typevar without an argument typevar...
I don't see anything in PEP 484 that states that a TypeVar used within a return type annotation must also appear in a parameter type annotation. Mypy doesn't appear to enforce this either, nor does the "stubtest" CI test for typeshed. There are legitimate uses for this. Here's an example:
```python
def my_decorator(timeout: float) -> Callable[[_T], _T]: ...
```
Building on Zac's example, here's how this could work:
```python
from typing import TypeVar
from ipaddress import IPv4Address, IPv6Address
IPAddress = TypeVar("IPAddress", IPv4Address, IPv6Address)
def ip_address(s: str) -> IPAddress:
if len(s.split('.')) == 4:
return IPv4Address(s)
else:
return IPv6Address(s)
a = ip_address("1.2.3.4")
reveal_type(a)
if isinstance(a, IPv4Address):
print("IPv4!")
reveal_type(a)
else:
print("IPv6!")
reveal_type(a)
```
Pyright's output is:
```
Found 1 source file
15:13 - info: Type of "a" is "IPAddress"
19:17 - info: Type of "a" is "IPv4Address"
22:17 - info: Type of "a" is "IPv6Address"
0 errors, 0 warnings, 3 infos
```
Mypy's output is:
```
test.py:9: error: Incompatible return value type (got "IPv4Address", expected "IPv6Address")
test.py:11: error: Incompatible return value type (got "IPv6Address", expected "IPv4Address")
test.py:15: note: Revealed type is 'ipaddress.IPv4Address*'
test.py:19: note: Revealed type is 'ipaddress.IPv4Address*'
Found 2 errors in 1 file (checked 1 source file)
```
(This looks like a bug. Perhaps someone from the mypy team could speak to how difficult a fix would be.)
(@Zac — You might be interested in the fact that I recently added support for recursive types in Pyright. https://devblogs.microsoft.com/python/pylance-introduces-five-new-features-that-enable-type-magic-for-python-developers/#1-support-for-recursive-type-aliases)
-Eric
--
Eric Traut
Microsoft
_______________________________________________
Typing-sig mailing list -- typing-sig@python.org
To unsubscribe send an email to typing-sig-leave@python.org
https://mail.python.org/mailman3/lists/typing-sig.python.org/
Member address: rechen@google.com