From: Siu Kwan Lam <1929845+sklam@users.noreply.github.com>
Origin: https://github.com/numba/numba/pull/8545
Date: Wed, 26 Oct 2022 12:53:47 -0500
Subject: Fix older python

---
 numba/core/bytecode.py |  30 +++++++++-----
 numba/core/byteflow.py | 106 +++++++++++++++++++++++++++++++------------------
 2 files changed, 87 insertions(+), 49 deletions(-)

diff --git a/numba/core/bytecode.py b/numba/core/bytecode.py
index 2a22bf7..2098173 100644
--- a/numba/core/bytecode.py
+++ b/numba/core/bytecode.py
@@ -227,16 +227,6 @@ class ByteCode(object):
         self.co_cellvars = code.co_cellvars
         self.co_freevars = code.co_freevars
 
-        def fixup_eh(ent):
-            from dis import _ExceptionTableEntry
-            out = _ExceptionTableEntry(
-                start=ent.start + 2, end=ent.end + 2, target=ent.target + 2,
-                depth=ent.depth, lasti=ent.lasti,
-            )
-            return out
-
-        entries = dis.Bytecode(code).exception_entries
-        self.exception_entries = tuple(map(fixup_eh, entries))
         self.table = table
         self.labels = sorted(labels)
 
@@ -316,6 +306,22 @@ class ByteCode(object):
         return self._compute_used_globals(self.func_id.func, self.table,
                                           self.co_consts, self.co_names)
 
+
+class ByteCodePy311(ByteCode):
+    def __init__(self, func_id):
+        super().__init__(func_id)
+
+        def fixup_eh(ent):
+            from dis import _ExceptionTableEntry
+            out = _ExceptionTableEntry(
+                start=ent.start + 2, end=ent.end + 2, target=ent.target + 2,
+                depth=ent.depth, lasti=ent.lasti,
+            )
+            return out
+
+        entries = dis.Bytecode(func_id.code).exception_entries
+        self.exception_entries = tuple(map(fixup_eh, entries))
+
     def find_exception_entry(self, offset):
         """
         Returns the exception entry for the given instruction offset
@@ -329,6 +335,10 @@ class ByteCode(object):
             return ent
 
 
+if PYVERSION >= (3, 11):
+    ByteCode = ByteCodePy311
+
+
 class FunctionIdentity(serialize.ReduceMixin):
     """
     A function's identity and metadata.
diff --git a/numba/core/byteflow.py b/numba/core/byteflow.py
index 853d470..5a49f99 100644
--- a/numba/core/byteflow.py
+++ b/numba/core/byteflow.py
@@ -106,42 +106,15 @@ class Flow(object):
                 _logger.debug("stack: %s", state._stack)
                 _logger.debug("state.pc_initial: %s", state)
                 first_encounter[state.pc_initial] = state
-
                 # Loop over the state until it is terminated.
                 while True:
                     runner.dispatch(state)
                     # Terminated?
                     if state.has_terminated():
                         break
-                    elif not state.in_with() and (
-                            state.has_active_try() and
-                            state.get_inst().opname not in _NO_RAISE_OPS):
-                        # Is in a *try* block
-                        state.fork(pc=state.get_inst().next)
-                        runner.handle_try(state)
-                        break
                     else:
-                        state.advance_pc()
-
-                        # Must the new PC be a new block?
-                        if PYVERSION == (3, 11):
-                            if not state.in_with() and state.is_in_exception():
-                                _logger.debug("3.11 exception %s PC=%s",
-                                              state.get_exception(), state._pc)
-                                eh = state.get_exception()
-                                eh_top = state.get_top_block('TRY')
-                                if eh_top and eh_top['end'] == eh.target:
-                                    # Same exception
-                                    eh_block = None
-                                else:
-                                    eh_block = state.make_block(
-                                        "TRY", end=eh.target)
-                                    eh_block['end_offset'] = eh.end
-                                    eh_block['stack_depth'] = eh.depth
-                                    eh_block['push_lasti'] = eh.lasti
-                                    state.fork(pc=state._pc,
-                                               extra_block=eh_block)
-                                    break
+                        if self._run_handle_exception(runner, state):
+                            break
 
                         if self._is_implicit_new_block(state):
                             # check if this is a with...as, abort if so
