Skip to content

Commit

Permalink
stubtest: fix literal type construction (#11931)
Browse files Browse the repository at this point in the history
Co-authored-by: hauntsaninja <>
  • Loading branch information
sobolevn authored and JukkaL committed Jan 7, 2022
1 parent 5b3280e commit 4d64557
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 25 deletions.
15 changes: 1 addition & 14 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from mypy.errors import Errors
from mypy.options import Options
from mypy.reachability import mark_block_unreachable
from mypy.util import bytes_to_human_readable_repr

try:
# pull this into a final variable to make mypyc be quiet about the
Expand Down Expand Up @@ -1638,17 +1639,3 @@ def stringify_name(n: AST) -> Optional[str]:
if sv is not None:
return "{}.{}".format(sv, n.attr)
return None # Can't do it.


def bytes_to_human_readable_repr(b: bytes) -> str:
"""Converts bytes into some human-readable representation. Unprintable
bytes such as the nul byte are escaped. For example:
>>> b = bytes([102, 111, 111, 10, 0])
>>> s = bytes_to_human_readable_repr(b)
>>> print(s)
foo\n\x00
>>> print(repr(s))
'foo\\n\\x00'
"""
return repr(b)[2:-1]
3 changes: 2 additions & 1 deletion mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,11 @@
from mypy import message_registry, errorcodes as codes
from mypy.errors import Errors
from mypy.fastparse import (
TypeConverter, parse_type_comment, bytes_to_human_readable_repr, parse_type_ignore_tag,
TypeConverter, parse_type_comment, parse_type_ignore_tag,
TYPE_IGNORE_PATTERN, INVALID_TYPE_IGNORE
)
from mypy.options import Options
from mypy.util import bytes_to_human_readable_repr
from mypy.reachability import mark_block_unreachable

try:
Expand Down
23 changes: 13 additions & 10 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from mypy import nodes
from mypy.config_parser import parse_config_file
from mypy.options import Options
from mypy.util import FancyFormatter
from mypy.util import FancyFormatter, bytes_to_human_readable_repr


class Missing:
Expand Down Expand Up @@ -942,6 +942,7 @@ def is_subtype_helper(left: mypy.types.Type, right: mypy.types.Type) -> bool:
):
# Pretend Literal[0, 1] is a subtype of bool to avoid unhelpful errors.
return True

with mypy.state.strict_optional_set(True):
return mypy.subtypes.is_subtype(left, right)

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

fallback = mypy.types.Instance(type_info, [anytype() for _ in type_info.type_vars])
try:
# Literals are supposed to be only bool, int, str, bytes or enums, but this seems to work
# well (when not using mypyc, for which bytes and enums are also problematic).
return mypy.types.LiteralType(
value=runtime,
fallback=fallback,
)
except TypeError:
# Ask for forgiveness if we're using mypyc.

value: Union[bool, int, str]
if isinstance(runtime, bytes):
value = bytes_to_human_readable_repr(runtime)
elif isinstance(runtime, enum.Enum):
value = runtime.name
elif isinstance(runtime, (bool, int, str)):
value = runtime
else:
return fallback

return mypy.types.LiteralType(value=value, fallback=fallback)


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

Expand Down
78 changes: 78 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,84 @@ def __init__(self, x): pass
error="X.__init__"
)

@collect_cases
def test_good_literal(self) -> Iterator[Case]:
yield Case(
stub=r"""
from typing_extensions import Literal
import enum
class Color(enum.Enum):
RED: int
NUM: Literal[1]
CHAR: Literal['a']
FLAG: Literal[True]
NON: Literal[None]
BYT1: Literal[b'abc']
BYT2: Literal[b'\x90']
ENUM: Literal[Color.RED]
""",
runtime=r"""
import enum
class Color(enum.Enum):
RED = 3
NUM = 1
CHAR = 'a'
NON = None
FLAG = True
BYT1 = b"abc"
BYT2 = b'\x90'
ENUM = Color.RED
""",
error=None,
)

@collect_cases
def test_bad_literal(self) -> Iterator[Case]:
yield Case("from typing_extensions import Literal", "", None) # dummy case
yield Case(
stub="INT_FLOAT_MISMATCH: Literal[1]",
runtime="INT_FLOAT_MISMATCH = 1.0",
error="INT_FLOAT_MISMATCH",
)
yield Case(
stub="WRONG_INT: Literal[1]",
runtime="WRONG_INT = 2",
error="WRONG_INT",
)
yield Case(
stub="WRONG_STR: Literal['a']",
runtime="WRONG_STR = 'b'",
error="WRONG_STR",
)
yield Case(
stub="BYTES_STR_MISMATCH: Literal[b'value']",
runtime="BYTES_STR_MISMATCH = 'value'",
error="BYTES_STR_MISMATCH",
)
yield Case(
stub="STR_BYTES_MISMATCH: Literal['value']",
runtime="STR_BYTES_MISMATCH = b'value'",
error="STR_BYTES_MISMATCH",
)
yield Case(
stub="WRONG_BYTES: Literal[b'abc']",
runtime="WRONG_BYTES = b'xyz'",
error="WRONG_BYTES",
)
yield Case(
stub="WRONG_BOOL_1: Literal[True]",
runtime="WRONG_BOOL_1 = False",
error='WRONG_BOOL_1',
)
yield Case(
stub="WRONG_BOOL_2: Literal[False]",
runtime="WRONG_BOOL_2 = True",
error='WRONG_BOOL_2',
)


def remove_color_code(s: str) -> str:
return re.sub("\\x1b.*?m", "", s) # this works!
Expand Down
14 changes: 14 additions & 0 deletions mypy/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ def find_python_encoding(text: bytes, pyversion: Tuple[int, int]) -> Tuple[str,
return default_encoding, -1


def bytes_to_human_readable_repr(b: bytes) -> str:
"""Converts bytes into some human-readable representation. Unprintable
bytes such as the nul byte are escaped. For example:
>>> b = bytes([102, 111, 111, 10, 0])
>>> s = bytes_to_human_readable_repr(b)
>>> print(s)
foo\n\x00
>>> print(repr(s))
'foo\\n\\x00'
"""
return repr(b)[2:-1]


class DecodeError(Exception):
"""Exception raised when a file cannot be decoded due to an unknown encoding type.
Expand Down

0 comments on commit 4d64557

Please sign in to comment.