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={

Reply via email to