bpo-38605: Make 'from __future__ import annotations' the default (GH-20434)

The hard part was making all the tests pass; there are some subtle issues here, because apparently the future import wasn't tested very thoroughly in previous Python versions.

For example, `inspect.signature()` returned type objects normally (except for forward references), but strings with the future import. We changed it to try and return type objects by calling `typing.get_type_hints()`, but fall back on returning strings if that function fails (which it may do if there are future references in the annotations that require passing in a specific namespace to resolve).
This commit is contained in:
Batuhan Taskaya 2020-10-06 23:03:02 +03:00 committed by GitHub
parent bef7d299eb
commit 044a1048ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 403 additions and 299 deletions

View file

@ -610,13 +610,9 @@ following the parameter name. Any parameter may have an annotation, even those
``*identifier`` or ``**identifier``. Functions may have "return" annotation of
the form "``-> expression``" after the parameter list. These annotations can be
any valid Python expression. The presence of annotations does not change the
semantics of a function. The annotation values are available as values of
a dictionary keyed by the parameters' names in the :attr:`__annotations__`
attribute of the function object. If the ``annotations`` import from
:mod:`__future__` is used, annotations are preserved as strings at runtime which
enables postponed evaluation. Otherwise, they are evaluated when the function
definition is executed. In this case annotations may be evaluated in
a different order than they appear in the source code.
semantics of a function. The annotation values are available as string values
in a dictionary keyed by the parameters' names in the :attr:`__annotations__`
attribute of the function object.
.. index:: pair: lambda; expression

View file

@ -70,6 +70,23 @@ Summary -- Release highlights
New Features
============
.. _whatsnew310-pep563:
PEP 563: Postponed Evaluation of Annotations Becomes Default
------------------------------------------------------------
In Python 3.7, postponed evaluation of annotations was added,
to be enabled with a ``from __future__ import annotations``
directive. In 3.10 this became the default behavior, even
without that future directive. With this being default, all
annotations stored in :attr:`__annotations__` will be strings.
If needed, annotations can be resolved at runtime using
:func:`typing.get_type_hints`. See :pep:`563` for a full
description. Also, the :func:`inspect.signature` will try to
resolve types from now on, and when it fails it will fall back to
showing the string annotations. (Contributed by Batuhan Taskaya
in :issue:`38605`.)
* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
number of ones in the binary expansion of a given integer, also known
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)

View file

@ -399,8 +399,10 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
ns = {}
exec(txt, globals, ns)
return ns['__create_fn__'](**locals)
func = ns['__create_fn__'](**locals)
for arg, annotation in func.__annotations__.copy().items():
func.__annotations__[arg] = locals[annotation]
return func
def _field_assign(frozen, name, value, self_name):
# If we're a frozen class, then assign to our fields in __init__
@ -651,6 +653,11 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
# a eval() penalty for every single field of every dataclass
# that's defined. It was judged not worth it.
# Strip away the extra quotes as a result of double-stringifying when the
# 'annotations' feature became default.
if annotation.startswith(("'", '"')) and annotation.endswith(("'", '"')):
annotation = annotation[1:-1]
match = _MODULE_IDENTIFIER_RE.match(annotation)
if match:
ns = None
@ -991,7 +998,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
if not getattr(cls, '__doc__'):
# Create a class doc-string.
cls.__doc__ = (cls.__name__ +
str(inspect.signature(cls)).replace(' -> None', ''))
str(inspect.signature(cls)).replace(' -> NoneType', ''))
abc.update_abstractmethods(cls)

View file

