Skip to content
12 changes: 12 additions & 0 deletions numba/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ class NumbaInvalidConfigWarning(NumbaWarning):
Warning category for using an invalid configuration.
"""


class NumbaPedanticWarning(NumbaWarning):
"""
Warning category for reporting pedantic messages.
"""


class NumbaIRAssumptionWarning(NumbaPedanticWarning):
"""
Warning category for reporting an IR assumption violation.
"""

# These are needed in the color formatting of errors setup


Expand Down
5 changes: 4 additions & 1 deletion numba/core/ssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,10 @@ def on_assign(self, states, assign):
if len(defmap) == 0:
newtarget = assign.target
_logger.debug("first assign: %s", newtarget)
assert newtarget.name in scope.localvars
if newtarget.name not in scope.localvars:
wmsg = f"variable {newtarget.name!r} is not in scope."
warnings.warn(errors.NumbaIRAssumptionWarning(wmsg,
loc=assign.loc))
else:
newtarget = scope.redefine(assign.target.name, loc=assign.loc)
assign = ir.Assign(
Expand Down
90 changes: 89 additions & 1 deletion numba/tests/test_ir.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import unittest
from numba import objmode
from unittest.case import TestCase
import warnings
import numpy as np

from numba import objmode
from numba.core import ir, compiler
from numba.core import errors
from numba.core.compiler import (
CompilerBase,
ReconstructSSA,
)
from numba.core.compiler_machinery import (
FunctionPass,
PassManager,
register_pass,
)
from numba.core.untyped_passes import (
TranslateByteCode,
IRProcessing,
)
from numba import njit


class TestIR(unittest.TestCase):
Expand Down Expand Up @@ -466,5 +484,75 @@ def baz(a, b):
check_diffstr(tmp, ["c + b", "b + c"])


class TestIRPedanticChecks(TestCase):
def test_var_in_scope_assumption(self):
# Create a pass that clears ir.Scope in ir.Block
@register_pass(mutates_CFG=False, analysis_only=False)
class RemoveVarInScope(FunctionPass):
_name = "_remove_var_in_scope"

def __init__(self):
FunctionPass.__init__(self)

# implement method to do the work, "state" is the internal compiler
# state from the CompilerBase instance.
def run_pass(self, state):
func_ir = state.func_ir
# walk the blocks
for blk in func_ir.blocks.values():
oldscope = blk.scope
# put in an empty Scope
blk.scope = ir.Scope(parent=oldscope.parent,
loc=oldscope.loc)
return True

# Create a pass that always fails, to stop the compiler
@register_pass(mutates_CFG=False, analysis_only=False)
class FailPass(FunctionPass):
_name = "_fail"

def __init__(self, *args, **kwargs):
FunctionPass.__init__(self)

def run_pass(self, state):
# This is unreachable. SSA pass should have raised before this
# pass when run with `error.NumbaPedanicWarning`s raised as

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# pass when run with `error.NumbaPedanicWarning`s raised as
# pass when run with `error.NumbaPedanticWarning`s raised as

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

# errors.
raise AssertionError("unreachable")

class MyCompiler(CompilerBase):
def define_pipelines(self):
pm = PassManager("testing pm")
pm.add_pass(TranslateByteCode, "analyzing bytecode")
pm.add_pass(IRProcessing, "processing IR")
pm.add_pass(RemoveVarInScope, "_remove_var_in_scope")
pm.add_pass(ReconstructSSA, "ssa")
pm.add_pass(FailPass, "_fail")
pm.finalize()
return [pm]

@njit(pipeline_class=MyCompiler)
def dummy(x):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment explaining why this dummy is needed: multiple assignments to the same variable name are needed to trigger SSA.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 3d6fb07

a = 1
b = 2
if a < b:
a = 2
else:
b = 3
return a, b

with warnings.catch_warnings():
# Make NumbaPedanticWarning an error
warnings.simplefilter("error", errors.NumbaPedanticWarning)
# Catch NumbaIRAssumptionWarning
with self.assertRaises(errors.NumbaIRAssumptionWarning) as raises:
dummy(1)
# Verify the error message
self.assertRegex(
str(raises.exception),
r"variable '[a-z]' is not in scope",
)


if __name__ == '__main__':
unittest.main()