Repository: numba/numba

Issue:
`AttributeError` for inlined generic functions with py312 pep-695 type-parameter syntax
This regex doesn't match functions that use the [PEP 695](https://peps.python.org/pep-0695/) type parameters syntax:
https://github.com/numba/numba/blob/0.60.0dev0/numba/core/ir.py#L29

In my case, this resulted in:

```
.venv/lib/python3.12/site-packages/numba/core/ir.py:85: in _raw_function_name
    return self._defmatcher.match(defn.strip()).groups()[0]
E   AttributeError: 'NoneType' object has no attribute 'groups'
        defn       = 'def drink_cigar[N: int](X: MatrixF8[_1, N], b: VectorF8[_2]) -> VectorF8[N]:\n'
        self       = Loc(filename=/projects/clowns_are_trains/inflate_bouncy_castle.py, line=192, col=None)
```

This `drink_cigar()` function is wearing a `@njit(inline='always')`.
To give a bit more context, `drink_cigar` is passed as argument to some other (non-inlined) `@njit()` function, `paint_donkeys(drink_cigar, ...)` is this case.
Back when I was still using the archaic `TypeVars`, the code in question was doing what it was supposed to (without errors or warnings).


Discussion:
Thanks for the report, @jorenham. Can you share a minimum viable reproducer for the issue you're facing?
@guilhermeleobas I'm having trouble reproducing the issue. 

