Skip to content

Commit 4d64557

Browse files
sobolevnJukkaL
authored andcommitted
stubtest: fix literal type construction (#11931)
Co-authored-by: hauntsaninja <>
1 parent 5b3280e commit 4d64557

File tree

5 files changed

+108
-25
lines changed

5 files changed

+108
-25
lines changed

Diff for: mypy/fastparse.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from mypy.errors import Errors
4141
from mypy.options import Options
4242
from mypy.reachability import mark_block_unreachable
43+
from mypy.util import bytes_to_human_readable_repr
4344

4445
try:
4546
# pull this into a final variable to make mypyc be quiet about the
@@ -1638,17 +1639,3 @@ def stringify_name(n: AST) -> Optional[str]:
16381639
if sv is not None:
16391640
return "{}.{}".format(sv, n.attr)
16401641
return None # Can't do it.
1641-
1642-
1643-
def bytes_to_human_readable_repr(b: bytes) -> str:
1644-
"""Converts bytes into some human-readable representation. Unprintable
1645-
bytes such as the nul byte are escaped. For example:
1646-
1647-
>>> b = bytes([102, 111, 111, 10, 0])
1648-
>>> s = bytes_to_human_readable_repr(b)
1649-
>>> print(s)
1650-
foo\n\x00
1651-
>>> print(repr(s))
1652-
'foo\\n\\x00'
1653-
"""
1654-
return repr(b)[2:-1]

Diff for: mypy/fastparse2.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@
4747
from mypy import message_registry, errorcodes as codes
4848
from mypy.errors import Errors
4949
from mypy.fastparse import (
50-
TypeConverter, parse_type_comment, bytes_to_human_readable_repr, parse_type_ignore_tag,
50+
TypeConverter, parse_type_comment, parse_type_ignore_tag,
5151
TYPE_IGNORE_PATTERN, INVALID_TYPE_IGNORE
5252
)
5353
from mypy.options import Options
54+
from mypy.util import bytes_to_human_readable_repr
5455
from mypy.reachability import mark_block_unreachable
5556

5657
try:

Diff for: mypy/stubtest.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from mypy import nodes
2626
from mypy.config_parser import parse_config_file
2727
from mypy.options import Options
28-
from mypy.util import FancyFormatter
28+
from mypy.util import FancyFormatter, bytes_to_human_readable_repr
2929

3030

3131
class Missing:
@@ -942,6 +942,7 @@ def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool:
942942
):
943943
# Pretend Literal[0, 1] is a subtype of bool to avoid unhelpful errors.
944944
return True
945+
945946
with mypy.state.strict_optional_set(True):
946947
return mypy.subtypes.is_subtype(left, right)
947948

@@ -1029,17 +1030,19 @@ def anytype() -> mypy.types.AnyType:
10291030
return mypy.types.TupleType(items, fallback)
10301031

10311032
fallback = mypy.types.Instance(type_info, [anytype() for _ in type_info.type_vars])
1032-
try:
1033-
# Literals are supposed to be only bool, int, str, bytes or enums, but this seems to work
1034-
# well (when not using mypyc, for which bytes and enums are also problematic).
1035-
return mypy.types.LiteralType(
1036-
value=runtime,
1037-
fallback=fallback,
1038-
)
1039-
except TypeError:
1040-
# Ask for forgiveness if we're using mypyc.
1033+
1034+
value: Union[bool, int, str]
1035+
if isinstance(runtime, bytes):
1036+
value = bytes_to_human_readable_repr(runtime)
1037+
elif isinstance(runtime, enum.Enum):
1038+
value = runtime.name
1039+
elif isinstance(runtime, (bool, int, str)):
1040+
value = runtime
1041+
else:
10411042
return fallback
10421043

1044+
return mypy.types.LiteralType(value=value, fallback=fallback)
1045+
10431046

10441047
_all_stubs: Dict[str, nodes.MypyFile] = {}
10451048

Diff for: mypy/test/teststubtest.py

