Skip to content
19 changes: 7 additions & 12 deletions conformance/tests/annotations_forward_refs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


import types
from typing import assert_type
from typing import assert_type, Any


def func1(
Expand Down Expand Up @@ -57,10 +57,9 @@ def invalid_annotations(
pass


# > It should evaluate without errors once the module has been fully loaded.
# > The local and global namespace in which it is evaluated should be the same
# > namespaces in which default arguments to the same function would be evaluated.

# > Names within the expression are looked up in the same way as they would be
# > looked up at runtime in Python 3.14 and higher if the annotation was not
# > enclosed in a string literal.

class ClassB:
def method1(self) -> ClassB: # E?: Runtime error prior to 3.14
Expand All @@ -79,23 +78,19 @@ class ClassD:

ClassF: "ClassF" # E: circular reference

str: "str" = "" # OK
str: "str" = "" # E: circular reference

def int(self) -> None: # OK
...

x: "int" = 0 # OK

y: int = 0 # E: Refers to local int, which isn't a legal type expression

x: "int" = 0 # # E: Refers to a local int as well

def __init__(self) -> None:
self.ClassC = ClassC()


assert_type(ClassD.str, str)
assert_type(ClassD.x, int)


# > If a triple quote is used, the string should be parsed as though it is implicitly
# > surrounded by parentheses. This allows newline characters to be
# > used within the string literal.
Expand Down
34 changes: 8 additions & 26 deletions docs/spec/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -222,32 +222,14 @@ String annotations
When a type hint cannot be evaluated at runtime, that
definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a
container class, where the class being defined occurs in the signature
of some of the methods. For example, the following code (the start of
a simple binary tree implementation) does not work::

class Tree:
def __init__(self, left: Tree, right: Tree):
self.left = left
self.right = right

To address this, we write::

class Tree:
def __init__(self, left: 'Tree', right: 'Tree'):
self.left = left
self.right = right

The string literal should contain a valid Python expression (i.e.,
``compile(lit, '', 'eval')`` should be a valid code object) and it
should evaluate without errors once the module has been fully loaded.
The local and global namespace in which it is evaluated should be the
same namespaces in which default arguments to the same function would
be evaluated.

Moreover, the expression should be parseable as a valid type hint, i.e.,
it is constrained by the rules from :ref:`the expression grammar <expression-grammar>`.
The string literal should contain a syntactically valid Python expression
(i.e., ``compile(lit, '', 'eval')`` should succeed) that evaluates to a valid
:term:`annotation expression`. Regardless of the Python version used, names within the expression are looked up in the
same way as they would be looked up at runtime in Python 3.14 and higher if the
annotation was not enclosed in a string literal. Thus, name lookup follows
general rules (e.g., the current function, class, or module scope first, and
the builtin scope last), but names defined later within the same scope can be
used in an earlier annotation.

If a triple quote is used, the string should be parsed as though it is
implicitly surrounded by parentheses. This allows newline characters to be
Expand Down