Repository: numba/numba

Issue:
Error hidden when source is not available
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I have tried using the latest released version of Numba (most recent is
 visible in the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).


Example:

```python
import numpy as np

from numba import njit, typed, typeof
from numba.core import types
from numba.experimental import structref


@structref.register
class ParticleType(types.StructRef):
    pass


class Particle(structref.StructRefProxy):
    def __new__(cls, pos, mass):
        return structref.StructRefProxy.__new__(cls, pos) # <- didn't provide the required mass argument


structref.define_proxy(Particle, ParticleType, ["pos", "mass"])

Particle(pos=1, mass=2)
```
Because `StructRef`is building the source code dynamically, the `inspect.getsourcelines` will fail in https://github.com/numba/numba/blob/a7c121a6631ba576561bcc6e72a4bfe97151fb4d/numba/core/typing/templates.py#L752

It will be nice to fix this to make `StructRef` error be meaningful



Discussion:

Patch:
diff --git a/numba/core/typing/templates.py b/numba/core/typing/templates.py
index 30082ce3b..33caa3257 100644
--- a/numba/core/typing/templates.py
+++ b/numba/core/typing/templates.py
@@ -285,6 +285,28 @@ class FunctionTemplate(ABC):
             key = key.im_func
         return key
 
+    @classmethod
+    def get_source_code_info(cls, impl):
+        """
+        Gets the source information about function impl.
+        Returns:
+
+        code - str: source code as a string
+        firstlineno - int: the first line number of the function impl
+        path - str: the path to file containing impl
+
+        if any of the above are not available something generic is returned
+        """
+        try:
+            code, firstlineno = inspect.getsourcelines(impl)
+        except OSError: # missing source, probably a string
+            code = "None available (built from string?)"
+            firstlineno = 0
+        path = inspect.getsourcefile(impl)
+        if path is None:
+            path = "<unknown> (built from string?)"
+        return code, firstlineno, path
+
     @abstractmethod
     def get_template_info(self):
         """
@@ -346,8 +368,8 @@ class AbstractTemplate(FunctionTemplate):
     def get_template_info(self):
         impl = getattr(self, "generic")
         basepath = os.path.dirname(os.path.dirname(numba.__file__))
-        code, firstlineno = inspect.getsourcelines(impl)
-        path = inspect.getsourcefile(impl)
+
+        code, firstlineno, path = self.get_source_code_info(impl)
         sig = str(utils.pysignature(impl))
         info = {
             'kind': "overload",
@@ -421,8 +443,7 @@ class CallableTemplate(FunctionTemplate):
     def get_template_info(self):
         impl = getattr(self, "generic")
         basepath = os.path.dirname(os.path.dirname(numba.__file__))
-        code, firstlineno = inspect.getsourcelines(impl)
-        path = inspect.getsourcefile(impl)
+        code, firstlineno, path = self.get_source_code_info(impl)
         sig = str(utils.pysignature(impl))
         info = {
             'kind': "overload",
@@ -730,7 +751,7 @@ class _OverloadFunctionTemplate(AbstractTemplate):
 
     @classmethod
     def get_source_info(cls):
-        """Return a dictionary with information about the source code  of the
+        """Return a dictionary with information about the source code of the
         implementation.
 
         Returns
@@ -751,8 +772,7 @@ class _OverloadFunctionTemplate(AbstractTemplate):
         """
         basepath = os.path.dirname(os.path.dirname(numba.__file__))
         impl = cls._overload_func
-        code, firstlineno = inspect.getsourcelines(impl)
-        path = inspect.getsourcefile(impl)
+        code, firstlineno, path = cls.get_source_code_info(impl)
         sig = str(utils.pysignature(impl))
         info = {
             'kind': "overload",
@@ -767,8 +787,7 @@ class _OverloadFunctionTemplate(AbstractTemplate):
     def get_template_info(self):
         basepath = os.path.dirname(os.path.dirname(numba.__file__))
         impl = self._overload_func
-        code, firstlineno = inspect.getsourcelines(impl)
-        path = inspect.getsourcefile(impl)
+        code, firstlineno, path = self.get_source_code_info(impl)
         sig = str(utils.pysignature(impl))
         info = {
             'kind': "overload",
@@ -836,8 +855,7 @@ class _IntrinsicTemplate(AbstractTemplate):
     def get_template_info(self):
         basepath = os.path.dirname(os.path.dirname(numba.__file__))
         impl = self._definition_func
-        code, firstlineno = inspect.getsourcelines(impl)
-        path = inspect.getsourcefile(impl)
+        code, firstlineno, path = self.get_source_code_info(impl)
         sig = str(utils.pysignature(impl))
         info = {
             'kind': "intrinsic",


Test patch:
diff --git a/numba/tests/test_errorhandling.py b/numba/tests/test_errorhandling.py
index fca546ccc..83f06211b 100644
--- a/numba/tests/test_errorhandling.py
+++ b/numba/tests/test_errorhandling.py
@@ -8,6 +8,7 @@ import os
 from numba import jit, njit, typed, int64, types
 from numba.core import errors
 import numba.core.typing.cffi_utils as cffi_support
+from numba.experimental import structref
 from numba.extending import (overload, intrinsic, overload_method,
                              overload_attribute)
 from numba.core.compiler import CompilerBase
@@ -423,6 +424,25 @@ class TestErrorMessages(unittest.TestCase):
         excstr = str(raises.exception)
         self.assertIn("Type Restricted Function in function 'unknown'", excstr)
 
+    def test_missing_source(self):
+
+        @structref.register
+        class ParticleType(types.StructRef):
+            pass
+
+        class Particle(structref.StructRefProxy):
+            def __new__(cls, pos, mass):
+                return structref.StructRefProxy.__new__(cls, pos)
+                # didn't provide the required mass argument ----^
+
+        structref.define_proxy(Particle, ParticleType, ["pos", "mass"])
+
+        with self.assertRaises(errors.TypingError) as raises:
+            Particle(pos=1, mass=2)
+
+        excstr = str(raises.exception)
+        self.assertIn("required positional argument: 'mass'", excstr)
+
 
 class TestDeveloperSpecificErrorMessages(SerialMixin, unittest.TestCase):