@ -45,6 +45,7 @@
import tokenize
import token
import types
import typing
import warnings
import functools
import builtins
@ -1877,7 +1878,10 @@ def _signature_is_functionlike(obj):
code = getattr(obj, '__code__', None)
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here
annotations = getattr(obj, '__annotations__', None)
try:
annotations = _get_type_hints(obj)
except AttributeError:
annotations = None
return (isinstance(code, types.CodeType) and
isinstance(name, str) and
@ -2118,6 +2122,16 @@ def p(name_node, default_node, default=empty):
return cls(parameters, return_annotation=cls.empty)
def _get_type_hints(func):
try:
return typing.get_type_hints(func)
except Exception:
# First, try to use the get_type_hints to resolve
# annotations. But for keeping the behavior intact
# if there was a problem with that (like the namespace
# can't resolve some annotation) continue to use
# string annotations
return func.__annotations__
def _signature_from_builtin(cls, func, skip_bound_arg=True):
"""Private helper function to get signature for
@ -2161,7 +2175,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
annotations = func.__annotations__
annotations = _get_type_hints(func)
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__

View file

@ -1,9 +1,3 @@
#from __future__ import annotations
USING_STRINGS = False
# dataclass_module_1.py and dataclass_module_1_str.py are identical
# except only the latter uses string annotations.
import dataclasses
import typing

View file

@ -1,32 +0,0 @@
from __future__ import annotations
USING_STRINGS = True
# dataclass_module_1.py and dataclass_module_1_str.py are identical
# except only the latter uses string annotations.
import dataclasses
import typing
T_CV2 = typing.ClassVar[int]
T_CV3 = typing.ClassVar
T_IV2 = dataclasses.InitVar[int]
T_IV3 = dataclasses.InitVar
@dataclasses.dataclass
class CV:
T_CV4 = typing.ClassVar
cv0: typing.ClassVar[int] = 20
cv1: typing.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclasses.dataclass
class IV:
T_IV4 = dataclasses.InitVar
iv0: dataclasses.InitVar[int]
iv1: dataclasses.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

View file

@ -1,9 +1,3 @@
#from __future__ import annotations
USING_STRINGS = False
# dataclass_module_2.py and dataclass_module_2_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass, InitVar
from typing import ClassVar

View file

@ -1,32 +0,0 @@
from __future__ import annotations
USING_STRINGS = True
# dataclass_module_2.py and dataclass_module_2_str.py are identical
# except only the latter uses string annotations.
from dataclasses import dataclass, InitVar
from typing import ClassVar
T_CV2 = ClassVar[int]
T_CV3 = ClassVar
T_IV2 = InitVar[int]
T_IV3 = InitVar
@dataclass
class CV:
T_CV4 = ClassVar
cv0: ClassVar[int] = 20
cv1: ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = InitVar
iv0: InitVar[int]
iv1: InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

View file

@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses

View file

@ -0,0 +1,228 @@
import unittest
import sys
from textwrap import dedent
class PostponedAnnotationsTestCase(unittest.TestCase):
template = dedent(
"""
def f() -> {ann}:
...
def g(arg: {ann}) -> None:
...
async def f2() -> {ann}:
...
async def g2(arg: {ann}) -> None:
...
var: {ann}
var2: {ann} = None
"""
)
def getActual(self, annotation):
scope = {}
exec(self.template.format(ann=annotation), {}, scope)
func_ret_ann = scope['f'].__annotations__['return']
func_arg_ann = scope['g'].__annotations__['arg']
async_func_ret_ann = scope['f2'].__annotations__['return']
async_func_arg_ann = scope['g2'].__annotations__['arg']
var_ann1 = scope['__annotations__']['var']
var_ann2 = scope['__annotations__']['var2']
self.assertEqual(func_ret_ann, func_arg_ann)
self.assertEqual(func_ret_ann, async_func_ret_ann)
self.assertEqual(func_ret_ann, async_func_arg_ann)
self.assertEqual(func_ret_ann, var_ann1)
self.assertEqual(func_ret_ann, var_ann2)
return func_ret_ann
def assertAnnotationEqual(
self, annotation, expected=None, drop_parens=False, is_tuple=False,
):
actual = self.getActual(annotation)
if expected is None:
expected = annotation if not is_tuple else annotation[1:-1]
if drop_parens:
self.assertNotEqual(actual, expected)
actual = actual.replace("(", "").replace(")", "")
self.assertEqual(actual, expected)
def test_annotations(self):
eq = self.assertAnnotationEqual
eq('...')
eq("'some_string'")
eq("u'some_string'")
eq("b'\\xa3'")
eq('Name')
eq('None')
eq('True')
eq('False')
eq('1')
eq('1.0')
eq('1j')
eq('True or False')
eq('True or False or None')
eq('True and False')
eq('True and False and None')
eq('Name1 and Name2 or Name3')
eq('Name1 and (Name2 or Name3)')
eq('Name1 or Name2 and Name3')
eq('(Name1 or Name2) and Name3')
eq('Name1 and Name2 or Name3 and Name4')
eq('Name1 or Name2 and Name3 or Name4')
eq('a + b + (c + d)')
eq('a * b * (c * d)')
eq('(a ** b) ** c ** d')
eq('v1 << 2')
eq('1 >> v2')
eq('1 % finished')
eq('1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8')
eq('not great')
eq('not not great')
eq('~great')
eq('+value')
eq('++value')
eq('-1')
eq('~int and not v1 ^ 123 + v2 | True')
eq('a + (not b)')
eq('lambda: None')
eq('lambda arg: None')
eq('lambda a=True: a')
eq('lambda a, b, c=True: a')
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
eq("lambda a, b, c=True, *vararg, d, e='str', **kwargs: a + b")
eq("lambda a, /, b, c=True, *vararg, d, e='str', **kwargs: a + b")
eq('lambda x, /: x')
eq('lambda x=1, /: x')
eq('lambda x, /, y: x + y')
eq('lambda x=1, /, y=2: x + y')
eq('lambda x, /, y=1: x + y')
eq('lambda x, /, y=1, *, z=3: x + y + z')
eq('lambda x=1, /, y=2, *, z=3: x + y + z')
eq('lambda x=1, /, y=2, *, z: x + y + z')
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2: x + y + z + w + l + l2')
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2, **kwargs: x + y + z + w + l + l2')
eq('lambda x, /, y=1, *, z: x + y + z')
eq('lambda x: lambda y: x + y')
eq('1 if True else 2')
eq('str or None if int or True else str or bytes or None')
eq('str or None if (1 if True else 2) else str or bytes or None')
eq("0 if not x else 1 if x > 0 else -1")
eq("(1 if x > 0 else -1) if x else 0")
eq("{'2.7': dead, '3.7': long_live or die_hard}")
eq("{'2.7': dead, '3.7': long_live or die_hard, **{'3.6': verygood}}")
eq("{**a, **b, **c}")
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
eq("{*a, *b, *c}")
eq("({'a': 'b'}, True or False, +value, 'string', b'bytes') or None")
eq("()")
eq("(a,)")
eq("(a, b)")
eq("(a, b, c)")
eq("(*a, *b, *c)")
eq("[]")
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
eq("[*a, *b, *c]")
eq("{i for i in (1, 2, 3)}")
eq("{i ** 2 for i in (1, 2, 3)}")
eq("{i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
eq("{i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)}")
eq("[i for i in (1, 2, 3)]")
eq("[i ** 2 for i in (1, 2, 3)]")
eq("[i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
eq("[i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)]")
eq("(i for i in (1, 2, 3))")
eq("(i ** 2 for i in (1, 2, 3))")
eq("(i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
eq("(i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3))")
eq("{i: 0 for i in (1, 2, 3)}")
eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
eq("[(x, y) for x, y in (a, b)]")
eq("[(x,) for x, in (a,)]")
eq("Python3 > Python2 > COBOL")
eq("Life is Life")
eq("call()")
eq("call(arg)")
eq("call(kwarg='hey')")
eq("call(arg, kwarg='hey')")
eq("call(arg, *args, another, kwarg='hey')")
eq("call(arg, another, kwarg='hey', **kwargs, kwarg2='ho')")
eq("lukasz.langa.pl")
eq("call.me(maybe)")
eq("1 .real")
eq("1.0.real")
eq("....__class__")
eq("list[str]")
eq("dict[str, int]")
eq("set[str,]")
eq("tuple[str, ...]")
eq("tuple[(str, *types)]")
eq("tuple[str, int, (str, int)]")
eq("tuple[(*int, str, str, (str, int))]")
eq("tuple[str, int, float, dict[str, int]]")
eq("slice[0]")
eq("slice[0:1]")
eq("slice[0:1:2]")
eq("slice[:]")
eq("slice[:-1]")
eq("slice[1:]")
eq("slice[::-1]")
eq("slice[:,]")
eq("slice[1:2,]")
eq("slice[1:2:3,]")
eq("slice[1:2, 1]")
eq("slice[1:2, 2, 3]")
eq("slice[()]")
eq("slice[a, b:c, d:e:f]")
eq("slice[(x for x in a)]")
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
eq("f'f-string without formatted values is just a string'")
eq("f'{{NOT a formatted value}}'")
eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
eq('''f"{f'{nested} inner'} outer"''')
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
eq("f'{(lambda x: x)}'")
eq("f'{(None if a else lambda x: x)}'")
eq("f'{x}'")
eq("f'{x!r}'")
eq("f'{x!a}'")
eq('(yield from outside_of_generator)')
eq('(yield)')
eq('(yield a + b)')
eq('await some.complicated[0].call(with_args=True or 1 is not 1)')
eq('[x for x in (a if b else c)]')
eq('[x for x in a if (b if c else d)]')
eq('f(x for x in a)')
eq('f(1, (x for x in a))')
eq('f((x for x in a), 2)')
eq('(((a)))', 'a')
eq('(((a, b)))', '(a, b)')
eq("(x := 10)")
eq("f'{(x := 10):=10}'")
eq("1 + 2")
eq("1 + 2 + 3")
def test_fstring_debug_annotations(self):
# f-strings with '=' don't round trip very well, so set the expected
# result explicitely.
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
self.assertAnnotationEqual("f'{x=:}'", expected="f'x={x:}'")
self.assertAnnotationEqual("f'{x=:.2f}'", expected="f'x={x:.2f}'")
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'")
self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'")
def test_infinity_numbers(self):
inf = "1e" + repr(sys.float_info.max_10_exp + 1)
infj = f"{inf}j"
self.assertAnnotationEqual("1e1000", expected=inf)
self.assertAnnotationEqual("1e1000j", expected=infj)
self.assertAnnotationEqual("-1e1000", expected=f"-{inf}")
self.assertAnnotationEqual("3+1e1000j", expected=f"3 + {infj}")
self.assertAnnotationEqual("(1e1000, 1e1000j)", expected=f"({inf}, {infj})")
self.assertAnnotationEqual("'inf'")
self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})")
self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))")
if __name__ == "__main__":
unittest.main()

View file

@ -91,10 +91,6 @@ def test_badsyntax_1(self):
pass
""",
"""async def foo(a:await something()):
pass
""",
"""async def foo():
def bar():
[i async for i in els]
@ -299,10 +295,6 @@ def bar():
pass
""",
"""async def foo(a:await b):
pass
""",
"""def baz():
async def foo(a=await b):
pass

View file

@ -9,6 +9,7 @@
import inspect
import builtins
import unittest
from textwrap import dedent
from unittest.mock import Mock
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
from typing import get_type_hints
@ -562,17 +563,17 @@ class C:
self.assertEqual(len(the_fields), 3)
self.assertEqual(the_fields[0].name, 'x')
self.assertEqual(the_fields[0].type, int)
self.assertEqual(the_fields[0].type, 'int')
self.assertFalse(hasattr(C, 'x'))
self.assertTrue (the_fields[0].init)
self.assertTrue (the_fields[0].repr)
self.assertEqual(the_fields[1].name, 'y')
self.assertEqual(the_fields[1].type, str)
self.assertEqual(the_fields[1].type, 'str')
self.assertIsNone(getattr(C, 'y'))
self.assertFalse(the_fields[1].init)
self.assertTrue (the_fields[1].repr)
self.assertEqual(the_fields[2].name, 'z')
self.assertEqual(the_fields[2].type, str)
self.assertEqual(the_fields[2].type, 'str')
self.assertFalse(hasattr(C, 'z'))
self.assertTrue (the_fields[2].init)
self.assertFalse(the_fields[2].repr)
@ -758,11 +759,11 @@ class F:
def validate_class(cls):
# First, check __annotations__, even though they're not
# function annotations.
self.assertEqual(cls.__annotations__['i'], int)
self.assertEqual(cls.__annotations__['j'], str)
self.assertEqual(cls.__annotations__['k'], F)
self.assertEqual(cls.__annotations__['l'], float)
self.assertEqual(cls.__annotations__['z'], complex)
self.assertEqual(cls.__annotations__['i'], 'int')
self.assertEqual(cls.__annotations__['j'], 'str')
self.assertEqual(cls.__annotations__['k'], 'F')
self.assertEqual(cls.__annotations__['l'], 'float')
self.assertEqual(cls.__annotations__['z'], 'complex')
# Verify __init__.
@ -777,22 +778,22 @@ def validate_class(cls):
self.assertEqual(param.name, 'self')
param = next(params)
self.assertEqual(param.name, 'i')
self.assertIs (param.annotation, int)
self.assertIs (param.annotation, 'int')
self.assertEqual(param.default, inspect.Parameter.empty)
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params)
self.assertEqual(param.name, 'j')
self.assertIs (param.annotation, str)
self.assertIs (param.annotation, 'str')
self.assertEqual(param.default, inspect.Parameter.empty)
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params)
self.assertEqual(param.name, 'k')
self.assertIs (param.annotation, F)
self.assertIs (param.annotation, 'F')
# Don't test for the default, since it's set to MISSING.
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params)
self.assertEqual(param.name, 'l')
self.assertIs (param.annotation, float)
self.assertIs (param.annotation, 'float')
# Don't test for the default, since it's set to MISSING.
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
self.assertRaises(StopIteration, next, params)
@ -2806,13 +2807,10 @@ class C:
class TestStringAnnotations(unittest.TestCase):
def test_classvar(self):
# Some expressions recognized as ClassVar really aren't. But
# if you're using string annotations, it's not an exact
# science.
# These tests assume that both "import typing" and "from
# typing import *" have been run in this file.
for typestr in ('ClassVar[int]',
'ClassVar [int]'
'ClassVar [int]',
' ClassVar [int]',
'ClassVar',
' ClassVar ',
@ -2823,17 +2821,15 @@ def test_classvar(self):
'typing. ClassVar[str]',
'typing.ClassVar [str]',
'typing.ClassVar [ str]',
# Double stringified
'"typing.ClassVar[int]"',
# Not syntactically valid, but these will
# be treated as ClassVars.
# be treated as ClassVars.
'typing.ClassVar.[int]',
'typing.ClassVar+',
):
with self.subTest(typestr=typestr):
@dataclass
class C:
x: typestr
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
# x is a ClassVar, so C() takes no args.
C()
@ -2854,9 +2850,7 @@ def test_isnt_classvar(self):
'typingxClassVar[str]',
):
with self.subTest(typestr=typestr):
@dataclass
class C:
x: typestr
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
# x is not a ClassVar, so C() takes one arg.
self.assertEqual(C(10).x, 10)
@ -2876,16 +2870,16 @@ def test_initvar(self):
'dataclasses. InitVar[str]',
'dataclasses.InitVar [str]',
'dataclasses.InitVar [ str]',
# Double stringified
'"dataclasses.InitVar[int]"',
# Not syntactically valid, but these will
# be treated as InitVars.
'dataclasses.InitVar.[int]',
'dataclasses.InitVar+',
):
with self.subTest(typestr=typestr):
@dataclass
class C:
x: typestr
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
# x is an InitVar, so doesn't create a member.
with self.assertRaisesRegex(AttributeError,
@ -2899,30 +2893,22 @@ def test_isnt_initvar(self):
'typing.xInitVar[int]',
):
with self.subTest(typestr=typestr):
@dataclass
class C:
x: typestr
C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
# x is not an InitVar, so there will be a member x.
self.assertEqual(C(10).x, 10)
def test_classvar_module_level_import(self):
from test import dataclass_module_1
from test import dataclass_module_1_str
from test import dataclass_module_2
from test import dataclass_module_2_str
for m in (dataclass_module_1, dataclass_module_1_str,
dataclass_module_2, dataclass_module_2_str,
):
for m in (dataclass_module_1,
dataclass_module_2):
with self.subTest(m=m):
# There's a difference in how the ClassVars are
# interpreted when using string annotations or
# not. See the imported modules for details.
if m.USING_STRINGS:
c = m.CV(10)
else:
c = m.CV()
c = m.CV(10)
self.assertEqual(c.cv0, 20)
@ -2938,14 +2924,9 @@ def test_classvar_module_level_import(self):
# not an instance field.
getattr(c, field_name)
if m.USING_STRINGS:
# iv4 is interpreted as a normal field.
self.assertIn('not_iv4', c.__dict__)
self.assertEqual(c.not_iv4, 4)
else:
# iv4 is interpreted as an InitVar, so it
# won't exist on the instance.
self.assertNotIn('not_iv4', c.__dict__)
# iv4 is interpreted as a normal field.
self.assertIn('not_iv4', c.__dict__)
self.assertEqual(c.not_iv4, 4)
def test_text_annotations(self):
from test import dataclass_textanno

View file

@ -227,28 +227,26 @@ def bug1333982(x=[]):
2 0 SETUP_ANNOTATIONS
2 LOAD_CONST 0 (1)
4 STORE_NAME 0 (x)
6 LOAD_NAME 1 (int)
8 LOAD_NAME 2 (__annotations__)
10 LOAD_CONST 1 ('x')
6 LOAD_CONST 1 ('int')
8 LOAD_NAME 1 (__annotations__)
10 LOAD_CONST 2 ('x')
12 STORE_SUBSCR
3 14 LOAD_NAME 3 (fun)
16 LOAD_CONST 0 (1)
18 CALL_FUNCTION 1
20 LOAD_NAME 2 (__annotations__)
22 LOAD_CONST 2 ('y')
24 STORE_SUBSCR
3 14 LOAD_CONST 3 ('fun(1)')
16 LOAD_NAME 1 (__annotations__)
18 LOAD_CONST 4 ('y')
20 STORE_SUBSCR
4 26 LOAD_CONST 0 (1)
28 LOAD_NAME 4 (lst)
30 LOAD_NAME 3 (fun)
32 LOAD_CONST 3 (0)
34 CALL_FUNCTION 1
36 STORE_SUBSCR
38 LOAD_NAME 1 (int)
40 POP_TOP
42 LOAD_CONST 4 (None)
44 RETURN_VALUE
4 22 LOAD_CONST 0 (1)
24 LOAD_NAME 2 (lst)
26 LOAD_NAME 3 (fun)
28 LOAD_CONST 5 (0)
30 CALL_FUNCTION 1
32 STORE_SUBSCR
34 LOAD_NAME 4 (int)
36 POP_TOP
38 LOAD_CONST 6 (None)
40 RETURN_VALUE
"""
compound_stmt_str = """\

View file

@ -618,7 +618,7 @@ def check_wrapper(self, wrapper, wrapped,
def _default_update(self):
def f(a:'This is a new annotation'):
def f(a: int):
"""This is a test"""
pass
f.attr = 'This is also a test'
@ -635,7 +635,7 @@ def test_default_update(self):
self.assertEqual(wrapper.__name__, 'f')
self.assertEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.attr, 'This is also a test')
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
self.assertEqual(wrapper.__annotations__['a'], 'int')
self.assertNotIn('b', wrapper.__annotations__)
@unittest.skipIf(sys.flags.optimize >= 2,

View file

@ -362,7 +362,7 @@ class C:
z = 2
def __init__(self, x):
self.x: int = x
self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str})
self.assertEqual(C.__annotations__, {'_C__foo': 'int', 's': 'str'})
with self.assertRaises(NameError):
class CBad:
no_such_name_defined.attr: int = 0
@ -378,15 +378,15 @@ def __prepare__(metacls, name, bases, **kwds):
return {'__annotations__': CNS()}
class CC(metaclass=CMeta):
XX: 'ANNOT'
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
self.assertEqual(CC.__annotations__['xx'], repr('ANNOT'))
def test_var_annot_module_semantics(self):
with self.assertRaises(AttributeError):
print(test.__annotations__)
self.assertEqual(ann_module.__annotations__,
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]})
{1: 2, 'x': 'int', 'y': 'str', 'f': 'Tuple[int, int]'})
self.assertEqual(ann_module.M.__annotations__,
{'123': 123, 'o': type})
{'123': 123, 'o': 'type'})
self.assertEqual(ann_module2.__annotations__, {})
def test_var_annot_in_module(self):
@ -405,7 +405,7 @@ def test_var_annot_simple_exec(self):
exec("'docstring'\n"
"__annotations__[1] = 2\n"
"x: int = 5\n", gns, lns)
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
self.assertEqual(lns["__annotations__"], {1: 2, 'x': 'int'})
with self.assertRaises(KeyError):
gns['__annotations__']
@ -413,8 +413,8 @@ def test_var_annot_custom_maps(self):
# tests with custom locals() and __annotations__
ns = {'__annotations__': CNS()}
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
self.assertEqual(ns['__annotations__']['x'], int)
self.assertEqual(ns['__annotations__']['z'], str)
self.assertEqual(ns['__annotations__']['x'], 'int')
self.assertEqual(ns['__annotations__']['z'], 'str')
with self.assertRaises(KeyError):
ns['__annotations__']['w']
nonloc_ns = {}
@ -428,7 +428,7 @@ def __setitem__(self, item, value):
def __getitem__(self, item):
return self._dct[item]
exec('x: int = 1', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], int)
self.assertEqual(nonloc_ns['__annotations__']['x'], 'int')
def test_var_annot_refleak(self):
# complex case: custom locals plus custom __annotations__
@ -445,7 +445,7 @@ def __setitem__(self, item, value):
def __getitem__(self, item):
return self._dct[item]
exec('X: str', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], str)
self.assertEqual(nonloc_ns['__annotations__']['x'], 'str')
def test_var_annot_rhs(self):
ns = {}
@ -625,50 +625,46 @@ def f(*args, **kwargs):
# argument annotation tests
def f(x) -> list: pass
self.assertEqual(f.__annotations__, {'return': list})
self.assertEqual(f.__annotations__, {'return': 'list'})
def f(x: int): pass
self.assertEqual(f.__annotations__, {'x': int})
self.assertEqual(f.__annotations__, {'x': 'int'})
def f(x: int, /): pass
self.assertEqual(f.__annotations__, {'x': int})
self.assertEqual(f.__annotations__, {'x': 'int'})
def f(x: int = 34, /): pass
self.assertEqual(f.__annotations__, {'x': int})
self.assertEqual(f.__annotations__, {'x': 'int'})
def f(*x: str): pass
self.assertEqual(f.__annotations__, {'x': str})
self.assertEqual(f.__annotations__, {'x': 'str'})
def f(**x: float): pass
self.assertEqual(f.__annotations__, {'x': float})
def f(x, y: 1+2): pass
self.assertEqual(f.__annotations__, {'y': 3})
def f(x, y: 1+2, /): pass
self.assertEqual(f.__annotations__, {'y': 3})
self.assertEqual(f.__annotations__, {'x': 'float'})
def f(a, b: 1, c: 2, d): pass
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2})
self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
def f(a, b: 1, /, c: 2, d): pass
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2})
self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass
self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'g': 6})
{'b': '1', 'c': '2', 'e': '3', 'g': '6'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10,
**k: 11) -> 12: pass
self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9,
'k': 11, 'return': 12})
{'b': '1', 'c': '2', 'e': '3', 'g': '6', 'h': '7', 'j': '9',
'k': '11', 'return': '12'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f: int = 5, /, *g: 6, h: 7, i=8, j: 9 = 10,
**k: 11) -> 12: pass
self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'f': int, 'g': 6, 'h': 7, 'j': 9,
'k': 11, 'return': 12})
{'b': '1', 'c': '2', 'e': '3', 'f': 'int', 'g': '6', 'h': '7', 'j': '9',
'k': '11', 'return': '12'})
# Check for issue #20625 -- annotations mangling
class Spam:
def f(self, *, __kw: 1):
pass
class Ham(Spam): pass
self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1})
self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1})
self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': '1'})
self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': '1'})
# Check for SF Bug #1697248 - mixing decorators and a return annotation
def null(x): return x
@null
def f(x) -> list: pass
self.assertEqual(f.__annotations__, {'return': list})
self.assertEqual(f.__annotations__, {'return': 'list'})
# Test expressions as decorators (PEP 614):
@False or null
@ -1116,8 +1112,6 @@ def g(): rest = 4, 5, 6; yield 1, 2, 3, *rest
# Not allowed at class scope
check_syntax_error(self, "class foo:yield 1")
check_syntax_error(self, "class foo:yield from ()")
# Check annotation refleak on SyntaxError
check_syntax_error(self, "def g(a:(yield)): pass")
def test_yield_in_comprehensions(self):
# Check yield in comprehensions

View file

@ -862,7 +862,7 @@ def test_getfullargspec(self):
self.assertFullArgSpecEquals(mod2.annotated, ['arg1'],
ann_e={'arg1' : list},
formatted='(arg1: list)')
formatted="(arg1: list)")
self.assertFullArgSpecEquals(mod2.keyword_only_arg, [],
kwonlyargs_e=['arg'],
formatted='(*, arg)')
@ -2211,8 +2211,8 @@ def test(a, b:'foo') -> 123:
pass
self.assertEqual(self.signature(test),
((('a', ..., ..., "positional_or_keyword"),
('b', ..., 'foo', "positional_or_keyword")),
123))
('b', ..., repr('foo'), "positional_or_keyword")),
'123'))
def test_signature_on_wkwonly(self):
def test(*, a:float, b:str) -> int:
@ -2227,11 +2227,11 @@ def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int):
pass
self.assertEqual(self.signature(test),
((('a', ..., ..., "positional_or_keyword"),
('b', 10, 'foo', "positional_or_keyword"),
('args', ..., 'bar', "var_positional"),
('spam', ..., 'baz', "keyword_only"),
('b', 10, repr('foo'), "positional_or_keyword"),
('args', ..., repr('bar'), "var_positional"),
('spam', ..., repr('baz'), "keyword_only"),
('ham', 123, ..., "keyword_only"),
('kwargs', ..., int, "var_keyword")),
('kwargs', ..., 'int', "var_keyword")),
...))
def test_signature_without_self(self):
@ -2640,12 +2640,12 @@ def test(a, b, c:int) -> 42:
self.assertEqual(self.signature(partial(partial(test, 1))),
((('b', ..., ..., "positional_or_keyword"),
('c', ..., int, "positional_or_keyword")),
42))
('c', ..., 'int', "positional_or_keyword")),
'42'))
self.assertEqual(self.signature(partial(partial(test, 1), 2)),
((('c', ..., int, "positional_or_keyword"),),
42))
((('c', ..., 'int', "positional_or_keyword"),),
'42'))
psig = inspect.signature(partial(partial(test, 1), 2))
@ -2764,12 +2764,12 @@ def test(it, a, *, c) -> 'spam':
((('it', ..., ..., 'positional_or_keyword'),
('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
repr('spam')))
self.assertEqual(self.signature(Spam().ham),
((('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
repr('spam')))
class Spam:
def test(self: 'anno', x):
@ -2778,7 +2778,7 @@ def test(self: 'anno', x):
g = partialmethod(test, 1)
self.assertEqual(self.signature(Spam.g),
((('self', ..., 'anno', 'positional_or_keyword'),),
((('self', ..., repr('anno'), 'positional_or_keyword'),),
...))
def test_signature_on_fake_partialmethod(self):
@ -3116,20 +3116,16 @@ def foo(a={}): pass
with self.assertRaisesRegex(TypeError, 'unhashable type'):
hash(inspect.signature(foo))
def foo(a) -> {}: pass
with self.assertRaisesRegex(TypeError, 'unhashable type'):
hash(inspect.signature(foo))
def test_signature_str(self):
def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
pass
self.assertEqual(str(inspect.signature(foo)),
'(a: int = 1, *, b, c=None, **kwargs) -> 42')
'(a: \'int\' = 1, *, b, c=None, **kwargs) -> \'42\'')
def foo(a:int=1, *args, b, c=None, **kwargs) -> 42:
pass
self.assertEqual(str(inspect.signature(foo)),
'(a: int = 1, *args, b, c=None, **kwargs) -> 42')
'(a: \'int\' = 1, *args, b, c=None, **kwargs) -> \'42\'')
def foo():
pass
@ -3172,8 +3168,8 @@ def test() -> 42:
self.assertIs(sig.return_annotation, None)
sig = sig.replace(return_annotation=sig.empty)
self.assertIs(sig.return_annotation, sig.empty)
sig = sig.replace(return_annotation=42)
self.assertEqual(sig.return_annotation, 42)
sig = sig.replace(return_annotation='42')
self.assertEqual(sig.return_annotation, '42')
self.assertEqual(sig, inspect.signature(test))
def test_signature_on_mangled_parameters(self):
@ -3185,8 +3181,8 @@ class Ham(Spam):
self.assertEqual(self.signature(Spam.foo),
((('self', ..., ..., "positional_or_keyword"),
('_Spam__p1', 2, 1, "positional_or_keyword"),
('_Spam__p2', 3, 2, "keyword_only")),
('_Spam__p1', 2, '1', "positional_or_keyword"),
('_Spam__p2', 3, '2', "keyword_only")),
...))
self.assertEqual(self.signature(Spam.foo),

View file

@ -39,7 +39,7 @@ class C: pass
def test_use_existing_annotations(self):
ns = {'__annotations__': {1: 2}}
exec('x: int', ns)
self.assertEqual(ns['__annotations__'], {'x': int, 1: 2})
self.assertEqual(ns['__annotations__'], {'x': 'int', 1: 2})
def test_do_not_recreate_annotations(self):
# Don't rely on the existence of the '__annotations__' global.

View file

@ -302,14 +302,14 @@ def inner_has_pos_only():
def f(x: int, /): ...
return f
assert inner_has_pos_only().__annotations__ == {'x': int}
assert inner_has_pos_only().__annotations__ == {'x': 'int'}
class Something:
def method(self):
def f(x: int, /): ...
return f
assert Something().method().__annotations__ == {'x': int}
assert Something().method().__annotations__ == {'x': 'int'}
def multiple_levels():
def inner_has_pos_only():
@ -317,7 +317,7 @@ def f(x: int, /): ...
return f
return inner_has_pos_only()
assert multiple_levels().__annotations__ == {'x': int}
assert multiple_levels().__annotations__ == {'x': 'int'}
def test_same_keyword_as_positional_with_kwargs(self):
def f(something,/,**kwargs):
@ -429,17 +429,6 @@ def method(self, /):
self.assertEqual(C().method(), sentinel)
def test_annotations_constant_fold(self):
def g():
def f(x: not (int is int), /): ...
# without constant folding we end up with
# COMPARE_OP(is), IS_OP (0)
# with constant folding we should expect a IS_OP (1)
codes = [(i.opname, i.argval) for i in dis.get_instructions(g)]
self.assertNotIn(('UNARY_NOT', None), codes)
self.assertIn(('IS_OP', 1), codes)
if __name__ == "__main__":
unittest.main()

View file

@ -81,7 +81,7 @@ class B(builtins.object)
|\x20\x20
| NO_MEANING = 'eggs'
|\x20\x20
| __annotations__ = {'NO_MEANING': <class 'str'>}
| __annotations__ = {'NO_MEANING': 'str'}
\x20\x20\x20\x20
class C(builtins.object)
| Methods defined here:
@ -194,7 +194,7 @@ class C(builtins.object)
Data and other attributes defined here:<br>
<dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': &lt;class 'str'&gt;}</dl>
<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': 'str'}</dl>
</td></tr></table> <p>
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">

View file

@ -752,14 +752,6 @@
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> def f(*args:(lambda __debug__:0)): pass
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> def f(**kwargs:(lambda __debug__:0)): pass
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> with (lambda *:0): pass
Traceback (most recent call last):
SyntaxError: named arguments must follow bare *

View file

@ -671,8 +671,8 @@ def test_or_type_operator_with_forward(self):
ForwardBefore = 'Forward' | T
def forward_after(x: ForwardAfter[int]) -> None: ...
def forward_before(x: ForwardBefore[int]) -> None: ...
assert typing.get_args(typing.get_type_hints(forward_after)['x']) == (int, Forward)
assert typing.get_args(typing.get_type_hints(forward_before)['x']) == (int, Forward)
assert typing.get_args(typing.get_type_hints(forward_after, localns=locals())['x']) == (int, Forward)
assert typing.get_args(typing.get_type_hints(forward_before, localns=locals())['x']) == (int, Forward)
def test_or_type_operator_with_Protocol(self):
class Proto(typing.Protocol):

View file

@ -349,7 +349,7 @@ def test_empty(self):
def test_no_eval_union(self):
u = Union[int, str]
def f(x: u): ...
self.assertIs(get_type_hints(f)['x'], u)
self.assertIs(get_type_hints(f, globals(), locals())['x'], u)
def test_function_repr_union(self):
def fun() -> int: ...
@ -2849,11 +2849,11 @@ def test_get_type_hints_classes(self):
self.assertEqual(gth(HasForeignBaseClass),
{'some_xrepr': XRepr, 'other_a': mod_generics_cache.A,
'some_b': mod_generics_cache.B})
self.assertEqual(gth(XRepr.__new__),
self.assertEqual(gth(XRepr),
{'x': int, 'y': int})
self.assertEqual(gth(mod_generics_cache.B),
{'my_inner_a1': mod_generics_cache.B.A,
'my_inner_a2': mod_generics_cache.B.A,
'my_inner_a2': mod_generics_cache.A,
'my_outer_a': mod_generics_cache.A})
def test_respect_no_type_check(self):
@ -3641,7 +3641,7 @@ def test_annotation_usage(self):
self.assertEqual(tim.cool, 9000)
self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee.__annotations__,
self.assertEqual(gth(CoolEmployee),
collections.OrderedDict(name=str, cool=int))
def test_annotation_usage_with_default(self):
@ -3655,7 +3655,7 @@ def test_annotation_usage_with_default(self):
self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault')
self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool'))
self.assertEqual(CoolEmployeeWithDefault.__annotations__,
self.assertEqual(gth(CoolEmployeeWithDefault),
dict(name=str, cool=int))
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
@ -3823,7 +3823,7 @@ def test_typeddict_errors(self):
def test_py36_class_syntax_usage(self):
self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D')
self.assertEqual(LabelPoint2D.__module__, __name__)
self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str})
self.assertEqual(gth(LabelPoint2D), {'x': int, 'y': int, 'label': str})
self.assertEqual(LabelPoint2D.__bases__, (dict,))
self.assertEqual(LabelPoint2D.__total__, True)
self.assertNotIsSubclass(LabelPoint2D, typing.Sequence)
@ -3882,11 +3882,11 @@ class Cat(Animal):
assert BaseAnimal.__required_keys__ == frozenset(['name'])
assert BaseAnimal.__optional_keys__ == frozenset([])
assert BaseAnimal.__annotations__ == {'name': str}
assert gth(BaseAnimal) == {'name': str}
assert Animal.__required_keys__ == frozenset(['name'])
assert Animal.__optional_keys__ == frozenset(['tail', 'voice'])
assert Animal.__annotations__ == {
assert gth(Animal) == {
'name': str,
'tail': bool,
'voice': str,
@ -3894,7 +3894,7 @@ class Cat(Animal):
assert Cat.__required_keys__ == frozenset(['name', 'fur_color'])
assert Cat.__optional_keys__ == frozenset(['tail', 'voice'])
assert Cat.__annotations__ == {
assert gth(Cat) == {
'fur_color': str,
'name': str,
'tail': bool,
@ -3915,7 +3915,7 @@ def test_io(self):
def stuff(a: IO) -> AnyStr:
return a.readline()
a = stuff.__annotations__['a']
a = gth(stuff)['a']
self.assertEqual(a.__parameters__, (AnyStr,))
def test_textio(self):
@ -3923,7 +3923,7 @@ def test_textio(self):
def stuff(a: TextIO) -> str:
return a.readline()
a = stuff.__annotations__['a']
a = gth(stuff)['a']
self.assertEqual(a.__parameters__, ())
def test_binaryio(self):
@ -3931,7 +3931,7 @@ def test_binaryio(self):
def stuff(a: BinaryIO) -> bytes:
return a.readline()
a = stuff.__annotations__['a']
a = gth(stuff)['a']
self.assertEqual(a.__parameters__, ())
def test_io_submodule(self):

View file

@ -18,6 +18,7 @@
"""
from abc import abstractmethod, ABCMeta
import ast
import collections
import collections.abc
import contextlib
@ -469,6 +470,13 @@ class ForwardRef(_Final, _root=True):
def __init__(self, arg, is_argument=True):
if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
# Double-stringified forward references is a result of activating
# the 'annotations' future by default. This way, we eliminate them in
# the runtime.
if arg.startswith(("'", '\"')) and arg.endswith(("'", '"')):
arg = arg[1:-1]
try:
code = compile(arg, '<string>', 'eval')
except SyntaxError:

View file

@ -0,0 +1,3 @@
Enable ``from __future__ import annotations`` (:pep:`563`) by default.
The values found in :attr:`__annotations__` dicts are now strings, e.g.
``{"x": "int"}`` instead of ``{"x": int}``.

View file

@ -392,7 +392,6 @@ static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state
static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
#define CALL(FUNC, TYPE, ARG) \
@ -595,25 +594,11 @@ astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState
static int
astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
CALL_SEQ(astfold_arg, arg, node_->posonlyargs);
CALL_SEQ(astfold_arg, arg, node_->args);
CALL_OPT(astfold_arg, arg_ty, node_->vararg);
CALL_SEQ(astfold_arg, arg, node_->kwonlyargs);
CALL_SEQ(astfold_expr, expr, node_->kw_defaults);
CALL_OPT(astfold_arg, arg_ty, node_->kwarg);
CALL_SEQ(astfold_expr, expr, node_->defaults);
return 1;
}
static int
astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->annotation);
}
return 1;
}
static int
astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
@ -622,17 +607,11 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args);
CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body);
CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns);
}
break;
case AsyncFunctionDef_kind:
CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args);
CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body);
CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns);
}
break;
case ClassDef_kind:
CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases);
@ -656,9 +635,6 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
break;
case AnnAssign_kind:
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.target);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.annotation);
}
CALL_OPT(astfold_expr, expr_ty, node_->v.AnnAssign.value);
break;
case For_kind:

