classification
Title: PEP 604 Union syntax does not support forward references
Type: behavior Stage:
Components: Versions: Python 3.11, Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: AlexWaygood, TNThung, eric.smith, gvanrossum, kj
Priority: normal Keywords:

Created on 2021-11-21 14:48 by TNThung, last changed 2021-11-22 16:46 by AlexWaygood.

Messages (6)
msg406720 - (view) Author: 洪明聖 (TNThung) Date: 2021-11-21 14:48
The class methods have a problem compiling when the type refers to the union of itself and others.


# tmp.py
class Foo:
    def __init__(self, tmp: "Foo"|int):
        pass


# Error
Traceback (most recent call last):
  File "/Project/Mslc/Grammar/tmp.py", line 1, in <module>
    class Foo:
  File "/Project/Mslc/Grammar/tmp.py", line 2, in Foo
    def __init__(self, tmp: "Foo"|int):
TypeError: unsupported operand type(s) for |: 'str' and 'type'
msg406724 - (view) Author: Alex Waygood (AlexWaygood) * Date: 2021-11-21 15:26
Reproduced on 3.11. The error occurs if a type is on the left-hand-side of the operand, as well as if a type is on the right-hand-side:

```
>>> int | "str"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'type' and 'str'
>>> "str" | int
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'str' and 'type'
```
msg406726 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2021-11-21 15:33
Presumably the correct way to do this is:

def __init__(self, tmp: "Foo|int"):

That is, the entire type hint is a string.
msg406731 - (view) Author: Alex Waygood (AlexWaygood) * Date: 2021-11-21 17:08
Arguably, either the implementation should be altered to support forward references, or the documentation at https://docs.python.org/3/library/stdtypes.html#union-type should be altered to make clear that, when type-hinting a union that includes a forward reference, the entire expression should be given as a string, as Eric suggests.
msg406775 - (view) Author: Ken Jin (kj) * (Python committer) Date: 2021-11-22 13:33
I think I saw a similar bug report elsewhere (or maybe I'm misremembering). Anyways, Eric is right, the correct way is to wrap the entire thing, so "Foo|int" instead of "Foo"|int.

@Alex you brought up some good suggestions, I'll try to address them:

> Arguably, either the implementation should be altered to support forward references

Unfortunately that's more complex than it seems. The original draft PEP 604 proposed implementation actually imported Union from typing.py, and I recall Guido disliking the idea that a builtin type should depend on typing.py. I have to agree with that philosophy here. I also don't think the alternative -- implementing a builtin ForwardRef type isn't worth the complexity unless our situation changes.

> the documentation at https://docs.python.org/3/library/stdtypes.html#union-type should be altered to make clear that ...

The first line says: "A union object holds the value of the | (bitwise or) operation on multiple type objects." It says *type objects*, which strings don't belong to.

@TNThung does Eric's suggestion work for you? Or do you need something else?
msg406792 - (view) Author: Alex Waygood (AlexWaygood) * Date: 2021-11-22 16:46
Thanks, Ken! To clarify: I agree that changing the implementation here would probably be a bad way to go: it would be foolish to try to replicate all the functionality of the typing module as builtins. I also think the existing documentation at https://docs.python.org/3/library/stdtypes.html#union-type is actually very good, so I don't think it needs a fundamental rewrite by any means.

I do still think a sentence or two could be added to the documentation, however, clarifying how to deal with forward references, since the behaviour here is different to the older, more established syntax using typing.Union. Something like this?

"""
Note: The object on both sides of the | operand must be an object that defines the __or__ special method. As the str type does not support __or__, the expression `int | "Foo"`, where  "Foo" is a reference to a class not yet defined, will fail at runtime. To annotate forward references using union-type expressions, present the whole expression as a string, e.g. `"int | Foo"`.
"""
History
Date User Action Args
2021-11-22 16:46:08AlexWaygoodsetstatus: pending -> open

messages: + msg406792
2021-11-22 13:33:37kjsetstatus: open -> pending

messages: + msg406775
2021-11-21 17:08:02AlexWaygoodsetmessages: + msg406731
2021-11-21 15:33:39eric.smithsetnosy: + eric.smith
messages: + msg406726
2021-11-21 15:26:57AlexWaygoodsetversions: + Python 3.11
nosy: + gvanrossum, kj, AlexWaygood
title: Type hint for methods -> PEP 604 Union syntax does not support forward references
messages: + msg406724


type: compile error -> behavior
2021-11-21 14:48:31TNThungcreate