Currently `ForwardRef` is evaluated in the context where it is used as an annotation, rather than where it was created. This doesn't seem intuitive to me and it can cause `NameError`s when sharing `typing` instances that contain a `ForwardRef`. For example consider the following project structure:
```text
$ tree test
test
├── __init__.py
├── aa.py
├── bb.py
├── main.py
```
Here `bb.py` defines a type union (it will be used in many places) and uses a forward ref since one of the types depends on itself:
```python
from typing import Union
class Foo:
pass
MyUnion = Union[Foo, 'Bar']
class Bar:
def __init__(self, obj: MyUnion):
self.obj = obj
```
Now `aa.py` contains a function that works with the types from `bb.py` and so it imports `MyUnion` to annotate its function:
```python
from .bb import MyUnion
def test(x: MyUnion):
pass
```
Finally `main.py` imports from `aa.py` and inspects this function via `typing.get_type_hints`:
```python
from typing import get_type_hints
from .aa import test
print(get_type_hints(test))
```
Running `main.py` however results in a `NameError`:
```text
Traceback (most recent call last):
File "/path/to/lib/python3.9/runpy.py", line 197, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/path/to/lib/python3.9/runpy.py", line 87, in _run_code
exec(code, run_globals)
File "/tmp/test/main.py", line 4, in <module>
print(get_type_hints(test))
File "/path/to/lib/python3.9/typing.py", line 1386, in get_type_hints
value = _eval_type(value, globalns, localns)
File "/path/to/lib/python3.9/typing.py", line 256, in _eval_type
ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
File "/path/to/lib/python3.9/typing.py", line 256, in <genexpr>
ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
File "/path/to/lib/python3.9/typing.py", line 254, in _eval_type
return t._evaluate(globalns, localns, recursive_guard)
File "/path/to/lib/python3.9/typing.py", line 493, in _evaluate
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
NameError: name 'Bar' is not defined
```
This is because the `ForwardRef` in `MyUnion` is evaluated in the context (namespace) of the function `test` which doesn't contain the `Bar` type.
As a slightly different scenario consider the case where the module `aa.py` defines its own `Bar` type. Then the type hint will be resolved to `aa.Bar` rather than `bb.Bar`. It seems that in most situations this is not the intention of the programmer.
```python
"""Modified version of aa.py"""
from .bb import MyUnion
class Bar:
pass
def test(x: MyUnion):
pass
```
Now running `main.py` gives:
```text
$ python -m test.main
{'x': typing.Union[test.bb.Foo, test.aa.Bar]}
```
The forward ref to 'Bar' was resolved to `test.aa.Bar` rather than `test.bb.Bar`. This is not intended by either the creator of `bb.py` nor the creator of `aa.py`:
* `bb.py`: `Bar` is expected to refer to `bb.Bar`.
* `aa.py`: `MyUnion` is expected to refer to `bb.Bar`; the fact that `Bar` is referred to with `"Bar"` in `MyUnion` is a detail of `bb.py` and the creator of `aa.py` might not be aware of that, and thus unintentionally creating the mismatch with `aa.Bar`.
Interestingly, when using the function `test` in `main` with objects imported from `bb.py` then `mypy` doesn't complain about that (so it doesn't seem to evaluate the forward reference in the `aa.py` namespace?):
```python
"""Modified version of main.py (use together with modified version of aa.py)"""
from typing import get_type_hints
from .aa import test
print(get_type_hints(test))
from .bb import Foo, Bar
test(Bar(Foo()))
```
The output is:
```text
$ python -m test.main
{'x': typing.Union[test.bb.Foo, test.aa.Bar]}
$ mypy test/
Success: no issues found in 4 source files
```
So I suggest that `ForwardRef` remembers the context (namespace) where it was created and uses this one to evaluate itself. This however is a backwards incompatible change due to the behavior mentioned above (resolution to `aa.Bar` would change to `bb.Bar`). What do you think?