@@ -301,6 +301,32 @@ def generate_dunder_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
301301 return gen .wrapper_name ()
302302
303303
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+
304330def generate_bin_op_wrapper (cl : ClassIR , fn : FuncIR , emitter : Emitter ) -> str :
305331 """Generates a wrapper for a native binary dunder method.
306332
@@ -311,13 +337,16 @@ def generate_bin_op_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
311337 """
312338 gen = WrapperGenerator (cl , emitter )
313339 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" ]
315344 wrapper_name = gen .wrapper_name ()
316345
317346 gen .emit_header ()
318347 if fn .name not in reverse_op_methods and fn .name in reverse_op_method_names :
319348 # 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 )
321350 else :
322351 rmethod = reverse_op_methods [fn .name ]
323352 fn_rev = cl .get_method (rmethod )
@@ -334,6 +363,7 @@ def generate_bin_op_forward_only_wrapper(
334363 fn : FuncIR , emitter : Emitter , gen : WrapperGenerator
335364) -> None :
336365 gen .emit_arg_processing (error = GotoHandler ("typefail" ), raise_exception = False )
366+ handle_third_pow_argument (fn , emitter , gen , if_unsupported = ["goto typefail;" ])
337367 gen .emit_call (not_implemented_handler = "goto typefail;" )
338368 gen .emit_error_handling ()
339369 emitter .emit_label ("typefail" )
@@ -352,19 +382,16 @@ def generate_bin_op_forward_only_wrapper(
352382 # if not isinstance(other, int):
353383 # return NotImplemented
354384 # ...
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 ])
362386 gen .finish ()
363387
364388
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 :
366392 gen .arg_names = ["right" , "left" ]
367393 gen .emit_arg_processing (error = GotoHandler ("typefail" ), raise_exception = False )
394+ handle_third_pow_argument (fn , emitter , gen , if_unsupported = ["goto typefail;" ])
368395 gen .emit_call ()
369396 gen .emit_error_handling ()
370397 emitter .emit_label ("typefail" )
@@ -390,7 +417,14 @@ def generate_bin_op_both_wrappers(
390417 )
391418 )
392419 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 )
394428 gen .emit_error_handling ()
395429 emitter .emit_line ("}" )
396430 emitter .emit_label ("typefail" )
@@ -402,22 +436,59 @@ def generate_bin_op_both_wrappers(
402436 gen .set_target (fn_rev )
403437 gen .arg_names = ["right" , "left" ]
404438 gen .emit_arg_processing (error = GotoHandler ("typefail2" ), raise_exception = False )
439+ handle_third_pow_argument (fn_rev , emitter , gen , if_unsupported = ["goto typefail2;" ])
405440 gen .emit_call ()
406441 gen .emit_error_handling ()
407442 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 )
414444 emitter .emit_line ("}" )
415445 emitter .emit_label ("typefail2" )
416446 emitter .emit_line ("Py_INCREF(Py_NotImplemented);" )
417447 emitter .emit_line ("return Py_NotImplemented;" )
418448 gen .finish ()
419449
420450
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+
421492RICHCOMPARE_OPS = {
422493 "__lt__" : "Py_LT" ,
423494 "__gt__" : "Py_GT" ,
0 commit comments