Skip to content

Commit 84126ab

Browse files
authored
Move some enum and literal related type ops to typeops.py (#7909)
I keep wanting access to these functions outside of checker.py in various diffs I'm working on. So as suggested earlier, I might as well just do this refactoring now, independent of the other PRs.
1 parent 59617e8 commit 84126ab

File tree

2 files changed

+97
-95
lines changed

2 files changed

+97
-95
lines changed

mypy/checker.py

Lines changed: 3 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import itertools
44
import fnmatch
5-
import sys
65
from contextlib import contextmanager
76

87
from typing import (
@@ -73,7 +72,9 @@
7372
from mypy.plugin import Plugin, CheckerPluginInterface
7473
from mypy.sharedparse import BINARY_MAGIC_METHODS
7574
from mypy.scope import Scope
76-
from mypy.typeops import tuple_fallback
75+
from mypy.typeops import (
76+
tuple_fallback, coerce_to_literal, is_singleton_type, try_expanding_enum_to_union
77+
)
7778
from mypy import state, errorcodes as codes
7879
from mypy.traverser import has_return_statement, all_return_statements
7980
from mypy.errorcodes import ErrorCode
@@ -4771,97 +4772,6 @@ def is_private(node_name: str) -> bool:
47714772
return node_name.startswith('__') and not node_name.endswith('__')
47724773

47734774

4774-
def get_enum_values(typ: Instance) -> List[str]:
4775-
"""Return the list of values for an Enum."""
4776-
return [name for name, sym in typ.type.names.items() if isinstance(sym.node, Var)]
4777-
4778-
4779-
def is_singleton_type(typ: Type) -> bool:
4780-
"""Returns 'true' if this type is a "singleton type" -- if there exists
4781-
exactly only one runtime value associated with this type.
4782-
4783-
That is, given two values 'a' and 'b' that have the same type 't',
4784-
'is_singleton_type(t)' returns True if and only if the expression 'a is b' is
4785-
always true.
4786-
4787-
Currently, this returns True when given NoneTypes, enum LiteralTypes and
4788-
enum types with a single value.
4789-
4790-
Note that other kinds of LiteralTypes cannot count as singleton types. For
4791-
example, suppose we do 'a = 100000 + 1' and 'b = 100001'. It is not guaranteed
4792-
that 'a is b' will always be true -- some implementations of Python will end up
4793-
constructing two distinct instances of 100001.
4794-
"""
4795-
typ = get_proper_type(typ)
4796-
# TODO: Also make this return True if the type is a bool LiteralType.
4797-
# Also make this return True if the type corresponds to ... (ellipsis) or NotImplemented?
4798-
return (
4799-
isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal())
4800-
or (isinstance(typ, Instance) and typ.type.is_enum and len(get_enum_values(typ)) == 1)
4801-
)
4802-
4803-
4804-
def try_expanding_enum_to_union(typ: Type, target_fullname: str) -> ProperType:
4805-
"""Attempts to recursively expand any enum Instances with the given target_fullname
4806-
into a Union of all of its component LiteralTypes.
4807-
4808-
For example, if we have:
4809-
4810-
class Color(Enum):
4811-
RED = 1
4812-
BLUE = 2
4813-
YELLOW = 3
4814-
4815-
class Status(Enum):
4816-
SUCCESS = 1
4817-
FAILURE = 2
4818-
UNKNOWN = 3
4819-
4820-
...and if we call `try_expanding_enum_to_union(Union[Color, Status], 'module.Color')`,
4821-
this function will return Literal[Color.RED, Color.BLUE, Color.YELLOW, Status].
4822-
"""
4823-
typ = get_proper_type(typ)
4824-
4825-
if isinstance(typ, UnionType):
4826-
items = [try_expanding_enum_to_union(item, target_fullname) for item in typ.items]
4827-
return make_simplified_union(items)
4828-
elif isinstance(typ, Instance) and typ.type.is_enum and typ.type.fullname() == target_fullname:
4829-
new_items = []
4830-
for name, symbol in typ.type.names.items():
4831-
if not isinstance(symbol.node, Var):
4832-
continue
4833-
new_items.append(LiteralType(name, typ))
4834-
# SymbolTables are really just dicts, and dicts are guaranteed to preserve
4835-
# insertion order only starting with Python 3.7. So, we sort these for older
4836-
# versions of Python to help make tests deterministic.
4837-
#
4838-
# We could probably skip the sort for Python 3.6 since people probably run mypy
4839-
# only using CPython, but we might as well for the sake of full correctness.
4840-
if sys.version_info < (3, 7):
4841-
new_items.sort(key=lambda lit: lit.value)
4842-
return make_simplified_union(new_items)
4843-
else:
4844-
return typ
4845-
4846-
4847-
def coerce_to_literal(typ: Type) -> ProperType:
4848-
"""Recursively converts any Instances that have a last_known_value or are
4849-
instances of enum types with a single value into the corresponding LiteralType.
4850-
"""
4851-
typ = get_proper_type(typ)
4852-
if isinstance(typ, UnionType):
4853-
new_items = [coerce_to_literal(item) for item in typ.items]
4854-
return make_simplified_union(new_items)
4855-
elif isinstance(typ, Instance):
4856-
if typ.last_known_value:
4857-
return typ.last_known_value
4858-
elif typ.type.is_enum:
4859-
enum_values = get_enum_values(typ)
4860-
if len(enum_values) == 1:
4861-
return LiteralType(value=enum_values[0], fallback=typ)
4862-
return typ
4863-
4864-
48654775
def has_bool_item(typ: ProperType) -> bool:
48664776
"""Return True if type is 'bool' or a union with a 'bool' item."""
48674777
if is_named_instance(typ, 'builtins.bool'):

mypy/typeops.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
from typing import cast, Optional, List, Sequence, Set
9+
import sys
910

1011
from mypy.types import (
1112
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
@@ -14,8 +15,8 @@
1415
copy_type, TypeAliasType
1516
)
1617
from mypy.nodes import (
17-
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, Expression,
18-
StrExpr, ARG_POS
18+
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, ARG_POS,
19+
Expression, StrExpr, Var
1920
)
2021
from mypy.maptype import map_instance_to_supertype
2122
from mypy.expandtype import expand_type_by_instance, expand_type
@@ -495,3 +496,94 @@ def try_getting_str_literals(expr: Expression, typ: Type) -> Optional[List[str]]
495496
else:
496497
return None
497498
return strings
499+
500+
501+
def get_enum_values(typ: Instance) -> List[str]:
502+
"""Return the list of values for an Enum."""
503+
return [name for name, sym in typ.type.names.items() if isinstance(sym.node, Var)]
504+
505+
506+
def is_singleton_type(typ: Type) -> bool:
507+
"""Returns 'true' if this type is a "singleton type" -- if there exists
508+
exactly only one runtime value associated with this type.
509+
510+
That is, given two values 'a' and 'b' that have the same type 't',
511+
'is_singleton_type(t)' returns True if and only if the expression 'a is b' is
512+
always true.
513+
514+
Currently, this returns True when given NoneTypes, enum LiteralTypes and
515+
enum types with a single value.
516+
517+
Note that other kinds of LiteralTypes cannot count as singleton types. For
518+
example, suppose we do 'a = 100000 + 1' and 'b = 100001'. It is not guaranteed
519+
that 'a is b' will always be true -- some implementations of Python will end up
520+
constructing two distinct instances of 100001.
521+
"""
522+
typ = get_proper_type(typ)
523+
# TODO: Also make this return True if the type is a bool LiteralType.
524+
# Also make this return True if the type corresponds to ... (ellipsis) or NotImplemented?
525+
return (
526+
isinstance(typ, NoneType) or (isinstance(typ, LiteralType) and typ.is_enum_literal())
527+
or (isinstance(typ, Instance) and typ.type.is_enum and len(get_enum_values(typ)) == 1)
528+
)
529+
530+
531+
def try_expanding_enum_to_union(typ: Type, target_fullname: str) -> ProperType:
532+
"""Attempts to recursively expand any enum Instances with the given target_fullname
533+
into a Union of all of its component LiteralTypes.
534+
535+
For example, if we have:
536+
537+
class Color(Enum):
538+
RED = 1
539+
BLUE = 2
540+
YELLOW = 3
541+
542+
class Status(Enum):
543+
SUCCESS = 1
544+
FAILURE = 2
545+
UNKNOWN = 3
546+
547+
...and if we call `try_expanding_enum_to_union(Union[Color, Status], 'module.Color')`,
548+
this function will return Literal[Color.RED, Color.BLUE, Color.YELLOW, Status].
549+
"""
550+
typ = get_proper_type(typ)
551+
552+
if isinstance(typ, UnionType):
553+
items = [try_expanding_enum_to_union(item, target_fullname) for item in typ.items]
554+
return make_simplified_union(items)
555+
elif isinstance(typ, Instance) and typ.type.is_enum and typ.type.fullname() == target_fullname:
556+
new_items = []
557+
for name, symbol in typ.type.names.items():
558+
if not isinstance(symbol.node, Var):
559+
continue
560+
new_items.append(LiteralType(name, typ))
561+
# SymbolTables are really just dicts, and dicts are guaranteed to preserve
562+
# insertion order only starting with Python 3.7. So, we sort these for older
563+
# versions of Python to help make tests deterministic.
564+
#
565+
# We could probably skip the sort for Python 3.6 since people probably run mypy
566+
# only using CPython, but we might as well for the sake of full correctness.
567+
if sys.version_info < (3, 7):
568+
new_items.sort(key=lambda lit: lit.value)
569+
return make_simplified_union(new_items)
570+
else:
571+
return typ
572+
573+
574+
def coerce_to_literal(typ: Type) -> ProperType:
575+
"""Recursively converts any Instances that have a last_known_value or are
576+
instances of enum types with a single value into the corresponding LiteralType.
577+
"""
578+
typ = get_proper_type(typ)
579+
if isinstance(typ, UnionType):
580+
new_items = [coerce_to_literal(item) for item in typ.items]
581+
return make_simplified_union(new_items)
582+
elif isinstance(typ, Instance):
583+
if typ.last_known_value:
584+
return typ.last_known_value
585+
elif typ.type.is_enum:
586+
enum_values = get_enum_values(typ)
587+
if len(enum_values) == 1:
588+
return LiteralType(value=enum_values[0], fallback=typ)
589+
return typ

0 commit comments

Comments
 (0)