mirror of
https://github.com/python/cpython.git
synced 2025-11-24 04:17:38 +00:00
[3.14] gh-137226: Fix behavior of ForwardRef.evaluate with type_params (GH-137227) (#137709)
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if the ABI has changed (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
Some checks are pending
Tests / (push) Blocked by required conditions
Tests / Windows MSI (push) Blocked by required conditions
Tests / Change detection (push) Waiting to run
Tests / Docs (push) Blocked by required conditions
Tests / Check if the ABI has changed (push) Blocked by required conditions
Tests / Check if Autoconf files are up to date (push) Blocked by required conditions
Tests / Check if generated files are up to date (push) Blocked by required conditions
Tests / Ubuntu SSL tests with OpenSSL (push) Blocked by required conditions
Tests / Android (aarch64) (push) Blocked by required conditions
Tests / Android (x86_64) (push) Blocked by required conditions
Tests / WASI (push) Blocked by required conditions
Tests / Hypothesis tests on Ubuntu (push) Blocked by required conditions
Tests / Address sanitizer (push) Blocked by required conditions
Tests / Sanitizers (push) Blocked by required conditions
Tests / Cross build Linux (push) Blocked by required conditions
Tests / CIFuzz (push) Blocked by required conditions
Tests / All required checks pass (push) Blocked by required conditions
Lint / lint (push) Waiting to run
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
ad9f20dffa
commit
902de283a8
4 changed files with 47 additions and 14 deletions
|
|
@ -158,21 +158,13 @@ class ForwardRef:
|
|||
# as a way of emulating annotation scopes when calling `eval()`
|
||||
type_params = getattr(owner, "__type_params__", None)
|
||||
|
||||
# type parameters require some special handling,
|
||||
# as they exist in their own scope
|
||||
# but `eval()` does not have a dedicated parameter for that scope.
|
||||
# For classes, names in type parameter scopes should override
|
||||
# names in the global scope (which here are called `localns`!),
|
||||
# but should in turn be overridden by names in the class scope
|
||||
# (which here are called `globalns`!)
|
||||
# Type parameters exist in their own scope, which is logically
|
||||
# between the locals and the globals. We simulate this by adding
|
||||
# them to the globals.
|
||||
if type_params is not None:
|
||||
globals = dict(globals)
|
||||
locals = dict(locals)
|
||||
for param in type_params:
|
||||
param_name = param.__name__
|
||||
if not self.__forward_is_class__ or param_name not in globals:
|
||||
globals[param_name] = param
|
||||
locals.pop(param_name, None)
|
||||
globals[param.__name__] = param
|
||||
if self.__extra_names__:
|
||||
locals = {**locals, **self.__extra_names__}
|
||||
|
||||
|
|
|
|||
|
|
@ -1365,6 +1365,11 @@ class TestAnnotationsToString(unittest.TestCase):
|
|||
class A:
|
||||
pass
|
||||
|
||||
TypeParamsAlias1 = int
|
||||
|
||||
class TypeParamsSample[TypeParamsAlias1, TypeParamsAlias2]:
|
||||
TypeParamsAlias2 = str
|
||||
|
||||
|
||||
class TestForwardRefClass(unittest.TestCase):
|
||||
def test_forwardref_instance_type_error(self):
|
||||
|
|
@ -1597,6 +1602,21 @@ class TestForwardRefClass(unittest.TestCase):
|
|||
ForwardRef("alias").evaluate(owner=Gen, locals={"alias": str}), str
|
||||
)
|
||||
|
||||
def test_evaluate_with_type_params_and_scope_conflict(self):
|
||||
for is_class in (False, True):
|
||||
with self.subTest(is_class=is_class):
|
||||
fwdref1 = ForwardRef("TypeParamsAlias1", owner=TypeParamsSample, is_class=is_class)
|
||||
fwdref2 = ForwardRef("TypeParamsAlias2", owner=TypeParamsSample, is_class=is_class)
|
||||
|
||||
self.assertIs(
|
||||
fwdref1.evaluate(),
|
||||
TypeParamsSample.__type_params__[0],
|
||||
)
|
||||
self.assertIs(
|
||||
fwdref2.evaluate(),
|
||||
TypeParamsSample.TypeParamsAlias2,
|
||||
)
|
||||
|
||||
def test_fwdref_with_module(self):
|
||||
self.assertIs(ForwardRef("Format", module="annotationlib").evaluate(), Format)
|
||||
self.assertIs(
|
||||
|
|
|
|||
|
|
@ -2340,10 +2340,13 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
|
|||
# *base_globals* first rather than *base_locals*.
|
||||
# This only affects ForwardRefs.
|
||||
base_globals, base_locals = base_locals, base_globals
|
||||
type_params = base.__type_params__
|
||||
base_globals, base_locals = _add_type_params_to_scope(
|
||||
type_params, base_globals, base_locals, True)
|
||||
for name, value in ann.items():
|
||||
if isinstance(value, str):
|
||||
value = _make_forward_ref(value, is_argument=False, is_class=True)
|
||||
value = _eval_type(value, base_globals, base_locals, base.__type_params__,
|
||||
value = _eval_type(value, base_globals, base_locals, (),
|
||||
format=format, owner=obj)
|
||||
if value is None:
|
||||
value = type(None)
|
||||
|
|
@ -2379,6 +2382,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
|
|||
elif localns is None:
|
||||
localns = globalns
|
||||
type_params = getattr(obj, "__type_params__", ())
|
||||
globalns, localns = _add_type_params_to_scope(type_params, globalns, localns, False)
|
||||
for name, value in hints.items():
|
||||
if isinstance(value, str):
|
||||
# class-level forward refs were handled above, this must be either
|
||||
|
|
@ -2388,13 +2392,27 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False,
|
|||
is_argument=not isinstance(obj, types.ModuleType),
|
||||
is_class=False,
|
||||
)
|
||||
value = _eval_type(value, globalns, localns, type_params, format=format, owner=obj)
|
||||
value = _eval_type(value, globalns, localns, (), format=format, owner=obj)
|
||||
if value is None:
|
||||
value = type(None)
|
||||
hints[name] = value
|
||||
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
|
||||
|
||||
|
||||
# Add type parameters to the globals and locals scope. This is needed for
|
||||
# compatibility.
|
||||
def _add_type_params_to_scope(type_params, globalns, localns, is_class):
|
||||
if not type_params:
|
||||
return globalns, localns
|
||||
globalns = dict(globalns)
|
||||
localns = dict(localns)
|
||||
for param in type_params:
|
||||
if not is_class or param.__name__ not in globalns:
|
||||
globalns[param.__name__] = param
|
||||
localns.pop(param.__name__, None)
|
||||
return globalns, localns
|
||||
|
||||
|
||||
def _strip_annotations(t):
|
||||
"""Strip the annotations from a given type."""
|
||||
if isinstance(t, _AnnotatedAlias):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
Fix behavior of :meth:`annotationlib.ForwardRef.evaluate` when the
|
||||
*type_params* parameter is passed and the name of a type param is also
|
||||
present in an enclosing scope.
|
||||
Loading…
Add table
Add a link
Reference in a new issue