This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: ForwardRef name conflict during evaluation
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.10, Python 3.9
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: gvanrossum, kj, levkivskyi, tefra
Priority: normal Keywords:

Created on 2021-03-28 09:52 by tefra, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (5)
msg389634 - (view) Author: Christodoulos Tsoulloftas (tefra) Date: 2021-03-28 09:52
Consider two modules with the same name forward references with the same type construct


./a.py


```
from typing import Optional


class Root:
    a: Optional["Person"]


class Person:
    value: str

```


./b.py

```
from typing import Optional


class Root:
    b: Optional["Person"]


class Person:
    value: str

```

There is a naming conflict, I think due to caching, and the type hint of the second property points to the first one.


```
>>> from typing import get_type_hints, Optional
>>> from a import Root as RootA, Person as PersonA
>>> from b import Root as RootB, Person as PersonB
>>> 
>>> roota_hints = get_type_hints(RootA)
>>> rootb_hints = get_type_hints(RootB)
>>> 
>>> print(roota_hints)
{'a': typing.Optional[a.Person]}
>>> print(rootb_hints)
{'b': typing.Optional[a.Person]}
>>> 
>>> assert roota_hints["a"] == Optional[PersonA]
>>> assert rootb_hints["b"] == Optional[PersonB]  # fails, points to PersonA
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
>", line 1, in <module>
AssertionError
>>> 

```


The behavior started in python 3.10, I am not sure which alpha version, I am using 3.10.0a6+
msg389643 - (view) Author: Ken Jin (kj) * (Python committer) Date: 2021-03-28 18:51
Wow, thank you for the excellent bug report! Surprisingly, issue42904 's patch fixes this problem as well. I've spent a few hours trying to debug this, and here's some findings:

TLDR:
3.9.1 - 3.10 added ForwardRef recursive evaluation. A guess is that since get_type_hints has always been passing the wrong locals() for classes as mentioned in issue42904, that may cause nested ForwardRefs to be resolved incorrectly.

Observations:

a.py:
```
class Root:
    a: List["Person"]
```

On first call of get_type_hints(Root), _eval_type evals in the following order:

```
# These print the repr, globals['__name__'], locals['__name__'], and code object (only for ForwardRef __forward_code__)

ForwardRef: ForwardRef("List['Person']"), a, None, <code object <module> at 0x0503A7A8, file "<string>", line 1>
GenericAlias: typing.List[ForwardRef('Person')], a, a
ForwardRef: ForwardRef('Person'), a, a, <code object <module> at 0x0503AAF0, file "<string>", line 1>
NA: <class 'a.Person'>
```

Then on a second repeated call of get_type_hints(Root), _eval_type does:

```
ForwardRef: ForwardRef("List['Person']"), a, None, <code object <module> at 0x0503A7A8, file "<string>", line 1>
GenericAlias: typing.List[ForwardRef('Person')], a, a
ForwardRef: ForwardRef('Person'), a, a, <code object <module> at 0x0503AAF0, file "<string>", line 1>
```

It's skipping the last evaluation of <class 'a.Person'>. This also occurs if I call it with RootB after Root.

I managed to reproduce this in 3.9.1-2 as well. The only requirement is to change the example to
```
class Root:
    a: 'List["Person"]'
```
msg390950 - (view) Author: Ken Jin (kj) * (Python committer) Date: 2021-04-13 10:05
@tefra, now that issue42904 has been solved, can you test your code again with 3.10.0a8? (it's not out yet, so you may have to pull the code from CPython's GitHub master branch and build CPython)

Running your code again, it seems to be fixed:

Python 3.10.0a7+
>>> from typing import get_type_hints, Optional
>>> from a import Root as RootA, Person as PersonA
>>> from b import Root as RootB, Person as PersonB
>>> roota_hints = get_type_hints(RootA)
>>> rootb_hints = get_type_hints(RootB)
>>> print(roota_hints)
{'a': typing.List[a.Person]}
>>> print(rootb_hints)
{'b': typing.List[b.Person]}

However, it's still around for 3.9 and below if I use string annotations in classes. There's some discussion in that issue about why it may not be backported though.
msg391148 - (view) Author: Christodoulos Tsoulloftas (tefra) Date: 2021-04-15 19:20
@kj I can confirm the issue is resolved with Python 3.10.0a7+
msg391290 - (view) Author: Ken Jin (kj) * (Python committer) Date: 2021-04-17 16:06
@tefra, thanks for testing. That's great to hear! And once again, thanks for the excellent bug report in your original message.

@tefra and @gvanrossum,
I'm closing this issue as it is now fixed in 3.10. If any of you feel that's wrong, please don't hesitate to re-open the issue.

For anyone wanting to discuss the fix further. I recommend going to issue42904 - it's the root cause of this issue and what the patch is linked to.
History
Date User Action Args
2022-04-11 14:59:43adminsetgithub: 87812
2021-04-17 16:06:33kjsetstatus: open -> closed
resolution: fixed
messages: + msg391290

stage: resolved
2021-04-15 19:20:08tefrasetstatus: pending -> open

messages: + msg391148
2021-04-13 10:05:17kjsetstatus: open -> pending

messages: + msg390950
2021-03-28 18:51:20kjsetnosy: + kj, gvanrossum, levkivskyi

messages: + msg389643
versions: + Python 3.9
2021-03-28 09:52:49tefracreate