From 762717513867e043f40fbfe66a58902ac7407989 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 5 Jan 2026 00:37:12 +0100 Subject: [PATCH 1/4] Change the typing spec around string references --- conformance/tests/annotations_forward_refs.py | 14 +++++++------- docs/spec/annotations.rst | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/conformance/tests/annotations_forward_refs.py b/conformance/tests/annotations_forward_refs.py index 70c64470..7233fa2c 100644 --- a/conformance/tests/annotations_forward_refs.py +++ b/conformance/tests/annotations_forward_refs.py @@ -7,7 +7,7 @@ import types -from typing import assert_type +from typing import assert_type, Any def func1( @@ -59,7 +59,7 @@ def invalid_annotations( # > 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. +# > namespaces in which normal non-string types would be evaluated. class ClassB: @@ -79,21 +79,21 @@ 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) +assert_type(ClassD.str, Any) +assert_type(ClassD.x, Any) # > If a triple quote is used, the string should be parsed as though it is implicitly diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 9ef5bfe5..62358d33 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -243,8 +243,7 @@ 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. +same namespaces in which normal non-string types 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 `. From d08c226aa8aa0bde47935b58218dfc319c347a77 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 5 Jan 2026 11:17:57 +0100 Subject: [PATCH 2/4] Improve the wording around string references in the spec --- conformance/tests/annotations_forward_refs.py | 7 ++-- docs/spec/annotations.rst | 33 +++++-------------- 2 files changed, 11 insertions(+), 29 deletions(-) diff --git a/conformance/tests/annotations_forward_refs.py b/conformance/tests/annotations_forward_refs.py index 7233fa2c..688aa4ba 100644 --- a/conformance/tests/annotations_forward_refs.py +++ b/conformance/tests/annotations_forward_refs.py @@ -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 normal non-string types 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 diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 62358d33..5992f3a3 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -222,31 +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 normal non-string types 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 `. +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`. 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 From 070312df7d477bf14557b1ed7756466b7af494de Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Mon, 5 Jan 2026 22:38:39 +0100 Subject: [PATCH 3/4] Remove error types --- conformance/tests/annotations_forward_refs.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/conformance/tests/annotations_forward_refs.py b/conformance/tests/annotations_forward_refs.py index 688aa4ba..7b6153d5 100644 --- a/conformance/tests/annotations_forward_refs.py +++ b/conformance/tests/annotations_forward_refs.py @@ -91,10 +91,6 @@ def __init__(self) -> None: self.ClassC = ClassC() -assert_type(ClassD.str, Any) -assert_type(ClassD.x, Any) - - # > 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. From 41fc20c93acbef9b339cb755495bb4139599ba4a Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 8 Jan 2026 09:35:41 +0000 Subject: [PATCH 4/4] Update docs/spec/annotations.rst Co-authored-by: Jelle Zijlstra --- docs/spec/annotations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spec/annotations.rst b/docs/spec/annotations.rst index 5992f3a3..28a5dd6d 100644 --- a/docs/spec/annotations.rst +++ b/docs/spec/annotations.rst @@ -224,7 +224,7 @@ definition may be expressed as a string literal, to be resolved later. 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`. Names within the expression are looked up in the +: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