Repository: numba/numba

Issue:
Unused closures in njitted function are wrongly reported as escaping
<!--

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 a self contained code sample to reproduce the problem.
  i.e. it's possible to run as 'python bug.py'.
MVE:
```python
import numba as nb
@nb.njit
def f(a,b):
    def g(b):
        return a+b
    
    return 1
print(nb.version_info)
f(1,2)
```
```
version_info(major=0, minor=53, patch=1, short=(0, 53), full=(0, 53, 1), string='0.53.1', tuple=('0', '53', '1'), git_revision=None)

---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
/tmp/ipykernel_30576/270852093.py in <module>
      7     return 1
      8 print(nb.version_info)
----> 9 f(1,2)

~/miniconda3/lib/python3.9/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    418                 e.patch_message(msg)
    419 
--> 420             error_rewrite(e, 'typing')
    421         except errors.UnsupportedError as e:
    422             # Something unsupported is present in the user code, add help info

~/miniconda3/lib/python3.9/site-packages/numba/core/dispatcher.py in error_rewrite(e, issue_type)
    359                 raise e
    360             else:
--> 361                 raise e.with_traceback(None)
    362 
    363         argtypes = []

TypingError: Failed in nopython mode pipeline (step: convert make_function into JIT functions)
Cannot capture the non-constant value associated with variable 'a' in a function that will escape.

File "../../../../../../../tmp/ipykernel_30576/270852093.py", line 4:
<source missing, REPL/exec in use?>

```


Discussion:
I can reproduce this. My intuition is that:

* This will not be very easy to fix (it would require analysis to determine that `g` is dead code and elide the error message)
* The error message is not entirely accurate - I do wonder if the problem is not that the function or its result might escape (it doesn't here), but that Numba doesn't compile inline functions with closure variables that are arguments to the outer jitted function.
Thanks for reproducing @gmarkall 

If the function is used the error message does dissapear:

```python
import numba as nb
@nb.njit
def f(a,x):
    def g(b):
        return a+b
    return g(x)
print(nb.version_info)
f(1,2)
```
```
version_info(major=0, minor=53, patch=1, short=(0, 53), full=(0, 53, 1), string='0.53.1', tuple=('0', '53', '1'), git_revision=None)
3
```

If the captured value is not an argument it still fails:

```python
import numpy as np
import numba as nb
@nb.njit
def f(x):
    c = np.random.rand(1)
    def g(b):
        return c+b
    return 1
print(nb.version_info)
f(1)
```
```
version_info(major=0, minor=53, patch=1, short=(0, 53), full=(0, 53, 1), string='0.53.1', tuple=('0', '53', '1'), git_revision=None)

---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
/tmp/ipykernel_2959/357528790.py in <module>
      8     return 1
      9 print(nb.version_info)
---> 10 f(1)

~/miniconda3/lib/python3.9/site-packages/numba/core/dispatcher.py in _compile_for_args(self, *args, **kws)
    418                 e.patch_message(msg)
    419 
--> 420             error_rewrite(e, 'typing')
    421         except errors.UnsupportedError as e:
    422             # Something unsupported is present in the user code, add help info

~/miniconda3/lib/python3.9/site-packages/numba/core/dispatcher.py in error_rewrite(e, issue_type)
    359                 raise e
    360             else:
--> 361                 raise e.with_traceback(None)
    362 
    363         argtypes = []

TypingError: Failed in nopython mode pipeline (step: convert make_function into JIT functions)
Cannot capture the non-constant value associated with variable 'c' in a function that will escape.

File "../../../../../../../tmp/ipykernel_2959/357528790.py", line 6:
<source missing, REPL/exec in use?>
```

I think the message perhaps ought to say "a function that may escape", as that covers the case of it not escaping. As to the above, any closed over variables must be compile time constants, calling `np.random.rand(1)` is not constant and using an argument from the outer function is also not constant.
NOTE: The good first issue task is to alter the error message to read `a function that may escape` and to fix up any tests that fail as a result of the change.
> using an argument from the outer function is also not constant.

I feel I need to clarify that this works([first example here](https://github.com/numba/numba/issues/7254#issuecomment-888176438) ).
The issue is when this function is left unused.

So if I'm reading the replies correctly, when the inner function is used, in this case it is inlined, so it's directly referencing the argument variable. When it is not used, it's compiled, and an attempt is made to capture the argument variable, which fails because it is not a constant.

The context for leaving the function unused but in the source code is that closures are one way of capturing default arguments, and in long functions with many helpers, I found myself frequently having to comment out helpers. This however, in my specific case, with compiling taking 3mn. 

I have since moved to using structrefs as a way to capture constant values with minimal overhead. This allows me to split functions without having to pass many arguments around. And the whole algorithm takes 1.5mn to compile, i.e. half of the original original time, and it's easy to incrementally modify it.

 However, I'm not sure how one could prevent confusion that arises from first seeing no issues because the variable is being inlined.

Resolving patch:
diff --git a/numba/core/ir_utils.py b/numba/core/ir_utils.py
--- a/numba/core/ir_utils.py
+++ b/numba/core/ir_utils.py
@@ -2245,7 +2245,7 @@ def convert_code_obj_to_function(code_obj, caller_ir):
             freevars.append(freevar_def.value)
         else:
             msg = ("Cannot capture the non-constant value associated with "
-                   "variable '%s' in a function that will escape." % x)
+                   "variable '%s' in a function that may escape." % x)
             raise TypingError(msg, loc=code_obj.loc)
 
     func_env = "\n".join(["\tc_%d = %s" % (i, x) for i, x in enumerate(freevars)])


Test patch: