9999)
100100from mypy .reachability import infer_reachability_of_if_statement , mark_block_unreachable
101101from mypy .sharedparse import argument_elide_name , special_function_elide_names
102+ from mypy .traverser import TraverserVisitor
102103from mypy .types import (
103104 AnyType ,
104105 CallableArgument ,
@@ -260,6 +261,11 @@ def parse(
260261 Return the parse tree. If errors is not provided, raise ParseError
261262 on failure. Otherwise, use the errors object to report parse errors.
262263 """
264+ ignore_errors = (options is not None and options .ignore_errors ) or (
265+ errors is not None and fnam in errors .ignored_files
266+ )
267+ # If errors are ignored, we can drop many function bodies to speed up type checking.
268+ strip_function_bodies = ignore_errors and (options is None or not options .preserve_asts )
263269 raise_on_error = False
264270 if options is None :
265271 options = Options ()
@@ -281,7 +287,13 @@ def parse(
281287 warnings .filterwarnings ("ignore" , category = DeprecationWarning )
282288 ast = ast3_parse (source , fnam , "exec" , feature_version = feature_version )
283289
284- tree = ASTConverter (options = options , is_stub = is_stub_file , errors = errors ).visit (ast )
290+ tree = ASTConverter (
291+ options = options ,
292+ is_stub = is_stub_file ,
293+ errors = errors ,
294+ ignore_errors = ignore_errors ,
295+ strip_function_bodies = strip_function_bodies ,
296+ ).visit (ast )
285297 tree .path = fnam
286298 tree .is_stub = is_stub_file
287299 except SyntaxError as e :
@@ -400,14 +412,24 @@ def is_no_type_check_decorator(expr: ast3.expr) -> bool:
400412
401413
402414class ASTConverter :
403- def __init__ (self , options : Options , is_stub : bool , errors : Errors ) -> None :
404- # 'C' for class, 'F' for function
405- self .class_and_function_stack : list [Literal ["C" , "F" ]] = []
415+ def __init__ (
416+ self ,
417+ options : Options ,
418+ is_stub : bool ,
419+ errors : Errors ,
420+ * ,
421+ ignore_errors : bool ,
422+ strip_function_bodies : bool ,
423+ ) -> None :
424+ # 'C' for class, 'D' for function signature, 'F' for function, 'L' for lambda
425+ self .class_and_function_stack : list [Literal ["C" , "D" , "F" , "L" ]] = []
406426 self .imports : list [ImportBase ] = []
407427
408428 self .options = options
409429 self .is_stub = is_stub
410430 self .errors = errors
431+ self .ignore_errors = ignore_errors
432+ self .strip_function_bodies = strip_function_bodies
411433
412434 self .type_ignores : dict [int , list [str ]] = {}
413435
@@ -475,7 +497,12 @@ def get_lineno(self, node: ast3.expr | ast3.stmt) -> int:
475497 return node .lineno
476498
477499 def translate_stmt_list (
478- self , stmts : Sequence [ast3 .stmt ], ismodule : bool = False
500+ self ,
501+ stmts : Sequence [ast3 .stmt ],
502+ * ,
503+ ismodule : bool = False ,
504+ can_strip : bool = False ,
505+ is_coroutine : bool = False ,
479506 ) -> list [Statement ]:
480507 # A "# type: ignore" comment before the first statement of a module
481508 # ignores the whole module:
@@ -504,11 +531,41 @@ def translate_stmt_list(
504531 mark_block_unreachable (block )
505532 return [block ]
506533
534+ stack = self .class_and_function_stack
535+ if self .strip_function_bodies and len (stack ) == 1 and stack [0 ] == "F" :
536+ return []
537+
507538 res : list [Statement ] = []
508539 for stmt in stmts :
509540 node = self .visit (stmt )
510541 res .append (node )
511542
543+ if (
544+ self .strip_function_bodies
545+ and can_strip
546+ and stack [- 2 :] == ["C" , "F" ]
547+ and not is_possible_trivial_body (res )
548+ ):
549+ # We only strip method bodies if they don't assign to an attribute, as
550+ # this may define an attribute which has an externally visible effect.
551+ visitor = FindAttributeAssign ()
552+ for s in res :
553+ s .accept (visitor )
554+ if visitor .found :
555+ break
556+ else :
557+ if is_coroutine :
558+ # Yields inside an async function affect the return type and should not
559+ # be stripped.
560+ yield_visitor = FindYield ()
561+ for s in res :
562+ s .accept (yield_visitor )
563+ if yield_visitor .found :
564+ break
565+ else :
566+ return []
567+ else :
568+ return []
512569 return res
513570
514571 def translate_type_comment (
@@ -573,9 +630,20 @@ def as_block(self, stmts: list[ast3.stmt], lineno: int) -> Block | None:
573630 b .set_line (lineno )
574631 return b
575632
576- def as_required_block (self , stmts : list [ast3 .stmt ], lineno : int ) -> Block :
633+ def as_required_block (
634+ self ,
635+ stmts : list [ast3 .stmt ],
636+ lineno : int ,
637+ * ,
638+ can_strip : bool = False ,
639+ is_coroutine : bool = False ,
640+ ) -> Block :
577641 assert stmts # must be non-empty
578- b = Block (self .fix_function_overloads (self .translate_stmt_list (stmts )))
642+ b = Block (
643+ self .fix_function_overloads (
644+ self .translate_stmt_list (stmts , can_strip = can_strip , is_coroutine = is_coroutine )
645+ )
646+ )
579647 # TODO: in most call sites line is wrong (includes first line of enclosing statement)
580648 # TODO: also we need to set the column, and the end position here.
581649 b .set_line (lineno )
@@ -831,9 +899,6 @@ def _is_stripped_if_stmt(self, stmt: Statement) -> bool:
831899 # For elif, IfStmt are stored recursively in else_body
832900 return self ._is_stripped_if_stmt (stmt .else_body .body [0 ])
833901
834- def in_method_scope (self ) -> bool :
835- return self .class_and_function_stack [- 2 :] == ["C" , "F" ]
836-
837902 def translate_module_id (self , id : str ) -> str :
838903 """Return the actual, internal module id for a source text id."""
839904 if id == self .options .custom_typing_module :
@@ -868,7 +933,7 @@ def do_func_def(
868933 self , n : ast3 .FunctionDef | ast3 .AsyncFunctionDef , is_coroutine : bool = False
869934 ) -> FuncDef | Decorator :
870935 """Helper shared between visit_FunctionDef and visit_AsyncFunctionDef."""
871- self .class_and_function_stack .append ("F " )
936+ self .class_and_function_stack .append ("D " )
872937 no_type_check = bool (
873938 n .decorator_list and any (is_no_type_check_decorator (d ) for d in n .decorator_list )
874939 )
@@ -915,7 +980,8 @@ def do_func_def(
915980 return_type = TypeConverter (self .errors , line = lineno ).visit (func_type_ast .returns )
916981
917982 # add implicit self type
918- if self .in_method_scope () and len (arg_types ) < len (args ):
983+ in_method_scope = self .class_and_function_stack [- 2 :] == ["C" , "D" ]
984+ if in_method_scope and len (arg_types ) < len (args ):
919985 arg_types .insert (0 , AnyType (TypeOfAny .special_form ))
920986 except SyntaxError :
921987 stripped_type = n .type_comment .split ("#" , 2 )[0 ].strip ()
@@ -965,7 +1031,10 @@ def do_func_def(
9651031 end_line = getattr (n , "end_lineno" , None )
9661032 end_column = getattr (n , "end_col_offset" , None )
9671033
968- func_def = FuncDef (n .name , args , self .as_required_block (n .body , lineno ), func_type )
1034+ self .class_and_function_stack .pop ()
1035+ self .class_and_function_stack .append ("F" )
1036+ body = self .as_required_block (n .body , lineno , can_strip = True , is_coroutine = is_coroutine )
1037+ func_def = FuncDef (n .name , args , body , func_type )
9691038 if isinstance (func_def .type , CallableType ):
9701039 # semanal.py does some in-place modifications we want to avoid
9711040 func_def .unanalyzed_type = func_def .type .copy_modified ()
@@ -1409,9 +1478,11 @@ def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr:
14091478 body .lineno = n .body .lineno
14101479 body .col_offset = n .body .col_offset
14111480
1481+ self .class_and_function_stack .append ("L" )
14121482 e = LambdaExpr (
14131483 self .transform_args (n .args , n .lineno ), self .as_required_block ([body ], n .lineno )
14141484 )
1485+ self .class_and_function_stack .pop ()
14151486 e .set_line (n .lineno , n .col_offset ) # Overrides set_line -- can't use self.set_line
14161487 return e
14171488
@@ -2081,3 +2152,85 @@ def stringify_name(n: AST) -> str | None:
20812152 if sv is not None :
20822153 return f"{ sv } .{ n .attr } "
20832154 return None # Can't do it.
2155+
2156+
2157+ class FindAttributeAssign (TraverserVisitor ):
2158+ """Check if an AST contains attribute assignments (e.g. self.x = 0)."""
2159+
2160+ def __init__ (self ) -> None :
2161+ self .lvalue = False
2162+ self .found = False
2163+
2164+ def visit_assignment_stmt (self , s : AssignmentStmt ) -> None :
2165+ self .lvalue = True
2166+ for lv in s .lvalues :
2167+ lv .accept (self )
2168+ self .lvalue = False
2169+
2170+ def visit_with_stmt (self , s : WithStmt ) -> None :
2171+ self .lvalue = True
2172+ for lv in s .target :
2173+ if lv is not None :
2174+ lv .accept (self )
2175+ self .lvalue = False
2176+ s .body .accept (self )
2177+
2178+ def visit_for_stmt (self , s : ForStmt ) -> None :
2179+ self .lvalue = True
2180+ s .index .accept (self )
2181+ self .lvalue = False
2182+ s .body .accept (self )
2183+ if s .else_body :
2184+ s .else_body .accept (self )
2185+
2186+ def visit_expression_stmt (self , s : ExpressionStmt ) -> None :
2187+ # No need to look inside these
2188+ pass
2189+
2190+ def visit_call_expr (self , e : CallExpr ) -> None :
2191+ # No need to look inside these
2192+ pass
2193+
2194+ def visit_index_expr (self , e : IndexExpr ) -> None :
2195+ # No need to look inside these
2196+ pass
2197+
2198+ def visit_member_expr (self , e : MemberExpr ) -> None :
2199+ if self .lvalue :
2200+ self .found = True
2201+
2202+
2203+ class FindYield (TraverserVisitor ):
2204+ """Check if an AST contains yields or yield froms."""
2205+
2206+ def __init__ (self ) -> None :
2207+ self .found = False
2208+
2209+ def visit_yield_expr (self , e : YieldExpr ) -> None :
2210+ self .found = True
2211+
2212+ def visit_yield_from_expr (self , e : YieldFromExpr ) -> None :
2213+ self .found = True
2214+
2215+
2216+ def is_possible_trivial_body (s : list [Statement ]) -> bool :
2217+ """Could the statements form a "trivial" function body, such as 'pass'?
2218+
2219+ This mimics mypy.semanal.is_trivial_body, but this runs before
2220+ semantic analysis so some checks must be conservative.
2221+ """
2222+ l = len (s )
2223+ if l == 0 :
2224+ return False
2225+ i = 0
2226+ if isinstance (s [0 ], ExpressionStmt ) and isinstance (s [0 ].expr , StrExpr ):
2227+ # Skip docstring
2228+ i += 1
2229+ if i == l :
2230+ return True
2231+ if l > i + 1 :
2232+ return False
2233+ stmt = s [i ]
2234+ return isinstance (stmt , (PassStmt , RaiseStmt )) or (
2235+ isinstance (stmt , ExpressionStmt ) and isinstance (stmt .expr , EllipsisExpr )
2236+ )
0 commit comments