@@ -163,6 +136,57 @@ 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 >= (3, 11):
+        def _run_handle_exception(self, runner, state):
+            if not state.in_with() and (
+                    state.has_active_try() and
+                    state.get_inst().opname not in _NO_RAISE_OPS):
+                # Is in a *try* block
+                state.fork(pc=state.get_inst().next)
+                runner.handle_try(state)
+                return True
+            else:
+                state.advance_pc()
+
+                # Must the new PC be a new block?
+                if not state.in_with() and state.is_in_exception():
+                    _logger.debug("3.11 exception %s PC=%s",
+                                  state.get_exception(), state._pc)
+                    eh = state.get_exception()
+                    eh_top = state.get_top_block('TRY')
+                    if eh_top and eh_top['end'] == eh.target:
+                        # Same exception
+                        eh_block = None
+                    else:
+                        eh_block = state.make_block("TRY", end=eh.target)
+                        eh_block['end_offset'] = eh.end
+                        eh_block['stack_depth'] = eh.depth
+                        eh_block['push_lasti'] = eh.lasti
+                        state.fork(pc=state._pc, extra_block=eh_block)
+                        return True
+    else:
+        def _run_handle_exception(self, runner, state):
+            if (state.has_active_try() and
+                    state.get_inst().opname not in _NO_RAISE_OPS):
+                # Is in a *try* block
+                state.fork(pc=state.get_inst().next)
+                tryblk = state.get_top_block('TRY')
+                state.pop_block_and_above(tryblk)
+                nstack = state.stack_depth
+                kwargs = {}
+                if nstack > tryblk['entry_stack']:
+                    kwargs['npop'] = nstack - tryblk['entry_stack']
+                handler = tryblk['handler']
+                kwargs['npush'] = {
+                    BlockKind('EXCEPT'): _EXCEPT_STACK_OFFSET,
+                    BlockKind('FINALLY'): _FINALLY_POP
+                }[handler['kind']]
+                kwargs['extra_block'] = handler
+                state.fork(pc=tryblk['end'], **kwargs)
+                return True
+            else:
+                state.advance_pc()
+
     def _build_cfg(self, all_states):
         graph = CFGraph()
         for state in all_states:
@@ -1370,13 +1394,17 @@ class TraceRunner(object):
     # NOTE: Please see notes in `interpreter.py` surrounding the implementation
     # of LOAD_METHOD and CALL_METHOD.
 
-    def op_LOAD_METHOD(self, state, inst):
-        item = state.pop()
-        extra = state.make_null()
-        state.push(extra)
-        res = state.make_temp()
-        state.append(inst, item=item, res=res)
-        state.push(res)
+    if PYVERSION == (3, 11):
+        def op_LOAD_METHOD(self, state, inst):
+            item = state.pop()
+            extra = state.make_null()
+            state.push(extra)
+            res = state.make_temp()
+            state.append(inst, item=item, res=res)
+            state.push(res)
+    else:
+        def op_LOAD_METHOD(self, state, inst):
+            self.op_LOAD_ATTR(state, inst)
 
     def op_CALL_METHOD(self, state, inst):
         self.op_CALL_FUNCTION(state, inst)
@@ -1522,9 +1550,6 @@ class State(object):
         self._temp_registers.append(name)
         return name
 
-    def make_null(self):
-        return self.make_temp(prefix="null$")
-
     def append(self, inst, **kwargs):
         """Append new inst"""
         self._insts.append((inst.offset, kwargs))
@@ -1719,6 +1744,9 @@ class StatePy311(State):
             if ent["kind"] == BlockKind("WITH"):
                 return True
 
+    def make_null(self):
+        return self.make_temp(prefix="null$")
+
 
 if PYVERSION >= (3, 11):
     State = StatePy311
