Repository: numba/numba

Issue:
typeinfer.py sets location with str instead of Loc object
There are at least 2 places where string literal is used instead of `Loc` object when building an error.
This leads to runtime error when `strformat` is called on `loc` attribute.
`str` lacks that method, so we have trace like:
```
Traceback (most recent call last):
  File "examples/hello.py", line 25, in <module>
    main()
  File "examples/hello.py", line 19, in main
    print(hpat(2))
  File "numba/numba/dispatcher.py", line 367, in _compile_for_args
    raise e
  File "numba/numba/dispatcher.py", line 324, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "numba/numba/dispatcher.py", line 655, in compile
    cres = self._compiler.compile(args, return_type)
  File "numba/numba/dispatcher.py", line 82, in compile
    pipeline_class=self.pipeline_class)
  File "numba/numba/compiler.py", line 905, in compile_extra
    return pipeline.compile_extra(func)
  File "numba/numba/compiler.py", line 367, in compile_extra
    return self._compile_bytecode()
  File "numba/numba/compiler.py", line 836, in _compile_bytecode
    return self._compile_core()
  File "numba/numba/compiler.py", line 823, in _compile_core
    res = pm.run(self.status)
  File "numba/numba/compiler_lock.py", line 32, in _acquire_compile_lock
    return func(*args, **kwargs)
  File "numba/numba/compiler.py", line 253, in run
    raise patched_exception
  File "numba/numba/compiler.py", line 244, in run
    stage()
  File "numba/numba/compiler.py", line 478, in stage_nopython_frontend
    self.locals)
  File "numba/numba/compiler.py", line 1009, in type_inference_stage
    typemap, restype, calltypes = infer.unify()
  File "numba/numba/typeinfer.py", line 924, in unify
    check_var(var)
  File "numba/numba/typeinfer.py", line 910, in check_var
    raise TypingError(msg % (var, val, loc), loc)
  File "numba/numba/errors.py", line 406, in __init__
    highlight("%s\n%s\n" % (msg, loc.strformat())))
AttributeError: Failed in hpat mode pipeline (step: nopython frontend)
'str' object has no attribute 'strformat'
```


Discussion:

Resolving patch:
diff --git a/numba/ir.py b/numba/ir.py
--- a/numba/ir.py
+++ b/numba/ir.py
@@ -147,6 +147,10 @@ def with_lineno(self, line, col=None):
         return type(self)(self.filename, line, col)
 
 
+# Used for annotating errors when source location is unknown.
+unknown_loc = Loc("unknown location", 0, 0)
+
+
 class VarMap(object):
     def __init__(self):
         self._con = {}
diff --git a/numba/typeinfer.py b/numba/typeinfer.py
--- a/numba/typeinfer.py
+++ b/numba/typeinfer.py
@@ -954,7 +954,7 @@ def check_var(name):
             if not tv.defined:
                 offender = find_offender(name)
                 val = getattr(offender, 'value', 'unknown operation')
-                loc = getattr(offender, 'loc', 'unknown location')
+                loc = getattr(offender, 'loc', ir.unknown_loc)
                 msg = "Undefined variable '%s', operation: %s, location: %s"
                 raise TypingError(msg % (var, val, loc), loc)
             tp = tv.getone()
@@ -963,7 +963,7 @@ def check_var(name):
                 msg = ("Cannot infer the type of variable '%s'%s, "
                       "have imprecise type: %s. %s")
                 istmp = " (temporary variable)" if var.startswith('$') else ""
-                loc = getattr(offender, 'loc', 'unknown location')
+                loc = getattr(offender, 'loc', ir.unknown_loc)
                 # is this an untyped list? try and provide help
                 extra_msg = diagnose_imprecision(offender)
                 raise TypingError(msg % (var, istmp, tp, extra_msg), loc)
@@ -1039,7 +1039,7 @@ def check_type(atype):
                                     break
 
                     for name, offender in returns.items():
-                        loc = getattr(offender, 'loc', 'unknown location')
+                        loc = getattr(offender, 'loc', ir.unknown_loc)
                         msg = ("Return of: IR name '%s', type '%s', "
                                "location: %s")
                         interped = msg % (name, atype, loc.strformat())


Test patch:
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -72,6 +72,40 @@ def fn(x):
         a = np.array([1.0],dtype=np.float64)
         fn(a) # should not raise
 
+    def test_use_of_ir_unknown_loc(self):
+        # for context see # 3390
+        import numba
+        class TestPipeline(numba.compiler.BasePipeline):
+            def define_pipelines(self, pm):
+                pm.create_pipeline('test_loc')
+                self.add_preprocessing_stage(pm)
+                self.add_with_handling_stage(pm)
+                self.add_pre_typing_stage(pm)
+                # remove dead before type inference so that the Arg node is removed
+                # and the location of the arg cannot be found
+                pm.add_stage(self.rm_dead_stage,
+                            "remove dead before type inference for testing")
+                self.add_typing_stage(pm)
+                self.add_optimization_stage(pm)
+                pm.add_stage(self.stage_ir_legalization,
+                            "ensure IR is legal prior to lowering")
+                self.add_lowering_stage(pm)
+                self.add_cleanup_stage(pm)
+
+            def rm_dead_stage(self):
+                numba.ir_utils.remove_dead(
+                    self.func_ir.blocks, self.func_ir.arg_names, self.func_ir)
+
+        @numba.jit(pipeline_class=TestPipeline)
+        def f(a):
+            return 0
+
+        with self.assertRaises(errors.TypingError) as raises:
+            f(iter([1,2]))  # use a type that Numba doesn't recognize
+
+        expected = 'File "unknown location", line 0:'
+        self.assertIn(expected, str(raises.exception))
+
 
 if __name__ == '__main__':
     unittest.main()