numba: FTBFS building against python 3.15

Maximiliano Curia <[email protected]>
Newsgroups gmane.linux.debian.devel.python
Message-ID <[email protected]>
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 /\/\ /\ >< `/
0013-Python-3.15-support.patch (text/x-diff, 59.7 KB)
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={
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.