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 IR assumption violation.
Comment thread
sklam marked this conversation as resolved.
Resolved
"""

# 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 name {newtarget.name!r} not in scope."
Comment thread
sklam marked this conversation as resolved.
Resolved
warnings.warn(errors.NumbaIRAssumptionWarning(wmsg,
loc=assign.loc))
else:
newtarget = scope.redefine(assign.target.name, loc=assign.loc)
assign = ir.Assign(
Expand Down
91 changes: 90 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,76 @@ 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 clear ir.Scope in ir.Block
Comment thread
sklam marked this conversation as resolved.
Resolved
@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 a empty Scope
Comment thread
sklam marked this conversation as resolved.
Resolved
blk.scope = ir.Scope(parent=oldscope.parent,
loc=oldscope.loc)
return True

# Create a pass that always fail to stop the compiler
Comment thread
sklam marked this conversation as resolved.
Resolved
@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
Comment thread
sklam marked this conversation as resolved.
Resolved
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]

# import logging
# logging.basicConfig(level=logging.DEBUG)

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.

Dead comments

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
# import logging
# logging.basicConfig(level=logging.DEBUG)

@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 name '[a-z]' not in scope",

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
r"variable name '[a-z]' not in scope",
r"variable '[a-z]' is not in scope",

)


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