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 2 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
11 changes: 10 additions & 1 deletion 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,15 @@ 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
if (
isinstance(left, mypy.types.LiteralType)
and isinstance(left.value, bytes)
and isinstance(right, mypy.types.LiteralType)
and isinstance(right.value, str)
and right.fallback.type.fullname == 'builtins.bytes'
):
# This is a representation mismatch.
return bytes_to_human_readable_repr(left.value) == right.value
with mypy.state.strict_optional_set(True):
return mypy.subtypes.is_subtype(left, right)

Expand Down
106 changes: 106 additions & 0 deletions mypy/test/teststubtest.py
Expand Up @@ -750,6 +750,112 @@ def __init__(self, x): pass
error="X.__init__"
)

@collect_cases
def test_correct_literal(self) -> Iterator[Case]:
yield Case(
stub=r"""
from typing_extensions import Literal

NUM: Literal[1]
CHAR: Literal['a']
FLAG: Literal[True]
NON: Literal[None]
BYT1: Literal[b'abc']
BYT2: Literal[b'\x90']
""",
runtime=r"""
NUM = 1
CHAR = 'a'
NON = None
FLAG = True
BYT1 = b"abc"
BYT2 = b'\x90'
""",
error=None,
)

@collect_cases
def test_invalid_int_float_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_NUM: Literal[1]
""",
runtime='WRONG_NUM = 1.0',
error='WRONG_NUM',
)

@collect_cases
def test_invalid_int_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_NUM: Literal[1]
""",
runtime='WRONG_NUM = 2',
error='WRONG_NUM',
)

@collect_cases
def test_invalid_str_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_STR: Literal['a']
""",
runtime="WRONG_STR = 'b'",
error='WRONG_STR',
)

@collect_cases
def test_invalid_str_bytes_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_STR1: Literal[b'a']
""",
runtime="WRONG_STR1 = 'a'",
error='WRONG_STR1',
)
yield Case(
stub="WRONG_STR2: Literal['b']",
runtime="WRONG_STR2 = b'b'",
error='WRONG_STR2',
)

@collect_cases
def test_invalid_bytes_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_BYTES1: Literal[b'a']
""",
runtime="WRONG_BYTES1 = b'b'",
error='WRONG_BYTES1',
)

@collect_cases
def test_invalid_bool_literal(self) -> Iterator[Case]:
yield Case(
stub="""
from typing_extensions import Literal

WRONG_BOOL1: Literal[True]
""",
runtime="WRONG_BOOL1 = False",
error='WRONG_BOOL1',
)
yield Case(
stub="WRONG_BOOL2: Literal[False]",
runtime="WRONG_BOOL2 = True",
error='WRONG_BOOL2',
)


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