1919from mypy .checkmember import analyze_member_access , has_operator
2020from mypy .checkstrformat import StringFormatterChecker
2121from mypy .erasetype import erase_type , remove_instance_last_known_values , replace_meta_vars
22- from mypy .errors import ErrorWatcher , report_internal_error
22+ from mypy .errors import ErrorInfo , ErrorWatcher , report_internal_error
2323from mypy .expandtype import (
2424 expand_type ,
2525 expand_type_by_instance ,
@@ -355,9 +355,15 @@ def __init__(
355355 type_state .infer_polymorphic = not self .chk .options .old_type_inference
356356
357357 self ._arg_infer_context_cache = None
358+ self .expr_cache : dict [
359+ tuple [Expression , Type | None ],
360+ tuple [int , Type , list [ErrorInfo ], dict [Expression , Type ]],
361+ ] = {}
362+ self .in_lambda_expr = False
358363
359364 def reset (self ) -> None :
360365 self .resolved_type = {}
366+ self .expr_cache .clear ()
361367
362368 def visit_name_expr (self , e : NameExpr ) -> Type :
363369 """Type check a name expression.
@@ -5402,6 +5408,8 @@ def find_typeddict_context(
54025408
54035409 def visit_lambda_expr (self , e : LambdaExpr ) -> Type :
54045410 """Type check lambda expression."""
5411+ old_in_lambda = self .in_lambda_expr
5412+ self .in_lambda_expr = True
54055413 self .chk .check_default_args (e , body_is_trivial = False )
54065414 inferred_type , type_override = self .infer_lambda_type_using_context (e )
54075415 if not inferred_type :
@@ -5422,6 +5430,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54225430 ret_type = self .accept (e .expr (), allow_none_return = True )
54235431 fallback = self .named_type ("builtins.function" )
54245432 self .chk .return_types .pop ()
5433+ self .in_lambda_expr = old_in_lambda
54255434 return callable_type (e , fallback , ret_type )
54265435 else :
54275436 # Type context available.
@@ -5434,6 +5443,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
54345443 self .accept (e .expr (), allow_none_return = True )
54355444 ret_type = self .chk .lookup_type (e .expr ())
54365445 self .chk .return_types .pop ()
5446+ self .in_lambda_expr = old_in_lambda
54375447 return replace_callable_return_type (inferred_type , ret_type )
54385448
54395449 def infer_lambda_type_using_context (
@@ -5978,6 +5988,24 @@ def accept(
59785988 typ = self .visit_conditional_expr (node , allow_none_return = True )
59795989 elif allow_none_return and isinstance (node , AwaitExpr ):
59805990 typ = self .visit_await_expr (node , allow_none_return = True )
5991+ # Deeply nested generic calls can deteriorate performance dramatically.
5992+ # Although in most cases caching makes little difference, in worst case
5993+ # it avoids exponential complexity.
5994+ # We cannot use cache inside lambdas, because they skip immediate type
5995+ # context, and use enclosing one, see infer_lambda_type_using_context().
5996+ # TODO: consider using cache for more expression kinds.
5997+ elif isinstance (node , (CallExpr , ListExpr , TupleExpr )) and not (
5998+ self .in_lambda_expr or self .chk .current_node_deferred
5999+ ):
6000+ if (node , type_context ) in self .expr_cache :
6001+ binder_version , typ , messages , type_map = self .expr_cache [(node , type_context )]
6002+ if binder_version == self .chk .binder .version :
6003+ self .chk .store_types (type_map )
6004+ self .msg .add_errors (messages )
6005+ else :
6006+ typ = self .accept_maybe_cache (node , type_context = type_context )
6007+ else :
6008+ typ = self .accept_maybe_cache (node , type_context = type_context )
59816009 else :
59826010 typ = node .accept (self )
59836011 except Exception as err :
@@ -6008,6 +6036,21 @@ def accept(
60086036 self .in_expression = False
60096037 return result
60106038
6039+ def accept_maybe_cache (self , node : Expression , type_context : Type | None = None ) -> Type :
6040+ binder_version = self .chk .binder .version
6041+ # Micro-optimization: inline local_type_map() as it is somewhat slow in mypyc.
6042+ type_map : dict [Expression , Type ] = {}
6043+ self .chk ._type_maps .append (type_map )
6044+ with self .msg .filter_errors (filter_errors = True , save_filtered_errors = True ) as msg :
6045+ typ = node .accept (self )
6046+ messages = msg .filtered_errors ()
6047+ if binder_version == self .chk .binder .version and not self .chk .current_node_deferred :
6048+ self .expr_cache [(node , type_context )] = (binder_version , typ , messages , type_map )
6049+ self .chk ._type_maps .pop ()
6050+ self .chk .store_types (type_map )
6051+ self .msg .add_errors (messages )
6052+ return typ
6053+
60116054 def named_type (self , name : str ) -> Instance :
60126055 """Return an instance type with type given by the name and no type
60136056 arguments. Alias for TypeChecker.named_type.
0 commit comments