Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fcf5265

Browse filesBrowse files
Backport evaluate_forward_ref() changes (#611)
Refer to python/cpython#133961 I copied the tests from Python 3.14. Two don't pass but could probably be made to pass by backporting more of annotationlib, but that's more than I think we should do now. Fixes #608
1 parent fadc1ed commit fcf5265
Copy full SHA for fcf5265

File tree

Expand file treeCollapse file tree

3 files changed

+158
-99
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+158
-99
lines changed

‎CHANGELOG.md

Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
on Python versions <3.10. PEP 604 was introduced in Python 3.10, and
55
`typing_extensions` does not generally attempt to backport PEP-604 methods
66
to prior versions.
7+
- Further update `typing_extensions.evaluate_forward_ref` with changes in Python 3.14.
78

89
# Release 4.14.0rc1 (May 24, 2025)
910

‎src/test_typing_extensions.py

Copy file name to clipboardExpand all lines: src/test_typing_extensions.py
+149-22Lines changed: 149 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8944,7 +8944,147 @@ def test_pep_695_generics_with_future_annotations_nested_in_function(self):
89448944
set(results.generic_func.__type_params__)
89458945
)
89468946

8947-
class TestEvaluateForwardRefs(BaseTestCase):
8947+
8948+
class EvaluateForwardRefTests(BaseTestCase):
8949+
def test_evaluate_forward_ref(self):
8950+
int_ref = typing_extensions.ForwardRef('int')
8951+
self.assertIs(typing_extensions.evaluate_forward_ref(int_ref), int)
8952+
self.assertIs(
8953+
typing_extensions.evaluate_forward_ref(int_ref, type_params=()),
8954+
int,
8955+
)
8956+
self.assertIs(
8957+
typing_extensions.evaluate_forward_ref(int_ref, format=typing_extensions.Format.VALUE),
8958+
int,
8959+
)
8960+
self.assertIs(
8961+
typing_extensions.evaluate_forward_ref(
8962+
int_ref, format=typing_extensions.Format.FORWARDREF,
8963+
),
8964+
int,
8965+
)
8966+
self.assertEqual(
8967+
typing_extensions.evaluate_forward_ref(
8968+
int_ref, format=typing_extensions.Format.STRING,
8969+
),
8970+
'int',
8971+
)
8972+
8973+
def test_evaluate_forward_ref_undefined(self):
8974+
missing = typing_extensions.ForwardRef('missing')
8975+
with self.assertRaises(NameError):
8976+
typing_extensions.evaluate_forward_ref(missing)
8977+
self.assertIs(
8978+
typing_extensions.evaluate_forward_ref(
8979+
missing, format=typing_extensions.Format.FORWARDREF,
8980+
),
8981+
missing,
8982+
)
8983+
self.assertEqual(
8984+
typing_extensions.evaluate_forward_ref(
8985+
missing, format=typing_extensions.Format.STRING,
8986+
),
8987+
"missing",
8988+
)
8989+
8990+
def test_evaluate_forward_ref_nested(self):
8991+
ref = typing_extensions.ForwardRef("Union[int, list['str']]")
8992+
ns = {"Union": Union}
8993+
if sys.version_info >= (3, 11):
8994+
expected = Union[int, list[str]]
8995+
else:
8996+
expected = Union[int, list['str']] # TODO: evaluate nested forward refs in Python < 3.11
8997+
self.assertEqual(
8998+
typing_extensions.evaluate_forward_ref(ref, globals=ns),
8999+
expected,
9000+
)
9001+
self.assertEqual(
9002+
typing_extensions.evaluate_forward_ref(
9003+
ref, globals=ns, format=typing_extensions.Format.FORWARDREF
9004+
),
9005+
expected,
9006+
)
9007+
self.assertEqual(
9008+
typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.STRING),
9009+
"Union[int, list['str']]",
9010+
)
9011+
9012+
why = typing_extensions.ForwardRef('"\'str\'"')
9013+
self.assertIs(typing_extensions.evaluate_forward_ref(why), str)
9014+
9015+
@skipUnless(sys.version_info >= (3, 10), "Relies on PEP 604")
9016+
def test_evaluate_forward_ref_nested_pep604(self):
9017+
ref = typing_extensions.ForwardRef("int | list['str']")
9018+
if sys.version_info >= (3, 11):
9019+
expected = int | list[str]
9020+
else:
9021+
expected = int | list['str'] # TODO: evaluate nested forward refs in Python < 3.11
9022+
self.assertEqual(
9023+
typing_extensions.evaluate_forward_ref(ref),
9024+
expected,
9025+
)
9026+
self.assertEqual(
9027+
typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.FORWARDREF),
9028+
expected,
9029+
)
9030+
self.assertEqual(
9031+
typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.STRING),
9032+
"int | list['str']",
9033+
)
9034+
9035+
def test_evaluate_forward_ref_none(self):
9036+
none_ref = typing_extensions.ForwardRef('None')
9037+
self.assertIs(typing_extensions.evaluate_forward_ref(none_ref), None)
9038+
9039+
def test_globals(self):
9040+
A = "str"
9041+
ref = typing_extensions.ForwardRef('list[A]')
9042+
with self.assertRaises(NameError):
9043+
typing_extensions.evaluate_forward_ref(ref)
9044+
self.assertEqual(
9045+
typing_extensions.evaluate_forward_ref(ref, globals={'A': A}),
9046+
list[str] if sys.version_info >= (3, 11) else list['str'],
9047+
)
9048+
9049+
def test_owner(self):
9050+
ref = typing_extensions.ForwardRef("A")
9051+
9052+
with self.assertRaises(NameError):
9053+
typing_extensions.evaluate_forward_ref(ref)
9054+
9055+
# We default to the globals of `owner`,
9056+
# so it no longer raises `NameError`
9057+
self.assertIs(
9058+
typing_extensions.evaluate_forward_ref(ref, owner=Loop), A
9059+
)
9060+
9061+
@skipUnless(sys.version_info >= (3, 14), "Not yet implemented in Python < 3.14")
9062+
def test_inherited_owner(self):
9063+
# owner passed to evaluate_forward_ref
9064+
ref = typing_extensions.ForwardRef("list['A']")
9065+
self.assertEqual(
9066+
typing_extensions.evaluate_forward_ref(ref, owner=Loop),
9067+
list[A],
9068+
)
9069+
9070+
# owner set on the ForwardRef
9071+
ref = typing_extensions.ForwardRef("list['A']", owner=Loop)
9072+
self.assertEqual(
9073+
typing_extensions.evaluate_forward_ref(ref),
9074+
list[A],
9075+
)
9076+
9077+
@skipUnless(sys.version_info >= (3, 14), "Not yet implemented in Python < 3.14")
9078+
def test_partial_evaluation(self):
9079+
ref = typing_extensions.ForwardRef("list[A]")
9080+
with self.assertRaises(NameError):
9081+
typing_extensions.evaluate_forward_ref(ref)
9082+
9083+
self.assertEqual(
9084+
typing_extensions.evaluate_forward_ref(ref, format=typing_extensions.Format.FORWARDREF),
9085+
list[EqualToForwardRef('A')],
9086+
)
9087+
89489088
def test_global_constant(self):
89499089
if sys.version_info[:3] > (3, 10, 0):
89509090
self.assertTrue(_FORWARD_REF_HAS_CLASS)
@@ -9107,30 +9247,17 @@ class Y(Generic[Tx]):
91079247
self.assertEqual(get_args(evaluated_ref3), (Z[str],))
91089248