+78
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,84 @@ def __init__(self, x): pass
750750
error="X.__init__"
751751
)
752752

753+
@collect_cases
754+
def test_good_literal(self) -> Iterator[Case]:
755+
yield Case(
756+
stub=r"""
757+
from typing_extensions import Literal
758+
759+
import enum
760+
class Color(enum.Enum):
761+
RED: int
762+
763+
NUM: Literal[1]
764+
CHAR: Literal['a']
765+
FLAG: Literal[True]
766+
NON: Literal[None]
767+
BYT1: Literal[b'abc']
768+
BYT2: Literal[b'\x90']
769+
ENUM: Literal[Color.RED]
770+
""",
771+
runtime=r"""
772+
import enum
773+
class Color(enum.Enum):
774+
RED = 3
775+
776+
NUM = 1
777+
CHAR = 'a'
778+
NON = None
779+
FLAG = True
780+
BYT1 = b"abc"
781+
BYT2 = b'\x90'
782+
ENUM = Color.RED
783+
""",
784+
error=None,
785+
)
786+
787+
@collect_cases
788+
def test_bad_literal(self) -> Iterator[Case]:
789+
yield Case("from typing_extensions import Literal", "", None) # dummy case
790+
yield Case(
791+
stub="INT_FLOAT_MISMATCH: Literal[1]",
792+
runtime="INT_FLOAT_MISMATCH = 1.0",
793+
error="INT_FLOAT_MISMATCH",
794+
)
795+
yield Case(
796+
stub="WRONG_INT: Literal[1]",
797+
runtime="WRONG_INT = 2",
798+
error="WRONG_INT",
799+
)
800+
yield Case(
801+
stub="WRONG_STR: Literal['a']",
802+
runtime="WRONG_STR = 'b'",
803+
error="WRONG_STR",
804+
)
805+
yield Case(
806+
stub="BYTES_STR_MISMATCH: Literal[b'value']",
807+
runtime="BYTES_STR_MISMATCH = 'value'",
808+
error="BYTES_STR_MISMATCH",
809+
)
810+
yield Case(
811+
stub="STR_BYTES_MISMATCH: Literal['value']",
812+
runtime="STR_BYTES_MISMATCH = b'value'",
813+
error="STR_BYTES_MISMATCH",
814+
)
815+
yield Case(
816+
stub="WRONG_BYTES: Literal[b'abc']",
817+
runtime="WRONG_BYTES = b'xyz'",
818+
error="WRONG_BYTES",
819+
)
820+
yield Case(
821+
stub="WRONG_BOOL_1: Literal[True]",
822+
runtime="WRONG_BOOL_1 = False",
823+
error='WRONG_BOOL_1',
824+
)
825+
yield Case(
826+
stub="WRONG_BOOL_2: Literal[False]",
827+
runtime="WRONG_BOOL_2 = True",
828+
error='WRONG_BOOL_2',
829+
)
830+
753831

754832
def remove_color_code(s: str) -> str:
755833
return re.sub("\\x1b.*?m", "", s) # this works!

Diff for: mypy/util.py

+14
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ def find_python_encoding(text: bytes, pyversion: Tuple[int, int]) -> Tuple[str,
105105
return default_encoding, -1
106106

107107

108+
def bytes_to_human_readable_repr(b: bytes) -> str:
109+
"""Converts bytes into some human-readable representation. Unprintable
110+
bytes such as the nul byte are escaped. For example:
111+
112+
>>> b = bytes([102, 111, 111, 10, 0])
113+
>>> s = bytes_to_human_readable_repr(b)
114+
>>> print(s)
115+
foo\n\x00
116+
>>> print(repr(s))
117+
'foo\\n\\x00'
118+
"""
119+
return repr(b)[2:-1]
120+
121+
108122
class DecodeError(Exception):
109123
"""Exception raised when a file cannot be decoded due to an unknown encoding type.
110124

0 commit comments

Comments
 (0)