Feature
Reproduction
RustPython:
ValueError: pow() 3rd argument cannot be 0
CPython:
TypeError: unsupported operand type(s) for ** or pow(): 'int', 'NoneType', 'int'
Root cause
PyInt::modpow downcasts modulus to PyInt and immediately raises ValueError("pow() 3rd argument cannot be 0") when it is zero, without first validating that other is an integer. CPython's long_pow instead runs CHECK_BINOP(v, w) and the x type-check before the zero-modulus branch, so a non-integer exponent (or non-int/non-None modulus) returns NotImplemented and the dispatch falls through to a TypeError.
The same observation applies to the related variant pow(True, -3939.40..., False): once PyInt::modpow correctly returns NotImplemented for a non-int exponent, the ternary_op fallback dispatches to PyFloat::AS_NUMBER.power, which raises the expected TypeError("pow() 3rd argument not allowed unless all arguments are integers").
Fix
In PyInt::modpow, downcast other to PyInt first and return NotImplemented if the cast fails; only then perform the zero-modulus check.
Environment
- RustPython d248a04 (Python 3.14.0)
- CPython v3.14.3
- OS: Debian 12
Python Documentation or reference to CPython source code
-
CPython long_pow: Objects/longobject.c#L4888-L4941 — type-check (CHECK_BINOP, PyLong_Check(x)) runs before the zero-check:
CHECK_BINOP(v, w);
a = (PyLongObject*)Py_NewRef(v);
b = (PyLongObject*)Py_NewRef(w);
if (PyLong_Check(x)) {
c = (PyLongObject *)Py_NewRef(x);
}
else if (x == Py_None)
c = NULL;
else {
Py_DECREF(a); Py_DECREF(b);
Py_RETURN_NOTIMPLEMENTED;
}
...
if (c) {
if (_PyLong_IsZero(c)) {
PyErr_SetString(PyExc_ValueError,
"pow() 3rd argument cannot be 0");
goto Error;
}
-
Documentation: builtins.pow
Feature
Reproduction
RustPython:
CPython:
Root cause
PyInt::modpowdowncastsmodulustoPyIntand immediately raisesValueError("pow() 3rd argument cannot be 0")when it is zero, without first validating thatotheris an integer. CPython'slong_powinstead runsCHECK_BINOP(v, w)and thextype-check before the zero-modulus branch, so a non-integer exponent (or non-int/non-Nonemodulus) returnsNotImplementedand the dispatch falls through to aTypeError.The same observation applies to the related variant
pow(True, -3939.40..., False): oncePyInt::modpowcorrectly returnsNotImplementedfor a non-int exponent, theternary_opfallback dispatches toPyFloat::AS_NUMBER.power, which raises the expectedTypeError("pow() 3rd argument not allowed unless all arguments are integers").Fix
In
PyInt::modpow, downcastothertoPyIntfirst and returnNotImplementedif the cast fails; only then perform the zero-modulus check.Environment
Python Documentation or reference to CPython source code
CPython
long_pow:Objects/longobject.c#L4888-L4941— type-check (CHECK_BINOP,PyLong_Check(x)) runs before the zero-check:Documentation:
builtins.pow