From 1974c6698377312ddc222b0768a34dd3329df103 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 19 Sep 2016 14:13:38 -0700 Subject: [PATCH 1/8] Distinguish b'' and '' in PY2. This has no effect as long as bytes remains an alias for str, but it enables experiments with differentiating them for better handling of bytes/str/union. Depends on python/typed_ast#17. --- mypy/fastparse.py | 3 +++ mypy/fastparse2.py | 10 ++++++++-- mypy/nodes.py | 11 +++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 961117861919..967815adf984 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -219,6 +219,7 @@ def translate_module_id(self, id: str) -> str: if id == self.custom_typing_module: return 'typing' elif id == '__builtin__' and self.pyversion[0] == 2: + assert False # Shouldn't get here # HACK: __builtin__ in Python 2 is aliases to builtins. However, the implementation # is named __builtin__.py (there is another layer of translation elsewhere). return 'builtins' @@ -744,6 +745,7 @@ def visit_Str(self, n: ast35.Str) -> Node: # unicode. return StrExpr(n.s) else: + assert False # Shouldn't get here with an ast35.Str return UnicodeExpr(n.s) # Bytes(bytes s) @@ -756,6 +758,7 @@ def visit_Bytes(self, n: ast35.Bytes) -> Node: if self.pyversion[0] >= 3: return BytesExpr(contents) else: + assert False # Shouldn't get here with an ast35.Str return StrExpr(contents) # NameConstant(singleton value) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index b1ef78f76f16..eed5476f2719 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -236,7 +236,8 @@ def translate_module_id(self, id: str) -> str: """ if id == self.custom_typing_module: return 'typing' - elif id == '__builtin__' and self.pyversion[0] == 2: + elif id == '__builtin__': + assert self.pyversion[0] == 2 # HACK: __builtin__ in Python 2 is aliases to builtins. However, the implementation # is named __builtin__.py (there is another layer of translation elsewhere). return 'builtins' @@ -819,11 +820,16 @@ def visit_Str(self, s: ast27.Str) -> Node: contents = str(n)[2:-1] if self.pyversion[0] >= 3: + assert False # Shouldn't get here with an ast27.Str return BytesExpr(contents) else: - return StrExpr(contents) + if s.has_b: + return BytesExpr(contents) + else: + return StrExpr(contents) else: if self.pyversion[0] >= 3 or self.is_stub: + assert False # Shouldn't get here with an ast27.Str return StrExpr(s.s) else: return UnicodeExpr(s.s) diff --git a/mypy/nodes.py b/mypy/nodes.py index 8f931ba57a87..4a6ee5263f58 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1007,9 +1007,12 @@ def accept(self, visitor: NodeVisitor[T]) -> T: # How mypy uses StrExpr, BytesExpr, and UnicodeExpr: # In Python 2 mode: -# b'x', 'x' -> StrExpr +# b'x' -> BytesExpr [new!] +# 'x' -> StrExpr # u'x' -> UnicodeExpr -# BytesExpr is unused +# However after `from __future__ import unicode_literals` [also new!]: +# b'x' -> BytesExpr +# 'x', u'x' -> UnicodeExpr # # In Python 3 mode: # b'x' -> BytesExpr @@ -1033,7 +1036,7 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class BytesExpr(Expression): """Bytes literal""" - value = '' # TODO use bytes + value = '' literal = LITERAL_YES def __init__(self, value: str) -> None: @@ -1047,7 +1050,7 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class UnicodeExpr(Expression): """Unicode literal (Python 2.x)""" - value = '' # TODO use bytes + value = '' literal = LITERAL_YES def __init__(self, value: str) -> None: From bfa0bacf3821cbfba5ddf828e8a572f65c22ffc8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 21 Sep 2016 08:41:57 -0700 Subject: [PATCH 2/8] Update the version of typed_ast we need (0.6.1). --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 0defc6aea1a4..80fcb8058403 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ flake8 -typed-ast +typed-ast>=0.6.1 pytest>=2.8 pytest-xdist>=1.13 From 44f5c951f895c4e272116d13aede7b85cfd886a9 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 14:49:25 -0700 Subject: [PATCH 3/8] Add the first bytes/str/unicode tests --- mypy/test/testcheck.py | 1 + .../unit/check-bytes-str-unicode-python2.test | 49 +++++++++++++++++++ test-data/unit/fixtures/bytes.pyi | 20 ++++++++ typeshed | 2 +- 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/check-bytes-str-unicode-python2.test create mode 100644 test-data/unit/fixtures/bytes.pyi diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index aaea713fa01c..0e854d4a358a 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -28,6 +28,7 @@ # List of files that contain test case descriptions. files = [ 'check-basic.test', + 'check-bytes-str-unicode-python2.test', 'check-classes.test', 'check-expressions.test', 'check-statements.test', diff --git a/test-data/unit/check-bytes-str-unicode-python2.test b/test-data/unit/check-bytes-str-unicode-python2.test new file mode 100644 index 000000000000..e7eb29cd922b --- /dev/null +++ b/test-data/unit/check-bytes-str-unicode-python2.test @@ -0,0 +1,49 @@ +-- Test cases for bytes/str/unicode in Python 2.x. + +[case testBytesStrUnicodeBasics] +# flags: --hide-error-context --fast-parser +def needs_bytes(b): + # type: (bytes) -> None + needs_bytes(b) + needs_str(b) # E: Argument 1 to "needs_str" has incompatible type "bytes"; expected "str" + needs_unicode(b) # E: Argument 1 to "needs_unicode" has incompatible type "bytes"; expected "unicode" + +def needs_str(s): + # type: (str) -> None + # TODO: The following line should not be an error + needs_bytes(s) # E: Argument 1 to "needs_bytes" has incompatible type "str"; expected "bytes" + needs_str(s) + needs_unicode(s) + +def needs_unicode(u): + # type: (unicode) -> None + needs_bytes(u) # E: Argument 1 to "needs_bytes" has incompatible type "unicode"; expected "bytes" + needs_str(u) # E: Argument 1 to "needs_str" has incompatible type "unicode"; expected "str" + needs_unicode(u) +[builtins_py2 fixtures/bytes.pyi] + +[case testBytesStrUnicodeLiterals] +# flags: --fast-parser +def needs_bytes(b): + # type: (bytes) -> None + pass +def needs_str(s): + # type: (str) -> None + pass +def needs_unicode(u): + # type: (unicode) -> None + pass + +needs_bytes(b'x') +# TODO: The following line should not be an error +needs_bytes('x') # E: Argument 1 to "needs_bytes" has incompatible type "str"; expected "bytes" +needs_bytes(u'x') # E: Argument 1 to "needs_bytes" has incompatible type "unicode"; expected "bytes" + +needs_str(b'x') # E: Argument 1 to "needs_str" has incompatible type "bytes"; expected "str" +needs_str('x') +needs_str(u'x') # E: Argument 1 to "needs_str" has incompatible type "unicode"; expected "str" + +needs_unicode(b'x') # E: Argument 1 to "needs_unicode" has incompatible type "bytes"; expected "unicode" +needs_unicode('x') +needs_unicode(u'x') +[builtins_py2 fixtures/bytes.pyi] diff --git a/test-data/unit/fixtures/bytes.pyi b/test-data/unit/fixtures/bytes.pyi new file mode 100644 index 000000000000..550360ab5bca --- /dev/null +++ b/test-data/unit/fixtures/bytes.pyi @@ -0,0 +1,20 @@ +class Any: pass + +class object: + def __init__(self) -> None: pass +class type: + def __init__(self, x: Any) -> None: pass +class bytes: + pass +class str: + pass +class unicode: + pass +Text = unicode +NativeStr = str + +class int: pass +class float: pass +class tuple: pass +class function: pass +class ellipsis: pass diff --git a/typeshed b/typeshed index aa549db5e5e5..c300ff181fdb 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit aa549db5e5e57ee2702899d1cc660163b52171ed +Subproject commit c300ff181fdb888708dd5d997647b279a1a098e8 From 71a9461bf776582c3d17333e7a2526c46494afae Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 15:08:49 -0700 Subject: [PATCH 4/8] Fix python2eval.test --- test-data/unit/python2eval.test | 3 +++ typeshed | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test-data/unit/python2eval.test b/test-data/unit/python2eval.test index b203b22f2eeb..d7b576a24dda 100644 --- a/test-data/unit/python2eval.test +++ b/test-data/unit/python2eval.test @@ -81,9 +81,12 @@ from typing import AnyStr def f(x): # type: (AnyStr) -> AnyStr if isinstance(x, str): return 'foo' + elif isinstance(x, bytes): + return b'qux' else: return u'zar' print f('') +print f(b'') print f(u'') [out] foo diff --git a/typeshed b/typeshed index c300ff181fdb..aeab82bb5352 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit c300ff181fdb888708dd5d997647b279a1a098e8 +Subproject commit aeab82bb53526b9934b26bd5a8def9fcf454659f From d5aea060a5fe6a5d1b7966e813d45f10147b47e4 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 15:13:22 -0700 Subject: [PATCH 5/8] Sync typeshed again --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index aeab82bb5352..30bf2517f57b 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit aeab82bb53526b9934b26bd5a8def9fcf454659f +Subproject commit 30bf2517f57b1289887ddba32c2ae8e7b98b3b4a From 86e7da9083af66ae7a61df9160bcfcfa79d5c85e Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 15:26:24 -0700 Subject: [PATCH 6/8] Fix test some more --- mypy/semanal.py | 2 +- test-data/unit/python2eval.test | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index cac77af31634..50bbab26b670 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -122,7 +122,7 @@ TYPE_PROMOTIONS_PYTHON2 = TYPE_PROMOTIONS.copy() TYPE_PROMOTIONS_PYTHON2.update({ 'builtins.str': 'builtins.unicode', - 'builtins.bytearray': 'builtins.str', + 'builtins.bytearray': 'builtins.bytes', }) # When analyzing a function, should we analyze the whole function in one go, or diff --git a/test-data/unit/python2eval.test b/test-data/unit/python2eval.test index d7b576a24dda..45fa2f81eade 100644 --- a/test-data/unit/python2eval.test +++ b/test-data/unit/python2eval.test @@ -90,6 +90,7 @@ print f(b'') print f(u'') [out] foo +qux zar [case testGenericPatterns_python2] From 8c690768386d60f755cbec7b06c54a7783caba7c Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 17:16:39 -0700 Subject: [PATCH 7/8] Fix all python2eval tests --- mypy/test/testpythoneval.py | 1 + test-data/unit/python2eval.test | 27 ++++++++++++++++----------- typeshed | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/mypy/test/testpythoneval.py b/mypy/test/testpythoneval.py index f5d94a8c8a94..e7d40fbc3887 100644 --- a/mypy/test/testpythoneval.py +++ b/mypy/test/testpythoneval.py @@ -62,6 +62,7 @@ def test_python_evaluation(testcase): interpreter = python3_path args = [] py2 = False + args.append('--fast-parser') # Some tests require this now. args.append('--tb') # Show traceback on crash. # Write the program to a file. program = '_program.py' diff --git a/test-data/unit/python2eval.test b/test-data/unit/python2eval.test index 45fa2f81eade..0916c350177f 100644 --- a/test-data/unit/python2eval.test +++ b/test-data/unit/python2eval.test @@ -79,10 +79,10 @@ x = 1.5 [case testAnyStr_python2] from typing import AnyStr def f(x): # type: (AnyStr) -> AnyStr - if isinstance(x, str): + if isinstance(x, bytes): + return b'foo' + elif isinstance(x, str): return 'foo' - elif isinstance(x, bytes): - return b'qux' else: return u'zar' print f('') @@ -90,7 +90,7 @@ print f(b'') print f(u'') [out] foo -qux +foo zar [case testGenericPatterns_python2] @@ -156,13 +156,16 @@ f(**params) [case testFromFutureImportUnicodeLiterals2_python2] from __future__ import unicode_literals -def f(x: str) -> None: pass +def f(x): + # type: (str) -> None + pass f(b'') f(u'') f('') [out] -_program.py:4: error: Argument 1 to "f" has incompatible type "unicode"; expected "str" -_program.py:5: error: Argument 1 to "f" has incompatible type "unicode"; expected "str" +_program.py:5: error: Argument 1 to "f" has incompatible type "bytes"; expected "str" +_program.py:6: error: Argument 1 to "f" has incompatible type "unicode"; expected "str" +_program.py:7: error: Argument 1 to "f" has incompatible type "unicode"; expected "str" [case testStrUnicodeCompatibility_python2] import typing @@ -237,7 +240,7 @@ u'\x89' import typing import io c = io.BytesIO() -c.write('\x89') +c.write(b'\x89') print(repr(c.getvalue())) [out] '\x89' @@ -400,11 +403,12 @@ def f(x: unicode) -> int: pass def f(x: bytearray) -> int: pass [out] _program.py:2: error: No overload variant of "f" matches argument types [builtins.int] +_program.py:5: error: No overload variant of "f" matches argument types [builtins.bytes] [case testByteArrayStrCompatibility_python2] -def f(x): # type: (str) -> None +def f(x): # type: (bytes) -> None pass -f(bytearray('foo')) +f(bytearray(b'foo')) [case testAbstractProperty_python2] from abc import abstractproperty, ABCMeta @@ -469,7 +473,8 @@ re.subn(upat, lambda m: u'', u'')[0] + u'' [case testYieldRegressionTypingAwaitable_python2] # Make sure we don't reference typing.Awaitable in Python 2 mode. -def g() -> int: +def g(): + # type: () -> int yield [out] _program.py: note: In function "g": diff --git a/typeshed b/typeshed index 30bf2517f57b..347aecf9f2f8 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 30bf2517f57b1289887ddba32c2ae8e7b98b3b4a +Subproject commit 347aecf9f2f8b8ec9f94a07d403bc0de78179fc2 From cd6ca21fd9e1ba365841fa39e8412a3458946fb8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 30 Sep 2016 18:12:50 -0700 Subject: [PATCH 8/8] Sync typeshed some more --- typeshed | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typeshed b/typeshed index 347aecf9f2f8..455f8aa834ee 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 347aecf9f2f8b8ec9f94a07d403bc0de78179fc2 +Subproject commit 455f8aa834ee7fc6b1527cce0f838d4829a4e1d3