Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(options)
STRUCT_FOR_ID(order)
STRUCT_FOR_ID(origin)
STRUCT_FOR_ID(other)
STRUCT_FOR_ID(out_fd)
STRUCT_FOR_ID(outgoing)
STRUCT_FOR_ID(outpath)
Expand Down Expand Up @@ -704,6 +705,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(return)
STRUCT_FOR_ID(reverse)
STRUCT_FOR_ID(reversed)
STRUCT_FOR_ID(rounding)
STRUCT_FOR_ID(salt)
STRUCT_FOR_ID(sched_priority)
STRUCT_FOR_ID(scheduler)
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 4 additions & 12 deletions Lib/annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,21 +158,13 @@ def evaluate(
# 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__}

Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_annotationlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,11 @@ def test_annotations_to_string(self):
class A:
pass

TypeParamsAlias1 = int

class TypeParamsSample[TypeParamsAlias1, TypeParamsAlias2]:
TypeParamsAlias2 = str


class TestForwardRefClass(unittest.TestCase):
def test_forwardref_instance_type_error(self):
Expand Down Expand Up @@ -1597,6 +1602,21 @@ class Gen[T]:
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(
Expand Down
84 changes: 43 additions & 41 deletions Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,52 +453,53 @@ def foo(a: int, b: str) -> str:
"""

dis_traceback = """\
%4d RESUME 0
%4d RESUME 0

%4d NOP
%4d NOP

%4d L1: LOAD_SMALL_INT 1
LOAD_SMALL_INT 0
--> BINARY_OP 11 (/)
POP_TOP
%4d L1: LOAD_SMALL_INT 1
LOAD_SMALL_INT 0
--> BINARY_OP 11 (/)
POP_TOP

%4d L2: LOAD_FAST_CHECK 1 (tb)
RETURN_VALUE
%4d L2: LOAD_FAST_CHECK 1 (tb)
RETURN_VALUE

-- L3: PUSH_EXC_INFO
-- L3: PUSH_EXC_INFO

%4d LOAD_GLOBAL 0 (Exception)
CHECK_EXC_MATCH
POP_JUMP_IF_FALSE 24 (to L7)
NOT_TAKEN
STORE_FAST 0 (e)
%4d LOAD_GLOBAL 0 (Exception)
CHECK_EXC_MATCH
POP_JUMP_IF_FALSE 24 (to L9)
L4: NOT_TAKEN
L5: STORE_FAST 0 (e)

%4d L4: LOAD_FAST 0 (e)
LOAD_ATTR 2 (__traceback__)
STORE_FAST 1 (tb)
L5: POP_EXCEPT
LOAD_CONST 1 (None)
STORE_FAST 0 (e)
DELETE_FAST 0 (e)
%4d L6: LOAD_FAST 0 (e)
LOAD_ATTR 2 (__traceback__)
STORE_FAST 1 (tb)
L7: POP_EXCEPT
LOAD_CONST 1 (None)
STORE_FAST 0 (e)
DELETE_FAST 0 (e)

%4d LOAD_FAST 1 (tb)
RETURN_VALUE
%4d LOAD_FAST 1 (tb)
RETURN_VALUE

-- L6: LOAD_CONST 1 (None)
STORE_FAST 0 (e)
DELETE_FAST 0 (e)
RERAISE 1
-- L8: LOAD_CONST 1 (None)
STORE_FAST 0 (e)
DELETE_FAST 0 (e)
RERAISE 1

%4d L7: RERAISE 0
%4d L9: RERAISE 0

-- L8: COPY 3
POP_EXCEPT
RERAISE 1
-- L10: COPY 3
POP_EXCEPT
RERAISE 1
ExceptionTable:
L1 to L2 -> L3 [0]
L3 to L4 -> L8 [1] lasti
L4 to L5 -> L6 [1] lasti
L6 to L8 -> L8 [1] lasti
L3 to L4 -> L10 [1] lasti
L5 to L6 -> L10 [1] lasti
L6 to L7 -> L8 [1] lasti
L8 to L10 -> L10 [1] lasti
""" % (TRACEBACK_CODE.co_firstlineno,
TRACEBACK_CODE.co_firstlineno + 1,
TRACEBACK_CODE.co_firstlineno + 2,
Expand Down Expand Up @@ -567,11 +568,11 @@ def _with(c):
%4d L3: PUSH_EXC_INFO
WITH_EXCEPT_START
TO_BOOL
POP_JUMP_IF_TRUE 2 (to L4)
NOT_TAKEN
RERAISE 2
L4: POP_TOP
L5: POP_EXCEPT
POP_JUMP_IF_TRUE 2 (to L6)
L4: NOT_TAKEN
L5: RERAISE 2
L6: POP_TOP
L7: POP_EXCEPT
POP_TOP
POP_TOP
POP_TOP
Expand All @@ -581,12 +582,13 @@ def _with(c):
LOAD_CONST 1 (None)
RETURN_VALUE

-- L6: COPY 3
-- L8: COPY 3
POP_EXCEPT
RERAISE 1
ExceptionTable:
L1 to L2 -> L3 [2] lasti
L3 to L5 -> L6 [4] lasti
L3 to L4 -> L8 [4] lasti
L5 to L7 -> L8 [4] lasti
""" % (_with.__code__.co_firstlineno,
_with.__code__.co_firstlineno + 1,
_with.__code__.co_firstlineno + 2,
Expand Down
22 changes: 20 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2366,10 +2366,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)
Expand Down Expand Up @@ -2405,6 +2408,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
Expand All @@ -2414,13 +2418,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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Speedup processing arguments (up to 1.5x) in the :mod:`decimal` module
methods, that now using :c:macro:`METH_FASTCALL` calling convention. Patch
by Sergey B Kirpichev.
Loading
Loading