88from typing import Final , Union
99
1010from mypy .nodes import (
11+ ArgKind ,
1112 CallExpr ,
1213 ComplexExpr ,
1314 Expression ,
@@ -77,34 +78,8 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non
7778 value = constant_fold_expr (expr .expr , cur_mod_id )
7879 if value is not None :
7980 return constant_fold_unary_op (expr .op , value )
80- # --- f-string requires partial support for both str.join and str.format ---
81- elif (
82- isinstance (expr , CallExpr )
83- and isinstance (callee := expr .callee , MemberExpr )
84- and isinstance (folded_callee := constant_fold_expr (callee .expr , cur_mod_id ), str )
85- ):
86- # --- partial str.join constant folding ---
87- if (
88- callee .name == "join"
89- and len (args := expr .args ) == 1
90- and isinstance (arg := args [0 ], (ListExpr , TupleExpr ))
91- ):
92- folded_items : list [str ] = []
93- for item in arg .items :
94- val = constant_fold_expr (item , cur_mod_id )
95- if not isinstance (val , str ):
96- return None
97- folded_items .append (val )
98- return folded_callee .join (folded_items )
99- # --- str.format constant folding
100- elif callee .name == "format" :
101- folded_args : list [str ] = []
102- for arg in expr .args :
103- arg_val = constant_fold_expr (arg , cur_mod_id )
104- if arg_val is None :
105- return None
106- folded_args .append (arg_val )
107- return folded_callee .format (* folded_args )
81+ elif isinstance (expr , CallExpr ):
82+ return constant_fold_call_expr (expr , cur_mod_id )
10883 return None
10984
11085
@@ -217,3 +192,87 @@ def constant_fold_unary_op(op: str, value: ConstantValue) -> int | float | None:
217192 elif op == "+" and isinstance (value , (int , float )):
218193 return value
219194 return None
195+
196+
197+ foldable_builtins = {
198+ "builtins.str" : str ,
199+ "builtins.int" : int ,
200+ "builtins.bool" : bool ,
201+ "builtins.float" : float ,
202+ "builtins.complex" : complex ,
203+ "builtins.repr" : repr ,
204+ "builtins.len" : len ,
205+ "builtins.hasattr" : hasattr ,
206+ "builtins.hex" : hex ,
207+ "builtins.hash" : hash ,
208+ "builtins.min" : min ,
209+ "builtins.max" : max ,
210+ "builtins.oct" : oct ,
211+ "builtins.pow" : pow ,
212+ "builtins.round" : round ,
213+ "builtins.abs" : abs ,
214+ "builtins.ascii" : ascii ,
215+ "builtins.bin" : bin ,
216+ "builtins.chr" : chr ,
217+ }
218+
219+ def constant_fold_call_expr (expr : CallExpr , cur_mod_id : str , foldable_builtins = foldable_builtins ) -> ConstantValue | None :
220+ callee = expr .callee
221+ if isinstance (callee , NameExpr ):
222+ func = foldable_builtins .get (callee .fullname )
223+ if func is None :
224+ return None
225+
226+ folded_args = []
227+ for arg in expr .args :
228+ val = constant_fold_expr (arg , cur_mod_id )
229+ if val is None :
230+ return None
231+ folded_args .append (arg )
232+
233+ args = []
234+ kwargs = {}
235+ for folded_arg , arg_kind , arg_name in zip (folded_args , expr .arg_kinds , expr .arg_names ):
236+ try :
237+ if arg_kind == ArgKind .ARG_POS :
238+ args .append (folded_arg )
239+ elif arg_kind == ArgKind .ARG_NAMED :
240+ kwargs [arg_name ] = folded_arg
241+ elif arg_kind == ArgKind .ARG_STAR :
242+ args .extend (folded_arg )
243+ elif arg_kind == ArgKind .ARG_STAR2 :
244+ kwargs .update (folded_arg )
245+ except :
246+ return None
247+
248+ try :
249+ return func (* args , ** kwargs )
250+ except :
251+ return None
252+ # --- f-string requires partial support for both str.join and str.format ---
253+ elif (
254+ isinstance (callee , MemberExpr )
255+ and isinstance (folded_callee := constant_fold_expr (callee .expr , cur_mod_id ), str )
256+ ):
257+ # --- partial str.join constant folding ---
258+ if (
259+ callee .name == "join"
260+ and len (args := expr .args ) == 1
261+ and isinstance (arg := args [0 ], (ListExpr , TupleExpr ))
262+ ):
263+ folded_items : list [str ] = []
264+ for item in arg .items :
265+ val = constant_fold_expr (item , cur_mod_id )
266+ if not isinstance (val , str ):
267+ return None
268+ folded_items .append (val )
269+ return folded_callee .join (folded_items )
270+ # --- str.format constant folding
271+ elif callee .name == "format" :
272+ folded_args : list [str ] = []
273+ for arg in expr .args :
274+ arg_val = constant_fold_expr (arg , cur_mod_id )
275+ if arg_val is None :
276+ return None
277+ folded_args .append (arg_val )
278+ return folded_callee .format (* folded_args )
0 commit comments