91099249
def test_invalid_special_forms(self):
9110-
# tests _lax_type_check to raise errors the same way as the typing module.
9111-
# Regex capture "< class 'module.name'> and "module.name"
9112-
with self.assertRaisesRegex(
9113-
TypeError, r"Plain .*Protocol('>)? is not valid as type argument"
9114-
):
9115-
evaluate_forward_ref(typing.ForwardRef("Protocol"), globals=vars(typing))
9116-
with self.assertRaisesRegex(
9117-
TypeError, r"Plain .*Generic('>)? is not valid as type argument"
9118-
):
9119-
evaluate_forward_ref(typing.ForwardRef("Generic"), globals=vars(typing))
9120-
with self.assertRaisesRegex(TypeError, r"Plain typing(_extensions)?\.Final is not valid as type argument"):
9121-
evaluate_forward_ref(typing.ForwardRef("Final"), globals=vars(typing))
9122-
with self.assertRaisesRegex(TypeError, r"Plain typing(_extensions)?\.ClassVar is not valid as type argument"):
9123-
evaluate_forward_ref(typing.ForwardRef("ClassVar"), globals=vars(typing))
9250+
for name in ("Protocol", "Final", "ClassVar", "Generic"):
9251+
with self.subTest(name=name):
9252+
self.assertIs(
9253+
evaluate_forward_ref(typing.ForwardRef(name), globals=vars(typing)),
9254+
getattr(typing, name),
9255+
)
91249256
if _FORWARD_REF_HAS_CLASS:
91259257
self.assertIs(evaluate_forward_ref(typing.ForwardRef("Final", is_class=True), globals=vars(typing)), Final)
91269258
self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_class=True), globals=vars(typing)), ClassVar)
9127-
with self.assertRaisesRegex(TypeError, r"Plain typing(_extensions)?\.Final is not valid as type argument"):
9128-
evaluate_forward_ref(typing.ForwardRef("Final", is_argument=False), globals=vars(typing))
9129-
with self.assertRaisesRegex(TypeError, r"Plain typing(_extensions)?\.ClassVar is not valid as type argument"):
9130-
evaluate_forward_ref(typing.ForwardRef("ClassVar", is_argument=False), globals=vars(typing))
9131-
else:
9132-
self.assertIs(evaluate_forward_ref(typing.ForwardRef("Final", is_argument=False), globals=vars(typing)), Final)
9133-
self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_argument=False), globals=vars(typing)), ClassVar)
9259+
self.assertIs(evaluate_forward_ref(typing.ForwardRef("Final", is_argument=False), globals=vars(typing)), Final)
9260+
self.assertIs(evaluate_forward_ref(typing.ForwardRef("ClassVar", is_argument=False), globals=vars(typing)), ClassVar)
91349261

