Commit 67290dbb authored by Robert Bradshaw's avatar Robert Bradshaw

Merge branch 'static'

parents 9610ed34 49d90c94
...@@ -13,7 +13,9 @@ Features added ...@@ -13,7 +13,9 @@ Features added
the module's Python namespace. Cpdef enums in pxd files export the module's Python namespace. Cpdef enums in pxd files export
their values to their own module, iff it exists. their values to their own module, iff it exists.
* Calls to ``slice()`` are translated to a straight C-API call. * Allow @staticmethod decorator to declare static cdef methods.
This is especially useful for declaring "constructors" for
cdef classes that can take non-Python arguments.
* Taking a ``char*`` from a temporary Python string object is safer * Taking a ``char*`` from a temporary Python string object is safer
in more cases and can be done inside of non-trivial expressions, in more cases and can be done inside of non-trivial expressions,
...@@ -21,16 +23,6 @@ Features added ...@@ -21,16 +23,6 @@ Features added
is raised only when such a pointer is assigned to a variable and is raised only when such a pointer is assigned to a variable and
would thus exceed the lifetime of the string itself. would thus exceed the lifetime of the string itself.
* The "and"/"or" operators try to avoid unnecessary coercions of their
arguments. They now evaluate the truth value of each argument
independently and only coerce the final result of the whole expression
to the target type (e.g. the type on the left side of an assignment).
This also avoids reference counting overhead for Python values during
evaluation and generally improves the code flow in the generated C code.
* Cascaded assignments (a = b = ...) try to minimise the number of
type coercions.
* Generators have new properties ``__name__`` and ``__qualname__`` * Generators have new properties ``__name__`` and ``__qualname__``
that provide the plain/qualified name of the generator function that provide the plain/qualified name of the generator function
(following CPython 3.5). See http://bugs.python.org/issue21205 (following CPython 3.5). See http://bugs.python.org/issue21205
...@@ -46,9 +38,6 @@ Features added ...@@ -46,9 +38,6 @@ Features added
* HTML output of annotated code uses Pygments for code highlighting * HTML output of annotated code uses Pygments for code highlighting
and generally received a major overhaul by Matthias Bussonier. and generally received a major overhaul by Matthias Bussonier.
* The Python expression "2 ** N" is optimised into bit shifting.
See http://bugs.python.org/issue21420
* Simple support for declaring Python object types in Python signature * Simple support for declaring Python object types in Python signature
annotations. Currently requires setting the compiler directive annotations. Currently requires setting the compiler directive
``annotation_typing=True``. ``annotation_typing=True``.
...@@ -58,6 +47,27 @@ Features added ...@@ -58,6 +47,27 @@ Features added
* Defines dynamic_cast et al. in `libcpp.cast`. * Defines dynamic_cast et al. in `libcpp.cast`.
Optimizations
-------------
* The "and"/"or" operators try to avoid unnecessary coercions of their
arguments. They now evaluate the truth value of each argument
independently and only coerce the final result of the whole expression
to the target type (e.g. the type on the left side of an assignment).
This also avoids reference counting overhead for Python values during
evaluation and generally improves the code flow in the generated C code.
* Cascaded assignments (a = b = ...) try to minimise the number of
type coercions.
* The Python expression "2 ** N" is optimised into bit shifting.
See http://bugs.python.org/issue21420
* Cascaded assignments (a = b = ...) try to minimise the number of
type coercions.
* Calls to ``slice()`` are translated to a straight C-API call.
Bugs fixed Bugs fixed
---------- ----------
......
...@@ -4456,16 +4456,24 @@ class SimpleCallNode(CallNode): ...@@ -4456,16 +4456,24 @@ class SimpleCallNode(CallNode):
return func_type return func_type
def analyse_c_function_call(self, env): def analyse_c_function_call(self, env):
if self.function.type is error_type: func_type = self.function.type
if func_type is error_type:
self.type = error_type self.type = error_type
return return
if self.self: if func_type.is_cfunction and func_type.is_static_method:
if self.self and self.self.type.is_extension_type:
# To support this we'd need to pass self to determine whether
# it was overloaded in Python space (possibly via a Cython
# superclass turning a cdef method into a cpdef one).
error(self.pos, "Cannot call a static method on an instance variable.")
args = self.args
elif self.self:
args = [self.self] + self.args args = [self.self] + self.args
else: else:
args = self.args args = self.args
if self.function.type.is_cpp_class: if func_type.is_cpp_class:
overloaded_entry = self.function.type.scope.lookup("operator()") overloaded_entry = self.function.type.scope.lookup("operator()")
if overloaded_entry is None: if overloaded_entry is None:
self.type = PyrexTypes.error_type self.type = PyrexTypes.error_type
...@@ -4515,7 +4523,7 @@ class SimpleCallNode(CallNode): ...@@ -4515,7 +4523,7 @@ class SimpleCallNode(CallNode):
self.is_temp = 1 self.is_temp = 1
# check 'self' argument # check 'self' argument
if entry and entry.is_cmethod and func_type.args: if entry and entry.is_cmethod and func_type.args and not func_type.is_static_method:
formal_arg = func_type.args[0] formal_arg = func_type.args[0]
arg = args[0] arg = args[0]
if formal_arg.not_none: if formal_arg.not_none:
...@@ -5316,10 +5324,13 @@ class AttributeNode(ExprNode): ...@@ -5316,10 +5324,13 @@ class AttributeNode(ExprNode):
# as an ordinary function. # as an ordinary function.
if entry.func_cname and not hasattr(entry.type, 'op_arg_struct'): if entry.func_cname and not hasattr(entry.type, 'op_arg_struct'):
cname = entry.func_cname cname = entry.func_cname
# Fix self type. if entry.type.is_static_method:
ctype = copy.copy(entry.type) ctype = entry.type
ctype.args = ctype.args[:] else:
ctype.args[0] = PyrexTypes.CFuncTypeArg('self', type, 'self', None) # Fix self type.
ctype = copy.copy(entry.type)
ctype.args = ctype.args[:]
ctype.args[0] = PyrexTypes.CFuncTypeArg('self', type, 'self', None)
else: else:
cname = "%s->%s" % (type.vtabptr_cname, entry.cname) cname = "%s->%s" % (type.vtabptr_cname, entry.cname)
ctype = entry.type ctype = entry.type
......
...@@ -617,7 +617,7 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -617,7 +617,7 @@ class CFuncDeclaratorNode(CDeclaratorNode):
func_type_args = [] func_type_args = []
for i, arg_node in enumerate(self.args): for i, arg_node in enumerate(self.args):
name_declarator, type = arg_node.analyse( name_declarator, type = arg_node.analyse(
env, nonempty=nonempty, is_self_arg=(i == 0 and env.is_c_class_scope)) env, nonempty=nonempty, is_self_arg=(i == 0 and env.is_c_class_scope and 'staticmethod' not in env.directives))
name = name_declarator.name name = name_declarator.name
if name in directive_locals: if name in directive_locals:
type_node = directive_locals[name] type_node = directive_locals[name]
...@@ -2143,10 +2143,11 @@ class CFuncDefNode(FuncDefNode): ...@@ -2143,10 +2143,11 @@ class CFuncDefNode(FuncDefNode):
# overridable whether or not this is a cpdef function # overridable whether or not this is a cpdef function
# inline_in_pxd whether this is an inline function in a pxd file # inline_in_pxd whether this is an inline function in a pxd file
# template_declaration String or None Used for c++ class methods # template_declaration String or None Used for c++ class methods
# is_const_method whether this is a const method # is_const_method whether this is a const method
# is_static_method whether this is a static method
# is_c_class_method whether this is a cclass method # is_c_class_method whether this is a cclass method
child_attrs = ["base_type", "declarator", "body", "py_func"] child_attrs = ["base_type", "declarator", "body", "py_func_stat"]
inline_in_pxd = False inline_in_pxd = False
decorators = None decorators = None
...@@ -2155,6 +2156,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2155,6 +2156,7 @@ class CFuncDefNode(FuncDefNode):
override = None override = None
template_declaration = None template_declaration = None
is_const_method = False is_const_method = False
py_func_stat = None
def unqualified_name(self): def unqualified_name(self):
return self.entry.name return self.entry.name
...@@ -2171,6 +2173,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2171,6 +2173,7 @@ class CFuncDefNode(FuncDefNode):
base_type = PyrexTypes.error_type base_type = PyrexTypes.error_type
else: else:
base_type = self.base_type.analyse(env) base_type = self.base_type.analyse(env)
self.is_static_method = 'staticmethod' in env.directives and not env.lookup_here('staticmethod')
# The 2 here is because we need both function and argument names. # The 2 here is because we need both function and argument names.
if isinstance(self.declarator, CFuncDeclaratorNode): if isinstance(self.declarator, CFuncDeclaratorNode):
name_declarator, type = self.declarator.analyse(base_type, env, name_declarator, type = self.declarator.analyse(base_type, env,
...@@ -2232,6 +2235,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2232,6 +2235,7 @@ class CFuncDefNode(FuncDefNode):
cname = name_declarator.cname cname = name_declarator.cname
type.is_const_method = self.is_const_method type.is_const_method = self.is_const_method
type.is_static_method = self.is_static_method
self.entry = env.declare_cfunction( self.entry = env.declare_cfunction(
name, type, self.pos, name, type, self.pos,
cname = cname, visibility = self.visibility, api = self.api, cname = cname, visibility = self.visibility, api = self.api,
...@@ -2244,7 +2248,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2244,7 +2248,7 @@ class CFuncDefNode(FuncDefNode):
if self.return_type.is_cpp_class: if self.return_type.is_cpp_class:
self.return_type.check_nullary_constructor(self.pos, "used as a return value") self.return_type.check_nullary_constructor(self.pos, "used as a return value")
if self.overridable and not env.is_module_scope: if self.overridable and not env.is_module_scope and not self.is_static_method:
if len(self.args) < 1 or not self.args[0].type.is_pyobject: if len(self.args) < 1 or not self.args[0].type.is_pyobject:
# An error will be produced in the cdef function # An error will be produced in the cdef function
self.overridable = False self.overridable = False
...@@ -2254,8 +2258,17 @@ class CFuncDefNode(FuncDefNode): ...@@ -2254,8 +2258,17 @@ class CFuncDefNode(FuncDefNode):
def declare_cpdef_wrapper(self, env): def declare_cpdef_wrapper(self, env):
if self.overridable: if self.overridable:
if self.is_static_method:
# TODO(robertwb): Finish this up, perhaps via more function refactoring.
error(self.pos, "static cpdef methods not yet supported")
name = self.entry.name name = self.entry.name
py_func_body = self.call_self_node(is_module_scope = env.is_module_scope) py_func_body = self.call_self_node(is_module_scope = env.is_module_scope)
if self.is_static_method:
from .ExprNodes import NameNode
decorators = [DecoratorNode(self.pos, decorator=NameNode(self.pos, name='staticmethod'))]
decorators[0].decorator.analyse_types(env)
else:
decorators = []
self.py_func = DefNode(pos = self.pos, self.py_func = DefNode(pos = self.pos,
name = self.entry.name, name = self.entry.name,
args = self.args, args = self.args,
...@@ -2263,9 +2276,12 @@ class CFuncDefNode(FuncDefNode): ...@@ -2263,9 +2276,12 @@ class CFuncDefNode(FuncDefNode):
starstar_arg = None, starstar_arg = None,
doc = self.doc, doc = self.doc,
body = py_func_body, body = py_func_body,
decorators = decorators,
is_wrapper = 1) is_wrapper = 1)
self.py_func.is_module_scope = env.is_module_scope self.py_func.is_module_scope = env.is_module_scope
self.py_func.analyse_declarations(env) self.py_func.analyse_declarations(env)
self.py_func_stat = StatListNode(pos = self.pos, stats = [self.py_func])
self.py_func.type = PyrexTypes.py_object_type
self.entry.as_variable = self.py_func.entry self.entry.as_variable = self.py_func.entry
self.entry.used = self.entry.as_variable.used = True self.entry.used = self.entry.as_variable.used = True
# Reset scope entry the above cfunction # Reset scope entry the above cfunction
...@@ -2296,6 +2312,16 @@ class CFuncDefNode(FuncDefNode): ...@@ -2296,6 +2312,16 @@ class CFuncDefNode(FuncDefNode):
arg_names = [arg.name for arg in args] arg_names = [arg.name for arg in args]
if is_module_scope: if is_module_scope:
cfunc = ExprNodes.NameNode(self.pos, name=self.entry.name) cfunc = ExprNodes.NameNode(self.pos, name=self.entry.name)
call_arg_names = arg_names
skip_dispatch = Options.lookup_module_cpdef
elif self.type.is_static_method:
class_entry = self.entry.scope.parent_type.entry
class_node = ExprNodes.NameNode(self.pos, name=class_entry.name)
class_node.entry = class_entry
cfunc = ExprNodes.AttributeNode(self.pos, obj=class_node, attribute=self.entry.name)
# Calling static c(p)def methods on an instance disallowed.
# TODO(robertwb): Support by passing self to check for override?
skip_dispatch = True
else: else:
type_entry = self.type.args[0].type.entry type_entry = self.type.args[0].type.entry
type_arg = ExprNodes.NameNode(self.pos, name=type_entry.name) type_arg = ExprNodes.NameNode(self.pos, name=type_entry.name)
...@@ -2446,6 +2472,11 @@ class CFuncDefNode(FuncDefNode): ...@@ -2446,6 +2472,11 @@ class CFuncDefNode(FuncDefNode):
elif arg.type.is_pyobject and not arg.accept_none: elif arg.type.is_pyobject and not arg.accept_none:
self.generate_arg_none_check(arg, code) self.generate_arg_none_check(arg, code)
def generate_execution_code(self, code):
super(CFuncDefNode, self).generate_execution_code(code)
if self.py_func_stat:
self.py_func_stat.generate_execution_code(code)
def error_value(self): def error_value(self):
if self.return_type.is_pyobject: if self.return_type.is_pyobject:
return "0" return "0"
...@@ -2901,10 +2932,10 @@ class DefNode(FuncDefNode): ...@@ -2901,10 +2932,10 @@ class DefNode(FuncDefNode):
return self return self
def needs_assignment_synthesis(self, env, code=None): def needs_assignment_synthesis(self, env, code=None):
if self.is_wrapper or self.specialized_cpdefs or self.entry.is_fused_specialized:
return False
if self.is_staticmethod: if self.is_staticmethod:
return True return True
if self.is_wrapper or self.specialized_cpdefs or self.entry.is_fused_specialized:
return False
if self.no_assignment_synthesis: if self.no_assignment_synthesis:
return False return False
# Should enable for module level as well, that will require more testing... # Should enable for module level as well, that will require more testing...
......
...@@ -209,6 +209,7 @@ directive_types = { ...@@ -209,6 +209,7 @@ directive_types = {
'cfunc' : None, # decorators do not take directive value 'cfunc' : None, # decorators do not take directive value
'ccall' : None, 'ccall' : None,
'inline' : None, 'inline' : None,
'staticmethod' : None,
'cclass' : None, 'cclass' : None,
'returns' : type, 'returns' : type,
'set_initial_path': str, 'set_initial_path': str,
...@@ -225,6 +226,7 @@ directive_scopes = { # defaults to available everywhere ...@@ -225,6 +226,7 @@ directive_scopes = { # defaults to available everywhere
# 'module', 'function', 'class', 'with statement' # 'module', 'function', 'class', 'with statement'
'final' : ('cclass', 'function'), 'final' : ('cclass', 'function'),
'inline' : ('function',), 'inline' : ('function',),
'staticmethod' : ('function',), # FIXME: analysis currently lacks more specific function scope
'no_gc_clear' : ('cclass',), 'no_gc_clear' : ('cclass',),
'internal' : ('cclass',), 'internal' : ('cclass',),
'autotestdict' : ('module',), 'autotestdict' : ('module',),
......
...@@ -674,7 +674,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -674,7 +674,7 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
for key, value in compilation_directive_defaults.items(): for key, value in compilation_directive_defaults.items():
self.compilation_directive_defaults[unicode(key)] = copy.deepcopy(value) self.compilation_directive_defaults[unicode(key)] = copy.deepcopy(value)
self.cython_module_names = set() self.cython_module_names = set()
self.directive_names = {} self.directive_names = {'staticmethod': 'staticmethod'}
self.parallel_directives = {} self.parallel_directives = {}
def check_directive_scope(self, pos, directive, scope): def check_directive_scope(self, pos, directive, scope):
...@@ -979,18 +979,23 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations): ...@@ -979,18 +979,23 @@ class InterpretCompilerDirectives(CythonTransform, SkipDeclarations):
# Split the decorators into two lists -- real decorators and directives # Split the decorators into two lists -- real decorators and directives
directives = [] directives = []
realdecs = [] realdecs = []
both = []
for dec in node.decorators: for dec in node.decorators:
new_directives = self.try_to_parse_directives(dec.decorator) new_directives = self.try_to_parse_directives(dec.decorator)
if new_directives is not None: if new_directives is not None:
for directive in new_directives: for directive in new_directives:
if self.check_directive_scope(node.pos, directive[0], scope_name): if self.check_directive_scope(node.pos, directive[0], scope_name):
directives.append(directive) name, value = directive
if self.directives.get(name, object()) != value:
directives.append(directive)
if directive[0] == 'staticmethod':
both.append(dec)
else: else:
realdecs.append(dec) realdecs.append(dec)
if realdecs and isinstance(node, (Nodes.CFuncDefNode, Nodes.CClassDefNode, Nodes.CVarDefNode)): if realdecs and isinstance(node, (Nodes.CFuncDefNode, Nodes.CClassDefNode, Nodes.CVarDefNode)):
raise PostParseError(realdecs[0].pos, "Cdef functions/classes cannot take arbitrary decorators.") raise PostParseError(realdecs[0].pos, "Cdef functions/classes cannot take arbitrary decorators.")
else: else:
node.decorators = realdecs node.decorators = realdecs + both
# merge or override repeated directives # merge or override repeated directives
optdict = {} optdict = {}
directives.reverse() # Decorators coming first take precedence directives.reverse() # Decorators coming first take precedence
...@@ -2708,6 +2713,8 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2708,6 +2713,8 @@ class TransformBuiltinMethods(EnvTransform):
node.cdivision = True node.cdivision = True
elif function == u'set': elif function == u'set':
node.function = ExprNodes.NameNode(node.pos, name=EncodedString('set')) node.function = ExprNodes.NameNode(node.pos, name=EncodedString('set'))
elif function == u'staticmethod':
node.function = ExprNodes.NameNode(node.pos, name=EncodedString('staticmethod'))
elif self.context.cython_scope.lookup_qualified_name(function): elif self.context.cython_scope.lookup_qualified_name(function):
pass pass
else: else:
......
...@@ -2315,6 +2315,7 @@ class CFuncType(CType): ...@@ -2315,6 +2315,7 @@ class CFuncType(CType):
# is_strict_signature boolean function refuses to accept coerced arguments # is_strict_signature boolean function refuses to accept coerced arguments
# (used for optimisation overrides) # (used for optimisation overrides)
# is_const_method boolean # is_const_method boolean
# is_static_method boolean
is_cfunction = 1 is_cfunction = 1
original_sig = None original_sig = None
...@@ -2327,7 +2328,8 @@ class CFuncType(CType): ...@@ -2327,7 +2328,8 @@ class CFuncType(CType):
def __init__(self, return_type, args, has_varargs = 0, def __init__(self, return_type, args, has_varargs = 0,
exception_value = None, exception_check = 0, calling_convention = "", exception_value = None, exception_check = 0, calling_convention = "",
nogil = 0, with_gil = 0, is_overridable = 0, optional_arg_count = 0, nogil = 0, with_gil = 0, is_overridable = 0, optional_arg_count = 0,
is_const_method = False, templates = None, is_strict_signature = False): is_const_method = False, is_static_method=False,
templates = None, is_strict_signature = False):
self.return_type = return_type self.return_type = return_type
self.args = args self.args = args
self.has_varargs = has_varargs self.has_varargs = has_varargs
...@@ -2339,6 +2341,7 @@ class CFuncType(CType): ...@@ -2339,6 +2341,7 @@ class CFuncType(CType):
self.with_gil = with_gil self.with_gil = with_gil
self.is_overridable = is_overridable self.is_overridable = is_overridable
self.is_const_method = is_const_method self.is_const_method = is_const_method
self.is_static_method = is_static_method
self.templates = templates self.templates = templates
self.is_strict_signature = is_strict_signature self.is_strict_signature = is_strict_signature
...@@ -2563,6 +2566,7 @@ class CFuncType(CType): ...@@ -2563,6 +2566,7 @@ class CFuncType(CType):
is_overridable = self.is_overridable, is_overridable = self.is_overridable,
optional_arg_count = self.optional_arg_count, optional_arg_count = self.optional_arg_count,
is_const_method = self.is_const_method, is_const_method = self.is_const_method,
is_static_method = self.is_static_method,
templates = self.templates) templates = self.templates)
result.from_fused = self.is_fused result.from_fused = self.is_fused
......
...@@ -1947,11 +1947,12 @@ class CClassScope(ClassScope): ...@@ -1947,11 +1947,12 @@ class CClassScope(ClassScope):
if get_special_method_signature(name) and not self.parent_type.is_builtin_type: if get_special_method_signature(name) and not self.parent_type.is_builtin_type:
error(pos, "Special methods must be declared with 'def', not 'cdef'") error(pos, "Special methods must be declared with 'def', not 'cdef'")
args = type.args args = type.args
if not args: if not type.is_static_method:
error(pos, "C method has no self argument") if not args:
elif not self.parent_type.assignable_from(args[0].type): error(pos, "C method has no self argument")
error(pos, "Self argument (%s) of C method '%s' does not match parent type (%s)" % elif not self.parent_type.assignable_from(args[0].type):
(args[0].type, name, self.parent_type)) error(pos, "Self argument (%s) of C method '%s' does not match parent type (%s)" %
(args[0].type, name, self.parent_type))
entry = self.lookup_here(name) entry = self.lookup_here(name)
if cname is None: if cname is None:
cname = c_safe_identifier(name) cname = c_safe_identifier(name)
......
cdef class A:
@staticmethod
def static_def(int x):
"""
>>> A.static_def(2)
('def', 2)
>>> A().static_def(2)
('def', 2)
"""
return 'def', x
@staticmethod
cdef static_cdef(int* x):
return 'cdef', x[0]
# @staticmethod
# cpdef static_cpdef(int x):
# """
# >>> A.static_def
# >>> A.static_cpdef
#
# >>> A().static_def
# >>> A().static_cpdef
#
# >>> A.static_cpdef(2)
# ('cpdef', 2)
# >>> A().static_cpdef(2)
# ('cpdef', 2)
# """
# return 'cpdef', x
def call_static_def(int x):
"""
>>> call_static_def(2)
('def', 2)
"""
return A.static_def(x)
def call_static_cdef(int x):
"""
>>> call_static_cdef(2)
('cdef', 2)
"""
cdef int *x_ptr = &x
return A.static_cdef(x_ptr)
# def call_static_cpdef(int x):
# """
# >>> call_static_cpdef(2)
# ('cpdef', 2)
# """
# return A.static_cpdef(x)
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