Commit d89862f4 authored by Stefan Behnel's avatar Stefan Behnel

Clean up directives to distinguish between those that belong to a function or...

Clean up directives to distinguish between those that belong to a function or class and those that are generally inherited. Everything that is not inherited should also not have a default value and instead exist or not.
Then, prevent lambdas and generator expressions from inheriting directives from their outer function/scope, e.g. "@cython.cdef".
Closes #459.
parent 1a91b07e
...@@ -102,6 +102,9 @@ Bugs fixed ...@@ -102,6 +102,9 @@ Bugs fixed
* Fix declarations of builtin or C types using strings in pure python mode. * Fix declarations of builtin or C types using strings in pure python mode.
(Github issue #2046) (Github issue #2046)
* Generator expressions and lambdas failed to compile in ``@cfunc`` functions.
(Github issue #459)
* Several internal function signatures were fixed that lead to warnings in gcc-8. * Several internal function signatures were fixed that lead to warnings in gcc-8.
(Github issue #2363) (Github issue #2363)
......
...@@ -2340,7 +2340,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2340,7 +2340,7 @@ class CFuncDefNode(FuncDefNode):
self.is_c_class_method = env.is_c_class_scope self.is_c_class_method = env.is_c_class_scope
if self.directive_locals is None: if self.directive_locals is None:
self.directive_locals = {} self.directive_locals = {}
self.directive_locals.update(env.directives['locals']) self.directive_locals.update(env.directives.get('locals', {}))
if self.directive_returns is not None: if self.directive_returns is not None:
base_type = self.directive_returns.analyse_as_type(env) base_type = self.directive_returns.analyse_as_type(env)
if base_type is None: if base_type is None:
...@@ -2907,7 +2907,7 @@ class DefNode(FuncDefNode): ...@@ -2907,7 +2907,7 @@ class DefNode(FuncDefNode):
self.py_wrapper.analyse_declarations(env) self.py_wrapper.analyse_declarations(env)
def analyse_argument_types(self, env): def analyse_argument_types(self, env):
self.directive_locals = env.directives['locals'] self.directive_locals = env.directives.get('locals', {})
allow_none_for_extension_args = env.directives['allow_none_for_extension_args'] allow_none_for_extension_args = env.directives['allow_none_for_extension_args']
f2s = env.fused_to_specific f2s = env.fused_to_specific
...@@ -4959,7 +4959,7 @@ class CClassDefNode(ClassDefNode): ...@@ -4959,7 +4959,7 @@ class CClassDefNode(ClassDefNode):
code.putln("if (__Pyx_MergeVtables(&%s) < 0) %s" % ( code.putln("if (__Pyx_MergeVtables(&%s) < 0) %s" % (
typeobj_cname, typeobj_cname,
code.error_goto(entry.pos))) code.error_goto(entry.pos)))
if not type.scope.is_internal and not type.scope.directives['internal']: if not type.scope.is_internal and not type.scope.directives.get('internal'):
# scope.is_internal is set for types defined by # scope.is_internal is set for types defined by
# Cython (such as closures), the 'internal' # Cython (such as closures), the 'internal'
# directive is set by users # directive is set by users
......
...@@ -170,8 +170,6 @@ _directive_defaults = { ...@@ -170,8 +170,6 @@ _directive_defaults = {
'nonecheck' : False, 'nonecheck' : False,
'initializedcheck' : True, 'initializedcheck' : True,
'embedsignature' : False, 'embedsignature' : False,
'locals' : {},
'exceptval' : None, # (except value=None, check=True)
'auto_cpdef': False, 'auto_cpdef': False,
'auto_pickle': None, 'auto_pickle': None,
'cdivision': False, # was True before 0.12 'cdivision': False, # was True before 0.12
...@@ -183,12 +181,8 @@ _directive_defaults = { ...@@ -183,12 +181,8 @@ _directive_defaults = {
'wraparound' : True, 'wraparound' : True,
'ccomplex' : False, # use C99/C++ for complex types and arith 'ccomplex' : False, # use C99/C++ for complex types and arith
'callspec' : "", 'callspec' : "",
'final' : False,
'nogil' : False, 'nogil' : False,
'internal' : False,
'profile': False, 'profile': False,
'no_gc_clear': False,
'no_gc': False,
'linetrace': False, 'linetrace': False,
'emit_code_comments': True, # copy original source code into C code comments 'emit_code_comments': True, # copy original source code into C code comments
'annotation_typing': True, # read type declarations from Python function annotations 'annotation_typing': True, # read type declarations from Python function annotations
...@@ -241,7 +235,6 @@ _directive_defaults = { ...@@ -241,7 +235,6 @@ _directive_defaults = {
# experimental, subject to change # experimental, subject to change
'binding': None, 'binding': None,
'freelist': 0,
'formal_grammar': False, 'formal_grammar': False,
} }
...@@ -301,7 +294,9 @@ def normalise_encoding_name(option_name, encoding): ...@@ -301,7 +294,9 @@ def normalise_encoding_name(option_name, encoding):
directive_types = { directive_types = {
'language_level': int, # values can be None/2/3, where None == 2+warning 'language_level': int, # values can be None/2/3, where None == 2+warning
'auto_pickle': bool, 'auto_pickle': bool,
'locals': dict,
'final' : bool, # final cdef classes and methods 'final' : bool, # final cdef classes and methods
'nogil' : bool,
'internal' : bool, # cdef class visibility in the module dict 'internal' : bool, # cdef class visibility in the module dict
'infer_types' : bool, # values can be True/None/False 'infer_types' : bool, # values can be True/None/False
'binding' : bool, 'binding' : bool,
...@@ -310,7 +305,10 @@ directive_types = { ...@@ -310,7 +305,10 @@ directive_types = {
'inline' : None, 'inline' : None,
'staticmethod' : None, 'staticmethod' : None,
'cclass' : None, 'cclass' : None,
'no_gc_clear' : bool,
'no_gc' : bool,
'returns' : type, 'returns' : type,
'exceptval': type, # actually (type, check=True/False), but has its own parser
'set_initial_path': str, 'set_initial_path': str,
'freelist': int, 'freelist': int,
'c_string_type': one_of('bytes', 'bytearray', 'str', 'unicode'), 'c_string_type': one_of('bytes', 'bytearray', 'str', 'unicode'),
...@@ -325,8 +323,10 @@ directive_scopes = { # defaults to available everywhere ...@@ -325,8 +323,10 @@ directive_scopes = { # defaults to available everywhere
# 'module', 'function', 'class', 'with statement' # 'module', 'function', 'class', 'with statement'
'auto_pickle': ('module', 'cclass'), 'auto_pickle': ('module', 'cclass'),
'final' : ('cclass', 'function'), 'final' : ('cclass', 'function'),
'nogil' : ('function',), 'nogil' : ('function', 'with statement'),
'inline' : ('function',), 'inline' : ('function',),
'cfunc' : ('function', 'with statement'),
'ccall' : ('function', 'with statement'),
'returns' : ('function',), 'returns' : ('function',),
'exceptval' : ('function',), 'exceptval' : ('function',),
'locals' : ('function',), 'locals' : ('function',),
...@@ -334,6 +334,7 @@ directive_scopes = { # defaults to available everywhere ...@@ -334,6 +334,7 @@ directive_scopes = { # defaults to available everywhere
'no_gc_clear' : ('cclass',), 'no_gc_clear' : ('cclass',),
'no_gc' : ('cclass',), 'no_gc' : ('cclass',),
'internal' : ('cclass',), 'internal' : ('cclass',),
'cclass' : ('class', 'cclass', 'with statement'),
'autotestdict' : ('module',), 'autotestdict' : ('module',),
'autotestdict.all' : ('module',), 'autotestdict.all' : ('module',),
'autotestdict.cdef' : ('module',), 'autotestdict.cdef' : ('module',),
......
...@@ -958,30 +958,33 @@ class InterpretCompilerDirectives(CythonTransform): ...@@ -958,30 +958,33 @@ class InterpretCompilerDirectives(CythonTransform):
else: else:
assert False assert False
def visit_with_directives(self, body, directives): def visit_with_directives(self, node, directives):
olddirectives = self.directives if not directives:
newdirectives = copy.copy(olddirectives) return self.visit_Node(node)
newdirectives.update(directives)
self.directives = newdirectives old_directives = self.directives
assert isinstance(body, Nodes.StatListNode), body new_directives = dict(old_directives)
retbody = self.visit_Node(body) new_directives.update(directives)
directive = Nodes.CompilerDirectivesNode(pos=retbody.pos, body=retbody,
directives=newdirectives) if new_directives == old_directives:
self.directives = olddirectives return self.visit_Node(node)
return directive
self.directives = new_directives
retbody = self.visit_Node(node)
self.directives = old_directives
if not isinstance(retbody, Nodes.StatListNode):
retbody = Nodes.StatListNode(node.pos, stats=[retbody])
return Nodes.CompilerDirectivesNode(
pos=retbody.pos, body=retbody, directives=new_directives)
# Handle decorators # Handle decorators
def visit_FuncDefNode(self, node): def visit_FuncDefNode(self, node):
directives = self._extract_directives(node, 'function') directives = self._extract_directives(node, 'function')
if not directives: return self.visit_with_directives(node, directives)
return self.visit_Node(node)
body = Nodes.StatListNode(node.pos, stats=[node])
return self.visit_with_directives(body, directives)
def visit_CVarDefNode(self, node): def visit_CVarDefNode(self, node):
directives = self._extract_directives(node, 'function') directives = self._extract_directives(node, 'function')
if not directives:
return self.visit_Node(node)
for name, value in directives.items(): for name, value in directives.items():
if name == 'locals': if name == 'locals':
node.directive_locals = value node.directive_locals = value
...@@ -990,29 +993,19 @@ class InterpretCompilerDirectives(CythonTransform): ...@@ -990,29 +993,19 @@ class InterpretCompilerDirectives(CythonTransform):
node.pos, node.pos,
"Cdef functions can only take cython.locals(), " "Cdef functions can only take cython.locals(), "
"staticmethod, or final decorators, got %s." % name)) "staticmethod, or final decorators, got %s." % name))
body = Nodes.StatListNode(node.pos, stats=[node]) return self.visit_with_directives(node, directives)
return self.visit_with_directives(body, directives)
def visit_CClassDefNode(self, node): def visit_CClassDefNode(self, node):
directives = self._extract_directives(node, 'cclass') directives = self._extract_directives(node, 'cclass')
if not directives: return self.visit_with_directives(node, directives)
return self.visit_Node(node)
body = Nodes.StatListNode(node.pos, stats=[node])
return self.visit_with_directives(body, directives)
def visit_CppClassNode(self, node): def visit_CppClassNode(self, node):
directives = self._extract_directives(node, 'cppclass') directives = self._extract_directives(node, 'cppclass')
if not directives: return self.visit_with_directives(node, directives)
return self.visit_Node(node)
body = Nodes.StatListNode(node.pos, stats=[node])
return self.visit_with_directives(body, directives)
def visit_PyClassDefNode(self, node): def visit_PyClassDefNode(self, node):
directives = self._extract_directives(node, 'class') directives = self._extract_directives(node, 'class')
if not directives: return self.visit_with_directives(node, directives)
return self.visit_Node(node)
body = Nodes.StatListNode(node.pos, stats=[node])
return self.visit_with_directives(body, directives)
def _extract_directives(self, node, scope_name): def _extract_directives(self, node, scope_name):
if not node.decorators: if not node.decorators:
...@@ -1059,7 +1052,7 @@ class InterpretCompilerDirectives(CythonTransform): ...@@ -1059,7 +1052,7 @@ class InterpretCompilerDirectives(CythonTransform):
optdict[name] = value optdict[name] = value
return optdict return optdict
# Handle with statements # Handle with-statements
def visit_WithStatNode(self, node): def visit_WithStatNode(self, node):
directive_dict = {} directive_dict = {}
for directive in self.try_to_parse_directives(node.manager) or []: for directive in self.try_to_parse_directives(node.manager) or []:
...@@ -2385,6 +2378,10 @@ class AdjustDefByDirectives(CythonTransform, SkipDeclarations): ...@@ -2385,6 +2378,10 @@ class AdjustDefByDirectives(CythonTransform, SkipDeclarations):
self.visitchildren(node) self.visitchildren(node)
return node return node
def visit_LambdaNode(self, node):
# No directives should modify lambdas or generator expressions (and also nothing in them).
return node
def visit_PyClassDefNode(self, node): def visit_PyClassDefNode(self, node):
if 'cclass' in self.directives: if 'cclass' in self.directives:
node = node.as_cclass() node = node.as_cclass()
......
...@@ -451,3 +451,19 @@ cdef class ExtTypeWithRefCycle: ...@@ -451,3 +451,19 @@ cdef class ExtTypeWithRefCycle:
def __init__(self, obj=None): def __init__(self, obj=None):
self.attribute = obj self.attribute = obj
@cython.freelist(3)
@cython.cclass
class DecoratedPyClass(object):
"""
>>> obj1 = DecoratedPyClass()
>>> obj2 = DecoratedPyClass()
>>> obj3 = DecoratedPyClass()
>>> obj4 = DecoratedPyClass()
>>> obj1 = DecoratedPyClass()
>>> obj2 = DecoratedPyClass()
>>> obj3 = DecoratedPyClass()
>>> obj4 = DecoratedPyClass()
"""
...@@ -28,6 +28,17 @@ with cfunc: ...@@ -28,6 +28,17 @@ with cfunc:
def fwith2(a): def fwith2(a):
return a*4 return a*4
@cython.test_assert_path_exists(
'//CFuncDefNode',
'//LambdaNode',
'//GeneratorDefNode',
'//GeneratorBodyDefNode',
)
def f_with_genexpr(a):
f = lambda x: x+1
return (f(x) for x in a)
with cclass: with cclass:
@cython.test_assert_path_exists('//CClassDefNode') @cython.test_assert_path_exists('//CClassDefNode')
class Egg(object): class Egg(object):
...@@ -123,3 +134,14 @@ def test_typed_return(): ...@@ -123,3 +134,14 @@ def test_typed_return():
""" """
x = cython.declare(int, 5) x = cython.declare(int, 5)
assert typed_return(cython.address(x))[0] is x assert typed_return(cython.address(x))[0] is x
def test_genexpr_in_cdef(l):
"""
>>> gen = test_genexpr_in_cdef([1, 2, 3])
>>> list(gen)
[2, 3, 4]
>>> list(gen)
[]
"""
return f_with_genexpr(l)
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