|
6 | 6 | """
|
7 | 7 |
|
8 | 8 | from typing import cast, Optional, List, Sequence, Set
|
| 9 | +import sys |
9 | 10 |
|
10 | 11 | from mypy.types import (
|
11 | 12 | TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
|
|
14 | 15 | copy_type, TypeAliasType
|
15 | 16 | )
|
16 | 17 | 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 |
19 | 20 | )
|
20 | 21 | from mypy.maptype import map_instance_to_supertype
|
21 | 22 | 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]]
|
495 | 496 | else:
|
496 | 497 | return None
|
497 | 498 | 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