19
19
from mypy .checkmember import analyze_member_access , has_operator
20
20
from mypy .checkstrformat import StringFormatterChecker
21
21
from 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
23
23
from mypy .expandtype import (
24
24
expand_type ,
25
25
expand_type_by_instance ,
@@ -355,9 +355,15 @@ def __init__(
355
355
type_state .infer_polymorphic = not self .chk .options .old_type_inference
356
356
357
357
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
358
363
359
364
def reset (self ) -> None :
360
365
self .resolved_type = {}
366
+ self .expr_cache .clear ()
361
367
362
368
def visit_name_expr (self , e : NameExpr ) -> Type :
363
369
"""Type check a name expression.
@@ -5402,6 +5408,8 @@ def find_typeddict_context(
5402
5408
5403
5409
def visit_lambda_expr (self , e : LambdaExpr ) -> Type :
5404
5410
"""Type check lambda expression."""
5411
+ old_in_lambda = self .in_lambda_expr
5412
+ self .in_lambda_expr = True
5405
5413
self .chk .check_default_args (e , body_is_trivial = False )
5406
5414
inferred_type , type_override = self .infer_lambda_type_using_context (e )
5407
5415
if not inferred_type :
@@ -5422,6 +5430,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
5422
5430
ret_type = self .accept (e .expr (), allow_none_return = True )
5423
5431
fallback = self .named_type ("builtins.function" )
5424
5432
self .chk .return_types .pop ()
5433
+ self .in_lambda_expr = old_in_lambda
5425
5434
return callable_type (e , fallback , ret_type )
5426
5435
else :
5427
5436
# Type context available.
@@ -5434,6 +5443,7 @@ def visit_lambda_expr(self, e: LambdaExpr) -> Type:
5434
5443
self .accept (e .expr (), allow_none_return = True )
5435
5444
ret_type = self .chk .lookup_type (e .expr ())
5436
5445
self .chk .return_types .pop ()
5446
+ self .in_lambda_expr = old_in_lambda
5437
5447
return replace_callable_return_type (inferred_type , ret_type )
5438
5448
5439
5449
def infer_lambda_type_using_context (
@@ -5978,6 +5988,24 @@ def accept(
5978
5988
typ = self .visit_conditional_expr (node , allow_none_return = True )
5979
5989
elif allow_none_return and isinstance (node , AwaitExpr ):
5980
5990
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 )
5981
6009
else :
5982
6010
typ = node .accept (self )
5983
6011
except Exception as err :
@@ -6008,6 +6036,21 @@ def accept(
6008
6036
self .in_expression = False
6009
6037
return result
6010
6038
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
+
6011
6054
def named_type (self , name : str ) -> Instance :
6012
6055
"""Return an instance type with type given by the name and no type
6013
6056
arguments. Alias for TypeChecker.named_type.
0 commit comments