@@ -301,6 +301,32 @@ def generate_dunder_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
301
301
return gen .wrapper_name ()
302
302
303
303
304
+ def generate_ipow_wrapper (cl : ClassIR , fn : FuncIR , emitter : Emitter ) -> str :
305
+ """Generate a wrapper for native __ipow__.
306
+
307
+ Since __ipow__ fills a ternary slot, but almost no one defines __ipow__ to take three
308
+ arguments, the wrapper needs to tweaked to force it to accept three arguments.
309
+ """
310
+ gen = WrapperGenerator (cl , emitter )
311
+ gen .set_target (fn )
312
+ assert len (fn .args ) in (2 , 3 ), "__ipow__ should only take 2 or 3 arguments"
313
+ gen .arg_names = ["self" , "exp" , "mod" ]
314
+ gen .emit_header ()
315
+ gen .emit_arg_processing ()
316
+ handle_third_pow_argument (
317
+ fn ,
318
+ emitter ,
319
+ gen ,
320
+ if_unsupported = [
321
+ 'PyErr_SetString(PyExc_TypeError, "__ipow__ takes 2 positional arguments but 3 were given");' ,
322
+ "return NULL;" ,
323
+ ],
324
+ )
325
+ gen .emit_call ()
326
+ gen .finish ()
327
+ return gen .wrapper_name ()
328
+
329
+
304
330
def generate_bin_op_wrapper (cl : ClassIR , fn : FuncIR , emitter : Emitter ) -> str :
305
331
"""Generates a wrapper for a native binary dunder method.
306
332
@@ -311,13 +337,16 @@ def generate_bin_op_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
311
337
"""
312
338
gen = WrapperGenerator (cl , emitter )
313
339
gen .set_target (fn )
314
- gen .arg_names = ["left" , "right" ]
340
+ if fn .name in ("__pow__" , "__rpow__" ):
341
+ gen .arg_names = ["left" , "right" , "mod" ]
342
+ else :
343
+ gen .arg_names = ["left" , "right" ]
315
344
wrapper_name = gen .wrapper_name ()
316
345
317
346
gen .emit_header ()
318
347
if fn .name not in reverse_op_methods and fn .name in reverse_op_method_names :
319
348
# There's only a reverse operator method.
320
- generate_bin_op_reverse_only_wrapper (emitter , gen )
349
+ generate_bin_op_reverse_only_wrapper (fn , emitter , gen )
321
350
else :
322
351
rmethod = reverse_op_methods [fn .name ]
323
352
fn_rev = cl .get_method (rmethod )
@@ -334,6 +363,7 @@ def generate_bin_op_forward_only_wrapper(
334
363
fn : FuncIR , emitter : Emitter , gen : WrapperGenerator
335
364
) -> None :
336
365
gen .emit_arg_processing (error = GotoHandler ("typefail" ), raise_exception = False )
366
+ handle_third_pow_argument (fn , emitter , gen , if_unsupported = ["goto typefail;" ])
337
367
gen .emit_call (not_implemented_handler = "goto typefail;" )
338
368
gen .emit_error_handling ()
339
369
emitter .emit_label ("typefail" )
@@ -352,19 +382,16 @@ def generate_bin_op_forward_only_wrapper(
352
382
# if not isinstance(other, int):
353
383
# return NotImplemented
354
384
# ...
355
- rmethod = reverse_op_methods [fn .name ]
356
- emitter .emit_line (f"_Py_IDENTIFIER({ rmethod } );" )
357
- emitter .emit_line (
358
- 'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});' .format (
359
- op_methods_to_symbols [fn .name ], rmethod
360
- )
361
- )
385
+ generate_bin_op_reverse_dunder_call (fn , emitter , reverse_op_methods [fn .name ])
362
386
gen .finish ()
363
387
364
388
365
- def generate_bin_op_reverse_only_wrapper (emitter : Emitter , gen : WrapperGenerator ) -> None :
389
+ def generate_bin_op_reverse_only_wrapper (
390
+ fn : FuncIR , emitter : Emitter , gen : WrapperGenerator
391
+ ) -> None :
366
392
gen .arg_names = ["right" , "left" ]
367
393
gen .emit_arg_processing (error = GotoHandler ("typefail" ), raise_exception = False )
394
+ handle_third_pow_argument (fn , emitter , gen , if_unsupported = ["goto typefail;" ])
368
395
gen .emit_call ()
369
396
gen .emit_error_handling ()
370
397
emitter .emit_label ("typefail" )
@@ -390,7 +417,14 @@ def generate_bin_op_both_wrappers(
390
417
)
391
418
)
392
419
gen .emit_arg_processing (error = GotoHandler ("typefail" ), raise_exception = False )
393
- gen .emit_call (not_implemented_handler = "goto typefail;" )
420
+ handle_third_pow_argument (fn , emitter , gen , if_unsupported = ["goto typefail2;" ])
421
+ # Ternary __rpow__ calls aren't a thing so immediately bail
422
+ # if ternary __pow__ returns NotImplemented.
423
+ if fn .name == "__pow__" and len (fn .args ) == 3 :
424
+ fwd_not_implemented_handler = "goto typefail2;"
425
+ else :
426
+ fwd_not_implemented_handler = "goto typefail;"
427
+ gen .emit_call (not_implemented_handler = fwd_not_implemented_handler )
394
428
gen .emit_error_handling ()
395
429
emitter .emit_line ("}" )
396
430
emitter .emit_label ("typefail" )
@@ -402,22 +436,59 @@ def generate_bin_op_both_wrappers(
402
436
gen .set_target (fn_rev )
403
437
gen .arg_names = ["right" , "left" ]
404
438
gen .emit_arg_processing (error = GotoHandler ("typefail2" ), raise_exception = False )
439
+ handle_third_pow_argument (fn_rev , emitter , gen , if_unsupported = ["goto typefail2;" ])
405
440
gen .emit_call ()
406
441
gen .emit_error_handling ()
407
442
emitter .emit_line ("} else {" )
408
- emitter .emit_line (f"_Py_IDENTIFIER({ fn_rev .name } );" )
409
- emitter .emit_line (
410
- 'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});' .format (
411
- op_methods_to_symbols [fn .name ], fn_rev .name
412
- )
413
- )
443
+ generate_bin_op_reverse_dunder_call (fn , emitter , fn_rev .name )
414
444
emitter .emit_line ("}" )
415
445
emitter .emit_label ("typefail2" )
416
446
emitter .emit_line ("Py_INCREF(Py_NotImplemented);" )
417
447
emitter .emit_line ("return Py_NotImplemented;" )
418
448
gen .finish ()
419
449
420
450
451
+ def generate_bin_op_reverse_dunder_call (fn : FuncIR , emitter : Emitter , rmethod : str ) -> None :
452
+ if fn .name in ("__pow__" , "__rpow__" ):
453
+ # Ternary pow() will never call the reverse dunder.
454
+ emitter .emit_line ("if (obj_mod == Py_None) {" )
455
+ emitter .emit_line (f"_Py_IDENTIFIER({ rmethod } );" )
456
+ emitter .emit_line (
457
+ 'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});' .format (
458
+ op_methods_to_symbols [fn .name ], rmethod
459
+ )
460
+ )
461
+ if fn .name in ("__pow__" , "__rpow__" ):
462
+ emitter .emit_line ("} else {" )
463
+ emitter .emit_line ("Py_INCREF(Py_NotImplemented);" )
464
+ emitter .emit_line ("return Py_NotImplemented;" )
465
+ emitter .emit_line ("}" )
466
+
467
+
468
+ def handle_third_pow_argument (
469
+ fn : FuncIR , emitter : Emitter , gen : WrapperGenerator , * , if_unsupported : list [str ]
470
+ ) -> None :
471
+ if fn .name not in ("__pow__" , "__rpow__" , "__ipow__" ):
472
+ return
473
+
474
+ if (fn .name in ("__pow__" , "__ipow__" ) and len (fn .args ) == 2 ) or fn .name == "__rpow__" :
475
+ # If the power dunder only supports two arguments and the third
476
+ # argument (AKA mod) is set to a non-default value, simply bail.
477
+ #
478
+ # Importantly, this prevents any ternary __rpow__ calls from
479
+ # happening (as per the language specification).
480
+ emitter .emit_line ("if (obj_mod != Py_None) {" )
481
+ for line in if_unsupported :
482
+ emitter .emit_line (line )
483
+ emitter .emit_line ("}" )
484
+ # The slot wrapper will receive three arguments, but the call only
485
+ # supports two so make sure that the third argument isn't passed
486
+ # along. This is needed as two-argument __(i)pow__ is allowed and
487
+ # rather common.
488
+ if len (gen .arg_names ) == 3 :
489
+ gen .arg_names .pop ()
490
+
491
+
421
492
RICHCOMPARE_OPS = {
422
493
"__lt__" : "Py_LT" ,
423
494
"__gt__" : "Py_GT" ,
0 commit comments