Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stubtest: fix literal type construction #11931

Merged
merged 7 commits into from Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 1 addition & 14 deletions mypy/fastparse.py
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
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
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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, I am almost sure that it does not work anymore, because bool() now is Union[Literal[False], Literal[True]].

I can address this in a new PR.

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
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
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