[ty] Add support for Literals in implicit type aliases (#21296)
## Summary Add support for `Literal` types in implicit type aliases. part of https://github.com/astral-sh/ty/issues/221 ## Ecosystem analysis This looks good to me, true positives and known problems. ## Test Plan New Markdown tests.
This commit is contained in:
@@ -181,30 +181,20 @@ def _(
|
||||
bool2: Literal[Bool2],
|
||||
multiple: Literal[SingleInt, SingleStr, SingleEnum],
|
||||
):
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(single_int) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal["foo"]`
|
||||
reveal_type(single_str) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[b"bar"]`
|
||||
reveal_type(single_bytes) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[True]`
|
||||
reveal_type(single_bool) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `None`
|
||||
reveal_type(single_none) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[E.A]`
|
||||
reveal_type(single_enum) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[1, "foo", b"bar", True, E.A] | None`
|
||||
reveal_type(union_literals) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(single_int) # revealed: Literal[1]
|
||||
reveal_type(single_str) # revealed: Literal["foo"]
|
||||
reveal_type(single_bytes) # revealed: Literal[b"bar"]
|
||||
reveal_type(single_bool) # revealed: Literal[True]
|
||||
reveal_type(single_none) # revealed: None
|
||||
reveal_type(single_enum) # revealed: Literal[E.A]
|
||||
reveal_type(union_literals) # revealed: Literal[1, "foo", b"bar", True, E.A] | None
|
||||
# Could also be `E`
|
||||
reveal_type(an_enum1) # revealed: Unknown
|
||||
# TODO should be `E`
|
||||
reveal_type(an_enum2) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(an_enum2) # revealed: E
|
||||
# Could also be `bool`
|
||||
reveal_type(bool1) # revealed: Unknown
|
||||
# TODO should be `bool`
|
||||
reveal_type(bool2) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[1, "foo", E.A]`
|
||||
reveal_type(multiple) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(bool2) # revealed: bool
|
||||
reveal_type(multiple) # revealed: Literal[1, "foo", E.A]
|
||||
```
|
||||
|
||||
### Implicit type alias
|
||||
@@ -246,28 +236,18 @@ def _(
|
||||
bool2: Literal[Bool2],
|
||||
multiple: Literal[SingleInt, SingleStr, SingleEnum],
|
||||
):
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(single_int) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal["foo"]`
|
||||
reveal_type(single_str) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[b"bar"]`
|
||||
reveal_type(single_bytes) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[True]`
|
||||
reveal_type(single_bool) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `None`
|
||||
reveal_type(single_none) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[E.A]`
|
||||
reveal_type(single_enum) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[1, "foo", b"bar", True, E.A] | None`
|
||||
reveal_type(union_literals) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(single_int) # revealed: Literal[1]
|
||||
reveal_type(single_str) # revealed: Literal["foo"]
|
||||
reveal_type(single_bytes) # revealed: Literal[b"bar"]
|
||||
reveal_type(single_bool) # revealed: Literal[True]
|
||||
reveal_type(single_none) # revealed: None
|
||||
reveal_type(single_enum) # revealed: Literal[E.A]
|
||||
reveal_type(union_literals) # revealed: Literal[1, "foo", b"bar", True, E.A] | None
|
||||
reveal_type(an_enum1) # revealed: Unknown
|
||||
# TODO should be `E`
|
||||
reveal_type(an_enum2) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(an_enum2) # revealed: E
|
||||
reveal_type(bool1) # revealed: Unknown
|
||||
# TODO should be `bool`
|
||||
reveal_type(bool2) # revealed: @Todo(Inference of subscript on special form)
|
||||
# TODO should be `Literal[1, "foo", E.A]`
|
||||
reveal_type(multiple) # revealed: @Todo(Inference of subscript on special form)
|
||||
reveal_type(bool2) # revealed: bool
|
||||
reveal_type(multiple) # revealed: Literal[1, "foo", E.A]
|
||||
```
|
||||
|
||||
## Shortening unions of literals
|
||||
|
||||
@@ -33,7 +33,7 @@ g(None)
|
||||
We also support unions in type aliases:
|
||||
|
||||
```py
|
||||
from typing_extensions import Any, Never
|
||||
from typing_extensions import Any, Never, Literal
|
||||
from ty_extensions import Unknown
|
||||
|
||||
IntOrStr = int | str
|
||||
@@ -54,6 +54,8 @@ NeverOrAny = Never | Any
|
||||
AnyOrNever = Any | Never
|
||||
UnknownOrInt = Unknown | int
|
||||
IntOrUnknown = int | Unknown
|
||||
StrOrZero = str | Literal[0]
|
||||
ZeroOrStr = Literal[0] | str
|
||||
|
||||
reveal_type(IntOrStr) # revealed: types.UnionType
|
||||
reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType
|
||||
@@ -73,6 +75,8 @@ reveal_type(NeverOrAny) # revealed: types.UnionType
|
||||
reveal_type(AnyOrNever) # revealed: types.UnionType
|
||||
reveal_type(UnknownOrInt) # revealed: types.UnionType
|
||||
reveal_type(IntOrUnknown) # revealed: types.UnionType
|
||||
reveal_type(StrOrZero) # revealed: types.UnionType
|
||||
reveal_type(ZeroOrStr) # revealed: types.UnionType
|
||||
|
||||
def _(
|
||||
int_or_str: IntOrStr,
|
||||
@@ -93,6 +97,8 @@ def _(
|
||||
any_or_never: AnyOrNever,
|
||||
unknown_or_int: UnknownOrInt,
|
||||
int_or_unknown: IntOrUnknown,
|
||||
str_or_zero: StrOrZero,
|
||||
zero_or_str: ZeroOrStr,
|
||||
):
|
||||
reveal_type(int_or_str) # revealed: int | str
|
||||
reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes
|
||||
@@ -112,6 +118,8 @@ def _(
|
||||
reveal_type(any_or_never) # revealed: Any
|
||||
reveal_type(unknown_or_int) # revealed: Unknown | int
|
||||
reveal_type(int_or_unknown) # revealed: int | Unknown
|
||||
reveal_type(str_or_zero) # revealed: str | Literal[0]
|
||||
reveal_type(zero_or_str) # revealed: Literal[0] | str
|
||||
```
|
||||
|
||||
If a type is unioned with itself in a value expression, the result is just that type. No
|
||||
@@ -255,6 +263,68 @@ def _(list_or_tuple: ListOrTuple[int]):
|
||||
reveal_type(list_or_tuple) # revealed: @Todo(Generic specialization of types.UnionType)
|
||||
```
|
||||
|
||||
## `Literal`s
|
||||
|
||||
We also support `typing.Literal` in implicit type aliases.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
from enum import Enum
|
||||
|
||||
IntLiteral1 = Literal[26]
|
||||
IntLiteral2 = Literal[0x1A]
|
||||
IntLiterals = Literal[-1, 0, 1]
|
||||
NestedLiteral = Literal[Literal[1]]
|
||||
StringLiteral = Literal["a"]
|
||||
BytesLiteral = Literal[b"b"]
|
||||
BoolLiteral = Literal[True]
|
||||
MixedLiterals = Literal[1, "a", True, None]
|
||||
|
||||
class Color(Enum):
|
||||
RED = 0
|
||||
GREEN = 1
|
||||
BLUE = 2
|
||||
|
||||
EnumLiteral = Literal[Color.RED]
|
||||
|
||||
def _(
|
||||
int_literal1: IntLiteral1,
|
||||
int_literal2: IntLiteral2,
|
||||
int_literals: IntLiterals,
|
||||
nested_literal: NestedLiteral,
|
||||
string_literal: StringLiteral,
|
||||
bytes_literal: BytesLiteral,
|
||||
bool_literal: BoolLiteral,
|
||||
mixed_literals: MixedLiterals,
|
||||
enum_literal: EnumLiteral,
|
||||
):
|
||||
reveal_type(int_literal1) # revealed: Literal[26]
|
||||
reveal_type(int_literal2) # revealed: Literal[26]
|
||||
reveal_type(int_literals) # revealed: Literal[-1, 0, 1]
|
||||
reveal_type(nested_literal) # revealed: Literal[1]
|
||||
reveal_type(string_literal) # revealed: Literal["a"]
|
||||
reveal_type(bytes_literal) # revealed: Literal[b"b"]
|
||||
reveal_type(bool_literal) # revealed: Literal[True]
|
||||
reveal_type(mixed_literals) # revealed: Literal[1, "a", True] | None
|
||||
reveal_type(enum_literal) # revealed: Literal[Color.RED]
|
||||
```
|
||||
|
||||
We reject invalid uses:
|
||||
|
||||
```py
|
||||
# error: [invalid-type-form] "Type arguments for `Literal` must be `None`, a literal value (int, bool, str, or bytes), or an enum member"
|
||||
LiteralInt = Literal[int]
|
||||
|
||||
reveal_type(LiteralInt) # revealed: Unknown
|
||||
|
||||
def _(weird: LiteralInt):
|
||||
reveal_type(weird) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form] "`Literal[26]` is not a generic class"
|
||||
def _(weird: IntLiteral1[int]):
|
||||
reveal_type(weird) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Stringified annotations?
|
||||
|
||||
From the [typing spec on type aliases](https://typing.python.org/en/latest/spec/aliases.html):
|
||||
|
||||
Reference in New Issue
Block a user