diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index 1c9323be056d..9ec54ce44b67 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -4,7 +4,6 @@ from collections.abc import Callable -from mypy.fastparse import parse_type_string from mypy.nodes import ( MISSING_FALLBACK, BytesExpr, @@ -30,6 +29,7 @@ get_member_expr_fullname, ) from mypy.options import Options +from mypy.parse import parse_type_string from mypy.types import ( ANNOTATED_TYPE_NAMES, AnyType, @@ -221,9 +221,9 @@ def expr_to_unanalyzed_type( column=expr.column, ) elif isinstance(expr, StrExpr): - return parse_type_string(expr.value, "builtins.str", expr.line, expr.column) + return parse_type_string(expr, options) elif isinstance(expr, BytesExpr): - return parse_type_string(expr.value, "builtins.bytes", expr.line, expr.column) + return RawExpressionType(expr.value, "builtins.bytes", expr.line, expr.column) elif isinstance(expr, UnaryExpr): typ = expr_to_unanalyzed_type( expr.expr, options, allow_new_syntax, lookup_qualified=lookup_qualified diff --git a/mypy/nativeparse.py b/mypy/nativeparse.py index d45d8bbb0af7..00c3b0ff7e1b 100644 --- a/mypy/nativeparse.py +++ b/mypy/nativeparse.py @@ -254,6 +254,23 @@ def native_parse( return node, errors, ignores +def native_parse_type_string( + expr_string: str, line: int, column: int, end_line: int, end_column: int, options: Options +) -> ProperType: + """Try to parse a string literal as a type expression (i.e. resolve a forward reference). + + If parsing fails, a RawExpressionType will be returned. + """ + ast_bytes = ast_serialize.parse_type_string( + expr_string, (line, column, end_line, end_column), cache_version=5 + ) + state = State(options) + data = ReadBuffer(ast_bytes) + ret = read_type(state, data) + assert isinstance(ret, ProperType) + return ret + + def expect_end_tag(data: ReadBuffer) -> None: assert read_tag(data) == END_TAG diff --git a/mypy/parse.py b/mypy/parse.py index 47e2f95f0f30..f440be5d3d85 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -7,8 +7,9 @@ from mypy import errorcodes as codes from mypy.cache import read_int from mypy.errors import Errors -from mypy.nodes import FileRawData, MypyFile, ParseError +from mypy.nodes import FileRawData, MypyFile, ParseError, StrExpr from mypy.options import Options +from mypy.types import ProperType def parse( @@ -115,3 +116,20 @@ def report_parse_error(error: ParseError, errors: Errors) -> None: # Fallback to [syntax] for backwards compatibility. error_code = codes.error_codes.get(error_code) or codes.SYNTAX errors.report(error["line"], error["column"], message, blocker=is_blocker, code=error_code) + + +def parse_type_string(expr: StrExpr, options: Options) -> ProperType: + if options.native_parser: + import mypy.nativeparse + + return mypy.nativeparse.native_parse_type_string( + expr.value, + expr.line, + expr.column, + expr.end_line or expr.line, + expr.end_column or expr.column, + options, + ) + import mypy.fastparse + + return mypy.fastparse.parse_type_string(expr.value, "builtins.str", expr.line, expr.column) diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index f795f1f5b354..595e4c7688d9 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -224,7 +224,7 @@ accepts_bytes(c_alias) [builtins fixtures/tuple.pyi] [out] -[case testLiteralMixingUnicodeAndBytesPython3ForwardStrings_no_native_parse] +[case testLiteralMixingUnicodeAndBytesPython3ForwardStrings] from typing import Literal, TypeVar, Generic a_unicode_wrapper: u"Literal[u'foo']" @@ -235,7 +235,7 @@ a_str_wrapper: "Literal[u'foo']" b_str_wrapper: "Literal['foo']" c_str_wrapper: "Literal[b'foo']" -# In Python 3, forward references MUST be str, not bytes +# In Python 3, forward references MUST be str, not bytes. a_bytes_wrapper: b"Literal[u'foo']" # E: Invalid type comment or annotation b_bytes_wrapper: b"Literal['foo']" # E: Invalid type comment or annotation c_bytes_wrapper: b"Literal[b'foo']" # E: Invalid type comment or annotation @@ -265,9 +265,10 @@ a_str_wrapper_alias: AStrWrapperAlias b_str_wrapper_alias: BStrWrapperAlias c_str_wrapper_alias: CStrWrapperAlias -ABytesWrapperAlias = Wrap[b"Literal[u'foo']"] -BBytesWrapperAlias = Wrap[b"Literal['foo']"] -CBytesWrapperAlias = Wrap[b"Literal[b'foo']"] +# Bytes literals are not valid as forward references. +ABytesWrapperAlias = Wrap[b"Literal[u'foo']"] # E: Invalid type comment or annotation +BBytesWrapperAlias = Wrap[b"Literal['foo']"] # E: Invalid type comment or annotation +CBytesWrapperAlias = Wrap[b"Literal[b'foo']"] # E: Invalid type comment or annotation a_bytes_wrapper_alias: ABytesWrapperAlias b_bytes_wrapper_alias: BBytesWrapperAlias c_bytes_wrapper_alias: CBytesWrapperAlias @@ -282,9 +283,9 @@ reveal_type(a_str_wrapper_alias) # N: Revealed type is "__main__.Wrap[Liter reveal_type(b_str_wrapper_alias) # N: Revealed type is "__main__.Wrap[Literal['foo']]" reveal_type(c_str_wrapper_alias) # N: Revealed type is "__main__.Wrap[Literal[b'foo']]" -reveal_type(a_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Literal['foo']]" -reveal_type(b_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Literal['foo']]" -reveal_type(c_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Literal[b'foo']]" +reveal_type(a_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Any]" +reveal_type(b_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Any]" +reveal_type(c_bytes_wrapper_alias) # N: Revealed type is "__main__.Wrap[Any]" [builtins fixtures/tuple.pyi] [out]