Repository: numba/numba

Issue:
with.. as construct breaks bytecode analysis and leads to an uncontrolled failure
numba.core.errors.UnsupportedError: Failed in nopython mode pipeline (step: analyzing bytecode)
'try' block not supported until python3.7 or later


Discussion:

Patch:
diff --git a/numba/core/byteflow.py b/numba/core/byteflow.py
index f84ac33b9..d82c07e67 100644
--- a/numba/core/byteflow.py
+++ b/numba/core/byteflow.py
@@ -130,6 +130,9 @@ class Flow(object):
                         state.advance_pc()
                         # Must the new PC be a new block?
                         if self._is_implicit_new_block(state):
+                            # check if this is a with...as, abort if so
+                            self._guard_with_as(state)
+                            # else split
                             state.split_new_block()
                             break
                 _logger.debug("end state. edges=%s", state.outgoing_edges)
@@ -245,6 +248,19 @@ class Flow(object):
         else:
             return False
 
+    def _guard_with_as(self, state):
+        """Checks if the next instruction after a SETUP_WITH is something other
+        than a POP_TOP, if it is something else it'll be some sort of store
+        which is not supported (this corresponds to `with CTXMGR as VAR(S)`)."""
+        current_inst = state.get_inst()
+        if current_inst.opname == "SETUP_WITH":
+            next_op = self._bytecode[current_inst.next].opname
+            if next_op != "POP_TOP":
+                msg = ("The 'with (context manager) as "
+                       "(variable):' construct is not "
+                       "supported.")
+                raise UnsupportedError(msg)
+
 
 class TraceRunner(object):
     """Trace runner contains the states for the trace and the opcode dispatch.
diff --git a/numba/core/controlflow.py b/numba/core/controlflow.py
index 8ecbd1184..c78a8719f 100644
--- a/numba/core/controlflow.py
+++ b/numba/core/controlflow.py
@@ -847,6 +847,7 @@ class ControlFlowAnalysis(object):
     def _iter_inst(self):
         for inst in self.bytecode:
             if self._use_new_block(inst):
+                self._guard_with_as(inst)
                 self._start_new_block(inst)
             self._curblock.body.append(inst.offset)
             yield inst
@@ -867,6 +868,18 @@ class ControlFlowAnalysis(object):
         self.blocks[inst.offset] = self._curblock
         self.blockseq.append(inst.offset)
 
+    def _guard_with_as(self, current_inst):
+        """Checks if the next instruction after a SETUP_WITH is something other
+        than a POP_TOP, if it is something else it'll be some sort of store
+        which is not supported (this corresponds to `with CTXMGR as VAR(S)`)."""
+        if current_inst.opname == "SETUP_WITH":
+            next_op = self.bytecode[current_inst.next].opname
+            if next_op != "POP_TOP":
+                msg = ("The 'with (context manager) as "
+                       "(variable):' construct is not "
+                       "supported.")
+                raise UnsupportedError(msg)
+
     def op_SETUP_LOOP(self, inst):
         end = inst.get_jump_target()
         self._blockstack.append(end)


Test patch:
diff --git a/numba/tests/test_withlifting.py b/numba/tests/test_withlifting.py
index 45c30e67a..e29bded1f 100644
--- a/numba/tests/test_withlifting.py
+++ b/numba/tests/test_withlifting.py
@@ -1022,6 +1022,20 @@ class TestBogusContext(BaseTestWithLifting):
             str(raises.exception),
             )
 
+    def test_with_as_fails_gracefully(self):
+        @njit
+        def foo():
+            with open('') as f:
+                pass
+
+        with self.assertRaises(errors.UnsupportedError) as raises:
+            foo()
+
+        excstr = str(raises.exception)
+        msg = ("The 'with (context manager) as (variable):' construct is not "
+               "supported.")
+        self.assertIn(msg, excstr)
+
 
 if __name__ == '__main__':
     unittest.main()