diff --git a/mypy/cache.py b/mypy/cache.py index 9e3b39ffaaa40..464188f090de4 100644 --- a/mypy/cache.py +++ b/mypy/cache.py @@ -266,6 +266,8 @@ def read(cls, data: ReadBuffer, data_file: str) -> CacheMeta | None: # Misc classes. EXTRA_ATTRS: Final[Tag] = 150 DT_SPEC: Final[Tag] = 151 +# Four integers representing source file (line, column) range. +LOCATION: Final[Tag] = 152 END_TAG: Final[Tag] = 255 diff --git a/mypy/main.py b/mypy/main.py index acc27f1806b78..bd04a427c4a23 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -1245,6 +1245,8 @@ def add_invertible_flag( # --local-partial-types disallows partial types spanning module top level and a function # (implicitly defined in fine-grained incremental mode) add_invertible_flag("--local-partial-types", default=False, help=argparse.SUPPRESS) + # --native-parser enables the native parser (experimental) + add_invertible_flag("--native-parser", default=False, help=argparse.SUPPRESS) # --logical-deps adds some more dependencies that are not semantically needed, but # may be helpful to determine relative importance of classes and functions for overall # type precision in a code base. It also _removes_ some deps, so this flag should be never diff --git a/mypy/nativeparse.py b/mypy/nativeparse.py new file mode 100644 index 0000000000000..7b6dec8cde172 --- /dev/null +++ b/mypy/nativeparse.py @@ -0,0 +1,2050 @@ +# mypy: allow-redefinition-new, local-partial-types +"""Python parser that directly constructs a native AST (when compiled). + +Use a Rust extension to generate a serialized AST, and deserialize the AST directly +to a mypy AST. + +NOTE: This is work in progress. To use this, you need to manually build the + ast_serialize Rust extension. See the README at https://github.com/mypyc/ast_serialize. + +Expected benefits over mypy.fastparse: + * No intermediate non-mypyc Python-level AST created, to improve performance + * Parsing doesn't need GIL => use multithreading to construct serialized ASTs in parallel + * Produce import dependencies without having to build an AST => helps parallel type checking + * Support all Python syntax even if mypy is running on an older Python version + * Generate an AST even if there are syntax errors + * Potential to support incremental parsing (quickly process modified sections in a file) + * Stripping function bodies in third-party code can happen earlier, for extra performance +""" + +from __future__ import annotations + +import os +from typing import Any, Final, cast + +import ast_serialize # type: ignore[import-untyped, import-not-found, unused-ignore] +from librt.internal import ( + read_float as read_float_bare, + read_int as read_int_bare, + read_str as read_str_bare, +) + +from mypy import message_registry, nodes, types +from mypy.cache import ( + DICT_STR_GEN, + END_TAG, + LIST_GEN, + LIST_INT, + LITERAL_FLOAT, + LITERAL_NONE, + LITERAL_STR, + LOCATION, + ReadBuffer, + Tag, + read_bool, + read_int, + read_str, + read_str_opt, + read_tag, +) +from mypy.nodes import ( + ARG_KINDS, + ARG_POS, + IMPORT_METADATA, + IMPORTALL_METADATA, + IMPORTFROM_METADATA, + MISSING_FALLBACK, + Argument, + AssertStmt, + AssignmentExpr, + AssignmentStmt, + AwaitExpr, + Block, + BreakStmt, + BytesExpr, + CallExpr, + ClassDef, + ComparisonExpr, + ComplexExpr, + ConditionalExpr, + Context, + ContinueStmt, + Decorator, + DelStmt, + DictExpr, + DictionaryComprehension, + EllipsisExpr, + Expression, + ExpressionStmt, + FloatExpr, + ForStmt, + FuncDef, + GeneratorExpr, + GlobalDecl, + IfStmt, + Import, + ImportAll, + ImportBase, + ImportFrom, + IndexExpr, + IntExpr, + LambdaExpr, + ListComprehension, + ListExpr, + MatchStmt, + MemberExpr, + MypyFile, + NameExpr, + NonlocalDecl, + OperatorAssignmentStmt, + OpExpr, + OverloadedFuncDef, + OverloadPart, + PassStmt, + RaiseStmt, + RefExpr, + ReturnStmt, + SetComprehension, + SetExpr, + SliceExpr, + StarExpr, + Statement, + StrExpr, + SuperExpr, + TempNode, + TryStmt, + TupleExpr, + TypeAliasStmt, + TypeParam, + UnaryExpr, + Var, + WhileStmt, + WithStmt, + YieldExpr, + YieldFromExpr, +) +from mypy.options import Options +from mypy.patterns import ( + AsPattern, + ClassPattern, + MappingPattern, + OrPattern, + Pattern, + SequencePattern, + SingletonPattern, + StarredPattern, + ValuePattern, +) +from mypy.reachability import infer_reachability_of_if_statement +from mypy.sharedparse import special_function_elide_names +from mypy.types import ( + AnyType, + CallableArgument, + CallableType, + EllipsisType, + Instance, + ProperType, + RawExpressionType, + TupleType, + Type, + TypedDictType, + TypeList, + TypeOfAny, + UnboundType, + UnionType, + UnpackType, +) +from mypy.util import unnamed_function + +TypeIgnores = list[tuple[int, list[str]]] + +# There is no way to create reasonable fallbacks at this stage, +# they must be patched later. +_dummy_fallback: Final = Instance(MISSING_FALLBACK, [], -1) + + +class State: + def __init__(self, options: Options) -> None: + self.options = options + self.errors: list[dict[str, Any]] = [] + self.num_funcs = 0 + + def add_error( + self, + message: str, + line: int, + column: int, + *, + blocker: bool = False, + code: str | None = None, + ) -> None: + """Report an error at a specific location. + + Args: + message: Error message to display + line: Line number where error occurred + column: Column number where error occurred + blocker: If True, this error blocks further analysis + code: Error code for categorization + """ + self.errors.append( + {"line": line, "column": column, "message": message, "blocker": blocker, "code": code} + ) + + +def native_parse( + filename: str, options: Options, skip_function_bodies: bool = False +) -> tuple[MypyFile, list[dict[str, Any]], TypeIgnores]: + """Parse a Python file using the native Rust-based parser. + + Uses the ast_serialize Rust extension to parse Python code and deserialize + the resulting AST directly into mypy's native AST representation. + + Args: + filename: Path to the Python source file to parse + options: Mypy options affecting parsing behavior (e.g., Python version) + skip_function_bodies: If True, many function and method bodies are omitted from + the AST, useful for parsing stubs or extracting signatures without full + implementation details + + Returns: + A tuple containing: + - MypyFile: The parsed AST as a mypy AST node + - list[dict[str, Any]]: List of parse errors and deserialization errors + - TypeIgnores: List of (line_number, ignored_codes) tuples for type: ignore comments + """ + # If the path is a directory, return empty AST (matching fastparse behavior) + # This can happen for packages that only contain .pyc files without source + if os.path.isdir(filename): + node = MypyFile([], []) + node.path = filename + return node, [], [] + + b, errors, ignores, import_bytes, is_partial_package = parse_to_binary_ast( + filename, options, skip_function_bodies + ) + data = ReadBuffer(b) + n = read_int(data) + state = State(options) + defs = read_statements(state, data, n) + + imports = deserialize_imports(import_bytes) + + node = MypyFile(defs, imports) + node.path = filename + node.is_partial_stub_package = is_partial_package + # Merge deserialization errors with parsing errors + all_errors = errors + state.errors + return node, all_errors, ignores + + +def expect_end_tag(data: ReadBuffer) -> None: + assert read_tag(data) == END_TAG + + +def expect_tag(data: ReadBuffer, tag: Tag) -> None: + assert read_tag(data) == tag + + +def read_statements(state: State, data: ReadBuffer, n: int) -> list[Statement]: + defs: list[Statement] = [] + old_num_funcs = state.num_funcs + for _ in range(n): + stmt = read_statement(state, data) + defs.append(stmt) + if state.num_funcs > old_num_funcs + 1: + # There were at least two functions, so we may need to merge overloads. + defs = fix_function_overloads(state, defs) + return defs + + +def parse_to_binary_ast( + filename: str, options: Options, skip_function_bodies: bool = False +) -> tuple[bytes, list[dict[str, Any]], TypeIgnores, bytes, bool]: + ast_bytes, errors, ignores, import_bytes, is_partial_package = ast_serialize.parse( + filename, skip_function_bodies, python_version=options.python_version + ) + return ( + ast_bytes, + cast("list[dict[str, Any]]", errors), + ignores, + import_bytes, + is_partial_package, + ) + + +def read_statement(state: State, data: ReadBuffer) -> Statement: + tag = read_tag(data) + stmt: Statement + if tag == nodes.FUNC_DEF_STMT: + return read_func_def(state, data) + elif tag == nodes.DECORATOR: + expect_tag(data, LIST_GEN) + n_decorators = read_int_bare(data) + decorators = [read_expression(state, data) for i in range(n_decorators)] + line = read_int(data) + column = read_int(data) + fdef = read_statement(state, data) + assert isinstance(fdef, FuncDef) + fdef.is_decorated = True + var = Var(fdef.name) + var.line = fdef.line + var.is_ready = False + stmt = Decorator(fdef, decorators, var) + stmt.line = line + stmt.column = column + stmt.end_line = fdef.end_line + stmt.end_column = fdef.end_column + # TODO: Adjust funcdef location to start after decorator? + expect_end_tag(data) + return stmt + elif tag == nodes.EXPR_STMT: + es = ExpressionStmt(read_expression(state, data)) + set_line_column_range(es, es.expr) + expect_end_tag(data) + return es + elif tag == nodes.ASSIGNMENT_STMT: + lvalues = read_expression_list(state, data) + rvalue = read_expression(state, data) + has_type = read_bool(data) + if has_type: + type_annotation = read_type(state, data) + else: + type_annotation = None + new_syntax = read_bool(data) + a = AssignmentStmt(lvalues, rvalue, type=type_annotation, new_syntax=new_syntax) + read_loc(data, a) + # If rvalue is TempNode, copy location from AssignmentStmt + if isinstance(rvalue, TempNode): + set_line_column_range(rvalue, a) + expect_end_tag(data) + return a + elif tag == nodes.OPERATOR_ASSIGNMENT_STMT: + # Read operator string + op = read_str(data) + # Read lvalue (target) + lvalue = read_expression(state, data) + # Read rvalue (value) + rvalue = read_expression(state, data) + stmt = OperatorAssignmentStmt(op, lvalue, rvalue) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.IF_STMT: + # Read the main if condition and body + expr = read_expression(state, data) + body = read_block(state, data) + + # Read elif clauses + num_elif = read_int(data) + elif_exprs = [] + elif_bodies = [] + for i in range(num_elif): + elif_exprs.append(read_expression(state, data)) + elif_bodies.append(read_block(state, data)) + + has_else = read_bool(data) + if has_else: + else_body = read_block(state, data) + else: + else_body = None + + # Normalize elif into nested if/else statements + # Build from the bottom up, starting with the final else body + current_else = else_body + + # Process elif clauses in reverse order + for i in range(len(elif_exprs) - 1, -1, -1): + elif_stmt = IfStmt([elif_exprs[i]], [elif_bodies[i]], current_else) + # Set location from the elif expression + elif_stmt.line = elif_exprs[i].line + elif_stmt.column = elif_exprs[i].column + # Set end location based on what follows + if current_else is not None: + elif_stmt.end_line = current_else.end_line + elif_stmt.end_column = current_else.end_column + else: + elif_stmt.end_line = elif_bodies[i].end_line + elif_stmt.end_column = elif_bodies[i].end_column + + # Wrap in a Block to become the else clause for the outer if + current_else = Block([elif_stmt]) + set_line_column_range(current_else, elif_stmt) + + if_stmt = IfStmt([expr], [body], current_else) + read_loc(data, if_stmt) + expect_end_tag(data) + return if_stmt + elif tag == nodes.RETURN_STMT: + has_value = read_bool(data) + if has_value: + value = read_expression(state, data) + else: + value = None + stmt = ReturnStmt(value) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.RAISE_STMT: + has_exc = read_bool(data) + if has_exc: + exc = read_expression(state, data) + else: + exc = None + has_from = read_bool(data) + if has_from: + from_expr = read_expression(state, data) + else: + from_expr = None + stmt = RaiseStmt(exc, from_expr) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.ASSERT_STMT: + test = read_expression(state, data) + has_msg = read_bool(data) + if has_msg: + msg = read_expression(state, data) + else: + msg = None + stmt = AssertStmt(test, msg) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.WHILE_STMT: + expr = read_expression(state, data) + body = read_block(state, data) + else_body = read_optional_block(state, data) + stmt = WhileStmt(expr, body, else_body) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.FOR_STMT: + index = read_expression(state, data) + expr = read_expression(state, data) + body = read_block(state, data) + else_body = read_optional_block(state, data) + is_async = read_bool(data) + stmt = ForStmt(index, expr, body, else_body) + stmt.is_async = is_async + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.WITH_STMT: + n = read_int(data) + expr_list = [] + target_list: list[Expression | None] = [] + for _ in range(n): + context_expr = read_expression(state, data) + expr_list.append(context_expr) + has_target = read_bool(data) + if has_target: + target = read_expression(state, data) + target_list.append(target) + else: + target_list.append(None) + body = read_block(state, data) + is_async = read_bool(data) + stmt = WithStmt(expr_list, target_list, body) + stmt.is_async = is_async + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.PASS_STMT: + stmt = PassStmt() + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.BREAK_STMT: + stmt = BreakStmt() + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.CONTINUE_STMT: + stmt = ContinueStmt() + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.IMPORT: + n = read_int(data) + ids = [] + for _ in range(n): + name = read_str(data) + has_asname = read_bool(data) + if has_asname: + asname = read_str(data) + else: + asname = None + ids.append((name, asname)) + stmt = Import(ids) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.IMPORT_FROM: + relative = read_int(data) + module_id = read_str(data) # Empty string for "from . import x" + n = read_int(data) + names = [] + for _ in range(n): + name = read_str(data) + has_asname = read_bool(data) + if has_asname: + asname = read_str(data) + else: + asname = None + names.append((name, asname)) + + stmt = ImportFrom(module_id, relative, names) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.IMPORT_ALL: + module_id = read_str(data) # Empty string for "from . import *" + relative = read_int(data) + + stmt = ImportAll(module_id, relative) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.CLASS_DEF: + return read_class_def(state, data) + elif tag == nodes.TYPE_ALIAS_STMT: + return read_type_alias_stmt(state, data) + elif tag == nodes.TRY_STMT: + return read_try_stmt(state, data) + elif tag == nodes.DEL_STMT: + expr = read_expression(state, data) + stmt = DelStmt(expr) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.GLOBAL_DECL: + n = read_int(data) + decl_names = [] + for _ in range(n): + decl_names.append(read_str(data)) + stmt = GlobalDecl(decl_names) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.NONLOCAL_DECL: + n = read_int(data) + decl_names = [] + for _ in range(n): + decl_names.append(read_str(data)) + stmt = NonlocalDecl(decl_names) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + elif tag == nodes.MATCH_STMT: + subject = read_expression(state, data) + n_cases = read_int(data) + patterns = [] + guards: list[Expression | None] = [] + bodies = [] + for _ in range(n_cases): + pattern = read_pattern(state, data) + patterns.append(pattern) + has_guard = read_bool(data) + if has_guard: + guard = read_expression(state, data) + guards.append(guard) + else: + guards.append(None) + body = read_block(state, data) + bodies.append(body) + stmt = MatchStmt(subject, patterns, guards, bodies) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + else: + assert False, tag + + +def read_parameters(state: State, data: ReadBuffer) -> tuple[list[Argument], bool]: + """Read function/lambda parameters from the buffer. + + Returns: + A tuple of (arguments list, has_annotations flag) + """ + expect_tag(data, LIST_GEN) + n_args = read_int_bare(data) + arguments = [] + has_ann = False + for _ in range(n_args): + arg_name = read_str(data) + arg_kind_int = read_int(data) + arg_kind = ARG_KINDS[arg_kind_int] + has_type = read_bool(data) + if has_type: + ann = read_type(state, data) + has_ann = True + else: + ann = None + has_default = read_bool(data) + if has_default: + default = read_expression(state, data) + else: + default = None + pos_only = read_bool(data) + + # Apply implicit_optional if enabled and default is None + if state.options.implicit_optional and ann is not None: + optional = isinstance(default, NameExpr) and default.name == "None" + if isinstance(ann, UnboundType): + ann.optional = optional + + var = Var(arg_name, ann) + var.is_inferred = False + arg = Argument(var, ann, default, arg_kind, pos_only) + read_loc(data, arg) + set_line_column_range(var, arg) + arguments.append(arg) + + return arguments, has_ann + + +def read_type_params(state: State, data: ReadBuffer) -> list[TypeParam]: + """Read type parameters (PEP 695 generics).""" + type_params: list[TypeParam] = [] + n = read_int_bare(data) + for _ in range(n): + kind = read_int(data) + name = read_str(data) + has_bound = read_bool(data) + if has_bound: + upper_bound = read_type(state, data) + else: + upper_bound = None + + expect_tag(data, LIST_GEN) + n_values = read_int_bare(data) + values = [read_type(state, data) for _ in range(n_values)] + + has_default = read_bool(data) + if has_default: + default = read_type(state, data) + else: + default = None + + type_params.append(TypeParam(name, kind, upper_bound, values, default)) + + return type_params + + +def read_func_def(state: State, data: ReadBuffer) -> FuncDef: + state.num_funcs += 1 + + name = read_str(data) + arguments, has_ann = read_parameters(state, data) + + if special_function_elide_names(name): + for arg in arguments: + arg.pos_only = True + + body = read_block(state, data) + is_async = read_bool(data) + + # Type parameters (PEP 695) + has_type_params = read_bool(data) + if has_type_params: + type_params = read_type_params(state, data) + else: + type_params = None + + has_return_type = read_bool(data) + if has_return_type: + return_type = read_type(state, data) + has_ann = True + else: + return_type = None + + if has_ann: + typ = CallableType( + [ + arg.type_annotation if arg.type_annotation else AnyType(TypeOfAny.unannotated) + for arg in arguments + ], + [arg.kind for arg in arguments], + [None if arg.pos_only else arg.variable.name for arg in arguments], + return_type if return_type else AnyType(TypeOfAny.unannotated), + _dummy_fallback, + ) + else: + typ = None + + func_def = FuncDef(name, arguments, body, typ=typ, type_args=type_params) + if is_async: + func_def.is_coroutine = True + read_loc(data, func_def) + if typ: + typ.line = func_def.line + typ.column = func_def.column + typ.definition = func_def + # TODO: This seems wasteful, can we avoid it? + func_def.unanalyzed_type = typ.copy_modified() + expect_end_tag(data) + return func_def + + +def read_class_def(state: State, data: ReadBuffer) -> ClassDef: + name = read_str(data) + body = read_block(state, data) + base_type_exprs = read_expression_list(state, data) + + expect_tag(data, LIST_GEN) + n_decorators = read_int_bare(data) + decorators = [read_expression(state, data) for _ in range(n_decorators)] + + # Type parameters (PEP 695) + has_type_params = read_bool(data) + if has_type_params: + type_params = read_type_params(state, data) + else: + type_params = None + + # Keywords (all keyword arguments including metaclass) + expect_tag(data, DICT_STR_GEN) + n_keywords = read_int_bare(data) + keywords = [] + for _ in range(n_keywords): + key = read_str(data) + value = read_expression(state, data) + keywords.append((key, value)) + + # Extract metaclass from keywords if present + metaclass = dict(keywords).get("metaclass") if keywords else None + # Remove metaclass from keywords since it's passed as a separate field + filtered_keywords = [(k, v) for k, v in keywords if k != "metaclass"] if keywords else None + + class_def = ClassDef( + name, + body, + base_type_exprs=base_type_exprs if base_type_exprs else None, + metaclass=metaclass, + keywords=filtered_keywords, + type_args=type_params, + ) + class_def.decorators = decorators + read_loc(data, class_def) + expect_end_tag(data) + return class_def + + +def read_type_alias_stmt(state: State, data: ReadBuffer) -> TypeAliasStmt: + """Read PEP 695 type alias statement.""" + name = read_expression(state, data) + assert isinstance(name, NameExpr), f"Expected NameExpr for type alias name, got {type(name)}" + + n_type_params = read_int_bare(data) + if n_type_params > 0: + type_params = [] + for _ in range(n_type_params): + kind = read_int(data) + param_name = read_str(data) + has_bound = read_bool(data) + if has_bound: + upper_bound = read_type(state, data) + else: + upper_bound = None + + # Read values (for constrained TypeVar) + expect_tag(data, LIST_GEN) + n_values = read_int_bare(data) + values = [read_type(state, data) for _ in range(n_values)] + + has_default = read_bool(data) + if has_default: + default = read_type(state, data) + else: + default = None + + type_params.append(TypeParam(param_name, kind, upper_bound, values, default)) + else: + type_params = [] + + value_expr = read_expression(state, data) + + # Wrap the value expression in a LambdaExpr as expected by TypeAliasStmt + # The LambdaExpr body is a Block with a single ReturnStmt + return_stmt = ReturnStmt(value_expr) + set_line_column_range(return_stmt, value_expr) + + block = Block([return_stmt]) + block.line = -1 # Synthetic block + block.column = 0 + block.end_line = -1 + block.end_column = 0 + + lambda_expr = LambdaExpr([], block) + set_line_column_range(lambda_expr, value_expr) + + stmt = TypeAliasStmt(name, type_params, lambda_expr) + read_loc(data, stmt) + expect_end_tag(data) + return stmt + + +def read_try_stmt(state: State, data: ReadBuffer) -> TryStmt: + body = read_block(state, data) + num_handlers = read_int(data) + + types_list: list[Expression | None] = [] + for _ in range(num_handlers): + has_type = read_bool(data) + if has_type: + exc_type = read_expression(state, data) + types_list.append(exc_type) + else: + types_list.append(None) + + vars_list: list[NameExpr | None] = [] + for _ in range(num_handlers): + has_name = read_bool(data) + if has_name: + var_name = read_str(data) + var_expr = NameExpr(var_name) + vars_list.append(var_expr) + else: + vars_list.append(None) + + handlers = [] + for _ in range(num_handlers): + handler_body = read_block(state, data) + handlers.append(handler_body) + + has_else = read_bool(data) + if has_else: + else_body = read_block(state, data) + else: + else_body = None + + has_finally = read_bool(data) + if has_finally: + finally_body = read_block(state, data) + else: + finally_body = None + + # Read is_star flag (for except* in Python 3.11+) + is_star = read_bool(data) + + stmt = TryStmt(body, vars_list, types_list, handlers, else_body, finally_body) + stmt.is_star = is_star + read_loc(data, stmt) + expect_end_tag(data) + return stmt + + +def read_type(state: State, data: ReadBuffer) -> Type: + tag = read_tag(data) + if tag == types.UNBOUND_TYPE: + name = read_str(data) + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + args = tuple(read_type(state, data) for i in range(n)) + empty_tuple_index = read_bool(data) + # Read optional original_str_expr + t = read_tag(data) + if t == LITERAL_NONE: + original_str_expr = None + elif t == LITERAL_STR: + original_str_expr = read_str_bare(data) + else: + assert False, f"Unexpected tag for original_str_expr: {t}" + # Read optional original_str_fallback + t = read_tag(data) + if t == LITERAL_NONE: + original_str_fallback = None + elif t == LITERAL_STR: + original_str_fallback = read_str_bare(data) + else: + assert False, f"Unexpected tag for original_str_fallback: {t}" + unbound = UnboundType( + name, + args, + empty_tuple_index=empty_tuple_index, + original_str_expr=original_str_expr, + original_str_fallback=original_str_fallback, + ) + read_loc(data, unbound) + expect_end_tag(data) + return unbound + elif tag == types.UNION_TYPE: + # Read items list + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + items = [read_type(state, data) for i in range(n)] + # Read uses_pep604_syntax flag + uses_pep604_syntax = read_bool(data) + # Read optional original_str_expr + t = read_tag(data) + if t == LITERAL_NONE: + original_str_expr = None + elif t == LITERAL_STR: + original_str_expr = read_str_bare(data) + else: + assert False, f"Unexpected tag for original_str_expr: {t}" + # Read optional original_str_fallback + t = read_tag(data) + if t == LITERAL_NONE: + original_str_fallback = None + elif t == LITERAL_STR: + original_str_fallback = read_str_bare(data) + else: + assert False, f"Unexpected tag for original_str_fallback: {t}" + union = UnionType(items, uses_pep604_syntax=uses_pep604_syntax) + union.original_str_expr = original_str_expr + union.original_str_fallback = original_str_fallback + union.is_evaluated = read_bool(data) + read_loc(data, union) + expect_end_tag(data) + return union + elif tag == types.LIST_TYPE: + # Read items list + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + items = [read_type(state, data) for i in range(n)] + type_list = TypeList(items) + read_loc(data, type_list) + expect_end_tag(data) + return type_list + elif tag == types.TUPLE_TYPE: + # Read items list + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + items = [read_type(state, data) for i in range(n)] + implicit = read_bool(data) + tuple_type = TupleType(items, _dummy_fallback, implicit=implicit) + read_loc(data, tuple_type) + expect_end_tag(data) + return tuple_type + elif tag == types.TYPED_DICT_TYPE: + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + keys = [read_str_opt(data) for i in range(n)] + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + values = [read_type(state, data) for i in range(n)] + td_items = {} + extra_items_from = [] + for key, val in zip(keys, values): + if key is None: + assert isinstance(val, ProperType) + extra_items_from.append(val) + else: + td_items[key] = val + typeddict_type = TypedDictType(td_items, set(), set(), _dummy_fallback) + typeddict_type.extra_items_from = extra_items_from + read_loc(data, typeddict_type) + expect_end_tag(data) + return typeddict_type + elif tag == types.ELLIPSIS_TYPE: + # EllipsisType has no attributes + ellipsis_type = EllipsisType() + read_loc(data, ellipsis_type) + expect_end_tag(data) + return ellipsis_type + elif tag == types.RAW_EXPRESSION_TYPE: + type_name = read_str(data) + value: types.LiteralValue | str | None + if type_name == "builtins.bool": + value = read_bool(data) + elif type_name == "builtins.int": + value = read_int(data) + elif type_name == "builtins.str": + value = read_str(data) + elif type_name == "builtins.bytes": + # Bytes literals are serialized as escaped strings + value = read_str(data) + elif type_name == "typing.Any": + # Invalid type - read None value + tag = read_tag(data) + assert tag == LITERAL_NONE, f"Expected LITERAL_NONE for invalid type, got {tag}" + value = None + else: + assert False, f"Unsupported RawExpressionType: {type_name}" + raw_type = RawExpressionType(value, type_name) + read_loc(data, raw_type) + expect_end_tag(data) + return raw_type + elif tag == types.UNPACK_TYPE: + inner_type = read_type(state, data) + from_star_syntax = read_bool(data) + unpack = UnpackType(inner_type, from_star_syntax=from_star_syntax) + read_loc(data, unpack) + expect_end_tag(data) + return unpack + elif tag == types.CALL_TYPE: + return read_call_type(state, data) + else: + assert False, tag + + +def stringify_type_name(typ: Type) -> str | None: + """Extract qualified name from a type (for Arg constructor detection).""" + if isinstance(typ, UnboundType): + return typ.name + return None + + +def extract_arg_name(typ: Type) -> str | None: + """Extract argument name from a type (for Arg name parameter).""" + if isinstance(typ, RawExpressionType) and typ.base_type_name == "builtins.str": + return typ.literal_value # type: ignore[return-value] + elif isinstance(typ, UnboundType): + # String literals in type context are parsed as UnboundType (forward references) + # For Arg names, these are typically simple names without dots + if typ.name == "None": + return None + # Return the name as-is (it's the argument name) + return typ.name + return None # Invalid, but let validation handle it + + +def read_call_type(state: State, data: ReadBuffer) -> Type: + """Read Call in type context - check if it's an Arg/DefaultArg/VarArg/KwArg constructor. + + This performs validation and error reporting similar to mypy/fastparse.py. + """ + callee_type = read_type(state, data) + + # Read positional arguments + expect_tag(data, LIST_GEN) + n_args = read_int_bare(data) + args = [read_type(state, data) for _ in range(n_args)] + + # Read keyword arguments + expect_tag(data, LIST_GEN) + n_kwargs = read_int_bare(data) + kwargs = [] + for _ in range(n_kwargs): + tag_kw = read_tag(data) + if tag_kw == LITERAL_NONE: + kw_name = None + elif tag_kw == LITERAL_STR: + kw_name = read_str_bare(data) + else: + assert False, f"Unexpected tag for keyword name: {tag_kw}" + kw_value = read_type(state, data) + kwargs.append((kw_name, kw_value)) + + # Try to detect Arg/DefaultArg/VarArg/KwArg pattern + constructor = stringify_type_name(callee_type) + + # We'll read location before processing errors so we can report them correctly + invalid = AnyType(TypeOfAny.from_error) + read_loc(data, invalid) + expect_end_tag(data) + + if not constructor: + # ARG_CONSTRUCTOR_NAME_EXPECTED + state.add_error( + message_registry.ARG_CONSTRUCTOR_NAME_EXPECTED.value, + invalid.line, + invalid.column, + blocker=True, + code="misc", + ) + return invalid + + # Extract type and name from arguments + name: str | None = None + name_set_from_positional = False + default_type = AnyType(TypeOfAny.special_form) + typ: Type = default_type + typ_set_from_positional = False + + # Process positional arguments + for i, arg in enumerate(args): + if i == 0: + typ = arg + typ_set_from_positional = True + elif i == 1: + name = extract_arg_name(arg) + name_set_from_positional = True + else: + # ARG_CONSTRUCTOR_TOO_MANY_ARGS + state.add_error( + message_registry.ARG_CONSTRUCTOR_TOO_MANY_ARGS.value, + invalid.line, + invalid.column, + blocker=True, + code="misc", + ) + + # Process keyword arguments + for kw_name, kw_value in kwargs: + if kw_name == "name": + # MULTIPLE_VALUES_FOR_NAME_KWARG + if name is not None and name_set_from_positional: + state.add_error( + message_registry.MULTIPLE_VALUES_FOR_NAME_KWARG.format(constructor).value, + invalid.line, + invalid.column, + blocker=True, + code="misc", + ) + name = extract_arg_name(kw_value) + elif kw_name == "type": + # MULTIPLE_VALUES_FOR_TYPE_KWARG + if typ is not default_type and typ_set_from_positional: + state.add_error( + message_registry.MULTIPLE_VALUES_FOR_TYPE_KWARG.format(constructor).value, + invalid.line, + invalid.column, + blocker=True, + code="misc", + ) + typ = kw_value + else: + # ARG_CONSTRUCTOR_UNEXPECTED_ARG + state.add_error( + message_registry.ARG_CONSTRUCTOR_UNEXPECTED_ARG.format(kw_name).value, + invalid.line, + invalid.column, + blocker=True, + code="misc", + ) + + # Create CallableArgument + call_arg = CallableArgument(typ, name, constructor) + set_line_column_range(call_arg, invalid) + return call_arg + + +def read_pattern(state: State, data: ReadBuffer) -> Pattern: + """Read a pattern node from the buffer.""" + tag = read_tag(data) + if tag == nodes.AS_PATTERN: + has_pattern = read_bool(data) + if has_pattern: + pattern = read_pattern(state, data) + else: + pattern = None + has_name = read_bool(data) + if has_name: + name_str = read_str(data) + name = NameExpr(name_str) + read_loc(data, name) + else: + name = None + as_pattern = AsPattern(pattern, name) + read_loc(data, as_pattern) + expect_end_tag(data) + return as_pattern + elif tag == nodes.OR_PATTERN: + n = read_int(data) + patterns = [read_pattern(state, data) for _ in range(n)] + or_pattern = OrPattern(patterns) + read_loc(data, or_pattern) + expect_end_tag(data) + return or_pattern + elif tag == nodes.VALUE_PATTERN: + expr = read_expression(state, data) + value_pattern = ValuePattern(expr) + read_loc(data, value_pattern) + expect_end_tag(data) + return value_pattern + elif tag == nodes.SINGLETON_PATTERN: + singleton_tag = read_tag(data) + if singleton_tag == LITERAL_NONE: + value = None + else: + # It's a boolean + value = singleton_tag == 1 # TAG_LITERAL_TRUE + singleton_pattern = SingletonPattern(value) + read_loc(data, singleton_pattern) + expect_end_tag(data) + return singleton_pattern + elif tag == nodes.SEQUENCE_PATTERN: + n = read_int(data) + patterns = [read_pattern(state, data) for _ in range(n)] + sequence_pattern = SequencePattern(patterns) + read_loc(data, sequence_pattern) + expect_end_tag(data) + return sequence_pattern + elif tag == nodes.STARRED_PATTERN: + # Read optional capture name + has_name = read_bool(data) + if has_name: + name_str = read_str(data) + name = NameExpr(name_str) + read_loc(data, name) + else: + name = None + starred_pattern = StarredPattern(name) + read_loc(data, starred_pattern) + expect_end_tag(data) + return starred_pattern + elif tag == nodes.MAPPING_PATTERN: + n = read_int(data) + keys = [] + values = [] + for _ in range(n): + key = read_expression(state, data) + value = read_pattern(state, data) + keys.append(key) + values.append(value) + has_rest = read_bool(data) + if has_rest: + rest_str = read_str(data) + rest = NameExpr(rest_str) + read_loc(data, rest) + else: + rest = None + mapping_pattern = MappingPattern(keys, values, rest) + read_loc(data, mapping_pattern) + expect_end_tag(data) + return mapping_pattern + elif tag == nodes.CLASS_PATTERN: + class_ref = cast(RefExpr, read_expression(state, data)) + n_positional = read_int(data) + positionals = [read_pattern(state, data) for _ in range(n_positional)] + n_keywords = read_int(data) + keyword_keys = [] + keyword_values = [] + for _ in range(n_keywords): + key = read_str(data) + value = read_pattern(state, data) + keyword_keys.append(key) + keyword_values.append(value) + class_pattern = ClassPattern(class_ref, positionals, keyword_keys, keyword_values) + read_loc(data, class_pattern) + expect_end_tag(data) + return class_pattern + else: + assert False, f"Unknown pattern tag: {tag}" + + +def read_block(state: State, data: ReadBuffer) -> Block: + expect_tag(data, nodes.BLOCK) + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + is_unreachable = read_bool(data) + if n == 0: + # Empty block - read explicit location + b = Block([], is_unreachable=is_unreachable) + read_loc(data, b) + expect_end_tag(data) + return b + else: + # Non-empty block - read statements and set location from them + a = read_statements(state, data, n) + expect_end_tag(data) + b = Block(a, is_unreachable=is_unreachable) + b.line = a[0].line + b.column = a[0].column + b.end_line = a[-1].end_line + b.end_column = a[-1].end_column + return b + + +def read_optional_block(state: State, data: ReadBuffer) -> Block | None: + expect_tag(data, nodes.BLOCK) + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + is_unreachable = read_bool(data) + if n == 0: + b = None + else: + a = [read_statement(state, data) for i in range(n)] + b = Block(a, is_unreachable=is_unreachable) + b.line = a[0].line + b.column = a[0].column + b.end_line = a[-1].end_line + b.end_column = a[-1].end_column + expect_end_tag(data) + return b + + +bin_ops: Final = ["+", "-", "*", "@", "/", "%", "**", "<<", ">>", "|", "^", "&", "//"] +bool_ops: Final = ["and", "or"] +cmp_ops: Final = ["==", "!=", "<", "<=", ">", ">=", "is", "is not", "in", "not in"] +unary_ops: Final = ["~", "not", "+", "-"] + + +def read_expression(state: State, data: ReadBuffer) -> Expression: + tag = read_tag(data) + expr: Expression + if tag == nodes.CALL_EXPR: + callee = read_expression(state, data) + args = read_expression_list(state, data) + # Read argument kinds + expect_tag(data, LIST_INT) + n_kinds = read_int_bare(data) + arg_kinds = [ARG_KINDS[read_int_bare(data)] for _ in range(n_kinds)] + # Read argument names + expect_tag(data, LIST_GEN) + n_names = read_int_bare(data) + arg_names: list[str | None] = [] + for _ in range(n_names): + tag = read_tag(data) + if tag == LITERAL_NONE: + arg_names.append(None) + elif tag == LITERAL_STR: + arg_names.append(read_str_bare(data)) + else: + assert False, f"Unexpected tag for arg_name: {tag}" + ce = CallExpr(callee, args, arg_kinds, arg_names) + read_loc(data, ce) + expect_end_tag(data) + return ce + elif tag == nodes.NAME_EXPR: + s = read_str(data) + ne = NameExpr(s) + read_loc(data, ne) + expect_end_tag(data) + return ne + elif tag == nodes.MEMBER_EXPR: + e = read_expression(state, data) + attr = read_str(data) + m = MemberExpr(e, attr) + # Check if this is a super() call - if so, convert to SuperExpr + if isinstance(e, CallExpr) and isinstance(e.callee, NameExpr) and e.callee.name == "super": + result: Expression = SuperExpr(attr, e) + else: + result = m + read_loc(data, result) + expect_end_tag(data) + return result + elif tag == nodes.STR_EXPR: + se = StrExpr(read_str(data)) + read_loc(data, se) + expect_end_tag(data) + return se + elif tag == nodes.INT_EXPR: + ie = IntExpr(read_int(data)) + read_loc(data, ie) + expect_end_tag(data) + return ie + elif tag == nodes.FLOAT_EXPR: + expect_tag(data, LITERAL_FLOAT) + value = read_float_bare(data) + fe = FloatExpr(value) + read_loc(data, fe) + expect_end_tag(data) + return fe + elif tag == nodes.LIST_EXPR: + items = read_expression_list(state, data) + expr = ListExpr(items) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.TUPLE_EXPR: + items = read_expression_list(state, data) + t = TupleExpr(items) + read_loc(data, t) + expect_end_tag(data) + return t + elif tag == nodes.SET_EXPR: + items = read_expression_list(state, data) + expr = SetExpr(items) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.GENERATOR_EXPR: + expr = read_generator_expr(state, data) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.LIST_COMPREHENSION: + generator = read_generator_expr(state, data) + expr = ListComprehension(generator) + read_loc(data, expr) + # Also copy location to the inner generator + set_line_column_range(generator, expr) + expect_end_tag(data) + return expr + elif tag == nodes.SET_COMPREHENSION: + generator = read_generator_expr(state, data) + expr = SetComprehension(generator) + read_loc(data, expr) + # Also copy location to the inner generator + set_line_column_range(generator, expr) + expect_end_tag(data) + return expr + elif tag == nodes.DICT_COMPREHENSION: + key = read_expression(state, data) + value = read_expression(state, data) + n_generators = read_int(data) + indices = [read_expression(state, data) for _ in range(n_generators)] + sequences = [read_expression(state, data) for _ in range(n_generators)] + condlists = [read_expression_list(state, data) for _ in range(n_generators)] + is_async = [read_bool(data) for _ in range(n_generators)] + expr = DictionaryComprehension(key, value, indices, sequences, condlists, is_async) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.YIELD_EXPR: + has_value = read_bool(data) + if has_value: + value = read_expression(state, data) + else: + value = None + expr = YieldExpr(value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.YIELD_FROM_EXPR: + value = read_expression(state, data) + expr = YieldFromExpr(value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.OP_EXPR: + op = bin_ops[read_int(data)] + left = read_expression(state, data) + right = read_expression(state, data) + o = OpExpr(op, left, right) + # TODO: Store these explicitly? + o.line = left.line + o.column = left.column + o.end_line = right.end_line + o.end_column = right.end_column + expect_end_tag(data) + return o + elif tag == nodes.INDEX_EXPR: + base = read_expression(state, data) + index = read_expression(state, data) + expr = IndexExpr(base, index) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.BOOL_OP_EXPR: + op = bool_ops[read_int(data)] + values = read_expression_list(state, data) + # Convert list of values to nested OpExpr nodes + # E.g., [a, b, c] with "and" becomes OpExpr("and", OpExpr("and", a, b), c) + assert len(values) >= 2 + result = values[0] + for val in values[1:]: + result = OpExpr(op, result, val) + result.line = values[0].line + result.column = values[0].column + result.end_line = val.end_line + result.end_column = val.end_column + read_loc(data, result) + expect_end_tag(data) + return result + elif tag == nodes.COMPARISON_EXPR: + left = read_expression(state, data) + expect_tag(data, LIST_INT) + n_ops = read_int_bare(data) + ops = [cmp_ops[read_int_bare(data)] for _ in range(n_ops)] + comparators = read_expression_list(state, data) + assert len(ops) == len(comparators) + expr = ComparisonExpr(ops, [left] + comparators) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.UNARY_EXPR: + op = unary_ops[read_int(data)] + operand = read_expression(state, data) + expr = UnaryExpr(op, operand) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.DICT_EXPR: + expect_tag(data, LIST_GEN) + n_keys = read_int_bare(data) + keys: list[Expression | None] = [] + for _ in range(n_keys): + has_key = read_bool(data) + if has_key: + keys.append(read_expression(state, data)) + else: + keys.append(None) + values = read_expression_list(state, data) + # Zip keys and values into items + items = list(zip(keys, values)) + expr = DictExpr(items) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.COMPLEX_EXPR: + expect_tag(data, LITERAL_FLOAT) + real = read_float_bare(data) + expect_tag(data, LITERAL_FLOAT) + imag = read_float_bare(data) + value = complex(real, imag) + expr = ComplexExpr(value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.SLICE_EXPR: + has_begin = read_bool(data) + begin_index = read_expression(state, data) if has_begin else None + has_end = read_bool(data) + end_index = read_expression(state, data) if has_end else None + has_stride = read_bool(data) + stride = read_expression(state, data) if has_stride else None + expr = SliceExpr(begin_index, end_index, stride) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.TEMP_NODE: + # TempNode with no attributes + temp = TempNode(AnyType(TypeOfAny.special_form), no_rhs=True) + expect_end_tag(data) + return temp + elif tag == nodes.ELLIPSIS_EXPR: + expr = EllipsisExpr() + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.CONDITIONAL_EXPR: + if_expr = read_expression(state, data) + cond = read_expression(state, data) + else_expr = read_expression(state, data) + expr = ConditionalExpr(cond, if_expr, else_expr) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.FSTRING_EXPR: + # F-strings are converted into nodes representing "".join([...]), to match + # pre-existing behavior. + nparts = read_int(data) + fitems = [] + for _ in range(nparts): + b = read_bool(data) + if b: + n = read_int(data) + for i in range(n): + fitems.append(read_fstring_item(state, data)) + else: + s = StrExpr(read_str(data)) + read_loc(data, s) + fitems.append(s) + expr = build_fstring_join(state, data, fitems) + expect_end_tag(data) + return expr + elif tag == nodes.LAMBDA_EXPR: + arguments, has_ann = read_parameters(state, data) + body = read_block(state, data) + + if has_ann: + typ = CallableType( + [ + arg.type_annotation if arg.type_annotation else AnyType(TypeOfAny.unannotated) + for arg in arguments + ], + [arg.kind for arg in arguments], + [None if arg.pos_only else arg.variable.name for arg in arguments], + AnyType(TypeOfAny.unannotated), + _dummy_fallback, + ) + else: + typ = None + + expr = LambdaExpr(arguments, body) + expr.type = typ + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.NAMED_EXPR: + target = read_expression(state, data) + value = read_expression(state, data) + # AssignmentExpr expects target to be a NameExpr + if not isinstance(target, NameExpr): + # In case target is not a NameExpr, we need to handle this + # For now, we'll assert since the grammar should ensure it's a NameExpr + assert isinstance( + target, NameExpr + ), f"Expected NameExpr for target, got {type(target)}" + expr = AssignmentExpr(target, value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.STAR_EXPR: + wrapped_expr = read_expression(state, data) + expr = StarExpr(wrapped_expr) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.BYTES_EXPR: + # Read bytes literal as string + value = read_str(data) + expr = BytesExpr(value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.AWAIT_EXPR: + value = read_expression(state, data) + expr = AwaitExpr(value) + read_loc(data, expr) + expect_end_tag(data) + return expr + elif tag == nodes.BIG_INT_EXPR: + strval = read_str(data) + ie = IntExpr(int(strval)) + read_loc(data, ie) + expect_end_tag(data) + return ie + else: + assert False, tag + + +def read_fstring_items(state: State, data: ReadBuffer) -> Expression: + items = [] + n = read_int(data) + items = [read_fstring_item(state, data) for i in range(n)] + return build_fstring_join(state, data, items) + + +def build_fstring_join(state: State, data: ReadBuffer, items: list[Expression]) -> Expression: + if len(items) == 1: + expr = items[0] + read_loc(data, expr) + return expr + if all(isinstance(item, StrExpr) for item in items): + s = "".join([cast(StrExpr, item).value for item in items]) + expr = StrExpr(s) + read_loc(data, expr) + return expr + args = ListExpr(items) + str_expr = StrExpr("") + member = MemberExpr(str_expr, "join") + call = CallExpr(member, [args], [ARG_POS], [None]) + read_loc(data, call) + set_line_column(args, call) + set_line_column(str_expr, call) + set_line_column(member, call) + return call + + +def read_fstring_item(state: State, data: ReadBuffer) -> Expression: + t = read_tag(data) + if t == LITERAL_STR: + str_expr = StrExpr(read_str_bare(data)) + read_loc(data, str_expr) + return str_expr + elif t == nodes.FSTRING_INTERPOLATION: + expr = read_expression(state, data) + + # Read conversion flag such as !r + has_conv = read_bool(data) + if has_conv: + c = read_str(data) + fmt = "{" + c + ":{}}" + else: + fmt = "{:{}}" + + # Read format spec such as <30 (which may have nested {...}) + has_spec = read_bool(data) + if has_spec: + spec = read_fstring_items(state, data) + else: + spec = StrExpr("") + + member = MemberExpr(StrExpr(fmt), "format") + set_line_column(member, expr) + call = CallExpr(member, [expr, spec], [ARG_POS, ARG_POS], [None, None]) + set_line_column(call, expr) + expect_end_tag(data) + return call + else: + raise ValueError(f"Unexpected tag {t}") + + +def set_line_column(target: Context, src: Context) -> None: + target.line = src.line + target.column = src.column + + +def set_line_column_range(target: Context, src: Context) -> None: + target.line = src.line + target.column = src.column + target.end_line = src.end_line + target.end_column = src.end_column + + +def read_expression_list(state: State, data: ReadBuffer) -> list[Expression]: + expect_tag(data, LIST_GEN) + n = read_int_bare(data) + return [read_expression(state, data) for i in range(n)] + + +def read_generator_expr(state: State, data: ReadBuffer) -> GeneratorExpr: + """Helper function to read comprehension data (shared by Generator, ListComp, SetComp)""" + left_expr = read_expression(state, data) + n_generators = read_int(data) + indices = [read_expression(state, data) for _ in range(n_generators)] + sequences = [read_expression(state, data) for _ in range(n_generators)] + condlists = [read_expression_list(state, data) for _ in range(n_generators)] + is_async = [read_bool(data) for _ in range(n_generators)] + return GeneratorExpr(left_expr, indices, sequences, condlists, is_async) + + +def read_loc(data: ReadBuffer, node: Context) -> None: + expect_tag(data, LOCATION) + line = read_int_bare(data) + node.line = line + column = read_int_bare(data) + node.column = column + node.end_line = line + read_int_bare(data) + node.end_column = column + read_int_bare(data) + + +def strip_contents_from_if_stmt(stmt: IfStmt) -> None: + """Remove contents from IfStmt. + + Needed to still be able to check the conditions after the contents + have been merged with the surrounding function overloads. + """ + if len(stmt.body) == 1: + stmt.body[0].body = [] + if stmt.else_body and len(stmt.else_body.body) == 1: + if isinstance(stmt.else_body.body[0], IfStmt): + strip_contents_from_if_stmt(stmt.else_body.body[0]) + else: + stmt.else_body.body = [] + + +def is_stripped_if_stmt(stmt: Statement) -> bool: + """Check stmt to make sure it is a stripped IfStmt. + + See also: strip_contents_from_if_stmt + """ + if not isinstance(stmt, IfStmt): + return False + + if not (len(stmt.body) == 1 and len(stmt.body[0].body) == 0): + # Body not empty + return False + + if not stmt.else_body or len(stmt.else_body.body) == 0: + # No or empty else_body + return True + + # For elif, IfStmt are stored recursively in else_body + return is_stripped_if_stmt(stmt.else_body.body[0]) + + +def fail_merge_overload(state: State, node: IfStmt) -> None: + """Report an error when overloads cannot be merged due to unknown condition.""" + state.add_error( + message_registry.FAILED_TO_MERGE_OVERLOADS.value, + node.line, + node.column, + blocker=False, + code="misc", + ) + + +def check_ifstmt_for_overloads( + stmt: IfStmt, current_overload_name: str | None = None +) -> str | None: + """Check if IfStmt contains only overloads with the same name. + Return overload_name if found, None otherwise. + """ + # Check that block only contains a single Decorator, FuncDef, or OverloadedFuncDef. + # Multiple overloads have already been merged as OverloadedFuncDef. + if not ( + len(stmt.body[0].body) == 1 + and ( + isinstance(stmt.body[0].body[0], (Decorator, OverloadedFuncDef)) + or current_overload_name is not None + and isinstance(stmt.body[0].body[0], FuncDef) + ) + or len(stmt.body[0].body) > 1 + and isinstance(stmt.body[0].body[-1], OverloadedFuncDef) + and all(is_stripped_if_stmt(if_stmt) for if_stmt in stmt.body[0].body[:-1]) + ): + return None + + overload_name = cast(Decorator | FuncDef | OverloadedFuncDef, stmt.body[0].body[-1]).name + if stmt.else_body is None or stmt.else_body.is_unreachable: + return overload_name + + if len(stmt.else_body.body) == 1: + # For elif: else_body contains an IfStmt itself -> do a recursive check. + if ( + isinstance(stmt.else_body.body[0], (Decorator, FuncDef, OverloadedFuncDef)) + and stmt.else_body.body[0].name == overload_name + ): + return overload_name + if ( + isinstance(stmt.else_body.body[0], IfStmt) + and check_ifstmt_for_overloads(stmt.else_body.body[0], current_overload_name) + == overload_name + ): + return overload_name + + return None + + +def get_executable_if_block_with_overloads( + stmt: IfStmt, options: Options +) -> tuple[Block | None, IfStmt | None]: + """Return block from IfStmt that will get executed. + + Return + 0 -> A block if sure that alternative blocks are unreachable. + 1 -> An IfStmt if the reachability of it can't be inferred, + i.e. the truth value is unknown. + """ + infer_reachability_of_if_statement(stmt, options) + if stmt.else_body is None and stmt.body[0].is_unreachable is True: + # always False condition with no else + return None, None + if ( + stmt.else_body is None + or stmt.body[0].is_unreachable is False + and stmt.else_body.is_unreachable is False + ): + # The truth value is unknown, thus not conclusive + return None, stmt + if stmt.else_body.is_unreachable: + # else_body will be set unreachable if condition is always True + return stmt.body[0], None + if stmt.body[0].is_unreachable is True: + # body will be set unreachable if condition is always False + # else_body can contain an IfStmt itself (for elif) -> do a recursive check + if isinstance(stmt.else_body.body[0], IfStmt): + return get_executable_if_block_with_overloads(stmt.else_body.body[0], options) + return stmt.else_body, None + return None, stmt + + +def fix_function_overloads(state: State, stmts: list[Statement]) -> list[Statement]: + """Merge consecutive function overloads into OverloadedFuncDef nodes. + + This function processes a list of statements and combines function overloads + (marked with @overload decorator) that have the same name into a single + OverloadedFuncDef node. It also handles conditional overloads (overloads + inside if statements) when the condition can be evaluated. + """ + ret: list[Statement] = [] + current_overload: list[OverloadPart] = [] + current_overload_name: str | None = None + last_unconditional_func_def: str | None = None + last_if_stmt: IfStmt | None = None + last_if_overload: Decorator | FuncDef | OverloadedFuncDef | None = None + last_if_stmt_overload_name: str | None = None + last_if_unknown_truth_value: IfStmt | None = None + skipped_if_stmts: list[IfStmt] = [] + for stmt in stmts: + if_overload_name: str | None = None + if_block_with_overload: Block | None = None + if_unknown_truth_value: IfStmt | None = None + if isinstance(stmt, IfStmt): + # Check IfStmt block to determine if function overloads can be merged + if_overload_name = check_ifstmt_for_overloads(stmt, current_overload_name) + if if_overload_name is not None: + if_block_with_overload, if_unknown_truth_value = ( + get_executable_if_block_with_overloads(stmt, state.options) + ) + + if ( + current_overload_name is not None + and isinstance(stmt, (Decorator, FuncDef)) + and stmt.name == current_overload_name + ): + if last_if_stmt is not None: + skipped_if_stmts.append(last_if_stmt) + if last_if_overload is not None: + # Last stmt was an IfStmt with same overload name + # Add overloads to current_overload + if isinstance(last_if_overload, OverloadedFuncDef): + current_overload.extend(last_if_overload.items) + else: + current_overload.append(last_if_overload) + last_if_stmt, last_if_overload = None, None + if last_if_unknown_truth_value: + fail_merge_overload(state, last_if_unknown_truth_value) + last_if_unknown_truth_value = None + current_overload.append(stmt) + if isinstance(stmt, FuncDef): + # This is, strictly speaking, wrong: there might be a decorated + # implementation. However, it only affects the error message we show: + # ideally it's "already defined", but "implementation must come last" + # is also reasonable. + # TODO: can we get rid of this completely and just always emit + # "implementation must come last" instead? + last_unconditional_func_def = stmt.name + elif ( + current_overload_name is not None + and isinstance(stmt, IfStmt) + and if_overload_name == current_overload_name + and last_unconditional_func_def != current_overload_name + ): + # IfStmt only contains stmts relevant to current_overload. + # Check if stmts are reachable and add them to current_overload, + # otherwise skip IfStmt to allow subsequent overload + # or function definitions. + skipped_if_stmts.append(stmt) + if if_block_with_overload is None: + if if_unknown_truth_value is not None: + fail_merge_overload(state, if_unknown_truth_value) + continue + if last_if_overload is not None: + # Last stmt was an IfStmt with same overload name + # Add overloads to current_overload + if isinstance(last_if_overload, OverloadedFuncDef): + current_overload.extend(last_if_overload.items) + else: + current_overload.append(last_if_overload) + last_if_stmt, last_if_overload = None, None + if isinstance(if_block_with_overload.body[-1], OverloadedFuncDef): + skipped_if_stmts.extend(cast(list[IfStmt], if_block_with_overload.body[:-1])) + current_overload.extend(if_block_with_overload.body[-1].items) + else: + current_overload.append(cast(Decorator | FuncDef, if_block_with_overload.body[0])) + else: + if last_if_stmt is not None: + ret.append(last_if_stmt) + last_if_stmt_overload_name = current_overload_name + last_if_stmt, last_if_overload = None, None + last_if_unknown_truth_value = None + + if current_overload and current_overload_name == last_if_stmt_overload_name: + # Remove last stmt (IfStmt) from ret if the overload names matched + # Only happens if no executable block had been found in IfStmt + popped = ret.pop() + assert isinstance(popped, IfStmt) + skipped_if_stmts.append(popped) + if current_overload and skipped_if_stmts: + # Add bare IfStmt (without overloads) to ret + # Required for mypy to be able to still check conditions + for if_stmt in skipped_if_stmts: + strip_contents_from_if_stmt(if_stmt) + ret.append(if_stmt) + skipped_if_stmts = [] + if len(current_overload) == 1: + ret.append(current_overload[0]) + elif len(current_overload) > 1: + ret.append(OverloadedFuncDef(current_overload)) + + # If we have multiple decorated functions named "_" next to each, we want to treat + # them as a series of regular FuncDefs instead of one OverloadedFuncDef because + # most of mypy/mypyc assumes that all the functions in an OverloadedFuncDef are + # related, but multiple underscore functions next to each other aren't necessarily + # related + last_unconditional_func_def = None + if isinstance(stmt, Decorator) and not unnamed_function(stmt.name): + current_overload = [stmt] + current_overload_name = stmt.name + elif isinstance(stmt, IfStmt) and if_overload_name is not None: + current_overload = [] + current_overload_name = if_overload_name + last_if_stmt = stmt + last_if_stmt_overload_name = None + if if_block_with_overload is not None: + skipped_if_stmts.extend(cast(list[IfStmt], if_block_with_overload.body[:-1])) + last_if_overload = cast( + Decorator | FuncDef | OverloadedFuncDef, if_block_with_overload.body[-1] + ) + last_if_unknown_truth_value = if_unknown_truth_value + else: + current_overload = [] + current_overload_name = None + ret.append(stmt) + + if current_overload and skipped_if_stmts: + # Add bare IfStmt (without overloads) to ret + # Required for mypy to be able to still check conditions + for if_stmt in skipped_if_stmts: + strip_contents_from_if_stmt(if_stmt) + ret.append(if_stmt) + if len(current_overload) == 1: + ret.append(current_overload[0]) + elif len(current_overload) > 1: + ret.append(OverloadedFuncDef(current_overload)) + elif last_if_overload is not None: + ret.append(last_if_overload) + elif last_if_stmt is not None: + ret.append(last_if_stmt) + return ret + + +def deserialize_imports(import_bytes: bytes) -> list[ImportBase]: + """Deserialize import metadata from bytes into mypy AST nodes. + + Args: + import_bytes: Serialized import metadata from the Rust parser + + Returns: + List of Import and ImportFrom AST nodes with location and metadata + """ + if not import_bytes: + return [] + + data = ReadBuffer(import_bytes) + + expect_tag(data, LIST_GEN) + n_imports = read_int_bare(data) + + imports: list[ImportBase] = [] + + for _ in range(n_imports): + tag = read_tag(data) + + if tag == IMPORT_METADATA: + name = read_str(data) + relative = read_int(data) + + has_asname = read_bool(data) + if has_asname: + asname = read_str(data) + else: + asname = None + + # Note: relative imports are handled via ImportFrom, so relative should be 0 here + stmt = Import([(name, asname)]) + _read_and_set_import_metadata(data, stmt) + imports.append(stmt) + + elif tag == IMPORTFROM_METADATA: + module = read_str(data) + relative = read_int(data) + + expect_tag(data, LIST_GEN) + n_names = read_int_bare(data) + names: list[tuple[str, str | None]] = [] + + for _ in range(n_names): + name = read_str(data) + has_asname = read_bool(data) + if has_asname: + asname = read_str(data) + else: + asname = None + names.append((name, asname)) + + stmt = ImportFrom(module, relative, names) + _read_and_set_import_metadata(data, stmt) + imports.append(stmt) + + elif tag == IMPORTALL_METADATA: + module = read_str(data) + relative = read_int(data) + + stmt = ImportAll(module, relative) + _read_and_set_import_metadata(data, stmt) + imports.append(stmt) + + else: + raise ValueError(f"Unexpected tag in import metadata: {tag}") + + return imports + + +def _read_and_set_import_metadata(data: ReadBuffer, stmt: Import | ImportFrom | ImportAll) -> None: + """Read location and metadata flags from buffer and set them on the import statement. + + Args: + data: Buffer containing serialized data + stmt: Import, ImportFrom, or ImportAll statement to populate with location and metadata + """ + read_loc(data, stmt) + + # Metadata flags as a single integer bitfield + flags = read_int(data) + + # Extract individual flags using bitwise operations + # Bit 0: is_top_level + # Bit 1: is_unreachable + # Bit 2: is_mypy_only + stmt.is_top_level = (flags & 0x01) != 0 + stmt.is_unreachable = (flags & 0x02) != 0 + stmt.is_mypy_only = (flags & 0x04) != 0 diff --git a/mypy/nodes.py b/mypy/nodes.py index 4c238592f9927..1affc6a497cc0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -5133,6 +5133,76 @@ def local_definitions( CLASS_DEF: Final[Tag] = 60 SYMBOL_TABLE_NODE: Final[Tag] = 61 +EXPR_STMT: Final[Tag] = 160 +CALL_EXPR: Final[Tag] = 161 +NAME_EXPR: Final[Tag] = 162 +STR_EXPR: Final[Tag] = 163 +IMPORT: Final[Tag] = 164 +MEMBER_EXPR: Final[Tag] = 165 +OP_EXPR: Final[Tag] = 166 +INT_EXPR: Final[Tag] = 167 +IF_STMT: Final[Tag] = 168 +ASSIGNMENT_STMT: Final[Tag] = 169 +TUPLE_EXPR: Final[Tag] = 170 +BLOCK: Final[Tag] = 171 +INDEX_EXPR: Final[Tag] = 172 +LIST_EXPR: Final[Tag] = 173 +SET_EXPR: Final[Tag] = 174 +RETURN_STMT: Final[Tag] = 175 +WHILE_STMT: Final[Tag] = 176 +COMPARISON_EXPR: Final[Tag] = 177 +BOOL_OP_EXPR: Final[Tag] = 178 +FUNC_DEF_STMT: Final[Tag] = 179 +PASS_STMT: Final[Tag] = 180 +FLOAT_EXPR: Final[Tag] = 181 +UNARY_EXPR: Final[Tag] = 182 +DICT_EXPR: Final[Tag] = 183 +COMPLEX_EXPR: Final[Tag] = 184 +SLICE_EXPR: Final[Tag] = 185 +TEMP_NODE: Final[Tag] = 186 +RAISE_STMT: Final[Tag] = 187 +BREAK_STMT: Final[Tag] = 188 +CONTINUE_STMT: Final[Tag] = 189 +GENERATOR_EXPR: Final[Tag] = 190 +YIELD_EXPR: Final[Tag] = 191 +YIELD_FROM_EXPR: Final[Tag] = 192 +LIST_COMPREHENSION: Final[Tag] = 193 +SET_COMPREHENSION: Final[Tag] = 194 +DICT_COMPREHENSION: Final[Tag] = 195 +IMPORT_FROM: Final[Tag] = 196 +ASSERT_STMT: Final[Tag] = 197 +FOR_STMT: Final[Tag] = 198 +WITH_STMT: Final[Tag] = 199 +OPERATOR_ASSIGNMENT_STMT: Final[Tag] = 200 +TRY_STMT: Final[Tag] = 201 +ELLIPSIS_EXPR: Final[Tag] = 202 +CONDITIONAL_EXPR: Final[Tag] = 203 +DEL_STMT: Final[Tag] = 204 +FSTRING_EXPR: Final[Tag] = 205 +FSTRING_INTERPOLATION: Final[Tag] = 206 +LAMBDA_EXPR: Final[Tag] = 207 +NAMED_EXPR: Final[Tag] = 208 +STAR_EXPR: Final[Tag] = 209 +BYTES_EXPR: Final[Tag] = 210 +GLOBAL_DECL: Final[Tag] = 211 +NONLOCAL_DECL: Final[Tag] = 212 +AWAIT_EXPR: Final[Tag] = 213 +BIG_INT_EXPR: Final[Tag] = 214 +IMPORT_ALL: Final[Tag] = 215 +MATCH_STMT: Final[Tag] = 216 +AS_PATTERN: Final[Tag] = 217 +OR_PATTERN: Final[Tag] = 218 +VALUE_PATTERN: Final[Tag] = 219 +SINGLETON_PATTERN: Final[Tag] = 220 +SEQUENCE_PATTERN: Final[Tag] = 221 +STARRED_PATTERN: Final[Tag] = 222 +MAPPING_PATTERN: Final[Tag] = 223 +CLASS_PATTERN: Final[Tag] = 224 +TYPE_ALIAS_STMT: Final[Tag] = 225 +IMPORT_METADATA: Final[Tag] = 226 +IMPORTFROM_METADATA: Final[Tag] = 227 +IMPORTALL_METADATA: Final[Tag] = 228 + def read_symbol(data: ReadBuffer) -> SymbolNode: tag = read_tag(data) diff --git a/mypy/options.py b/mypy/options.py index 3dc72c7e30512..02528fcbdee5e 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -69,6 +69,7 @@ class BuildType: | { "platform", "bazel", + "native_parser", "old_type_inference", "plugins", "disable_bytearray_promotion", @@ -378,6 +379,8 @@ def __init__(self) -> None: self.logical_deps = False # If True, partial types can't span a module top level and a function self.local_partial_types = False + # If True, use the native parser (experimental) + self.native_parser = False # Some behaviors are changed when using Bazel (https://bazel.build). self.bazel = False # If True, export inferred types for all expressions as BuildResult.types diff --git a/mypy/parse.py b/mypy/parse.py index ee61760c0ac07..b58c847f5d569 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -1,5 +1,9 @@ from __future__ import annotations +import os +import re + +from mypy import errorcodes as codes from mypy.errors import Errors from mypy.nodes import MypyFile from mypy.options import Options @@ -20,6 +24,45 @@ def parse( The python_version (major, minor) option determines the Python syntax variant. """ + if options.native_parser: + # Native parser only works with actual files on disk + # Fall back to fastparse for in-memory source or non-existent files + if os.path.exists(fnam): + import mypy.nativeparse + + ignore_errors = options.ignore_errors or fnam in errors.ignored_files + # If errors are ignored, we can drop many function bodies to speed up type checking. + strip_function_bodies = ignore_errors and not options.preserve_asts + + errors.set_file(fnam, module, options=options) + tree, parse_errors, type_ignores = mypy.nativeparse.native_parse( + fnam, options, skip_function_bodies=strip_function_bodies + ) + # Convert type ignores list to dict + tree.ignored_lines = dict(type_ignores) + # Set is_stub based on file extension + tree.is_stub = fnam.endswith(".pyi") + # Note: tree.imports is populated directly by native_parse with deserialized + # import metadata, so we don't need to collect imports via AST traversal + + # Report parse errors + for error in parse_errors: + message = error["message"] + # Standardize error message by capitalizing the first word + message = re.sub(r"^(\s*\w)", lambda m: m.group(1).upper(), message) + # Respect blocker status from error, default to True for syntax errors + is_blocker = error.get("blocker", True) + error_code = error.get("code") + if error_code is None: + error_code = codes.SYNTAX + errors.report( + error["line"], error["column"], message, blocker=is_blocker, code=error_code + ) + if raise_on_error and errors.is_errors(): + errors.raise_error() + return tree + # Fall through to fastparse for non-existent files + if options.transform_source is not None: source = options.transform_source(source) import mypy.fastparse diff --git a/mypy/strconv.py b/mypy/strconv.py index 168a8bcffdc7e..ee4a7feed6ce4 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -147,6 +147,8 @@ def visit_func_def(self, o: mypy.nodes.FuncDef) -> str: arg_kinds = {arg.kind for arg in o.arguments} if len(arg_kinds & {mypy.nodes.ARG_NAMED, mypy.nodes.ARG_NAMED_OPT}) > 0: a.insert(1, f"MaxPos({o.max_pos})") + if o.is_coroutine: + a.insert(1, "Async") if o.abstract_status in (mypy.nodes.IS_ABSTRACT, mypy.nodes.IMPLICITLY_ABSTRACT): a.insert(-1, "Abstract") if o.is_static: @@ -183,6 +185,9 @@ def visit_class_def(self, o: mypy.nodes.ClassDef) -> str: a.insert(1, ("TypeVars", o.type_vars)) if o.metaclass: a.insert(1, f"Metaclass({o.metaclass.accept(self)})") + if o.keywords: + keyword_items = [f"{k}={v.accept(self)}" for k, v in o.keywords.items()] + a.insert(1, f"Keywords({', '.join(keyword_items)})") if o.decorators: a.insert(1, ("Decorators", o.decorators)) if o.info and o.info._promote: diff --git a/mypy/test/test_nativeparse.py b/mypy/test/test_nativeparse.py new file mode 100644 index 0000000000000..71f9c06892dd2 --- /dev/null +++ b/mypy/test/test_nativeparse.py @@ -0,0 +1,260 @@ +"""Tests for the experimental native mypy parser. + +To run these, you will need to manually install ast_serialize from +https://github.com/mypyc/ast_serialize first (see the README for the details). +""" + +from __future__ import annotations + +import contextlib +import os +import tempfile +import unittest +from collections.abc import Iterator +from typing import Any + +from mypy import defaults, nodes +from mypy.cache import ( + END_TAG, + LIST_GEN, + LIST_INT, + LITERAL_INT, + LITERAL_NONE, + LITERAL_STR, + LOCATION, +) +from mypy.config_parser import parse_mypy_comments +from mypy.errors import CompileError +from mypy.nodes import MypyFile +from mypy.options import Options +from mypy.test.data import DataDrivenTestCase, DataSuite +from mypy.test.helpers import assert_string_arrays_equal +from mypy.util import get_mypy_comments + +# If the experimental ast_serialize module isn't installed, the following import will fail +# and we won't run any native parser tests. +try: + from mypy.nativeparse import native_parse, parse_to_binary_ast + + has_nativeparse = True +except ImportError: + has_nativeparse = False + + +class NativeParserSuite(DataSuite): + required_out_section = True + base_path = "." + files = ["native-parser.test"] if has_nativeparse else [] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + test_parser(testcase) + + +class NativeParserImportsSuite(DataSuite): + required_out_section = True + base_path = "." + files = ["native-parser-imports.test"] if has_nativeparse else [] + + def run_case(self, testcase: DataDrivenTestCase) -> None: + test_parser_imports(testcase) + + +def test_parser(testcase: DataDrivenTestCase) -> None: + """Perform a single native parser test case. + + The argument contains the description of the test case. + """ + options = Options() + options.hide_error_codes = True + + if testcase.file.endswith("python310.test"): + options.python_version = (3, 10) + elif testcase.file.endswith("python312.test"): + options.python_version = (3, 12) + elif testcase.file.endswith("python313.test"): + options.python_version = (3, 13) + elif testcase.file.endswith("python314.test"): + options.python_version = (3, 14) + else: + options.python_version = defaults.PYTHON3_VERSION + + source = "\n".join(testcase.input) + + # Apply mypy: comments to options. + comments = get_mypy_comments(source) + changes, _ = parse_mypy_comments(comments, options) + options = options.apply_changes(changes) + + # Check if we should skip function bodies (when ignoring errors) + skip_function_bodies = "# mypy: ignore-errors=True" in source + + try: + with temp_source(source) as fnam: + node, errors, type_ignores = native_parse(fnam, options, skip_function_bodies) + node.path = "main" + a = node.str_with_options(options).split("\n") + a = [format_error(err) for err in errors] + a + a = [format_ignore(ignore) for ignore in type_ignores] + a + except CompileError as e: + a = e.messages + assert_string_arrays_equal( + testcase.output, a, f"Invalid parser output ({testcase.file}, line {testcase.line})" + ) + + +def format_error(err: dict[str, Any]) -> str: + return f"{err['line']}:{err['column']}: error: {err['message']}" + + +def format_ignore(ignore: tuple[int, list[str]]) -> str: + line, codes = ignore + if not codes: + return f"ignore: {line}" + else: + return f"ignore: {line} [{', '.join(codes)}]" + + +def test_parser_imports(testcase: DataDrivenTestCase) -> None: + """Perform a single native parser imports test case. + + The argument contains the description of the test case. + This test outputs only reachable import information. + """ + options = Options() + options.hide_error_codes = True + options.python_version = (3, 10) + + source = "\n".join(testcase.input) + + try: + with temp_source(source) as fnam: + node, errors, type_ignores = native_parse(fnam, options) + + # Extract and format reachable imports + a = format_reachable_imports(node) + a = [format_error(err) for err in errors] + a + except CompileError as e: + a = e.messages + + assert_string_arrays_equal( + testcase.output, a, f"Invalid parser output ({testcase.file}, line {testcase.line})" + ) + + +def format_reachable_imports(node: MypyFile) -> list[str]: + """Format reachable imports from a MypyFile node. + + Returns a list of strings representing reachable imports with line numbers and flags. + """ + from mypy.nodes import Import, ImportAll, ImportFrom + + output: list[str] = [] + + # Filter for reachable imports (is_unreachable == False) + reachable_imports = [imp for imp in node.imports if not imp.is_unreachable] + + for imp in reachable_imports: + line_num = imp.line + + # Collect flags (only show when flag is False/not set) + flags = [] + if not imp.is_top_level: + flags.append("not top_level") + if imp.is_mypy_only: + flags.append("mypy_only") + + flags_str = " [" + ", ".join(flags) + "]" if flags else "" + + if isinstance(imp, Import): + # Format: line: import foo [as bar] [flags] + for module_id, as_id in imp.ids: + if as_id: + output.append(f"{line_num}: import {module_id} as {as_id}{flags_str}") + else: + output.append(f"{line_num}: import {module_id}{flags_str}") + elif isinstance(imp, ImportFrom): + # Format: line: from foo import bar, baz [as b] [flags] + # Handle relative imports + if imp.relative > 0: + prefix = "." * imp.relative + if imp.id: + module = f"{prefix}{imp.id}" + else: + module = prefix + else: + module = imp.id + + # Group all names together + name_parts = [] + for name, as_name in imp.names: + if as_name: + name_parts.append(f"{name} as {as_name}") + else: + name_parts.append(name) + + names_str = ", ".join(name_parts) + output.append(f"{line_num}: from {module} import {names_str}{flags_str}") + elif isinstance(imp, ImportAll): + # Format: line: from foo import * [flags] + # Handle relative imports + if imp.relative > 0: + prefix = "." * imp.relative + if imp.id: + module = f"{prefix}{imp.id}" + else: + module = prefix + else: + module = imp.id + + output.append(f"{line_num}: from {module} import *{flags_str}") + + return output + + +@unittest.skipUnless(has_nativeparse, "nativeparse not available") +class TestNativeParserBinaryFormat(unittest.TestCase): + def test_trivial_binary_data(self) -> None: + # A quick sanity check to ensure the serialized data looks as expected. Only covers + # a few AST nodes. + + def int_enc(n: int) -> int: + return (n + 10) << 1 + + def locs(start_line: int, start_column: int, end_line: int, end_column: int) -> list[int]: + return [ + LOCATION, + int_enc(start_line), + int_enc(start_column), + int_enc(end_line - start_line), + int_enc(end_column - start_column), + ] + + with temp_source("print('hello')") as fnam: + b, _, _, _, _ = parse_to_binary_ast(fnam, Options()) + assert list(b) == ( + [LITERAL_INT, 22, nodes.EXPR_STMT, nodes.CALL_EXPR] + + [nodes.NAME_EXPR, LITERAL_STR] + + [int_enc(5)] + + list(b"print") + + locs(1, 0, 1, 5) + + [END_TAG, LIST_GEN, 22, nodes.STR_EXPR] + + [LITERAL_STR, int_enc(5)] + + list(b"hello") + + locs(1, 6, 1, 13) + + [END_TAG] + # arg_kinds: [ARG_POS] + + [LIST_INT, 22, int_enc(0)] + # arg_names: [None] + + [LIST_GEN, 22, LITERAL_NONE] + + locs(1, 0, 1, 14) + + [END_TAG, END_TAG] + ) + + +@contextlib.contextmanager +def temp_source(text: str) -> Iterator[str]: + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = os.path.join(temp_dir, "t.py") + with open(temp_path, "w") as f: + f.write(text) + yield temp_path diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 00268ea998dff..3326d642c6a40 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -62,7 +62,7 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: with tempfile.NamedTemporaryFile(prefix="test", dir=".") as temp_file: temp_path = Path(temp_file.name) if not temp_path.with_name(temp_path.name.upper()).exists(): - pytest.skip("File system is not case‐insensitive") + pytest.skip("File system is not case-insensitive") if lxml is None and os.path.basename(testcase.file) == "check-reports.test": pytest.skip("Cannot import lxml. Is it installed?") incremental = ( @@ -136,6 +136,7 @@ def run_case_once( options = parse_options(original_program_text, testcase, incremental_step) options.use_builtins_fixtures = True options.show_traceback = True + options.native_parser = bool(os.environ.get("TEST_NATIVE_PARSER")) if options.num_workers: options.fixed_format_cache = True diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 1a76a50a2d94e..fb783a0ce7596 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -259,12 +259,14 @@ def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDe return new def visit_class_def(self, node: ClassDef) -> ClassDef: + keywords = [(key, self.expr(value)) for key, value in node.keywords.items()] new = ClassDef( node.name, self.block(node.defs), node.type_vars, self.expressions(node.base_type_exprs), self.optional_expr(node.metaclass), + keywords, ) new.fullname = node.fullname new.info = node.info diff --git a/mypy/types.py b/mypy/types.py index db8fd4605659c..80a699b9ebe9c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -3937,6 +3937,11 @@ def item_str(name: str, typ: str) -> str: return f"TypedDict({prefix}{s})" def visit_raw_expression_type(self, t: RawExpressionType, /) -> str: + # For bytes literals, the value is already escaped, just add quotes and b prefix + if t.base_type_name == "builtins.bytes": + # The value is already escaped (e.g., "foo" or "hello\\nworld") + # Just add quotes and b prefix + return f"b'{t.literal_value}'" return repr(t.literal_value) def visit_literal_type(self, t: LiteralType, /) -> str: @@ -4296,6 +4301,10 @@ def type_vars_as_args(type_vars: Sequence[TypeVarLikeType]) -> tuple[Type, ...]: UNION_TYPE: Final[Tag] = 115 TYPE_TYPE: Final[Tag] = 116 PARAMETERS: Final[Tag] = 117 +LIST_TYPE: Final[Tag] = 118 # Only valid in serialized ASTs +ELLIPSIS_TYPE: Final[Tag] = 119 # Only valid in serialized ASTs +RAW_EXPRESSION_TYPE: Final[Tag] = 120 # Only valid in serialized ASTs +CALL_TYPE: Final[Tag] = 121 # Only valid in serialized ASTs def read_type(data: ReadBuffer, tag: Tag | None = None) -> Type: diff --git a/test-data/unit/native-parser-imports.test b/test-data/unit/native-parser-imports.test new file mode 100644 index 0000000000000..b8dcac283a525 --- /dev/null +++ b/test-data/unit/native-parser-imports.test @@ -0,0 +1,733 @@ +-- Test cases for import returned by the native parser using a side channel +-- In particular, test import flags related to reachability and priority when ordering SCCs + +[case testNoImport] +# No imports +print() +[out] + +[case testSimpleImport] +import foo +[out] +1: import foo + +[case testMultipleImports] +import foo +import bar +import baz +[out] +1: import foo +2: import bar +3: import baz + +[case testImportAs] +import foo as f +import bar as b +[out] +1: import foo as f +2: import bar as b + +[case testFromImport] +from foo import bar +[out] +1: from foo import bar + +[case testFromImportMultiple] +from foo import bar, baz, qux +[out] +1: from foo import bar, baz, qux + +[case testFromImportAs] +from foo import bar as b, baz as z +[out] +1: from foo import bar as b, baz as z + +[case testRelativeImport] +from . import foo +from .. import bar +from ...baz import qux +[out] +1: from . import foo +2: from .. import bar +3: from ...baz import qux + +[case testNonTopLevelImport] +def f(): + import foo + from bar import baz +[out] +2: import foo [not top_level] +3: from bar import baz [not top_level] + +[case testImportInClass] +class Foo: + import bar +[out] +2: import bar +[case testUnreachableImportPY2] +# Imports in unreachable blocks should not appear +if PY2: + import unreachable_module +import reachable_module +[out] +4: import reachable_module + +[case testReachableImportPY3] +# Imports in reachable if PY3 blocks should appear +if PY3: + import reachable_module +[out] +3: import reachable_module + +[case testUnreachableElseBlock] +# Imports in unreachable else block should not appear +if PY3: + import reachable +else: + import unreachable +[out] +3: import reachable + +[case testMixedReachableUnreachable] +# Mix of reachable and unreachable imports +import before +if PY2: + import unreachable_in_if +else: + import reachable_in_else +import after +[out] +2: import before +6: import reachable_in_else +7: import after + +[case testNestedUnreachable] +# Nested unreachable blocks +if PY2: + import outer_unreachable + if True: + import inner_unreachable +import reachable +[out] +6: import reachable + +[case testUnreachableWithTopLevel] +# Unreachable imports together with top_level flag handling +def f(): + if PY2: + import unreachable_func + import reachable_func +if PY2: + import unreachable_top +import reachable_top +[out] +5: import reachable_func [not top_level] +8: import reachable_top + +[case testVersionCheckUnreachable] +# sys.version_info check makes else unreachable +import sys +if sys.version_info >= (3, 8): + import reachable_version_check +else: + import unreachable_old_version +[out] +2: import sys +4: import reachable_version_check + +[case testMultipeBranchesUnreachable] +if PY2: + import unreachable_if +elif PY2: + import unreachable_elif +else: + import reachable_else +[out] +6: import reachable_else + +[case testMypyOnlyImport] +# Imports in TYPE_CHECKING blocks are mypy_only +from typing import TYPE_CHECKING +if TYPE_CHECKING: + import mypy_only_module +import regular_module +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_module [mypy_only] +5: import regular_module + +[case testMypyNameAlias] +# MYPY is also recognized as mypy_only +if MYPY: + import only_for_mypy +import for_everyone +[out] +3: import only_for_mypy [mypy_only] +4: import for_everyone + +[case testMypyOnlyElseBranch] +# Else branch of TYPE_CHECKING is unreachable (runtime-only, not analyzed by mypy) +from typing import TYPE_CHECKING +if TYPE_CHECKING: + import for_mypy +else: + import for_runtime +[out] +2: from typing import TYPE_CHECKING +4: import for_mypy [mypy_only] + +[case testNestedMypyOnly] +# Nested blocks inside TYPE_CHECKING are also mypy_only +from typing import TYPE_CHECKING +if TYPE_CHECKING: + if True: + import deeply_nested + import also_mypy_only +[out] +2: from typing import TYPE_CHECKING +5: import deeply_nested [mypy_only] +6: import also_mypy_only [mypy_only] + +[case testMypyOnlyInFunction] +# Mypy-only imports in functions +from typing import TYPE_CHECKING +def f(): + if TYPE_CHECKING: + import mypy_func_import + import regular_func_import +[out] +2: from typing import TYPE_CHECKING +5: import mypy_func_import [not top_level, mypy_only] +6: import regular_func_import [not top_level] + +[case testMypyOnlyElifBranch] +# Elif after TYPE_CHECKING is unreachable (runtime-only) +from typing import TYPE_CHECKING +if TYPE_CHECKING: + import for_mypy +elif True: + import for_runtime_elif +[out] +2: from typing import TYPE_CHECKING +4: import for_mypy [mypy_only] + +[case testNotMypyFalseBranch] +# Not TYPE_CHECKING makes body unreachable, else is mypy_only +from typing import TYPE_CHECKING +if not TYPE_CHECKING: + import runtime_only +else: + import mypy_only +[out] +2: from typing import TYPE_CHECKING +6: import mypy_only [mypy_only] + +[case testMixedMypyOnlyAndUnreachable] +# Combination of mypy_only and unreachable +from typing import TYPE_CHECKING +if PY2: + import unreachable_py2 +if TYPE_CHECKING: + import mypy_only_import +if PY3: + import always_reachable +[out] +2: from typing import TYPE_CHECKING +6: import mypy_only_import [mypy_only] +8: import always_reachable + +[case testTypingExtensionsTypeChecking] +# typing_extensions.TYPE_CHECKING is also recognized +from typing_extensions import TYPE_CHECKING +if TYPE_CHECKING: + import mypy_import +[out] +2: from typing_extensions import TYPE_CHECKING +4: import mypy_import [mypy_only] + +[case testUnknownCondition] +# Unknown conditions - all branches reachable +if x: + import in_if +else: + import in_else +[out] +3: import in_if +5: import in_else + +[case testUnknownConditionWithElif] +# Unknown conditions with elif +if unknown_var: + import first +elif another_var: + import second +else: + import third +[out] +3: import first +5: import second +7: import third + +[case testMixedKnownUnknown] +# Mix of known and unknown conditions +if TYPE_CHECKING: + import mypy_only +if some_var: + import maybe_reachable +if PY2: + import unreachable +[out] +3: import mypy_only [mypy_only] +5: import maybe_reachable + +[case testNestedUnknownInMypyOnly] +# Unknown condition nested in mypy-only block +from typing import TYPE_CHECKING +if TYPE_CHECKING: + if condition: + import nested_mypy + import also_mypy +[out] +2: from typing import TYPE_CHECKING +5: import nested_mypy [mypy_only] +6: import also_mypy [mypy_only] + +[case testUnknownAfterMypyTrue] +# Unknown elif after TYPE_CHECKING is unreachable +from typing import TYPE_CHECKING +if TYPE_CHECKING: + import for_mypy +elif x: + import unreachable_elif +[out] +2: from typing import TYPE_CHECKING +4: import for_mypy [mypy_only] + +[case testStarImport] +from foo import * +[out] +1: from foo import * + +[case testStarImportInFunction] +def f(): + from foo import * +[out] +2: from foo import * [not top_level] + +[case testMypyOnlyStarImport] +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from mypy_types import * +from regular import * +[out] +1: from typing import TYPE_CHECKING +3: from mypy_types import * [mypy_only] +4: from regular import * + +[case testUnreachableStarImport] +if PY2: + from unreachable import * +from reachable import * +[out] +3: from reachable import * + +[case testNotPY2] +# not PY2 should be always true +if not PY2: + import always_reachable +[out] +3: import always_reachable + +[case testNotPY3] +# not PY3 should be always false +if not PY3: + import unreachable +import reachable +[out] +4: import reachable + +[case testNotPY3Else] +# not PY3 else branch should be reachable +if not PY3: + import unreachable_if +else: + import reachable_else +[out] +5: import reachable_else + +[case testBoolAndAlwaysTrueWithMypyTrue] +# PY3 and TYPE_CHECKING - both true, result is mypy_true +from typing import TYPE_CHECKING +if PY3 and TYPE_CHECKING: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_import [mypy_only] + +[case testBoolAndAlwaysTrueWithUnknown] +# PY3 and unknown - result is unknown, both branches reachable +if PY3 and some_var: + import maybe_reachable +else: + import also_maybe_reachable +[out] +3: import maybe_reachable +5: import also_maybe_reachable + +[case testBoolAndAlwaysFalseWithAnything] +# PY2 and anything - result is always false +if PY2 and some_var: + import unreachable +import reachable +[out] +4: import reachable + +[case testBoolAndMypyFalseWithAlwaysTrue] +# not TYPE_CHECKING and PY3 - result is mypy_false, else is mypy_only +from typing import TYPE_CHECKING +if not TYPE_CHECKING and PY3: + import runtime_only +else: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +6: import mypy_only_import [mypy_only] + +[case testBoolAndMypyTrueOnly] +# MYPY and TYPE_CHECKING - both mypy_true, result is mypy_true +from typing import TYPE_CHECKING +if MYPY and TYPE_CHECKING: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_import [mypy_only] + +[case testBoolOrAlwaysTrueWithAnything] +# PY3 or anything - result is always true +if PY3 or some_var: + import always_reachable +else: + import unreachable_else +[out] +3: import always_reachable + +[case testBoolOrMypyTrueWithAlwaysFalse] +# TYPE_CHECKING or PY2 - result is mypy_true +from typing import TYPE_CHECKING +if TYPE_CHECKING or PY2: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_import [mypy_only] + +[case testBoolOrMypyFalseOnly] +# not MYPY or not TYPE_CHECKING - all mypy_false, result is mypy_false, else is mypy_only +from typing import TYPE_CHECKING +if not MYPY or not TYPE_CHECKING: + import runtime_only +else: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +6: import mypy_only_import [mypy_only] + +[case testBoolOrAlwaysFalseWithUnknown] +# PY2 or unknown - result is unknown, both branches reachable +if PY2 or some_var: + import maybe_reachable +else: + import also_maybe_reachable +[out] +3: import maybe_reachable +5: import also_maybe_reachable + +[case testBoolOrAllFalse] +# PY2 or not TYPE_CHECKING - all false values, result is always_false, else is unreachable +from typing import TYPE_CHECKING +if PY2 or not TYPE_CHECKING: + import runtime_only +else: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +6: import mypy_only_import + +[case testBoolComplexAndOr] +# Complex: (PY3 and TYPE_CHECKING) or PY2 - evaluates to mypy_true +from typing import TYPE_CHECKING +if (PY3 and TYPE_CHECKING) or PY2: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_import [mypy_only] + +[case testBoolMultipleOr] +# Multiple or: PY2 or PY2 or TYPE_CHECKING - result is mypy_true +from typing import TYPE_CHECKING +if PY2 or PY2 or TYPE_CHECKING: + import mypy_only_import +[out] +2: from typing import TYPE_CHECKING +4: import mypy_only_import [mypy_only] + +[case testBoolMultipleAnd] +# Multiple and: PY3 and PY3 and PY3 - result is always_true +if PY3 and PY3 and PY3: + import always_reachable +else: + import unreachable_else +[out] +3: import always_reachable + +[case testVersionInfoIndexMajor] +# sys.version_info[0] == 3 should be always true for Python 3.10 +import sys +if sys.version_info[0] == 3: + import reachable_py3 +else: + import unreachable_not_py3 +[out] +2: import sys +4: import reachable_py3 + +[case testVersionInfoIndexMajorNotEqual] +# sys.version_info[0] == 2 should be always false for Python 3.10 +import sys +if sys.version_info[0] == 2: + import unreachable_py2 +import reachable +[out] +2: import sys +5: import reachable + +[case testVersionInfoIndexMinor] +# sys.version_info[1] >= 10 should be always true for Python 3.10 +import sys +if sys.version_info[1] >= 10: + import reachable_310_plus +else: + import unreachable_pre_310 +[out] +2: import sys +4: import reachable_310_plus + +[case testVersionInfoIndexMinorLess] +# sys.version_info[1] < 10 should be always false for Python 3.10 +import sys +if sys.version_info[1] < 10: + import unreachable_pre_310 +import reachable +[out] +2: import sys +5: import reachable + +[case testVersionInfoIndexMinorGreater] +# sys.version_info[1] > 9 should be always true for Python 3.10 +import sys +if sys.version_info[1] > 9: + import reachable_310_plus +[out] +2: import sys +4: import reachable_310_plus + +[case testVersionInfoIndexLessOrEqual] +# sys.version_info[0] <= 3 should be always true for Python 3.10 +import sys +if sys.version_info[0] <= 3: + import reachable_py3_or_less +[out] +2: import sys +4: import reachable_py3_or_less + +[case testVersionInfoIndexNotEqual] +# sys.version_info[0] != 2 should be always true for Python 3.10 +import sys +if sys.version_info[0] != 2: + import reachable_not_py2 +[out] +2: import sys +4: import reachable_not_py2 + +[case testVersionInfoSliceExplicit] +# sys.version_info[:2] >= (3, 10) should be always true for Python 3.10 +import sys +if sys.version_info[:2] >= (3, 10): + import reachable_310_plus +else: + import unreachable_pre_310 +[out] +2: import sys +4: import reachable_310_plus + +[case testVersionInfoSliceExplicitBothBounds] +# sys.version_info[0:2] >= (3, 10) should be always true for Python 3.10 +import sys +if sys.version_info[0:2] >= (3, 10): + import reachable_310_plus +[out] +2: import sys +4: import reachable_310_plus + +[case testVersionInfoSliceExplicitLess] +# sys.version_info[:2] < (3, 10) should be always false for Python 3.10 +import sys +if sys.version_info[:2] < (3, 10): + import unreachable_pre_310 +import reachable +[out] +2: import sys +5: import reachable + +[case testVersionInfoSliceSingleElement] +# sys.version_info[:1] >= (3,) should be always true for Python 3.10 +import sys +if sys.version_info[:1] >= (3,): + import reachable_py3 +[out] +2: import sys +4: import reachable_py3 + +[case testVersionInfoSliceSingleElementLess] +# sys.version_info[:1] < (3,) should be always false for Python 3.10 +import sys +if sys.version_info[:1] < (3,): + import unreachable_py2 +import reachable +[out] +2: import sys +5: import reachable + +[case testVersionInfoReversedOperands] +# (3, 8) <= sys.version_info should be always true for Python 3.10 +import sys +if (3, 8) <= sys.version_info: + import reachable_38_plus +[out] +2: import sys +4: import reachable_38_plus + +[case testVersionInfoReversedGreater] +# (3, 11) > sys.version_info should be always true for Python 3.10 +import sys +if (3, 11) > sys.version_info: + import reachable_pre_311 +[out] +2: import sys +4: import reachable_pre_311 + +[case testVersionInfoReversedEquals] +# (3, 10) == sys.version_info should be always true for Python 3.10 +import sys +if (3, 10) == sys.version_info: + import reachable_exactly_310 +[out] +2: import sys +4: import reachable_exactly_310 + +[case testVersionInfoEquals] +# sys.version_info == (3, 10) should be always true for Python 3.10 +import sys +if sys.version_info == (3, 10): + import reachable_exactly_310 +else: + import unreachable_not_310 +[out] +2: import sys +4: import reachable_exactly_310 + +[case testVersionInfoNotEquals] +# sys.version_info != (3, 9) should be always true for Python 3.10 +import sys +if sys.version_info != (3, 9): + import reachable_not_39 +[out] +2: import sys +4: import reachable_not_39 + +[case testVersionInfoNotEqualsExact] +# sys.version_info != (3, 10) should be always false for Python 3.10 +import sys +if sys.version_info != (3, 10): + import unreachable_not_310 +import reachable +[out] +2: import sys +5: import reachable + +[case testVersionInfoSliceMinorOnly] +# sys.version_info[1:2] >= (10,) should be always true for Python 3.10 +import sys +if sys.version_info[1:2] >= (10,): + import reachable_minor_10_plus +[out] +2: import sys +4: import reachable_minor_10_plus + +[case testAttributeTypeChecking] +# typing.TYPE_CHECKING should be recognized as mypy_true +import typing +if typing.TYPE_CHECKING: + import mypy_only_import +[out] +2: import typing +4: import mypy_only_import [mypy_only] + +[case testAttributeMYPY] +# foo.MYPY should be recognized as mypy_true +import foo +if foo.MYPY: + import mypy_only_import +[out] +2: import foo +4: import mypy_only_import [mypy_only] + +[case testAttributePY2] +# bar.PY2 should be recognized as always_false +import bar +if bar.PY2: + import unreachable +import reachable +[out] +2: import bar +5: import reachable + +[case testAttributePY3] +# baz.PY3 should be recognized as always_true +import baz +if baz.PY3: + import always_reachable +[out] +2: import baz +4: import always_reachable + +[case testVersionInfoWithBoolAnd] +# Combining version check with TYPE_CHECKING using and +import sys +from typing import TYPE_CHECKING +if sys.version_info >= (3, 8) and TYPE_CHECKING: + import mypy_only_import +[out] +2: import sys +3: from typing import TYPE_CHECKING +5: import mypy_only_import [mypy_only] + +[case testVersionInfoWithBoolOr] +# Combining version check with PY2 using or - should be always_true +import sys +if sys.version_info >= (3, 8) or PY2: + import always_reachable +else: + import unreachable_else +[out] +2: import sys +4: import always_reachable + +[case testComplexBoolVersionMypyUnknown] +# Complex: (MYPY or unknown) evaluates to MYPY_TRUE, and version_info >= (3,8) is ALWAYS_TRUE +# ALWAYS_TRUE and MYPY_TRUE evaluates to MYPY_TRUE +import sys +if sys.version_info >= (3, 8) and (MYPY or unknown_var): + import mypy_only_import +[out] +3: import sys +5: import mypy_only_import [mypy_only] diff --git a/test-data/unit/native-parser.test b/test-data/unit/native-parser.test new file mode 100644 index 0000000000000..b9c2e3fe09390 --- /dev/null +++ b/test-data/unit/native-parser.test @@ -0,0 +1,3276 @@ +[case testHello] +print("hello") +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(print) + Args( + StrExpr(hello))))) + +[case testMemberExpr] +foo_bar.xyz2 +[out] +MypyFile:1( + ExpressionStmt:1( + MemberExpr:1( + NameExpr(foo_bar) + xyz2))) + +[case testTupleExpr] +("a", "b") +[out] +MypyFile:1( + ExpressionStmt:1( + TupleExpr:1( + StrExpr(a) + StrExpr(b)))) + +[case testOpExpr] +("x" + "y") * "z" +[out] +MypyFile:1( + ExpressionStmt:1( + OpExpr:1( + * + OpExpr:1( + + + StrExpr(x) + StrExpr(y)) + StrExpr(z)))) + +[case testIntExpr] +(0, 1, 2, 203, 5345, 50123, 1234567890, 9982374892739487239847897928374897298374) +[out] +MypyFile:1( + ExpressionStmt:1( + TupleExpr:1( + IntExpr(0) + IntExpr(1) + IntExpr(2) + IntExpr(203) + IntExpr(5345) + IntExpr(50123) + IntExpr(1234567890) + IntExpr(9982374892739487239847897928374897298374)))) + +[case testAssignmentStmt] +x = 1 +a, b = c = 6 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + IntExpr(1)) + AssignmentStmt:2( + Lvalues( + TupleExpr:2( + NameExpr(a) + NameExpr(b)) + NameExpr(c)) + IntExpr(6))) + +[case testOperatorAssignment] +x += 1 +y -= 2 +z *= 3 +[out] +MypyFile:1( + OperatorAssignmentStmt:1( + + + NameExpr(x) + IntExpr(1)) + OperatorAssignmentStmt:2( + - + NameExpr(y) + IntExpr(2)) + OperatorAssignmentStmt:3( + * + NameExpr(z) + IntExpr(3))) + +[case testIfStmt] +if x: + y +if a: + b +else: + c +[out] +MypyFile:1( + IfStmt:1( + If( + NameExpr(x)) + Then( + ExpressionStmt:2( + NameExpr(y)))) + IfStmt:3( + If( + NameExpr(a)) + Then( + ExpressionStmt:4( + NameExpr(b))) + Else( + ExpressionStmt:6( + NameExpr(c))))) + +[case testIfStmtElif] +if a: + b +elif c: + d +else: + e +[out] +MypyFile:1( + IfStmt:1( + If( + NameExpr(a)) + Then( + ExpressionStmt:2( + NameExpr(b))) + Else( + IfStmt:3( + If( + NameExpr(c)) + Then( + ExpressionStmt:4( + NameExpr(d))) + Else( + ExpressionStmt:6( + NameExpr(e))))))) + +[case testIfStmtMultipleElif] +if 1: + 2 +elif 3: + 4 +elif 5: + 6 +else: + 7 +[out] +MypyFile:1( + IfStmt:1( + If( + IntExpr(1)) + Then( + ExpressionStmt:2( + IntExpr(2))) + Else( + IfStmt:3( + If( + IntExpr(3)) + Then( + ExpressionStmt:4( + IntExpr(4))) + Else( + IfStmt:5( + If( + IntExpr(5)) + Then( + ExpressionStmt:6( + IntExpr(6))) + Else( + ExpressionStmt:8( + IntExpr(7))))))))) + +[case testIfStmtElifNoElse] +if a: + b +elif c: + d +[out] +MypyFile:1( + IfStmt:1( + If( + NameExpr(a)) + Then( + ExpressionStmt:2( + NameExpr(b))) + Else( + IfStmt:3( + If( + NameExpr(c)) + Then( + ExpressionStmt:4( + NameExpr(d))))))) + +[case testWhileStmt] +while a: + b +while c: + d +else: + e +[out] +MypyFile:1( + WhileStmt:1( + NameExpr(a) + Block:2( + ExpressionStmt:2( + NameExpr(b)))) + WhileStmt:3( + NameExpr(c) + Block:4( + ExpressionStmt:4( + NameExpr(d))) + Else( + ExpressionStmt:6( + NameExpr(e))))) + +[case testIndexExpr] +a[b] +[out] +MypyFile:1( + ExpressionStmt:1( + IndexExpr:1( + NameExpr(a) + NameExpr(b)))) + +[case testListAndSetExpr] +a = [] +b = [1, 2] +c = {3} +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(a) + ListExpr:1()) + AssignmentStmt:2( + NameExpr(b) + ListExpr:2( + IntExpr(1) + IntExpr(2))) + AssignmentStmt:3( + NameExpr(c) + SetExpr:3( + IntExpr(3)))) + +[case testBoolOpAnd] +a and b +a and b and c +[out] +MypyFile:1( + ExpressionStmt:1( + OpExpr:1( + and + NameExpr(a) + NameExpr(b))) + ExpressionStmt:2( + OpExpr:2( + and + OpExpr:2( + and + NameExpr(a) + NameExpr(b)) + NameExpr(c)))) + +[case testBoolOpOr] +x or y +1 or 2 or 3 +[out] +MypyFile:1( + ExpressionStmt:1( + OpExpr:1( + or + NameExpr(x) + NameExpr(y))) + ExpressionStmt:2( + OpExpr:2( + or + OpExpr:2( + or + IntExpr(1) + IntExpr(2)) + IntExpr(3)))) + +[case testComparisonSimple] +a < b +x == y +[out] +MypyFile:1( + ExpressionStmt:1( + ComparisonExpr:1( + < + NameExpr(a) + NameExpr(b))) + ExpressionStmt:2( + ComparisonExpr:2( + == + NameExpr(x) + NameExpr(y)))) + +[case testComparisonChained] +a < b < c +x == y != z +[out] +MypyFile:1( + ExpressionStmt:1( + ComparisonExpr:1( + < + < + NameExpr(a) + NameExpr(b) + NameExpr(c))) + ExpressionStmt:2( + ComparisonExpr:2( + == + != + NameExpr(x) + NameExpr(y) + NameExpr(z)))) + +[case testComparisonIsNotIn] +a is b +x is not y +z in w +q not in r +[out] +MypyFile:1( + ExpressionStmt:1( + ComparisonExpr:1( + is + NameExpr(a) + NameExpr(b))) + ExpressionStmt:2( + ComparisonExpr:2( + is not + NameExpr(x) + NameExpr(y))) + ExpressionStmt:3( + ComparisonExpr:3( + in + NameExpr(z) + NameExpr(w))) + ExpressionStmt:4( + ComparisonExpr:4( + not in + NameExpr(q) + NameExpr(r)))) + +[case testLiterals] +(None, True, False, ...) +[out] +MypyFile:1( + ExpressionStmt:1( + TupleExpr:1( + NameExpr(None) + NameExpr(True) + NameExpr(False) + Ellipsis))) + +[case testSimpleFunction] +def foo(): + x = 1 +[out] +MypyFile:1( + FuncDef:1( + foo + Block:2( + AssignmentStmt:2( + NameExpr(x) + IntExpr(1))))) + +[case testFunctionWithArgs] +def add(x, y): + return x + y +[out] +MypyFile:1( + FuncDef:1( + add + Args( + Var(x) + Var(y)) + Block:2( + ReturnStmt:2( + OpExpr:2( + + + NameExpr(x) + NameExpr(y)))))) + +[case testFunctionWithVarArgs] +def foo(*args): + pass +[out] +MypyFile:1( + FuncDef:1( + foo + VarArg( + Var(args)) + Block:2( + PassStmt:2()))) + +[case testFunctionWithKwargs] +def bar(**kwargs): + pass +[out] +MypyFile:1( + FuncDef:1( + bar + DictVarArg( + Var(kwargs)) + Block:2( + PassStmt:2()))) + +[case testFunctionWithKwOnly] +def baz(x, *, y): + pass +[out] +MypyFile:1( + FuncDef:1( + baz + MaxPos(1) + Args( + Var(x) + Var(y)) + Block:2( + PassStmt:2()))) + +[case testFunctionWithAllArgKinds] +def complex_fn(a, b, *args, c, **kwargs): + pass +[out] +MypyFile:1( + FuncDef:1( + complex_fn + MaxPos(2) + Args( + Var(a) + Var(b) + Var(c)) + VarArg( + Var(args)) + DictVarArg( + Var(kwargs)) + Block:2( + PassStmt:2()))) + +[case testAsyncFunction] +async def async_foo(): + pass +[out] +MypyFile:1( + FuncDef:1( + async_foo + Async + Block:2( + PassStmt:2()))) + +[case testFunctionWithDefaultArg] +def greet(name="World"): + pass +[out] +MypyFile:1( + FuncDef:1( + greet + Args( + default( + Var(name) + StrExpr(World))) + Block:2( + PassStmt:2()))) + +[case testFunctionWithMultipleDefaults] +def add(x=1, y=2): + return x +[out] +MypyFile:1( + FuncDef:1( + add + Args( + default( + Var(x) + IntExpr(1)) + default( + Var(y) + IntExpr(2))) + Block:2( + ReturnStmt:2( + NameExpr(x))))) + +[case testFunctionMixedDefaultsAndRegular] +def func(a, b=5, c=10): + pass +[out] +MypyFile:1( + FuncDef:1( + func + Args( + Var(a) + default( + Var(b) + IntExpr(5)) + default( + Var(c) + IntExpr(10))) + Block:2( + PassStmt:2()))) + +[case testFunctionWithKwOnlyDefault] +def func(*, x=42): + pass +[out] +MypyFile:1( + FuncDef:1( + func + MaxPos(0) + Args( + default( + Var(x) + IntExpr(42))) + Block:2( + PassStmt:2()))) + +[case testCallWithKeywordArgs] +foo(x=1, y=2) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(foo) + Args() + KwArgs( + x + IntExpr(1)) + KwArgs( + y + IntExpr(2))))) + +[case testCallMixedPositionalAndKeyword] +bar(1, 2, x=3, y=4) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(bar) + Args( + IntExpr(1) + IntExpr(2)) + KwArgs( + x + IntExpr(3)) + KwArgs( + y + IntExpr(4))))) + +[case testCallWithStarArgs] +f(1, *x, 2) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(f) + Args( + IntExpr(1) + NameExpr(x) + IntExpr(2)) + VarArg))) + +[case testCallWithDictStarArgs] +f(**x) +f(1, **y) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(f) + Args() + DictVarArg( + NameExpr(x)))) + ExpressionStmt:2( + CallExpr:2( + NameExpr(f) + Args( + IntExpr(1)) + DictVarArg( + NameExpr(y))))) + +[case testCallWithAllArgTypes] +f(1, *x, a=2, **y) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(f) + Args( + IntExpr(1) + NameExpr(x)) + VarArg + KwArgs( + a + IntExpr(2)) + DictVarArg( + NameExpr(y))))) + +[case testSimpleClass] +class A: + pass +[out] +MypyFile:1( + ClassDef:1( + A + PassStmt:2())) + +[case testClassWithMethod] +class Foo: + def bar(self): + return 1 +[out] +MypyFile:1( + ClassDef:1( + Foo + FuncDef:2( + bar + Args( + Var(self)) + Block:3( + ReturnStmt:3( + IntExpr(1)))))) + +[case testClassWithSingleBase] +class A(B): + pass +[out] +MypyFile:1( + ClassDef:1( + A + BaseTypeExpr( + NameExpr(B)) + PassStmt:2())) + +[case testClassWithMultipleBases] +class Child(Base1, Base2): + x = 1 +[out] +MypyFile:1( + ClassDef:1( + Child + BaseTypeExpr( + NameExpr(Base1) + NameExpr(Base2)) + AssignmentStmt:2( + NameExpr(x) + IntExpr(1)))) + +[case testFloatLiteral] +x = 3.14 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + FloatExpr(3.14))) + +[case testUnaryExpressions] +-x ++y +~z +not w +[out] +MypyFile:1( + ExpressionStmt:1( + UnaryExpr:1( + - + NameExpr(x))) + ExpressionStmt:2( + UnaryExpr:2( + + + NameExpr(y))) + ExpressionStmt:3( + UnaryExpr:3( + ~ + NameExpr(z))) + ExpressionStmt:4( + UnaryExpr:4( + not + NameExpr(w)))) + +[case testDictionaryExpression] +{} +{1:x} +[out] +MypyFile:1( + ExpressionStmt:1( + DictExpr:1()) + ExpressionStmt:2( + DictExpr:2( + IntExpr(1) + NameExpr(x)))) + +[case testComplexLiteral] +1j +2.5j +[out] +MypyFile:1( + ExpressionStmt:1( + ComplexExpr(1j)) + ExpressionStmt:2( + ComplexExpr(2.5j))) + +[case testSlices] +x[1:2] +x[:1] +x[1:] +x[:] +[out] +MypyFile:1( + ExpressionStmt:1( + IndexExpr:1( + NameExpr(x) + SliceExpr:1( + IntExpr(1) + IntExpr(2)))) + ExpressionStmt:2( + IndexExpr:2( + NameExpr(x) + SliceExpr:2( + + IntExpr(1)))) + ExpressionStmt:3( + IndexExpr:3( + NameExpr(x) + SliceExpr:3( + IntExpr(1) + ))) + ExpressionStmt:4( + IndexExpr:4( + NameExpr(x) + SliceExpr:4( + + )))) + +[case testSliceWithStride] +x[1:2:3] +x[1::2] +x[:1:2] +x[::2] +[out] +MypyFile:1( + ExpressionStmt:1( + IndexExpr:1( + NameExpr(x) + SliceExpr:1( + IntExpr(1) + IntExpr(2) + IntExpr(3)))) + ExpressionStmt:2( + IndexExpr:2( + NameExpr(x) + SliceExpr:2( + IntExpr(1) + + IntExpr(2)))) + ExpressionStmt:3( + IndexExpr:3( + NameExpr(x) + SliceExpr:3( + + IntExpr(1) + IntExpr(2)))) + ExpressionStmt:4( + IndexExpr:4( + NameExpr(x) + SliceExpr:4( + + + IntExpr(2))))) + +[case testFunctionSignature] +def f() -> int: + pass +def g() -> None: + pass +def h(x: foo.bar, y, z: x.y.z): + pass +[out] +MypyFile:1( + FuncDef:1( + f + def () -> int? + Block:2( + PassStmt:2())) + FuncDef:3( + g + def () -> None? + Block:4( + PassStmt:4())) + FuncDef:5( + h + Args( + Var(x) + Var(y) + Var(z)) + def (x: foo.bar?, y: Any, z: x.y.z?) -> Any + Block:6( + PassStmt:6()))) + +[case testFunctionSignature2] +def f(x: a[b]) -> c.d[e.f.g[h], i]: + pass +[out] +MypyFile:1( + FuncDef:1( + f + Args( + Var(x)) + def (x: a?[b?]) -> c.d?[e.f.g?[h?], i?] + Block:2( + PassStmt:2()))) + +[case testUnionTypes] +def f(x: int | str) -> bool | None: + pass +[out] +MypyFile:1( + FuncDef:1( + f + Args( + Var(x)) + def (x: int? | str?) -> bool? | None? + Block:2( + PassStmt:2()))) + +[case testAnnotatedAssignment] +x: int +y: str = "hello" +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + int?) + AssignmentStmt:2( + NameExpr(y) + StrExpr(hello) + str?)) + +[case testImportStatements] +import x +import a, b +import y as z +[out] +MypyFile:1( + Import:1(x) + Import:2(a, b) + Import:3(y : z)) + +[case testImportFromStatements] +from os import path +from sys import argv, exit +from typing import List as L +from . import foo +from .. import bar +from .pkg import baz +[out] +MypyFile:1( + ImportFrom:1(os, [path]) + ImportFrom:2(sys, [argv, exit]) + ImportFrom:3(typing, [List : L]) + ImportFrom:4(., [foo]) + ImportFrom:5(.., [bar]) + ImportFrom:6(.pkg, [baz])) + +[case testImportAllStatements] +from os import * +from . import * +from .. import * +from .pkg import * +[out] +MypyFile:1( + ImportAll:1(os) + ImportAll:2(.) + ImportAll:3(..) + ImportAll:4(.pkg)) + +[case testRaiseStatements] +raise +raise ValueError +raise ValueError("error message") +raise KeyError from e +[out] +MypyFile:1( + RaiseStmt:1() + RaiseStmt:2( + NameExpr(ValueError)) + RaiseStmt:3( + CallExpr:3( + NameExpr(ValueError) + Args( + StrExpr(error message)))) + RaiseStmt:4( + NameExpr(KeyError) + NameExpr(e))) + +[case testAssertStatements] +assert x +assert x == y +assert condition, "error message" +[out] +MypyFile:1( + AssertStmt:1( + NameExpr(x)) + AssertStmt:2( + ComparisonExpr:2( + == + NameExpr(x) + NameExpr(y))) + AssertStmt:3( + NameExpr(condition) + StrExpr(error message))) + +[case testForStatements] +for x in items: + pass +for i, j in pairs: + x = 1 +for y in range(10): + break +else: + continue +[out] +MypyFile:1( + ForStmt:1( + NameExpr(x) + NameExpr(items) + Block:2( + PassStmt:2())) + ForStmt:3( + TupleExpr:3( + NameExpr(i) + NameExpr(j)) + NameExpr(pairs) + Block:4( + AssignmentStmt:4( + NameExpr(x) + IntExpr(1)))) + ForStmt:5( + NameExpr(y) + CallExpr:5( + NameExpr(range) + Args( + IntExpr(10))) + Block:6( + BreakStmt:6()) + Else( + ContinueStmt:8()))) + +[case testWithStatements] +with f: + pass +with open("file") as f: + x = 1 +with a as x, b as y: + break +[out] +MypyFile:1( + WithStmt:1( + Expr( + NameExpr(f)) + Block:2( + PassStmt:2())) + WithStmt:3( + Expr( + CallExpr:3( + NameExpr(open) + Args( + StrExpr(file)))) + Target( + NameExpr(f)) + Block:4( + AssignmentStmt:4( + NameExpr(x) + IntExpr(1)))) + WithStmt:5( + Expr( + NameExpr(a)) + Target( + NameExpr(x)) + Expr( + NameExpr(b)) + Target( + NameExpr(y)) + Block:6( + BreakStmt:6()))) + +[case testBreakAndContinue] +while True: + break +while x: + continue +while y: + if z: + break + else: + continue +[out] +MypyFile:1( + WhileStmt:1( + NameExpr(True) + Block:2( + BreakStmt:2())) + WhileStmt:3( + NameExpr(x) + Block:4( + ContinueStmt:4())) + WhileStmt:5( + NameExpr(y) + Block:6( + IfStmt:6( + If( + NameExpr(z)) + Then( + BreakStmt:7()) + Else( + ContinueStmt:9()))))) + +[case testGeneratorExpressions] +(x for x in items) +(x * 2 for x in range(10) if x > 5) +(x + y for x in range(3) for y in range(2)) +[out] +MypyFile:1( + ExpressionStmt:1( + GeneratorExpr:1( + NameExpr(x) + NameExpr(x) + NameExpr(items))) + ExpressionStmt:2( + GeneratorExpr:2( + OpExpr:2( + * + NameExpr(x) + IntExpr(2)) + NameExpr(x) + CallExpr:2( + NameExpr(range) + Args( + IntExpr(10))) + ComparisonExpr:2( + > + NameExpr(x) + IntExpr(5)))) + ExpressionStmt:3( + GeneratorExpr:3( + OpExpr:3( + + + NameExpr(x) + NameExpr(y)) + NameExpr(x) + NameExpr(y) + CallExpr:3( + NameExpr(range) + Args( + IntExpr(3))) + CallExpr:3( + NameExpr(range) + Args( + IntExpr(2)))))) + +[case testYieldExpressions] +def gen(): + yield + yield 1 + yield from items +[out] +MypyFile:1( + FuncDef:1( + gen + Block:2( + ExpressionStmt:2( + YieldExpr:2()) + ExpressionStmt:3( + YieldExpr:3( + IntExpr(1))) + ExpressionStmt:4( + YieldFromExpr:4( + NameExpr(items)))))) + +[case testListAndSetComprehensions] +a = [x for x in items] +b = [x * 2 for x in range(10) if x > 5] +c = {y for y in data} +d = {z + 1 for z in nums if z < 100} +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(a) + ListComprehension:1( + GeneratorExpr:1( + NameExpr(x) + NameExpr(x) + NameExpr(items)))) + AssignmentStmt:2( + NameExpr(b) + ListComprehension:2( + GeneratorExpr:2( + OpExpr:2( + * + NameExpr(x) + IntExpr(2)) + NameExpr(x) + CallExpr:2( + NameExpr(range) + Args( + IntExpr(10))) + ComparisonExpr:2( + > + NameExpr(x) + IntExpr(5))))) + AssignmentStmt:3( + NameExpr(c) + SetComprehension:3( + GeneratorExpr:3( + NameExpr(y) + NameExpr(y) + NameExpr(data)))) + AssignmentStmt:4( + NameExpr(d) + SetComprehension:4( + GeneratorExpr:4( + OpExpr:4( + + + NameExpr(z) + IntExpr(1)) + NameExpr(z) + NameExpr(nums) + ComparisonExpr:4( + < + NameExpr(z) + IntExpr(100)))))) + +[case testDictComprehensions] +a = {k: v for k, v in items} +b = {x: x * 2 for x in range(10) if x > 5} +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(a) + DictionaryComprehension:1( + NameExpr(k) + NameExpr(v) + TupleExpr:1( + NameExpr(k) + NameExpr(v)) + NameExpr(items))) + AssignmentStmt:2( + NameExpr(b) + DictionaryComprehension:2( + NameExpr(x) + OpExpr:2( + * + NameExpr(x) + IntExpr(2)) + NameExpr(x) + CallExpr:2( + NameExpr(range) + Args( + IntExpr(10))) + ComparisonExpr:2( + > + NameExpr(x) + IntExpr(5))))) + +[case testDecoratedFuncDef] +@dec +def foo(): + pass +class C: + @dec() + @staticmethod + def foo(): + pass +[out] +MypyFile:1( + Decorator:1( + Var(foo) + NameExpr(dec) + FuncDef:2( + foo + Block:3( + PassStmt:3()))) + ClassDef:4( + C + Decorator:5( + Var(foo) + CallExpr:5( + NameExpr(dec) + Args()) + NameExpr(staticmethod) + FuncDef:7( + foo + Block:8( + PassStmt:8()))))) +[case testTryFinally] +try: + x +finally: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + Finally( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryExcept] +try: + x +except Exception: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + NameExpr(Exception) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryExceptWithVar] +try: + x +except Exception as e: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + NameExpr(Exception) + NameExpr(e) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryExceptElse] +try: + x +except Exception: + y +else: + z +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + NameExpr(Exception) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + Else( + ExpressionStmt:6( + NameExpr(z))))) + +[case testTryMultipleExcept] +try: + x +except ValueError: + y +except KeyError as e: + z +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + NameExpr(KeyError) + NameExpr(e) + Block:6( + ExpressionStmt:6( + NameExpr(z))))) + +[case testTryExceptStar] +try: + x +except* ValueError: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryExceptStarWithVar] +try: + x +except* Exception as e: + y +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(Exception) + NameExpr(e) + Block:4( + ExpressionStmt:4( + NameExpr(y))))) + +[case testTryMultipleExceptStar] +try: + x +except* ValueError: + y +except* KeyError as e: + z +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + NameExpr(KeyError) + NameExpr(e) + Block:6( + ExpressionStmt:6( + NameExpr(z))))) + +[case testTryExceptStarElseFinally] +try: + x +except* ValueError: + y +else: + z +finally: + w +[out] +MypyFile:1( + TryStmt:1( + Block:2( + ExpressionStmt:2( + NameExpr(x))) + * + NameExpr(ValueError) + Block:4( + ExpressionStmt:4( + NameExpr(y))) + Else( + ExpressionStmt:6( + NameExpr(z))) + Finally( + ExpressionStmt:8( + NameExpr(w))))) + +[case testConditionalExprSimple] +x if y else z +[out] +MypyFile:1( + ExpressionStmt:1( + ConditionalExpr:1( + Condition( + NameExpr(y)) + NameExpr(x) + NameExpr(z)))) + +[case testFunctionWithCallableType] +def f(x: Callable[[int, str], None]) -> None: + pass +[out] +MypyFile:1( + FuncDef:1( + f + Args( + Var(x)) + def (x: Callable?[, None?]) -> None? + Block:2( + PassStmt:2()))) + +[case testFunctionWithEmptyCallableType] +def g(callback: Callable[[], bool]) -> int: + return 1 +[out] +MypyFile:1( + FuncDef:1( + g + Args( + Var(callback)) + def (callback: Callable?[, bool?]) -> int? + Block:2( + ReturnStmt:2( + IntExpr(1))))) + +[case testFunctionWithComplexCallableType] +def h(f: Callable[[List[int], Dict[str, int]], Tuple[int, str]]) -> None: + pass +[out] +MypyFile:1( + FuncDef:1( + h + Args( + Var(f)) + def (f: Callable?[, Tuple?[int?, str?]]) -> None? + Block:2( + PassStmt:2()))) + +[case testFunctionWithEllipsisCallableType] +def f(callback: Callable[..., int]) -> None: + pass +[out] +MypyFile:1( + FuncDef:1( + f + Args( + Var(callback)) + def (callback: Callable?[..., int?]) -> None? + Block:2( + PassStmt:2()))) + +[case testCallableWithArgConstructor] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, 'x'), Arg(str, 'y')], bool]) -> None: + pass +[out] +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testCallableWithArgConstructorKeywordSyntax] +from typing import Callable +from mypy_extensions import Arg +def f(callback: Callable[[Arg(type=int, name='value')], str]) -> None: + pass +[out] +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(callback)) + def (callback: Callable?[, str?]) -> None? + Block:4( + PassStmt:4()))) + +[case testCallableWithArgNoName] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int)], bool]) -> None: + pass +[out] +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testLiteralTypes] +x: Literal[True, False] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + Literal?[True, False])) + +[case testLiteralIntTypes] +x: Literal[1] +y: Literal[1, 2, 3] +z: Literal[-1] +w: Literal[-1, 0, 1] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + Literal?[1]) + AssignmentStmt:2( + NameExpr(y) + TempNode:2( + Any) + Literal?[1, 2, 3]) + AssignmentStmt:3( + NameExpr(z) + TempNode:3( + Any) + Literal?[-1]) + AssignmentStmt:4( + NameExpr(w) + TempNode:4( + Any) + Literal?[-1, 0, 1])) + +[case testDelStmt] +del x +del x[0], y[1] +del a.b +[out] +MypyFile:1( + DelStmt:1( + NameExpr(x)) + DelStmt:2( + TupleExpr:2( + IndexExpr:2( + NameExpr(x) + IntExpr(0)) + IndexExpr:2( + NameExpr(y) + IntExpr(1)))) + DelStmt:3( + MemberExpr:3( + NameExpr(a) + b))) + +[case testFString] +f"foo {x}" +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + MemberExpr:1( + StrExpr() + join) + Args( + ListExpr:1( + StrExpr(foo ) + CallExpr:1( + MemberExpr:1( + StrExpr({:{}}) + format) + Args( + NameExpr(x) + StrExpr()))))))) + +[case testFStringWithConversion] +x = 'mypy' +F'Hello {x!r}' +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + StrExpr(mypy)) + ExpressionStmt:2( + CallExpr:2( + MemberExpr:2( + StrExpr() + join) + Args( + ListExpr:2( + StrExpr(Hello ) + CallExpr:2( + MemberExpr:2( + StrExpr({!r:{}}) + format) + Args( + NameExpr(x) + StrExpr()))))))) + +[case testFStringTrivial] +f"foo" +f"{x}" +f"foo" "bar" +[out] +MypyFile:1( + ExpressionStmt:1( + StrExpr(foo)) + ExpressionStmt:2( + CallExpr:2( + MemberExpr:2( + StrExpr({:{}}) + format) + Args( + NameExpr(x) + StrExpr()))) + ExpressionStmt:3( + StrExpr(foobar))) + +[case testFStringWithOnlyFormatSpecifier] +x = 'mypy' +f'Hello {x:<30}' +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + StrExpr(mypy)) + ExpressionStmt:2( + CallExpr:2( + MemberExpr:2( + StrExpr() + join) + Args( + ListExpr:2( + StrExpr(Hello ) + CallExpr:2( + MemberExpr:2( + StrExpr({:{}}) + format) + Args( + NameExpr(x) + StrExpr(<30)))))))) + +[case testFStringWithFormatSpecifierExpression] +x = 'mypy' +y = 30 +f'Hello {x!s:<{y+y}}' +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + StrExpr(mypy)) + AssignmentStmt:2( + NameExpr(y) + IntExpr(30)) + ExpressionStmt:3( + CallExpr:3( + MemberExpr:3( + StrExpr() + join) + Args( + ListExpr:3( + StrExpr(Hello ) + CallExpr:3( + MemberExpr:3( + StrExpr({!s:{}}) + format) + Args( + NameExpr(x) + CallExpr:3( + MemberExpr:3( + StrExpr() + join) + Args( + ListExpr:3( + StrExpr(<) + CallExpr:3( + MemberExpr:3( + StrExpr({:{}}) + format) + Args( + OpExpr:3( + + + NameExpr(y) + NameExpr(y)) + StrExpr())))))))))))) + +[case testSimpleLambda] +lambda: 1 +[out] +MypyFile:1( + ExpressionStmt:1( + LambdaExpr:1( + Block:1( + ReturnStmt:1( + IntExpr(1)))))) + +[case testLambdaWithOneArg] +lambda x: y + 1 +[out] +MypyFile:1( + ExpressionStmt:1( + LambdaExpr:1( + Args( + Var(x)) + Block:1( + ReturnStmt:1( + OpExpr:1( + + + NameExpr(y) + IntExpr(1))))))) + +[case testLambdaWithMultipleArgs] +lambda x, y: x + y +[out] +MypyFile:1( + ExpressionStmt:1( + LambdaExpr:1( + Args( + Var(x) + Var(y)) + Block:1( + ReturnStmt:1( + OpExpr:1( + + + NameExpr(x) + NameExpr(y))))))) + +[case testLambdaWithDefault] +lambda x=2: x +[out] +MypyFile:1( + ExpressionStmt:1( + LambdaExpr:1( + Args( + default( + Var(x) + IntExpr(2))) + Block:1( + ReturnStmt:1( + NameExpr(x)))))) + +[case testLambdaInCall] +map(lambda x: x * 2, items) +[out] +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(map) + Args( + LambdaExpr:1( + Args( + Var(x)) + Block:1( + ReturnStmt:1( + OpExpr:1( + * + NameExpr(x) + IntExpr(2))))) + NameExpr(items))))) + +[case testNamedExprSimple] +if (x := 1): + pass +[out] +MypyFile:1( + IfStmt:1( + If( + AssignmentExpr:1( + NameExpr(x) + IntExpr(1))) + Then( + PassStmt:2()))) + +[case testStarExpression] +*a +*a, b +a, *b +[out] +MypyFile:1( + ExpressionStmt:1( + StarExpr:1( + NameExpr(a))) + ExpressionStmt:2( + TupleExpr:2( + StarExpr:2( + NameExpr(a)) + NameExpr(b))) + ExpressionStmt:3( + TupleExpr:3( + NameExpr(a) + StarExpr:3( + NameExpr(b))))) + +[case testBytesLiteral] +b"foo" +b"foobar" +b"x\\n\\'" +[out] +MypyFile:1( + ExpressionStmt:1( + BytesExpr(foo)) + ExpressionStmt:2( + BytesExpr(foobar)) + ExpressionStmt:3( + BytesExpr(x\\n\\\'))) + +[case testBytesLiteralWithEscapes] +b'\r\n\t\x2f\xbe' +[out] +MypyFile:1( + ExpressionStmt:1( + BytesExpr(\r\n\t/\xbe))) + +[case testBytesLiteralOctalEscapes] +b'\1\476' +[out] +MypyFile:1( + ExpressionStmt:1( + BytesExpr(\x01>))) +[case testGlobalAndNonlocal] +x = 1 +y = 2 +def outer(): + global x + def inner(): + nonlocal y + global z + y = 3 + z = 4 + x = 5 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + IntExpr(1)) + AssignmentStmt:2( + NameExpr(y) + IntExpr(2)) + FuncDef:3( + outer + Block:4( + GlobalDecl:4( + x) + FuncDef:5( + inner + Block:6( + NonlocalDecl:6( + y) + GlobalDecl:7( + z) + AssignmentStmt:8( + NameExpr(y) + IntExpr(3)) + AssignmentStmt:9( + NameExpr(z) + IntExpr(4)))) + AssignmentStmt:10( + NameExpr(x) + IntExpr(5))))) +[case testAwaitExpression] +async def foo(): + x = await bar() + return await baz(x) +[out] +MypyFile:1( + FuncDef:1( + foo + Async + Block:2( + AssignmentStmt:2( + NameExpr(x) + AwaitExpr:2( + CallExpr:2( + NameExpr(bar) + Args()))) + ReturnStmt:3( + AwaitExpr:3( + CallExpr:3( + NameExpr(baz) + Args( + NameExpr(x)))))))) + +[case testFunctionOverload] +@overload +def f() -> x: pass +@overload +def f() -> y: pass +[out] +MypyFile:1( + OverloadedFuncDef:1( + Decorator:1( + Var(f) + NameExpr(overload) + FuncDef:2( + f + def () -> x? + Block:2( + PassStmt:2()))) + Decorator:3( + Var(f) + NameExpr(overload) + FuncDef:4( + f + def () -> y? + Block:4( + PassStmt:4()))))) + +[case testFunctionOverloadWithImplementation] +@overload +def f(a: x) -> x: ... +@overload +def f(a: y) -> y: ... +def f(a): pass +[out] +MypyFile:1( + OverloadedFuncDef:1( + Decorator:1( + Var(f) + NameExpr(overload) + FuncDef:2( + f + Args( + Var(a)) + def (a: x?) -> x? + Block:2( + ExpressionStmt:2( + Ellipsis)))) + Decorator:3( + Var(f) + NameExpr(overload) + FuncDef:4( + f + Args( + Var(a)) + def (a: y?) -> y? + Block:4( + ExpressionStmt:4( + Ellipsis)))) + FuncDef:5( + f + Args( + Var(a)) + Block:5( + PassStmt:5())))) + +[case testTypeIgnores] +x = 1 # type: ignore +y = ( + 2 # type: ignore # Comment +) +y = 1 # foo: ignore +z = 1 #type: ignore[x] +zz = 1 #type: ignore [ foo, arg-type ] +[out] +ignore: 1 +ignore: 3 +ignore: 6 [x] +ignore: 7 [foo, arg-type] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + IntExpr(1)) + AssignmentStmt:2( + NameExpr(y) + IntExpr(2)) + AssignmentStmt:5( + NameExpr(y) + IntExpr(1)) + AssignmentStmt:6( + NameExpr(z) + IntExpr(1)) + AssignmentStmt:7( + NameExpr(zz) + IntExpr(1))) + +[case testMetaclass] +class Foo(metaclass=Bar): pass +[out] +MypyFile:1( + ClassDef:1( + Foo + Metaclass(NameExpr(Bar)) + PassStmt:1())) + +[case testClassWithKeywordArgs] +class Foo(_root=None, total=False): pass +[out] +MypyFile:1( + ClassDef:1( + Foo + Keywords(_root=NameExpr(None), total=NameExpr(False)) + PassStmt:1())) + +[case testClassDecorator] +@foo +class X: pass +@foo(bar) +@x.y +class Z: pass +[out] +MypyFile:1( + ClassDef:2( + X + Decorators( + NameExpr(foo)) + PassStmt:2()) + ClassDef:5( + Z + Decorators( + CallExpr:3( + NameExpr(foo) + Args( + NameExpr(bar))) + MemberExpr:4( + NameExpr(x) + y)) + PassStmt:5())) + +[case testUnpackTypeInSignature] +def f(*args: *Ts) -> None: + pass +[out] +MypyFile:1( + FuncDef:1( + f + def (*args: Unpack[Ts?]) -> None? + VarArg( + Var(args)) + Block:2( + PassStmt:2()))) + +[case testUnpackTypeInTuple] +x: tuple[int, *Ts, str] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + tuple?[int?, Unpack[Ts?], str?])) + + +[case testLiteralStringType] +from typing import Literal +x: Literal["hello"] +[out] +MypyFile:1( + ImportFrom:1(typing, [Literal]) + AssignmentStmt:2( + NameExpr(x) + TempNode:2( + Any) + Literal?[hello?])) + +[case testLiteralStringWithEscapes] +from typing import Literal +x: Literal["hello\nworld"] +[out] +MypyFile:1( + ImportFrom:1(typing, [Literal]) + AssignmentStmt:2( + NameExpr(x) + TempNode:2( + Any) + Literal?['hello\nworld'])) + +[case testForwardReference] +x: "int" +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + int?)) + +[case testForwardReferenceWithUnion] +x: "int | str" +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + int? | str?)) + +[case testForwardReferenceWithComplexType] +x: "list[int]" +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + list?[int?])) + +[case testMultipleLiteralStrings] +from typing import Literal +x: Literal["a", "b", "c"] +[out] +MypyFile:1( + ImportFrom:1(typing, [Literal]) + AssignmentStmt:2( + NameExpr(x) + TempNode:2( + Any) + Literal?[a?, b?, c?])) + +[case testLiteralBytesType] +from typing import Literal +x: Literal[b"foo"] +y: Literal[b"hello\nworld", b"\x00\xff"] +[out] +MypyFile:1( + ImportFrom:1(typing, [Literal]) + AssignmentStmt:2( + NameExpr(x) + TempNode:2( + Any) + Literal?[b'foo']) + AssignmentStmt:3( + NameExpr(y) + TempNode:3( + Any) + Literal?[b'hello\nworld', b'\x00\xff'])) + +[case testStripFunctionBodiesIfIgnoringErrors] +# mypy: ignore-errors=True +def f(self): + self.x = 1 # Cannot define an attribute + return 1 +[out] +MypyFile:1( + FuncDef:2( + f + Args( + Var(self)) + Block:3())) + +[case testStripMethodBodiesIfIgnoringErrors] +# mypy: ignore-errors=True +class C: + def f(self): + x = self.x + for x in y: + pass + with a as y: + pass + while self.foo(): + self.bah() + a[self.x] = 1 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + f + Args( + Var(self)) + Block:4()))) + +[case testDoNotStripModuleTopLevelOrClassBody] +# mypy: ignore-errors=True +f() +class C: + x = 5 +[out] +MypyFile:1( + ExpressionStmt:2( + CallExpr:2( + NameExpr(f) + Args())) + ClassDef:3( + C + AssignmentStmt:4( + NameExpr(x) + IntExpr(5)))) + +[case testDoNotStripMethodThatAssignsToAttribute] +# mypy: ignore-errors=True +class C: + def m1(self): + self.x = 0 + def m2(self): + a, self.y = 0 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + AssignmentStmt:4( + MemberExpr:4( + NameExpr(self) + x) + IntExpr(0)))) + FuncDef:5( + m2 + Args( + Var(self)) + Block:6( + AssignmentStmt:6( + TupleExpr:6( + NameExpr(a) + MemberExpr:6( + NameExpr(self) + y)) + IntExpr(0)))))) + +[case testDoNotStripMethodThatAssignsToAttributeWithinStatement] +# mypy: ignore-errors=True +class C: + def m1(self): + for x in y: + self.x = 0 + def m2(self): + with x: + self.y = 0 + def m3(self): + if x: + self.y = 0 + else: + x = 4 +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + ForStmt:4( + NameExpr(x) + NameExpr(y) + Block:5( + AssignmentStmt:5( + MemberExpr:5( + NameExpr(self) + x) + IntExpr(0)))))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7( + WithStmt:7( + Expr( + NameExpr(x)) + Block:8( + AssignmentStmt:8( + MemberExpr:8( + NameExpr(self) + y) + IntExpr(0)))))) + FuncDef:9( + m3 + Args( + Var(self)) + Block:10( + IfStmt:10( + If( + NameExpr(x)) + Then( + AssignmentStmt:11( + MemberExpr:11( + NameExpr(self) + y) + IntExpr(0))) + Else( + AssignmentStmt:13( + NameExpr(x) + IntExpr(4)))))))) + +[case testDoNotStripMethodThatDefinesAttributeWithoutAssignment] +# mypy: ignore-errors=True +class C: + def m1(self): + with y as self.x: + pass + def m2(self): + for self.y in x: + pass +[out] +MypyFile:1( + ClassDef:2( + C + FuncDef:3( + m1 + Args( + Var(self)) + Block:4( + WithStmt:4( + Expr( + NameExpr(y)) + Target( + MemberExpr:4( + NameExpr(self) + x)) + Block:5( + PassStmt:5())))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7( + ForStmt:7( + MemberExpr:7( + NameExpr(self) + y) + NameExpr(x) + Block:8( + PassStmt:8())))))) + +[case testStripDecoratedFunctionOrMethod] +# mypy: ignore-errors=True +@deco +def f(): + x = 0 + +class C: + @deco + def m1(self): + x = 0 + + @deco + def m2(self): + self.x = 0 +[out] +MypyFile:1( + Decorator:2( + Var(f) + NameExpr(deco) + FuncDef:3( + f + Block:4())) + ClassDef:6( + C + Decorator:7( + Var(m1) + NameExpr(deco) + FuncDef:8( + m1 + Args( + Var(self)) + Block:9())) + Decorator:11( + Var(m2) + NameExpr(deco) + FuncDef:12( + m2 + Args( + Var(self)) + Block:13( + AssignmentStmt:13( + MemberExpr:13( + NameExpr(self) + x) + IntExpr(0))))))) + +[case testStripOverloadedMethod] +# mypy: ignore-errors=True +class C: + @overload + def m1(self, x: int) -> None: ... + @overload + def m1(self, x: str) -> None: ... + def m1(self, x): + x = 0 + + @overload + def m2(self, x: int) -> None: ... + @overload + def m2(self, x: str) -> None: ... + def m2(self, x): + self.x = 0 +[out] +MypyFile:1( + ClassDef:2( + C + OverloadedFuncDef:3( + Decorator:3( + Var(m1) + NameExpr(overload) + FuncDef:4( + m1 + Args( + Var(self) + Var(x)) + def (self: Any, x: int?) -> None? + Block:4( + ExpressionStmt:4( + Ellipsis)))) + Decorator:5( + Var(m1) + NameExpr(overload) + FuncDef:6( + m1 + Args( + Var(self) + Var(x)) + def (self: Any, x: str?) -> None? + Block:6( + ExpressionStmt:6( + Ellipsis)))) + FuncDef:7( + m1 + Args( + Var(self) + Var(x)) + Block:8())) + OverloadedFuncDef:10( + Decorator:10( + Var(m2) + NameExpr(overload) + FuncDef:11( + m2 + Args( + Var(self) + Var(x)) + def (self: Any, x: int?) -> None? + Block:11( + ExpressionStmt:11( + Ellipsis)))) + Decorator:12( + Var(m2) + NameExpr(overload) + FuncDef:13( + m2 + Args( + Var(self) + Var(x)) + def (self: Any, x: str?) -> None? + Block:13( + ExpressionStmt:13( + Ellipsis)))) + FuncDef:14( + m2 + Args( + Var(self) + Var(x)) + Block:15( + AssignmentStmt:15( + MemberExpr:15( + NameExpr(self) + x) + IntExpr(0))))))) + +[case testStripMethodInNestedClass] +# mypy: ignore-errors=True +class C: + class D: + def m1(self): + self.x = 1 + def m2(self): + return self.x +[out] +MypyFile:1( + ClassDef:2( + C + ClassDef:3( + D + FuncDef:4( + m1 + Args( + Var(self)) + Block:5( + AssignmentStmt:5( + MemberExpr:5( + NameExpr(self) + x) + IntExpr(1)))) + FuncDef:6( + m2 + Args( + Var(self)) + Block:7())))) + +[case testSuperExpr] +super().x +[out] +MypyFile:1( + ExpressionStmt:1( + SuperExpr:1( + x + CallExpr:1( + NameExpr(super) + Args())))) + +[case testTypeCommentSimple] +x = [] # type: list[int] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + ListExpr:1() + list?[int?])) + +[case testTypeCommentMultiple] +x = [] # type: list[int] +y = {} # type: dict[str, int] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + ListExpr:1() + list?[int?]) + AssignmentStmt:2( + NameExpr(y) + DictExpr:2() + dict?[str?, int?])) + +[case testTypeCommentWithTrailingComment] +x = [] # type: list[str] # This is a comment +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + ListExpr:1() + list?[str?])) + +[case testTypeCommentUnion] +x = None # type: str | None +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + NameExpr(None) + str? | None?)) + +[case testNestedListAssignment] +a1, [b1, c1] = a2, [b2, c2] +[out] +MypyFile:1( + AssignmentStmt:1( + TupleExpr:1( + NameExpr(a1) + TupleExpr:1( + NameExpr(b1) + NameExpr(c1))) + TupleExpr:1( + NameExpr(a2) + ListExpr:1( + NameExpr(b2) + NameExpr(c2))))) + +[case testSyntaxError1] +x = [1 2] +y = 2 +[out] +1:8: error: Expected ',', found int +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + ListExpr:1( + IntExpr(1) + IntExpr(2))) + AssignmentStmt:2( + NameExpr(y) + IntExpr(2))) + +[case testSyntaxError2] +f(a.) +[out] +1:5: error: Expected an identifier +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(f) + Args( + MemberExpr:1( + NameExpr(a) + ))))) + +[case testSyntaxError3] +f(a[]) +[out] +1:5: error: Expected index or slice expression +MypyFile:1( + ExpressionStmt:1( + CallExpr:1( + NameExpr(f) + Args( + IndexExpr:1( + NameExpr(a) + NameExpr()))))) + +[case testSyntaxError4] +from m import +1 +[out] +1:14: error: Expected one or more symbol names after import +MypyFile:1( + ImportFrom:1(m, []) + ExpressionStmt:2( + IntExpr(1))) + +[case testSyntaxError5] +from m +1 +[out] +1:7: error: Expected 'import', found newline +MypyFile:1( + ImportFrom:1(m, []) + ExpressionStmt:2( + IntExpr(1))) + +[case testSyntaxError6] +from m +1 +[out] +1:7: error: Expected 'import', found newline +MypyFile:1( + ImportFrom:1(m, []) + ExpressionStmt:2( + IntExpr(1))) + +[case testSyntaxError7] +def f(): + g( + h() + +def g(): ... +[out] +3:8: error: Expected ')', found newline +MypyFile:1( + FuncDef:1( + f + Block:2( + ExpressionStmt:2( + CallExpr:2( + NameExpr(g) + Args( + CallExpr:3( + NameExpr(h) + Args())))))) + FuncDef:5( + g + Block:5( + ExpressionStmt:5( + Ellipsis)))) + +[case testSyntaxError8] +def f( + x, + y + +def g(): ... +[out] +3:6: error: Expected ')', found newline +5:1: error: Expected an indented block after function definition +MypyFile:1( + FuncDef:1( + f + Args( + Var(x) + Var(y)) + Block:1()) + FuncDef:5( + g + Block:5( + ExpressionStmt:5( + Ellipsis)))) + +[case testSyntaxError9] +class A + +def f(): pass +[out] +1:8: error: Expected ':', found newline +3:1: error: Expected an indented block after `class` definition +MypyFile:1( + ClassDef:1( + A) + FuncDef:3( + f + Block:3( + PassStmt:3()))) + +[case testArgConstructorTooManyArgs] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, 'x', 'extra')], bool]) -> None: + pass +[out] +3:19: error: Too many arguments for argument constructor +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testArgConstructorMultipleValuesForName] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, 'x', name='y')], bool]) -> None: + pass +[out] +3:19: error: "Arg" gets multiple values for keyword argument "name" +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testArgConstructorMultipleValuesForType] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, type=str)], bool]) -> None: + pass +[out] +3:19: error: "Arg" gets multiple values for keyword argument "type" +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testArgConstructorUnexpectedArg] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, foo='bar')], bool]) -> None: + pass +[out] +3:19: error: Unexpected argument "foo" for argument constructor +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testArgConstructorMultipleErrors] +from typing import Callable +from mypy_extensions import Arg +def f(x: Callable[[Arg(int, 'x', 'extra', foo='bar', name='y')], bool]) -> None: + pass +[out] +3:19: error: Too many arguments for argument constructor +3:19: error: Unexpected argument "foo" for argument constructor +3:19: error: "Arg" gets multiple values for keyword argument "name" +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, bool?]) -> None? + Block:4( + PassStmt:4()))) + +[case testArgConstructorValidSyntax] +from typing import Callable +from mypy_extensions import Arg, DefaultArg, VarArg, KwArg +def f(x: Callable[[Arg(int, 'x'), DefaultArg(str, 'y'), VarArg(bool), KwArg(float)], None]) -> None: + pass +[out] +MypyFile:1( + ImportFrom:1(typing, [Callable]) + ImportFrom:2(mypy_extensions, [Arg, DefaultArg, VarArg, KwArg]) + FuncDef:3( + f + Args( + Var(x)) + def (x: Callable?[, None?]) -> None? + Block:4( + PassStmt:4()))) +[case testInvalidTypeFloatLiteral] +x: 3.14 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + None)) + +[case testInvalidTypeComplexLiteral] +y: 1j +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(y) + TempNode:1( + Any) + None)) + +[case testInvalidTypeBinaryOperatorAdd] +z: int + str +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(z) + TempNode:1( + Any) + None)) + +[case testInvalidTypeBinaryOperatorMul] +a: int * str +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(a) + TempNode:1( + Any) + None)) + +[case testInvalidTypeUnaryOperatorNot] +b: not int +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(b) + TempNode:1( + Any) + None)) + +[case testInvalidTypeUnaryOperatorInvert] +c: ~int +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(c) + TempNode:1( + Any) + None)) + +[case testInvalidTypeSetExpression] +f: {1, 2, 3} +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(f) + TempNode:1( + Any) + None)) + +[case testInvalidTypeListComprehension] +g: [x for x in range(10)] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(g) + TempNode:1( + Any) + None)) + +[case testInvalidTypeLambda] +h: lambda x: x +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(h) + TempNode:1( + Any) + None)) + +[case testInvalidTypeIfExpression] +i: int if True else str +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(i) + TempNode:1( + Any) + None)) + +[case testInvalidTypeComparisonExpression] +j: int < str +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(j) + TempNode:1( + Any) + None)) + +[case testInvalidTypePositiveUnaryOp] +k: +5 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(k) + TempNode:1( + Any) + 5)) + +[case testConditionalOverload1] +@overload +def foo(x: int): ... +if sys.version_info[0] > 2: + @overload + def foo(x: str): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:3( + If( + ComparisonExpr:3( + > + IndexExpr:3( + MemberExpr:3( + NameExpr(sys) + version_info) + IntExpr(0)) + IntExpr(2))) + Then() + Else()) + OverloadedFuncDef:1( + Decorator:1( + Var(foo) + NameExpr(overload) + FuncDef:2( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:2( + ExpressionStmt:2( + Ellipsis)))) + Decorator:4( + Var(foo) + NameExpr(overload) + FuncDef:5( + foo + Args( + Var(x)) + def (x: str?) -> Any + Block:5( + ExpressionStmt:5( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + +[case testConditionalOverload2] +@overload +def foo(x: int): ... +if sys.platform == "foo": + @overload + def foo(x: str): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:3( + If( + ComparisonExpr:3( + == + MemberExpr:3( + NameExpr(sys) + platform) + StrExpr(foo))) + Then()) + OverloadedFuncDef:1( + Decorator:1( + Var(foo) + NameExpr(overload) + FuncDef:2( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:2( + ExpressionStmt:2( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + +[case testConditionalOverload3] +if sys.version_info[0] > 2: + @overload + def foo(x: str): ... +@overload +def foo(x: int): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:1( + If( + ComparisonExpr:1( + > + IndexExpr:1( + MemberExpr:1( + NameExpr(sys) + version_info) + IntExpr(0)) + IntExpr(2))) + Then() + Else()) + OverloadedFuncDef:2( + Decorator:2( + Var(foo) + NameExpr(overload) + FuncDef:3( + foo + Args( + Var(x)) + def (x: str?) -> Any + Block:3( + ExpressionStmt:3( + Ellipsis)))) + Decorator:4( + Var(foo) + NameExpr(overload) + FuncDef:5( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:5( + ExpressionStmt:5( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + +[case testMatchStmt] +match x: + case foo(y): + pass + case _: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + ClassPattern:2( + NameExpr(foo) + Positionals( + AsPattern:2( + NameExpr(y))))) + Body( + PassStmt:3()) + Pattern( + AsPattern:4()) + Body( + PassStmt:5()))) + +[case testMatchLiteralPatterns] +match x: + case 1: + pass + case "hello": + pass + case True: + pass + case None: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + ValuePattern:2( + IntExpr(1))) + Body( + PassStmt:3()) + Pattern( + ValuePattern:4( + StrExpr(hello))) + Body( + PassStmt:5()) + Pattern( + SingletonPattern:6( + True)) + Body( + PassStmt:7()) + Pattern( + SingletonPattern:8()) + Body( + PassStmt:9()))) + +[case testMatchOrPattern] +match x: + case 1 | 2 | 3: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + OrPattern:2( + ValuePattern:2( + IntExpr(1)) + ValuePattern:2( + IntExpr(2)) + ValuePattern:2( + IntExpr(3)))) + Body( + PassStmt:3()))) + +[case testMatchSequencePattern] +match x: + case [a, *rest, b]: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + SequencePattern:2( + AsPattern:2( + NameExpr(a)) + StarredPattern:2( + NameExpr(rest)) + AsPattern:2( + NameExpr(b)))) + Body( + PassStmt:3()))) + +[case testMatchMappingPattern] +match x: + case {"key": value, "x": y}: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + MappingPattern:2( + Key( + StrExpr(key)) + Value( + AsPattern:2( + NameExpr(value))) + Key( + StrExpr(x)) + Value( + AsPattern:2( + NameExpr(y))))) + Body( + PassStmt:3()))) + +[case testMatchGuard] +match x: + case y if y > 0: + pass +[out] +MypyFile:1( + MatchStmt:1( + NameExpr(x) + Pattern( + AsPattern:2( + NameExpr(y))) + Guard( + ComparisonExpr:2( + > + NameExpr(y) + IntExpr(0))) + Body( + PassStmt:3()))) + +[case testPEP695TypeAlias] +# comment +type A[T] = C[T] +[out] +MypyFile:1( + TypeAliasStmt:2( + NameExpr(A) + TypeParam( + T) + LambdaExpr:2( + Block:-1( + ReturnStmt:2( + IndexExpr:2( + NameExpr(C) + NameExpr(T))))))) + +[case testPEP695GenericFunction] +# comment + +def f[T](): pass +def g[T: str](): pass +def h[T: (int, str)](): pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + T) + Block:3( + PassStmt:3())) + FuncDef:4( + g + TypeParam( + T + str?) + Block:4( + PassStmt:4())) + FuncDef:5( + h + TypeParam( + T + Values( + int? + str?)) + Block:5( + PassStmt:5()))) + +[case testPEP695ParamSpec] +# comment + +def f[**P](): pass +class C[T: int, **P]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + **P) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + **P) + PassStmt:4())) + +[case testPEP695TypeVarTuple] +# comment + +def f[*Ts](): pass +class C[T: int, *Ts]: pass +[out] +MypyFile:1( + FuncDef:3( + f + TypeParam( + *Ts) + Block:3( + PassStmt:3())) + ClassDef:4( + C + TypeParam( + T + int?) + TypeParam( + *Ts) + PassStmt:4())) diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index b1c0918365a69..c8ebd9dced6ed 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -194,6 +194,22 @@ MypyFile:1( IntExpr(2) IntExpr(3))))) +[case testNestedListAssignment] +a1, [b1, c1] = a2, [b2, c2] +[out] +MypyFile:1( + AssignmentStmt:1( + TupleExpr:1( + NameExpr(a1) + TupleExpr:1( + NameExpr(b1) + NameExpr(c1))) + TupleExpr:1( + NameExpr(a2) + ListExpr:1( + NameExpr(b2) + NameExpr(c2))))) + [case testSimpleFunction] def main(): 1 @@ -2444,12 +2460,23 @@ MypyFile:1( Block:1( PassStmt:1()))) +[case testBytesLiteralType] +x: Literal[b'foo\xfe\x00'] +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x) + TempNode:1( + Any) + Literal?[b'foo\xfe\x00'])) + [case testMetaclass] class Foo(metaclass=Bar): pass [out] MypyFile:1( ClassDef:1( Foo + Keywords(metaclass=NameExpr(Bar)) Metaclass(NameExpr(Bar)) PassStmt:1())) @@ -2459,6 +2486,9 @@ class Foo(metaclass=foo.Bar): pass MypyFile:1( ClassDef:1( Foo + Keywords(metaclass=MemberExpr:1( + NameExpr(foo) + Bar)) Metaclass(MemberExpr:1( NameExpr(foo) Bar)) @@ -2470,6 +2500,7 @@ class Foo(foo.bar[x], metaclass=Bar): pass MypyFile:1( ClassDef:1( Foo + Keywords(metaclass=NameExpr(Bar)) Metaclass(NameExpr(Bar)) BaseTypeExpr( IndexExpr:1( @@ -2485,6 +2516,7 @@ class Foo(_root=None): pass MypyFile:1( ClassDef:1( Foo + Keywords(_root=NameExpr(None)) PassStmt:1())) [case testClassKeywordArgsBeforeMeta] @@ -2493,6 +2525,7 @@ class Foo(_root=None, metaclass=Bar): pass MypyFile:1( ClassDef:1( Foo + Keywords(_root=NameExpr(None), metaclass=NameExpr(Bar)) Metaclass(NameExpr(Bar)) PassStmt:1())) @@ -2502,6 +2535,7 @@ class Foo(metaclass=Bar, _root=None): pass MypyFile:1( ClassDef:1( Foo + Keywords(metaclass=NameExpr(Bar), _root=NameExpr(None)) Metaclass(NameExpr(Bar)) PassStmt:1())) @@ -2657,6 +2691,145 @@ MypyFile:1( Block:5( PassStmt:5()))))))) +[case testConditionalOverload1] +@overload +def foo(x: int): ... +if sys.version_info[0] > 2: + @overload + def foo(x: str): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:3( + If( + ComparisonExpr:3( + > + IndexExpr:3( + MemberExpr:3( + NameExpr(sys) + version_info) + IntExpr(0)) + IntExpr(2))) + Then() + Else()) + OverloadedFuncDef:1( + Decorator:1( + Var(foo) + NameExpr(overload) + FuncDef:2( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:2( + ExpressionStmt:2( + Ellipsis)))) + Decorator:4( + Var(foo) + NameExpr(overload) + FuncDef:5( + foo + Args( + Var(x)) + def (x: str?) -> Any + Block:5( + ExpressionStmt:5( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + +[case testConditionalOverload2] +@overload +def foo(x: int): ... +if sys.platform == "foo": + @overload + def foo(x: str): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:3( + If( + ComparisonExpr:3( + == + MemberExpr:3( + NameExpr(sys) + platform) + StrExpr(foo))) + Then()) + OverloadedFuncDef:1( + Decorator:1( + Var(foo) + NameExpr(overload) + FuncDef:2( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:2( + ExpressionStmt:2( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + +[case testConditionalOverload3] +if sys.version_info[0] > 2: + @overload + def foo(x: str): ... +@overload +def foo(x: int): ... +def foo(x): pass +[out] +MypyFile:1( + IfStmt:1( + If( + ComparisonExpr:1( + > + IndexExpr:1( + MemberExpr:1( + NameExpr(sys) + version_info) + IntExpr(0)) + IntExpr(2))) + Then() + Else()) + OverloadedFuncDef:2( + Decorator:2( + Var(foo) + NameExpr(overload) + FuncDef:3( + foo + Args( + Var(x)) + def (x: str?) -> Any + Block:3( + ExpressionStmt:3( + Ellipsis)))) + Decorator:4( + Var(foo) + NameExpr(overload) + FuncDef:5( + foo + Args( + Var(x)) + def (x: int?) -> Any + Block:5( + ExpressionStmt:5( + Ellipsis)))) + FuncDef:6( + foo + Args( + Var(x)) + Block:6( + PassStmt:6())))) + [case testCommentFunctionAnnotation] def f(): # type: () -> A pass diff --git a/test-data/unit/semanal-abstractclasses.test b/test-data/unit/semanal-abstractclasses.test index b0cb00e82106c..6e317e8d05ac1 100644 --- a/test-data/unit/semanal-abstractclasses.test +++ b/test-data/unit/semanal-abstractclasses.test @@ -13,6 +13,7 @@ MypyFile:1( Import:2(typing) ClassDef:4( A + Keywords(metaclass=NameExpr(ABCMeta [abc.ABCMeta])) Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) Decorator:5( Var(g) @@ -49,10 +50,12 @@ MypyFile:1( Import:2(typing) ClassDef:4( A + Keywords(metaclass=NameExpr(ABCMeta [abc.ABCMeta])) Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) PassStmt:4()) ClassDef:5( B + Keywords(metaclass=NameExpr(ABCMeta [abc.ABCMeta])) Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) PassStmt:5()) ClassDef:6( @@ -106,6 +109,7 @@ MypyFile:1( Import:3(typing) ClassDef:5( A + Keywords(metaclass=NameExpr(ABCMeta [abc.ABCMeta])) Metaclass(NameExpr(ABCMeta [abc.ABCMeta])) Decorator:6( Var(g) diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index c43d7754c75f4..e30dbac8ecbb0 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -413,6 +413,9 @@ MypyFile:1( Import:1(abc) ClassDef:2( A + Keywords(metaclass=MemberExpr:2( + NameExpr(abc) + ABCMeta [abc.ABCMeta])) Metaclass(MemberExpr:2( NameExpr(abc) ABCMeta [abc.ABCMeta]))