91359262

91369263
class TestSentinels(BaseTestCase):

‎src/typing_extensions.py

Copy file name to clipboardExpand all lines: src/typing_extensions.py
+8-77Lines changed: 8 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4060,57 +4060,6 @@ def _eval_with_owner(
40604060
forward_ref.__forward_value__ = value
40614061
return value
40624062

4063-
def _lax_type_check(
4064-
value, msg, is_argument=True, *, module=None, allow_special_forms=False
4065-
):
4066-
"""
4067-
A lax Python 3.11+ like version of typing._type_check
4068-
"""
4069-
if hasattr(typing, "_type_convert"):
4070-
if (
4071-
sys.version_info >= (3, 10, 3)
4072-
or (3, 9, 10) < sys.version_info[:3] < (3, 10)
4073-
):
4074-
# allow_special_forms introduced later cpython/#30926 (bpo-46539)
4075-
type_ = typing._type_convert(
4076-
value,
4077-
module=module,
4078-
allow_special_forms=allow_special_forms,
4079-
)
4080-
# module was added with bpo-41249 before is_class (bpo-46539)
4081-
elif "__forward_module__" in typing.ForwardRef.__slots__:
4082-
type_ = typing._type_convert(value, module=module)
4083-
else:
4084-
type_ = typing._type_convert(value)
4085-
else:
4086-
if value is None:
4087-
return type(None)
4088-
if isinstance(value, str):
4089-
return ForwardRef(value)
4090-
type_ = value
4091-
invalid_generic_forms = (Generic, Protocol)
4092-
if not allow_special_forms:
4093-
invalid_generic_forms += (ClassVar,)
4094-
if is_argument:
4095-
invalid_generic_forms += (Final,)
4096-
if (
4097-
isinstance(type_, typing._GenericAlias)
4098-
and get_origin(type_) in invalid_generic_forms
4099-
):
4100-
raise TypeError(f"{type_} is not valid as type argument") from None
4101-
if type_ in (Any, LiteralString, NoReturn, Never, Self, TypeAlias):
4102-
return type_
4103-
if allow_special_forms and type_ in (ClassVar, Final):
4104-
return type_
4105-
if (
4106-
isinstance(type_, (_SpecialForm, typing._SpecialForm))
4107-
or type_ in (Generic, Protocol)
4108-
):
4109-
raise TypeError(f"Plain {type_} is not valid as type argument") from None
4110-
if type(type_) is tuple: # lax version with tuple instead of callable
4111-
raise TypeError(f"{msg} Got {type_!r:.100}.")
4112-
return type_
4113-
41144063
def evaluate_forward_ref(
41154064
forward_ref,
41164065
*,
@@ -4163,24 +4112,15 @@ def evaluate_forward_ref(
41634112
else:
41644113
raise
41654114

4166-
msg = "Forward references must evaluate to types."
4167-
if not _FORWARD_REF_HAS_CLASS:
4168-
allow_special_forms = not forward_ref.__forward_is_argument__
4169-
else:
4170-
allow_special_forms = forward_ref.__forward_is_class__
4171-
type_ = _lax_type_check(
4172-
value,
4173-
msg,
4174-
is_argument=forward_ref.__forward_is_argument__,
4175-
allow_special_forms=allow_special_forms,
4176-
)
4115+
if isinstance(value, str):
4116+
value = ForwardRef(value)
41774117

41784118
# Recursively evaluate the type
4179-
if isinstance(type_, ForwardRef):
4180-
if getattr(type_, "__forward_module__", True) is not None:
4119+
if isinstance(value, ForwardRef):
4120+
if getattr(value, "__forward_module__", True) is not None:
41814121
globals = None
41824122
return evaluate_forward_ref(
4183-
type_,
4123+
value,
41844124
globals=globals,
41854125
locals=locals,
41864126
type_params=type_params, owner=owner,
@@ -4194,28 +4134,19 @@ def evaluate_forward_ref(
41944134
locals[tvar.__name__] = tvar
41954135
if sys.version_info < (3, 12, 5):
41964136
return typing._eval_type(
4197-
type_,
4137+
value,
41984138
globals,
41994139
locals,
42004140
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
42014141
)
4202-
if sys.version_info < (3, 14):
4142+
else:
42034143
return typing._eval_type(
4204-
type_,
4144+
value,
42054145
globals,
42064146
locals,
42074147
type_params,
42084148
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
42094149
)
4210-
return typing._eval_type(
4211-
type_,
4212-
globals,
4213-
locals,
4214-
type_params,
4215-
recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
4216-
format=format,
4217-
owner=owner,
4218-
)
42194150

42204151

42214152
class Sentinel:

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.