But the main issue here is that the [regular expression](https://github.com/numba/numba/blob/0.60.0dev0/numba/core/ir.py#L29) (`def\s+(\w+)\(.*`) does not match PEP 695 functions, which is easy to verify.

I realize that knowing when this issue exactly occurs is useful information, but isn't it a possibility to call this something like "a rare bug with python 3.12 type parameter syntax" in the changelog?

I've have been trying for two hours reproduce this already, but I'm out of ideas for now. So now I'll shift my focus to the original code that is causing this issue, and try to see if there's a specific condition that triggers it, i.e. a workaround that doesn't involve removing the PEP 695 syntax. My hope is that'll lead me to a minimal reproducible example.
to illustrate the regex failing:

```pycon
>>> import re
>>> re.match(r'def\s+(\w+)\(.*', 'def boogie():')
<re.Match object; span=(0, 13), match='def boogie():'>
>>> re.match(r'def\s+(\w+)\(.*', 'def boogie[T](ceiling: T) -> T:')
>>> # :(
```

@guilhermeleobas does this count as a minimum viable reproducer 😉 ?
Hi @jorenham, thank you. That should be enough.
I figured out what exactly triggers it:

It requires 

1. One function with py312-style type params
2. ... decorated with `@jit`, and an explicit signature
3. Either:
    - An `assert _`
    - or a `raise _`

To illustrate:

```python
import numba

@numba.jit('f8(f8)')
def wiggle[X: float](x: X) -> X:
    assert x != 0  # or e.g. `raise NotADirectoryError`
    return x
```

Now insert a coin and pull the lever (don't forget to cover your ears!):

```pytb
Traceback (most recent call last):
  File "/dev/flying_snails/yellow_snow.py", line 4, in <module>
    @numba.jit('f8(f8)')
     ^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/decorators.py", line 236, in wrapper
    disp.compile(sig)
  File "/lib/python3.12/site-packages/numba/core/dispatcher.py", line 957, in compile
    cres = self._compiler.compile(args, return_type)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/dispatcher.py", line 125, in compile
    status, retval = self._compile_cached(args, return_type)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/dispatcher.py", line 139, in _compile_cached
    retval = self._compile_core(args, return_type)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/dispatcher.py", line 152, in _compile_core
    cres = compiler.compile_extra(self.targetdescr.typing_context,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler.py", line 751, in compile_extra
    return pipeline.compile_extra(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler.py", line 445, in compile_extra
    return self._compile_bytecode()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler.py", line 513, in _compile_bytecode
    return self._compile_core()
           ^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler.py", line 488, in _compile_core
    raise e
  File "/lib/python3.12/site-packages/numba/core/compiler.py", line 479, in _compile_core
    pm.run(self.state)
  File "/lib/python3.12/site-packages/numba/core/compiler_machinery.py", line 364, in run
    raise e
  File "/lib/python3.12/site-packages/numba/core/compiler_machinery.py", line 356, in run
    self._runPass(idx, pass_inst, state)
  File "/lib/python3.12/site-packages/numba/core/compiler_lock.py", line 35, in _acquire_compile_lock
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler_machinery.py", line 311, in _runPass
    mutated |= check(pss.run_pass, internal_state)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/compiler_machinery.py", line 273, in check
    mangled = func(compiler_state)
              ^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/typed_passes.py", line 468, in run_pass
    lower.lower()
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 187, in lower
    self.lower_normal_function(self.fndesc)
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 226, in lower_normal_function
    entry_block_tail = self.lower_function_body()
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 256, in lower_function_body
    self.lower_block(block)
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 270, in lower_block
    self.lower_inst(inst)
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 573, in lower_inst
    self.lower_static_raise(inst)
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 641, in lower_static_raise
    self.return_exception(inst.exc_class, inst.exc_args, loc=self.loc)
  File "/lib/python3.12/site-packages/numba/core/lowering.py", line 162, in return_exception
    self.call_conv.return_user_exc(
  File "/lib/python3.12/site-packages/numba/core/callconv.py", line 457, in return_user_exc
    self.set_static_user_exc(builder, exc, exc_args=exc_args,
  File "/lib/python3.12/site-packages/numba/core/callconv.py", line 447, in set_static_user_exc
    exc = self.build_excinfo_struct(exc, exc_args, loc, func_name)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/callconv.py", line 383, in build_excinfo_struct
    fname = loc._raw_function_name()
            ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/lib/python3.12/site-packages/numba/core/ir.py", line 85, in _raw_function_name
    return self._defmatcher.match(defn.strip()).groups()[0]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'groups'
```

... et voilà

Resolving patch:
diff --git a/numba/core/ir.py b/numba/core/ir.py
--- a/numba/core/ir.py
+++ b/numba/core/ir.py
@@ -26,7 +26,7 @@ class Loc(object):
     """Source location
 
     """
-    _defmatcher = re.compile(r'def\s+(\w+)\(.*')
+    _defmatcher = re.compile(r'def\s+(\w+)')
 
     def __init__(self, filename, line, col=None, maybe_decorator=False):
         """ Arguments:
@@ -82,10 +82,11 @@ def _find_definition(self):
     def _raw_function_name(self):
         defn = self._find_definition()
         if defn:
-            return self._defmatcher.match(defn.strip()).groups()[0]
-        else:
-            # Probably exec(<string>) or REPL.
-            return None
+            m = self._defmatcher.match(defn.strip())
+            if m:
+                return m.groups()[0]
+        # Probably exec(<string>) or REPL.
+        return None
 
     def get_lines(self):
         if self.lines is None:


Test patch:
diff --git a/numba/tests/test_exceptions.py b/numba/tests/test_exceptions.py
--- a/numba/tests/test_exceptions.py
+++ b/numba/tests/test_exceptions.py
@@ -3,7 +3,7 @@
 import traceback
 
 from numba import jit, njit
-from numba.core import types, errors
+from numba.core import types, errors, utils
 from numba.tests.support import (TestCase, expected_failure_py311,
                                  expected_failure_py312,
                                  )
@@ -309,9 +309,18 @@ def check_raise_from_exec_string(self, flags):
         # issue #3428
         simple_raise = "def f(a):\n  raise exc('msg', 10)"
         assert_raise = "def f(a):\n  assert a != 1"
-        for f_text, exc in [(assert_raise, AssertionError),
-                            (simple_raise, UDEArgsToSuper),
-                            (simple_raise, UDENoArgSuper)]:
+        py312_pep695_raise = "def f[T: int](a: T) -> T:\n  assert a != 1"
+        py312_pep695_raise_2 = "def f[T: int\n](a: T) -> T:\n  assert a != 1"
+        test_cases = [
+            (assert_raise, AssertionError),
+            (simple_raise, UDEArgsToSuper),
+            (simple_raise, UDENoArgSuper),
+        ]
+        if utils.PYVERSION >= (3, 12):
+            # Added for https://github.com/numba/numba/issues/9443
+            test_cases.append((py312_pep695_raise, AssertionError))
+            test_cases.append((py312_pep695_raise_2, AssertionError))
+        for f_text, exc in test_cases:
             loc = {}
             exec(f_text, {'exc': exc}, loc)
             pyfunc = loc['f']