-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Mypy's Literal? types are briefly documented here, however, their behavior is not completely specified.
Intuitively, a value like Literal["x"]? means that, depending on context, the value could be Literal["x"] or a plain str. This makes Literal["x"]? effectively a gradual type. I noticed some bugs with how mypy treats these types, most notably #19560. To holistically address such issues, I propose the following concrete specification:
0. prelim
Within this post, I interpret
≲/mypy.subtypes.is_subtypeas checking "assignable" / "consistent subtyping"<:/mypy.subtypes.is_proper_subtypeas checking "(proper) subtyping"
If this is incorrect, please let me know.
1. AnyOf specification
We define:
x <: AnyOf[y₁, ..., yₙ]if and only ifx <: Union[y₁, ..., yₙ]x ≲ AnyOf[y₁, ..., yₙ]if and only ifx ≲ yₖfor some k.AnyOf[y₁, ...., yₙ] <: xif and only ifUnion[y₁, ..., yₙ] <: xAnyOf[y₁, ...., yₙ] ≲ xif and only ifyₖ ≲ xfor all k.
2. Definition of Literal[T]?
Literal[T]? denotes a gradual type equivalent to one of
AnyOf[T, str]ifTis a literal stringAnyOf[T, int]ifTis a literal integerAnyOf[T, bool]ifTis a literal booleanAnyOf[T, NoneType]ifTis a liteAnyOf[T, EnumType]ifTis a literal enum
the first member is called its value, the latter its fallback.
It satisfies the following rules:
Literal[T]?is assignable to both itsfallbackandvalue.- Both its
fallbackandvalueare assignable toLiteral[T]?. Literal[T]?is a (proper) subtype of itsfallback.- Its
valueis a proper subtype ofLiteral[T]?. Literal[T]?is not a (proper) subtype of itsvalue.- Its
fallbackis not a (proper) subtype ofLiteral[T]?.
Which (I think) makes Literal["x"]? essentially AnyOf[Literal["x"], str] (see python/typing#566)
Rationale:
- ① and ②: should be obvious.
- ③ and ④: is because in either case, when
Literal["x"]?refers toLiteral["x"]or when it refers tostr,
both timesLiteral["x"]is a proper subtype, andstra proper supertype. - ⑤ and ⑥: are because the proper subtyping is not satisfied for both possible choices.
2. Changes to basic Ops
Below is a list of changes that derive from these rules and the assumption that meet and join are symmetric. These were generated using the following test script:
Details
from mypy.meet import meet_types
from mypy.join import join_types
from mypy.nodes import Block, ClassDef, SymbolTable, TypeInfo
from mypy.subtypes import restrict_subtype_away as restrict_types, is_proper_subtype, is_subtype, is_same_type
from mypy.types import Instance, LiteralType, TypeOfAny, AnyType, UnionType
from mypy.typeops import make_simplified_union as union
ST = SymbolTable()
def make_typeinfo(name: str, module_name: str = "__main__") -> TypeInfo:
class_def = ClassDef(name, Block([])) # Create a dummy ClassDef
info = TypeInfo(ST, class_def, module_name)
class_def.info = info # circular reference
return info
# Create demo types
str_info = make_typeinfo("str", module_name="builtins")
str_info.mro = [str_info] # Simplify MRO for this example
str_type = Instance(str_info, [], last_known_value=None)
max_litr = LiteralType("x", fallback=str_type)
sum_litr = LiteralType("y", fallback=str_type)
sum_inst = Instance(str_info, [], last_known_value=sum_litr)
max_inst = Instance(str_info, [], last_known_value=max_litr)
any_type = AnyType(TypeOfAny.unannotated)
print(f"\nMEET SCHEMA")
print(f"meet_types({max_litr!s:<16}, {str_type!s:<16}) = {meet_types(max_litr, str_type)}")
print(f"meet_types({str_type!s:<16}, {max_litr!s:<16}) = {meet_types(str_type, max_litr)}")
print(f"meet_types({max_inst!s:<16}, {str_type!s:<16}) = {meet_types(max_inst, str_type)}")
print(f"meet_types({str_type!s:<16}, {max_inst!s:<16}) = {meet_types(str_type, max_inst)}")
print(f"meet_types({max_litr!s:<16}, {max_litr!s:<16}) = {meet_types(max_litr, max_litr)}")
print(f"meet_types({max_litr!s:<16}, {max_inst!s:<16}) = {meet_types(max_litr, max_inst)}")
print(f"meet_types({max_inst!s:<16}, {max_litr!s:<16}) = {meet_types(max_inst, max_litr)}")
print(f"meet_types({max_inst!s:<16}, {max_inst!s:<16}) = {meet_types(max_inst, max_inst)}")
print(f"meet_types({max_litr!s:<16}, {sum_litr!s:<16}) = {meet_types(max_litr, sum_litr)}")
print(f"meet_types({max_litr!s:<16}, {sum_inst!s:<16}) = {meet_types(max_litr, sum_inst)}")
print(f"meet_types({max_inst!s:<16}, {sum_litr!s:<16}) = {meet_types(max_inst, sum_litr)}")
print(f"meet_types({max_inst!s:<16}, {sum_inst!s:<16}) = {meet_types(max_inst, sum_inst)}")
print(f"\nJOIN SCHEMA")
print(f"join_types({max_litr!s:<16}, {str_type!s:<16}) = {join_types(max_litr, str_type)}")
print(f"join_types({str_type!s:<16}, {max_litr!s:<16}) = {join_types(str_type, max_litr)}")
print(f"join_types({max_inst!s:<16}, {str_type!s:<16}) = {join_types(max_inst, str_type)}")
print(f"join_types({str_type!s:<16}, {max_inst!s:<16}) = {join_types(str_type, max_inst)}")
print(f"join_types({max_litr!s:<16}, {max_litr!s:<16}) = {join_types(max_litr, max_litr)}")
print(f"join_types({max_litr!s:<16}, {max_inst!s:<16}) = {join_types(max_litr, max_inst)}")
print(f"join_types({max_inst!s:<16}, {max_litr!s:<16}) = {join_types(max_inst, max_litr)}")
print(f"join_types({max_inst!s:<16}, {max_inst!s:<16}) = {join_types(max_inst, max_inst)}")
print(f"join_types({max_litr!s:<16}, {sum_litr!s:<16}) = {join_types(max_litr, sum_litr)}")
print(f"join_types({max_litr!s:<16}, {sum_inst!s:<16}) = {join_types(max_litr, sum_inst)}")
print(f"join_types({max_inst!s:<16}, {sum_litr!s:<16}) = {join_types(max_inst, sum_litr)}")
print(f"join_types({max_inst!s:<16}, {sum_inst!s:<16}) = {join_types(max_inst, sum_inst)}")
print(f"\nUNION SCHEMA")
print(f"union({max_litr!s:<16}, {str_type!s:<16}) = {union([max_litr, str_type])}")
print(f"union({str_type!s:<16}, {max_litr!s:<16}) = {union([str_type, max_litr])}")
print(f"union({max_inst!s:<16}, {str_type!s:<16}) = {union([max_inst, str_type])}")
print(f"union({str_type!s:<16}, {max_inst!s:<16}) = {union([str_type, max_inst])}")
print(f"union({max_litr!s:<16}, {max_litr!s:<16}) = {union([max_litr, max_litr])}")
print(f"union({max_litr!s:<16}, {max_inst!s:<16}) = {union([max_litr, max_inst])}")
print(f"union({max_inst!s:<16}, {max_litr!s:<16}) = {union([max_inst, max_litr])}")
print(f"union({max_inst!s:<16}, {max_inst!s:<16}) = {union([max_inst, max_inst])}")
print(f"union({max_litr!s:<16}, {sum_litr!s:<16}) = {union([max_litr, sum_litr])}")
print(f"union({max_litr!s:<16}, {sum_inst!s:<16}) = {union([max_litr, sum_inst])}")
print(f"union({max_inst!s:<16}, {sum_litr!s:<16}) = {union([max_inst, sum_litr])}")
print(f"union({max_inst!s:<16}, {sum_inst!s:<16}) = {union([max_inst, sum_inst])}")
print(f"\nRESTRICT SCHEMA")
print(f"restrict_types({max_litr!s:<16}, {str_type!s:<16}) = {restrict_types(max_litr, str_type)}")
print(f"restrict_types({str_type!s:<16}, {max_litr!s:<16}) = {restrict_types(str_type, max_litr)}")
print(f"restrict_types({max_inst!s:<16}, {str_type!s:<16}) = {restrict_types(max_inst, str_type)}")
print(f"restrict_types({str_type!s:<16}, {max_inst!s:<16}) = {restrict_types(str_type, max_inst)}")
print(f"restrict_types({max_litr!s:<16}, {max_litr!s:<16}) = {restrict_types(max_litr, max_litr)}")
print(f"restrict_types({max_litr!s:<16}, {max_inst!s:<16}) = {restrict_types(max_litr, max_inst)}")
print(f"restrict_types({max_inst!s:<16}, {max_litr!s:<16}) = {restrict_types(max_inst, max_litr)}")
print(f"restrict_types({max_inst!s:<16}, {max_inst!s:<16}) = {restrict_types(max_inst, max_inst)}")
print(f"restrict_types({max_litr!s:<16}, {sum_litr!s:<16}) = {restrict_types(max_litr, sum_litr)}")
print(f"restrict_types({max_litr!s:<16}, {sum_inst!s:<16}) = {restrict_types(max_litr, sum_inst)}")
print(f"restrict_types({max_inst!s:<16}, {sum_litr!s:<16}) = {restrict_types(max_inst, sum_litr)}")
print(f"restrict_types({max_inst!s:<16}, {sum_inst!s:<16}) = {restrict_types(max_inst, sum_inst)}")
print(f"\n SUBTYPE SCHEMA")
print(f"is_subtype({max_litr!s:<16}, {str_type!s:<16}) = {is_subtype(max_litr, str_type)}")
print(f"is_subtype({str_type!s:<16}, {max_litr!s:<16}) = {is_subtype(str_type, max_litr)}")
print(f"is_subtype({max_inst!s:<16}, {str_type!s:<16}) = {is_subtype(max_inst, str_type)}")
print(f"is_subtype({str_type!s:<16}, {max_inst!s:<16}) = {is_subtype(str_type, max_inst)}")
print(f"is_subtype({max_litr!s:<16}, {max_litr!s:<16}) = {is_subtype(max_litr, max_litr)}")
print(f"is_subtype({max_litr!s:<16}, {max_inst!s:<16}) = {is_subtype(max_litr, max_inst)}")
print(f"is_subtype({max_inst!s:<16}, {max_litr!s:<16}) = {is_subtype(max_inst, max_litr)}")
print(f"is_subtype({max_inst!s:<16}, {max_inst!s:<16}) = {is_subtype(max_inst, max_inst)}")
print(f"is_subtype({max_litr!s:<16}, {sum_litr!s:<16}) = {is_subtype(max_litr, sum_litr)}")
print(f"is_subtype({max_litr!s:<16}, {sum_inst!s:<16}) = {is_subtype(max_litr, sum_inst)}")
print(f"is_subtype({max_inst!s:<16}, {sum_litr!s:<16}) = {is_subtype(max_inst, sum_litr)}")
print(f"is_subtype({max_inst!s:<16}, {sum_inst!s:<16}) = {is_subtype(max_inst, sum_inst)}")
print(f"\n PROPER SUBTYPE SCHEMA")
print(f"is_proper_subtype({max_litr!s:<16}, {str_type!s:<16}) = {is_proper_subtype(max_litr, str_type)}")
print(f"is_proper_subtype({str_type!s:<16}, {max_litr!s:<16}) = {is_proper_subtype(str_type, max_litr)}")
print(f"is_proper_subtype({max_inst!s:<16}, {str_type!s:<16}) = {is_proper_subtype(max_inst, str_type)}")
print(f"is_proper_subtype({str_type!s:<16}, {max_inst!s:<16}) = {is_proper_subtype(str_type, max_inst)}")
print(f"is_proper_subtype({max_litr!s:<16}, {max_litr!s:<16}) = {is_proper_subtype(max_litr, max_litr)}")
print(f"is_proper_subtype({max_litr!s:<16}, {max_inst!s:<16}) = {is_proper_subtype(max_litr, max_inst)}")
print(f"is_proper_subtype({max_inst!s:<16}, {max_litr!s:<16}) = {is_proper_subtype(max_inst, max_litr)}")
print(f"is_proper_subtype({max_inst!s:<16}, {max_inst!s:<16}) = {is_proper_subtype(max_inst, max_inst)}")
print(f"is_proper_subtype({max_litr!s:<16}, {sum_litr!s:<16}) = {is_proper_subtype(max_litr, sum_litr)}")
print(f"is_proper_subtype({max_litr!s:<16}, {sum_inst!s:<16}) = {is_proper_subtype(max_litr, sum_inst)}")
print(f"is_proper_subtype({max_inst!s:<16}, {sum_litr!s:<16}) = {is_proper_subtype(max_inst, sum_litr)}")
print(f"is_proper_subtype({max_inst!s:<16}, {sum_inst!s:<16}) = {is_proper_subtype(max_inst, sum_inst)}")
print(f"\n SAME TYPE SCHEMA")
print(f"is_same_type({max_litr!s:<16}, {str_type!s:<16}) = {is_same_type(max_litr, str_type)}")
print(f"is_same_type({str_type!s:<16}, {max_litr!s:<16}) = {is_same_type(str_type, max_litr)}")
print(f"is_same_type({max_inst!s:<16}, {str_type!s:<16}) = {is_same_type(max_inst, str_type)}")
print(f"is_same_type({str_type!s:<16}, {max_inst!s:<16}) = {is_same_type(str_type, max_inst)}")
print(f"is_same_type({max_litr!s:<16}, {max_litr!s:<16}) = {is_same_type(max_litr, max_litr)}")
print(f"is_same_type({max_litr!s:<16}, {max_inst!s:<16}) = {is_same_type(max_litr, max_inst)}")
print(f"is_same_type({max_inst!s:<16}, {max_litr!s:<16}) = {is_same_type(max_inst, max_litr)}")
print(f"is_same_type({max_inst!s:<16}, {max_inst!s:<16}) = {is_same_type(max_inst, max_inst)}")
print(f"is_same_type({max_litr!s:<16}, {sum_litr!s:<16}) = {is_same_type(max_litr, sum_litr)}")
print(f"is_same_type({max_litr!s:<16}, {sum_inst!s:<16}) = {is_same_type(max_litr, sum_inst)}")
print(f"is_same_type({max_inst!s:<16}, {sum_litr!s:<16}) = {is_same_type(max_inst, sum_litr)}")
print(f"is_same_type({max_inst!s:<16}, {sum_inst!s:<16}) = {is_same_type(max_inst, sum_inst)}")The feature results are based on commit 8dbc066 of PR #19605
MEET SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | "x" | "x" | |
| str | "x" | "x" | "x" | |
| "x"? | str | "x"? | "x"? | |
| str | "x"? | str | "x"? | |
| "x" | "x" | "x" | "x" | |
| "x" | "x"? | "x" | "x" | |
| "x"? | "x" | "x"? | "x" | |
| "x"? | "x"? | "x"? | "x"? | |
| "x" | "y" | Never | Never | |
| "x" | "y"? | "x" | Never | |
| "x"? | "y" | "y" | Never | |
| "x"? | "y"? | "x"? | str |
JOIN SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | str | str | |
| str | "x" | str | str | |
| "x"? | str | str | str | |
| str | "x"? | str | str | |
| "x" | "x" | "x" | "x" | |
| "x" | "x"? | "x" | "x"? | |
| "x"? | "x" | "x" | "x"? | |
| "x"? | "x"? | str | "x"? | |
| "x" | "y" | str | str | ️ |
| "x" | "y"? | str | str | |
| "x"? | "y" | str | str | |
| "x"? | "y"? | str | str |
UNION SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | str | str | |
| str | "x" | str | str | |
| "x"? | str | str | str | |
| str | "x"? | str | str | |
| "x" | "x" | "x" | "x" | |
| "x" | "x"? | "x" | "x"? | |
| "x"? | "x" | "x" | "x"? | |
| "x"? | "x"? | "x"? | "x"? | |
| "x" | "y" | "x" | "y" | "x" | "y" | |
| "x" | "y"? | "x" | "y"? | "x" | "y"? | |
| "x"? | "y" | "x"? | "y" | "x"? | "y" | |
| "x"? | "y"? | "x"? | "y"? | "x"? | "y"? |
RESTRICT SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | Never | Never | |
| str | "x" | str | str | |
| "x"? | str | Never | Never | |
| str | "x"? | Never | Never | |
| "x" | "x" | Never | Never | |
| "x" | "x"? | Never | Never | |
| "x"? | "x" | "x"? | Never | |
| "x"? | "x"? | Never | Never | |
| "x" | "y" | "x" | "x" | |
| "x" | "y"? | Never | Never | |
| "x"? | "y" | "x"? | "x"? | |
| "x"? | "y"? | Never | Never |
SUBTYPE SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | True | True | |
| str | "x" | False | False | |
| "x"? | str | True | True | |
| str | "x"? | True | True | |
| "x" | "x" | True | True | |
| "x" | "x"? | True | True | |
| "x"? | "x" | True | True | |
| "x"? | "x"? | True | True | |
| "x" | "y" | False | False | |
| "x" | "y"? | True | True | |
| "x"? | "y" | False | False | |
| "x"? | "y"? | True | True |
PROPER SUBTYPE SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | True | True | |
| str | "x" | False | False | |
| "x"? | str | True | True | |
| str | "x"? | True | False | |
| "x" | "x" | True | True | |
| "x" | "x"? | True | True | |
| "x"? | "x" | True | False | |
| "x"? | "x"? | True | True | |
| "x" | "y" | False | False | |
| "x" | "y"? | True | False | |
| "x"? | "y" | False | False | |
| "x"? | "y"? | True | False |
SAME TYPE SCHEMA
| left | right | master | feature | changed |
|---|---|---|---|---|
| "x" | str | False | False | |
| str | "x" | False | False | |
| "x"? | str | True | False | |
| str | "x"? | True | False | |
| "x" | "x" | True | True | |
| "x" | "x"? | True | False | |
| "x"? | "x" | True | False | |
| "x"? | "x"? | True | True | |
| "x" | "y" | False | False | |
| "x" | "y"? | False | False | |
| "x"? | "y" | False | False | |
| "x"? | "y"? | True | True |