View file

@ -2026,12 +2026,7 @@ compiler_visit_argannotation(struct compiler *c, identifier id,
{
if (annotation) {
PyObject *mangled;
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
VISIT(c, annexpr, annotation)
}
else {
VISIT(c, expr, annotation);
}
VISIT(c, annexpr, annotation);
mangled = _Py_Mangle(c->u->u_private, id);
if (!mangled)
return 0;
@ -5261,12 +5256,7 @@ compiler_annassign(struct compiler *c, stmt_ty s)
if (s->v.AnnAssign.simple &&
(c->u->u_scope_type == COMPILER_SCOPE_MODULE ||
c->u->u_scope_type == COMPILER_SCOPE_CLASS)) {
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) {
VISIT(c, annexpr, s->v.AnnAssign.annotation)
}
else {
VISIT(c, expr, s->v.AnnAssign.annotation);
}
VISIT(c, annexpr, s->v.AnnAssign.annotation);
ADDOP_NAME(c, LOAD_NAME, __annotations__, names);
mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id);
ADDOP_LOAD_CONST_NEW(c, mangled);

View file

@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
continue;
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
ff->ff_features |= CO_FUTURE_ANNOTATIONS;
continue;
} else if (strcmp(feature, "braces") == 0) {
PyErr_SetString(PyExc_SyntaxError,
"not a chance");