Commit 3dc2b9df authored by da-woods's avatar da-woods Committed by Stefan Behnel

Implement PEP-563, annotations as strings (GH-3285)

Annotations are now dealt with according to PEP-563 - they are
saved as strings, rather than evaluated as Python objects.
They can/are still be used by Cython for typing.
Previous behaviour for evaluating them as Python objects was
convoluted and has been removed entirely, which hopefully doesn't
break too much.
parent acf3abf8
...@@ -5,16 +5,35 @@ from .StringEncoding import EncodedString ...@@ -5,16 +5,35 @@ from .StringEncoding import EncodedString
from . import Options from . import Options
from . import PyrexTypes, ExprNodes from . import PyrexTypes, ExprNodes
from ..CodeWriter import ExpressionWriter from ..CodeWriter import ExpressionWriter
from .Errors import warning
class AnnotationWriter(ExpressionWriter): class AnnotationWriter(ExpressionWriter):
def __init__(self, description=None):
"""description is optional. If specified it is used in
warning messages for the nodes that don't convert to string properly.
If not specified then no messages are generated.
"""
ExpressionWriter.__init__(self)
self.description = description
def visit_Node(self, node): def visit_Node(self, node):
self.put(u"<???>") self.put(u"<???>")
if self.description:
warning(node.pos,
"Failed to convert code to string representation in {0}".format(
self.description), level=1)
def visit_LambdaNode(self, node): def visit_LambdaNode(self, node):
# XXX Should we do better? # XXX Should we do better?
self.put("<lambda>") self.put("<lambda>")
if self.description:
warning(node.pos,
"Failed to convert lambda to string representation in {0}".format(
self.description), level=1)
def visit_AnnotationNode(self, node):
self.put(node.string.unicode_value)
class EmbedSignature(CythonTransform): class EmbedSignature(CythonTransform):
......
...@@ -28,7 +28,7 @@ from .Code import UtilityCode, TempitaUtilityCode ...@@ -28,7 +28,7 @@ from .Code import UtilityCode, TempitaUtilityCode
from . import StringEncoding from . import StringEncoding
from . import Naming from . import Naming
from . import Nodes from . import Nodes
from .Nodes import Node, utility_code_for_imports, analyse_type_annotation from .Nodes import Node, utility_code_for_imports
from . import PyrexTypes from . import PyrexTypes
from .PyrexTypes import py_object_type, c_long_type, typecast, error_type, \ from .PyrexTypes import py_object_type, c_long_type, typecast, error_type, \
unspecified_type unspecified_type
...@@ -1928,15 +1928,15 @@ class NameNode(AtomicExprNode): ...@@ -1928,15 +1928,15 @@ class NameNode(AtomicExprNode):
return return
annotation = self.annotation annotation = self.annotation
if annotation.is_string_literal: if annotation.expr.is_string_literal:
# name: "description" => not a type, but still a declared variable or attribute # name: "description" => not a type, but still a declared variable or attribute
atype = None atype = None
else: else:
_, atype = analyse_type_annotation(annotation, env) _, atype = annotation.analyse_type_annotation(env)
if atype is None: if atype is None:
atype = unspecified_type if as_target and env.directives['infer_types'] != False else py_object_type atype = unspecified_type if as_target and env.directives['infer_types'] != False else py_object_type
self.entry = env.declare_var(name, atype, self.pos, is_cdef=not as_target) self.entry = env.declare_var(name, atype, self.pos, is_cdef=not as_target)
self.entry.annotation = annotation self.entry.annotation = annotation.expr
def analyse_as_module(self, env): def analyse_as_module(self, env):
# Try to interpret this as a reference to a cimported module. # Try to interpret this as a reference to a cimported module.
...@@ -9221,19 +9221,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9221,19 +9221,19 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
else: else:
default_args.append(arg) default_args.append(arg)
if arg.annotation: if arg.annotation:
arg.annotation = self.analyse_annotation(env, arg.annotation) arg.annotation = arg.annotation.analyse_types(env)
annotations.append((arg.pos, arg.name, arg.annotation)) annotations.append((arg.pos, arg.name, arg.annotation.string))
for arg in (self.def_node.star_arg, self.def_node.starstar_arg): for arg in (self.def_node.star_arg, self.def_node.starstar_arg):
if arg and arg.annotation: if arg and arg.annotation:
arg.annotation = self.analyse_annotation(env, arg.annotation) arg.annotation = arg.annotation.analyse_types(env)
annotations.append((arg.pos, arg.name, arg.annotation)) annotations.append((arg.pos, arg.name, arg.annotation.string))
annotation = self.def_node.return_type_annotation annotation = self.def_node.return_type_annotation
if annotation: if annotation:
annotation = self.analyse_annotation(env, annotation) self.def_node.return_type_annotation = annotation.analyse_types(env)
self.def_node.return_type_annotation = annotation annotations.append((annotation.pos, StringEncoding.EncodedString("return"),
annotations.append((annotation.pos, StringEncoding.EncodedString("return"), annotation)) annotation.string))
if nonliteral_objects or nonliteral_other: if nonliteral_objects or nonliteral_other:
module_scope = env.global_scope() module_scope = env.global_scope()
...@@ -9310,20 +9310,6 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9310,20 +9310,6 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
for pos, name, value in annotations]) for pos, name, value in annotations])
self.annotations_dict = annotations_dict.analyse_types(env) self.annotations_dict = annotations_dict.analyse_types(env)
def analyse_annotation(self, env, annotation):
if annotation is None:
return None
atype = annotation.analyse_as_type(env)
if atype is not None:
# Keep parsed types as strings as they might not be Python representable.
annotation = UnicodeNode(
annotation.pos,
value=StringEncoding.EncodedString(atype.declaration_code('', for_display=True)))
annotation = annotation.analyse_types(env)
if not annotation.type.is_pyobject:
annotation = annotation.coerce_to_pyobject(env)
return annotation
def may_be_none(self): def may_be_none(self):
return False return False
...@@ -12015,6 +12001,9 @@ class BoolBinopResultNode(ExprNode): ...@@ -12015,6 +12001,9 @@ class BoolBinopResultNode(ExprNode):
code.putln("}") code.putln("}")
self.arg.free_temps(code) self.arg.free_temps(code)
def analyse_types(self, env):
return self
class CondExprNode(ExprNode): class CondExprNode(ExprNode):
# Short-circuiting conditional expression. # Short-circuiting conditional expression.
...@@ -13330,6 +13319,9 @@ class CoerceToBooleanNode(CoercionNode): ...@@ -13330,6 +13319,9 @@ class CoerceToBooleanNode(CoercionNode):
self.arg.py_result(), self.arg.py_result(),
code.error_goto_if_neg(self.result(), self.pos))) code.error_goto_if_neg(self.result(), self.pos)))
def analyse_types(self, env):
return self
class CoerceToComplexNode(CoercionNode): class CoerceToComplexNode(CoercionNode):
...@@ -13355,6 +13347,9 @@ class CoerceToComplexNode(CoercionNode): ...@@ -13355,6 +13347,9 @@ class CoerceToComplexNode(CoercionNode):
def generate_result_code(self, code): def generate_result_code(self, code):
pass pass
def analyse_types(self, env):
return self
class CoerceToTempNode(CoercionNode): class CoerceToTempNode(CoercionNode):
# This node is used to force the result of another node # This node is used to force the result of another node
# to be stored in a temporary. It is only used if the # to be stored in a temporary. It is only used if the
...@@ -13560,7 +13555,83 @@ class DocstringRefNode(ExprNode): ...@@ -13560,7 +13555,83 @@ class DocstringRefNode(ExprNode):
code.error_goto_if_null(self.result(), self.pos))) code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.result()) code.put_gotref(self.result())
class AnnotationNode(ExprNode):
# Deals with the two possible uses of an annotation.
# 1. The post PEP-563 use where an annotation is stored
# as a string
# 2. The Cython use where the annotation can indicate an
# object type
#
# doesn't handle the pre PEP-563 version where the
# annotation is evaluated into a Python Object
subexprs = []
def __init__(self, pos, expr, string=None):
"""string is expected to already be a StringNode or None"""
ExprNode.__init__(self, pos)
if string is None:
# import doesn't work at top of file?
from .AutoDocTransforms import AnnotationWriter
string = StringEncoding.EncodedString(
AnnotationWriter(description="annotation").write(expr))
string = StringNode(pos, unicode_value=string, value=string.as_utf8_string())
self.string = string
self.expr = expr
def analyse_types(self, env):
return self # nothing needs doing
def analyse_as_type(self, env):
# for compatibility when used as a return_type_node, have this interface too
return self.analyse_type_annotation(env)[1]
def analyse_type_annotation(self, env, assigned_value=None):
annotation = self.expr
base_type = None
is_ambiguous = False
explicit_pytype = explicit_ctype = False
if annotation.is_dict_literal:
warning(annotation.pos,
"Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.")
for name, value in annotation.key_value_pairs:
if not name.is_string_literal:
continue
if name.value in ('type', b'type'):
explicit_pytype = True
if not explicit_ctype:
annotation = value
elif name.value in ('ctype', b'ctype'):
explicit_ctype = True
annotation = value
if explicit_pytype and explicit_ctype:
warning(annotation.pos, "Duplicate type declarations found in signature annotation")
arg_type = annotation.analyse_as_type(env)
if annotation.is_name and not annotation.cython_attribute and annotation.name in ('int', 'long', 'float'):
# Map builtin numeric Python types to C types in safe cases.
if assigned_value is not None and arg_type is not None and not arg_type.is_pyobject:
assigned_type = assigned_value.infer_type(env)
if assigned_type and assigned_type.is_pyobject:
# C type seems unsafe, e.g. due to 'None' default value => ignore annotation type
is_ambiguous = True
arg_type = None
# ignore 'int' and require 'cython.int' to avoid unsafe integer declarations
if arg_type in (PyrexTypes.c_long_type, PyrexTypes.c_int_type, PyrexTypes.c_float_type):
arg_type = PyrexTypes.c_double_type if annotation.name == 'float' else py_object_type
elif arg_type is not None and annotation.is_string_literal:
warning(annotation.pos,
"Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.",
level=1)
if arg_type is not None:
if explicit_pytype and not explicit_ctype and not arg_type.is_pyobject:
warning(annotation.pos,
"Python type declaration in signature annotation does not refer to a Python type")
base_type = Nodes.CAnalysedBaseTypeNode(
annotation.pos, type=arg_type, is_arg=True)
elif is_ambiguous:
warning(annotation.pos, "Ambiguous types in annotation, ignoring")
else:
warning(annotation.pos, "Unknown type declaration in annotation, ignoring")
return base_type, arg_type
#------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------
# #
......
...@@ -11,5 +11,6 @@ absolute_import = _get_feature("absolute_import") ...@@ -11,5 +11,6 @@ absolute_import = _get_feature("absolute_import")
nested_scopes = _get_feature("nested_scopes") # dummy nested_scopes = _get_feature("nested_scopes") # dummy
generators = _get_feature("generators") # dummy generators = _get_feature("generators") # dummy
generator_stop = _get_feature("generator_stop") generator_stop = _get_feature("generator_stop")
annotations = _get_feature("annotations")
del _get_feature del _get_feature
...@@ -68,54 +68,6 @@ def embed_position(pos, docstring): ...@@ -68,54 +68,6 @@ def embed_position(pos, docstring):
doc.encoding = encoding doc.encoding = encoding
return doc return doc
def analyse_type_annotation(annotation, env, assigned_value=None):
base_type = None
is_ambiguous = False
explicit_pytype = explicit_ctype = False
if annotation.is_dict_literal:
warning(annotation.pos,
"Dicts should no longer be used as type annotations. Use 'cython.int' etc. directly.")
for name, value in annotation.key_value_pairs:
if not name.is_string_literal:
continue
if name.value in ('type', b'type'):
explicit_pytype = True
if not explicit_ctype:
annotation = value
elif name.value in ('ctype', b'ctype'):
explicit_ctype = True
annotation = value
if explicit_pytype and explicit_ctype:
warning(annotation.pos, "Duplicate type declarations found in signature annotation")
arg_type = annotation.analyse_as_type(env)
if annotation.is_name and not annotation.cython_attribute and annotation.name in ('int', 'long', 'float'):
# Map builtin numeric Python types to C types in safe cases.
if assigned_value is not None and arg_type is not None and not arg_type.is_pyobject:
assigned_type = assigned_value.infer_type(env)
if assigned_type and assigned_type.is_pyobject:
# C type seems unsafe, e.g. due to 'None' default value => ignore annotation type
is_ambiguous = True
arg_type = None
# ignore 'int' and require 'cython.int' to avoid unsafe integer declarations
if arg_type in (PyrexTypes.c_long_type, PyrexTypes.c_int_type, PyrexTypes.c_float_type):
arg_type = PyrexTypes.c_double_type if annotation.name == 'float' else py_object_type
elif arg_type is not None and annotation.is_string_literal:
warning(annotation.pos,
"Strings should no longer be used for type declarations. Use 'cython.int' etc. directly.")
if arg_type is not None:
if explicit_pytype and not explicit_ctype and not arg_type.is_pyobject:
warning(annotation.pos,
"Python type declaration in signature annotation does not refer to a Python type")
base_type = CAnalysedBaseTypeNode(
annotation.pos, type=arg_type, is_arg=True)
elif is_ambiguous:
warning(annotation.pos, "Ambiguous types in annotation, ignoring")
else:
warning(annotation.pos, "Unknown type declaration in annotation, ignoring")
return base_type, arg_type
def write_func_call(func, codewriter_class): def write_func_call(func, codewriter_class):
def f(*args, **kwds): def f(*args, **kwds):
if len(args) > 1 and isinstance(args[1], codewriter_class): if len(args) > 1 and isinstance(args[1], codewriter_class):
...@@ -937,7 +889,7 @@ class CArgDeclNode(Node): ...@@ -937,7 +889,7 @@ class CArgDeclNode(Node):
annotation = self.annotation annotation = self.annotation
if not annotation: if not annotation:
return None return None
base_type, arg_type = analyse_type_annotation(annotation, env, assigned_value=self.default) base_type, arg_type = annotation.analyse_type_annotation(env, assigned_value=self.default)
if base_type is not None: if base_type is not None:
self.base_type = base_type self.base_type = base_type
return arg_type return arg_type
...@@ -1685,6 +1637,7 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1685,6 +1637,7 @@ class FuncDefNode(StatNode, BlockNode):
starstar_arg = None starstar_arg = None
is_cyfunction = False is_cyfunction = False
code_object = None code_object = None
return_type_annotation = None
def analyse_default_values(self, env): def analyse_default_values(self, env):
default_seen = 0 default_seen = 0
...@@ -1702,18 +1655,12 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -1702,18 +1655,12 @@ class FuncDefNode(StatNode, BlockNode):
elif default_seen: elif default_seen:
error(arg.pos, "Non-default argument following default argument") error(arg.pos, "Non-default argument following default argument")
def analyse_annotation(self, env, annotation):
# Annotations can not only contain valid Python expressions but arbitrary type references.
if annotation is None:
return None
if not env.directives['annotation_typing'] or annotation.analyse_as_type(env) is None:
annotation = annotation.analyse_types(env)
return annotation
def analyse_annotations(self, env): def analyse_annotations(self, env):
for arg in self.args: for arg in self.args:
if arg.annotation: if arg.annotation:
arg.annotation = self.analyse_annotation(env, arg.annotation) arg.annotation = arg.annotation.analyse_types(env)
if self.return_type_annotation:
self.return_type_annotation = self.return_type_annotation.analyse_types(env)
def align_argument_type(self, env, arg): def align_argument_type(self, env, arg):
# @cython.locals() # @cython.locals()
...@@ -2208,7 +2155,7 @@ class FuncDefNode(StatNode, BlockNode): ...@@ -2208,7 +2155,7 @@ class FuncDefNode(StatNode, BlockNode):
error(arg.pos, "Argument type '%s' is incomplete" % arg.type) error(arg.pos, "Argument type '%s' is incomplete" % arg.type)
entry = env.declare_arg(arg.name, arg.type, arg.pos) entry = env.declare_arg(arg.name, arg.type, arg.pos)
if arg.annotation: if arg.annotation:
entry.annotation = arg.annotation entry.annotation = arg.annotation.expr
return entry return entry
def generate_arg_type_test(self, arg, code): def generate_arg_type_test(self, arg, code):
...@@ -2947,7 +2894,7 @@ class DefNode(FuncDefNode): ...@@ -2947,7 +2894,7 @@ class DefNode(FuncDefNode):
# if a signature annotation provides a more specific return object type, use it # if a signature annotation provides a more specific return object type, use it
if self.return_type is py_object_type and self.return_type_annotation: if self.return_type is py_object_type and self.return_type_annotation:
if env.directives['annotation_typing'] and not self.entry.is_special: if env.directives['annotation_typing'] and not self.entry.is_special:
_, return_type = analyse_type_annotation(self.return_type_annotation, env) _, return_type = self.return_type_annotation.analyse_type_annotation(env)
if return_type and return_type.is_pyobject: if return_type and return_type.is_pyobject:
self.return_type = return_type self.return_type = return_type
...@@ -3208,8 +3155,6 @@ class DefNode(FuncDefNode): ...@@ -3208,8 +3155,6 @@ class DefNode(FuncDefNode):
self.local_scope.directives = env.directives self.local_scope.directives = env.directives
self.analyse_default_values(env) self.analyse_default_values(env)
self.analyse_annotations(env) self.analyse_annotations(env)
if self.return_type_annotation:
self.return_type_annotation = self.analyse_annotation(env, self.return_type_annotation)
if not self.needs_assignment_synthesis(env) and self.decorators: if not self.needs_assignment_synthesis(env) and self.decorators:
for decorator in self.decorators[::-1]: for decorator in self.decorators[::-1]:
......
...@@ -196,3 +196,4 @@ cdef dict p_compiler_directive_comments(PyrexScanner s) ...@@ -196,3 +196,4 @@ cdef dict p_compiler_directive_comments(PyrexScanner s)
cdef p_template_definition(PyrexScanner s) cdef p_template_definition(PyrexScanner s)
cdef p_cpp_class_definition(PyrexScanner s, pos, ctx) cdef p_cpp_class_definition(PyrexScanner s, pos, ctx)
cdef p_cpp_class_attribute(PyrexScanner s, ctx) cdef p_cpp_class_attribute(PyrexScanner s, ctx)
cdef p_annotation(PyrexScanner s)
...@@ -1493,7 +1493,7 @@ def p_expression_or_assignment(s): ...@@ -1493,7 +1493,7 @@ def p_expression_or_assignment(s):
expr = p_testlist_star_expr(s) expr = p_testlist_star_expr(s)
if s.sy == ':' and (expr.is_name or expr.is_subscript or expr.is_attribute): if s.sy == ':' and (expr.is_name or expr.is_subscript or expr.is_attribute):
s.next() s.next()
expr.annotation = p_test(s) expr.annotation = p_annotation(s)
if s.sy == '=' and expr.is_starred: if s.sy == '=' and expr.is_starred:
# This is a common enough error to make when learning Cython to let # This is a common enough error to make when learning Cython to let
# it fail as early as possible and give a very clear error message. # it fail as early as possible and give a very clear error message.
...@@ -3019,7 +3019,7 @@ def p_c_arg_decl(s, ctx, in_pyfunc, cmethod_flag = 0, nonempty = 0, ...@@ -3019,7 +3019,7 @@ def p_c_arg_decl(s, ctx, in_pyfunc, cmethod_flag = 0, nonempty = 0,
not_none = kind == 'not' not_none = kind == 'not'
if annotated and s.sy == ':': if annotated and s.sy == ':':
s.next() s.next()
annotation = p_test(s) annotation = p_annotation(s)
if s.sy == '=': if s.sy == '=':
s.next() s.next()
if 'pxd' in ctx.level: if 'pxd' in ctx.level:
...@@ -3406,7 +3406,7 @@ def p_def_statement(s, decorators=None, is_async_def=False): ...@@ -3406,7 +3406,7 @@ def p_def_statement(s, decorators=None, is_async_def=False):
return_type_annotation = None return_type_annotation = None
if s.sy == '->': if s.sy == '->':
s.next() s.next()
return_type_annotation = p_test(s) return_type_annotation = p_annotation(s)
_reject_cdef_modifier_in_py(s, s.systring) _reject_cdef_modifier_in_py(s, s.systring)
doc, body = p_suite_with_docstring(s, Ctx(level='function')) doc, body = p_suite_with_docstring(s, Ctx(level='function'))
...@@ -3461,7 +3461,7 @@ def p_py_arg_decl(s, annotated = 1): ...@@ -3461,7 +3461,7 @@ def p_py_arg_decl(s, annotated = 1):
annotation = None annotation = None
if annotated and s.sy == ':': if annotated and s.sy == ':':
s.next() s.next()
annotation = p_test(s) annotation = p_annotation(s)
return Nodes.PyArgDeclNode(pos, name = name, annotation = annotation) return Nodes.PyArgDeclNode(pos, name = name, annotation = annotation)
...@@ -3869,3 +3869,14 @@ def print_parse_tree(f, node, level, key = None): ...@@ -3869,3 +3869,14 @@ def print_parse_tree(f, node, level, key = None):
f.write("%s]\n" % ind) f.write("%s]\n" % ind)
return return
f.write("%s%s\n" % (ind, node)) f.write("%s%s\n" % (ind, node))
def p_annotation(s):
"""An annotation just has the "test" syntax, but also stores the string it came from
Note that the string is *allowed* to be changed/processed (although isn't here)
so may not exactly match the string generated by Python, and if it doesn't
then it is not a bug.
"""
pos = s.position()
expr = p_test(s)
return ExprNodes.AnnotationNode(pos, expr=expr)
...@@ -656,7 +656,8 @@ class MemoryViewSliceType(PyrexType): ...@@ -656,7 +656,8 @@ class MemoryViewSliceType(PyrexType):
assert not pyrex assert not pyrex
assert not dll_linkage assert not dll_linkage
from . import MemoryView from . import MemoryView
base_code = str(self) if for_display else MemoryView.memviewslice_cname base_code = StringEncoding.EncodedString(
(str(self)) if for_display else MemoryView.memviewslice_cname)
return self.base_declaration_code( return self.base_declaration_code(
base_code, base_code,
entity_code) entity_code)
...@@ -1767,6 +1768,7 @@ class CNumericType(CType): ...@@ -1767,6 +1768,7 @@ class CNumericType(CType):
base_code = type_name.replace('PY_LONG_LONG', 'long long') base_code = type_name.replace('PY_LONG_LONG', 'long long')
else: else:
base_code = public_decl(type_name, dll_linkage) base_code = public_decl(type_name, dll_linkage)
base_code = StringEncoding.EncodedString(base_code)
return self.base_declaration_code(base_code, entity_code) return self.base_declaration_code(base_code, entity_code)
def attributes_known(self): def attributes_known(self):
......
# mode: error
# tag: pep492, async
async def foo():
def foo(a:await list()):
pass
_ERRORS = """
5:14: 'await' not supported here
5:14: 'await' not supported here
"""
...@@ -271,13 +271,13 @@ def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret": ...@@ -271,13 +271,13 @@ def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
>>> isinstance(test_annotations.__annotations__, dict) >>> isinstance(test_annotations.__annotations__, dict)
True True
>>> sorted(test_annotations.__annotations__.items()) >>> sorted(test_annotations.__annotations__.items())
[('a', 'test'), ('b', 'other'), ('c', 123), ('return', 'ret')] [('a', "'test'"), ('b', "'other'"), ('c', '123'), ('return', "'ret'")]
>>> def func_b(): return 42 >>> def func_b(): return 42
>>> def func_c(): return 99 >>> def func_c(): return 99
>>> inner = test_annotations(1, func_b, func_c) >>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items()) >>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)] [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
>>> inner.__annotations__ = {234: 567} >>> inner.__annotations__ = {234: 567}
>>> inner.__annotations__ >>> inner.__annotations__
...@@ -293,14 +293,14 @@ def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret": ...@@ -293,14 +293,14 @@ def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
>>> inner = test_annotations(1, func_b, func_c) >>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items()) >>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)] [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
>>> inner.__annotations__['abc'] = 66 >>> inner.__annotations__['abc'] = 66
>>> sorted(inner.__annotations__.items()) >>> sorted(inner.__annotations__.items())
[('abc', 66), ('return', 99), ('x', 'banana'), ('y', 42)] [('abc', 66), ('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
>>> inner = test_annotations(1, func_b, func_c) >>> inner = test_annotations(1, func_b, func_c)
>>> sorted(inner.__annotations__.items()) >>> sorted(inner.__annotations__.items())
[('return', 99), ('x', 'banana'), ('y', 42)] [('return', 'c()'), ('x', "'banana'"), ('y', 'b()')]
""" """
def inner(x: "banana", y: b()) -> c(): def inner(x: "banana", y: b()) -> c():
return x,y return x,y
......
...@@ -578,15 +578,15 @@ def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwar ...@@ -578,15 +578,15 @@ def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwar
>>> len(annotation_syntax.__annotations__) >>> len(annotation_syntax.__annotations__)
5 5
>>> print(annotation_syntax.__annotations__['a']) >>> print(annotation_syntax.__annotations__['a'])
test new test u'test new test'
>>> print(annotation_syntax.__annotations__['b']) >>> print(annotation_syntax.__annotations__['b'])
other u'other'
>>> print(annotation_syntax.__annotations__['args']) >>> print(annotation_syntax.__annotations__['args'])
ARGS u'ARGS'
>>> print(annotation_syntax.__annotations__['kwargs']) >>> print(annotation_syntax.__annotations__['kwargs'])
KWARGS u'KWARGS'
>>> print(annotation_syntax.__annotations__['return']) >>> print(annotation_syntax.__annotations__['return'])
ret u'ret'
""" """
result : int = a + b result : int = a + b
...@@ -610,9 +610,9 @@ async def async_def_annotations(x: 'int') -> 'float': ...@@ -610,9 +610,9 @@ async def async_def_annotations(x: 'int') -> 'float':
>>> ret, arg = sorted(async_def_annotations.__annotations__.items()) >>> ret, arg = sorted(async_def_annotations.__annotations__.items())
>>> print(ret[0]); print(ret[1]) >>> print(ret[0]); print(ret[1])
return return
float u'float'
>>> print(arg[0]); print(arg[1]) >>> print(arg[0]); print(arg[1])
x x
int u'int'
""" """
return float(x) return float(x)
...@@ -140,6 +140,12 @@ def str_type_is_str(): ...@@ -140,6 +140,12 @@ def str_type_is_str():
cdef str s = 'abc' cdef str s = 'abc'
return str, s return str, s
def strip_wrapped_string(s):
# PEP 563 translates an annotation of "test new test" to '"test new test"'
# but choice of string delimiters is a bit arbitrary
# this function handles that
assert s[0] == s[-1] # delimiters on either end are the same
return s[1:-1] # strip them
def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwargs: "KWARGS") -> "ret": def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwargs: "KWARGS") -> "ret":
""" """
...@@ -150,15 +156,15 @@ def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwar ...@@ -150,15 +156,15 @@ def annotation_syntax(a: "test new test", b : "other" = 2, *args: "ARGS", **kwar
>>> len(annotation_syntax.__annotations__) >>> len(annotation_syntax.__annotations__)
5 5
>>> annotation_syntax.__annotations__['a'] >>> strip_wrapped_string(annotation_syntax.__annotations__['a'])
'test new test' 'test new test'
>>> annotation_syntax.__annotations__['b'] >>> strip_wrapped_string(annotation_syntax.__annotations__['b'])
'other' 'other'
>>> annotation_syntax.__annotations__['args'] >>> strip_wrapped_string(annotation_syntax.__annotations__['args'])
'ARGS' 'ARGS'
>>> annotation_syntax.__annotations__['kwargs'] >>> strip_wrapped_string(annotation_syntax.__annotations__['kwargs'])
'KWARGS' 'KWARGS'
>>> annotation_syntax.__annotations__['return'] >>> strip_wrapped_string(annotation_syntax.__annotations__['return'])
'ret' 'ret'
""" """
result : int = a + b result : int = a + b
......
#cython: embedsignature=True, annotation_typing=False #cython: embedsignature=True, annotation_typing=False
# signatures here are a little fragile - when they are
# generated during the build process gives slightly
# different (but equivalent) forms - therefore tests
# may need changing occasionally to reflect behaviour
# and this isn't necessarily a bug
import sys import sys
if sys.version_info >= (3, 4): if sys.version_info >= (3, 4):
def funcdoc(f): def funcdoc(f):
if not f.__text_signature__: if not getattr(f, "__text_signature__", None):
return f.__doc__ return f.__doc__
doc = '%s%s' % (f.__name__, f.__text_signature__) doc = '%s%s' % (f.__name__, f.__text_signature__)
if f.__doc__: if f.__doc__:
...@@ -447,10 +453,10 @@ Foo.m01(self, a: ...) -> Ellipsis ...@@ -447,10 +453,10 @@ Foo.m01(self, a: ...) -> Ellipsis
Foo.m02(self, a: True, b: False) -> bool Foo.m02(self, a: True, b: False) -> bool
>>> print(Foo.m03.__doc__) >>> print(Foo.m03.__doc__)
Foo.m03(self, a: 42, b: 42, c: -42) -> int Foo.m03(self, a: 42, b: +42, c: -42) -> int
>>> print(Foo.m04.__doc__) >>> print(Foo.m04.__doc__)
Foo.m04(self, a: 3.14, b: 3.14, c: -3.14) -> float Foo.m04(self, a: 3.14, b: +3.14, c: -3.14) -> float
>>> print(Foo.m05.__doc__) >>> print(Foo.m05.__doc__)
Foo.m05(self, a: 1 + 2j, b: +2j, c: -2j) -> complex Foo.m05(self, a: 1 + 2j, b: +2j, c: -2j) -> complex
...@@ -465,7 +471,7 @@ Foo.m07(self, a: [1, 2, 3], b: []) -> list ...@@ -465,7 +471,7 @@ Foo.m07(self, a: [1, 2, 3], b: []) -> list
Foo.m08(self, a: (1, 2, 3), b: ()) -> tuple Foo.m08(self, a: (1, 2, 3), b: ()) -> tuple
>>> print(Foo.m09.__doc__) >>> print(Foo.m09.__doc__)
Foo.m09(self, a: {1, 2, 3}, b: set()) -> set Foo.m09(self, a: {1, 2, 3}, b: {i for i in ()}) -> set
>>> print(Foo.m10.__doc__) >>> print(Foo.m10.__doc__)
Foo.m10(self, a: {1: 1, 2: 2, 3: 3}, b: {}) -> dict Foo.m10(self, a: {1: 1, 2: 2, 3: 3}, b: {}) -> dict
......
...@@ -30,10 +30,10 @@ def anno_gen(x: 'int') -> 'float': ...@@ -30,10 +30,10 @@ def anno_gen(x: 'int') -> 'float':
>>> next(gen) >>> next(gen)
2.0 2.0
>>> ret, arg = sorted(anno_gen.__annotations__.items()) >>> ret, arg = sorted(anno_gen.__annotations__.items())
>>> print(ret[0]); print(ret[1]) >>> print(ret[0]); print(str(ret[1]).strip("'")) # strip makes it pass with/without PEP563
return return
float float
>>> print(arg[0]); print(arg[1]) >>> print(arg[0]); print(str(arg[1]).strip("'"))
x x
int int
""" """
......
# mode: run
# tag: pep563, pure3.7
from __future__ import annotations
def f(a: 1+2==3, b: list, c: this_cant_evaluate, d: "Hello from inside a string") -> "Return me!":
"""
The absolute exact strings aren't reproducible according to the PEP,
so be careful to avoid being too specific
>>> stypes = (type(""), type(u"")) # Python 2 is a bit awkward here
>>> eval(f.__annotations__['a'])
True
>>> isinstance(f.__annotations__['a'], stypes)
True
>>> print(f.__annotations__['b'])
list
>>> print(f.__annotations__['c'])
this_cant_evaluate
>>> isinstance(eval(f.__annotations__['d']), stypes)
True
>>> print(f.__annotations__['return'][1:-1]) # First and last could be either " or '
Return me!
>>> f.__annotations__['return'][0] == f.__annotations__['return'][-1]
True
"""
pass
...@@ -197,9 +197,10 @@ class AsyncBadSyntaxTest(unittest.TestCase): ...@@ -197,9 +197,10 @@ class AsyncBadSyntaxTest(unittest.TestCase):
pass pass
""", """,
"""async def foo(a:await something()): #"""async def foo(a:await something()):
pass # pass
""", #""", # No longer an error with pep-563 (although still nonsense)
# Some other similar tests have also been commented out
"""async def foo(): """async def foo():
def bar(): def bar():
...@@ -397,9 +398,9 @@ class AsyncBadSyntaxTest(unittest.TestCase): ...@@ -397,9 +398,9 @@ class AsyncBadSyntaxTest(unittest.TestCase):
pass pass
""", """,
"""async def foo(a:await b): #"""async def foo(a:await b):
pass # pass
""", #""",
"""def baz(): """def baz():
async def foo(a=await b): async def foo(a=await b):
...@@ -612,9 +613,9 @@ class AsyncBadSyntaxTest(unittest.TestCase): ...@@ -612,9 +613,9 @@ class AsyncBadSyntaxTest(unittest.TestCase):
pass pass
""", """,
"""async def foo(a:await b): #"""async def foo(a:await b):
pass # pass
""", #""",
"""def baz(): """def baz():
async def foo(a=await b): async def foo(a=await b):
......
...@@ -1112,7 +1112,7 @@ class GrammarTests(unittest.TestCase): ...@@ -1112,7 +1112,7 @@ class GrammarTests(unittest.TestCase):
check_syntax_error(self, "class foo:yield 1") check_syntax_error(self, "class foo:yield 1")
check_syntax_error(self, "class foo:yield from ()") check_syntax_error(self, "class foo:yield from ()")
# Check annotation refleak on SyntaxError # Check annotation refleak on SyntaxError
check_syntax_error(self, "def g(a:(yield)): pass") #check_syntax_error(self, "def g(a:(yield)): pass") # no longer a syntax error with PEP563
@skip("DeprecationWarning not implemented") @skip("DeprecationWarning not implemented")
def test_yield_in_comprehensions(self): def test_yield_in_comprehensions(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment