Repository: numba/numba

Issue:
DWARF generation failing for OmittedArgs
<!--

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 RC of Numba (0.55rc1)
- [x] I have included a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.

This:
```python
from numba import njit
import numpy as np

@njit
def foo():
    x = np.arange(5.)

foo()
```
run like this:
```
NUMBA_DEBUGINFO=1 python omitted1.py
```
does this:
```
numba/core/datamodel/models.py", line 43, in get_value_type
    raise NotImplementedError(self)
NotImplementedError: <numba.core.datamodel.models.OmittedArgDataModel object at 0x7f2a5e883df0>
```



Discussion:

Resolving patch:
diff --git a/numba/core/datamodel/models.py b/numba/core/datamodel/models.py
--- a/numba/core/datamodel/models.py
+++ b/numba/core/datamodel/models.py
@@ -160,8 +160,11 @@ class OmittedArgDataModel(DataModel):
     A data model for omitted arguments.  Only the "argument" representation
     is defined, other representations raise a NotImplementedError.
     """
-    # Omitted arguments don't produce any LLVM function argument.
+    # Omitted arguments are using a dummy value type
+    def get_value_type(self):
+        return ir.LiteralStructType([])
 
+    # Omitted arguments don't produce any LLVM function argument.
     def get_argument_type(self):
         return ()
 
diff --git a/numba/core/debuginfo.py b/numba/core/debuginfo.py
--- a/numba/core/debuginfo.py
+++ b/numba/core/debuginfo.py
@@ -207,7 +207,7 @@ def _var_type(self, lltype, size, datamodel=None):
             # Struct type
             meta = []
             offset = 0
-            if datamodel is None:
+            if datamodel is None or not datamodel.inner_models():
                 name = f"Anonymous struct ({str(lltype)})"
                 for field_id, element in enumerate(lltype.elements):
                     size = self.cgctx.get_abi_sizeof(element)


Test patch:
diff --git a/numba/tests/test_debuginfo.py b/numba/tests/test_debuginfo.py
--- a/numba/tests/test_debuginfo.py
+++ b/numba/tests/test_debuginfo.py
@@ -144,6 +144,19 @@ def _subprocess_test_runner(self, test_name):
                                     test_name=test_name,
                                     envvars=self._NUMBA_OPT_0_ENV)
 
+    def _get_metadata_map(self, metadata):
+        """Gets the map of DI label to md, e.g.
+        '!33' -> '!{!"branch_weights", i32 1, i32 99}'
+        """
+        metadata_definition_map = dict()
+        meta_definition_split = re.compile(r'(![0-9]+) = (.*)')
+        for line in metadata:
+            matched = meta_definition_split.match(line)
+            if matched:
+                dbg_val, info = matched.groups()
+                metadata_definition_map[dbg_val] = info
+        return metadata_definition_map
+
     def test_DW_LANG(self):
 
         @njit(debug=True)
@@ -236,13 +249,7 @@ def foo(a):
         pysrc, pysrc_line_start = inspect.getsourcelines(foo)
 
         # build a map of dbg reference to DI* information
-        metadata_definition_map = dict()
-        meta_definition_split = re.compile(r'(![0-9]+) = (.*)')
-        for line in metadata:
-            matched = meta_definition_split.match(line)
-            if matched:
-                dbg_val, info = matched.groups()
-                metadata_definition_map[dbg_val] = info
+        metadata_definition_map = self._get_metadata_map(metadata)
 
         # Pull out metadata entries referred to by the llvm line end !dbg
         # check they match the python source, the +2 is for the @njit decorator
@@ -482,13 +489,7 @@ def foo():
                 return a
 
             metadata = self._get_metadata(foo, sig=())
-            metadata_definition_map = dict()
-            meta_definition_split = re.compile(r'(![0-9]+) = (.*)')
-            for line in metadata:
-                matched = meta_definition_split.match(line)
-                if matched:
-                    dbg_val, info = matched.groups()
-                    metadata_definition_map[dbg_val] = info
+            metadata_definition_map = self._get_metadata_map(metadata)
 
             for k, v in metadata_definition_map.items():
                 if 'DILocalVariable(name: "a"' in v:
@@ -523,13 +524,7 @@ def foo():
             return a
 
         metadata = self._get_metadata(foo, sig=())
-        metadata_definition_map = dict()
-        meta_definition_split = re.compile(r'(![0-9]+) = (.*)')
-        for line in metadata:
-            matched = meta_definition_split.match(line)
-            if matched:
-                dbg_val, info = matched.groups()
-                metadata_definition_map[dbg_val] = info
+        metadata_definition_map = self._get_metadata_map(metadata)
 
         for k, v in metadata_definition_map.items():
             if 'DILocalVariable(name: "a"' in v:
@@ -595,6 +590,53 @@ def foo():
             data_type = metadata_definition_map[base_type_marker]
             self.assertRegex(data_type, expected[field])
 
+    def test_omitted_arg(self):
+        # See issue 7726
+        @njit(debug=True)
+        def foo(missing=None):
+            pass
+
+        # check that it will actually compile (verifies DI emission is ok)
+        with override_config('DEBUGINFO_DEFAULT', 1):
+            foo()
+
+        metadata = self._get_metadata(foo, sig=(types.Omitted(None),))
+        metadata_definition_map = self._get_metadata_map(metadata)
+
+        # Find DISubroutineType
+        tmp_disubr = []
+        for md in metadata:
+            if "DISubroutineType" in md:
+                tmp_disubr.append(md)
+        self.assertEqual(len(tmp_disubr), 1)
+        disubr = tmp_disubr.pop()
+
+        disubr_matched = re.match(r'.*!DISubroutineType\(types: ([!0-9]+)\)$',
+                                  disubr)
+        self.assertIsNotNone(disubr_matched)
+        disubr_groups = disubr_matched.groups()
+        self.assertEqual(len(disubr_groups), 1)
+        disubr_meta = disubr_groups[0]
+
+        # Find the types in the DISubroutineType arg list
+        disubr_types = metadata_definition_map[disubr_meta]
+        disubr_types_matched = re.match(r'!{(.*)}', disubr_types)
+        self.assertIsNotNone(disubr_matched)
+        disubr_types_groups = disubr_types_matched.groups()
+        self.assertEqual(len(disubr_types_groups), 1)
+
+        # fetch out and assert the last argument type, should be void *
+        md_fn_arg = [x.strip() for x in disubr_types_groups[0].split(',')][-1]
+        arg_ty = metadata_definition_map[md_fn_arg]
+        expected_arg_ty = (r'^.*!DICompositeType\(tag: DW_TAG_structure_type, '
+                           r'name: "Anonymous struct \({}\)", elements: '
+                           r'(![0-9]+), identifier: "{}"\)')
+        self.assertRegex(arg_ty, expected_arg_ty)
+        md_base_ty = re.match(expected_arg_ty, arg_ty).groups()[0]
+        base_ty = metadata_definition_map[md_base_ty]
+        # expect ir.LiteralStructType([])
+        self.assertEqual(base_ty, ('!{}'))
+
 
 if __name__ == '__main__':
     unittest.main()