8
8
from typing import Final , Union
9
9
10
10
from mypy .nodes import (
11
+ ArgKind ,
11
12
CallExpr ,
12
13
ComplexExpr ,
13
14
Expression ,
@@ -77,34 +78,8 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non
77
78
value = constant_fold_expr (expr .expr , cur_mod_id )
78
79
if value is not None :
79
80
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 )
108
83
return None
109
84
110
85
@@ -217,3 +192,87 @@ def constant_fold_unary_op(op: str, value: ConstantValue) -> int | float | None:
217
192
elif op == "+" and isinstance (value , (int , float )):
218
193
return value
219
194
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