Package: src:numba Version: 0.65.1+dfsg-3 User: [email protected] Usertags: python3.15 Tags: patch
Hi! While rebuilding the python related packages against the python version 3.15rc1 we found that python-gevent fails to build from source [1]. Upstream is currently working on adding the support, and the open pr can be seen in https://github.com/numba/numba/pull/10732. But as numba was a blocker to build other packages I used a backport of the changes in that pr to keep building the packages that depend on numba. I'm attaching the backported and squashed upstream patch. Happy hacking, [1]: https://debusine.debian.net/debian/r-python-python3.15/work-request/1014450/ -- "Can you imagine what I would do if I could do all I can?" -- Sun Tzu Saludos /\/\ /\ >< `/
Description: Support Python 3.15 Backport fixes from upstream PR #10732 to support Python 3.15. Includes updates to setup.py, C/C++ extension modules, bytecode analysis, byteflow, interpreter, and test suite. Origin: backport, https://github.com/numba/numba/pull/10732 Forwarded: not-needed Index: numba/numba/_dispatcher.cpp =================================================================== --- numba.orig/numba/_dispatcher.cpp +++ numba/numba/_dispatcher.cpp @@ -27,7 +27,7 @@ * */ -#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) #ifndef Py_BUILD_CORE #define Py_BUILD_CORE 1 @@ -791,7 +791,7 @@ call_cfunc(Dispatcher *self, PyObject *c } } -#elif (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#elif (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) // Python 3.12 has a completely new approach to tracing and profiling due to // the new `sys.monitoring` system. @@ -1672,7 +1672,7 @@ static PyTypeObject DispatcherType = { }; -#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) static bool is_sysmon_enabled(Dispatcher * self) { return self->enable_sysmon; Index: numba/numba/_dynfunc.c =================================================================== --- numba.orig/numba/_dynfunc.c +++ numba/numba/_dynfunc.c @@ -8,8 +8,8 @@ #include <string.h> -// if python version is 3.13 -#if ((PY_MAJOR_VERSION == 3) && ((PY_MINOR_VERSION == 13) || PY_MINOR_VERSION == 14)) +// if python version is 3.13+ +#if ((PY_MAJOR_VERSION == 3) && ((PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15))) #include "pythoncapi_compat.h" #define _Py_IsFinalizing Py_IsFinalizing #endif Index: numba/numba/_helperlib.c =================================================================== --- numba.orig/numba/_helperlib.c +++ numba/numba/_helperlib.c @@ -839,7 +839,7 @@ static void traceback_add(const char *fu if (!frame) goto error; -#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) #elif (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION == 11) /* 3.11 */ /* unsafe cast to our copy of _frame to access the f_lineno field */ @@ -857,7 +857,7 @@ static void traceback_add(const char *fu Py_DECREF(frame); return; -#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) error: _PyErr_ChainExceptions1(exc); #elif (PY_MAJOR_VERSION == 3) && ((PY_MINOR_VERSION == 10) || (PY_MINOR_VERSION == 11)) /* 3.11 and below */ Index: numba/numba/_pymodule.h =================================================================== --- numba.orig/numba/_pymodule.h +++ numba/numba/_pymodule.h @@ -46,6 +46,6 @@ Py_DECREF(tmp); } while (0) -#define NB_SUPPORTED_PYTHON_MINOR ((PY_MINOR_VERSION == 10) || (PY_MINOR_VERSION == 11) || (PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#define NB_SUPPORTED_PYTHON_MINOR ((PY_MINOR_VERSION == 10) || (PY_MINOR_VERSION == 11) || (PY_MINOR_VERSION == 12) || (PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) #endif /* NUMBA_PY_MODULE_H_ */ Index: numba/numba/_typeof.cpp =================================================================== --- numba.orig/numba/_typeof.cpp +++ numba/numba/_typeof.cpp @@ -16,7 +16,7 @@ #include <numpy/npy_2_compat.h> #endif -#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14)) +#if (PY_MAJOR_VERSION >= 3) && ((PY_MINOR_VERSION == 13) || (PY_MINOR_VERSION == 14) || (PY_MINOR_VERSION == 15)) #ifndef Py_BUILD_CORE #define Py_BUILD_CORE 1 #endif Index: numba/numba/core/bytecode.py =================================================================== --- numba.orig/numba/core/bytecode.py +++ numba/numba/core/bytecode.py @@ -10,7 +10,7 @@ from numba.core import errors, utils, se from numba.core.utils import PYVERSION -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): from opcode import _inline_cache_entries # Instruction/opcode length in bytes INSTR_LEN = 2 @@ -105,7 +105,7 @@ class ByteCodeInst(object): # https://bugs.python.org/issue27129 # https://github.com/python/cpython/pull/25069 assert self.is_jump - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): if self.opcode in (dis.opmap[k] for k in ["JUMP_BACKWARD", "JUMP_BACKWARD_NO_INTERRUPT"]): @@ -127,7 +127,7 @@ class ByteCodeInst(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): if self.opcode in JREL_OPS: return self.next + self.arg * 2 else: @@ -159,7 +159,7 @@ NO_ARG_LEN = 1 OPCODE_NOP = dis.opname.index('NOP') -if PYVERSION in ((3, 13), (3, 14)): +if PYVERSION in ((3, 13), (3, 14), (3, 15)): def _unpack_opargs(code): buf = [] @@ -383,7 +383,7 @@ class _ByteCode(object): def _fix_LOAD_GLOBAL_arg(arg): - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): return arg >> 1 elif PYVERSION in ((3, 10),): return arg @@ -484,6 +484,17 @@ class ByteCodePy312(ByteCodePy311): Update for Python 3.13.1, there's now a GET_ITER before FOR_ITER. This patch the GET_ITER to NOP to minimize changes downstream (e.g. array-comprehension). + + Update for Python 3.15, a ternary ``x if c else y`` in a + comprehension inserts NOT_TAKEN and splits the exception + table into two same-target entries: + + ExceptionTable: + L1 to L3 -> L9 # ends at NOT_TAKEN + L4 to L8 -> L9 # resumes after NOT_TAKEN + + Merge those halves so the pattern below still matches + (e.g. array-comprehension). """ def pop_and_merge_exceptions(entries: list, entry_to_remove: _ExceptionTableEntry): @@ -542,16 +553,41 @@ class ByteCodePy312(ByteCodePy311): # In Python 3.13.4, this becomes the only GET_ITER, # so don't turn it into a NOP. # Python 3.13.5 reverted the change. - if sys.version_info[:3] != (3, 13, 4): - # Add the inst to potentially be replaced to NOP. - current_nop_fixes.add(next_inst) + # Python 3.15 likewise keeps the GET_ITER. + if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if sys.version_info[:3] != (3, 13, 4): + # Add the inst to potentially be replaced to NOP. + current_nop_fixes.add(next_inst) + elif PYVERSION in ((3, 15),): + pass + else: + raise NotImplementedError(PYVERSION) # Loop up next instruction. next_inst = self.table[self.ordered_offsets[index + 3]] if not next_inst.opname == "FOR_ITER": continue - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 15),): + # NOT_TAKEN may split one region into two same-target + # entries. Merge them and retry. + end_inst = self.table.get(entry.end) + if (entry in entries + and end_inst is not None + and end_inst.opname == "NOT_TAKEN"): + i = entries.index(entry) + if (i + 1 < len(entries) + and entries[i + 1].target == entry.target + and entries[i + 1].start == end_inst.next): + nxt = entries[i + 1] + entries[i] = _ExceptionTableEntry( + entry.start, nxt.end, entry.target, + entry.depth, nxt.lasti) + entries.pop(i + 1) + work_remaining = True + continue + + if PYVERSION in ((3, 13), (3, 14), (3, 15)): # Check end of pattern, two instructions. # Check for the corresponding END_FOR, exception table end # is non-inclusive, so subtract one. @@ -563,7 +599,7 @@ class ByteCodePy312(ByteCodePy311): if PYVERSION in ((3, 13), ): if not next_inst.opname == "POP_TOP": continue - elif PYVERSION in ((3, 14), ): + elif PYVERSION in ((3, 14), (3, 15)): if not next_inst.opname == "POP_ITER": continue else: @@ -609,7 +645,7 @@ class ByteCodePy312(ByteCodePy311): if PYVERSION == (3, 11): ByteCode = ByteCodePy311 -elif PYVERSION in ((3, 12), (3, 13), (3, 14)): +elif PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): ByteCode = ByteCodePy312 elif PYVERSION < (3, 11): ByteCode = _ByteCode Index: numba/numba/core/byteflow.py =================================================================== --- numba.orig/numba/core/byteflow.py +++ numba/numba/core/byteflow.py @@ -24,7 +24,7 @@ _NO_RAISE_OPS = frozenset({ 'PRECALL', }) -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): from enum import Enum # Operands for CALL_INTRINSIC_1 @@ -141,7 +141,7 @@ class Flow(object): self.block_infos[state.pc_initial] = si = adapt_state_infos(state) _logger.debug("block_infos %s:\n%s", state, si) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def _run_handle_exception(self, runner, state): if not state.in_with() and ( state.has_active_try() and @@ -293,7 +293,7 @@ class Flow(object): else: return False - if PYVERSION in ((3, 14),): + if PYVERSION in ((3, 14), (3, 15)): def _guard_with_as(self, state): # Handled as part of `LOAD_SPECIAL` as of 3.14. pass @@ -330,7 +330,7 @@ class TraceRunner(object): return Loc(self.debug_filename, lineno) def dispatch(self, state): - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): if state._blockstack: state: State while state._blockstack: @@ -379,7 +379,7 @@ class TraceRunner(object): def op_NOP(self, state, inst): state.append(inst) - if PYVERSION in ((3,14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14 op_NOT_TAKEN = op_NOP elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -413,7 +413,7 @@ class TraceRunner(object): state.push(state.make_temp()) state.append(inst) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_FORMAT_SIMPLE(self, state, inst): value = state.pop() strvar = state.make_temp() @@ -463,7 +463,12 @@ class TraceRunner(object): def op_POP_TOP(self, state, inst): state.pop() - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 15), ): + # 3.15: POP_ITER pops both iterator and iterable (stack effect -2) + def op_POP_ITER(self, state, inst): + state.pop() + state.pop() + elif PYVERSION in ((3, 14), ): # New in 3.14 op_POP_ITER = op_POP_TOP elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -471,7 +476,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 13), (3,14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_TO_BOOL(self, state, inst): res = state.make_temp() tos = state.pop() @@ -482,7 +487,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_LOAD_GLOBAL(self, state, inst): # Ordering of the global value and NULL is swapped in Py3.13 res = state.make_temp() @@ -526,7 +531,7 @@ class TraceRunner(object): state.push(res) state.append(inst, res=res) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14 def op_LOAD_SMALL_INT(self, state, inst): assert 0 <= inst.arg < 256 @@ -541,7 +546,7 @@ class TraceRunner(object): def op_LOAD_ATTR(self, state, inst): item = state.pop() res = state.make_temp() - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): state.push(res) # the attr if inst.arg & 1: state.push(state.make_null()) @@ -556,7 +561,7 @@ class TraceRunner(object): state.append(inst, item=item, res=res) def op_LOAD_FAST(self, state, inst): - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): try: name = state.get_varname(inst) except IndexError: # oparg is out of range @@ -583,7 +588,7 @@ class TraceRunner(object): state.append(inst, res=res) state.push(res) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_LOAD_FAST_LOAD_FAST(self, state, inst): oparg = inst.arg oparg1 = oparg >> 4 @@ -616,7 +621,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): op_LOAD_FAST_CHECK = op_LOAD_FAST op_LOAD_FAST_AND_CLEAR = op_LOAD_FAST elif PYVERSION in ((3, 10), (3, 11)): @@ -624,7 +629,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14),): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14. op_LOAD_FAST_BORROW = op_LOAD_FAST op_LOAD_FAST_BORROW_LOAD_FAST_BORROW = op_LOAD_FAST_LOAD_FAST @@ -884,7 +889,7 @@ class TraceRunner(object): ) state.push(res) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_BINARY_SLICE(self, state, inst): end = state.pop() start = state.pop() @@ -902,7 +907,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_STORE_SLICE(self, state, inst): end = state.pop() start = state.pop() @@ -935,7 +940,7 @@ class TraceRunner(object): op_POP_JUMP_IF_TRUE = _op_POP_JUMP_IF op_POP_JUMP_IF_FALSE = _op_POP_JUMP_IF - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): op_POP_JUMP_IF_NONE = _op_POP_JUMP_IF op_POP_JUMP_IF_NOT_NONE = _op_POP_JUMP_IF elif PYVERSION in ((3, 10), (3, 11)): @@ -1001,7 +1006,7 @@ class TraceRunner(object): state.append(inst, retval=state.pop(), castval=state.make_temp()) state.terminate() - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_RETURN_CONST(self, state, inst): res = state.make_temp("const") state.append(inst, retval=res, castval=state.make_temp()) @@ -1017,7 +1022,7 @@ class TraceRunner(object): state.append(inst, value=val, res=res) state.push(res) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_RAISE_VARARGS(self, state, inst): if inst.arg == 0: exc = None @@ -1073,7 +1078,7 @@ class TraceRunner(object): blk = state.pop_block() state.reset_stack(blk['entry_stack']) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_END_FOR(self, state, inst): state.pop() elif PYVERSION in ((3, 12),): @@ -1115,7 +1120,7 @@ class TraceRunner(object): end=inst.get_jump_target(), ) ) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # Replaced by LOAD_SPECIAL in 3.14. pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -1202,7 +1207,7 @@ class TraceRunner(object): 'FINALLY', state, next=inst.next, end=inst.get_jump_target(), ) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_POP_EXCEPT(self, state, inst): state.pop() @@ -1230,7 +1235,7 @@ class TraceRunner(object): state.append(inst, kind='with') state.fork(pc=inst.next) - if PYVERSION in ((3, 14),): + if PYVERSION in ((3, 14), (3, 15)): # Removed in 3.14 -- replaced with BINARY_OP and [] pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -1257,7 +1262,7 @@ class TraceRunner(object): def op_CALL(self, state, inst): narg = inst.arg args = list(reversed([state.pop() for _ in range(narg)])) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): null_or_self = state.pop() # position of the callable is fixed callable = state.pop() @@ -1300,7 +1305,7 @@ class TraceRunner(object): state.append(inst, func=func, args=args, names=names, res=res) state.push(res) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_CALL_KW(self, state, inst): narg = inst.arg kw_names = state.pop() @@ -1320,7 +1325,7 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): def op_CALL_FUNCTION_EX(self, state, inst): # (func, unused, callargs, kwargs -- result)) # In 3.14 CALL_FUNCTION_EX always take a kwargs argument @@ -1388,7 +1393,7 @@ class TraceRunner(object): for val in duped: state.push(val) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_CALL_INTRINSIC_1(self, state, inst): # See https://github.com/python/cpython/blob/v3.12.0rc2/Include/ # internal/pycore_intrinsics.h#L3-L17C36 @@ -1600,7 +1605,14 @@ class TraceRunner(object): res=res) def op_GET_ITER(self, state, inst): - value = state.pop() + if PYVERSION in ((3, 15), ): + # 3.15: GET_ITER keeps iterable on stack and pushes iterator + # on top. POP_ITER later cleans up both. + value = state.get_tos() + elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14)): + value = state.pop() + else: + raise NotImplementedError(PYVERSION) res = state.make_temp() state.append(inst, value=value, res=res) state.push(res) @@ -1614,7 +1626,7 @@ class TraceRunner(object): pred=pred) state.push(indval) end = inst.get_jump_target() - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): # Changed in version 3.12: Up until 3.11 the iterator was # popped when it was exhausted. Now this is handled using END_FOR # op code. @@ -1641,8 +1653,8 @@ class TraceRunner(object): rhs = state.pop() lhs = state.pop() if op == '[]': - # Special case 3.14 -- body of BINARY_SUBSCR now here - assert PYVERSION == (3, 14) + # Special case 3.14+ -- body of BINARY_SUBSCR now here + assert PYVERSION in ((3, 14), (3, 15)) res = state.make_temp() state.append(inst, op=op, lhs=lhs, rhs=rhs, res=res) state.push(res) @@ -1707,7 +1719,7 @@ class TraceRunner(object): op_BINARY_XOR = _binaryop def op_MAKE_FUNCTION(self, state, inst, MAKE_CLOSURE=False): - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # https://github.com/python/cpython/commit/2f180ce # name set via co_qualname name = None @@ -1717,7 +1729,7 @@ class TraceRunner(object): raise NotImplementedError(PYVERSION) code = state.pop() closure = annotations = annotate = kwdefaults = defaults = None - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): assert inst.arg is None # SET_FUNCTION_ATTRIBUTE is responsible for setting # closure, annotations, annotate, kwdefaults and defaults. @@ -1745,7 +1757,7 @@ class TraceRunner(object): state.push(res) def op_SET_FUNCTION_ATTRIBUTE(self, state, inst): - assert PYVERSION in ((3, 13), (3, 14)) + assert PYVERSION in ((3, 13), (3, 14), (3, 15)) make_func_stack = state.pop() data = state.pop() if inst.arg & 0x01: @@ -1777,7 +1789,7 @@ class TraceRunner(object): state.append(inst, res=res) state.push(res) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # Removed in 3.14 pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -1805,7 +1817,7 @@ class TraceRunner(object): state.fork(pc=inst.next) state.fork(pc=inst.get_jump_target()) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3,14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_RERAISE(self, state, inst): # This isn't handled, but the state is set up anyway exc = state.pop() @@ -1830,7 +1842,7 @@ class TraceRunner(object): # NOTE: Please see notes in `interpreter.py` surrounding the implementation # of LOAD_METHOD and CALL_METHOD. - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): # LOAD_METHOD has become a pseudo-instruction in 3.12 pass elif PYVERSION in ((3, 11), ): @@ -1850,7 +1862,7 @@ class TraceRunner(object): def op_CALL_METHOD(self, state, inst): self.op_CALL_FUNCTION(state, inst) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14, replaces BEFORE_WITH. def op_LOAD_SPECIAL(self, state, inst): @@ -1970,14 +1982,17 @@ class TraceRunner(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): def op_LOAD_COMMON_CONSTANT(self, state, inst): + # Keep in sync with dis._common_constants and + # interpreter.op_LOAD_COMMON_CONSTANT. oparg = inst.arg - if dis._common_constants[oparg] == AssertionError: - name = 'assertion_error' - else: - raise NotImplementedError - res = state.make_temp(name) + const = dis._common_constants[oparg] + if const not in (AssertionError, NotImplementedError, + tuple, all, any, list, set, + None, '', True, False, -1): + raise NotImplementedError(const) + res = state.make_temp('common_constant') state.append(inst, res=res, idx=oparg) state.push(res) elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -2246,7 +2261,7 @@ class _State(object): stack.append(self.make_temp()) # Handle changes on the blockstack blockstack = list(self._blockstack) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # pop expired block in destination pc while blockstack: top = blockstack[-1] @@ -2346,7 +2361,7 @@ class StatePy313(StatePy311): return self._make_func_attrs[make_func_res] -if PYVERSION in ((3, 13), (3, 14)): +if PYVERSION in ((3, 13), (3, 14), (3, 15)): State = StatePy313 elif PYVERSION in ((3, 11), (3, 12)): State = StatePy311 @@ -2384,7 +2399,7 @@ def adapt_state_infos(state): if inst.opname == "MAKE_FUNCTION": data.update(state.get_function_attributes(data['res'])) return offset, data - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): insts = tuple(map(process_function_attributes, state.instructions)) elif PYVERSION in ((3, 10), (3, 11), (3, 12)): insts = tuple(state.instructions) Index: numba/numba/core/controlflow.py =================================================================== --- numba.orig/numba/core/controlflow.py +++ numba/numba/core/controlflow.py @@ -8,7 +8,7 @@ from numba.core.utils import PYVERSION # List of bytecodes creating a new block in the control flow graph # (in addition to explicit jump labels). -if PYVERSION in ((3, 14),): +if PYVERSION in ((3, 14), (3, 15)): NEW_BLOCKERS = frozenset([ 'SETUP_LOOP', 'FOR_ITER', 'SETUP_WITH', 'BEFORE_WITH', 'LOAD_SPECIAL' ]) @@ -961,7 +961,7 @@ class ControlFlowAnalysis(object): self._curblock.terminating = True self._force_new_block = True - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_RETURN_CONST(self, inst): self._curblock.terminating = True self._force_new_block = True Index: numba/numba/core/interpreter.py =================================================================== --- numba.orig/numba/core/interpreter.py +++ numba/numba/core/interpreter.py @@ -19,7 +19,7 @@ from numba.core.unsafe import eh from numba.cpython.unsafe.tuple import unpack_single_tuple -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): # Operands for CALL_INTRINSIC_1 from numba.core.byteflow import CALL_INTRINSIC_1_Operand as ci1op elif PYVERSION in ((3, 10), (3, 11)): @@ -1389,7 +1389,7 @@ class Interpreter(object): max(inst_blocks.body)) self.last_active_offset = last_active_offset - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): self.active_exception_entries = tuple( [entry for entry in self.bytecode.exception_entries if entry.start < self.last_active_offset]) @@ -1405,7 +1405,7 @@ class Interpreter(object): # Interpret loop for inst, kws in self._iter_inst(): self._dispatch(inst, kws) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # Insert end of try markers self._end_try_blocks() elif PYVERSION in ((3, 10),): @@ -1423,12 +1423,12 @@ class Interpreter(object): # post process the IR to rewrite opcodes/byte sequences that are too # involved to risk handling as part of direct interpretation peepholes = [] - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): peepholes.append(peep_hole_split_at_pop_block) - if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): peepholes.append(peep_hole_list_to_tuple) peepholes.append(peep_hole_delete_with_exit) - if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # peep_hole_call_function_ex_to_call_function_kw # depends on peep_hole_list_to_tuple converting # any large number of arguments from a list to a @@ -1461,7 +1461,7 @@ class Interpreter(object): See also: _insert_try_block_end """ - assert PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)) + assert PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)) graph = self.cfa.graph for offset, block in self.blocks.items(): # Get current blockstack @@ -1569,7 +1569,7 @@ class Interpreter(object): self.dfainfo = self.dfa.infos[self.current_block_offset] self.assigner = Assigner() # Check out-of-scope syntactic-block - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # This is recreating pre-3.11 code structure while self.syntax_blocks: if offset >= self.syntax_blocks[-1].exit: @@ -1740,7 +1740,8 @@ class Interpreter(object): val = self.get(varname) except ir.NotDefinedError: # Hack to make sure exception variables are defined - assert PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)), \ + assert PYVERSION in ((3, 11), (3, 12), (3, 13), + (3, 14), (3, 15)), \ "unexpected missing definition" val = ir.Const(value=None, loc=self.loc) stmt = ir.Assign(value=val, target=target, @@ -1800,7 +1801,7 @@ class Interpreter(object): if self._DEBUG_PRINT: print(inst) assert self.current_block is not None - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): if self.syntax_blocks: top = self.syntax_blocks[-1] if isinstance(top, ir.With) : @@ -1887,7 +1888,7 @@ class Interpreter(object): def op_NOP(self, inst): pass - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14 op_NOT_TAKEN = op_NOP elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -1991,7 +1992,7 @@ class Interpreter(object): (), loc=self.loc) self.store(value=sliceinst, name=res) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_BINARY_SLICE(self, inst, start, end, container, res, slicevar, temp_res): start = self.get(start) @@ -2010,7 +2011,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_STORE_SLICE(self, inst, start, end, container, value, res, slicevar): start = self.get(start) @@ -2242,7 +2243,7 @@ class Interpreter(object): srcname = self.code_locals[inst.arg] self.store(value=self.get(srcname), name=res) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_LOAD_FAST(self, inst, res, as_load_deref=False): if as_load_deref: self.op_LOAD_DEREF(inst, res) @@ -2252,7 +2253,7 @@ class Interpreter(object): else: op_LOAD_FAST = _op_LOAD_FAST - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_LOAD_FAST_LOAD_FAST(self, inst, res1, res2): oparg = inst.arg oparg1 = oparg >> 4 @@ -2289,7 +2290,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): op_LOAD_FAST_CHECK = op_LOAD_FAST def op_LOAD_FAST_AND_CLEAR(self, inst, res): @@ -2307,7 +2308,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14),): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14. op_LOAD_FAST_BORROW = op_LOAD_FAST op_LOAD_FAST_BORROW_LOAD_FAST_BORROW = op_LOAD_FAST_LOAD_FAST @@ -2346,7 +2347,7 @@ class Interpreter(object): def op_LOAD_ATTR(self, inst, item, res): item = self.get(item) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): attr = self.code_names[inst.arg >> 1] elif PYVERSION in ((3, 10), (3, 11)): attr = self.code_names[inst.arg] @@ -2404,7 +2405,7 @@ class Interpreter(object): const = ir.Const(value, loc=self.loc) self.store(const, res) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14 def op_LOAD_SMALL_INT(self, inst, res): value = inst.arg @@ -2415,7 +2416,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_LOAD_GLOBAL(self, inst, idx, res): name = self.code_names[idx] value = self.get_global_value(name) @@ -2433,7 +2434,7 @@ class Interpreter(object): def op_COPY_FREE_VARS(self, inst): pass - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_LOAD_DEREF(self, inst, res): name = self.func_id.func.__code__._varname_from_oparg(inst.arg) if name in self.code_cellvars: @@ -2462,11 +2463,11 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_MAKE_CELL(self, inst): pass # ignored bytecode - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_STORE_DEREF(self, inst, value): name = self.func_id.func.__code__._varname_from_oparg(inst.arg) value = self.get(value) @@ -2504,7 +2505,7 @@ class Interpreter(object): exit_fn_obj = ir.Const(None, loc=self.loc) self.store(value=exit_fn_obj, name=exitfn) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # Replaced by LOAD_SPECIAL in 3.14. pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -2571,7 +2572,7 @@ class Interpreter(object): expr = ir.Expr.call(func, args, kwargs, loc=self.loc) self.store(expr, res) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_CALL_KW(self, inst, func, args, kw_names, res): func = self.get(func) args = [self.get(x) for x in args] @@ -2770,7 +2771,7 @@ class Interpreter(object): loc=self.loc) self.current_block.append(br) - if PYVERSION in ((3, 14),): + if PYVERSION in ((3, 14), (3, 15)): # Removed in 3.14 -- replaced with BINARY_OP and [] pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -2931,7 +2932,7 @@ class Interpreter(object): def op_BINARY_OP(self, inst, op, lhs, rhs, res): if op == "[]": - # Special case 3.14 -- body of BINARY_SUBSCR now here + # Special case 3.14+: body of BINARY_SUBSCR now here lhs = self.get(lhs) rhs = self.get(rhs) expr = ir.Expr.getitem(lhs, index=rhs, loc=self.loc) @@ -3053,7 +3054,7 @@ class Interpreter(object): ret = ir.Return(self.get(castval), loc=self.loc) self.current_block.append(ret) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_RETURN_CONST(self, inst, retval, castval): value = self.code_consts[inst.arg] const = ir.Const(value, loc=self.loc) @@ -3066,7 +3067,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): def op_TO_BOOL(self, inst, val, res): self.store(self.get(val), res) # TODO: just a lazy hack @@ -3076,7 +3077,7 @@ class Interpreter(object): raise NotImplementedError(PYVERSION) def op_COMPARE_OP(self, inst, lhs, rhs, res): - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): op = dis.cmp_op[inst.arg >> 5] # TODO: fifth lowest bit now indicates a forced version to bool. elif PYVERSION in ((3, 12),): @@ -3197,7 +3198,7 @@ class Interpreter(object): def op_POP_JUMP_FORWARD_IF_NOT_NONE(self, inst, pred): self._jump_if_none(inst, pred, False) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_POP_JUMP_IF_NONE(self, inst, pred): self._jump_if_none(inst, pred, True) @@ -3341,7 +3342,7 @@ class Interpreter(object): self.op_MAKE_FUNCTION(inst, name, code, closure, annotations, kwdefaults, defaults, res) - if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): def op_LOAD_CLOSURE(self, inst, res): name = self.func_id.func.__code__._varname_from_oparg(inst.arg) if name in self.code_cellvars: @@ -3461,7 +3462,7 @@ class Interpreter(object): loc=self.loc) self.store(value=appendinst, name=res) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # Removed in 3.14 pass elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -3485,7 +3486,7 @@ class Interpreter(object): def op_CALL_METHOD(self, *args, **kws): self.op_CALL_FUNCTION(*args, **kws) - if PYVERSION in ((3, 12), (3, 13), (3, 14)): + if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): def op_CALL_INTRINSIC_1(self, inst, operand, **kwargs): if operand == ci1op.INTRINSIC_STOPITERATION_ERROR: stmt = ir.StaticRaise(INTRINSIC_STOPITERATION_ERROR, (), @@ -3505,7 +3506,7 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # New in 3.14, replaces BEFORE_WITH. def op_LOAD_SPECIAL(self, inst, contextmanager, exit_method, block_end): assert self.blocks[inst.offset] is self.current_block @@ -3534,22 +3535,28 @@ class Interpreter(object): else: raise NotImplementedError(PYVERSION) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): def op_LOAD_COMMON_CONSTANT(self, inst, res, idx): - if dis._common_constants[idx] == AssertionError: - gv_fn = ir.Global("AssertionError", - AssertionError, - loc=self.loc) - self.store(value=gv_fn, name=res) + # Keep in sync with dis._common_constants and + # byteflow.op_LOAD_COMMON_CONSTANT. + # Types/callables become ir.Global (like LOAD_GLOBAL); + # literals become ir.Const (like LOAD_CONST). + const = dis._common_constants[idx] + if const in (AssertionError, NotImplementedError, + tuple, all, any, list, set): + value = ir.Global(const.__name__, const, loc=self.loc) + elif const in (None, '', True, False, -1): + value = ir.Const(const, loc=self.loc) else: - raise NotImplementedError + raise NotImplementedError(const) + self.store(value=value, name=res) elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): pass else: raise NotImplementedError(PYVERSION) -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): class INTRINSIC_STOPITERATION_ERROR(AssertionError): pass elif PYVERSION in ((3, 10), (3, 11)): Index: numba/numba/core/typing/asnumbatype.py =================================================================== --- numba.orig/numba/core/typing/asnumbatype.py +++ numba/numba/core/typing/asnumbatype.py @@ -41,7 +41,7 @@ class AsNumbaTypeRegistry: return py_type def _builtin_infer(self, py_type): - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # As of 3.14 the typing module has been updated to return a # different type when calling: `typing.Optional[X]`. # Index: numba/numba/cpython/unicode.py =================================================================== --- numba.orig/numba/cpython/unicode.py +++ numba/numba/cpython/unicode.py @@ -349,7 +349,7 @@ def _set_code_point(a, i, ch): "Unexpected unicode representation in _set_code_point") -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): @register_jitable def _pick_kind(kind1, kind2): if kind1 == PY_UNICODE_1BYTE_KIND: @@ -393,7 +393,7 @@ def _pick_ascii(is_ascii1, is_ascii2): return types.uint32(0) -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): @register_jitable def _kind_to_byte_width(kind): if kind == PY_UNICODE_1BYTE_KIND: Index: numba/numba/experimental/jitclass/base.py =================================================================== --- numba.orig/numba/experimental/jitclass/base.py +++ numba/numba/experimental/jitclass/base.py @@ -282,7 +282,7 @@ def _drop_ignored_attrs(dct): drop = set(['__weakref__', '__module__', '__dict__']) - if utils.PYVERSION in ((3, 13), (3, 14)): + if utils.PYVERSION in ((3, 13), (3, 14), (3, 15)): # new in python 3.13 drop |= set(['__firstlineno__', '__static_attributes__']) Index: numba/numba/tests/support.py =================================================================== --- numba.orig/numba/tests/support.py +++ numba/numba/tests/support.py @@ -113,6 +113,10 @@ skip_if_py314= unittest.skipIf( utils.PYVERSION == (3, 14), "Test unstable on 3.14" ) +skip_if_py315= unittest.skipIf( + utils.PYVERSION == (3, 15), "Test unstable on 3.15" + ) + skip_if_linux_aarch64 = unittest.skipIf( sys.platform.startswith('linux') and platform.machine() == 'aarch64', "Not supported on Linux aarch64" @@ -173,6 +177,13 @@ def expected_failure_py314(fn): return unittest.expectedFailure(fn) else: return fn + + +def expected_failure_py315(fn): + if utils.PYVERSION == (3, 15): + return unittest.expectedFailure(fn) + else: + return fn def expected_failure_np2(fn): Index: numba/numba/tests/test_analysis.py =================================================================== --- numba.orig/numba/tests/test_analysis.py +++ numba/numba/tests/test_analysis.py @@ -654,7 +654,7 @@ class TestBranchPrunePredicates(TestBran _CONST2 = "PLACEHOLDER2" return _CONST2 + 4 - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # The order of the __code__.co_consts changes with 3.14 new = self._literal_const_sample_generator(impl, {0:0, 2:20}) elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): @@ -665,7 +665,7 @@ class TestBranchPrunePredicates(TestBran iconst = impl.__code__.co_consts nconst = new.__code__.co_consts - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): self.assertEqual(iconst, ("PLACEHOLDER1", 3.14159, "PLACEHOLDER2")) self.assertEqual(nconst, (0, 3.14159, 20)) @@ -689,7 +689,7 @@ class TestBranchPrunePredicates(TestBran for c_inp, prune in (self._TRUTHY, False), (self._FALSEY, True): for const in c_inp: - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # The order of the __code__.co_consts changes with 3.14 func = self._literal_const_sample_generator(impl, {0: const}) @@ -712,7 +712,7 @@ class TestBranchPrunePredicates(TestBran for c_inp, prune in (self._TRUTHY, False), (self._FALSEY, True): for const in c_inp: - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # The order of the __code__.co_consts changes with 3.14 func = self._literal_const_sample_generator(impl, {0: const}) @@ -737,7 +737,7 @@ class TestBranchPrunePredicates(TestBran for c_inp, prune in (self._TRUTHY, False), (self._FALSEY, True): for const in c_inp: - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # The order of the __code__.co_consts changes with 3.14 func = self._literal_const_sample_generator(impl, {0: const}) @@ -762,7 +762,7 @@ class TestBranchPrunePredicates(TestBran for c_inp, prune in (self._TRUTHY, False), (self._FALSEY, True): for const in c_inp: - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): # The order of the __code__.co_consts changes with 3.14 func = self._literal_const_sample_generator(impl, {0: const}) Index: numba/numba/tests/test_caching.py =================================================================== --- numba.orig/numba/tests/test_caching.py +++ numba/numba/tests/test_caching.py @@ -232,6 +232,19 @@ class BaseCacheTest(TestCase): sys.modules.pop(self.modname, None) sys.path.remove(self.tempdir) + @staticmethod + def _module_cached_path(module): + # Python 3.15 no longer sets module.__cached__, use + # __spec__.cached instead. See: + # https://docs.python.org/3/deprecations/pending-removal-in-3.15.html + cached = getattr(module, '__cached__', None) + if cached is not None: + return cached + spec = getattr(module, '__spec__', None) + if spec is not None: + return spec.cached + return None + def import_module(self): # Import a fresh version of the test module. All jitted functions # in the test module will start anew and load overloads from @@ -239,10 +252,10 @@ class BaseCacheTest(TestCase): old = sys.modules.pop(self.modname, None) if old is not None: # Make sure cached bytecode is removed - cached = [old.__cached__] - for fn in cached: + cached_path = self._module_cached_path(old) + if cached_path: try: - os.unlink(fn) + os.unlink(cached_path) except FileNotFoundError: pass mod = import_dynamic(self.modname) Index: numba/numba/tests/test_complex.py =================================================================== --- numba.orig/numba/tests/test_complex.py +++ numba/numba/tests/test_complex.py @@ -3,7 +3,7 @@ import math import sys from numba import jit, types -from numba.tests.support import TestCase, skip_if_py314 +from numba.tests.support import TestCase, skip_if_py314, skip_if_py315 from .complex_usecases import * import unittest @@ -115,6 +115,7 @@ class TestComplex(BaseComplexTest, TestC self.run_binary(div_usecase, value_types, values, flags=flags) @skip_if_py314 + @skip_if_py315 def test_div_npm(self): self.test_div(flags=no_pyobj_flags) @@ -209,6 +210,7 @@ class TestCMath(BaseComplexTest, TestCas ulps=3) @skip_if_py314 + @skip_if_py315 def test_log_base_npm(self): self.test_log_base(flags=no_pyobj_flags) Index: numba/numba/tests/test_debug.py =================================================================== --- numba.orig/numba/tests/test_debug.py +++ numba/numba/tests/test_debug.py @@ -73,7 +73,7 @@ class DebugTestBase(TestCase): self.assert_fails(check_meth, out) def _check_dump_bytecode(self, out): - if utils.PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if utils.PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): self.assertIn('BINARY_OP', out) elif utils.PYVERSION in ((3, 10),): self.assertIn('BINARY_ADD', out) Index: numba/numba/tests/test_dyn_array.py =================================================================== --- numba.orig/numba/tests/test_dyn_array.py +++ numba/numba/tests/test_dyn_array.py @@ -501,7 +501,7 @@ class TestDynArray(NrtRefCtTest, TestCas np.testing.assert_equal(expected_x, got_x) np.testing.assert_equal(expected_y, got_y) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): expected_refcount = 1 elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): expected_refcount = 2 @@ -528,7 +528,7 @@ class TestDynArray(NrtRefCtTest, TestCas arr = np.arange(10) old_refct = sys.getrefcount(arr) - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): self.assertEqual(2, sys.getrefcount(pyfunc(arr))) self.assertEqual(2, sys.getrefcount(cfunc(arr))) elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): Index: numba/numba/tests/test_exceptions.py =================================================================== --- numba.orig/numba/tests/test_exceptions.py +++ numba/numba/tests/test_exceptions.py @@ -8,6 +8,7 @@ from numba.tests.support import (TestCas expected_failure_py312, expected_failure_py313, expected_failure_py314, + expected_failure_py315, ) import unittest @@ -444,6 +445,7 @@ class TestRaising(TestCase): @expected_failure_py312 @expected_failure_py313 @expected_failure_py314 + @expected_failure_py315 def test_dynamic_raise(self): @njit Index: numba/numba/tests/test_extended_arg.py =================================================================== --- numba.orig/numba/tests/test_extended_arg.py +++ numba/numba/tests/test_extended_arg.py @@ -4,7 +4,6 @@ import dis import struct from numba import jit -from numba.core import utils from numba.tests.support import TestCase @@ -26,12 +25,10 @@ class TestExtendedArg(TestCase): consts = f.__code__.co_consts bytecode_format = "<BB" consts = consts + (None,) * self.bytecode_len + (42,) - if utils.PYVERSION >= (3, 11): - # Python 3.11 has a RESUME op code at the start of a function, need - # to inject the EXTENDED_ARG after this to influence the LOAD_CONST - offset = 2 # 2 byte op code - else: - offset = 0 + offset = next( + i for i in range(0, len(b), 2) + if b[i] == dis.opmap['LOAD_CONST'] + ) packed_extend_arg = struct.pack(bytecode_format, dis.EXTENDED_ARG, 1) b[:] = b[:offset] + packed_extend_arg + b[offset:] Index: numba/numba/tests/test_ir_inlining.py =================================================================== --- numba.orig/numba/tests/test_ir_inlining.py +++ numba/numba/tests/test_ir_inlining.py @@ -444,7 +444,7 @@ class TestFunctionInlining(MemoryLeakMix return bar(z + 2) # block count changes with Python version due to bytecode differences. - if utils.PYVERSION in ((3, 12), (3, 13), (3, 14)): + if utils.PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): bc = 39 elif utils.PYVERSION in ((3, 10), (3, 11)): bc = 35 Index: numba/numba/tests/test_object_mode.py =================================================================== --- numba.orig/numba/tests/test_object_mode.py +++ numba/numba/tests/test_object_mode.py @@ -91,7 +91,7 @@ class TestObjectMode(TestCase): # This tests 'None in None' with the python interpreter. The error # message has changed in 3.14. - if PYVERSION in ((3, 14), ): + if PYVERSION in ((3, 14), (3, 15)): expected_snippet = "is not a container or iterable" elif PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13)): expected_snippet = "is not iterable" Index: numba/numba/tests/test_operators.py =================================================================== --- numba.orig/numba/tests/test_operators.py +++ numba/numba/tests/test_operators.py @@ -766,7 +766,7 @@ class TestOperators(TestCase): cres(4j, 2j) # error message depends on Python version. - if utils.PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14)): + if utils.PYVERSION in ((3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): msg = "unsupported operand type(s) for %" else: raise NotImplementedError(utils.PYVERSION) Index: numba/numba/tests/test_parfors.py =================================================================== --- numba.orig/numba/tests/test_parfors.py +++ numba/numba/tests/test_parfors.py @@ -3567,7 +3567,7 @@ class TestPrangeBase(TestParforsBase): prange_names.append('prange') prange_names = tuple(prange_names) prange_idx = len(prange_names) - 1 - if utils.PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14)): + if utils.PYVERSION in ((3, 11), (3, 12), (3, 13), (3, 14), (3, 15)): # this is the inverse of _fix_LOAD_GLOBAL_arg prange_idx = 1 + (prange_idx << 1) elif utils.PYVERSION in ((3, 10),): Index: numba/numba/tests/test_sys_monitoring.py =================================================================== --- numba.orig/numba/tests/test_sys_monitoring.py +++ numba/numba/tests/test_sys_monitoring.py @@ -31,7 +31,7 @@ def generate_usecase(): return foo, call_foo -if PYVERSION in ((3, 12), (3, 13), (3, 14)): +if PYVERSION in ((3, 12), (3, 13), (3, 14), (3, 15)): PY_START = sys.monitoring.events.PY_START PY_RETURN = sys.monitoring.events.PY_RETURN RAISE = sys.monitoring.events.RAISE Index: numba/numba/tests/test_try_except.py =================================================================== --- numba.orig/numba/tests/test_try_except.py +++ numba/numba/tests/test_try_except.py @@ -15,6 +15,7 @@ from numba.tests.support import ( TestCase, unittest, captured_stdout, MemoryLeakMixin, skip_parfors_unsupported, skip_unless_scipy, expected_failure_py311, expected_failure_py312, expected_failure_py313, expected_failure_py314, + expected_failure_py315, ) @@ -695,6 +696,7 @@ class TestTryExceptOtherControlFlow(Test @expected_failure_py312 @expected_failure_py313 @expected_failure_py314 + @expected_failure_py315 def test_objmode(self): @njit def udt(): @@ -717,6 +719,7 @@ class TestTryExceptOtherControlFlow(Test @expected_failure_py312 @expected_failure_py313 @expected_failure_py314 + @expected_failure_py315 def test_objmode_output_type(self): def bar(x): return np.asarray(list(reversed(x.tolist()))) Index: numba/numba/tests/test_unicode.py =================================================================== --- numba.orig/numba/tests/test_unicode.py +++ numba/numba/tests/test_unicode.py @@ -2702,7 +2702,7 @@ class TestUnicodeAuxillary(BaseTest): unsupported_errors = (UnsupportedError, UnsupportedBytecodeError) with self.assertRaises(unsupported_errors) as raises: njit(impl4)(["A", "B"]) - if PYVERSION in ((3, 13), (3, 14)): + if PYVERSION in ((3, 13), (3, 14), (3, 15)): msg = "Use of unsupported opcode (FORMAT_WITH_SPEC)" self.assertIn(msg, str(raises.exception)) elif PYVERSION in ((3, 10), (3, 11), (3, 12)): Index: numba/numba/tests/test_withlifting.py =================================================================== --- numba.orig/numba/tests/test_withlifting.py +++ numba/numba/tests/test_withlifting.py @@ -19,6 +19,7 @@ from numba.tests.support import (MemoryL expected_failure_py312, expected_failure_py313, expected_failure_py314, + expected_failure_py315, ) from numba.core.utils import PYVERSION from numba.experimental import jitclass @@ -285,6 +286,7 @@ class TestLiftCall(BaseTestWithLifting): @expected_failure_py312 @expected_failure_py313 @expected_failure_py314 + @expected_failure_py315 def test_liftcall5(self): self.check_extracted_with(liftcall5, expect_count=1, expected_stdout="0\n1\n2\n3\n4\n5\nA\n") @@ -726,6 +728,7 @@ class TestLiftObj(MemoryLeak, TestCase): @expected_failure_py312 @expected_failure_py313 @expected_failure_py314 + @expected_failure_py315 def test_case19_recursion(self): def foo(x): with objmode_context(): Index: numba/setup.py =================================================================== --- numba.orig/setup.py +++ numba/setup.py @@ -20,7 +20,7 @@ except ImportError: min_python_version = "3.10" -max_python_version = "3.15" # exclusive +max_python_version = "3.16" # exclusive min_numpy_build_version = "2.0.0rc1" min_numpy_run_version = "1.22" max_numpy_run_version = "2.5" @@ -403,6 +403,7 @@ metadata = dict( "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: Software Development :: Compilers", ], package_data={

