diff options
author | Anton Samokhvalov <pg83@yandex.ru> | 2022-02-10 16:45:15 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:45:15 +0300 |
commit | 72cb13b4aff9bc9cf22e49251bc8fd143f82538f (patch) | |
tree | da2c34829458c7d4e74bdfbdf85dff449e9e7fb8 /contrib/tools/cython/Cython/Compiler/Nodes.py | |
parent | 778e51ba091dc39e7b7fcab2b9cf4dbedfb6f2b5 (diff) | |
download | ydb-72cb13b4aff9bc9cf22e49251bc8fd143f82538f.tar.gz |
Restoring authorship annotation for Anton Samokhvalov <pg83@yandex.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/cython/Cython/Compiler/Nodes.py')
-rw-r--r-- | contrib/tools/cython/Cython/Compiler/Nodes.py | 14782 |
1 files changed, 7391 insertions, 7391 deletions
diff --git a/contrib/tools/cython/Cython/Compiler/Nodes.py b/contrib/tools/cython/Cython/Compiler/Nodes.py index 6436c5002d..c54707ec40 100644 --- a/contrib/tools/cython/Cython/Compiler/Nodes.py +++ b/contrib/tools/cython/Cython/Compiler/Nodes.py @@ -1,73 +1,73 @@ -# -# Parse tree nodes -# - -from __future__ import absolute_import - -import cython -cython.declare(sys=object, os=object, copy=object, - Builtin=object, error=object, warning=object, Naming=object, PyrexTypes=object, - py_object_type=object, ModuleScope=object, LocalScope=object, ClosureScope=object, - StructOrUnionScope=object, PyClassScope=object, - CppClassScope=object, UtilityCode=object, EncodedString=object, +# +# Parse tree nodes +# + +from __future__ import absolute_import + +import cython +cython.declare(sys=object, os=object, copy=object, + Builtin=object, error=object, warning=object, Naming=object, PyrexTypes=object, + py_object_type=object, ModuleScope=object, LocalScope=object, ClosureScope=object, + StructOrUnionScope=object, PyClassScope=object, + CppClassScope=object, UtilityCode=object, EncodedString=object, error_type=object, _py_int_types=object) - -import sys, os, copy -from itertools import chain - -from . import Builtin -from .Errors import error, warning, InternalError, CompileError -from . import Naming -from . import PyrexTypes -from . import TypeSlots -from .PyrexTypes import py_object_type, error_type -from .Symtab import (ModuleScope, LocalScope, ClosureScope, + +import sys, os, copy +from itertools import chain + +from . import Builtin +from .Errors import error, warning, InternalError, CompileError +from . import Naming +from . import PyrexTypes +from . import TypeSlots +from .PyrexTypes import py_object_type, error_type +from .Symtab import (ModuleScope, LocalScope, ClosureScope, StructOrUnionScope, PyClassScope, CppClassScope, TemplateScope) -from .Code import UtilityCode +from .Code import UtilityCode from .StringEncoding import EncodedString from . import Future -from . import Options -from . import DebugFlags +from . import Options +from . import DebugFlags from .Pythran import has_np_pythran, pythran_type, is_pythran_buffer from ..Utils import add_metaclass - - + + if sys.version_info[0] >= 3: _py_int_types = int else: _py_int_types = (int, long) + - -def relative_position(pos): +def relative_position(pos): return (pos[0].get_filenametable_entry(), pos[1]) - - -def embed_position(pos, docstring): - if not Options.embed_pos_in_docstring: - return docstring - pos_line = u'File: %s (starting at line %s)' % relative_position(pos) - if docstring is None: - # unicode string - return EncodedString(pos_line) - - # make sure we can encode the filename in the docstring encoding - # otherwise make the docstring a unicode string - encoding = docstring.encoding - if encoding is not None: - try: - pos_line.encode(encoding) - except UnicodeEncodeError: - encoding = None - - if not docstring: - # reuse the string encoding of the original docstring - doc = EncodedString(pos_line) - else: - doc = EncodedString(pos_line + u'\n' + docstring) - doc.encoding = encoding - return doc - - + + +def embed_position(pos, docstring): + if not Options.embed_pos_in_docstring: + return docstring + pos_line = u'File: %s (starting at line %s)' % relative_position(pos) + if docstring is None: + # unicode string + return EncodedString(pos_line) + + # make sure we can encode the filename in the docstring encoding + # otherwise make the docstring a unicode string + encoding = docstring.encoding + if encoding is not None: + try: + pos_line.encode(encoding) + except UnicodeEncodeError: + encoding = None + + if not docstring: + # reuse the string encoding of the original docstring + doc = EncodedString(pos_line) + else: + doc = EncodedString(pos_line + u'\n' + docstring) + doc.encoding = encoding + return doc + + def analyse_type_annotation(annotation, env, assigned_value=None): base_type = None is_ambiguous = False @@ -115,72 +115,72 @@ def analyse_type_annotation(annotation, env, assigned_value=None): return base_type, arg_type -def write_func_call(func, codewriter_class): - def f(*args, **kwds): - if len(args) > 1 and isinstance(args[1], codewriter_class): - # here we annotate the code with this function call - # but only if new code is generated - node, code = args[:2] - marker = ' /* %s -> %s.%s %s */' % ( +def write_func_call(func, codewriter_class): + def f(*args, **kwds): + if len(args) > 1 and isinstance(args[1], codewriter_class): + # here we annotate the code with this function call + # but only if new code is generated + node, code = args[:2] + marker = ' /* %s -> %s.%s %s */' % ( ' ' * code.call_level, node.__class__.__name__, func.__name__, node.pos[1:]) - pristine = code.buffer.stream.tell() - code.putln(marker) - start = code.buffer.stream.tell() - code.call_level += 4 - res = func(*args, **kwds) - code.call_level -= 4 - if start == code.buffer.stream.tell(): + pristine = code.buffer.stream.tell() + code.putln(marker) + start = code.buffer.stream.tell() + code.call_level += 4 + res = func(*args, **kwds) + code.call_level -= 4 + if start == code.buffer.stream.tell(): # no code written => undo writing marker code.buffer.stream.truncate(pristine) - else: + else: marker = marker.replace('->', '<-', 1) - code.putln(marker) - return res - else: - return func(*args, **kwds) - return f - - -class VerboseCodeWriter(type): - # Set this as a metaclass to trace function calls in code. - # This slows down code generation and makes much larger files. - def __new__(cls, name, bases, attrs): - from types import FunctionType - from .Code import CCodeWriter - attrs = dict(attrs) - for mname, m in attrs.items(): - if isinstance(m, FunctionType): - attrs[mname] = write_func_call(m, CCodeWriter) - return super(VerboseCodeWriter, cls).__new__(cls, name, bases, attrs) - - -class CheckAnalysers(type): - """Metaclass to check that type analysis functions return a node. - """ - methods = set(['analyse_types', - 'analyse_expressions', - 'analyse_target_types']) - - def __new__(cls, name, bases, attrs): - from types import FunctionType - def check(name, func): - def call(*args, **kwargs): - retval = func(*args, **kwargs) - if retval is None: + code.putln(marker) + return res + else: + return func(*args, **kwds) + return f + + +class VerboseCodeWriter(type): + # Set this as a metaclass to trace function calls in code. + # This slows down code generation and makes much larger files. + def __new__(cls, name, bases, attrs): + from types import FunctionType + from .Code import CCodeWriter + attrs = dict(attrs) + for mname, m in attrs.items(): + if isinstance(m, FunctionType): + attrs[mname] = write_func_call(m, CCodeWriter) + return super(VerboseCodeWriter, cls).__new__(cls, name, bases, attrs) + + +class CheckAnalysers(type): + """Metaclass to check that type analysis functions return a node. + """ + methods = set(['analyse_types', + 'analyse_expressions', + 'analyse_target_types']) + + def __new__(cls, name, bases, attrs): + from types import FunctionType + def check(name, func): + def call(*args, **kwargs): + retval = func(*args, **kwargs) + if retval is None: print('%s %s %s' % (name, args, kwargs)) - return retval - return call - - attrs = dict(attrs) - for mname, m in attrs.items(): - if isinstance(m, FunctionType) and mname in cls.methods: - attrs[mname] = check(mname, m) - return super(CheckAnalysers, cls).__new__(cls, name, bases, attrs) - - + return retval + return call + + attrs = dict(attrs) + for mname, m in attrs.items(): + if isinstance(m, FunctionType) and mname in cls.methods: + attrs[mname] = check(mname, m) + return super(CheckAnalysers, cls).__new__(cls, name, bases, attrs) + + def _with_metaclass(cls): if DebugFlags.debug_trace_code_generation: return add_metaclass(VerboseCodeWriter)(cls) @@ -189,303 +189,303 @@ def _with_metaclass(cls): @_with_metaclass -class Node(object): - # pos (string, int, int) Source file position - # is_name boolean Is a NameNode - # is_literal boolean Is a ConstNode - - is_name = 0 - is_none = 0 - is_nonecheck = 0 - is_literal = 0 - is_terminator = 0 +class Node(object): + # pos (string, int, int) Source file position + # is_name boolean Is a NameNode + # is_literal boolean Is a ConstNode + + is_name = 0 + is_none = 0 + is_nonecheck = 0 + is_literal = 0 + is_terminator = 0 is_wrapper = False # is a DefNode wrapper for a C function - temps = None - - # All descendants should set child_attrs to a list of the attributes - # containing nodes considered "children" in the tree. Each such attribute - # can either contain a single node or a list of nodes. See Visitor.py. - child_attrs = None - + temps = None + + # All descendants should set child_attrs to a list of the attributes + # containing nodes considered "children" in the tree. Each such attribute + # can either contain a single node or a list of nodes. See Visitor.py. + child_attrs = None + # Subset of attributes that are evaluated in the outer scope (e.g. function default arguments). outer_attrs = None - cf_state = None - - # This may be an additional (or 'actual') type that will be checked when - # this node is coerced to another type. This could be useful to set when - # the actual type to which it can coerce is known, but you want to leave - # the type a py_object_type - coercion_type = None - - def __init__(self, pos, **kw): - self.pos = pos - self.__dict__.update(kw) - - gil_message = "Operation" - - nogil_check = None + cf_state = None + + # This may be an additional (or 'actual') type that will be checked when + # this node is coerced to another type. This could be useful to set when + # the actual type to which it can coerce is known, but you want to leave + # the type a py_object_type + coercion_type = None + + def __init__(self, pos, **kw): + self.pos = pos + self.__dict__.update(kw) + + gil_message = "Operation" + + nogil_check = None in_nogil_context = False # For use only during code generation. - - def gil_error(self, env=None): - error(self.pos, "%s not allowed without gil" % self.gil_message) - - cpp_message = "Operation" - - def cpp_check(self, env): - if not env.is_cpp(): - self.cpp_error() - - def cpp_error(self): - error(self.pos, "%s only allowed in c++" % self.cpp_message) - - def clone_node(self): - """Clone the node. This is defined as a shallow copy, except for member lists - amongst the child attributes (from get_child_accessors) which are also - copied. Lists containing child nodes are thus seen as a way for the node - to hold multiple children directly; the list is not treated as a separate - level in the tree.""" - result = copy.copy(self) - for attrname in result.child_attrs: - value = getattr(result, attrname) - if isinstance(value, list): - setattr(result, attrname, [x for x in value]) - return result - - - # - # There are 3 phases of parse tree processing, applied in order to - # all the statements in a given scope-block: - # - # (0) analyse_declarations - # Make symbol table entries for all declarations at the current - # level, both explicit (def, cdef, etc.) and implicit (assignment - # to an otherwise undeclared name). - # - # (1) analyse_expressions - # Determine the result types of expressions and fill in the - # 'type' attribute of each ExprNode. Insert coercion nodes into the - # tree where needed to convert to and from Python objects. - # Allocate temporary locals for intermediate results. Fill - # in the 'result_code' attribute of each ExprNode with a C code - # fragment. - # - # (2) generate_code - # Emit C code for all declarations, statements and expressions. - # Recursively applies the 3 processing phases to the bodies of - # functions. - # - - def analyse_declarations(self, env): - pass - - def analyse_expressions(self, env): - raise InternalError("analyse_expressions not implemented for %s" % \ - self.__class__.__name__) - - def generate_code(self, code): - raise InternalError("generate_code not implemented for %s" % \ - self.__class__.__name__) - - def annotate(self, code): - # mro does the wrong thing - if isinstance(self, BlockNode): - self.body.annotate(code) - - def end_pos(self): - try: - return self._end_pos - except AttributeError: - pos = self.pos - if not self.child_attrs: - self._end_pos = pos - return pos - for attr in self.child_attrs: - child = getattr(self, attr) - # Sometimes lists, sometimes nodes - if child is None: - pass - elif isinstance(child, list): - for c in child: - pos = max(pos, c.end_pos()) - else: - pos = max(pos, child.end_pos()) - self._end_pos = pos - return pos - - def dump(self, level=0, filter_out=("pos",), cutoff=100, encountered=None): - """Debug helper method that returns a recursive string representation of this node. - """ - if cutoff == 0: - return "<...nesting level cutoff...>" - if encountered is None: - encountered = set() - if id(self) in encountered: - return "<%s (0x%x) -- already output>" % (self.__class__.__name__, id(self)) - encountered.add(id(self)) - - def dump_child(x, level): - if isinstance(x, Node): - return x.dump(level, filter_out, cutoff-1, encountered) - elif isinstance(x, list): - return "[%s]" % ", ".join([dump_child(item, level) for item in x]) - else: - return repr(x) - - attrs = [(key, value) for key, value in self.__dict__.items() if key not in filter_out] - if len(attrs) == 0: - return "<%s (0x%x)>" % (self.__class__.__name__, id(self)) - else: - indent = " " * level - res = "<%s (0x%x)\n" % (self.__class__.__name__, id(self)) - for key, value in attrs: - res += "%s %s: %s\n" % (indent, key, dump_child(value, level + 1)) - res += "%s>" % indent - return res - - def dump_pos(self, mark_column=False, marker='(#)'): - """Debug helper method that returns the source code context of this node as a string. - """ - if not self.pos: - return u'' - source_desc, line, col = self.pos + + def gil_error(self, env=None): + error(self.pos, "%s not allowed without gil" % self.gil_message) + + cpp_message = "Operation" + + def cpp_check(self, env): + if not env.is_cpp(): + self.cpp_error() + + def cpp_error(self): + error(self.pos, "%s only allowed in c++" % self.cpp_message) + + def clone_node(self): + """Clone the node. This is defined as a shallow copy, except for member lists + amongst the child attributes (from get_child_accessors) which are also + copied. Lists containing child nodes are thus seen as a way for the node + to hold multiple children directly; the list is not treated as a separate + level in the tree.""" + result = copy.copy(self) + for attrname in result.child_attrs: + value = getattr(result, attrname) + if isinstance(value, list): + setattr(result, attrname, [x for x in value]) + return result + + + # + # There are 3 phases of parse tree processing, applied in order to + # all the statements in a given scope-block: + # + # (0) analyse_declarations + # Make symbol table entries for all declarations at the current + # level, both explicit (def, cdef, etc.) and implicit (assignment + # to an otherwise undeclared name). + # + # (1) analyse_expressions + # Determine the result types of expressions and fill in the + # 'type' attribute of each ExprNode. Insert coercion nodes into the + # tree where needed to convert to and from Python objects. + # Allocate temporary locals for intermediate results. Fill + # in the 'result_code' attribute of each ExprNode with a C code + # fragment. + # + # (2) generate_code + # Emit C code for all declarations, statements and expressions. + # Recursively applies the 3 processing phases to the bodies of + # functions. + # + + def analyse_declarations(self, env): + pass + + def analyse_expressions(self, env): + raise InternalError("analyse_expressions not implemented for %s" % \ + self.__class__.__name__) + + def generate_code(self, code): + raise InternalError("generate_code not implemented for %s" % \ + self.__class__.__name__) + + def annotate(self, code): + # mro does the wrong thing + if isinstance(self, BlockNode): + self.body.annotate(code) + + def end_pos(self): + try: + return self._end_pos + except AttributeError: + pos = self.pos + if not self.child_attrs: + self._end_pos = pos + return pos + for attr in self.child_attrs: + child = getattr(self, attr) + # Sometimes lists, sometimes nodes + if child is None: + pass + elif isinstance(child, list): + for c in child: + pos = max(pos, c.end_pos()) + else: + pos = max(pos, child.end_pos()) + self._end_pos = pos + return pos + + def dump(self, level=0, filter_out=("pos",), cutoff=100, encountered=None): + """Debug helper method that returns a recursive string representation of this node. + """ + if cutoff == 0: + return "<...nesting level cutoff...>" + if encountered is None: + encountered = set() + if id(self) in encountered: + return "<%s (0x%x) -- already output>" % (self.__class__.__name__, id(self)) + encountered.add(id(self)) + + def dump_child(x, level): + if isinstance(x, Node): + return x.dump(level, filter_out, cutoff-1, encountered) + elif isinstance(x, list): + return "[%s]" % ", ".join([dump_child(item, level) for item in x]) + else: + return repr(x) + + attrs = [(key, value) for key, value in self.__dict__.items() if key not in filter_out] + if len(attrs) == 0: + return "<%s (0x%x)>" % (self.__class__.__name__, id(self)) + else: + indent = " " * level + res = "<%s (0x%x)\n" % (self.__class__.__name__, id(self)) + for key, value in attrs: + res += "%s %s: %s\n" % (indent, key, dump_child(value, level + 1)) + res += "%s>" % indent + return res + + def dump_pos(self, mark_column=False, marker='(#)'): + """Debug helper method that returns the source code context of this node as a string. + """ + if not self.pos: + return u'' + source_desc, line, col = self.pos contents = source_desc.get_lines(encoding='ASCII', error_handling='ignore') - # line numbers start at 1 + # line numbers start at 1 lines = contents[max(0, line-3):line] - current = lines[-1] - if mark_column: - current = current[:col] + marker + current[col:] - lines[-1] = current.rstrip() + u' # <<<<<<<<<<<<<<\n' - lines += contents[line:line+2] - return u'"%s":%d:%d\n%s\n' % ( - source_desc.get_escaped_description(), line, col, u''.join(lines)) - -class CompilerDirectivesNode(Node): - """ - Sets compiler directives for the children nodes - """ - # directives {string:value} A dictionary holding the right value for - # *all* possible directives. - # body Node - child_attrs = ["body"] - - def analyse_declarations(self, env): - old = env.directives - env.directives = self.directives - self.body.analyse_declarations(env) - env.directives = old - - def analyse_expressions(self, env): - old = env.directives - env.directives = self.directives - self.body = self.body.analyse_expressions(env) - env.directives = old - return self - - def generate_function_definitions(self, env, code): - env_old = env.directives - code_old = code.globalstate.directives - code.globalstate.directives = self.directives - self.body.generate_function_definitions(env, code) - env.directives = env_old - code.globalstate.directives = code_old - - def generate_execution_code(self, code): - old = code.globalstate.directives - code.globalstate.directives = self.directives - self.body.generate_execution_code(code) - code.globalstate.directives = old - - def annotate(self, code): - old = code.globalstate.directives - code.globalstate.directives = self.directives - self.body.annotate(code) - code.globalstate.directives = old - -class BlockNode(object): - # Mixin class for nodes representing a declaration block. - - def generate_cached_builtins_decls(self, env, code): - entries = env.global_scope().undeclared_cached_builtins - for entry in entries: - code.globalstate.add_cached_builtin_decl(entry) - del entries[:] - - def generate_lambda_definitions(self, env, code): - for node in env.lambda_defs: - node.generate_function_definitions(env, code) - -class StatListNode(Node): - # stats a list of StatNode - - child_attrs = ["stats"] - + current = lines[-1] + if mark_column: + current = current[:col] + marker + current[col:] + lines[-1] = current.rstrip() + u' # <<<<<<<<<<<<<<\n' + lines += contents[line:line+2] + return u'"%s":%d:%d\n%s\n' % ( + source_desc.get_escaped_description(), line, col, u''.join(lines)) + +class CompilerDirectivesNode(Node): + """ + Sets compiler directives for the children nodes + """ + # directives {string:value} A dictionary holding the right value for + # *all* possible directives. + # body Node + child_attrs = ["body"] + + def analyse_declarations(self, env): + old = env.directives + env.directives = self.directives + self.body.analyse_declarations(env) + env.directives = old + + def analyse_expressions(self, env): + old = env.directives + env.directives = self.directives + self.body = self.body.analyse_expressions(env) + env.directives = old + return self + + def generate_function_definitions(self, env, code): + env_old = env.directives + code_old = code.globalstate.directives + code.globalstate.directives = self.directives + self.body.generate_function_definitions(env, code) + env.directives = env_old + code.globalstate.directives = code_old + + def generate_execution_code(self, code): + old = code.globalstate.directives + code.globalstate.directives = self.directives + self.body.generate_execution_code(code) + code.globalstate.directives = old + + def annotate(self, code): + old = code.globalstate.directives + code.globalstate.directives = self.directives + self.body.annotate(code) + code.globalstate.directives = old + +class BlockNode(object): + # Mixin class for nodes representing a declaration block. + + def generate_cached_builtins_decls(self, env, code): + entries = env.global_scope().undeclared_cached_builtins + for entry in entries: + code.globalstate.add_cached_builtin_decl(entry) + del entries[:] + + def generate_lambda_definitions(self, env, code): + for node in env.lambda_defs: + node.generate_function_definitions(env, code) + +class StatListNode(Node): + # stats a list of StatNode + + child_attrs = ["stats"] + @staticmethod - def create_analysed(pos, env, *args, **kw): - node = StatListNode(pos, *args, **kw) + def create_analysed(pos, env, *args, **kw): + node = StatListNode(pos, *args, **kw) return node # No node-specific analysis needed - - def analyse_declarations(self, env): - #print "StatListNode.analyse_declarations" ### - for stat in self.stats: - stat.analyse_declarations(env) - - def analyse_expressions(self, env): - #print "StatListNode.analyse_expressions" ### + + def analyse_declarations(self, env): + #print "StatListNode.analyse_declarations" ### + for stat in self.stats: + stat.analyse_declarations(env) + + def analyse_expressions(self, env): + #print "StatListNode.analyse_expressions" ### self.stats = [stat.analyse_expressions(env) for stat in self.stats] - return self - - def generate_function_definitions(self, env, code): - #print "StatListNode.generate_function_definitions" ### - for stat in self.stats: - stat.generate_function_definitions(env, code) - - def generate_execution_code(self, code): - #print "StatListNode.generate_execution_code" ### - for stat in self.stats: - code.mark_pos(stat.pos) - stat.generate_execution_code(code) - - def annotate(self, code): - for stat in self.stats: - stat.annotate(code) - - -class StatNode(Node): - # - # Code generation for statements is split into the following subphases: - # - # (1) generate_function_definitions - # Emit C code for the definitions of any structs, - # unions, enums and functions defined in the current - # scope-block. - # - # (2) generate_execution_code - # Emit C code for executable statements. - # - - def generate_function_definitions(self, env, code): - pass - - def generate_execution_code(self, code): - raise InternalError("generate_execution_code not implemented for %s" % \ - self.__class__.__name__) - - -class CDefExternNode(StatNode): + return self + + def generate_function_definitions(self, env, code): + #print "StatListNode.generate_function_definitions" ### + for stat in self.stats: + stat.generate_function_definitions(env, code) + + def generate_execution_code(self, code): + #print "StatListNode.generate_execution_code" ### + for stat in self.stats: + code.mark_pos(stat.pos) + stat.generate_execution_code(code) + + def annotate(self, code): + for stat in self.stats: + stat.annotate(code) + + +class StatNode(Node): + # + # Code generation for statements is split into the following subphases: + # + # (1) generate_function_definitions + # Emit C code for the definitions of any structs, + # unions, enums and functions defined in the current + # scope-block. + # + # (2) generate_execution_code + # Emit C code for executable statements. + # + + def generate_function_definitions(self, env, code): + pass + + def generate_execution_code(self, code): + raise InternalError("generate_execution_code not implemented for %s" % \ + self.__class__.__name__) + + +class CDefExternNode(StatNode): # include_file string or None # verbatim_include string or None # body StatListNode - - child_attrs = ["body"] - - def analyse_declarations(self, env): - old_cinclude_flag = env.in_cinclude - env.in_cinclude = 1 - self.body.analyse_declarations(env) - env.in_cinclude = old_cinclude_flag - + + child_attrs = ["body"] + + def analyse_declarations(self, env): + old_cinclude_flag = env.in_cinclude + env.in_cinclude = 1 + self.body.analyse_declarations(env) + env.in_cinclude = old_cinclude_flag + if self.include_file or self.verbatim_include: # Determine whether include should be late stats = self.body.stats @@ -498,231 +498,231 @@ class CDefExternNode(StatNode): late = all(isinstance(node, CVarDefNode) for node in stats) env.add_include_file(self.include_file, self.verbatim_include, late) - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - def annotate(self, code): - self.body.annotate(code) - - -class CDeclaratorNode(Node): - # Part of a C declaration. - # - # Processing during analyse_declarations phase: - # - # analyse - # Returns (name, type) pair where name is the - # CNameDeclaratorNode of the name being declared - # and type is the type it is being declared as. - # - # calling_convention string Calling convention of CFuncDeclaratorNode - # for which this is a base - - child_attrs = [] - - calling_convention = "" - - def analyse_templates(self): - # Only C++ functions have templates. - return None - - -class CNameDeclaratorNode(CDeclaratorNode): - # name string The Cython name being declared - # cname string or None C name, if specified - # default ExprNode or None the value assigned on declaration - - child_attrs = ['default'] - - default = None - + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + def annotate(self, code): + self.body.annotate(code) + + +class CDeclaratorNode(Node): + # Part of a C declaration. + # + # Processing during analyse_declarations phase: + # + # analyse + # Returns (name, type) pair where name is the + # CNameDeclaratorNode of the name being declared + # and type is the type it is being declared as. + # + # calling_convention string Calling convention of CFuncDeclaratorNode + # for which this is a base + + child_attrs = [] + + calling_convention = "" + + def analyse_templates(self): + # Only C++ functions have templates. + return None + + +class CNameDeclaratorNode(CDeclaratorNode): + # name string The Cython name being declared + # cname string or None C name, if specified + # default ExprNode or None the value assigned on declaration + + child_attrs = ['default'] + + default = None + def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): - if nonempty and self.name == '': - # May have mistaken the name for the type. - if base_type.is_ptr or base_type.is_array or base_type.is_buffer: - error(self.pos, "Missing argument name") - elif base_type.is_void: - error(self.pos, "Use spam() rather than spam(void) to declare a function with no arguments.") - else: - self.name = base_type.declaration_code("", for_display=1, pyrex=1) - base_type = py_object_type - - if base_type.is_fused and env.fused_to_specific: - base_type = base_type.specialize(env.fused_to_specific) - - self.type = base_type - return self, base_type - - -class CPtrDeclaratorNode(CDeclaratorNode): - # base CDeclaratorNode - - child_attrs = ["base"] - + if nonempty and self.name == '': + # May have mistaken the name for the type. + if base_type.is_ptr or base_type.is_array or base_type.is_buffer: + error(self.pos, "Missing argument name") + elif base_type.is_void: + error(self.pos, "Use spam() rather than spam(void) to declare a function with no arguments.") + else: + self.name = base_type.declaration_code("", for_display=1, pyrex=1) + base_type = py_object_type + + if base_type.is_fused and env.fused_to_specific: + base_type = base_type.specialize(env.fused_to_specific) + + self.type = base_type + return self, base_type + + +class CPtrDeclaratorNode(CDeclaratorNode): + # base CDeclaratorNode + + child_attrs = ["base"] + def analyse_templates(self): return self.base.analyse_templates() def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): - if base_type.is_pyobject: + if base_type.is_pyobject: error(self.pos, "Pointer base type cannot be a Python object") - ptr_type = PyrexTypes.c_ptr_type(base_type) + ptr_type = PyrexTypes.c_ptr_type(base_type) return self.base.analyse(ptr_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) + - -class CReferenceDeclaratorNode(CDeclaratorNode): - # base CDeclaratorNode - - child_attrs = ["base"] - +class CReferenceDeclaratorNode(CDeclaratorNode): + # base CDeclaratorNode + + child_attrs = ["base"] + def analyse_templates(self): return self.base.analyse_templates() def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): - if base_type.is_pyobject: + if base_type.is_pyobject: error(self.pos, "Reference base type cannot be a Python object") - ref_type = PyrexTypes.c_ref_type(base_type) + ref_type = PyrexTypes.c_ref_type(base_type) return self.base.analyse(ref_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) - - -class CArrayDeclaratorNode(CDeclaratorNode): - # base CDeclaratorNode - # dimension ExprNode - - child_attrs = ["base", "dimension"] - + + +class CArrayDeclaratorNode(CDeclaratorNode): + # base CDeclaratorNode + # dimension ExprNode + + child_attrs = ["base", "dimension"] + def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): if (base_type.is_cpp_class and base_type.is_template_type()) or base_type.is_cfunction: - from .ExprNodes import TupleNode - if isinstance(self.dimension, TupleNode): - args = self.dimension.args - else: - args = self.dimension, - values = [v.analyse_as_type(env) for v in args] - if None in values: - ix = values.index(None) - error(args[ix].pos, "Template parameter not a type") - base_type = error_type - else: - base_type = base_type.specialize_here(self.pos, values) + from .ExprNodes import TupleNode + if isinstance(self.dimension, TupleNode): + args = self.dimension.args + else: + args = self.dimension, + values = [v.analyse_as_type(env) for v in args] + if None in values: + ix = values.index(None) + error(args[ix].pos, "Template parameter not a type") + base_type = error_type + else: + base_type = base_type.specialize_here(self.pos, values) return self.base.analyse(base_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) - if self.dimension: - self.dimension = self.dimension.analyse_const_expression(env) - if not self.dimension.type.is_int: - error(self.dimension.pos, "Array dimension not integer") - size = self.dimension.get_constant_c_result_code() - if size is not None: - try: - size = int(size) - except ValueError: - # runtime constant? - pass - else: - size = None - if not base_type.is_complete(): + if self.dimension: + self.dimension = self.dimension.analyse_const_expression(env) + if not self.dimension.type.is_int: + error(self.dimension.pos, "Array dimension not integer") + size = self.dimension.get_constant_c_result_code() + if size is not None: + try: + size = int(size) + except ValueError: + # runtime constant? + pass + else: + size = None + if not base_type.is_complete(): error(self.pos, "Array element type '%s' is incomplete" % base_type) - if base_type.is_pyobject: + if base_type.is_pyobject: error(self.pos, "Array element cannot be a Python object") - if base_type.is_cfunction: + if base_type.is_cfunction: error(self.pos, "Array element cannot be a function") - array_type = PyrexTypes.c_array_type(base_type, size) + array_type = PyrexTypes.c_array_type(base_type, size) return self.base.analyse(array_type, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) - - -class CFuncDeclaratorNode(CDeclaratorNode): - # base CDeclaratorNode - # args [CArgDeclNode] - # templates [TemplatePlaceholderType] - # has_varargs boolean - # exception_value ConstNode - # exception_check boolean True if PyErr_Occurred check needed - # nogil boolean Can be called without gil - # with_gil boolean Acquire gil around function body - # is_const_method boolean Whether this is a const method - - child_attrs = ["base", "args", "exception_value"] - - overridable = 0 - optional_arg_count = 0 - is_const_method = 0 - templates = None - - def analyse_templates(self): - if isinstance(self.base, CArrayDeclaratorNode): - from .ExprNodes import TupleNode, NameNode - template_node = self.base.dimension - if isinstance(template_node, TupleNode): - template_nodes = template_node.args - elif isinstance(template_node, NameNode): - template_nodes = [template_node] - else: - error(template_node.pos, "Template arguments must be a list of names") - return None - self.templates = [] - for template in template_nodes: - if isinstance(template, NameNode): - self.templates.append(PyrexTypes.TemplatePlaceholderType(template.name)) - else: - error(template.pos, "Template arguments must be a list of names") - self.base = self.base.base - return self.templates - else: - return None - + + +class CFuncDeclaratorNode(CDeclaratorNode): + # base CDeclaratorNode + # args [CArgDeclNode] + # templates [TemplatePlaceholderType] + # has_varargs boolean + # exception_value ConstNode + # exception_check boolean True if PyErr_Occurred check needed + # nogil boolean Can be called without gil + # with_gil boolean Acquire gil around function body + # is_const_method boolean Whether this is a const method + + child_attrs = ["base", "args", "exception_value"] + + overridable = 0 + optional_arg_count = 0 + is_const_method = 0 + templates = None + + def analyse_templates(self): + if isinstance(self.base, CArrayDeclaratorNode): + from .ExprNodes import TupleNode, NameNode + template_node = self.base.dimension + if isinstance(template_node, TupleNode): + template_nodes = template_node.args + elif isinstance(template_node, NameNode): + template_nodes = [template_node] + else: + error(template_node.pos, "Template arguments must be a list of names") + return None + self.templates = [] + for template in template_nodes: + if isinstance(template, NameNode): + self.templates.append(PyrexTypes.TemplatePlaceholderType(template.name)) + else: + error(template.pos, "Template arguments must be a list of names") + self.base = self.base.base + return self.templates + else: + return None + def analyse(self, return_type, env, nonempty=0, directive_locals=None, visibility=None, in_pxd=False): if directive_locals is None: directive_locals = {} - if nonempty: - nonempty -= 1 - func_type_args = [] - for i, arg_node in enumerate(self.args): - name_declarator, type = arg_node.analyse( + if nonempty: + nonempty -= 1 + func_type_args = [] + for i, arg_node in enumerate(self.args): + name_declarator, type = arg_node.analyse( env, nonempty=nonempty, is_self_arg=(i == 0 and env.is_c_class_scope and 'staticmethod' not in env.directives)) - name = name_declarator.name - if name in directive_locals: - type_node = directive_locals[name] - other_type = type_node.analyse_as_type(env) - if other_type is None: - error(type_node.pos, "Not a type") - elif (type is not PyrexTypes.py_object_type - and not type.same_as(other_type)): - error(self.base.pos, "Signature does not agree with previous declaration") - error(type_node.pos, "Previous declaration here") - else: - type = other_type - if name_declarator.cname: + name = name_declarator.name + if name in directive_locals: + type_node = directive_locals[name] + other_type = type_node.analyse_as_type(env) + if other_type is None: + error(type_node.pos, "Not a type") + elif (type is not PyrexTypes.py_object_type + and not type.same_as(other_type)): + error(self.base.pos, "Signature does not agree with previous declaration") + error(type_node.pos, "Previous declaration here") + else: + type = other_type + if name_declarator.cname: error(self.pos, "Function argument cannot have C name specification") if i == 0 and env.is_c_class_scope and type.is_unspecified: - # fix the type of self - type = env.parent_type - # Turn *[] argument into ** - if type.is_array: - type = PyrexTypes.c_ptr_type(type.base_type) - # Catch attempted C-style func(void) decl - if type.is_void: - error(arg_node.pos, "Use spam() rather than spam(void) to declare a function with no arguments.") - func_type_args.append( - PyrexTypes.CFuncTypeArg(name, type, arg_node.pos)) - if arg_node.default: - self.optional_arg_count += 1 - elif self.optional_arg_count: - error(self.pos, "Non-default argument follows default argument") - - exc_val = None - exc_check = 0 - if self.exception_check == '+': - env.add_include_file('ios') # for std::ios_base::failure - env.add_include_file('new') # for std::bad_alloc - env.add_include_file('stdexcept') - env.add_include_file('typeinfo') # for std::bad_cast - if (return_type.is_pyobject - and (self.exception_value or self.exception_check) - and self.exception_check != '+'): + # fix the type of self + type = env.parent_type + # Turn *[] argument into ** + if type.is_array: + type = PyrexTypes.c_ptr_type(type.base_type) + # Catch attempted C-style func(void) decl + if type.is_void: + error(arg_node.pos, "Use spam() rather than spam(void) to declare a function with no arguments.") + func_type_args.append( + PyrexTypes.CFuncTypeArg(name, type, arg_node.pos)) + if arg_node.default: + self.optional_arg_count += 1 + elif self.optional_arg_count: + error(self.pos, "Non-default argument follows default argument") + + exc_val = None + exc_check = 0 + if self.exception_check == '+': + env.add_include_file('ios') # for std::ios_base::failure + env.add_include_file('new') # for std::bad_alloc + env.add_include_file('stdexcept') + env.add_include_file('typeinfo') # for std::bad_cast + if (return_type.is_pyobject + and (self.exception_value or self.exception_check) + and self.exception_check != '+'): error(self.pos, "Exception clause not allowed for function returning Python object") - else: + else: if self.exception_value is None and self.exception_check and self.exception_check != '+': # Use an explicit exception return value to speed up exception checks. # Even if it is not declared, we can use the default exception value of the return type, @@ -733,489 +733,489 @@ class CFuncDeclaratorNode(CDeclaratorNode): from .ExprNodes import ConstNode self.exception_value = ConstNode( self.pos, value=return_type.exception_value, type=return_type) - if self.exception_value: - self.exception_value = self.exception_value.analyse_const_expression(env) - if self.exception_check == '+': - exc_val_type = self.exception_value.type - if (not exc_val_type.is_error - and not exc_val_type.is_pyobject - and not (exc_val_type.is_cfunction - and not exc_val_type.return_type.is_pyobject + if self.exception_value: + self.exception_value = self.exception_value.analyse_const_expression(env) + if self.exception_check == '+': + exc_val_type = self.exception_value.type + if (not exc_val_type.is_error + and not exc_val_type.is_pyobject + and not (exc_val_type.is_cfunction + and not exc_val_type.return_type.is_pyobject and not exc_val_type.args) and not (exc_val_type == PyrexTypes.c_char_type and self.exception_value.value == '*')): - error(self.exception_value.pos, + error(self.exception_value.pos, "Exception value must be a Python exception or cdef function with no arguments or *.") - exc_val = self.exception_value - else: - self.exception_value = self.exception_value.coerce_to( - return_type, env).analyse_const_expression(env) - exc_val = self.exception_value.get_constant_c_result_code() - if exc_val is None: - raise InternalError( - "get_constant_c_result_code not implemented for %s" % - self.exception_value.__class__.__name__) - if not return_type.assignable_from(self.exception_value.type): - error(self.exception_value.pos, - "Exception value incompatible with function return type") - exc_check = self.exception_check - if return_type.is_cfunction: + exc_val = self.exception_value + else: + self.exception_value = self.exception_value.coerce_to( + return_type, env).analyse_const_expression(env) + exc_val = self.exception_value.get_constant_c_result_code() + if exc_val is None: + raise InternalError( + "get_constant_c_result_code not implemented for %s" % + self.exception_value.__class__.__name__) + if not return_type.assignable_from(self.exception_value.type): + error(self.exception_value.pos, + "Exception value incompatible with function return type") + exc_check = self.exception_check + if return_type.is_cfunction: error(self.pos, "Function cannot return a function") - func_type = PyrexTypes.CFuncType( - return_type, func_type_args, self.has_varargs, + func_type = PyrexTypes.CFuncType( + return_type, func_type_args, self.has_varargs, optional_arg_count=self.optional_arg_count, exception_value=exc_val, exception_check=exc_check, calling_convention=self.base.calling_convention, nogil=self.nogil, with_gil=self.with_gil, is_overridable=self.overridable, is_const_method=self.is_const_method, templates=self.templates) - - if self.optional_arg_count: - if func_type.is_fused: - # This is a bit of a hack... When we need to create specialized CFuncTypes - # on the fly because the cdef is defined in a pxd, we need to declare the specialized optional arg - # struct - def declare_opt_arg_struct(func_type, fused_cname): - self.declare_optional_arg_struct(func_type, env, fused_cname) - - func_type.declare_opt_arg_struct = declare_opt_arg_struct - else: - self.declare_optional_arg_struct(func_type, env) - - callspec = env.directives['callspec'] - if callspec: - current = func_type.calling_convention - if current and current != callspec: - error(self.pos, "cannot have both '%s' and '%s' " - "calling conventions" % (current, callspec)) - func_type.calling_convention = callspec + + if self.optional_arg_count: + if func_type.is_fused: + # This is a bit of a hack... When we need to create specialized CFuncTypes + # on the fly because the cdef is defined in a pxd, we need to declare the specialized optional arg + # struct + def declare_opt_arg_struct(func_type, fused_cname): + self.declare_optional_arg_struct(func_type, env, fused_cname) + + func_type.declare_opt_arg_struct = declare_opt_arg_struct + else: + self.declare_optional_arg_struct(func_type, env) + + callspec = env.directives['callspec'] + if callspec: + current = func_type.calling_convention + if current and current != callspec: + error(self.pos, "cannot have both '%s' and '%s' " + "calling conventions" % (current, callspec)) + func_type.calling_convention = callspec return self.base.analyse(func_type, env, visibility=visibility, in_pxd=in_pxd) - - def declare_optional_arg_struct(self, func_type, env, fused_cname=None): - """ - Declares the optional argument struct (the struct used to hold the - values for optional arguments). For fused cdef functions, this is - deferred as analyse_declarations is called only once (on the fused - cdef function). - """ - scope = StructOrUnionScope() - arg_count_member = '%sn' % Naming.pyrex_prefix - scope.declare_var(arg_count_member, PyrexTypes.c_int_type, self.pos) - + + def declare_optional_arg_struct(self, func_type, env, fused_cname=None): + """ + Declares the optional argument struct (the struct used to hold the + values for optional arguments). For fused cdef functions, this is + deferred as analyse_declarations is called only once (on the fused + cdef function). + """ + scope = StructOrUnionScope() + arg_count_member = '%sn' % Naming.pyrex_prefix + scope.declare_var(arg_count_member, PyrexTypes.c_int_type, self.pos) + for arg in func_type.args[len(func_type.args) - self.optional_arg_count:]: scope.declare_var(arg.name, arg.type, arg.pos, allow_pyobject=True, allow_memoryview=True) - - struct_cname = env.mangle(Naming.opt_arg_prefix, self.base.name) - - if fused_cname is not None: - struct_cname = PyrexTypes.get_fused_cname(fused_cname, struct_cname) - - op_args_struct = env.global_scope().declare_struct_or_union( + + struct_cname = env.mangle(Naming.opt_arg_prefix, self.base.name) + + if fused_cname is not None: + struct_cname = PyrexTypes.get_fused_cname(fused_cname, struct_cname) + + op_args_struct = env.global_scope().declare_struct_or_union( name=struct_cname, kind='struct', scope=scope, typedef_flag=0, pos=self.pos, cname=struct_cname) - - op_args_struct.defined_in_pxd = 1 - op_args_struct.used = 1 - - func_type.op_arg_struct = PyrexTypes.c_ptr_type(op_args_struct.type) - - -class CConstDeclaratorNode(CDeclaratorNode): - # base CDeclaratorNode - - child_attrs = ["base"] - + + op_args_struct.defined_in_pxd = 1 + op_args_struct.used = 1 + + func_type.op_arg_struct = PyrexTypes.c_ptr_type(op_args_struct.type) + + +class CConstDeclaratorNode(CDeclaratorNode): + # base CDeclaratorNode + + child_attrs = ["base"] + def analyse(self, base_type, env, nonempty=0, visibility=None, in_pxd=False): - if base_type.is_pyobject: - error(self.pos, - "Const base type cannot be a Python object") - const = PyrexTypes.c_const_type(base_type) + if base_type.is_pyobject: + error(self.pos, + "Const base type cannot be a Python object") + const = PyrexTypes.c_const_type(base_type) return self.base.analyse(const, env, nonempty=nonempty, visibility=visibility, in_pxd=in_pxd) - - -class CArgDeclNode(Node): - # Item in a function declaration argument list. - # - # base_type CBaseTypeNode - # declarator CDeclaratorNode - # not_none boolean Tagged with 'not None' - # or_none boolean Tagged with 'or None' - # accept_none boolean Resolved boolean for not_none/or_none - # default ExprNode or None - # default_value PyObjectConst constant for default value - # annotation ExprNode or None Py3 function arg annotation - # is_self_arg boolean Is the "self" arg of an extension type method - # is_type_arg boolean Is the "class" arg of an extension type classmethod - # is_kw_only boolean Is a keyword-only argument - # is_dynamic boolean Non-literal arg stored inside CyFunction - - child_attrs = ["base_type", "declarator", "default", "annotation"] + + +class CArgDeclNode(Node): + # Item in a function declaration argument list. + # + # base_type CBaseTypeNode + # declarator CDeclaratorNode + # not_none boolean Tagged with 'not None' + # or_none boolean Tagged with 'or None' + # accept_none boolean Resolved boolean for not_none/or_none + # default ExprNode or None + # default_value PyObjectConst constant for default value + # annotation ExprNode or None Py3 function arg annotation + # is_self_arg boolean Is the "self" arg of an extension type method + # is_type_arg boolean Is the "class" arg of an extension type classmethod + # is_kw_only boolean Is a keyword-only argument + # is_dynamic boolean Non-literal arg stored inside CyFunction + + child_attrs = ["base_type", "declarator", "default", "annotation"] outer_attrs = ["default", "annotation"] - - is_self_arg = 0 - is_type_arg = 0 - is_generic = 1 - kw_only = 0 - not_none = 0 - or_none = 0 - type = None - name_declarator = None - default_value = None - annotation = None - is_dynamic = 0 - + + is_self_arg = 0 + is_type_arg = 0 + is_generic = 1 + kw_only = 0 + not_none = 0 + or_none = 0 + type = None + name_declarator = None + default_value = None + annotation = None + is_dynamic = 0 + def analyse(self, env, nonempty=0, is_self_arg=False): - if is_self_arg: - self.base_type.is_self_arg = self.is_self_arg = True - if self.type is None: - # The parser may misinterpret names as types. We fix that here. - if isinstance(self.declarator, CNameDeclaratorNode) and self.declarator.name == '': - if nonempty: - if self.base_type.is_basic_c_type: - # char, short, long called "int" - type = self.base_type.analyse(env, could_be_name=True) + if is_self_arg: + self.base_type.is_self_arg = self.is_self_arg = True + if self.type is None: + # The parser may misinterpret names as types. We fix that here. + if isinstance(self.declarator, CNameDeclaratorNode) and self.declarator.name == '': + if nonempty: + if self.base_type.is_basic_c_type: + # char, short, long called "int" + type = self.base_type.analyse(env, could_be_name=True) arg_name = type.empty_declaration_code() - else: - arg_name = self.base_type.name - self.declarator.name = EncodedString(arg_name) - self.base_type.name = None - self.base_type.is_basic_c_type = False - could_be_name = True - else: - could_be_name = False - self.base_type.is_arg = True - base_type = self.base_type.analyse(env, could_be_name=could_be_name) - if hasattr(self.base_type, 'arg_name') and self.base_type.arg_name: - self.declarator.name = self.base_type.arg_name - - # The parser is unable to resolve the ambiguity of [] as part of the - # type (e.g. in buffers) or empty declarator (as with arrays). - # This is only arises for empty multi-dimensional arrays. - if (base_type.is_array - and isinstance(self.base_type, TemplatedTypeNode) - and isinstance(self.declarator, CArrayDeclaratorNode)): - declarator = self.declarator - while isinstance(declarator.base, CArrayDeclaratorNode): - declarator = declarator.base - declarator.base = self.base_type.array_declarator - base_type = base_type.base_type - - # inject type declaration from annotations + else: + arg_name = self.base_type.name + self.declarator.name = EncodedString(arg_name) + self.base_type.name = None + self.base_type.is_basic_c_type = False + could_be_name = True + else: + could_be_name = False + self.base_type.is_arg = True + base_type = self.base_type.analyse(env, could_be_name=could_be_name) + if hasattr(self.base_type, 'arg_name') and self.base_type.arg_name: + self.declarator.name = self.base_type.arg_name + + # The parser is unable to resolve the ambiguity of [] as part of the + # type (e.g. in buffers) or empty declarator (as with arrays). + # This is only arises for empty multi-dimensional arrays. + if (base_type.is_array + and isinstance(self.base_type, TemplatedTypeNode) + and isinstance(self.declarator, CArrayDeclaratorNode)): + declarator = self.declarator + while isinstance(declarator.base, CArrayDeclaratorNode): + declarator = declarator.base + declarator.base = self.base_type.array_declarator + base_type = base_type.base_type + + # inject type declaration from annotations # this is called without 'env' by AdjustDefByDirectives transform before declaration analysis if self.annotation and env and env.directives['annotation_typing'] and self.base_type.name is None: - arg_type = self.inject_type_from_annotations(env) - if arg_type is not None: - base_type = arg_type - return self.declarator.analyse(base_type, env, nonempty=nonempty) - else: - return self.name_declarator, self.type - - def inject_type_from_annotations(self, env): - annotation = self.annotation - if not annotation: - return None + arg_type = self.inject_type_from_annotations(env) + if arg_type is not None: + base_type = arg_type + return self.declarator.analyse(base_type, env, nonempty=nonempty) + else: + return self.name_declarator, self.type + + def inject_type_from_annotations(self, env): + annotation = self.annotation + if not annotation: + return None base_type, arg_type = analyse_type_annotation(annotation, env, assigned_value=self.default) if base_type is not None: self.base_type = base_type - return arg_type - - def calculate_default_value_code(self, code): - if self.default_value is None: - if self.default: - if self.default.is_literal: - # will not output any code, just assign the result_code - self.default.generate_evaluation_code(code) - return self.type.cast_code(self.default.result()) - self.default_value = code.get_argument_default_const(self.type) - return self.default_value - - def annotate(self, code): - if self.default: - self.default.annotate(code) - + return arg_type + + def calculate_default_value_code(self, code): + if self.default_value is None: + if self.default: + if self.default.is_literal: + # will not output any code, just assign the result_code + self.default.generate_evaluation_code(code) + return self.type.cast_code(self.default.result()) + self.default_value = code.get_argument_default_const(self.type) + return self.default_value + + def annotate(self, code): + if self.default: + self.default.annotate(code) + def generate_assignment_code(self, code, target=None, overloaded_assignment=False): - default = self.default - if default is None or default.is_literal: - return - if target is None: - target = self.calculate_default_value_code(code) - default.generate_evaluation_code(code) - default.make_owned_reference(code) + default = self.default + if default is None or default.is_literal: + return + if target is None: + target = self.calculate_default_value_code(code) + default.generate_evaluation_code(code) + default.make_owned_reference(code) result = default.result() if overloaded_assignment else default.result_as(self.type) - code.putln("%s = %s;" % (target, result)) - if self.type.is_pyobject: - code.put_giveref(default.result()) - default.generate_post_assignment_code(code) - default.free_temps(code) - - -class CBaseTypeNode(Node): - # Abstract base class for C base type nodes. - # - # Processing during analyse_declarations phase: - # - # analyse - # Returns the type. - - def analyse_as_type(self, env): - return self.analyse(env) - - -class CAnalysedBaseTypeNode(Node): - # type type - - child_attrs = [] - + code.putln("%s = %s;" % (target, result)) + if self.type.is_pyobject: + code.put_giveref(default.result()) + default.generate_post_assignment_code(code) + default.free_temps(code) + + +class CBaseTypeNode(Node): + # Abstract base class for C base type nodes. + # + # Processing during analyse_declarations phase: + # + # analyse + # Returns the type. + + def analyse_as_type(self, env): + return self.analyse(env) + + +class CAnalysedBaseTypeNode(Node): + # type type + + child_attrs = [] + def analyse(self, env, could_be_name=False): - return self.type - - -class CSimpleBaseTypeNode(CBaseTypeNode): - # name string - # module_path [string] Qualifying name components - # is_basic_c_type boolean - # signed boolean - # longness integer - # complex boolean - # is_self_arg boolean Is self argument of C method - # ##is_type_arg boolean Is type argument of class method - - child_attrs = [] - arg_name = None # in case the argument name was interpreted as a type - module_path = [] - is_basic_c_type = False - complex = False - + return self.type + + +class CSimpleBaseTypeNode(CBaseTypeNode): + # name string + # module_path [string] Qualifying name components + # is_basic_c_type boolean + # signed boolean + # longness integer + # complex boolean + # is_self_arg boolean Is self argument of C method + # ##is_type_arg boolean Is type argument of class method + + child_attrs = [] + arg_name = None # in case the argument name was interpreted as a type + module_path = [] + is_basic_c_type = False + complex = False + def analyse(self, env, could_be_name=False): - # Return type descriptor. - #print "CSimpleBaseTypeNode.analyse: is_self_arg =", self.is_self_arg ### - type = None - if self.is_basic_c_type: - type = PyrexTypes.simple_c_type(self.signed, self.longness, self.name) - if not type: - error(self.pos, "Unrecognised type modifier combination") - elif self.name == "object" and not self.module_path: - type = py_object_type - elif self.name is None: - if self.is_self_arg and env.is_c_class_scope: - #print "CSimpleBaseTypeNode.analyse: defaulting to parent type" ### - type = env.parent_type - ## elif self.is_type_arg and env.is_c_class_scope: - ## type = Builtin.type_type - else: - type = py_object_type - else: - if self.module_path: - # Maybe it's a nested C++ class. - scope = env - for item in self.module_path: - entry = scope.lookup(item) - if entry is not None and entry.is_cpp_class: - scope = entry.type.scope - else: - scope = None - break - - if scope is None: - # Maybe it's a cimport. - scope = env.find_imported_module(self.module_path, self.pos) - else: - scope = env - - if scope: - if scope.is_c_class_scope: - scope = scope.global_scope() - - type = scope.lookup_type(self.name) - if type is not None: - pass - elif could_be_name: - if self.is_self_arg and env.is_c_class_scope: - type = env.parent_type - ## elif self.is_type_arg and env.is_c_class_scope: - ## type = Builtin.type_type - else: - type = py_object_type - self.arg_name = EncodedString(self.name) - else: - if self.templates: - if not self.name in self.templates: - error(self.pos, "'%s' is not a type identifier" % self.name) - type = PyrexTypes.TemplatePlaceholderType(self.name) - else: - error(self.pos, "'%s' is not a type identifier" % self.name) + # Return type descriptor. + #print "CSimpleBaseTypeNode.analyse: is_self_arg =", self.is_self_arg ### + type = None + if self.is_basic_c_type: + type = PyrexTypes.simple_c_type(self.signed, self.longness, self.name) + if not type: + error(self.pos, "Unrecognised type modifier combination") + elif self.name == "object" and not self.module_path: + type = py_object_type + elif self.name is None: + if self.is_self_arg and env.is_c_class_scope: + #print "CSimpleBaseTypeNode.analyse: defaulting to parent type" ### + type = env.parent_type + ## elif self.is_type_arg and env.is_c_class_scope: + ## type = Builtin.type_type + else: + type = py_object_type + else: + if self.module_path: + # Maybe it's a nested C++ class. + scope = env + for item in self.module_path: + entry = scope.lookup(item) + if entry is not None and entry.is_cpp_class: + scope = entry.type.scope + else: + scope = None + break + + if scope is None: + # Maybe it's a cimport. + scope = env.find_imported_module(self.module_path, self.pos) + else: + scope = env + + if scope: + if scope.is_c_class_scope: + scope = scope.global_scope() + + type = scope.lookup_type(self.name) + if type is not None: + pass + elif could_be_name: + if self.is_self_arg and env.is_c_class_scope: + type = env.parent_type + ## elif self.is_type_arg and env.is_c_class_scope: + ## type = Builtin.type_type + else: + type = py_object_type + self.arg_name = EncodedString(self.name) + else: + if self.templates: + if not self.name in self.templates: + error(self.pos, "'%s' is not a type identifier" % self.name) + type = PyrexTypes.TemplatePlaceholderType(self.name) + else: + error(self.pos, "'%s' is not a type identifier" % self.name) if type and type.is_fused and env.fused_to_specific: type = type.specialize(env.fused_to_specific) - if self.complex: - if not type.is_numeric or type.is_complex: - error(self.pos, "can only complexify c numeric types") - type = PyrexTypes.CComplexType(type) - type.create_declaration_utility_code(env) - elif type is Builtin.complex_type: - # Special case: optimise builtin complex type into C's - # double complex. The parser cannot do this (as for the - # normal scalar types) as the user may have redeclared the - # 'complex' type. Testing for the exact type here works. - type = PyrexTypes.c_double_complex_type - type.create_declaration_utility_code(env) - self.complex = True - if type: - return type - else: - return PyrexTypes.error_type - -class MemoryViewSliceTypeNode(CBaseTypeNode): - - name = 'memoryview' - child_attrs = ['base_type_node', 'axes'] - + if self.complex: + if not type.is_numeric or type.is_complex: + error(self.pos, "can only complexify c numeric types") + type = PyrexTypes.CComplexType(type) + type.create_declaration_utility_code(env) + elif type is Builtin.complex_type: + # Special case: optimise builtin complex type into C's + # double complex. The parser cannot do this (as for the + # normal scalar types) as the user may have redeclared the + # 'complex' type. Testing for the exact type here works. + type = PyrexTypes.c_double_complex_type + type.create_declaration_utility_code(env) + self.complex = True + if type: + return type + else: + return PyrexTypes.error_type + +class MemoryViewSliceTypeNode(CBaseTypeNode): + + name = 'memoryview' + child_attrs = ['base_type_node', 'axes'] + def analyse(self, env, could_be_name=False): - - base_type = self.base_type_node.analyse(env) - if base_type.is_error: return base_type - - from . import MemoryView - - try: - axes_specs = MemoryView.get_axes_specs(env, self.axes) + + base_type = self.base_type_node.analyse(env) + if base_type.is_error: return base_type + + from . import MemoryView + + try: + axes_specs = MemoryView.get_axes_specs(env, self.axes) except CompileError as e: - error(e.position, e.message_only) - self.type = PyrexTypes.ErrorType() - return self.type - - if not MemoryView.validate_axes(self.pos, axes_specs): - self.type = error_type - else: - self.type = PyrexTypes.MemoryViewSliceType(base_type, axes_specs) + error(e.position, e.message_only) + self.type = PyrexTypes.ErrorType() + return self.type + + if not MemoryView.validate_axes(self.pos, axes_specs): + self.type = error_type + else: + self.type = PyrexTypes.MemoryViewSliceType(base_type, axes_specs) self.type.validate_memslice_dtype(self.pos) - self.use_memview_utilities(env) - - return self.type - - def use_memview_utilities(self, env): - from . import MemoryView - env.use_utility_code(MemoryView.view_utility_code) - - -class CNestedBaseTypeNode(CBaseTypeNode): - # For C++ classes that live inside other C++ classes. - - # name string - # base_type CBaseTypeNode - - child_attrs = ['base_type'] - + self.use_memview_utilities(env) + + return self.type + + def use_memview_utilities(self, env): + from . import MemoryView + env.use_utility_code(MemoryView.view_utility_code) + + +class CNestedBaseTypeNode(CBaseTypeNode): + # For C++ classes that live inside other C++ classes. + + # name string + # base_type CBaseTypeNode + + child_attrs = ['base_type'] + def analyse(self, env, could_be_name=None): - base_type = self.base_type.analyse(env) - if base_type is PyrexTypes.error_type: - return PyrexTypes.error_type - if not base_type.is_cpp_class: - error(self.pos, "'%s' is not a valid type scope" % base_type) - return PyrexTypes.error_type - type_entry = base_type.scope.lookup_here(self.name) - if not type_entry or not type_entry.is_type: - error(self.pos, "'%s.%s' is not a type identifier" % (base_type, self.name)) - return PyrexTypes.error_type - return type_entry.type - - -class TemplatedTypeNode(CBaseTypeNode): - # After parsing: - # positional_args [ExprNode] List of positional arguments - # keyword_args DictNode Keyword arguments - # base_type_node CBaseTypeNode - - # After analysis: - # type PyrexTypes.BufferType or PyrexTypes.CppClassType ...containing the right options - - child_attrs = ["base_type_node", "positional_args", - "keyword_args", "dtype_node"] - - dtype_node = None - - name = None - + base_type = self.base_type.analyse(env) + if base_type is PyrexTypes.error_type: + return PyrexTypes.error_type + if not base_type.is_cpp_class: + error(self.pos, "'%s' is not a valid type scope" % base_type) + return PyrexTypes.error_type + type_entry = base_type.scope.lookup_here(self.name) + if not type_entry or not type_entry.is_type: + error(self.pos, "'%s.%s' is not a type identifier" % (base_type, self.name)) + return PyrexTypes.error_type + return type_entry.type + + +class TemplatedTypeNode(CBaseTypeNode): + # After parsing: + # positional_args [ExprNode] List of positional arguments + # keyword_args DictNode Keyword arguments + # base_type_node CBaseTypeNode + + # After analysis: + # type PyrexTypes.BufferType or PyrexTypes.CppClassType ...containing the right options + + child_attrs = ["base_type_node", "positional_args", + "keyword_args", "dtype_node"] + + dtype_node = None + + name = None + def analyse(self, env, could_be_name=False, base_type=None): - if base_type is None: - base_type = self.base_type_node.analyse(env) - if base_type.is_error: return base_type - + if base_type is None: + base_type = self.base_type_node.analyse(env) + if base_type.is_error: return base_type + if base_type.is_cpp_class and base_type.is_template_type(): - # Templated class - if self.keyword_args and self.keyword_args.key_value_pairs: - error(self.pos, "c++ templates cannot take keyword arguments") - self.type = PyrexTypes.error_type - else: - template_types = [] - for template_node in self.positional_args: - type = template_node.analyse_as_type(env) - if type is None: - error(template_node.pos, "unknown type in template argument") + # Templated class + if self.keyword_args and self.keyword_args.key_value_pairs: + error(self.pos, "c++ templates cannot take keyword arguments") + self.type = PyrexTypes.error_type + else: + template_types = [] + for template_node in self.positional_args: + type = template_node.analyse_as_type(env) + if type is None: + error(template_node.pos, "unknown type in template argument") type = error_type - template_types.append(type) - self.type = base_type.specialize_here(self.pos, template_types) - - elif base_type.is_pyobject: - # Buffer - from . import Buffer - - options = Buffer.analyse_buffer_options( - self.pos, - env, - self.positional_args, - self.keyword_args, - base_type.buffer_defaults) - - if sys.version_info[0] < 3: - # Py 2.x enforces byte strings as keyword arguments ... + template_types.append(type) + self.type = base_type.specialize_here(self.pos, template_types) + + elif base_type.is_pyobject: + # Buffer + from . import Buffer + + options = Buffer.analyse_buffer_options( + self.pos, + env, + self.positional_args, + self.keyword_args, + base_type.buffer_defaults) + + if sys.version_info[0] < 3: + # Py 2.x enforces byte strings as keyword arguments ... options = dict([(name.encode('ASCII'), value) for name, value in options.items()]) - - self.type = PyrexTypes.BufferType(base_type, **options) + + self.type = PyrexTypes.BufferType(base_type, **options) if has_np_pythran(env) and is_pythran_buffer(self.type): self.type = PyrexTypes.PythranExpr(pythran_type(self.type), self.type) - - else: - # Array - empty_declarator = CNameDeclaratorNode(self.pos, name="", cname=None) - if len(self.positional_args) > 1 or self.keyword_args.key_value_pairs: - error(self.pos, "invalid array declaration") - self.type = PyrexTypes.error_type - else: - # It would be nice to merge this class with CArrayDeclaratorNode, - # but arrays are part of the declaration, not the type... - if not self.positional_args: - dimension = None - else: - dimension = self.positional_args[0] + + else: + # Array + empty_declarator = CNameDeclaratorNode(self.pos, name="", cname=None) + if len(self.positional_args) > 1 or self.keyword_args.key_value_pairs: + error(self.pos, "invalid array declaration") + self.type = PyrexTypes.error_type + else: + # It would be nice to merge this class with CArrayDeclaratorNode, + # but arrays are part of the declaration, not the type... + if not self.positional_args: + dimension = None + else: + dimension = self.positional_args[0] self.array_declarator = CArrayDeclaratorNode( self.pos, base=empty_declarator, dimension=dimension) - self.type = self.array_declarator.analyse(base_type, env)[1] - - if self.type.is_fused and env.fused_to_specific: - self.type = self.type.specialize(env.fused_to_specific) - - return self.type - - -class CComplexBaseTypeNode(CBaseTypeNode): - # base_type CBaseTypeNode - # declarator CDeclaratorNode - - child_attrs = ["base_type", "declarator"] - + self.type = self.array_declarator.analyse(base_type, env)[1] + + if self.type.is_fused and env.fused_to_specific: + self.type = self.type.specialize(env.fused_to_specific) + + return self.type + + +class CComplexBaseTypeNode(CBaseTypeNode): + # base_type CBaseTypeNode + # declarator CDeclaratorNode + + child_attrs = ["base_type", "declarator"] + def analyse(self, env, could_be_name=False): - base = self.base_type.analyse(env, could_be_name) - _, type = self.declarator.analyse(base, env) - return type - - + base = self.base_type.analyse(env, could_be_name) + _, type = self.declarator.analyse(base, env) + return type + + class CTupleBaseTypeNode(CBaseTypeNode): # components [CBaseTypeNode] @@ -1234,456 +1234,456 @@ class CTupleBaseTypeNode(CBaseTypeNode): return entry.type -class FusedTypeNode(CBaseTypeNode): - """ - Represents a fused type in a ctypedef statement: - - ctypedef cython.fused_type(int, long, long long) integral - - name str name of this fused type - types [CSimpleBaseTypeNode] is the list of types to be fused - """ - - child_attrs = [] - - def analyse_declarations(self, env): - type = self.analyse(env) - entry = env.declare_typedef(self.name, type, self.pos) - - # Omit the typedef declaration that self.declarator would produce - entry.in_cinclude = True - +class FusedTypeNode(CBaseTypeNode): + """ + Represents a fused type in a ctypedef statement: + + ctypedef cython.fused_type(int, long, long long) integral + + name str name of this fused type + types [CSimpleBaseTypeNode] is the list of types to be fused + """ + + child_attrs = [] + + def analyse_declarations(self, env): + type = self.analyse(env) + entry = env.declare_typedef(self.name, type, self.pos) + + # Omit the typedef declaration that self.declarator would produce + entry.in_cinclude = True + def analyse(self, env, could_be_name=False): - types = [] - for type_node in self.types: - type = type_node.analyse_as_type(env) - - if not type: - error(type_node.pos, "Not a type") - continue - - if type in types: - error(type_node.pos, "Type specified multiple times") - else: - types.append(type) - - # if len(self.types) == 1: - # return types[0] - - return PyrexTypes.FusedType(types, name=self.name) - - -class CConstTypeNode(CBaseTypeNode): - # base_type CBaseTypeNode - - child_attrs = ["base_type"] - + types = [] + for type_node in self.types: + type = type_node.analyse_as_type(env) + + if not type: + error(type_node.pos, "Not a type") + continue + + if type in types: + error(type_node.pos, "Type specified multiple times") + else: + types.append(type) + + # if len(self.types) == 1: + # return types[0] + + return PyrexTypes.FusedType(types, name=self.name) + + +class CConstTypeNode(CBaseTypeNode): + # base_type CBaseTypeNode + + child_attrs = ["base_type"] + def analyse(self, env, could_be_name=False): - base = self.base_type.analyse(env, could_be_name) - if base.is_pyobject: - error(self.pos, - "Const base type cannot be a Python object") - return PyrexTypes.c_const_type(base) - - -class CVarDefNode(StatNode): - # C variable definition or forward/extern function declaration. - # - # visibility 'private' or 'public' or 'extern' - # base_type CBaseTypeNode - # declarators [CDeclaratorNode] - # in_pxd boolean - # api boolean - # overridable boolean whether it is a cpdef - # modifiers ['inline'] - - # decorators [cython.locals(...)] or None - # directive_locals { string : NameNode } locals defined by cython.locals(...) - - child_attrs = ["base_type", "declarators"] - - decorators = None - directive_locals = None - + base = self.base_type.analyse(env, could_be_name) + if base.is_pyobject: + error(self.pos, + "Const base type cannot be a Python object") + return PyrexTypes.c_const_type(base) + + +class CVarDefNode(StatNode): + # C variable definition or forward/extern function declaration. + # + # visibility 'private' or 'public' or 'extern' + # base_type CBaseTypeNode + # declarators [CDeclaratorNode] + # in_pxd boolean + # api boolean + # overridable boolean whether it is a cpdef + # modifiers ['inline'] + + # decorators [cython.locals(...)] or None + # directive_locals { string : NameNode } locals defined by cython.locals(...) + + child_attrs = ["base_type", "declarators"] + + decorators = None + directive_locals = None + def analyse_declarations(self, env, dest_scope=None): - if self.directive_locals is None: - self.directive_locals = {} - if not dest_scope: - dest_scope = env - self.dest_scope = dest_scope - - if self.declarators: - templates = self.declarators[0].analyse_templates() - else: - templates = None - if templates is not None: - if self.visibility != 'extern': - error(self.pos, "Only extern functions allowed") - if len(self.declarators) > 1: - error(self.declarators[1].pos, "Can't multiply declare template types") - env = TemplateScope('func_template', env) - env.directives = env.outer_scope.directives - for template_param in templates: - env.declare_type(template_param.name, template_param, self.pos) - - base_type = self.base_type.analyse(env) - - if base_type.is_fused and not self.in_pxd and (env.is_c_class_scope or - env.is_module_scope): - error(self.pos, "Fused types not allowed here") - return error_type - - self.entry = None - visibility = self.visibility - - for declarator in self.declarators: - - if (len(self.declarators) > 1 + if self.directive_locals is None: + self.directive_locals = {} + if not dest_scope: + dest_scope = env + self.dest_scope = dest_scope + + if self.declarators: + templates = self.declarators[0].analyse_templates() + else: + templates = None + if templates is not None: + if self.visibility != 'extern': + error(self.pos, "Only extern functions allowed") + if len(self.declarators) > 1: + error(self.declarators[1].pos, "Can't multiply declare template types") + env = TemplateScope('func_template', env) + env.directives = env.outer_scope.directives + for template_param in templates: + env.declare_type(template_param.name, template_param, self.pos) + + base_type = self.base_type.analyse(env) + + if base_type.is_fused and not self.in_pxd and (env.is_c_class_scope or + env.is_module_scope): + error(self.pos, "Fused types not allowed here") + return error_type + + self.entry = None + visibility = self.visibility + + for declarator in self.declarators: + + if (len(self.declarators) > 1 and not isinstance(declarator, CNameDeclaratorNode) and env.directives['warn.multiple_declarators']): warning( declarator.pos, "Non-trivial type declarators in shared declaration (e.g. mix of pointers and values). " - "Each pointer declaration should be on its own line.", 1) - + "Each pointer declaration should be on its own line.", 1) + create_extern_wrapper = (self.overridable and self.visibility == 'extern' and env.is_module_scope) if create_extern_wrapper: declarator.overridable = False - if isinstance(declarator, CFuncDeclaratorNode): + if isinstance(declarator, CFuncDeclaratorNode): name_declarator, type = declarator.analyse( base_type, env, directive_locals=self.directive_locals, visibility=visibility, in_pxd=self.in_pxd) - else: + else: name_declarator, type = declarator.analyse( base_type, env, visibility=visibility, in_pxd=self.in_pxd) - if not type.is_complete(): - if not (self.visibility == 'extern' and type.is_array or type.is_memoryviewslice): + if not type.is_complete(): + if not (self.visibility == 'extern' and type.is_array or type.is_memoryviewslice): error(declarator.pos, "Variable type '%s' is incomplete" % type) - if self.visibility == 'extern' and type.is_pyobject: + if self.visibility == 'extern' and type.is_pyobject: error(declarator.pos, "Python object cannot be declared extern") - name = name_declarator.name - cname = name_declarator.cname - if name == '': - error(declarator.pos, "Missing name in declaration.") - return + name = name_declarator.name + cname = name_declarator.cname + if name == '': + error(declarator.pos, "Missing name in declaration.") + return if type.is_reference and self.visibility != 'extern': error(declarator.pos, "C++ references cannot be declared; use a pointer instead") - if type.is_cfunction: + if type.is_cfunction: if 'staticmethod' in env.directives: type.is_static_method = True self.entry = dest_scope.declare_cfunction( name, type, declarator.pos, cname=cname, visibility=self.visibility, in_pxd=self.in_pxd, api=self.api, modifiers=self.modifiers, overridable=self.overridable) - if self.entry is not None: - self.entry.directive_locals = copy.copy(self.directive_locals) + if self.entry is not None: + self.entry.directive_locals = copy.copy(self.directive_locals) if create_extern_wrapper: self.entry.type.create_to_py_utility_code(env) self.entry.create_wrapper = True - else: + else: if self.overridable: warning(self.pos, "cpdef variables will not be supported in Cython 3; " "currently they are no different from cdef variables", 2) - if self.directive_locals: - error(self.pos, "Decorators can only be followed by functions") + if self.directive_locals: + error(self.pos, "Decorators can only be followed by functions") self.entry = dest_scope.declare_var( name, type, declarator.pos, cname=cname, visibility=visibility, in_pxd=self.in_pxd, api=self.api, is_cdef=1) - if Options.docstrings: - self.entry.doc = embed_position(self.pos, self.doc) - - -class CStructOrUnionDefNode(StatNode): - # name string - # cname string or None - # kind "struct" or "union" - # typedef_flag boolean - # visibility "public" or "private" - # api boolean - # in_pxd boolean - # attributes [CVarDefNode] or None - # entry Entry - # packed boolean - - child_attrs = ["attributes"] - - def declare(self, env, scope=None): - self.entry = env.declare_struct_or_union( - self.name, self.kind, scope, self.typedef_flag, self.pos, + if Options.docstrings: + self.entry.doc = embed_position(self.pos, self.doc) + + +class CStructOrUnionDefNode(StatNode): + # name string + # cname string or None + # kind "struct" or "union" + # typedef_flag boolean + # visibility "public" or "private" + # api boolean + # in_pxd boolean + # attributes [CVarDefNode] or None + # entry Entry + # packed boolean + + child_attrs = ["attributes"] + + def declare(self, env, scope=None): + self.entry = env.declare_struct_or_union( + self.name, self.kind, scope, self.typedef_flag, self.pos, self.cname, visibility=self.visibility, api=self.api, packed=self.packed) - - def analyse_declarations(self, env): - scope = None - if self.attributes is not None: - scope = StructOrUnionScope(self.name) - self.declare(env, scope) - if self.attributes is not None: - if self.in_pxd and not env.in_cinclude: - self.entry.defined_in_pxd = 1 - for attr in self.attributes: - attr.analyse_declarations(env, scope) - if self.visibility != 'extern': - for attr in scope.var_entries: - type = attr.type - while type.is_array: - type = type.base_type - if type == self.entry.type: - error(attr.pos, "Struct cannot contain itself as a member.") - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class CppClassNode(CStructOrUnionDefNode, BlockNode): - - # name string - # cname string or None - # visibility "extern" - # in_pxd boolean - # attributes [CVarDefNode] or None - # entry Entry - # base_classes [CBaseTypeNode] + + def analyse_declarations(self, env): + scope = None + if self.attributes is not None: + scope = StructOrUnionScope(self.name) + self.declare(env, scope) + if self.attributes is not None: + if self.in_pxd and not env.in_cinclude: + self.entry.defined_in_pxd = 1 + for attr in self.attributes: + attr.analyse_declarations(env, scope) + if self.visibility != 'extern': + for attr in scope.var_entries: + type = attr.type + while type.is_array: + type = type.base_type + if type == self.entry.type: + error(attr.pos, "Struct cannot contain itself as a member.") + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class CppClassNode(CStructOrUnionDefNode, BlockNode): + + # name string + # cname string or None + # visibility "extern" + # in_pxd boolean + # attributes [CVarDefNode] or None + # entry Entry + # base_classes [CBaseTypeNode] # templates [(string, bool)] or None - # decorators [DecoratorNode] or None - - decorators = None - - def declare(self, env): - if self.templates is None: - template_types = None - else: + # decorators [DecoratorNode] or None + + decorators = None + + def declare(self, env): + if self.templates is None: + template_types = None + else: template_types = [PyrexTypes.TemplatePlaceholderType(template_name, not required) for template_name, required in self.templates] num_optional_templates = sum(not required for _, required in self.templates) if num_optional_templates and not all(required for _, required in self.templates[:-num_optional_templates]): error(self.pos, "Required template parameters must precede optional template parameters.") - self.entry = env.declare_cpp_class( + self.entry = env.declare_cpp_class( self.name, None, self.pos, self.cname, base_classes=[], visibility=self.visibility, templates=template_types) - - def analyse_declarations(self, env): + + def analyse_declarations(self, env): if self.templates is None: template_types = template_names = None else: template_names = [template_name for template_name, _ in self.templates] template_types = [PyrexTypes.TemplatePlaceholderType(template_name, not required) for template_name, required in self.templates] - scope = None - if self.attributes is not None: + scope = None + if self.attributes is not None: scope = CppClassScope(self.name, env, templates=template_names) - def base_ok(base_class): - if base_class.is_cpp_class or base_class.is_struct: - return True - else: - error(self.pos, "Base class '%s' not a struct or class." % base_class) - base_class_types = filter(base_ok, [b.analyse(scope or env) for b in self.base_classes]) - self.entry = env.declare_cpp_class( - self.name, scope, self.pos, + def base_ok(base_class): + if base_class.is_cpp_class or base_class.is_struct: + return True + else: + error(self.pos, "Base class '%s' not a struct or class." % base_class) + base_class_types = filter(base_ok, [b.analyse(scope or env) for b in self.base_classes]) + self.entry = env.declare_cpp_class( + self.name, scope, self.pos, self.cname, base_class_types, visibility=self.visibility, templates=template_types) - if self.entry is None: - return - self.entry.is_cpp_class = 1 - if scope is not None: - scope.type = self.entry.type - defined_funcs = [] - def func_attributes(attributes): - for attr in attributes: - if isinstance(attr, CFuncDefNode): - yield attr - elif isinstance(attr, CompilerDirectivesNode): - for sub_attr in func_attributes(attr.body.stats): - yield sub_attr - if self.attributes is not None: - if self.in_pxd and not env.in_cinclude: - self.entry.defined_in_pxd = 1 - for attr in self.attributes: + if self.entry is None: + return + self.entry.is_cpp_class = 1 + if scope is not None: + scope.type = self.entry.type + defined_funcs = [] + def func_attributes(attributes): + for attr in attributes: + if isinstance(attr, CFuncDefNode): + yield attr + elif isinstance(attr, CompilerDirectivesNode): + for sub_attr in func_attributes(attr.body.stats): + yield sub_attr + if self.attributes is not None: + if self.in_pxd and not env.in_cinclude: + self.entry.defined_in_pxd = 1 + for attr in self.attributes: declare = getattr(attr, 'declare', None) if declare: attr.declare(scope) - attr.analyse_declarations(scope) - for func in func_attributes(self.attributes): - defined_funcs.append(func) - if self.templates is not None: + attr.analyse_declarations(scope) + for func in func_attributes(self.attributes): + defined_funcs.append(func) + if self.templates is not None: func.template_declaration = "template <typename %s>" % ", typename ".join(template_names) - self.body = StatListNode(self.pos, stats=defined_funcs) - self.scope = scope - - def analyse_expressions(self, env): - self.body = self.body.analyse_expressions(self.entry.type.scope) - return self - - def generate_function_definitions(self, env, code): - self.body.generate_function_definitions(self.entry.type.scope, code) - - def generate_execution_code(self, code): - self.body.generate_execution_code(code) - - def annotate(self, code): - self.body.annotate(code) - - -class CEnumDefNode(StatNode): - # name string or None - # cname string or None - # items [CEnumDefItemNode] - # typedef_flag boolean - # visibility "public" or "private" or "extern" - # api boolean - # in_pxd boolean - # create_wrapper boolean - # entry Entry - - child_attrs = ["items"] - - def declare(self, env): + self.body = StatListNode(self.pos, stats=defined_funcs) + self.scope = scope + + def analyse_expressions(self, env): + self.body = self.body.analyse_expressions(self.entry.type.scope) + return self + + def generate_function_definitions(self, env, code): + self.body.generate_function_definitions(self.entry.type.scope, code) + + def generate_execution_code(self, code): + self.body.generate_execution_code(code) + + def annotate(self, code): + self.body.annotate(code) + + +class CEnumDefNode(StatNode): + # name string or None + # cname string or None + # items [CEnumDefItemNode] + # typedef_flag boolean + # visibility "public" or "private" or "extern" + # api boolean + # in_pxd boolean + # create_wrapper boolean + # entry Entry + + child_attrs = ["items"] + + def declare(self, env): self.entry = env.declare_enum( self.name, self.pos, cname=self.cname, typedef_flag=self.typedef_flag, visibility=self.visibility, api=self.api, create_wrapper=self.create_wrapper) - - def analyse_declarations(self, env): - if self.items is not None: - if self.in_pxd and not env.in_cinclude: - self.entry.defined_in_pxd = 1 - for item in self.items: - item.analyse_declarations(env, self.entry) - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - if self.visibility == 'public' or self.api: + + def analyse_declarations(self, env): + if self.items is not None: + if self.in_pxd and not env.in_cinclude: + self.entry.defined_in_pxd = 1 + for item in self.items: + item.analyse_declarations(env, self.entry) + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + if self.visibility == 'public' or self.api: code.mark_pos(self.pos) - temp = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True) - for item in self.entry.enum_values: - code.putln("%s = PyInt_FromLong(%s); %s" % ( + temp = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True) + for item in self.entry.enum_values: + code.putln("%s = PyInt_FromLong(%s); %s" % ( temp, item.cname, code.error_goto_if_null(temp, item.pos))) - code.put_gotref(temp) - code.putln('if (PyDict_SetItemString(%s, "%s", %s) < 0) %s' % ( + code.put_gotref(temp) + code.putln('if (PyDict_SetItemString(%s, "%s", %s) < 0) %s' % ( Naming.moddict_cname, item.name, temp, code.error_goto(item.pos))) - code.put_decref_clear(temp, PyrexTypes.py_object_type) - code.funcstate.release_temp(temp) - - -class CEnumDefItemNode(StatNode): - # name string - # cname string or None - # value ExprNode or None - - child_attrs = ["value"] - - def analyse_declarations(self, env, enum_entry): - if self.value: - self.value = self.value.analyse_const_expression(env) - if not self.value.type.is_int: - self.value = self.value.coerce_to(PyrexTypes.c_int_type, env) - self.value = self.value.analyse_const_expression(env) + code.put_decref_clear(temp, PyrexTypes.py_object_type) + code.funcstate.release_temp(temp) + + +class CEnumDefItemNode(StatNode): + # name string + # cname string or None + # value ExprNode or None + + child_attrs = ["value"] + + def analyse_declarations(self, env, enum_entry): + if self.value: + self.value = self.value.analyse_const_expression(env) + if not self.value.type.is_int: + self.value = self.value.coerce_to(PyrexTypes.c_int_type, env) + self.value = self.value.analyse_const_expression(env) entry = env.declare_const( self.name, enum_entry.type, self.value, self.pos, cname=self.cname, visibility=enum_entry.visibility, api=enum_entry.api, create_wrapper=enum_entry.create_wrapper and enum_entry.name is None) - enum_entry.enum_values.append(entry) + enum_entry.enum_values.append(entry) if enum_entry.name: enum_entry.type.values.append(entry.name) - - -class CTypeDefNode(StatNode): - # base_type CBaseTypeNode - # declarator CDeclaratorNode - # visibility "public" or "private" - # api boolean - # in_pxd boolean - - child_attrs = ["base_type", "declarator"] - - def analyse_declarations(self, env): - base = self.base_type.analyse(env) + + +class CTypeDefNode(StatNode): + # base_type CBaseTypeNode + # declarator CDeclaratorNode + # visibility "public" or "private" + # api boolean + # in_pxd boolean + + child_attrs = ["base_type", "declarator"] + + def analyse_declarations(self, env): + base = self.base_type.analyse(env) name_declarator, type = self.declarator.analyse( base, env, visibility=self.visibility, in_pxd=self.in_pxd) - name = name_declarator.name - cname = name_declarator.cname - + name = name_declarator.name + cname = name_declarator.cname + entry = env.declare_typedef( name, type, self.pos, cname=cname, visibility=self.visibility, api=self.api) - - if type.is_fused: - entry.in_cinclude = True - - if self.in_pxd and not env.in_cinclude: - entry.defined_in_pxd = 1 - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class FuncDefNode(StatNode, BlockNode): - # Base class for function definition nodes. - # - # return_type PyrexType - # #filename string C name of filename string const - # entry Symtab.Entry - # needs_closure boolean Whether or not this function has inner functions/classes/yield - # needs_outer_scope boolean Whether or not this function requires outer scope - # pymethdef_required boolean Force Python method struct generation - # directive_locals { string : ExprNode } locals defined by cython.locals(...) - # directive_returns [ExprNode] type defined by cython.returns(...) + + if type.is_fused: + entry.in_cinclude = True + + if self.in_pxd and not env.in_cinclude: + entry.defined_in_pxd = 1 + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class FuncDefNode(StatNode, BlockNode): + # Base class for function definition nodes. + # + # return_type PyrexType + # #filename string C name of filename string const + # entry Symtab.Entry + # needs_closure boolean Whether or not this function has inner functions/classes/yield + # needs_outer_scope boolean Whether or not this function requires outer scope + # pymethdef_required boolean Force Python method struct generation + # directive_locals { string : ExprNode } locals defined by cython.locals(...) + # directive_returns [ExprNode] type defined by cython.returns(...) # star_arg PyArgDeclNode or None * argument # starstar_arg PyArgDeclNode or None ** argument # # is_async_def boolean is a Coroutine function # - # has_fused_arguments boolean - # Whether this cdef function has fused parameters. This is needed - # by AnalyseDeclarationsTransform, so it can replace CFuncDefNodes - # with fused argument types with a FusedCFuncDefNode - - py_func = None - needs_closure = False - needs_outer_scope = False - pymethdef_required = False - is_generator = False - is_generator_body = False + # has_fused_arguments boolean + # Whether this cdef function has fused parameters. This is needed + # by AnalyseDeclarationsTransform, so it can replace CFuncDefNodes + # with fused argument types with a FusedCFuncDefNode + + py_func = None + needs_closure = False + needs_outer_scope = False + pymethdef_required = False + is_generator = False + is_generator_body = False is_async_def = False - modifiers = [] - has_fused_arguments = False - star_arg = None - starstar_arg = None - is_cyfunction = False + modifiers = [] + has_fused_arguments = False + star_arg = None + starstar_arg = None + is_cyfunction = False code_object = None - - def analyse_default_values(self, env): - default_seen = 0 - for arg in self.args: - if arg.default: - default_seen = 1 - if arg.is_generic: - arg.default = arg.default.analyse_types(env) - arg.default = arg.default.coerce_to(arg.type, env) - else: + + def analyse_default_values(self, env): + default_seen = 0 + for arg in self.args: + if arg.default: + default_seen = 1 + if arg.is_generic: + arg.default = arg.default.analyse_types(env) + arg.default = arg.default.coerce_to(arg.type, env) + else: error(arg.pos, "This argument cannot have a default value") - arg.default = None - elif arg.kw_only: - default_seen = 1 - elif default_seen: - error(arg.pos, "Non-default argument following default argument") - + arg.default = None + elif arg.kw_only: + default_seen = 1 + elif default_seen: + 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: @@ -1697,177 +1697,177 @@ class FuncDefNode(StatNode, BlockNode): if arg.annotation: arg.annotation = self.analyse_annotation(env, arg.annotation) - def align_argument_type(self, env, arg): - # @cython.locals() - directive_locals = self.directive_locals - orig_type = arg.type - if arg.name in directive_locals: - type_node = directive_locals[arg.name] - other_type = type_node.analyse_as_type(env) + def align_argument_type(self, env, arg): + # @cython.locals() + directive_locals = self.directive_locals + orig_type = arg.type + if arg.name in directive_locals: + type_node = directive_locals[arg.name] + other_type = type_node.analyse_as_type(env) elif isinstance(arg, CArgDeclNode) and arg.annotation and env.directives['annotation_typing']: - type_node = arg.annotation - other_type = arg.inject_type_from_annotations(env) - if other_type is None: - return arg - else: - return arg - if other_type is None: - error(type_node.pos, "Not a type") + type_node = arg.annotation + other_type = arg.inject_type_from_annotations(env) + if other_type is None: + return arg + else: + return arg + if other_type is None: + error(type_node.pos, "Not a type") elif orig_type is not py_object_type and not orig_type.same_as(other_type): - error(arg.base_type.pos, "Signature does not agree with previous declaration") - error(type_node.pos, "Previous declaration here") - else: - arg.type = other_type - return arg - - def need_gil_acquisition(self, lenv): - return 0 - - def create_local_scope(self, env): - genv = env - while genv.is_py_class_scope or genv.is_c_class_scope: - genv = genv.outer_scope - if self.needs_closure: - lenv = ClosureScope(name=self.entry.name, + error(arg.base_type.pos, "Signature does not agree with previous declaration") + error(type_node.pos, "Previous declaration here") + else: + arg.type = other_type + return arg + + def need_gil_acquisition(self, lenv): + return 0 + + def create_local_scope(self, env): + genv = env + while genv.is_py_class_scope or genv.is_c_class_scope: + genv = genv.outer_scope + if self.needs_closure: + lenv = ClosureScope(name=self.entry.name, outer_scope=genv, parent_scope=env, - scope_name=self.entry.cname) - else: - lenv = LocalScope(name=self.entry.name, - outer_scope=genv, - parent_scope=env) - lenv.return_type = self.return_type - type = self.entry.type - if type.is_cfunction: - lenv.nogil = type.nogil and not type.with_gil - self.local_scope = lenv - lenv.directives = env.directives - return lenv - - def generate_function_body(self, env, code): - self.body.generate_execution_code(code) - - def generate_function_definitions(self, env, code): - from . import Buffer - if self.return_type.is_memoryviewslice: - from . import MemoryView - - lenv = self.local_scope - if lenv.is_closure_scope and not lenv.is_passthrough: - outer_scope_cname = "%s->%s" % (Naming.cur_scope_cname, - Naming.outer_scope_cname) - else: - outer_scope_cname = Naming.outer_scope_cname - lenv.mangle_closure_cnames(outer_scope_cname) - # Generate closure function definitions - self.body.generate_function_definitions(lenv, code) - # generate lambda function definitions - self.generate_lambda_definitions(lenv, code) - - is_getbuffer_slot = (self.entry.name == "__getbuffer__" and - self.entry.scope.is_c_class_scope) - is_releasebuffer_slot = (self.entry.name == "__releasebuffer__" and - self.entry.scope.is_c_class_scope) - is_buffer_slot = is_getbuffer_slot or is_releasebuffer_slot - if is_buffer_slot: - if 'cython_unused' not in self.modifiers: - self.modifiers = self.modifiers + ['cython_unused'] - - preprocessor_guard = self.get_preprocessor_guard() - - profile = code.globalstate.directives['profile'] - linetrace = code.globalstate.directives['linetrace'] - if profile or linetrace: - code.globalstate.use_utility_code( - UtilityCode.load_cached("Profile", "Profile.c")) - - # Generate C code for header and body of function + scope_name=self.entry.cname) + else: + lenv = LocalScope(name=self.entry.name, + outer_scope=genv, + parent_scope=env) + lenv.return_type = self.return_type + type = self.entry.type + if type.is_cfunction: + lenv.nogil = type.nogil and not type.with_gil + self.local_scope = lenv + lenv.directives = env.directives + return lenv + + def generate_function_body(self, env, code): + self.body.generate_execution_code(code) + + def generate_function_definitions(self, env, code): + from . import Buffer + if self.return_type.is_memoryviewslice: + from . import MemoryView + + lenv = self.local_scope + if lenv.is_closure_scope and not lenv.is_passthrough: + outer_scope_cname = "%s->%s" % (Naming.cur_scope_cname, + Naming.outer_scope_cname) + else: + outer_scope_cname = Naming.outer_scope_cname + lenv.mangle_closure_cnames(outer_scope_cname) + # Generate closure function definitions + self.body.generate_function_definitions(lenv, code) + # generate lambda function definitions + self.generate_lambda_definitions(lenv, code) + + is_getbuffer_slot = (self.entry.name == "__getbuffer__" and + self.entry.scope.is_c_class_scope) + is_releasebuffer_slot = (self.entry.name == "__releasebuffer__" and + self.entry.scope.is_c_class_scope) + is_buffer_slot = is_getbuffer_slot or is_releasebuffer_slot + if is_buffer_slot: + if 'cython_unused' not in self.modifiers: + self.modifiers = self.modifiers + ['cython_unused'] + + preprocessor_guard = self.get_preprocessor_guard() + + profile = code.globalstate.directives['profile'] + linetrace = code.globalstate.directives['linetrace'] + if profile or linetrace: + code.globalstate.use_utility_code( + UtilityCode.load_cached("Profile", "Profile.c")) + + # Generate C code for header and body of function code.enter_cfunc_scope(lenv) - code.return_from_error_cleanup_label = code.new_label() + code.return_from_error_cleanup_label = code.new_label() code.funcstate.gil_owned = not lenv.nogil - - # ----- Top-level constants used by this function - code.mark_pos(self.pos) - self.generate_cached_builtins_decls(lenv, code) - # ----- Function header - code.putln("") - - if preprocessor_guard: - code.putln(preprocessor_guard) - - with_pymethdef = (self.needs_assignment_synthesis(env, code) or - self.pymethdef_required) - if self.py_func: + + # ----- Top-level constants used by this function + code.mark_pos(self.pos) + self.generate_cached_builtins_decls(lenv, code) + # ----- Function header + code.putln("") + + if preprocessor_guard: + code.putln(preprocessor_guard) + + with_pymethdef = (self.needs_assignment_synthesis(env, code) or + self.pymethdef_required) + if self.py_func: self.py_func.generate_function_header( code, with_pymethdef=with_pymethdef, proto_only=True) self.generate_function_header(code, with_pymethdef=with_pymethdef) - # ----- Local variable declarations - # Find function scope - cenv = env - while cenv.is_py_class_scope or cenv.is_c_class_scope: - cenv = cenv.outer_scope - if self.needs_closure: - code.put(lenv.scope_class.type.declaration_code(Naming.cur_scope_cname)) - code.putln(";") - elif self.needs_outer_scope: - if lenv.is_passthrough: - code.put(lenv.scope_class.type.declaration_code(Naming.cur_scope_cname)) - code.putln(";") - code.put(cenv.scope_class.type.declaration_code(Naming.outer_scope_cname)) - code.putln(";") - self.generate_argument_declarations(lenv, code) - - for entry in lenv.var_entries: - if not (entry.in_closure or entry.is_arg): - code.put_var_declaration(entry) - - # Initialize the return variable __pyx_r - init = "" - if not self.return_type.is_void: - if self.return_type.is_pyobject: - init = " = NULL" - elif self.return_type.is_memoryviewslice: - init = ' = ' + MemoryView.memslice_entry_init - + # ----- Local variable declarations + # Find function scope + cenv = env + while cenv.is_py_class_scope or cenv.is_c_class_scope: + cenv = cenv.outer_scope + if self.needs_closure: + code.put(lenv.scope_class.type.declaration_code(Naming.cur_scope_cname)) + code.putln(";") + elif self.needs_outer_scope: + if lenv.is_passthrough: + code.put(lenv.scope_class.type.declaration_code(Naming.cur_scope_cname)) + code.putln(";") + code.put(cenv.scope_class.type.declaration_code(Naming.outer_scope_cname)) + code.putln(";") + self.generate_argument_declarations(lenv, code) + + for entry in lenv.var_entries: + if not (entry.in_closure or entry.is_arg): + code.put_var_declaration(entry) + + # Initialize the return variable __pyx_r + init = "" + if not self.return_type.is_void: + if self.return_type.is_pyobject: + init = " = NULL" + elif self.return_type.is_memoryviewslice: + init = ' = ' + MemoryView.memslice_entry_init + code.putln("%s%s;" % ( self.return_type.declaration_code(Naming.retval_cname), init)) - - tempvardecl_code = code.insertion_point() - self.generate_keyword_list(code) - - # ----- GIL acquisition - acquire_gil = self.acquire_gil - - # See if we need to acquire the GIL for variable declarations, or for - # refnanny only - + + tempvardecl_code = code.insertion_point() + self.generate_keyword_list(code) + + # ----- GIL acquisition + acquire_gil = self.acquire_gil + + # See if we need to acquire the GIL for variable declarations, or for + # refnanny only + # Closures are not currently possible for cdef nogil functions, # but check them anyway have_object_args = self.needs_closure or self.needs_outer_scope - for arg in lenv.arg_entries: - if arg.type.is_pyobject: - have_object_args = True - break - + for arg in lenv.arg_entries: + if arg.type.is_pyobject: + have_object_args = True + break + used_buffer_entries = [entry for entry in lenv.buffer_entries if entry.used] - acquire_gil_for_var_decls_only = ( + acquire_gil_for_var_decls_only = ( lenv.nogil and lenv.has_with_gil_block and (have_object_args or used_buffer_entries)) - - acquire_gil_for_refnanny_only = ( + + acquire_gil_for_refnanny_only = ( lenv.nogil and lenv.has_with_gil_block and not acquire_gil_for_var_decls_only) - - use_refnanny = not lenv.nogil or lenv.has_with_gil_block - - if acquire_gil or acquire_gil_for_var_decls_only: - code.put_ensure_gil() + + use_refnanny = not lenv.nogil or lenv.has_with_gil_block + + if acquire_gil or acquire_gil_for_var_decls_only: + code.put_ensure_gil() code.funcstate.gil_owned = True - elif lenv.nogil and lenv.has_with_gil_block: - code.declare_gilstate() - + elif lenv.nogil and lenv.has_with_gil_block: + code.declare_gilstate() + if profile or linetrace: if not self.is_generator: # generators are traced when iterated, not at creation @@ -1879,28 +1879,28 @@ class FuncDefNode(StatNode, BlockNode): if is_getbuffer_slot: self.getbuffer_check(code) - # ----- set up refnanny - if use_refnanny: - tempvardecl_code.put_declare_refcount_context() - code.put_setup_refcount_context( - self.entry.name, acquire_gil=acquire_gil_for_refnanny_only) - - # ----- Automatic lead-ins for certain special functions - if is_getbuffer_slot: - self.getbuffer_init(code) - # ----- Create closure scope object - if self.needs_closure: - tp_slot = TypeSlots.ConstructorSlot("tp_new", '__new__') - slot_func_cname = TypeSlots.get_slot_function(lenv.scope_class.type.scope, tp_slot) - if not slot_func_cname: - slot_func_cname = '%s->tp_new' % lenv.scope_class.type.typeptr_cname - code.putln("%s = (%s)%s(%s, %s, NULL);" % ( - Naming.cur_scope_cname, + # ----- set up refnanny + if use_refnanny: + tempvardecl_code.put_declare_refcount_context() + code.put_setup_refcount_context( + self.entry.name, acquire_gil=acquire_gil_for_refnanny_only) + + # ----- Automatic lead-ins for certain special functions + if is_getbuffer_slot: + self.getbuffer_init(code) + # ----- Create closure scope object + if self.needs_closure: + tp_slot = TypeSlots.ConstructorSlot("tp_new", '__new__') + slot_func_cname = TypeSlots.get_slot_function(lenv.scope_class.type.scope, tp_slot) + if not slot_func_cname: + slot_func_cname = '%s->tp_new' % lenv.scope_class.type.typeptr_cname + code.putln("%s = (%s)%s(%s, %s, NULL);" % ( + Naming.cur_scope_cname, lenv.scope_class.type.empty_declaration_code(), - slot_func_cname, - lenv.scope_class.type.typeptr_cname, - Naming.empty_tuple)) - code.putln("if (unlikely(!%s)) {" % Naming.cur_scope_cname) + slot_func_cname, + lenv.scope_class.type.typeptr_cname, + Naming.empty_tuple)) + code.putln("if (unlikely(!%s)) {" % Naming.cur_scope_cname) # Scope unconditionally DECREFed on return. code.putln("%s = %s;" % ( Naming.cur_scope_cname, @@ -1909,29 +1909,29 @@ class FuncDefNode(StatNode, BlockNode): code.putln(code.error_goto(self.pos)) code.putln("} else {") code.put_gotref(Naming.cur_scope_cname) - code.putln("}") - # Note that it is unsafe to decref the scope at this point. - if self.needs_outer_scope: - if self.is_cyfunction: - code.putln("%s = (%s) __Pyx_CyFunction_GetClosure(%s);" % ( - outer_scope_cname, + code.putln("}") + # Note that it is unsafe to decref the scope at this point. + if self.needs_outer_scope: + if self.is_cyfunction: + code.putln("%s = (%s) __Pyx_CyFunction_GetClosure(%s);" % ( + outer_scope_cname, cenv.scope_class.type.empty_declaration_code(), - Naming.self_cname)) - else: - code.putln("%s = (%s) %s;" % ( - outer_scope_cname, + Naming.self_cname)) + else: + code.putln("%s = (%s) %s;" % ( + outer_scope_cname, cenv.scope_class.type.empty_declaration_code(), - Naming.self_cname)) - if lenv.is_passthrough: - code.putln("%s = %s;" % (Naming.cur_scope_cname, outer_scope_cname)) - elif self.needs_closure: - # inner closures own a reference to their outer parent - code.put_incref(outer_scope_cname, cenv.scope_class.type) - code.put_giveref(outer_scope_cname) - # ----- Trace function call - if profile or linetrace: - # this looks a bit late, but if we don't get here due to a - # fatal error before hand, it's not really worth tracing + Naming.self_cname)) + if lenv.is_passthrough: + code.putln("%s = %s;" % (Naming.cur_scope_cname, outer_scope_cname)) + elif self.needs_closure: + # inner closures own a reference to their outer parent + code.put_incref(outer_scope_cname, cenv.scope_class.type) + code.put_giveref(outer_scope_cname) + # ----- Trace function call + if profile or linetrace: + # this looks a bit late, but if we don't get here due to a + # fatal error before hand, it's not really worth tracing if not self.is_generator: # generators are traced when iterated, not at creation if self.is_wrapper: @@ -1940,207 +1940,207 @@ class FuncDefNode(StatNode, BlockNode): trace_name = self.entry.name code.put_trace_call( trace_name, self.pos, nogil=not code.funcstate.gil_owned) - code.funcstate.can_trace = True - # ----- Fetch arguments - self.generate_argument_parsing_code(env, code) - # If an argument is assigned to in the body, we must - # incref it to properly keep track of refcounts. - is_cdef = isinstance(self, CFuncDefNode) - for entry in lenv.arg_entries: - if entry.type.is_pyobject: + code.funcstate.can_trace = True + # ----- Fetch arguments + self.generate_argument_parsing_code(env, code) + # If an argument is assigned to in the body, we must + # incref it to properly keep track of refcounts. + is_cdef = isinstance(self, CFuncDefNode) + for entry in lenv.arg_entries: + if entry.type.is_pyobject: if (acquire_gil or len(entry.cf_assignments) > 1) and not entry.in_closure: - code.put_var_incref(entry) - - # Note: defaults are always incref-ed. For def functions, we + code.put_var_incref(entry) + + # Note: defaults are always incref-ed. For def functions, we # we acquire arguments from object conversion, so we have - # new references. If we are a cdef function, we need to - # incref our arguments + # new references. If we are a cdef function, we need to + # incref our arguments elif is_cdef and entry.type.is_memoryviewslice and len(entry.cf_assignments) > 1: code.put_incref_memoryviewslice(entry.cname, have_gil=code.funcstate.gil_owned) - for entry in lenv.var_entries: + for entry in lenv.var_entries: if entry.is_arg and len(entry.cf_assignments) > 1 and not entry.in_closure: if entry.xdecref_cleanup: code.put_var_xincref(entry) else: code.put_var_incref(entry) - - # ----- Initialise local buffer auxiliary variables - for entry in lenv.var_entries + lenv.arg_entries: - if entry.type.is_buffer and entry.buffer_aux.buflocal_nd_var.used: - Buffer.put_init_vars(entry, code) - - # ----- Check and convert arguments - self.generate_argument_type_tests(code) - # ----- Acquire buffer arguments - for entry in lenv.arg_entries: - if entry.type.is_buffer: - Buffer.put_acquire_arg_buffer(entry, code, self.pos) - - if acquire_gil_for_var_decls_only: - code.put_release_ensured_gil() + + # ----- Initialise local buffer auxiliary variables + for entry in lenv.var_entries + lenv.arg_entries: + if entry.type.is_buffer and entry.buffer_aux.buflocal_nd_var.used: + Buffer.put_init_vars(entry, code) + + # ----- Check and convert arguments + self.generate_argument_type_tests(code) + # ----- Acquire buffer arguments + for entry in lenv.arg_entries: + if entry.type.is_buffer: + Buffer.put_acquire_arg_buffer(entry, code, self.pos) + + if acquire_gil_for_var_decls_only: + code.put_release_ensured_gil() code.funcstate.gil_owned = False - - # ------------------------- - # ----- Function body ----- - # ------------------------- - self.generate_function_body(env, code) - + + # ------------------------- + # ----- Function body ----- + # ------------------------- + self.generate_function_body(env, code) + code.mark_pos(self.pos, trace=False) - code.putln("") - code.putln("/* function exit code */") - - # ----- Default return value - if not self.body.is_terminator: - if self.return_type.is_pyobject: - #if self.return_type.is_extension_type: - # lhs = "(PyObject *)%s" % Naming.retval_cname - #else: - lhs = Naming.retval_cname - code.put_init_to_py_none(lhs, self.return_type) - else: - val = self.return_type.default_value - if val: - code.putln("%s = %s;" % (Naming.retval_cname, val)) + code.putln("") + code.putln("/* function exit code */") + + # ----- Default return value + if not self.body.is_terminator: + if self.return_type.is_pyobject: + #if self.return_type.is_extension_type: + # lhs = "(PyObject *)%s" % Naming.retval_cname + #else: + lhs = Naming.retval_cname + code.put_init_to_py_none(lhs, self.return_type) + else: + val = self.return_type.default_value + if val: + code.putln("%s = %s;" % (Naming.retval_cname, val)) elif not self.return_type.is_void: code.putln("__Pyx_pretend_to_initialize(&%s);" % Naming.retval_cname) - # ----- Error cleanup - if code.error_label in code.labels_used: - if not self.body.is_terminator: - code.put_goto(code.return_label) - code.put_label(code.error_label) - for cname, type in code.funcstate.all_managed_temps(): - code.put_xdecref(cname, type, have_gil=not lenv.nogil) - - # Clean up buffers -- this calls a Python function - # so need to save and restore error state + # ----- Error cleanup + if code.error_label in code.labels_used: + if not self.body.is_terminator: + code.put_goto(code.return_label) + code.put_label(code.error_label) + for cname, type in code.funcstate.all_managed_temps(): + code.put_xdecref(cname, type, have_gil=not lenv.nogil) + + # Clean up buffers -- this calls a Python function + # so need to save and restore error state buffers_present = len(used_buffer_entries) > 0 #memslice_entries = [e for e in lenv.entries.values() if e.type.is_memoryviewslice] - if buffers_present: - code.globalstate.use_utility_code(restore_exception_utility_code) - code.putln("{ PyObject *__pyx_type, *__pyx_value, *__pyx_tb;") + if buffers_present: + code.globalstate.use_utility_code(restore_exception_utility_code) + code.putln("{ PyObject *__pyx_type, *__pyx_value, *__pyx_tb;") code.putln("__Pyx_PyThreadState_declare") code.putln("__Pyx_PyThreadState_assign") - code.putln("__Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);") + code.putln("__Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);") for entry in used_buffer_entries: Buffer.put_release_buffer_code(code, entry) - #code.putln("%s = 0;" % entry.cname) - code.putln("__Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}") - - if self.return_type.is_memoryviewslice: - MemoryView.put_init_entry(Naming.retval_cname, code) - err_val = Naming.retval_cname - else: - err_val = self.error_value() - - exc_check = self.caller_will_check_exceptions() - if err_val is not None or exc_check: - # TODO: Fix exception tracing (though currently unused by cProfile). - # code.globalstate.use_utility_code(get_exception_tuple_utility_code) - # code.put_trace_exception() - - if lenv.nogil and not lenv.has_with_gil_block: - code.putln("{") - code.put_ensure_gil() - - code.put_add_traceback(self.entry.qualified_name) - - if lenv.nogil and not lenv.has_with_gil_block: - code.put_release_ensured_gil() - code.putln("}") - else: - warning(self.entry.pos, - "Unraisable exception in function '%s'." % - self.entry.qualified_name, 0) + #code.putln("%s = 0;" % entry.cname) + code.putln("__Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}") + + if self.return_type.is_memoryviewslice: + MemoryView.put_init_entry(Naming.retval_cname, code) + err_val = Naming.retval_cname + else: + err_val = self.error_value() + + exc_check = self.caller_will_check_exceptions() + if err_val is not None or exc_check: + # TODO: Fix exception tracing (though currently unused by cProfile). + # code.globalstate.use_utility_code(get_exception_tuple_utility_code) + # code.put_trace_exception() + + if lenv.nogil and not lenv.has_with_gil_block: + code.putln("{") + code.put_ensure_gil() + + code.put_add_traceback(self.entry.qualified_name) + + if lenv.nogil and not lenv.has_with_gil_block: + code.put_release_ensured_gil() + code.putln("}") + else: + warning(self.entry.pos, + "Unraisable exception in function '%s'." % + self.entry.qualified_name, 0) code.put_unraisable(self.entry.qualified_name, lenv.nogil) - default_retval = self.return_type.default_value - if err_val is None and default_retval: - err_val = default_retval - if err_val is not None: + default_retval = self.return_type.default_value + if err_val is None and default_retval: + err_val = default_retval + if err_val is not None: if err_val != Naming.retval_cname: code.putln("%s = %s;" % (Naming.retval_cname, err_val)) elif not self.return_type.is_void: code.putln("__Pyx_pretend_to_initialize(&%s);" % Naming.retval_cname) - - if is_getbuffer_slot: - self.getbuffer_error_cleanup(code) - - # If we are using the non-error cleanup section we should - # jump past it if we have an error. The if-test below determine - # whether this section is used. - if buffers_present or is_getbuffer_slot or self.return_type.is_memoryviewslice: - code.put_goto(code.return_from_error_cleanup_label) - - # ----- Non-error return cleanup - code.put_label(code.return_label) + + if is_getbuffer_slot: + self.getbuffer_error_cleanup(code) + + # If we are using the non-error cleanup section we should + # jump past it if we have an error. The if-test below determine + # whether this section is used. + if buffers_present or is_getbuffer_slot or self.return_type.is_memoryviewslice: + code.put_goto(code.return_from_error_cleanup_label) + + # ----- Non-error return cleanup + code.put_label(code.return_label) for entry in used_buffer_entries: Buffer.put_release_buffer_code(code, entry) - if is_getbuffer_slot: - self.getbuffer_normal_cleanup(code) - - if self.return_type.is_memoryviewslice: - # See if our return value is uninitialized on non-error return - # from . import MemoryView - # MemoryView.err_if_nogil_initialized_check(self.pos, env) + if is_getbuffer_slot: + self.getbuffer_normal_cleanup(code) + + if self.return_type.is_memoryviewslice: + # See if our return value is uninitialized on non-error return + # from . import MemoryView + # MemoryView.err_if_nogil_initialized_check(self.pos, env) cond = code.unlikely(self.return_type.error_condition(Naming.retval_cname)) - code.putln( - 'if (%s) {' % cond) - if env.nogil: - code.put_ensure_gil() - code.putln( + code.putln( + 'if (%s) {' % cond) + if env.nogil: + code.put_ensure_gil() + code.putln( 'PyErr_SetString(PyExc_TypeError, "Memoryview return value is not initialized");') - if env.nogil: - code.put_release_ensured_gil() - code.putln( - '}') - - # ----- Return cleanup for both error and no-error return - code.put_label(code.return_from_error_cleanup_label) - - for entry in lenv.var_entries: - if not entry.used or entry.in_closure: - continue - - if entry.type.is_memoryviewslice: + if env.nogil: + code.put_release_ensured_gil() + code.putln( + '}') + + # ----- Return cleanup for both error and no-error return + code.put_label(code.return_from_error_cleanup_label) + + for entry in lenv.var_entries: + if not entry.used or entry.in_closure: + continue + + if entry.type.is_memoryviewslice: code.put_xdecref_memoryviewslice(entry.cname, have_gil=not lenv.nogil) - elif entry.type.is_pyobject: - if not entry.is_arg or len(entry.cf_assignments) > 1: + elif entry.type.is_pyobject: + if not entry.is_arg or len(entry.cf_assignments) > 1: if entry.xdecref_cleanup: code.put_var_xdecref(entry) else: code.put_var_decref(entry) - - # Decref any increfed args - for entry in lenv.arg_entries: - if entry.type.is_pyobject: + + # Decref any increfed args + for entry in lenv.arg_entries: + if entry.type.is_pyobject: if (acquire_gil or len(entry.cf_assignments) > 1) and not entry.in_closure: - code.put_var_decref(entry) - elif (entry.type.is_memoryviewslice and - (not is_cdef or len(entry.cf_assignments) > 1)): - # decref slices of def functions and acquired slices from cdef - # functions, but not borrowed slices from cdef functions. - code.put_xdecref_memoryviewslice(entry.cname, - have_gil=not lenv.nogil) - if self.needs_closure: - code.put_decref(Naming.cur_scope_cname, lenv.scope_class.type) - - # ----- Return - # This code is duplicated in ModuleNode.generate_module_init_func - if not lenv.nogil: - default_retval = self.return_type.default_value - err_val = self.error_value() - if err_val is None and default_retval: - err_val = default_retval # FIXME: why is err_val not used? - if self.return_type.is_pyobject: - code.put_xgiveref(self.return_type.as_pyobject(Naming.retval_cname)) - - if self.entry.is_special and self.entry.name == "__hash__": - # Returning -1 for __hash__ is supposed to signal an error - # We do as Python instances and coerce -1 into -2. - code.putln("if (unlikely(%s == -1) && !PyErr_Occurred()) %s = -2;" % ( + code.put_var_decref(entry) + elif (entry.type.is_memoryviewslice and + (not is_cdef or len(entry.cf_assignments) > 1)): + # decref slices of def functions and acquired slices from cdef + # functions, but not borrowed slices from cdef functions. + code.put_xdecref_memoryviewslice(entry.cname, + have_gil=not lenv.nogil) + if self.needs_closure: + code.put_decref(Naming.cur_scope_cname, lenv.scope_class.type) + + # ----- Return + # This code is duplicated in ModuleNode.generate_module_init_func + if not lenv.nogil: + default_retval = self.return_type.default_value + err_val = self.error_value() + if err_val is None and default_retval: + err_val = default_retval # FIXME: why is err_val not used? + if self.return_type.is_pyobject: + code.put_xgiveref(self.return_type.as_pyobject(Naming.retval_cname)) + + if self.entry.is_special and self.entry.name == "__hash__": + # Returning -1 for __hash__ is supposed to signal an error + # We do as Python instances and coerce -1 into -2. + code.putln("if (unlikely(%s == -1) && !PyErr_Occurred()) %s = -2;" % ( Naming.retval_cname, Naming.retval_cname)) - - if profile or linetrace: - code.funcstate.can_trace = False + + if profile or linetrace: + code.funcstate.can_trace = False if not self.is_generator: # generators are traced when iterated, not at creation if self.return_type.is_pyobject: @@ -2149,87 +2149,87 @@ class FuncDefNode(StatNode, BlockNode): else: code.put_trace_return( "Py_None", nogil=not code.funcstate.gil_owned) - - if not lenv.nogil: - # GIL holding function - code.put_finish_refcount_context() - - if acquire_gil or (lenv.nogil and lenv.has_with_gil_block): - # release the GIL (note that with-gil blocks acquire it on exit in their EnsureGILNode) - code.put_release_ensured_gil() + + if not lenv.nogil: + # GIL holding function + code.put_finish_refcount_context() + + if acquire_gil or (lenv.nogil and lenv.has_with_gil_block): + # release the GIL (note that with-gil blocks acquire it on exit in their EnsureGILNode) + code.put_release_ensured_gil() code.funcstate.gil_owned = False - - if not self.return_type.is_void: - code.putln("return %s;" % Naming.retval_cname) - - code.putln("}") - - if preprocessor_guard: - code.putln("#endif /*!(%s)*/" % preprocessor_guard) - - # ----- Go back and insert temp variable declarations - tempvardecl_code.put_temp_declarations(code.funcstate) - - # ----- Python version - code.exit_cfunc_scope() - if self.py_func: - self.py_func.generate_function_definitions(env, code) - self.generate_wrapper_functions(code) - - def declare_argument(self, env, arg): - if arg.type.is_void: - error(arg.pos, "Invalid use of 'void'") - elif not arg.type.is_complete() and not (arg.type.is_array or arg.type.is_memoryviewslice): + + if not self.return_type.is_void: + code.putln("return %s;" % Naming.retval_cname) + + code.putln("}") + + if preprocessor_guard: + code.putln("#endif /*!(%s)*/" % preprocessor_guard) + + # ----- Go back and insert temp variable declarations + tempvardecl_code.put_temp_declarations(code.funcstate) + + # ----- Python version + code.exit_cfunc_scope() + if self.py_func: + self.py_func.generate_function_definitions(env, code) + self.generate_wrapper_functions(code) + + def declare_argument(self, env, arg): + if arg.type.is_void: + error(arg.pos, "Invalid use of 'void'") + elif not arg.type.is_complete() and not (arg.type.is_array or arg.type.is_memoryviewslice): error(arg.pos, "Argument type '%s' is incomplete" % arg.type) entry = env.declare_arg(arg.name, arg.type, arg.pos) if arg.annotation: entry.annotation = arg.annotation return entry - - def generate_arg_type_test(self, arg, code): - # Generate type test for one argument. - if arg.type.typeobj_is_available(): - code.globalstate.use_utility_code( - UtilityCode.load_cached("ArgTypeTest", "FunctionArguments.c")) - typeptr_cname = arg.type.typeptr_cname - arg_code = "((PyObject *)%s)" % arg.entry.cname - code.putln( - 'if (unlikely(!__Pyx_ArgTypeTest(%s, %s, %d, "%s", %s))) %s' % ( - arg_code, - typeptr_cname, - arg.accept_none, - arg.name, + + def generate_arg_type_test(self, arg, code): + # Generate type test for one argument. + if arg.type.typeobj_is_available(): + code.globalstate.use_utility_code( + UtilityCode.load_cached("ArgTypeTest", "FunctionArguments.c")) + typeptr_cname = arg.type.typeptr_cname + arg_code = "((PyObject *)%s)" % arg.entry.cname + code.putln( + 'if (unlikely(!__Pyx_ArgTypeTest(%s, %s, %d, "%s", %s))) %s' % ( + arg_code, + typeptr_cname, + arg.accept_none, + arg.name, arg.type.is_builtin_type and arg.type.require_exact, - code.error_goto(arg.pos))) - else: + code.error_goto(arg.pos))) + else: error(arg.pos, "Cannot test type of extern C class without type object name specification") - - def generate_arg_none_check(self, arg, code): - # Generate None check for one argument. - if arg.type.is_memoryviewslice: - cname = "%s.memview" % arg.entry.cname - else: - cname = arg.entry.cname - - code.putln('if (unlikely(((PyObject *)%s) == Py_None)) {' % cname) - code.putln('''PyErr_Format(PyExc_TypeError, "Argument '%%.%ds' must not be None", "%s"); %s''' % ( - max(200, len(arg.name)), arg.name, - code.error_goto(arg.pos))) - code.putln('}') - - def generate_wrapper_functions(self, code): - pass - - def generate_execution_code(self, code): + + def generate_arg_none_check(self, arg, code): + # Generate None check for one argument. + if arg.type.is_memoryviewslice: + cname = "%s.memview" % arg.entry.cname + else: + cname = arg.entry.cname + + code.putln('if (unlikely(((PyObject *)%s) == Py_None)) {' % cname) + code.putln('''PyErr_Format(PyExc_TypeError, "Argument '%%.%ds' must not be None", "%s"); %s''' % ( + max(200, len(arg.name)), arg.name, + code.error_goto(arg.pos))) + code.putln('}') + + def generate_wrapper_functions(self, code): + pass + + def generate_execution_code(self, code): code.mark_pos(self.pos) - # Evaluate and store argument default values - for arg in self.args: - if not arg.is_dynamic: - arg.generate_assignment_code(code) - - # - # Special code for the __getbuffer__ function - # + # Evaluate and store argument default values + for arg in self.args: + if not arg.is_dynamic: + arg.generate_assignment_code(code) + + # + # Special code for the __getbuffer__ function + # def _get_py_buffer_info(self): py_buffer = self.local_scope.arg_entries[1] try: @@ -2253,8 +2253,8 @@ class FuncDefNode(StatNode, BlockNode): code.putln("PyErr_SetString(PyExc_BufferError, " "\"PyObject_GetBuffer: view==NULL argument is obsolete\");") code.putln("return -1;") - code.putln("}") - + code.putln("}") + def getbuffer_init(self, code): py_buffer, obj_type = self._get_py_buffer_info() view = py_buffer.cname @@ -2264,7 +2264,7 @@ class FuncDefNode(StatNode, BlockNode): else: code.putln("%s->obj = NULL;" % view) - def getbuffer_error_cleanup(self, code): + def getbuffer_error_cleanup(self, code): py_buffer, obj_type = self._get_py_buffer_info() view = py_buffer.cname if obj_type and obj_type.is_pyobject: @@ -2274,8 +2274,8 @@ class FuncDefNode(StatNode, BlockNode): code.putln("}") else: code.putln("Py_CLEAR(%s->obj);" % view) - - def getbuffer_normal_cleanup(self, code): + + def getbuffer_normal_cleanup(self, code): py_buffer, obj_type = self._get_py_buffer_info() view = py_buffer.cname if obj_type and obj_type.is_pyobject: @@ -2283,170 +2283,170 @@ class FuncDefNode(StatNode, BlockNode): code.put_gotref("%s->obj" % view) code.put_decref_clear("%s->obj" % view, obj_type) code.putln("}") - - def get_preprocessor_guard(self): - if not self.entry.is_special: - return None - name = self.entry.name - slot = TypeSlots.method_name_to_slot.get(name) - if not slot: - return None - if name == '__long__' and not self.entry.scope.lookup_here('__int__'): - return None - if name in ("__getbuffer__", "__releasebuffer__") and self.entry.scope.is_c_class_scope: - return None - return slot.preprocessor_guard_code() - - -class CFuncDefNode(FuncDefNode): - # C function definition. - # - # modifiers ['inline'] - # visibility 'private' or 'public' or 'extern' - # base_type CBaseTypeNode - # declarator CDeclaratorNode - # cfunc_declarator the CFuncDeclarator of this function - # (this is also available through declarator or a - # base thereof) - # body StatListNode - # api boolean - # decorators [DecoratorNode] list of decorators - # - # with_gil boolean Acquire GIL around body - # type CFuncType - # py_func wrapper for calling from Python - # overridable whether or not this is a cpdef function - # inline_in_pxd whether this is an inline function in a pxd file - # template_declaration String or None Used for c++ class methods - # 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 - - child_attrs = ["base_type", "declarator", "body", "py_func_stat"] - - inline_in_pxd = False - decorators = None - directive_locals = None - directive_returns = None - override = None - template_declaration = None - is_const_method = False - py_func_stat = None - - def unqualified_name(self): - return self.entry.name - + + def get_preprocessor_guard(self): + if not self.entry.is_special: + return None + name = self.entry.name + slot = TypeSlots.method_name_to_slot.get(name) + if not slot: + return None + if name == '__long__' and not self.entry.scope.lookup_here('__int__'): + return None + if name in ("__getbuffer__", "__releasebuffer__") and self.entry.scope.is_c_class_scope: + return None + return slot.preprocessor_guard_code() + + +class CFuncDefNode(FuncDefNode): + # C function definition. + # + # modifiers ['inline'] + # visibility 'private' or 'public' or 'extern' + # base_type CBaseTypeNode + # declarator CDeclaratorNode + # cfunc_declarator the CFuncDeclarator of this function + # (this is also available through declarator or a + # base thereof) + # body StatListNode + # api boolean + # decorators [DecoratorNode] list of decorators + # + # with_gil boolean Acquire GIL around body + # type CFuncType + # py_func wrapper for calling from Python + # overridable whether or not this is a cpdef function + # inline_in_pxd whether this is an inline function in a pxd file + # template_declaration String or None Used for c++ class methods + # 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 + + child_attrs = ["base_type", "declarator", "body", "py_func_stat"] + + inline_in_pxd = False + decorators = None + directive_locals = None + directive_returns = None + override = None + template_declaration = None + is_const_method = False + py_func_stat = None + + def unqualified_name(self): + return self.entry.name + @property def code_object(self): # share the CodeObject with the cpdef wrapper (if available) return self.py_func.code_object if self.py_func else None - def analyse_declarations(self, env): - self.is_c_class_method = env.is_c_class_scope - if self.directive_locals is None: - self.directive_locals = {} + def analyse_declarations(self, env): + self.is_c_class_method = env.is_c_class_scope + if self.directive_locals is None: + self.directive_locals = {} self.directive_locals.update(env.directives.get('locals', {})) - if self.directive_returns is not None: - base_type = self.directive_returns.analyse_as_type(env) - if base_type is None: - error(self.directive_returns.pos, "Not a type") - base_type = PyrexTypes.error_type - else: - 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. - if isinstance(self.declarator, CFuncDeclaratorNode): + if self.directive_returns is not None: + base_type = self.directive_returns.analyse_as_type(env) + if base_type is None: + error(self.directive_returns.pos, "Not a type") + base_type = PyrexTypes.error_type + else: + 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. + if isinstance(self.declarator, CFuncDeclaratorNode): name_declarator, type = self.declarator.analyse( base_type, env, nonempty=2 * (self.body is not None), directive_locals=self.directive_locals, visibility=self.visibility) - else: + else: name_declarator, type = self.declarator.analyse( base_type, env, nonempty=2 * (self.body is not None), visibility=self.visibility) - if not type.is_cfunction: + if not type.is_cfunction: error(self.pos, "Suite attached to non-function declaration") - # Remember the actual type according to the function header - # written here, because the type in the symbol table entry - # may be different if we're overriding a C method inherited - # from the base type of an extension type. - self.type = type - type.is_overridable = self.overridable - declarator = self.declarator - while not hasattr(declarator, 'args'): - declarator = declarator.base - - self.cfunc_declarator = declarator - self.args = declarator.args - - opt_arg_count = self.cfunc_declarator.optional_arg_count - if (self.visibility == 'public' or self.api) and opt_arg_count: - error(self.cfunc_declarator.pos, + # Remember the actual type according to the function header + # written here, because the type in the symbol table entry + # may be different if we're overriding a C method inherited + # from the base type of an extension type. + self.type = type + type.is_overridable = self.overridable + declarator = self.declarator + while not hasattr(declarator, 'args'): + declarator = declarator.base + + self.cfunc_declarator = declarator + self.args = declarator.args + + opt_arg_count = self.cfunc_declarator.optional_arg_count + if (self.visibility == 'public' or self.api) and opt_arg_count: + error(self.cfunc_declarator.pos, "Function with optional arguments may not be declared public or api") - + if type.exception_check == '+' and self.visibility != 'extern': - warning(self.cfunc_declarator.pos, - "Only extern functions can throw C++ exceptions.") - - for formal_arg, type_arg in zip(self.args, type.args): - self.align_argument_type(env, type_arg) - formal_arg.type = type_arg.type - formal_arg.name = type_arg.name - formal_arg.cname = type_arg.cname - - self._validate_type_visibility(type_arg.type, type_arg.pos, env) - - if type_arg.type.is_fused: - self.has_fused_arguments = True - - if type_arg.type.is_buffer and 'inline' in self.modifiers: - warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1) - + warning(self.cfunc_declarator.pos, + "Only extern functions can throw C++ exceptions.") + + for formal_arg, type_arg in zip(self.args, type.args): + self.align_argument_type(env, type_arg) + formal_arg.type = type_arg.type + formal_arg.name = type_arg.name + formal_arg.cname = type_arg.cname + + self._validate_type_visibility(type_arg.type, type_arg.pos, env) + + if type_arg.type.is_fused: + self.has_fused_arguments = True + + if type_arg.type.is_buffer and 'inline' in self.modifiers: + warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1) + if type_arg.type.is_buffer or type_arg.type.is_pythran_expr: - if self.type.nogil: - error(formal_arg.pos, + if self.type.nogil: + error(formal_arg.pos, "Buffer may not be acquired without the GIL. Consider using memoryview slices instead.") - elif 'inline' in self.modifiers: - warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1) - - self._validate_type_visibility(type.return_type, self.pos, env) - - name = name_declarator.name - cname = name_declarator.cname - - type.is_const_method = self.is_const_method - type.is_static_method = self.is_static_method - self.entry = env.declare_cfunction( - name, type, self.pos, + elif 'inline' in self.modifiers: + warning(formal_arg.pos, "Buffer unpacking not optimized away.", 1) + + self._validate_type_visibility(type.return_type, self.pos, env) + + name = name_declarator.name + cname = name_declarator.cname + + type.is_const_method = self.is_const_method + type.is_static_method = self.is_static_method + self.entry = env.declare_cfunction( + name, type, self.pos, cname=cname, visibility=self.visibility, api=self.api, defining=self.body is not None, modifiers=self.modifiers, overridable=self.overridable) - self.entry.inline_func_in_pxd = self.inline_in_pxd - self.return_type = type.return_type - if self.return_type.is_array and self.visibility != 'extern': + self.entry.inline_func_in_pxd = self.inline_in_pxd + self.return_type = type.return_type + if self.return_type.is_array and self.visibility != 'extern': error(self.pos, "Function cannot return an array") - if self.return_type.is_cpp_class: - self.return_type.check_nullary_constructor(self.pos, "used as a return value") - - 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: - # An error will be produced in the cdef function - self.overridable = False - - self.declare_cpdef_wrapper(env) - self.create_local_scope(env) - - def declare_cpdef_wrapper(self, env): - 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 + if self.return_type.is_cpp_class: + self.return_type.check_nullary_constructor(self.pos, "used as a return value") + + 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: + # An error will be produced in the cdef function + self.overridable = False + + self.declare_cpdef_wrapper(env) + self.create_local_scope(env) + + def declare_cpdef_wrapper(self, env): + 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 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 = [] + 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, name=self.entry.name, args=self.args, @@ -2456,353 +2456,353 @@ class CFuncDefNode(FuncDefNode): body=py_func_body, decorators=decorators, is_wrapper=1) - self.py_func.is_module_scope = env.is_module_scope - self.py_func.analyse_declarations(env) + self.py_func.is_module_scope = env.is_module_scope + self.py_func.analyse_declarations(env) self.py_func.entry.is_overridable = True self.py_func_stat = StatListNode(self.pos, stats=[self.py_func]) - self.py_func.type = PyrexTypes.py_object_type - self.entry.as_variable = self.py_func.entry - self.entry.used = self.entry.as_variable.used = True - # Reset scope entry the above cfunction - env.entries[name] = self.entry - if (not self.entry.is_final_cmethod and + self.py_func.type = PyrexTypes.py_object_type + self.entry.as_variable = self.py_func.entry + self.entry.used = self.entry.as_variable.used = True + # Reset scope entry the above cfunction + env.entries[name] = self.entry + if (not self.entry.is_final_cmethod and (not env.is_module_scope or Options.lookup_module_cpdef)): self.override = OverrideCheckNode(self.pos, py_func=self.py_func) - self.body = StatListNode(self.pos, stats=[self.override, self.body]) - - def _validate_type_visibility(self, type, pos, env): - """ - Ensure that types used in cdef functions are public or api, or - defined in a C header. - """ - public_or_api = (self.visibility == 'public' or self.api) - entry = getattr(type, 'entry', None) - if public_or_api and entry and env.is_module_scope: - if not (entry.visibility in ('public', 'extern') or - entry.api or entry.in_cinclude): + self.body = StatListNode(self.pos, stats=[self.override, self.body]) + + def _validate_type_visibility(self, type, pos, env): + """ + Ensure that types used in cdef functions are public or api, or + defined in a C header. + """ + public_or_api = (self.visibility == 'public' or self.api) + entry = getattr(type, 'entry', None) + if public_or_api and entry and env.is_module_scope: + if not (entry.visibility in ('public', 'extern') or + entry.api or entry.in_cinclude): error(pos, "Function declared public or api may not have private types") - - def call_self_node(self, omit_optional_args=0, is_module_scope=0): - from . import ExprNodes - args = self.type.args - if omit_optional_args: - args = args[:len(args) - self.type.optional_arg_count] - arg_names = [arg.name for arg in args] - if is_module_scope: - 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: - type_entry = self.type.args[0].type.entry - type_arg = ExprNodes.NameNode(self.pos, name=type_entry.name) - type_arg.entry = type_entry - cfunc = ExprNodes.AttributeNode(self.pos, obj=type_arg, attribute=self.entry.name) - skip_dispatch = not is_module_scope or Options.lookup_module_cpdef - c_call = ExprNodes.SimpleCallNode( - self.pos, - function=cfunc, - args=[ExprNodes.NameNode(self.pos, name=n) for n in arg_names], - wrapper_call=skip_dispatch) - return ReturnStatNode(pos=self.pos, return_type=PyrexTypes.py_object_type, value=c_call) - - def declare_arguments(self, env): - for arg in self.type.args: - if not arg.name: - error(arg.pos, "Missing argument name") - self.declare_argument(env, arg) - - def need_gil_acquisition(self, lenv): - return self.type.with_gil - - def nogil_check(self, env): - type = self.type - with_gil = type.with_gil - if type.nogil and not with_gil: - if type.return_type.is_pyobject: - error(self.pos, - "Function with Python return type cannot be declared nogil") - for entry in self.local_scope.var_entries: - if entry.type.is_pyobject and not entry.in_with_gil_block: - error(self.pos, "Function declared nogil has Python locals or temporaries") - - def analyse_expressions(self, env): - self.local_scope.directives = env.directives + + def call_self_node(self, omit_optional_args=0, is_module_scope=0): + from . import ExprNodes + args = self.type.args + if omit_optional_args: + args = args[:len(args) - self.type.optional_arg_count] + arg_names = [arg.name for arg in args] + if is_module_scope: + 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: + type_entry = self.type.args[0].type.entry + type_arg = ExprNodes.NameNode(self.pos, name=type_entry.name) + type_arg.entry = type_entry + cfunc = ExprNodes.AttributeNode(self.pos, obj=type_arg, attribute=self.entry.name) + skip_dispatch = not is_module_scope or Options.lookup_module_cpdef + c_call = ExprNodes.SimpleCallNode( + self.pos, + function=cfunc, + args=[ExprNodes.NameNode(self.pos, name=n) for n in arg_names], + wrapper_call=skip_dispatch) + return ReturnStatNode(pos=self.pos, return_type=PyrexTypes.py_object_type, value=c_call) + + def declare_arguments(self, env): + for arg in self.type.args: + if not arg.name: + error(arg.pos, "Missing argument name") + self.declare_argument(env, arg) + + def need_gil_acquisition(self, lenv): + return self.type.with_gil + + def nogil_check(self, env): + type = self.type + with_gil = type.with_gil + if type.nogil and not with_gil: + if type.return_type.is_pyobject: + error(self.pos, + "Function with Python return type cannot be declared nogil") + for entry in self.local_scope.var_entries: + if entry.type.is_pyobject and not entry.in_with_gil_block: + error(self.pos, "Function declared nogil has Python locals or temporaries") + + def analyse_expressions(self, env): + self.local_scope.directives = env.directives if self.py_func_stat is not None: # this will also analyse the default values and the function name assignment self.py_func_stat = self.py_func_stat.analyse_expressions(env) elif self.py_func is not None: - # this will also analyse the default values - self.py_func = self.py_func.analyse_expressions(env) - else: - self.analyse_default_values(env) + # this will also analyse the default values + self.py_func = self.py_func.analyse_expressions(env) + else: + self.analyse_default_values(env) self.analyse_annotations(env) - self.acquire_gil = self.need_gil_acquisition(self.local_scope) - return self - - def needs_assignment_synthesis(self, env, code=None): - return False - + self.acquire_gil = self.need_gil_acquisition(self.local_scope) + return self + + def needs_assignment_synthesis(self, env, code=None): + return False + def generate_function_header(self, code, with_pymethdef, with_opt_args=1, with_dispatch=1, cname=None): - scope = self.local_scope - arg_decls = [] - type = self.type - for arg in type.args[:len(type.args)-type.optional_arg_count]: - arg_decl = arg.declaration_code() - entry = scope.lookup(arg.name) - if not entry.cf_used: - arg_decl = 'CYTHON_UNUSED %s' % arg_decl - arg_decls.append(arg_decl) - if with_dispatch and self.overridable: - dispatch_arg = PyrexTypes.c_int_type.declaration_code( - Naming.skip_dispatch_cname) - if self.override: - arg_decls.append(dispatch_arg) - else: - arg_decls.append('CYTHON_UNUSED %s' % dispatch_arg) - if type.optional_arg_count and with_opt_args: - arg_decls.append(type.op_arg_struct.declaration_code(Naming.optional_args_cname)) - if type.has_varargs: - arg_decls.append("...") - if not arg_decls: - arg_decls = ["void"] - if cname is None: - cname = self.entry.func_cname - entity = type.function_header_code(cname, ', '.join(arg_decls)) - if self.entry.visibility == 'private' and '::' not in cname: - storage_class = "static " - else: - storage_class = "" - dll_linkage = None - modifiers = code.build_function_modifiers(self.entry.func_modifiers) - - header = self.return_type.declaration_code(entity, dll_linkage=dll_linkage) - #print (storage_class, modifiers, header) - needs_proto = self.is_c_class_method - if self.template_declaration: - if needs_proto: - code.globalstate.parts['module_declarations'].putln(self.template_declaration) - code.putln(self.template_declaration) - if needs_proto: + scope = self.local_scope + arg_decls = [] + type = self.type + for arg in type.args[:len(type.args)-type.optional_arg_count]: + arg_decl = arg.declaration_code() + entry = scope.lookup(arg.name) + if not entry.cf_used: + arg_decl = 'CYTHON_UNUSED %s' % arg_decl + arg_decls.append(arg_decl) + if with_dispatch and self.overridable: + dispatch_arg = PyrexTypes.c_int_type.declaration_code( + Naming.skip_dispatch_cname) + if self.override: + arg_decls.append(dispatch_arg) + else: + arg_decls.append('CYTHON_UNUSED %s' % dispatch_arg) + if type.optional_arg_count and with_opt_args: + arg_decls.append(type.op_arg_struct.declaration_code(Naming.optional_args_cname)) + if type.has_varargs: + arg_decls.append("...") + if not arg_decls: + arg_decls = ["void"] + if cname is None: + cname = self.entry.func_cname + entity = type.function_header_code(cname, ', '.join(arg_decls)) + if self.entry.visibility == 'private' and '::' not in cname: + storage_class = "static " + else: + storage_class = "" + dll_linkage = None + modifiers = code.build_function_modifiers(self.entry.func_modifiers) + + header = self.return_type.declaration_code(entity, dll_linkage=dll_linkage) + #print (storage_class, modifiers, header) + needs_proto = self.is_c_class_method + if self.template_declaration: + if needs_proto: + code.globalstate.parts['module_declarations'].putln(self.template_declaration) + code.putln(self.template_declaration) + if needs_proto: code.globalstate.parts['module_declarations'].putln( "%s%s%s; /* proto*/" % (storage_class, modifiers, header)) - code.putln("%s%s%s {" % (storage_class, modifiers, header)) - - def generate_argument_declarations(self, env, code): - scope = self.local_scope - for arg in self.args: - if arg.default: - entry = scope.lookup(arg.name) - if self.override or entry.cf_used: - result = arg.calculate_default_value_code(code) - code.putln('%s = %s;' % ( - arg.type.declaration_code(arg.cname), result)) - - def generate_keyword_list(self, code): - pass - - def generate_argument_parsing_code(self, env, code): - i = 0 - used = 0 - scope = self.local_scope - if self.type.optional_arg_count: - code.putln('if (%s) {' % Naming.optional_args_cname) - for arg in self.args: - if arg.default: - entry = scope.lookup(arg.name) - if self.override or entry.cf_used: - code.putln('if (%s->%sn > %s) {' % - (Naming.optional_args_cname, - Naming.pyrex_prefix, i)) - declarator = arg.declarator - while not hasattr(declarator, 'name'): - declarator = declarator.base - code.putln('%s = %s->%s;' % - (arg.cname, Naming.optional_args_cname, - self.type.opt_arg_cname(declarator.name))) - used += 1 - i += 1 - for _ in range(used): - code.putln('}') - code.putln('}') - - # Move arguments into closure if required - def put_into_closure(entry): - if entry.in_closure and not arg.default: - code.putln('%s = %s;' % (entry.cname, entry.original_cname)) - code.put_var_incref(entry) - code.put_var_giveref(entry) - for arg in self.args: - put_into_closure(scope.lookup_here(arg.name)) - - - def generate_argument_conversion_code(self, code): - pass - - def generate_argument_type_tests(self, code): - # Generate type tests for args whose type in a parent - # class is a supertype of the declared type. - for arg in self.type.args: - if arg.needs_type_test: - self.generate_arg_type_test(arg, code) - elif arg.type.is_pyobject and not arg.accept_none: - self.generate_arg_none_check(arg, code) - - def generate_execution_code(self, code): + code.putln("%s%s%s {" % (storage_class, modifiers, header)) + + def generate_argument_declarations(self, env, code): + scope = self.local_scope + for arg in self.args: + if arg.default: + entry = scope.lookup(arg.name) + if self.override or entry.cf_used: + result = arg.calculate_default_value_code(code) + code.putln('%s = %s;' % ( + arg.type.declaration_code(arg.cname), result)) + + def generate_keyword_list(self, code): + pass + + def generate_argument_parsing_code(self, env, code): + i = 0 + used = 0 + scope = self.local_scope + if self.type.optional_arg_count: + code.putln('if (%s) {' % Naming.optional_args_cname) + for arg in self.args: + if arg.default: + entry = scope.lookup(arg.name) + if self.override or entry.cf_used: + code.putln('if (%s->%sn > %s) {' % + (Naming.optional_args_cname, + Naming.pyrex_prefix, i)) + declarator = arg.declarator + while not hasattr(declarator, 'name'): + declarator = declarator.base + code.putln('%s = %s->%s;' % + (arg.cname, Naming.optional_args_cname, + self.type.opt_arg_cname(declarator.name))) + used += 1 + i += 1 + for _ in range(used): + code.putln('}') + code.putln('}') + + # Move arguments into closure if required + def put_into_closure(entry): + if entry.in_closure and not arg.default: + code.putln('%s = %s;' % (entry.cname, entry.original_cname)) + code.put_var_incref(entry) + code.put_var_giveref(entry) + for arg in self.args: + put_into_closure(scope.lookup_here(arg.name)) + + + def generate_argument_conversion_code(self, code): + pass + + def generate_argument_type_tests(self, code): + # Generate type tests for args whose type in a parent + # class is a supertype of the declared type. + for arg in self.type.args: + if arg.needs_type_test: + self.generate_arg_type_test(arg, code) + elif arg.type.is_pyobject and not arg.accept_none: + self.generate_arg_none_check(arg, code) + + def generate_execution_code(self, code): if code.globalstate.directives['linetrace']: code.mark_pos(self.pos) code.putln("") # generate line tracing 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): - if self.return_type.is_pyobject: - return "0" - else: - #return None - return self.entry.type.exception_value - - def caller_will_check_exceptions(self): - return self.entry.type.exception_check - - def generate_wrapper_functions(self, code): - # If the C signature of a function has changed, we need to generate - # wrappers to put in the slots here. - k = 0 - entry = self.entry - func_type = entry.type - while entry.prev_entry is not None: - k += 1 - entry = entry.prev_entry - entry.func_cname = "%s%swrap_%s" % (self.entry.func_cname, Naming.pyrex_prefix, k) - code.putln() + super(CFuncDefNode, self).generate_execution_code(code) + if self.py_func_stat: + self.py_func_stat.generate_execution_code(code) + + def error_value(self): + if self.return_type.is_pyobject: + return "0" + else: + #return None + return self.entry.type.exception_value + + def caller_will_check_exceptions(self): + return self.entry.type.exception_check + + def generate_wrapper_functions(self, code): + # If the C signature of a function has changed, we need to generate + # wrappers to put in the slots here. + k = 0 + entry = self.entry + func_type = entry.type + while entry.prev_entry is not None: + k += 1 + entry = entry.prev_entry + entry.func_cname = "%s%swrap_%s" % (self.entry.func_cname, Naming.pyrex_prefix, k) + code.putln() self.generate_function_header( code, 0, with_dispatch=entry.type.is_overridable, with_opt_args=entry.type.optional_arg_count, cname=entry.func_cname) - if not self.return_type.is_void: - code.put('return ') - args = self.type.args - arglist = [arg.cname for arg in args[:len(args)-self.type.optional_arg_count]] - if entry.type.is_overridable: - arglist.append(Naming.skip_dispatch_cname) - elif func_type.is_overridable: - arglist.append('0') - if entry.type.optional_arg_count: - arglist.append(Naming.optional_args_cname) - elif func_type.optional_arg_count: - arglist.append('NULL') - code.putln('%s(%s);' % (self.entry.func_cname, ', '.join(arglist))) - code.putln('}') - - -class PyArgDeclNode(Node): - # Argument which must be a Python object (used - # for * and ** arguments). - # - # name string - # entry Symtab.Entry - # annotation ExprNode or None Py3 argument annotation - child_attrs = [] - is_self_arg = False - is_type_arg = False - - def generate_function_definitions(self, env, code): - self.entry.generate_function_definitions(env, code) - - -class DecoratorNode(Node): - # A decorator - # - # decorator NameNode or CallNode or AttributeNode - child_attrs = ['decorator'] - - -class DefNode(FuncDefNode): - # A Python function definition. - # - # name string the Python name of the function - # lambda_name string the internal name of a lambda 'function' - # decorators [DecoratorNode] list of decorators - # args [CArgDeclNode] formal arguments - # doc EncodedString or None - # body StatListNode - # return_type_annotation - # ExprNode or None the Py3 return type annotation - # - # The following subnode is constructed internally - # when the def statement is inside a Python class definition. - # - # fused_py_func DefNode The original fused cpdef DefNode - # (in case this is a specialization) - # specialized_cpdefs [DefNode] list of specialized cpdef DefNodes - # py_cfunc_node PyCFunctionNode/InnerFunctionNode The PyCFunction to create and assign - # - # decorator_indirection IndirectionNode Used to remove __Pyx_Method_ClassMethod for fused functions - + if not self.return_type.is_void: + code.put('return ') + args = self.type.args + arglist = [arg.cname for arg in args[:len(args)-self.type.optional_arg_count]] + if entry.type.is_overridable: + arglist.append(Naming.skip_dispatch_cname) + elif func_type.is_overridable: + arglist.append('0') + if entry.type.optional_arg_count: + arglist.append(Naming.optional_args_cname) + elif func_type.optional_arg_count: + arglist.append('NULL') + code.putln('%s(%s);' % (self.entry.func_cname, ', '.join(arglist))) + code.putln('}') + + +class PyArgDeclNode(Node): + # Argument which must be a Python object (used + # for * and ** arguments). + # + # name string + # entry Symtab.Entry + # annotation ExprNode or None Py3 argument annotation + child_attrs = [] + is_self_arg = False + is_type_arg = False + + def generate_function_definitions(self, env, code): + self.entry.generate_function_definitions(env, code) + + +class DecoratorNode(Node): + # A decorator + # + # decorator NameNode or CallNode or AttributeNode + child_attrs = ['decorator'] + + +class DefNode(FuncDefNode): + # A Python function definition. + # + # name string the Python name of the function + # lambda_name string the internal name of a lambda 'function' + # decorators [DecoratorNode] list of decorators + # args [CArgDeclNode] formal arguments + # doc EncodedString or None + # body StatListNode + # return_type_annotation + # ExprNode or None the Py3 return type annotation + # + # The following subnode is constructed internally + # when the def statement is inside a Python class definition. + # + # fused_py_func DefNode The original fused cpdef DefNode + # (in case this is a specialization) + # specialized_cpdefs [DefNode] list of specialized cpdef DefNodes + # py_cfunc_node PyCFunctionNode/InnerFunctionNode The PyCFunction to create and assign + # + # decorator_indirection IndirectionNode Used to remove __Pyx_Method_ClassMethod for fused functions + child_attrs = ["args", "star_arg", "starstar_arg", "body", "decorators", "return_type_annotation"] outer_attrs = ["decorators", "return_type_annotation"] - + is_staticmethod = False is_classmethod = False - lambda_name = None - reqd_kw_flags_cname = "0" - is_wrapper = 0 - no_assignment_synthesis = 0 - decorators = None - return_type_annotation = None - entry = None - acquire_gil = 0 - self_in_stararg = 0 - py_cfunc_node = None - requires_classobj = False - defaults_struct = None # Dynamic kwrds structure name - doc = None - - fused_py_func = False - specialized_cpdefs = None - py_wrapper = None - py_wrapper_required = True - func_cname = None - - defaults_getter = None - - def __init__(self, pos, **kwds): - FuncDefNode.__init__(self, pos, **kwds) - k = rk = r = 0 - for arg in self.args: - if arg.kw_only: - k += 1 - if not arg.default: - rk += 1 - if not arg.default: - r += 1 - self.num_kwonly_args = k - self.num_required_kw_args = rk - self.num_required_args = r - + lambda_name = None + reqd_kw_flags_cname = "0" + is_wrapper = 0 + no_assignment_synthesis = 0 + decorators = None + return_type_annotation = None + entry = None + acquire_gil = 0 + self_in_stararg = 0 + py_cfunc_node = None + requires_classobj = False + defaults_struct = None # Dynamic kwrds structure name + doc = None + + fused_py_func = False + specialized_cpdefs = None + py_wrapper = None + py_wrapper_required = True + func_cname = None + + defaults_getter = None + + def __init__(self, pos, **kwds): + FuncDefNode.__init__(self, pos, **kwds) + k = rk = r = 0 + for arg in self.args: + if arg.kw_only: + k += 1 + if not arg.default: + rk += 1 + if not arg.default: + r += 1 + self.num_kwonly_args = k + self.num_required_kw_args = rk + self.num_required_args = r + def as_cfunction(self, cfunc=None, scope=None, overridable=True, returns=None, except_val=None, modifiers=None, nogil=False, with_gil=False): - if self.star_arg: - error(self.star_arg.pos, "cdef function cannot have star argument") - if self.starstar_arg: - error(self.starstar_arg.pos, "cdef function cannot have starstar argument") + if self.star_arg: + error(self.star_arg.pos, "cdef function cannot have star argument") + if self.starstar_arg: + error(self.starstar_arg.pos, "cdef function cannot have starstar argument") exception_value, exception_check = except_val or (None, False) - if cfunc is None: - cfunc_args = [] - for formal_arg in self.args: - name_declarator, type = formal_arg.analyse(scope, nonempty=1) + if cfunc is None: + cfunc_args = [] + for formal_arg in self.args: + name_declarator, type = formal_arg.analyse(scope, nonempty=1) cfunc_args.append(PyrexTypes.CFuncTypeArg(name=name_declarator.name, cname=None, annotation=formal_arg.annotation, @@ -2816,26 +2816,26 @@ class DefNode(FuncDefNode): nogil=nogil, with_gil=with_gil, is_overridable=overridable) - cfunc = CVarDefNode(self.pos, type=cfunc_type) - else: - if scope is None: - scope = cfunc.scope - cfunc_type = cfunc.type - if len(self.args) != len(cfunc_type.args) or cfunc_type.has_varargs: - error(self.pos, "wrong number of arguments") - error(cfunc.pos, "previous declaration here") - for i, (formal_arg, type_arg) in enumerate(zip(self.args, cfunc_type.args)): - name_declarator, type = formal_arg.analyse(scope, nonempty=1, + cfunc = CVarDefNode(self.pos, type=cfunc_type) + else: + if scope is None: + scope = cfunc.scope + cfunc_type = cfunc.type + if len(self.args) != len(cfunc_type.args) or cfunc_type.has_varargs: + error(self.pos, "wrong number of arguments") + error(cfunc.pos, "previous declaration here") + for i, (formal_arg, type_arg) in enumerate(zip(self.args, cfunc_type.args)): + name_declarator, type = formal_arg.analyse(scope, nonempty=1, is_self_arg=(i == 0 and scope.is_c_class_scope)) - if type is None or type is PyrexTypes.py_object_type: - formal_arg.type = type_arg.type - formal_arg.name_declarator = name_declarator + if type is None or type is PyrexTypes.py_object_type: + formal_arg.type = type_arg.type + formal_arg.name_declarator = name_declarator if exception_value is None and cfunc_type.exception_value is not None: from .ExprNodes import ConstNode exception_value = ConstNode( self.pos, value=cfunc_type.exception_value, type=cfunc_type.return_type) - declarator = CFuncDeclaratorNode(self.pos, + declarator = CFuncDeclaratorNode(self.pos, base=CNameDeclaratorNode(self.pos, name=self.name, cname=None), args=self.args, has_varargs=False, @@ -2843,7 +2843,7 @@ class DefNode(FuncDefNode): exception_value=exception_value, with_gil=cfunc_type.with_gil, nogil=cfunc_type.nogil) - return CFuncDefNode(self.pos, + return CFuncDefNode(self.pos, modifiers=modifiers or [], base_type=CAnalysedBaseTypeNode(self.pos, type=cfunc_type.return_type), declarator=declarator, @@ -2857,44 +2857,44 @@ class DefNode(FuncDefNode): api=False, directive_locals=getattr(cfunc, 'directive_locals', {}), directive_returns=returns) - - def is_cdef_func_compatible(self): - """Determines if the function's signature is compatible with a - cdef function. This can be used before calling - .as_cfunction() to see if that will be successful. - """ - if self.needs_closure: - return False - if self.star_arg or self.starstar_arg: - return False - return True - - def analyse_declarations(self, env): - if self.decorators: - for decorator in self.decorators: - func = decorator.decorator - if func.is_name: - self.is_classmethod |= func.name == 'classmethod' - self.is_staticmethod |= func.name == 'staticmethod' - - if self.is_classmethod and env.lookup_here('classmethod'): - # classmethod() was overridden - not much we can do here ... - self.is_classmethod = False - if self.is_staticmethod and env.lookup_here('staticmethod'): - # staticmethod() was overridden - not much we can do here ... - self.is_staticmethod = False - - if self.name == '__new__' and env.is_py_class_scope: - self.is_staticmethod = 1 - - self.analyse_argument_types(env) - if self.name == '<lambda>': - self.declare_lambda_function(env) - else: - self.declare_pyfunction(env) - - self.analyse_signature(env) - self.return_type = self.entry.signature.return_type() + + def is_cdef_func_compatible(self): + """Determines if the function's signature is compatible with a + cdef function. This can be used before calling + .as_cfunction() to see if that will be successful. + """ + if self.needs_closure: + return False + if self.star_arg or self.starstar_arg: + return False + return True + + def analyse_declarations(self, env): + if self.decorators: + for decorator in self.decorators: + func = decorator.decorator + if func.is_name: + self.is_classmethod |= func.name == 'classmethod' + self.is_staticmethod |= func.name == 'staticmethod' + + if self.is_classmethod and env.lookup_here('classmethod'): + # classmethod() was overridden - not much we can do here ... + self.is_classmethod = False + if self.is_staticmethod and env.lookup_here('staticmethod'): + # staticmethod() was overridden - not much we can do here ... + self.is_staticmethod = False + + if self.name == '__new__' and env.is_py_class_scope: + self.is_staticmethod = 1 + + self.analyse_argument_types(env) + if self.name == '<lambda>': + self.declare_lambda_function(env) + else: + self.declare_pyfunction(env) + + self.analyse_signature(env) + self.return_type = self.entry.signature.return_type() # 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 env.directives['annotation_typing'] and not self.entry.is_special: @@ -2902,30 +2902,30 @@ class DefNode(FuncDefNode): if return_type and return_type.is_pyobject: self.return_type = return_type - self.create_local_scope(env) - - self.py_wrapper = DefNodeWrapper( - self.pos, - target=self, - name=self.entry.name, - args=self.args, - star_arg=self.star_arg, - starstar_arg=self.starstar_arg, - return_type=self.return_type) - self.py_wrapper.analyse_declarations(env) - - def analyse_argument_types(self, env): + self.create_local_scope(env) + + self.py_wrapper = DefNodeWrapper( + self.pos, + target=self, + name=self.entry.name, + args=self.args, + star_arg=self.star_arg, + starstar_arg=self.starstar_arg, + return_type=self.return_type) + self.py_wrapper.analyse_declarations(env) + + def analyse_argument_types(self, env): self.directive_locals = env.directives.get('locals', {}) - allow_none_for_extension_args = env.directives['allow_none_for_extension_args'] - - f2s = env.fused_to_specific - env.fused_to_specific = None - - for arg in self.args: - if hasattr(arg, 'name'): - name_declarator = None - else: - base_type = arg.base_type.analyse(env) + allow_none_for_extension_args = env.directives['allow_none_for_extension_args'] + + f2s = env.fused_to_specific + env.fused_to_specific = None + + for arg in self.args: + if hasattr(arg, 'name'): + name_declarator = None + else: + base_type = arg.base_type.analyse(env) # If we hare in pythran mode and we got a buffer supported by # Pythran, we change this node to a fused type if has_np_pythran(env) and base_type.is_pythran_expr: @@ -2933,308 +2933,308 @@ class DefNode(FuncDefNode): base_type, #PyrexTypes.PythranExpr(pythran_type(self.type, "numpy_texpr")), base_type.org_buffer]) - name_declarator, type = \ - arg.declarator.analyse(base_type, env) - arg.name = name_declarator.name - arg.type = type - - if type.is_fused: - self.has_fused_arguments = True - - self.align_argument_type(env, arg) - if name_declarator and name_declarator.cname: + name_declarator, type = \ + arg.declarator.analyse(base_type, env) + arg.name = name_declarator.name + arg.type = type + + if type.is_fused: + self.has_fused_arguments = True + + self.align_argument_type(env, arg) + if name_declarator and name_declarator.cname: error(self.pos, "Python function argument cannot have C name specification") - arg.type = arg.type.as_argument_type() - arg.hdr_type = None - arg.needs_conversion = 0 - arg.needs_type_test = 0 - arg.is_generic = 1 - if arg.type.is_pyobject or arg.type.is_buffer or arg.type.is_memoryviewslice: - if arg.or_none: - arg.accept_none = True - elif arg.not_none: - arg.accept_none = False - elif (arg.type.is_extension_type or arg.type.is_builtin_type + arg.type = arg.type.as_argument_type() + arg.hdr_type = None + arg.needs_conversion = 0 + arg.needs_type_test = 0 + arg.is_generic = 1 + if arg.type.is_pyobject or arg.type.is_buffer or arg.type.is_memoryviewslice: + if arg.or_none: + arg.accept_none = True + elif arg.not_none: + arg.accept_none = False + elif (arg.type.is_extension_type or arg.type.is_builtin_type or arg.type.is_buffer or arg.type.is_memoryviewslice): - if arg.default and arg.default.constant_result is None: - # special case: def func(MyType obj = None) - arg.accept_none = True - else: - # default depends on compiler directive - arg.accept_none = allow_none_for_extension_args - else: - # probably just a plain 'object' - arg.accept_none = True - else: - arg.accept_none = True # won't be used, but must be there - if arg.not_none: - error(arg.pos, "Only Python type arguments can have 'not None'") - if arg.or_none: - error(arg.pos, "Only Python type arguments can have 'or None'") - env.fused_to_specific = f2s - + if arg.default and arg.default.constant_result is None: + # special case: def func(MyType obj = None) + arg.accept_none = True + else: + # default depends on compiler directive + arg.accept_none = allow_none_for_extension_args + else: + # probably just a plain 'object' + arg.accept_none = True + else: + arg.accept_none = True # won't be used, but must be there + if arg.not_none: + error(arg.pos, "Only Python type arguments can have 'not None'") + if arg.or_none: + error(arg.pos, "Only Python type arguments can have 'or None'") + env.fused_to_specific = f2s + if has_np_pythran(env): self.np_args_idx = [i for i,a in enumerate(self.args) if a.type.is_numpy_buffer] else: self.np_args_idx = [] - def analyse_signature(self, env): - if self.entry.is_special: - if self.decorators: - error(self.pos, "special functions of cdef classes cannot have decorators") - self.entry.trivial_signature = len(self.args) == 1 and not (self.star_arg or self.starstar_arg) - elif not env.directives['always_allow_keywords'] and not (self.star_arg or self.starstar_arg): - # Use the simpler calling signature for zero- and one-argument functions. - if self.entry.signature is TypeSlots.pyfunction_signature: - if len(self.args) == 0: - self.entry.signature = TypeSlots.pyfunction_noargs - elif len(self.args) == 1: - if self.args[0].default is None and not self.args[0].kw_only: - self.entry.signature = TypeSlots.pyfunction_onearg - elif self.entry.signature is TypeSlots.pymethod_signature: - if len(self.args) == 1: - self.entry.signature = TypeSlots.unaryfunc - elif len(self.args) == 2: - if self.args[1].default is None and not self.args[1].kw_only: - self.entry.signature = TypeSlots.ibinaryfunc - - sig = self.entry.signature - nfixed = sig.num_fixed_args() + def analyse_signature(self, env): + if self.entry.is_special: + if self.decorators: + error(self.pos, "special functions of cdef classes cannot have decorators") + self.entry.trivial_signature = len(self.args) == 1 and not (self.star_arg or self.starstar_arg) + elif not env.directives['always_allow_keywords'] and not (self.star_arg or self.starstar_arg): + # Use the simpler calling signature for zero- and one-argument functions. + if self.entry.signature is TypeSlots.pyfunction_signature: + if len(self.args) == 0: + self.entry.signature = TypeSlots.pyfunction_noargs + elif len(self.args) == 1: + if self.args[0].default is None and not self.args[0].kw_only: + self.entry.signature = TypeSlots.pyfunction_onearg + elif self.entry.signature is TypeSlots.pymethod_signature: + if len(self.args) == 1: + self.entry.signature = TypeSlots.unaryfunc + elif len(self.args) == 2: + if self.args[1].default is None and not self.args[1].kw_only: + self.entry.signature = TypeSlots.ibinaryfunc + + sig = self.entry.signature + nfixed = sig.num_fixed_args() if (sig is TypeSlots.pymethod_signature and nfixed == 1 and len(self.args) == 0 and self.star_arg): - # this is the only case where a diverging number of - # arguments is not an error - when we have no explicit - # 'self' parameter as in method(*args) - sig = self.entry.signature = TypeSlots.pyfunction_signature # self is not 'really' used - self.self_in_stararg = 1 - nfixed = 0 - - if self.is_staticmethod and env.is_c_class_scope: - nfixed = 0 - self.self_in_stararg = True # FIXME: why for staticmethods? - - self.entry.signature = sig = copy.copy(sig) - sig.fixed_arg_format = "*" - sig.is_staticmethod = True - sig.has_generic_args = True - - if ((self.is_classmethod or self.is_staticmethod) and + # this is the only case where a diverging number of + # arguments is not an error - when we have no explicit + # 'self' parameter as in method(*args) + sig = self.entry.signature = TypeSlots.pyfunction_signature # self is not 'really' used + self.self_in_stararg = 1 + nfixed = 0 + + if self.is_staticmethod and env.is_c_class_scope: + nfixed = 0 + self.self_in_stararg = True # FIXME: why for staticmethods? + + self.entry.signature = sig = copy.copy(sig) + sig.fixed_arg_format = "*" + sig.is_staticmethod = True + sig.has_generic_args = True + + if ((self.is_classmethod or self.is_staticmethod) and self.has_fused_arguments and env.is_c_class_scope): - del self.decorator_indirection.stats[:] - - for i in range(min(nfixed, len(self.args))): - arg = self.args[i] - arg.is_generic = 0 - if sig.is_self_arg(i) and not self.is_staticmethod: - if self.is_classmethod: - arg.is_type_arg = 1 - arg.hdr_type = arg.type = Builtin.type_type - else: - arg.is_self_arg = 1 - arg.hdr_type = arg.type = env.parent_type - arg.needs_conversion = 0 - else: - arg.hdr_type = sig.fixed_arg_type(i) - if not arg.type.same_as(arg.hdr_type): - if arg.hdr_type.is_pyobject and arg.type.is_pyobject: - arg.needs_type_test = 1 - else: - arg.needs_conversion = 1 - if arg.needs_conversion: - arg.hdr_cname = Naming.arg_prefix + arg.name - else: - arg.hdr_cname = Naming.var_prefix + arg.name - - if nfixed > len(self.args): - self.bad_signature() - return - elif nfixed < len(self.args): - if not sig.has_generic_args: - self.bad_signature() - for arg in self.args: + del self.decorator_indirection.stats[:] + + for i in range(min(nfixed, len(self.args))): + arg = self.args[i] + arg.is_generic = 0 + if sig.is_self_arg(i) and not self.is_staticmethod: + if self.is_classmethod: + arg.is_type_arg = 1 + arg.hdr_type = arg.type = Builtin.type_type + else: + arg.is_self_arg = 1 + arg.hdr_type = arg.type = env.parent_type + arg.needs_conversion = 0 + else: + arg.hdr_type = sig.fixed_arg_type(i) + if not arg.type.same_as(arg.hdr_type): + if arg.hdr_type.is_pyobject and arg.type.is_pyobject: + arg.needs_type_test = 1 + else: + arg.needs_conversion = 1 + if arg.needs_conversion: + arg.hdr_cname = Naming.arg_prefix + arg.name + else: + arg.hdr_cname = Naming.var_prefix + arg.name + + if nfixed > len(self.args): + self.bad_signature() + return + elif nfixed < len(self.args): + if not sig.has_generic_args: + self.bad_signature() + for arg in self.args: if arg.is_generic and (arg.type.is_extension_type or arg.type.is_builtin_type): - arg.needs_type_test = 1 - - def bad_signature(self): - sig = self.entry.signature - expected_str = "%d" % sig.num_fixed_args() - if sig.has_generic_args: - expected_str += " or more" - name = self.name - if name.startswith("__") and name.endswith("__"): - desc = "Special method" - else: - desc = "Method" + arg.needs_type_test = 1 + + def bad_signature(self): + sig = self.entry.signature + expected_str = "%d" % sig.num_fixed_args() + if sig.has_generic_args: + expected_str += " or more" + name = self.name + if name.startswith("__") and name.endswith("__"): + desc = "Special method" + else: + desc = "Method" error(self.pos, "%s %s has wrong number of arguments (%d declared, %s expected)" % ( desc, self.name, len(self.args), expected_str)) - - def declare_pyfunction(self, env): - #print "DefNode.declare_pyfunction:", self.name, "in", env ### - name = self.name - entry = env.lookup_here(name) - if entry: - if entry.is_final_cmethod and not env.parent_type.is_final_type: - error(self.pos, "Only final types can have final Python (def/cpdef) methods") + + def declare_pyfunction(self, env): + #print "DefNode.declare_pyfunction:", self.name, "in", env ### + name = self.name + entry = env.lookup_here(name) + if entry: + if entry.is_final_cmethod and not env.parent_type.is_final_type: + error(self.pos, "Only final types can have final Python (def/cpdef) methods") if entry.type.is_cfunction and not entry.is_builtin_cmethod and not self.is_wrapper: - warning(self.pos, "Overriding cdef method with def method.", 5) - entry = env.declare_pyfunction(name, self.pos, allow_redefine=not self.is_wrapper) - self.entry = entry - prefix = env.next_id(env.scope_prefix) - self.entry.pyfunc_cname = Naming.pyfunc_prefix + prefix + name - if Options.docstrings: - entry.doc = embed_position(self.pos, self.doc) - entry.doc_cname = Naming.funcdoc_prefix + prefix + name - if entry.is_special: + warning(self.pos, "Overriding cdef method with def method.", 5) + entry = env.declare_pyfunction(name, self.pos, allow_redefine=not self.is_wrapper) + self.entry = entry + prefix = env.next_id(env.scope_prefix) + self.entry.pyfunc_cname = Naming.pyfunc_prefix + prefix + name + if Options.docstrings: + entry.doc = embed_position(self.pos, self.doc) + entry.doc_cname = Naming.funcdoc_prefix + prefix + name + if entry.is_special: if entry.name in TypeSlots.invisible or not entry.doc or ( entry.name in '__getattr__' and env.directives['fast_getattr']): - entry.wrapperbase_cname = None - else: - entry.wrapperbase_cname = Naming.wrapperbase_prefix + prefix + name - else: - entry.doc = None - - def declare_lambda_function(self, env): - entry = env.declare_lambda_function(self.lambda_name, self.pos) - entry.doc = None - self.entry = entry - self.entry.pyfunc_cname = entry.cname - - def declare_arguments(self, env): - for arg in self.args: - if not arg.name: - error(arg.pos, "Missing argument name") - if arg.needs_conversion: - arg.entry = env.declare_var(arg.name, arg.type, arg.pos) - if arg.type.is_pyobject: - arg.entry.init = "0" - else: - arg.entry = self.declare_argument(env, arg) - arg.entry.is_arg = 1 - arg.entry.used = 1 - arg.entry.is_self_arg = arg.is_self_arg - self.declare_python_arg(env, self.star_arg) - self.declare_python_arg(env, self.starstar_arg) - - def declare_python_arg(self, env, arg): - if arg: - if env.directives['infer_types'] != False: - type = PyrexTypes.unspecified_type - else: - type = py_object_type - entry = env.declare_var(arg.name, type, arg.pos) - entry.is_arg = 1 - entry.used = 1 - entry.init = "0" - entry.xdecref_cleanup = 1 - arg.entry = entry - - def analyse_expressions(self, env): - self.local_scope.directives = env.directives - self.analyse_default_values(env) + entry.wrapperbase_cname = None + else: + entry.wrapperbase_cname = Naming.wrapperbase_prefix + prefix + name + else: + entry.doc = None + + def declare_lambda_function(self, env): + entry = env.declare_lambda_function(self.lambda_name, self.pos) + entry.doc = None + self.entry = entry + self.entry.pyfunc_cname = entry.cname + + def declare_arguments(self, env): + for arg in self.args: + if not arg.name: + error(arg.pos, "Missing argument name") + if arg.needs_conversion: + arg.entry = env.declare_var(arg.name, arg.type, arg.pos) + if arg.type.is_pyobject: + arg.entry.init = "0" + else: + arg.entry = self.declare_argument(env, arg) + arg.entry.is_arg = 1 + arg.entry.used = 1 + arg.entry.is_self_arg = arg.is_self_arg + self.declare_python_arg(env, self.star_arg) + self.declare_python_arg(env, self.starstar_arg) + + def declare_python_arg(self, env, arg): + if arg: + if env.directives['infer_types'] != False: + type = PyrexTypes.unspecified_type + else: + type = py_object_type + entry = env.declare_var(arg.name, type, arg.pos) + entry.is_arg = 1 + entry.used = 1 + entry.init = "0" + entry.xdecref_cleanup = 1 + arg.entry = entry + + def analyse_expressions(self, env): + self.local_scope.directives = env.directives + self.analyse_default_values(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: - for decorator in self.decorators[::-1]: - decorator.decorator = decorator.decorator.analyse_expressions(env) - - self.py_wrapper.prepare_argument_coercion(env) - return self - - def needs_assignment_synthesis(self, env, code=None): - if self.is_staticmethod: - return True + + if not self.needs_assignment_synthesis(env) and self.decorators: + for decorator in self.decorators[::-1]: + decorator.decorator = decorator.decorator.analyse_expressions(env) + + self.py_wrapper.prepare_argument_coercion(env) + return self + + def needs_assignment_synthesis(self, env, code=None): + if self.is_staticmethod: + return True if self.specialized_cpdefs or self.entry.is_fused_specialized: - return False - if self.no_assignment_synthesis: - return False + return False + if self.no_assignment_synthesis: + return False if self.entry.is_special: return False - if self.entry.is_anonymous: - return True + if self.entry.is_anonymous: + return True if env.is_module_scope or env.is_c_class_scope: - if code is None: + if code is None: return self.local_scope.directives['binding'] - else: - return code.globalstate.directives['binding'] - return env.is_py_class_scope or env.is_closure_scope - - def error_value(self): - return self.entry.signature.error_value - - def caller_will_check_exceptions(self): - return self.entry.signature.exception_check - - def generate_function_definitions(self, env, code): - if self.defaults_getter: + else: + return code.globalstate.directives['binding'] + return env.is_py_class_scope or env.is_closure_scope + + def error_value(self): + return self.entry.signature.error_value + + def caller_will_check_exceptions(self): + return self.entry.signature.exception_check + + def generate_function_definitions(self, env, code): + if self.defaults_getter: # defaults getter must never live in class scopes, it's always a module function self.defaults_getter.generate_function_definitions(env.global_scope(), code) - - # Before closure cnames are mangled - if self.py_wrapper_required: - # func_cname might be modified by @cname - self.py_wrapper.func_cname = self.entry.func_cname - self.py_wrapper.generate_function_definitions(env, code) - FuncDefNode.generate_function_definitions(self, env, code) - - def generate_function_header(self, code, with_pymethdef, proto_only=0): - if proto_only: - if self.py_wrapper_required: - self.py_wrapper.generate_function_header( - code, with_pymethdef, True) - return - arg_code_list = [] - if self.entry.signature.has_dummy_arg: - self_arg = 'PyObject *%s' % Naming.self_cname - if not self.needs_outer_scope: - self_arg = 'CYTHON_UNUSED ' + self_arg - arg_code_list.append(self_arg) - - def arg_decl_code(arg): - entry = arg.entry - if entry.in_closure: - cname = entry.original_cname - else: - cname = entry.cname - decl = entry.type.declaration_code(cname) - if not entry.cf_used: - decl = 'CYTHON_UNUSED ' + decl - return decl - - for arg in self.args: - arg_code_list.append(arg_decl_code(arg)) - if self.star_arg: - arg_code_list.append(arg_decl_code(self.star_arg)) - if self.starstar_arg: - arg_code_list.append(arg_decl_code(self.starstar_arg)) + + # Before closure cnames are mangled + if self.py_wrapper_required: + # func_cname might be modified by @cname + self.py_wrapper.func_cname = self.entry.func_cname + self.py_wrapper.generate_function_definitions(env, code) + FuncDefNode.generate_function_definitions(self, env, code) + + def generate_function_header(self, code, with_pymethdef, proto_only=0): + if proto_only: + if self.py_wrapper_required: + self.py_wrapper.generate_function_header( + code, with_pymethdef, True) + return + arg_code_list = [] + if self.entry.signature.has_dummy_arg: + self_arg = 'PyObject *%s' % Naming.self_cname + if not self.needs_outer_scope: + self_arg = 'CYTHON_UNUSED ' + self_arg + arg_code_list.append(self_arg) + + def arg_decl_code(arg): + entry = arg.entry + if entry.in_closure: + cname = entry.original_cname + else: + cname = entry.cname + decl = entry.type.declaration_code(cname) + if not entry.cf_used: + decl = 'CYTHON_UNUSED ' + decl + return decl + + for arg in self.args: + arg_code_list.append(arg_decl_code(arg)) + if self.star_arg: + arg_code_list.append(arg_decl_code(self.star_arg)) + if self.starstar_arg: + arg_code_list.append(arg_decl_code(self.starstar_arg)) if arg_code_list: arg_code = ', '.join(arg_code_list) else: arg_code = 'void' # No arguments - dc = self.return_type.declaration_code(self.entry.pyfunc_cname) - - decls_code = code.globalstate['decls'] - preprocessor_guard = self.get_preprocessor_guard() - if preprocessor_guard: - decls_code.putln(preprocessor_guard) - decls_code.putln( - "static %s(%s); /* proto */" % (dc, arg_code)) - if preprocessor_guard: - decls_code.putln("#endif") - code.putln("static %s(%s) {" % (dc, arg_code)) - - def generate_argument_declarations(self, env, code): - pass - - def generate_keyword_list(self, code): - pass - - def generate_argument_parsing_code(self, env, code): - # Move arguments into closure if required - def put_into_closure(entry): - if entry.in_closure: - code.putln('%s = %s;' % (entry.cname, entry.original_cname)) + dc = self.return_type.declaration_code(self.entry.pyfunc_cname) + + decls_code = code.globalstate['decls'] + preprocessor_guard = self.get_preprocessor_guard() + if preprocessor_guard: + decls_code.putln(preprocessor_guard) + decls_code.putln( + "static %s(%s); /* proto */" % (dc, arg_code)) + if preprocessor_guard: + decls_code.putln("#endif") + code.putln("static %s(%s) {" % (dc, arg_code)) + + def generate_argument_declarations(self, env, code): + pass + + def generate_keyword_list(self, code): + pass + + def generate_argument_parsing_code(self, env, code): + # Move arguments into closure if required + def put_into_closure(entry): + if entry.in_closure: + code.putln('%s = %s;' % (entry.cname, entry.original_cname)) if entry.xdecref_cleanup: # mostly applies to the starstar arg - this can sometimes be NULL # so must be xincrefed instead @@ -3243,54 +3243,54 @@ class DefNode(FuncDefNode): else: code.put_var_incref(entry) code.put_var_giveref(entry) - for arg in self.args: - put_into_closure(arg.entry) - for arg in self.star_arg, self.starstar_arg: - if arg: - put_into_closure(arg.entry) - - def generate_argument_type_tests(self, code): - pass - - -class DefNodeWrapper(FuncDefNode): - # DefNode python wrapper code generator - - defnode = None - target = None # Target DefNode - - def __init__(self, *args, **kwargs): - FuncDefNode.__init__(self, *args, **kwargs) - self.num_kwonly_args = self.target.num_kwonly_args - self.num_required_kw_args = self.target.num_required_kw_args - self.num_required_args = self.target.num_required_args - self.self_in_stararg = self.target.self_in_stararg - self.signature = None - - def analyse_declarations(self, env): - target_entry = self.target.entry - name = self.name - prefix = env.next_id(env.scope_prefix) - target_entry.func_cname = Naming.pywrap_prefix + prefix + name - target_entry.pymethdef_cname = Naming.pymethdef_prefix + prefix + name - - self.signature = target_entry.signature - + for arg in self.args: + put_into_closure(arg.entry) + for arg in self.star_arg, self.starstar_arg: + if arg: + put_into_closure(arg.entry) + + def generate_argument_type_tests(self, code): + pass + + +class DefNodeWrapper(FuncDefNode): + # DefNode python wrapper code generator + + defnode = None + target = None # Target DefNode + + def __init__(self, *args, **kwargs): + FuncDefNode.__init__(self, *args, **kwargs) + self.num_kwonly_args = self.target.num_kwonly_args + self.num_required_kw_args = self.target.num_required_kw_args + self.num_required_args = self.target.num_required_args + self.self_in_stararg = self.target.self_in_stararg + self.signature = None + + def analyse_declarations(self, env): + target_entry = self.target.entry + name = self.name + prefix = env.next_id(env.scope_prefix) + target_entry.func_cname = Naming.pywrap_prefix + prefix + name + target_entry.pymethdef_cname = Naming.pymethdef_prefix + prefix + name + + self.signature = target_entry.signature + self.np_args_idx = self.target.np_args_idx - def prepare_argument_coercion(self, env): - # This is only really required for Cython utility code at this time, - # everything else can be done during code generation. But we expand - # all utility code here, simply because we cannot easily distinguish - # different code types. - for arg in self.args: - if not arg.type.is_pyobject: - if not arg.type.create_from_py_utility_code(env): - pass # will fail later - elif arg.hdr_type and not arg.hdr_type.is_pyobject: - if not arg.hdr_type.create_to_py_utility_code(env): - pass # will fail later - + def prepare_argument_coercion(self, env): + # This is only really required for Cython utility code at this time, + # everything else can be done during code generation. But we expand + # all utility code here, simply because we cannot easily distinguish + # different code types. + for arg in self.args: + if not arg.type.is_pyobject: + if not arg.type.create_from_py_utility_code(env): + pass # will fail later + elif arg.hdr_type and not arg.hdr_type.is_pyobject: + if not arg.hdr_type.create_to_py_utility_code(env): + pass # will fail later + if self.starstar_arg and not self.starstar_arg.entry.cf_used: # we will set the kwargs argument to NULL instead of a new dict # and must therefore correct the control flow state @@ -3300,268 +3300,268 @@ class DefNodeWrapper(FuncDefNode): if not ass.is_arg and ass.lhs.is_name: ass.lhs.cf_maybe_null = True - def signature_has_nongeneric_args(self): - argcount = len(self.args) - if argcount == 0 or ( - argcount == 1 and (self.args[0].is_self_arg or - self.args[0].is_type_arg)): - return 0 - return 1 - - def signature_has_generic_args(self): - return self.signature.has_generic_args - - def generate_function_body(self, code): - args = [] - if self.signature.has_dummy_arg: - args.append(Naming.self_cname) - for arg in self.args: - if arg.hdr_type and not (arg.type.is_memoryviewslice or - arg.type.is_struct or - arg.type.is_complex): - args.append(arg.type.cast_code(arg.entry.cname)) - else: - args.append(arg.entry.cname) - if self.star_arg: - args.append(self.star_arg.entry.cname) - if self.starstar_arg: - args.append(self.starstar_arg.entry.cname) - args = ', '.join(args) - if not self.return_type.is_void: - code.put('%s = ' % Naming.retval_cname) - code.putln('%s(%s);' % ( - self.target.entry.pyfunc_cname, args)) - - def generate_function_definitions(self, env, code): - lenv = self.target.local_scope - # Generate C code for header and body of function - code.mark_pos(self.pos) - code.putln("") - code.putln("/* Python wrapper */") - preprocessor_guard = self.target.get_preprocessor_guard() - if preprocessor_guard: - code.putln(preprocessor_guard) - + def signature_has_nongeneric_args(self): + argcount = len(self.args) + if argcount == 0 or ( + argcount == 1 and (self.args[0].is_self_arg or + self.args[0].is_type_arg)): + return 0 + return 1 + + def signature_has_generic_args(self): + return self.signature.has_generic_args + + def generate_function_body(self, code): + args = [] + if self.signature.has_dummy_arg: + args.append(Naming.self_cname) + for arg in self.args: + if arg.hdr_type and not (arg.type.is_memoryviewslice or + arg.type.is_struct or + arg.type.is_complex): + args.append(arg.type.cast_code(arg.entry.cname)) + else: + args.append(arg.entry.cname) + if self.star_arg: + args.append(self.star_arg.entry.cname) + if self.starstar_arg: + args.append(self.starstar_arg.entry.cname) + args = ', '.join(args) + if not self.return_type.is_void: + code.put('%s = ' % Naming.retval_cname) + code.putln('%s(%s);' % ( + self.target.entry.pyfunc_cname, args)) + + def generate_function_definitions(self, env, code): + lenv = self.target.local_scope + # Generate C code for header and body of function + code.mark_pos(self.pos) + code.putln("") + code.putln("/* Python wrapper */") + preprocessor_guard = self.target.get_preprocessor_guard() + if preprocessor_guard: + code.putln(preprocessor_guard) + code.enter_cfunc_scope(lenv) - code.return_from_error_cleanup_label = code.new_label() - - with_pymethdef = (self.target.needs_assignment_synthesis(env, code) or - self.target.pymethdef_required) - self.generate_function_header(code, with_pymethdef) - self.generate_argument_declarations(lenv, code) - tempvardecl_code = code.insertion_point() - - if self.return_type.is_pyobject: - retval_init = ' = 0' - else: - retval_init = '' - if not self.return_type.is_void: - code.putln('%s%s;' % ( - self.return_type.declaration_code(Naming.retval_cname), - retval_init)) - code.put_declare_refcount_context() - code.put_setup_refcount_context('%s (wrapper)' % self.name) - - self.generate_argument_parsing_code(lenv, code) - self.generate_argument_type_tests(code) - self.generate_function_body(code) - - # ----- Go back and insert temp variable declarations - tempvardecl_code.put_temp_declarations(code.funcstate) - - code.mark_pos(self.pos) - code.putln("") - code.putln("/* function exit code */") - - # ----- Error cleanup - if code.error_label in code.labels_used: - code.put_goto(code.return_label) - code.put_label(code.error_label) - for cname, type in code.funcstate.all_managed_temps(): - code.put_xdecref(cname, type) - err_val = self.error_value() - if err_val is not None: - code.putln("%s = %s;" % (Naming.retval_cname, err_val)) - - # ----- Non-error return cleanup - code.put_label(code.return_label) - for entry in lenv.var_entries: - if entry.is_arg and entry.type.is_pyobject: - code.put_var_decref(entry) - - code.put_finish_refcount_context() - if not self.return_type.is_void: - code.putln("return %s;" % Naming.retval_cname) - code.putln('}') - code.exit_cfunc_scope() - if preprocessor_guard: - code.putln("#endif /*!(%s)*/" % preprocessor_guard) - - def generate_function_header(self, code, with_pymethdef, proto_only=0): - arg_code_list = [] - sig = self.signature - - if sig.has_dummy_arg or self.self_in_stararg: - arg_code = "PyObject *%s" % Naming.self_cname - if not sig.has_dummy_arg: - arg_code = 'CYTHON_UNUSED ' + arg_code - arg_code_list.append(arg_code) - - for arg in self.args: - if not arg.is_generic: - if arg.is_self_arg or arg.is_type_arg: - arg_code_list.append("PyObject *%s" % arg.hdr_cname) - else: - arg_code_list.append( - arg.hdr_type.declaration_code(arg.hdr_cname)) - entry = self.target.entry - if not entry.is_special and sig.method_flags() == [TypeSlots.method_noargs]: - arg_code_list.append("CYTHON_UNUSED PyObject *unused") - if entry.scope.is_c_class_scope and entry.name == "__ipow__": - arg_code_list.append("CYTHON_UNUSED PyObject *unused") - if sig.has_generic_args: - arg_code_list.append( + code.return_from_error_cleanup_label = code.new_label() + + with_pymethdef = (self.target.needs_assignment_synthesis(env, code) or + self.target.pymethdef_required) + self.generate_function_header(code, with_pymethdef) + self.generate_argument_declarations(lenv, code) + tempvardecl_code = code.insertion_point() + + if self.return_type.is_pyobject: + retval_init = ' = 0' + else: + retval_init = '' + if not self.return_type.is_void: + code.putln('%s%s;' % ( + self.return_type.declaration_code(Naming.retval_cname), + retval_init)) + code.put_declare_refcount_context() + code.put_setup_refcount_context('%s (wrapper)' % self.name) + + self.generate_argument_parsing_code(lenv, code) + self.generate_argument_type_tests(code) + self.generate_function_body(code) + + # ----- Go back and insert temp variable declarations + tempvardecl_code.put_temp_declarations(code.funcstate) + + code.mark_pos(self.pos) + code.putln("") + code.putln("/* function exit code */") + + # ----- Error cleanup + if code.error_label in code.labels_used: + code.put_goto(code.return_label) + code.put_label(code.error_label) + for cname, type in code.funcstate.all_managed_temps(): + code.put_xdecref(cname, type) + err_val = self.error_value() + if err_val is not None: + code.putln("%s = %s;" % (Naming.retval_cname, err_val)) + + # ----- Non-error return cleanup + code.put_label(code.return_label) + for entry in lenv.var_entries: + if entry.is_arg and entry.type.is_pyobject: + code.put_var_decref(entry) + + code.put_finish_refcount_context() + if not self.return_type.is_void: + code.putln("return %s;" % Naming.retval_cname) + code.putln('}') + code.exit_cfunc_scope() + if preprocessor_guard: + code.putln("#endif /*!(%s)*/" % preprocessor_guard) + + def generate_function_header(self, code, with_pymethdef, proto_only=0): + arg_code_list = [] + sig = self.signature + + if sig.has_dummy_arg or self.self_in_stararg: + arg_code = "PyObject *%s" % Naming.self_cname + if not sig.has_dummy_arg: + arg_code = 'CYTHON_UNUSED ' + arg_code + arg_code_list.append(arg_code) + + for arg in self.args: + if not arg.is_generic: + if arg.is_self_arg or arg.is_type_arg: + arg_code_list.append("PyObject *%s" % arg.hdr_cname) + else: + arg_code_list.append( + arg.hdr_type.declaration_code(arg.hdr_cname)) + entry = self.target.entry + if not entry.is_special and sig.method_flags() == [TypeSlots.method_noargs]: + arg_code_list.append("CYTHON_UNUSED PyObject *unused") + if entry.scope.is_c_class_scope and entry.name == "__ipow__": + arg_code_list.append("CYTHON_UNUSED PyObject *unused") + if sig.has_generic_args: + arg_code_list.append( "PyObject *%s, PyObject *%s" % ( Naming.args_cname, Naming.kwds_cname)) - arg_code = ", ".join(arg_code_list) - - # Prevent warning: unused function '__pyx_pw_5numpy_7ndarray_1__getbuffer__' - mf = "" - if (entry.name in ("__getbuffer__", "__releasebuffer__") + arg_code = ", ".join(arg_code_list) + + # Prevent warning: unused function '__pyx_pw_5numpy_7ndarray_1__getbuffer__' + mf = "" + if (entry.name in ("__getbuffer__", "__releasebuffer__") and entry.scope.is_c_class_scope): - mf = "CYTHON_UNUSED " - with_pymethdef = False - - dc = self.return_type.declaration_code(entry.func_cname) - header = "static %s%s(%s)" % (mf, dc, arg_code) - code.putln("%s; /*proto*/" % header) - - if proto_only: - if self.target.fused_py_func: - # If we are the specialized version of the cpdef, we still - # want the prototype for the "fused cpdef", in case we're - # checking to see if our method was overridden in Python - self.target.fused_py_func.generate_function_header( + mf = "CYTHON_UNUSED " + with_pymethdef = False + + dc = self.return_type.declaration_code(entry.func_cname) + header = "static %s%s(%s)" % (mf, dc, arg_code) + code.putln("%s; /*proto*/" % header) + + if proto_only: + if self.target.fused_py_func: + # If we are the specialized version of the cpdef, we still + # want the prototype for the "fused cpdef", in case we're + # checking to see if our method was overridden in Python + self.target.fused_py_func.generate_function_header( code, with_pymethdef, proto_only=True) - return - - if (Options.docstrings and entry.doc and - not self.target.fused_py_func and - not entry.scope.is_property_scope and - (not entry.is_special or entry.wrapperbase_cname)): - # h_code = code.globalstate['h_code'] - docstr = entry.doc - - if docstr.is_unicode: + return + + if (Options.docstrings and entry.doc and + not self.target.fused_py_func and + not entry.scope.is_property_scope and + (not entry.is_special or entry.wrapperbase_cname)): + # h_code = code.globalstate['h_code'] + docstr = entry.doc + + if docstr.is_unicode: docstr = docstr.as_utf8_string() - + if not (entry.is_special and entry.name in ('__getbuffer__', '__releasebuffer__')): code.putln('static char %s[] = %s;' % ( - entry.doc_cname, + entry.doc_cname, docstr.as_c_string_literal())) - - if entry.is_special: - code.putln('#if CYTHON_COMPILING_IN_CPYTHON') - code.putln( - "struct wrapperbase %s;" % entry.wrapperbase_cname) - code.putln('#endif') - - if with_pymethdef or self.target.fused_py_func: - code.put( + + if entry.is_special: + code.putln('#if CYTHON_COMPILING_IN_CPYTHON') + code.putln( + "struct wrapperbase %s;" % entry.wrapperbase_cname) + code.putln('#endif') + + if with_pymethdef or self.target.fused_py_func: + code.put( "static PyMethodDef %s = " % entry.pymethdef_cname) - code.put_pymethoddef(self.target.entry, ";", allow_skip=False) - code.putln("%s {" % header) - - def generate_argument_declarations(self, env, code): - for arg in self.args: - if arg.is_generic: - if arg.needs_conversion: - code.putln("PyObject *%s = 0;" % arg.hdr_cname) - else: - code.put_var_declaration(arg.entry) - for entry in env.var_entries: - if entry.is_arg: - code.put_var_declaration(entry) - - def generate_argument_parsing_code(self, env, code): - # Generate fast equivalent of PyArg_ParseTuple call for - # generic arguments, if any, including args/kwargs - old_error_label = code.new_error_label() - our_error_label = code.error_label - end_label = code.new_label("argument_unpacking_done") - - has_kwonly_args = self.num_kwonly_args > 0 - has_star_or_kw_args = self.star_arg is not None \ - or self.starstar_arg is not None or has_kwonly_args - - for arg in self.args: - if not arg.type.is_pyobject: - if not arg.type.create_from_py_utility_code(env): + code.put_pymethoddef(self.target.entry, ";", allow_skip=False) + code.putln("%s {" % header) + + def generate_argument_declarations(self, env, code): + for arg in self.args: + if arg.is_generic: + if arg.needs_conversion: + code.putln("PyObject *%s = 0;" % arg.hdr_cname) + else: + code.put_var_declaration(arg.entry) + for entry in env.var_entries: + if entry.is_arg: + code.put_var_declaration(entry) + + def generate_argument_parsing_code(self, env, code): + # Generate fast equivalent of PyArg_ParseTuple call for + # generic arguments, if any, including args/kwargs + old_error_label = code.new_error_label() + our_error_label = code.error_label + end_label = code.new_label("argument_unpacking_done") + + has_kwonly_args = self.num_kwonly_args > 0 + has_star_or_kw_args = self.star_arg is not None \ + or self.starstar_arg is not None or has_kwonly_args + + for arg in self.args: + if not arg.type.is_pyobject: + if not arg.type.create_from_py_utility_code(env): pass # will fail later - - if not self.signature_has_generic_args(): - if has_star_or_kw_args: - error(self.pos, "This method cannot have * or keyword arguments") - self.generate_argument_conversion_code(code) - - elif not self.signature_has_nongeneric_args(): - # func(*args) or func(**kw) or func(*args, **kw) - self.generate_stararg_copy_code(code) - - else: - self.generate_tuple_and_keyword_parsing_code(self.args, end_label, code) - - code.error_label = old_error_label - if code.label_used(our_error_label): - if not code.label_used(end_label): - code.put_goto(end_label) - code.put_label(our_error_label) - if has_star_or_kw_args: - self.generate_arg_decref(self.star_arg, code) - if self.starstar_arg: - if self.starstar_arg.entry.xdecref_cleanup: - code.put_var_xdecref_clear(self.starstar_arg.entry) - else: - code.put_var_decref_clear(self.starstar_arg.entry) - code.put_add_traceback(self.target.entry.qualified_name) - code.put_finish_refcount_context() - code.putln("return %s;" % self.error_value()) - if code.label_used(end_label): - code.put_label(end_label) - - def generate_arg_xdecref(self, arg, code): - if arg: - code.put_var_xdecref_clear(arg.entry) - - def generate_arg_decref(self, arg, code): - if arg: - code.put_var_decref_clear(arg.entry) - - def generate_stararg_copy_code(self, code): - if not self.star_arg: - code.globalstate.use_utility_code( - UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) - code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > 0)) {" % - Naming.args_cname) - code.put('__Pyx_RaiseArgtupleInvalid("%s", 1, 0, 0, PyTuple_GET_SIZE(%s)); return %s;' % ( + + if not self.signature_has_generic_args(): + if has_star_or_kw_args: + error(self.pos, "This method cannot have * or keyword arguments") + self.generate_argument_conversion_code(code) + + elif not self.signature_has_nongeneric_args(): + # func(*args) or func(**kw) or func(*args, **kw) + self.generate_stararg_copy_code(code) + + else: + self.generate_tuple_and_keyword_parsing_code(self.args, end_label, code) + + code.error_label = old_error_label + if code.label_used(our_error_label): + if not code.label_used(end_label): + code.put_goto(end_label) + code.put_label(our_error_label) + if has_star_or_kw_args: + self.generate_arg_decref(self.star_arg, code) + if self.starstar_arg: + if self.starstar_arg.entry.xdecref_cleanup: + code.put_var_xdecref_clear(self.starstar_arg.entry) + else: + code.put_var_decref_clear(self.starstar_arg.entry) + code.put_add_traceback(self.target.entry.qualified_name) + code.put_finish_refcount_context() + code.putln("return %s;" % self.error_value()) + if code.label_used(end_label): + code.put_label(end_label) + + def generate_arg_xdecref(self, arg, code): + if arg: + code.put_var_xdecref_clear(arg.entry) + + def generate_arg_decref(self, arg, code): + if arg: + code.put_var_decref_clear(arg.entry) + + def generate_stararg_copy_code(self, code): + if not self.star_arg: + code.globalstate.use_utility_code( + UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) + code.putln("if (unlikely(PyTuple_GET_SIZE(%s) > 0)) {" % + Naming.args_cname) + code.put('__Pyx_RaiseArgtupleInvalid("%s", 1, 0, 0, PyTuple_GET_SIZE(%s)); return %s;' % ( self.name, Naming.args_cname, self.error_value())) - code.putln("}") - - if self.starstar_arg: + code.putln("}") + + if self.starstar_arg: if self.star_arg or not self.starstar_arg.entry.cf_used: - kwarg_check = "unlikely(%s)" % Naming.kwds_cname - else: - kwarg_check = "%s" % Naming.kwds_cname - else: - kwarg_check = "unlikely(%s) && unlikely(PyDict_Size(%s) > 0)" % ( - Naming.kwds_cname, Naming.kwds_cname) - code.globalstate.use_utility_code( - UtilityCode.load_cached("KeywordStringCheck", "FunctionArguments.c")) - code.putln( - "if (%s && unlikely(!__Pyx_CheckKeywordStrings(%s, \"%s\", %d))) return %s;" % ( - kwarg_check, Naming.kwds_cname, self.name, - bool(self.starstar_arg), self.error_value())) - + kwarg_check = "unlikely(%s)" % Naming.kwds_cname + else: + kwarg_check = "%s" % Naming.kwds_cname + else: + kwarg_check = "unlikely(%s) && unlikely(PyDict_Size(%s) > 0)" % ( + Naming.kwds_cname, Naming.kwds_cname) + code.globalstate.use_utility_code( + UtilityCode.load_cached("KeywordStringCheck", "FunctionArguments.c")) + code.putln( + "if (%s && unlikely(!__Pyx_CheckKeywordStrings(%s, \"%s\", %d))) return %s;" % ( + kwarg_check, Naming.kwds_cname, self.name, + bool(self.starstar_arg), self.error_value())) + if self.starstar_arg and self.starstar_arg.entry.cf_used: if all(ref.node.allow_null for ref in self.starstar_arg.entry.cf_references): code.putln("if (%s) {" % kwarg_check) @@ -3584,376 +3584,376 @@ class DefNodeWrapper(FuncDefNode): self.starstar_arg.entry.cname, self.error_value())) self.starstar_arg.entry.xdecref_cleanup = 0 code.put_gotref(self.starstar_arg.entry.cname) - - if self.self_in_stararg and not self.target.is_staticmethod: - # need to create a new tuple with 'self' inserted as first item - code.put("%s = PyTuple_New(PyTuple_GET_SIZE(%s)+1); if (unlikely(!%s)) " % ( + + if self.self_in_stararg and not self.target.is_staticmethod: + # need to create a new tuple with 'self' inserted as first item + code.put("%s = PyTuple_New(PyTuple_GET_SIZE(%s)+1); if (unlikely(!%s)) " % ( self.star_arg.entry.cname, Naming.args_cname, self.star_arg.entry.cname)) if self.starstar_arg and self.starstar_arg.entry.cf_used: - code.putln("{") + code.putln("{") code.put_xdecref_clear(self.starstar_arg.entry.cname, py_object_type) - code.putln("return %s;" % self.error_value()) - code.putln("}") - else: - code.putln("return %s;" % self.error_value()) - code.put_gotref(self.star_arg.entry.cname) - code.put_incref(Naming.self_cname, py_object_type) - code.put_giveref(Naming.self_cname) - code.putln("PyTuple_SET_ITEM(%s, 0, %s);" % ( - self.star_arg.entry.cname, Naming.self_cname)) - temp = code.funcstate.allocate_temp(PyrexTypes.c_py_ssize_t_type, manage_ref=False) - code.putln("for (%s=0; %s < PyTuple_GET_SIZE(%s); %s++) {" % ( - temp, temp, Naming.args_cname, temp)) - code.putln("PyObject* item = PyTuple_GET_ITEM(%s, %s);" % ( - Naming.args_cname, temp)) - code.put_incref("item", py_object_type) - code.put_giveref("item") - code.putln("PyTuple_SET_ITEM(%s, %s+1, item);" % ( - self.star_arg.entry.cname, temp)) - code.putln("}") - code.funcstate.release_temp(temp) - self.star_arg.entry.xdecref_cleanup = 0 - elif self.star_arg: - code.put_incref(Naming.args_cname, py_object_type) - code.putln("%s = %s;" % ( + code.putln("return %s;" % self.error_value()) + code.putln("}") + else: + code.putln("return %s;" % self.error_value()) + code.put_gotref(self.star_arg.entry.cname) + code.put_incref(Naming.self_cname, py_object_type) + code.put_giveref(Naming.self_cname) + code.putln("PyTuple_SET_ITEM(%s, 0, %s);" % ( + self.star_arg.entry.cname, Naming.self_cname)) + temp = code.funcstate.allocate_temp(PyrexTypes.c_py_ssize_t_type, manage_ref=False) + code.putln("for (%s=0; %s < PyTuple_GET_SIZE(%s); %s++) {" % ( + temp, temp, Naming.args_cname, temp)) + code.putln("PyObject* item = PyTuple_GET_ITEM(%s, %s);" % ( + Naming.args_cname, temp)) + code.put_incref("item", py_object_type) + code.put_giveref("item") + code.putln("PyTuple_SET_ITEM(%s, %s+1, item);" % ( + self.star_arg.entry.cname, temp)) + code.putln("}") + code.funcstate.release_temp(temp) + self.star_arg.entry.xdecref_cleanup = 0 + elif self.star_arg: + code.put_incref(Naming.args_cname, py_object_type) + code.putln("%s = %s;" % ( self.star_arg.entry.cname, Naming.args_cname)) - self.star_arg.entry.xdecref_cleanup = 0 - - def generate_tuple_and_keyword_parsing_code(self, args, success_label, code): - argtuple_error_label = code.new_label("argtuple_error") - - positional_args = [] - required_kw_only_args = [] - optional_kw_only_args = [] - for arg in args: - if arg.is_generic: - if arg.default: - if not arg.is_self_arg and not arg.is_type_arg: - if arg.kw_only: - optional_kw_only_args.append(arg) - else: - positional_args.append(arg) - elif arg.kw_only: - required_kw_only_args.append(arg) - elif not arg.is_self_arg and not arg.is_type_arg: - positional_args.append(arg) - - # sort required kw-only args before optional ones to avoid special - # cases in the unpacking code - kw_only_args = required_kw_only_args + optional_kw_only_args - - min_positional_args = self.num_required_args - self.num_required_kw_args - if len(args) > 0 and (args[0].is_self_arg or args[0].is_type_arg): - min_positional_args -= 1 - max_positional_args = len(positional_args) - has_fixed_positional_count = not self.star_arg and \ - min_positional_args == max_positional_args - has_kw_only_args = bool(kw_only_args) - - if self.starstar_arg or self.star_arg: - self.generate_stararg_init_code(max_positional_args, code) - - code.putln('{') - all_args = tuple(positional_args) + tuple(kw_only_args) - code.putln("static PyObject **%s[] = {%s,0};" % ( - Naming.pykwdlist_cname, + self.star_arg.entry.xdecref_cleanup = 0 + + def generate_tuple_and_keyword_parsing_code(self, args, success_label, code): + argtuple_error_label = code.new_label("argtuple_error") + + positional_args = [] + required_kw_only_args = [] + optional_kw_only_args = [] + for arg in args: + if arg.is_generic: + if arg.default: + if not arg.is_self_arg and not arg.is_type_arg: + if arg.kw_only: + optional_kw_only_args.append(arg) + else: + positional_args.append(arg) + elif arg.kw_only: + required_kw_only_args.append(arg) + elif not arg.is_self_arg and not arg.is_type_arg: + positional_args.append(arg) + + # sort required kw-only args before optional ones to avoid special + # cases in the unpacking code + kw_only_args = required_kw_only_args + optional_kw_only_args + + min_positional_args = self.num_required_args - self.num_required_kw_args + if len(args) > 0 and (args[0].is_self_arg or args[0].is_type_arg): + min_positional_args -= 1 + max_positional_args = len(positional_args) + has_fixed_positional_count = not self.star_arg and \ + min_positional_args == max_positional_args + has_kw_only_args = bool(kw_only_args) + + if self.starstar_arg or self.star_arg: + self.generate_stararg_init_code(max_positional_args, code) + + code.putln('{') + all_args = tuple(positional_args) + tuple(kw_only_args) + code.putln("static PyObject **%s[] = {%s,0};" % ( + Naming.pykwdlist_cname, ','.join(['&%s' % code.intern_identifier(arg.name) for arg in all_args]))) - - # Before being converted and assigned to the target variables, - # borrowed references to all unpacked argument values are - # collected into a local PyObject* array called "values", - # regardless if they were taken from default arguments, - # positional arguments or keyword arguments. Note that - # C-typed default arguments are handled at conversion time, - # so their array value is NULL in the end if no argument - # was passed for them. - self.generate_argument_values_setup_code(all_args, code) - - # --- optimised code when we receive keyword arguments - code.putln("if (%s(%s)) {" % ( - (self.num_required_kw_args > 0) and "likely" or "unlikely", - Naming.kwds_cname)) - self.generate_keyword_unpacking_code( - min_positional_args, max_positional_args, - has_fixed_positional_count, has_kw_only_args, - all_args, argtuple_error_label, code) - - # --- optimised code when we do not receive any keyword arguments - if (self.num_required_kw_args and min_positional_args > 0) or min_positional_args == max_positional_args: - # Python raises arg tuple related errors first, so we must - # check the length here - if min_positional_args == max_positional_args and not self.star_arg: - compare = '!=' - else: - compare = '<' - code.putln('} else if (PyTuple_GET_SIZE(%s) %s %d) {' % ( + + # Before being converted and assigned to the target variables, + # borrowed references to all unpacked argument values are + # collected into a local PyObject* array called "values", + # regardless if they were taken from default arguments, + # positional arguments or keyword arguments. Note that + # C-typed default arguments are handled at conversion time, + # so their array value is NULL in the end if no argument + # was passed for them. + self.generate_argument_values_setup_code(all_args, code) + + # --- optimised code when we receive keyword arguments + code.putln("if (%s(%s)) {" % ( + (self.num_required_kw_args > 0) and "likely" or "unlikely", + Naming.kwds_cname)) + self.generate_keyword_unpacking_code( + min_positional_args, max_positional_args, + has_fixed_positional_count, has_kw_only_args, + all_args, argtuple_error_label, code) + + # --- optimised code when we do not receive any keyword arguments + if (self.num_required_kw_args and min_positional_args > 0) or min_positional_args == max_positional_args: + # Python raises arg tuple related errors first, so we must + # check the length here + if min_positional_args == max_positional_args and not self.star_arg: + compare = '!=' + else: + compare = '<' + code.putln('} else if (PyTuple_GET_SIZE(%s) %s %d) {' % ( Naming.args_cname, compare, min_positional_args)) - code.put_goto(argtuple_error_label) - - if self.num_required_kw_args: - # pure error case: keywords required but not passed - if max_positional_args > min_positional_args and not self.star_arg: - code.putln('} else if (PyTuple_GET_SIZE(%s) > %d) {' % ( + code.put_goto(argtuple_error_label) + + if self.num_required_kw_args: + # pure error case: keywords required but not passed + if max_positional_args > min_positional_args and not self.star_arg: + code.putln('} else if (PyTuple_GET_SIZE(%s) > %d) {' % ( Naming.args_cname, max_positional_args)) - code.put_goto(argtuple_error_label) - code.putln('} else {') - for i, arg in enumerate(kw_only_args): - if not arg.default: - pystring_cname = code.intern_identifier(arg.name) - # required keyword-only argument missing + code.put_goto(argtuple_error_label) + code.putln('} else {') + for i, arg in enumerate(kw_only_args): + if not arg.default: + pystring_cname = code.intern_identifier(arg.name) + # required keyword-only argument missing code.globalstate.use_utility_code( UtilityCode.load_cached("RaiseKeywordRequired", "FunctionArguments.c")) - code.put('__Pyx_RaiseKeywordRequired("%s", %s); ' % ( + code.put('__Pyx_RaiseKeywordRequired("%s", %s); ' % ( self.name, pystring_cname)) - code.putln(code.error_goto(self.pos)) - break - - else: - # optimised tuple unpacking code - code.putln('} else {') - if min_positional_args == max_positional_args: - # parse the exact number of positional arguments from - # the args tuple - for i, arg in enumerate(positional_args): - code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % (i, Naming.args_cname, i)) - else: - # parse the positional arguments from the variable length - # args tuple and reject illegal argument tuple sizes - code.putln('switch (PyTuple_GET_SIZE(%s)) {' % Naming.args_cname) - if self.star_arg: - code.putln('default:') - reversed_args = list(enumerate(positional_args))[::-1] - for i, arg in reversed_args: - if i >= min_positional_args-1: + code.putln(code.error_goto(self.pos)) + break + + else: + # optimised tuple unpacking code + code.putln('} else {') + if min_positional_args == max_positional_args: + # parse the exact number of positional arguments from + # the args tuple + for i, arg in enumerate(positional_args): + code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % (i, Naming.args_cname, i)) + else: + # parse the positional arguments from the variable length + # args tuple and reject illegal argument tuple sizes + code.putln('switch (PyTuple_GET_SIZE(%s)) {' % Naming.args_cname) + if self.star_arg: + code.putln('default:') + reversed_args = list(enumerate(positional_args))[::-1] + for i, arg in reversed_args: + if i >= min_positional_args-1: if i != reversed_args[0][0]: code.putln('CYTHON_FALLTHROUGH;') - code.put('case %2d: ' % (i+1)) - code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % (i, Naming.args_cname, i)) - if min_positional_args == 0: + code.put('case %2d: ' % (i+1)) + code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % (i, Naming.args_cname, i)) + if min_positional_args == 0: code.putln('CYTHON_FALLTHROUGH;') - code.put('case 0: ') - code.putln('break;') - if self.star_arg: - if min_positional_args: - for i in range(min_positional_args-1, -1, -1): - code.putln('case %2d:' % i) - code.put_goto(argtuple_error_label) - else: - code.put('default: ') - code.put_goto(argtuple_error_label) - code.putln('}') - - code.putln('}') # end of the conditional unpacking blocks - - # Convert arg values to their final type and assign them. - # Also inject non-Python default arguments, which do cannot - # live in the values[] array. - for i, arg in enumerate(all_args): - self.generate_arg_assignment(arg, "values[%d]" % i, code) - - code.putln('}') # end of the whole argument unpacking block - - if code.label_used(argtuple_error_label): - code.put_goto(success_label) - code.put_label(argtuple_error_label) - code.globalstate.use_utility_code( - UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) - code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d, PyTuple_GET_SIZE(%s)); ' % ( + code.put('case 0: ') + code.putln('break;') + if self.star_arg: + if min_positional_args: + for i in range(min_positional_args-1, -1, -1): + code.putln('case %2d:' % i) + code.put_goto(argtuple_error_label) + else: + code.put('default: ') + code.put_goto(argtuple_error_label) + code.putln('}') + + code.putln('}') # end of the conditional unpacking blocks + + # Convert arg values to their final type and assign them. + # Also inject non-Python default arguments, which do cannot + # live in the values[] array. + for i, arg in enumerate(all_args): + self.generate_arg_assignment(arg, "values[%d]" % i, code) + + code.putln('}') # end of the whole argument unpacking block + + if code.label_used(argtuple_error_label): + code.put_goto(success_label) + code.put_label(argtuple_error_label) + code.globalstate.use_utility_code( + UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) + code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d, PyTuple_GET_SIZE(%s)); ' % ( self.name, has_fixed_positional_count, min_positional_args, max_positional_args, Naming.args_cname)) - code.putln(code.error_goto(self.pos)) - - def generate_arg_assignment(self, arg, item, code): - if arg.type.is_pyobject: - # Python default arguments were already stored in 'item' at the very beginning - if arg.is_generic: - item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item) - entry = arg.entry - code.putln("%s = %s;" % (entry.cname, item)) - else: + code.putln(code.error_goto(self.pos)) + + def generate_arg_assignment(self, arg, item, code): + if arg.type.is_pyobject: + # Python default arguments were already stored in 'item' at the very beginning + if arg.is_generic: + item = PyrexTypes.typecast(arg.type, PyrexTypes.py_object_type, item) + entry = arg.entry + code.putln("%s = %s;" % (entry.cname, item)) + else: if arg.type.from_py_function: - if arg.default: - # C-typed default arguments must be handled here - code.putln('if (%s) {' % item) + if arg.default: + # C-typed default arguments must be handled here + code.putln('if (%s) {' % item) code.putln(arg.type.from_py_call_code( item, arg.entry.cname, arg.pos, code)) - if arg.default: - code.putln('} else {') + if arg.default: + code.putln('} else {') code.putln("%s = %s;" % ( arg.entry.cname, arg.calculate_default_value_code(code))) - if arg.type.is_memoryviewslice: - code.put_incref_memoryviewslice(arg.entry.cname, - have_gil=True) - code.putln('}') - else: - error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type) - - def generate_stararg_init_code(self, max_positional_args, code): - if self.starstar_arg: - self.starstar_arg.entry.xdecref_cleanup = 0 - code.putln('%s = PyDict_New(); if (unlikely(!%s)) return %s;' % ( + if arg.type.is_memoryviewslice: + code.put_incref_memoryviewslice(arg.entry.cname, + have_gil=True) + code.putln('}') + else: + error(arg.pos, "Cannot convert Python object argument to type '%s'" % arg.type) + + def generate_stararg_init_code(self, max_positional_args, code): + if self.starstar_arg: + self.starstar_arg.entry.xdecref_cleanup = 0 + code.putln('%s = PyDict_New(); if (unlikely(!%s)) return %s;' % ( self.starstar_arg.entry.cname, self.starstar_arg.entry.cname, self.error_value())) - code.put_gotref(self.starstar_arg.entry.cname) - if self.star_arg: - self.star_arg.entry.xdecref_cleanup = 0 - code.putln('if (PyTuple_GET_SIZE(%s) > %d) {' % ( + code.put_gotref(self.starstar_arg.entry.cname) + if self.star_arg: + self.star_arg.entry.xdecref_cleanup = 0 + code.putln('if (PyTuple_GET_SIZE(%s) > %d) {' % ( Naming.args_cname, max_positional_args)) - code.putln('%s = PyTuple_GetSlice(%s, %d, PyTuple_GET_SIZE(%s));' % ( + code.putln('%s = PyTuple_GetSlice(%s, %d, PyTuple_GET_SIZE(%s));' % ( self.star_arg.entry.cname, Naming.args_cname, max_positional_args, Naming.args_cname)) - code.putln("if (unlikely(!%s)) {" % self.star_arg.entry.cname) - if self.starstar_arg: - code.put_decref_clear(self.starstar_arg.entry.cname, py_object_type) - code.put_finish_refcount_context() - code.putln('return %s;' % self.error_value()) - code.putln('}') - code.put_gotref(self.star_arg.entry.cname) - code.putln('} else {') - code.put("%s = %s; " % (self.star_arg.entry.cname, Naming.empty_tuple)) - code.put_incref(Naming.empty_tuple, py_object_type) - code.putln('}') - - def generate_argument_values_setup_code(self, args, code): - max_args = len(args) - # the 'values' array collects borrowed references to arguments - # before doing any type coercion etc. - code.putln("PyObject* values[%d] = {%s};" % ( - max_args, ','.join('0'*max_args))) - - if self.target.defaults_struct: - code.putln('%s *%s = __Pyx_CyFunction_Defaults(%s, %s);' % ( - self.target.defaults_struct, Naming.dynamic_args_cname, - self.target.defaults_struct, Naming.self_cname)) - - # assign borrowed Python default values to the values array, - # so that they can be overwritten by received arguments below - for i, arg in enumerate(args): - if arg.default and arg.type.is_pyobject: - default_value = arg.calculate_default_value_code(code) - code.putln('values[%d] = %s;' % (i, arg.type.as_pyobject(default_value))) - - def generate_keyword_unpacking_code(self, min_positional_args, max_positional_args, - has_fixed_positional_count, has_kw_only_args, - all_args, argtuple_error_label, code): - code.putln('Py_ssize_t kw_args;') - code.putln('const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);' % Naming.args_cname) - # copy the values from the args tuple and check that it's not too long - code.putln('switch (pos_args) {') - if self.star_arg: - code.putln('default:') - for i in range(max_positional_args-1, -1, -1): - code.put('case %2d: ' % (i+1)) - code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % ( + code.putln("if (unlikely(!%s)) {" % self.star_arg.entry.cname) + if self.starstar_arg: + code.put_decref_clear(self.starstar_arg.entry.cname, py_object_type) + code.put_finish_refcount_context() + code.putln('return %s;' % self.error_value()) + code.putln('}') + code.put_gotref(self.star_arg.entry.cname) + code.putln('} else {') + code.put("%s = %s; " % (self.star_arg.entry.cname, Naming.empty_tuple)) + code.put_incref(Naming.empty_tuple, py_object_type) + code.putln('}') + + def generate_argument_values_setup_code(self, args, code): + max_args = len(args) + # the 'values' array collects borrowed references to arguments + # before doing any type coercion etc. + code.putln("PyObject* values[%d] = {%s};" % ( + max_args, ','.join('0'*max_args))) + + if self.target.defaults_struct: + code.putln('%s *%s = __Pyx_CyFunction_Defaults(%s, %s);' % ( + self.target.defaults_struct, Naming.dynamic_args_cname, + self.target.defaults_struct, Naming.self_cname)) + + # assign borrowed Python default values to the values array, + # so that they can be overwritten by received arguments below + for i, arg in enumerate(args): + if arg.default and arg.type.is_pyobject: + default_value = arg.calculate_default_value_code(code) + code.putln('values[%d] = %s;' % (i, arg.type.as_pyobject(default_value))) + + def generate_keyword_unpacking_code(self, min_positional_args, max_positional_args, + has_fixed_positional_count, has_kw_only_args, + all_args, argtuple_error_label, code): + code.putln('Py_ssize_t kw_args;') + code.putln('const Py_ssize_t pos_args = PyTuple_GET_SIZE(%s);' % Naming.args_cname) + # copy the values from the args tuple and check that it's not too long + code.putln('switch (pos_args) {') + if self.star_arg: + code.putln('default:') + for i in range(max_positional_args-1, -1, -1): + code.put('case %2d: ' % (i+1)) + code.putln("values[%d] = PyTuple_GET_ITEM(%s, %d);" % ( i, Naming.args_cname, i)) code.putln('CYTHON_FALLTHROUGH;') - code.putln('case 0: break;') - if not self.star_arg: - code.put('default: ') # more arguments than allowed - code.put_goto(argtuple_error_label) - code.putln('}') - - # The code above is very often (but not always) the same as - # the optimised non-kwargs tuple unpacking code, so we keep - # the code block above at the very top, before the following - # 'external' PyDict_Size() call, to make it easy for the C - # compiler to merge the two separate tuple unpacking - # implementations into one when they turn out to be identical. - - # If we received kwargs, fill up the positional/required - # arguments with values from the kw dict - code.putln('kw_args = PyDict_Size(%s);' % Naming.kwds_cname) - if self.num_required_args or max_positional_args > 0: - last_required_arg = -1 - for i, arg in enumerate(all_args): - if not arg.default: - last_required_arg = i - if last_required_arg < max_positional_args: - last_required_arg = max_positional_args-1 - if max_positional_args > 0: - code.putln('switch (pos_args) {') - for i, arg in enumerate(all_args[:last_required_arg+1]): - if max_positional_args > 0 and i <= max_positional_args: + code.putln('case 0: break;') + if not self.star_arg: + code.put('default: ') # more arguments than allowed + code.put_goto(argtuple_error_label) + code.putln('}') + + # The code above is very often (but not always) the same as + # the optimised non-kwargs tuple unpacking code, so we keep + # the code block above at the very top, before the following + # 'external' PyDict_Size() call, to make it easy for the C + # compiler to merge the two separate tuple unpacking + # implementations into one when they turn out to be identical. + + # If we received kwargs, fill up the positional/required + # arguments with values from the kw dict + code.putln('kw_args = PyDict_Size(%s);' % Naming.kwds_cname) + if self.num_required_args or max_positional_args > 0: + last_required_arg = -1 + for i, arg in enumerate(all_args): + if not arg.default: + last_required_arg = i + if last_required_arg < max_positional_args: + last_required_arg = max_positional_args-1 + if max_positional_args > 0: + code.putln('switch (pos_args) {') + for i, arg in enumerate(all_args[:last_required_arg+1]): + if max_positional_args > 0 and i <= max_positional_args: if i != 0: code.putln('CYTHON_FALLTHROUGH;') - if self.star_arg and i == max_positional_args: - code.putln('default:') - else: - code.putln('case %2d:' % i) - pystring_cname = code.intern_identifier(arg.name) - if arg.default: - if arg.kw_only: - # optional kw-only args are handled separately below - continue - code.putln('if (kw_args > 0) {') - # don't overwrite default argument + if self.star_arg and i == max_positional_args: + code.putln('default:') + else: + code.putln('case %2d:' % i) + pystring_cname = code.intern_identifier(arg.name) + if arg.default: + if arg.kw_only: + # optional kw-only args are handled separately below + continue + code.putln('if (kw_args > 0) {') + # don't overwrite default argument code.putln('PyObject* value = __Pyx_PyDict_GetItemStr(%s, %s);' % ( - Naming.kwds_cname, pystring_cname)) - code.putln('if (value) { values[%d] = value; kw_args--; }' % i) - code.putln('}') - else: + Naming.kwds_cname, pystring_cname)) + code.putln('if (value) { values[%d] = value; kw_args--; }' % i) + code.putln('}') + else: code.putln('if (likely((values[%d] = __Pyx_PyDict_GetItemStr(%s, %s)) != 0)) kw_args--;' % ( - i, Naming.kwds_cname, pystring_cname)) - if i < min_positional_args: - if i == 0: - # special case: we know arg 0 is missing - code.put('else ') - code.put_goto(argtuple_error_label) - else: - # print the correct number of values (args or - # kwargs) that were passed into positional - # arguments up to this point - code.putln('else {') - code.globalstate.use_utility_code( - UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) - code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d, %d); ' % ( + i, Naming.kwds_cname, pystring_cname)) + if i < min_positional_args: + if i == 0: + # special case: we know arg 0 is missing + code.put('else ') + code.put_goto(argtuple_error_label) + else: + # print the correct number of values (args or + # kwargs) that were passed into positional + # arguments up to this point + code.putln('else {') + code.globalstate.use_utility_code( + UtilityCode.load_cached("RaiseArgTupleInvalid", "FunctionArguments.c")) + code.put('__Pyx_RaiseArgtupleInvalid("%s", %d, %d, %d, %d); ' % ( self.name, has_fixed_positional_count, min_positional_args, max_positional_args, i)) - code.putln(code.error_goto(self.pos)) - code.putln('}') - elif arg.kw_only: - code.putln('else {') + code.putln(code.error_goto(self.pos)) + code.putln('}') + elif arg.kw_only: + code.putln('else {') code.globalstate.use_utility_code( UtilityCode.load_cached("RaiseKeywordRequired", "FunctionArguments.c")) code.put('__Pyx_RaiseKeywordRequired("%s", %s); ' % ( self.name, pystring_cname)) - code.putln(code.error_goto(self.pos)) - code.putln('}') - if max_positional_args > 0: - code.putln('}') - - if has_kw_only_args: - # unpack optional keyword-only arguments separately because - # checking for interned strings in a dict is faster than iterating - self.generate_optional_kwonly_args_unpacking_code(all_args, code) - - code.putln('if (unlikely(kw_args > 0)) {') - # non-positional/-required kw args left in dict: default args, - # kw-only args, **kwargs or error - # - # This is sort of a catch-all: except for checking required - # arguments, this will always do the right thing for unpacking - # keyword arguments, so that we can concentrate on optimising - # common cases above. - if max_positional_args == 0: - pos_arg_count = "0" - elif self.star_arg: - code.putln("const Py_ssize_t used_pos_args = (pos_args < %d) ? pos_args : %d;" % ( + code.putln(code.error_goto(self.pos)) + code.putln('}') + if max_positional_args > 0: + code.putln('}') + + if has_kw_only_args: + # unpack optional keyword-only arguments separately because + # checking for interned strings in a dict is faster than iterating + self.generate_optional_kwonly_args_unpacking_code(all_args, code) + + code.putln('if (unlikely(kw_args > 0)) {') + # non-positional/-required kw args left in dict: default args, + # kw-only args, **kwargs or error + # + # This is sort of a catch-all: except for checking required + # arguments, this will always do the right thing for unpacking + # keyword arguments, so that we can concentrate on optimising + # common cases above. + if max_positional_args == 0: + pos_arg_count = "0" + elif self.star_arg: + code.putln("const Py_ssize_t used_pos_args = (pos_args < %d) ? pos_args : %d;" % ( max_positional_args, max_positional_args)) - pos_arg_count = "used_pos_args" - else: - pos_arg_count = "pos_args" - code.globalstate.use_utility_code( - UtilityCode.load_cached("ParseKeywords", "FunctionArguments.c")) + pos_arg_count = "used_pos_args" + else: + pos_arg_count = "pos_args" + code.globalstate.use_utility_code( + UtilityCode.load_cached("ParseKeywords", "FunctionArguments.c")) code.putln('if (unlikely(__Pyx_ParseOptionalKeywords(%s, %s, %s, values, %s, "%s") < 0)) %s' % ( Naming.kwds_cname, Naming.pykwdlist_cname, @@ -3961,69 +3961,69 @@ class DefNodeWrapper(FuncDefNode): pos_arg_count, self.name, code.error_goto(self.pos))) - code.putln('}') - - def generate_optional_kwonly_args_unpacking_code(self, all_args, code): - optional_args = [] - first_optional_arg = -1 - for i, arg in enumerate(all_args): - if not arg.kw_only or not arg.default: - continue - if not optional_args: - first_optional_arg = i - optional_args.append(arg.name) - if optional_args: - if len(optional_args) > 1: - # if we receive more than the named kwargs, we either have **kwargs - # (in which case we must iterate anyway) or it's an error (which we - # also handle during iteration) => skip this part if there are more - code.putln('if (kw_args > 0 && %s(kw_args <= %d)) {' % ( - not self.starstar_arg and 'likely' or '', - len(optional_args))) - code.putln('Py_ssize_t index;') - # not unrolling the loop here reduces the C code overhead - code.putln('for (index = %d; index < %d && kw_args > 0; index++) {' % ( - first_optional_arg, first_optional_arg + len(optional_args))) - else: - code.putln('if (kw_args == 1) {') - code.putln('const Py_ssize_t index = %d;' % first_optional_arg) + code.putln('}') + + def generate_optional_kwonly_args_unpacking_code(self, all_args, code): + optional_args = [] + first_optional_arg = -1 + for i, arg in enumerate(all_args): + if not arg.kw_only or not arg.default: + continue + if not optional_args: + first_optional_arg = i + optional_args.append(arg.name) + if optional_args: + if len(optional_args) > 1: + # if we receive more than the named kwargs, we either have **kwargs + # (in which case we must iterate anyway) or it's an error (which we + # also handle during iteration) => skip this part if there are more + code.putln('if (kw_args > 0 && %s(kw_args <= %d)) {' % ( + not self.starstar_arg and 'likely' or '', + len(optional_args))) + code.putln('Py_ssize_t index;') + # not unrolling the loop here reduces the C code overhead + code.putln('for (index = %d; index < %d && kw_args > 0; index++) {' % ( + first_optional_arg, first_optional_arg + len(optional_args))) + else: + code.putln('if (kw_args == 1) {') + code.putln('const Py_ssize_t index = %d;' % first_optional_arg) code.putln('PyObject* value = __Pyx_PyDict_GetItemStr(%s, *%s[index]);' % ( - Naming.kwds_cname, Naming.pykwdlist_cname)) - code.putln('if (value) { values[index] = value; kw_args--; }') - if len(optional_args) > 1: - code.putln('}') - code.putln('}') - - def generate_argument_conversion_code(self, code): - # Generate code to convert arguments from signature type to - # declared type, if needed. Also copies signature arguments - # into closure fields. - for arg in self.args: - if arg.needs_conversion: - self.generate_arg_conversion(arg, code) - - def generate_arg_conversion(self, arg, code): - # Generate conversion code for one argument. - old_type = arg.hdr_type - new_type = arg.type - if old_type.is_pyobject: - if arg.default: - code.putln("if (%s) {" % arg.hdr_cname) - else: - code.putln("assert(%s); {" % arg.hdr_cname) - self.generate_arg_conversion_from_pyobject(arg, code) - code.putln("}") - elif new_type.is_pyobject: - self.generate_arg_conversion_to_pyobject(arg, code) - else: - if new_type.assignable_from(old_type): + Naming.kwds_cname, Naming.pykwdlist_cname)) + code.putln('if (value) { values[index] = value; kw_args--; }') + if len(optional_args) > 1: + code.putln('}') + code.putln('}') + + def generate_argument_conversion_code(self, code): + # Generate code to convert arguments from signature type to + # declared type, if needed. Also copies signature arguments + # into closure fields. + for arg in self.args: + if arg.needs_conversion: + self.generate_arg_conversion(arg, code) + + def generate_arg_conversion(self, arg, code): + # Generate conversion code for one argument. + old_type = arg.hdr_type + new_type = arg.type + if old_type.is_pyobject: + if arg.default: + code.putln("if (%s) {" % arg.hdr_cname) + else: + code.putln("assert(%s); {" % arg.hdr_cname) + self.generate_arg_conversion_from_pyobject(arg, code) + code.putln("}") + elif new_type.is_pyobject: + self.generate_arg_conversion_to_pyobject(arg, code) + else: + if new_type.assignable_from(old_type): code.putln("%s = %s;" % (arg.entry.cname, arg.hdr_cname)) - else: + else: error(arg.pos, "Cannot convert 1 argument from '%s' to '%s'" % (old_type, new_type)) - - def generate_arg_conversion_from_pyobject(self, arg, code): - new_type = arg.type - # copied from CoerceFromPyTypeNode + + def generate_arg_conversion_from_pyobject(self, arg, code): + new_type = arg.type + # copied from CoerceFromPyTypeNode if new_type.from_py_function: code.putln(new_type.from_py_call_code( arg.hdr_cname, @@ -4031,94 +4031,94 @@ class DefNodeWrapper(FuncDefNode): arg.pos, code, )) - else: + else: error(arg.pos, "Cannot convert Python object argument to type '%s'" % new_type) - - def generate_arg_conversion_to_pyobject(self, arg, code): - old_type = arg.hdr_type - func = old_type.to_py_function - if func: - code.putln("%s = %s(%s); %s" % ( - arg.entry.cname, - func, - arg.hdr_cname, - code.error_goto_if_null(arg.entry.cname, arg.pos))) - code.put_var_gotref(arg.entry) - else: + + def generate_arg_conversion_to_pyobject(self, arg, code): + old_type = arg.hdr_type + func = old_type.to_py_function + if func: + code.putln("%s = %s(%s); %s" % ( + arg.entry.cname, + func, + arg.hdr_cname, + code.error_goto_if_null(arg.entry.cname, arg.pos))) + code.put_var_gotref(arg.entry) + else: error(arg.pos, "Cannot convert argument of type '%s' to Python object" % old_type) - - def generate_argument_type_tests(self, code): - # Generate type tests for args whose signature - # type is PyObject * and whose declared type is - # a subtype thereof. - for arg in self.args: - if arg.needs_type_test: - self.generate_arg_type_test(arg, code) - elif not arg.accept_none and (arg.type.is_pyobject or - arg.type.is_buffer or - arg.type.is_memoryviewslice): - self.generate_arg_none_check(arg, code) - - def error_value(self): - return self.signature.error_value - - -class GeneratorDefNode(DefNode): - # Generator function node that creates a new generator instance when called. - # - # gbody GeneratorBodyDefNode the function implementing the generator - # - - is_generator = True + + def generate_argument_type_tests(self, code): + # Generate type tests for args whose signature + # type is PyObject * and whose declared type is + # a subtype thereof. + for arg in self.args: + if arg.needs_type_test: + self.generate_arg_type_test(arg, code) + elif not arg.accept_none and (arg.type.is_pyobject or + arg.type.is_buffer or + arg.type.is_memoryviewslice): + self.generate_arg_none_check(arg, code) + + def error_value(self): + return self.signature.error_value + + +class GeneratorDefNode(DefNode): + # Generator function node that creates a new generator instance when called. + # + # gbody GeneratorBodyDefNode the function implementing the generator + # + + is_generator = True is_coroutine = False is_iterable_coroutine = False is_asyncgen = False gen_type_name = 'Generator' - needs_closure = True - - child_attrs = DefNode.child_attrs + ["gbody"] - - def __init__(self, pos, **kwargs): - # XXX: don't actually needs a body - kwargs['body'] = StatListNode(pos, stats=[], is_terminator=True) - super(GeneratorDefNode, self).__init__(pos, **kwargs) - - def analyse_declarations(self, env): - super(GeneratorDefNode, self).analyse_declarations(env) - self.gbody.local_scope = self.local_scope - self.gbody.analyse_declarations(env) - - def generate_function_body(self, env, code): - body_cname = self.gbody.entry.func_cname - name = code.intern_identifier(self.name) - qualname = code.intern_identifier(self.qualname) + needs_closure = True + + child_attrs = DefNode.child_attrs + ["gbody"] + + def __init__(self, pos, **kwargs): + # XXX: don't actually needs a body + kwargs['body'] = StatListNode(pos, stats=[], is_terminator=True) + super(GeneratorDefNode, self).__init__(pos, **kwargs) + + def analyse_declarations(self, env): + super(GeneratorDefNode, self).analyse_declarations(env) + self.gbody.local_scope = self.local_scope + self.gbody.analyse_declarations(env) + + def generate_function_body(self, env, code): + body_cname = self.gbody.entry.func_cname + name = code.intern_identifier(self.name) + qualname = code.intern_identifier(self.qualname) module_name = code.intern_identifier(self.module_name) - - code.putln('{') + + code.putln('{') code.putln('__pyx_CoroutineObject *gen = __Pyx_%s_New(' '(__pyx_coroutine_body_t) %s, %s, (PyObject *) %s, %s, %s, %s); %s' % ( self.gen_type_name, body_cname, self.code_object.calculate_result_code(code) if self.code_object else 'NULL', Naming.cur_scope_cname, name, qualname, module_name, - code.error_goto_if_null('gen', self.pos))) - code.put_decref(Naming.cur_scope_cname, py_object_type) - if self.requires_classobj: - classobj_cname = 'gen->classobj' - code.putln('%s = __Pyx_CyFunction_GetClassObj(%s);' % ( - classobj_cname, Naming.self_cname)) - code.put_incref(classobj_cname, py_object_type) - code.put_giveref(classobj_cname) - code.put_finish_refcount_context() - code.putln('return (PyObject *) gen;') - code.putln('}') - - def generate_function_definitions(self, env, code): + code.error_goto_if_null('gen', self.pos))) + code.put_decref(Naming.cur_scope_cname, py_object_type) + if self.requires_classobj: + classobj_cname = 'gen->classobj' + code.putln('%s = __Pyx_CyFunction_GetClassObj(%s);' % ( + classobj_cname, Naming.self_cname)) + code.put_incref(classobj_cname, py_object_type) + code.put_giveref(classobj_cname) + code.put_finish_refcount_context() + code.putln('return (PyObject *) gen;') + code.putln('}') + + def generate_function_definitions(self, env, code): env.use_utility_code(UtilityCode.load_cached(self.gen_type_name, "Coroutine.c")) - self.gbody.generate_function_header(code, proto=True) - super(GeneratorDefNode, self).generate_function_definitions(env, code) - self.gbody.generate_function_definitions(env, code) - - + self.gbody.generate_function_header(code, proto=True) + super(GeneratorDefNode, self).generate_function_definitions(env, code) + self.gbody.generate_function_definitions(env, code) + + class AsyncDefNode(GeneratorDefNode): gen_type_name = 'Coroutine' is_coroutine = True @@ -4134,71 +4134,71 @@ class AsyncGenNode(AsyncDefNode): is_asyncgen = True -class GeneratorBodyDefNode(DefNode): - # Main code body of a generator implemented as a DefNode. - # - - is_generator_body = True +class GeneratorBodyDefNode(DefNode): + # Main code body of a generator implemented as a DefNode. + # + + is_generator_body = True is_inlined = False is_async_gen_body = False inlined_comprehension_type = None # container type for inlined comprehensions - + def __init__(self, pos=None, name=None, body=None, is_async_gen_body=False): - super(GeneratorBodyDefNode, self).__init__( + super(GeneratorBodyDefNode, self).__init__( pos=pos, body=body, name=name, is_async_gen_body=is_async_gen_body, doc=None, args=[], star_arg=None, starstar_arg=None) - - def declare_generator_body(self, env): - prefix = env.next_id(env.scope_prefix) - name = env.next_id('generator') - cname = Naming.genbody_prefix + prefix + name - entry = env.declare_var(None, py_object_type, self.pos, - cname=cname, visibility='private') - entry.func_cname = cname - entry.qualified_name = EncodedString(self.name) + + def declare_generator_body(self, env): + prefix = env.next_id(env.scope_prefix) + name = env.next_id('generator') + cname = Naming.genbody_prefix + prefix + name + entry = env.declare_var(None, py_object_type, self.pos, + cname=cname, visibility='private') + entry.func_cname = cname + entry.qualified_name = EncodedString(self.name) # Work-around for https://github.com/cython/cython/issues/1699 # We don't currently determine whether the generator entry is used or not, # so mark it as used to avoid false warnings. entry.used = True - self.entry = entry - - def analyse_declarations(self, env): - self.analyse_argument_types(env) - self.declare_generator_body(env) - - def generate_function_header(self, code, proto=False): + self.entry = entry + + def analyse_declarations(self, env): + self.analyse_argument_types(env) + self.declare_generator_body(env) + + def generate_function_header(self, code, proto=False): header = "static PyObject *%s(PyObject *%s_obj, CYTHON_UNUSED PyThreadState *%s, PyObject *%s)" % ( - self.entry.func_cname, - Naming.generator_cname, + self.entry.func_cname, + Naming.generator_cname, Naming.local_tstate_cname, - Naming.sent_value_cname) - if proto: - code.putln('%s; /* proto */' % header) - else: - code.putln('%s /* generator body */\n{' % header) - - def generate_function_definitions(self, env, code): - lenv = self.local_scope - - # Generate closure function definitions - self.body.generate_function_definitions(lenv, code) - - # Generate C code for header and body of function + Naming.sent_value_cname) + if proto: + code.putln('%s; /* proto */' % header) + else: + code.putln('%s /* generator body */\n{' % header) + + def generate_function_definitions(self, env, code): + lenv = self.local_scope + + # Generate closure function definitions + self.body.generate_function_definitions(lenv, code) + + # Generate C code for header and body of function code.enter_cfunc_scope(lenv) - code.return_from_error_cleanup_label = code.new_label() - - # ----- Top-level constants used by this function - code.mark_pos(self.pos) - self.generate_cached_builtins_decls(lenv, code) - # ----- Function header - code.putln("") - self.generate_function_header(code) + code.return_from_error_cleanup_label = code.new_label() + + # ----- Top-level constants used by this function + code.mark_pos(self.pos) + self.generate_cached_builtins_decls(lenv, code) + # ----- Function header + code.putln("") + self.generate_function_header(code) code.putln("__pyx_CoroutineObject *%s = (__pyx_CoroutineObject *)%s_obj;" % (Naming.generator_cname, Naming.generator_cname)) - closure_init_code = code.insertion_point() - # ----- Local variables - code.putln("PyObject *%s = NULL;" % Naming.retval_cname) - tempvardecl_code = code.insertion_point() - code.put_declare_refcount_context() + closure_init_code = code.insertion_point() + # ----- Local variables + code.putln("PyObject *%s = NULL;" % Naming.retval_cname) + tempvardecl_code = code.insertion_point() + code.put_declare_refcount_context() code.put_setup_refcount_context(self.entry.name or self.entry.qualified_name) profile = code.globalstate.directives['profile'] linetrace = code.globalstate.directives['linetrace'] @@ -4207,16 +4207,16 @@ class GeneratorBodyDefNode(DefNode): code.funcstate.can_trace = True code_object = self.code_object.calculate_result_code(code) if self.code_object else None code.put_trace_frame_init(code_object) - - # ----- Resume switch point. - code.funcstate.init_closure_temps(lenv.scope_class.type.scope) - resume_code = code.insertion_point() - first_run_label = code.new_label('first_run') - code.use_label(first_run_label) - code.put_label(first_run_label) - code.putln('%s' % - (code.error_goto_if_null(Naming.sent_value_cname, self.pos))) - + + # ----- Resume switch point. + code.funcstate.init_closure_temps(lenv.scope_class.type.scope) + resume_code = code.insertion_point() + first_run_label = code.new_label('first_run') + code.use_label(first_run_label) + code.put_label(first_run_label) + code.putln('%s' % + (code.error_goto_if_null(Naming.sent_value_cname, self.pos))) + # ----- prepare target container for inlined comprehension if self.is_inlined and self.inlined_comprehension_type is not None: target_type = self.inlined_comprehension_type @@ -4234,49 +4234,49 @@ class GeneratorBodyDefNode(DefNode): code.error_goto_if_null(Naming.retval_cname, self.pos))) code.put_gotref(Naming.retval_cname) - # ----- Function body - self.generate_function_body(env, code) - # ----- Closure initialization + # ----- Function body + self.generate_function_body(env, code) + # ----- Closure initialization if lenv.scope_class.type.scope.var_entries: - closure_init_code.putln('%s = %s;' % ( - lenv.scope_class.type.declaration_code(Naming.cur_scope_cname), - lenv.scope_class.type.cast_code('%s->closure' % - Naming.generator_cname))) + closure_init_code.putln('%s = %s;' % ( + lenv.scope_class.type.declaration_code(Naming.cur_scope_cname), + lenv.scope_class.type.cast_code('%s->closure' % + Naming.generator_cname))) # FIXME: this silences a potential "unused" warning => try to avoid unused closures in more cases code.putln("CYTHON_MAYBE_UNUSED_VAR(%s);" % Naming.cur_scope_cname) - + if profile or linetrace: code.funcstate.can_trace = False - code.mark_pos(self.pos) - code.putln("") - code.putln("/* function exit code */") - - # on normal generator termination, we do not take the exception propagation - # path: no traceback info is required and not creating it is much faster + code.mark_pos(self.pos) + code.putln("") + code.putln("/* function exit code */") + + # on normal generator termination, we do not take the exception propagation + # path: no traceback info is required and not creating it is much faster if not self.is_inlined and not self.body.is_terminator: if self.is_async_gen_body: code.globalstate.use_utility_code( UtilityCode.load_cached("StopAsyncIteration", "Coroutine.c")) code.putln('PyErr_SetNone(%s);' % ( '__Pyx_PyExc_StopAsyncIteration' if self.is_async_gen_body else 'PyExc_StopIteration')) - # ----- Error cleanup + # ----- Error cleanup if code.label_used(code.error_label): - if not self.body.is_terminator: - code.put_goto(code.return_label) - code.put_label(code.error_label) + if not self.body.is_terminator: + code.put_goto(code.return_label) + code.put_label(code.error_label) if self.is_inlined and self.inlined_comprehension_type is not None: code.put_xdecref_clear(Naming.retval_cname, py_object_type) if Future.generator_stop in env.global_scope().context.future_directives: # PEP 479: turn accidental StopIteration exceptions into a RuntimeError code.globalstate.use_utility_code(UtilityCode.load_cached("pep479", "Coroutine.c")) code.putln("__Pyx_Generator_Replace_StopIteration(%d);" % bool(self.is_async_gen_body)) - for cname, type in code.funcstate.all_managed_temps(): - code.put_xdecref(cname, type) - code.put_add_traceback(self.entry.qualified_name) - - # ----- Non-error return cleanup - code.put_label(code.return_label) + for cname, type in code.funcstate.all_managed_temps(): + code.put_xdecref(cname, type) + code.put_add_traceback(self.entry.qualified_name) + + # ----- Non-error return cleanup + code.put_label(code.return_label) if self.is_inlined: code.put_xgiveref(Naming.retval_cname) else: @@ -4285,64 +4285,64 @@ class GeneratorBodyDefNode(DefNode): code.putln("#if !CYTHON_USE_EXC_INFO_STACK") code.putln("__Pyx_Coroutine_ResetAndClearException(%s);" % Naming.generator_cname) code.putln("#endif") - code.putln('%s->resume_label = -1;' % Naming.generator_cname) - # clean up as early as possible to help breaking any reference cycles + code.putln('%s->resume_label = -1;' % Naming.generator_cname) + # clean up as early as possible to help breaking any reference cycles code.putln('__Pyx_Coroutine_clear((PyObject*)%s);' % Naming.generator_cname) if profile or linetrace: code.put_trace_return(Naming.retval_cname, nogil=not code.funcstate.gil_owned) - code.put_finish_refcount_context() + code.put_finish_refcount_context() code.putln("return %s;" % Naming.retval_cname) - code.putln("}") - - # ----- Go back and insert temp variable declarations - tempvardecl_code.put_temp_declarations(code.funcstate) - # ----- Generator resume code + code.putln("}") + + # ----- Go back and insert temp variable declarations + tempvardecl_code.put_temp_declarations(code.funcstate) + # ----- Generator resume code if profile or linetrace: resume_code.put_trace_call(self.entry.qualified_name, self.pos, nogil=not code.funcstate.gil_owned) - resume_code.putln("switch (%s->resume_label) {" % ( - Naming.generator_cname)) - - resume_code.putln("case 0: goto %s;" % first_run_label) - - for i, label in code.yield_labels: - resume_code.putln("case %d: goto %s;" % (i, label)) - resume_code.putln("default: /* CPython raises the right error here */") + resume_code.putln("switch (%s->resume_label) {" % ( + Naming.generator_cname)) + + resume_code.putln("case 0: goto %s;" % first_run_label) + + for i, label in code.yield_labels: + resume_code.putln("case %d: goto %s;" % (i, label)) + resume_code.putln("default: /* CPython raises the right error here */") if profile or linetrace: resume_code.put_trace_return("Py_None", nogil=not code.funcstate.gil_owned) - resume_code.put_finish_refcount_context() - resume_code.putln("return NULL;") - resume_code.putln("}") - - code.exit_cfunc_scope() - - -class OverrideCheckNode(StatNode): - # A Node for dispatching to the def method if it + resume_code.put_finish_refcount_context() + resume_code.putln("return NULL;") + resume_code.putln("}") + + code.exit_cfunc_scope() + + +class OverrideCheckNode(StatNode): + # A Node for dispatching to the def method if it # is overridden. - # - # py_func - # - # args - # func_temp - # body - - child_attrs = ['body'] - - body = None - - def analyse_expressions(self, env): - self.args = env.arg_entries - if self.py_func.is_module_scope: - first_arg = 0 - else: - first_arg = 1 - from . import ExprNodes - self.func_node = ExprNodes.RawCNameExprNode(self.pos, py_object_type) - call_node = ExprNodes.SimpleCallNode( - self.pos, function=self.func_node, + # + # py_func + # + # args + # func_temp + # body + + child_attrs = ['body'] + + body = None + + def analyse_expressions(self, env): + self.args = env.arg_entries + if self.py_func.is_module_scope: + first_arg = 0 + else: + first_arg = 1 + from . import ExprNodes + self.func_node = ExprNodes.RawCNameExprNode(self.pos, py_object_type) + call_node = ExprNodes.SimpleCallNode( + self.pos, function=self.func_node, args=[ExprNodes.NameNode(self.pos, name=arg.name) for arg in self.args[first_arg:]]) if env.return_type.is_void or env.return_type.is_returncode: @@ -4351,22 +4351,22 @@ class OverrideCheckNode(StatNode): ReturnStatNode(self.pos, value=None)]) else: self.body = ReturnStatNode(self.pos, value=call_node) - self.body = self.body.analyse_expressions(env) - return self - - def generate_execution_code(self, code): - interned_attr_cname = code.intern_identifier(self.py_func.entry.name) - # Check to see if we are an extension type - if self.py_func.is_module_scope: - self_arg = "((PyObject *)%s)" % Naming.module_cname - else: - self_arg = "((PyObject *)%s)" % self.args[0].cname - code.putln("/* Check if called by wrapper */") - code.putln("if (unlikely(%s)) ;" % Naming.skip_dispatch_cname) - code.putln("/* Check if overridden in Python */") - if self.py_func.is_module_scope: - code.putln("else {") - else: + self.body = self.body.analyse_expressions(env) + return self + + def generate_execution_code(self, code): + interned_attr_cname = code.intern_identifier(self.py_func.entry.name) + # Check to see if we are an extension type + if self.py_func.is_module_scope: + self_arg = "((PyObject *)%s)" % Naming.module_cname + else: + self_arg = "((PyObject *)%s)" % self.args[0].cname + code.putln("/* Check if called by wrapper */") + code.putln("if (unlikely(%s)) ;" % Naming.skip_dispatch_cname) + code.putln("/* Check if overridden in Python */") + if self.py_func.is_module_scope: + code.putln("else {") + else: code.putln("else if (unlikely((Py_TYPE(%s)->tp_dictoffset != 0)" " || (Py_TYPE(%s)->tp_flags & (Py_TPFLAGS_IS_ABSTRACT | Py_TPFLAGS_HEAPTYPE)))) {" % ( self_arg, self_arg)) @@ -4386,22 +4386,22 @@ class OverrideCheckNode(StatNode): Naming.type_dict_guard_temp, self_arg)) code.putln("#endif") - func_node_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) - self.func_node.set_cname(func_node_temp) - # need to get attribute manually--scope would return cdef method - code.globalstate.use_utility_code( - UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c")) - err = code.error_goto_if_null(func_node_temp, self.pos) - code.putln("%s = __Pyx_PyObject_GetAttrStr(%s, %s); %s" % ( - func_node_temp, self_arg, interned_attr_cname, err)) - code.put_gotref(func_node_temp) - - is_builtin_function_or_method = "PyCFunction_Check(%s)" % func_node_temp + func_node_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) + self.func_node.set_cname(func_node_temp) + # need to get attribute manually--scope would return cdef method + code.globalstate.use_utility_code( + UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c")) + err = code.error_goto_if_null(func_node_temp, self.pos) + code.putln("%s = __Pyx_PyObject_GetAttrStr(%s, %s); %s" % ( + func_node_temp, self_arg, interned_attr_cname, err)) + code.put_gotref(func_node_temp) + + is_builtin_function_or_method = "PyCFunction_Check(%s)" % func_node_temp is_overridden = "(PyCFunction_GET_FUNCTION(%s) != (PyCFunction)(void*)%s)" % ( - func_node_temp, self.py_func.entry.func_cname) - code.putln("if (!%s || %s) {" % (is_builtin_function_or_method, is_overridden)) - self.body.generate_execution_code(code) - code.putln("}") + func_node_temp, self.py_func.entry.func_cname) + code.putln("if (!%s || %s) {" % (is_builtin_function_or_method, is_overridden)) + self.body.generate_execution_code(code) + code.putln("}") # NOTE: it's not 100% sure that we catch the exact versions here that were used for the lookup, # but it is very unlikely that the versions change during lookup, and the type dict safe guard @@ -4423,63 +4423,63 @@ class OverrideCheckNode(StatNode): code.putln("}") code.putln("#endif") - code.put_decref_clear(func_node_temp, PyrexTypes.py_object_type) - code.funcstate.release_temp(func_node_temp) + code.put_decref_clear(func_node_temp, PyrexTypes.py_object_type) + code.funcstate.release_temp(func_node_temp) code.putln("#if CYTHON_USE_DICT_VERSIONS && CYTHON_USE_PYTYPE_LOOKUP && CYTHON_USE_TYPE_SLOTS") - code.putln("}") + code.putln("}") code.putln("#endif") - + code.putln("}") -class ClassDefNode(StatNode, BlockNode): - pass - - -class PyClassDefNode(ClassDefNode): - # A Python class definition. - # - # name EncodedString Name of the class - # doc string or None - # body StatNode Attribute definition code - # entry Symtab.Entry - # scope PyClassScope - # decorators [DecoratorNode] list of decorators or None - # - # The following subnodes are constructed internally: - # - # dict DictNode Class dictionary or Py3 namespace - # classobj ClassNode Class object - # target NameNode Variable to assign class object to - - child_attrs = ["body", "dict", "metaclass", "mkw", "bases", "class_result", - "target", "class_cell", "decorators"] - decorators = None - class_result = None - is_py3_style_class = False # Python3 style class (kwargs) - metaclass = None - mkw = None - - def __init__(self, pos, name, bases, doc, body, decorators=None, +class ClassDefNode(StatNode, BlockNode): + pass + + +class PyClassDefNode(ClassDefNode): + # A Python class definition. + # + # name EncodedString Name of the class + # doc string or None + # body StatNode Attribute definition code + # entry Symtab.Entry + # scope PyClassScope + # decorators [DecoratorNode] list of decorators or None + # + # The following subnodes are constructed internally: + # + # dict DictNode Class dictionary or Py3 namespace + # classobj ClassNode Class object + # target NameNode Variable to assign class object to + + child_attrs = ["body", "dict", "metaclass", "mkw", "bases", "class_result", + "target", "class_cell", "decorators"] + decorators = None + class_result = None + is_py3_style_class = False # Python3 style class (kwargs) + metaclass = None + mkw = None + + def __init__(self, pos, name, bases, doc, body, decorators=None, keyword_args=None, force_py3_semantics=False): - StatNode.__init__(self, pos) - self.name = name - self.doc = doc - self.body = body - self.decorators = decorators - self.bases = bases - from . import ExprNodes - if self.doc and Options.docstrings: - doc = embed_position(self.pos, self.doc) - doc_node = ExprNodes.StringNode(pos, value=doc) - else: - doc_node = None - - allow_py2_metaclass = not force_py3_semantics + StatNode.__init__(self, pos) + self.name = name + self.doc = doc + self.body = body + self.decorators = decorators + self.bases = bases + from . import ExprNodes + if self.doc and Options.docstrings: + doc = embed_position(self.pos, self.doc) + doc_node = ExprNodes.StringNode(pos, value=doc) + else: + doc_node = None + + allow_py2_metaclass = not force_py3_semantics if keyword_args: - allow_py2_metaclass = False - self.is_py3_style_class = True + allow_py2_metaclass = False + self.is_py3_style_class = True if keyword_args.is_dict_literal: if keyword_args.key_value_pairs: for i, item in list(enumerate(keyword_args.key_value_pairs))[::-1]: @@ -4494,53 +4494,53 @@ class PyClassDefNode(ClassDefNode): self.mkw = keyword_args else: assert self.metaclass is not None - else: + else: # MergedDictNode self.mkw = ExprNodes.ProxyNode(keyword_args) - - if force_py3_semantics or self.bases or self.mkw or self.metaclass: - if self.metaclass is None: + + if force_py3_semantics or self.bases or self.mkw or self.metaclass: + if self.metaclass is None: if keyword_args and not keyword_args.is_dict_literal: - # **kwargs may contain 'metaclass' arg - mkdict = self.mkw - else: - mkdict = None - if (not mkdict and - self.bases.is_sequence_constructor and - not self.bases.args): - pass # no base classes => no inherited metaclass - else: - self.metaclass = ExprNodes.PyClassMetaclassNode( + # **kwargs may contain 'metaclass' arg + mkdict = self.mkw + else: + mkdict = None + if (not mkdict and + self.bases.is_sequence_constructor and + not self.bases.args): + pass # no base classes => no inherited metaclass + else: + self.metaclass = ExprNodes.PyClassMetaclassNode( pos, class_def_node=self) - needs_metaclass_calculation = False - else: - needs_metaclass_calculation = True - - self.dict = ExprNodes.PyClassNamespaceNode( + needs_metaclass_calculation = False + else: + needs_metaclass_calculation = True + + self.dict = ExprNodes.PyClassNamespaceNode( pos, name=name, doc=doc_node, class_def_node=self) - self.classobj = ExprNodes.Py3ClassNode( + self.classobj = ExprNodes.Py3ClassNode( pos, name=name, class_def_node=self, doc=doc_node, - calculate_metaclass=needs_metaclass_calculation, - allow_py2_metaclass=allow_py2_metaclass) - else: - # no bases, no metaclass => old style class creation - self.dict = ExprNodes.DictNode(pos, key_value_pairs=[]) - self.classobj = ExprNodes.ClassNode( + calculate_metaclass=needs_metaclass_calculation, + allow_py2_metaclass=allow_py2_metaclass) + else: + # no bases, no metaclass => old style class creation + self.dict = ExprNodes.DictNode(pos, key_value_pairs=[]) + self.classobj = ExprNodes.ClassNode( pos, name=name, class_def_node=self, doc=doc_node) - - self.target = ExprNodes.NameNode(pos, name=name) - self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos) - - def as_cclass(self): - """ - Return this node as if it were declared as an extension class - """ - if self.is_py3_style_class: - error(self.classobj.pos, "Python3 style class could not be represented as C class") - return - + + self.target = ExprNodes.NameNode(pos, name=name) + self.class_cell = ExprNodes.ClassCellInjectorNode(self.pos) + + def as_cclass(self): + """ + Return this node as if it were declared as an extension class + """ + if self.is_py3_style_class: + error(self.classobj.pos, "Python3 style class could not be represented as C class") + return + from . import ExprNodes - return CClassDefNode(self.pos, + return CClassDefNode(self.pos, visibility='private', module_name=None, class_name=self.name, @@ -4549,67 +4549,67 @@ class PyClassDefNode(ClassDefNode): body=self.body, in_pxd=False, doc=self.doc) - - def create_scope(self, env): - genv = env - while genv.is_py_class_scope or genv.is_c_class_scope: - genv = genv.outer_scope + + def create_scope(self, env): + genv = env + while genv.is_py_class_scope or genv.is_c_class_scope: + genv = genv.outer_scope cenv = self.scope = PyClassScope(name=self.name, outer_scope=genv) - return cenv - - def analyse_declarations(self, env): - class_result = self.classobj - if self.decorators: - from .ExprNodes import SimpleCallNode - for decorator in self.decorators[::-1]: - class_result = SimpleCallNode( - decorator.pos, + return cenv + + def analyse_declarations(self, env): + class_result = self.classobj + if self.decorators: + from .ExprNodes import SimpleCallNode + for decorator in self.decorators[::-1]: + class_result = SimpleCallNode( + decorator.pos, function=decorator.decorator, args=[class_result]) - self.decorators = None - self.class_result = class_result + self.decorators = None + self.class_result = class_result if self.bases: self.bases.analyse_declarations(env) if self.mkw: self.mkw.analyse_declarations(env) - self.class_result.analyse_declarations(env) - self.target.analyse_target_declaration(env) - cenv = self.create_scope(env) - cenv.directives = env.directives - cenv.class_obj_cname = self.target.entry.cname - self.body.analyse_declarations(cenv) - - def analyse_expressions(self, env): - if self.bases: - self.bases = self.bases.analyse_expressions(env) + self.class_result.analyse_declarations(env) + self.target.analyse_target_declaration(env) + cenv = self.create_scope(env) + cenv.directives = env.directives + cenv.class_obj_cname = self.target.entry.cname + self.body.analyse_declarations(cenv) + + def analyse_expressions(self, env): + if self.bases: + self.bases = self.bases.analyse_expressions(env) if self.mkw: self.mkw = self.mkw.analyse_expressions(env) - if self.metaclass: - self.metaclass = self.metaclass.analyse_expressions(env) - self.dict = self.dict.analyse_expressions(env) - self.class_result = self.class_result.analyse_expressions(env) - cenv = self.scope - self.body = self.body.analyse_expressions(cenv) - self.target.analyse_target_expression(env, self.classobj) - self.class_cell = self.class_cell.analyse_expressions(cenv) - return self - - def generate_function_definitions(self, env, code): - self.generate_lambda_definitions(self.scope, code) - self.body.generate_function_definitions(self.scope, code) - - def generate_execution_code(self, code): + if self.metaclass: + self.metaclass = self.metaclass.analyse_expressions(env) + self.dict = self.dict.analyse_expressions(env) + self.class_result = self.class_result.analyse_expressions(env) + cenv = self.scope + self.body = self.body.analyse_expressions(cenv) + self.target.analyse_target_expression(env, self.classobj) + self.class_cell = self.class_cell.analyse_expressions(cenv) + return self + + def generate_function_definitions(self, env, code): + self.generate_lambda_definitions(self.scope, code) + self.body.generate_function_definitions(self.scope, code) + + def generate_execution_code(self, code): code.mark_pos(self.pos) - code.pyclass_stack.append(self) - cenv = self.scope - if self.bases: - self.bases.generate_evaluation_code(code) - if self.mkw: - self.mkw.generate_evaluation_code(code) - if self.metaclass: - self.metaclass.generate_evaluation_code(code) - self.dict.generate_evaluation_code(code) - cenv.namespace_cname = cenv.class_obj_cname = self.dict.result() + code.pyclass_stack.append(self) + cenv = self.scope + if self.bases: + self.bases.generate_evaluation_code(code) + if self.mkw: + self.mkw.generate_evaluation_code(code) + if self.metaclass: + self.metaclass.generate_evaluation_code(code) + self.dict.generate_evaluation_code(code) + cenv.namespace_cname = cenv.class_obj_cname = self.dict.result() class_cell = self.class_cell if class_cell is not None and not class_cell.is_active: @@ -4617,8 +4617,8 @@ class PyClassDefNode(ClassDefNode): if class_cell is not None: class_cell.generate_evaluation_code(code) - self.body.generate_execution_code(code) - self.class_result.generate_evaluation_code(code) + self.body.generate_execution_code(code) + self.class_result.generate_evaluation_code(code) if class_cell is not None: class_cell.generate_injection_code( code, self.class_result.result()) @@ -4626,77 +4626,77 @@ class PyClassDefNode(ClassDefNode): class_cell.generate_disposal_code(code) class_cell.free_temps(code) - cenv.namespace_cname = cenv.class_obj_cname = self.classobj.result() - self.target.generate_assignment_code(self.class_result, code) - self.dict.generate_disposal_code(code) - self.dict.free_temps(code) - if self.metaclass: - self.metaclass.generate_disposal_code(code) - self.metaclass.free_temps(code) - if self.mkw: - self.mkw.generate_disposal_code(code) - self.mkw.free_temps(code) - if self.bases: - self.bases.generate_disposal_code(code) - self.bases.free_temps(code) - code.pyclass_stack.pop() - - -class CClassDefNode(ClassDefNode): - # An extension type definition. - # - # visibility 'private' or 'public' or 'extern' - # typedef_flag boolean - # api boolean - # module_name string or None For import of extern type objects - # class_name string Unqualified name of class - # as_name string or None Name to declare as in this scope + cenv.namespace_cname = cenv.class_obj_cname = self.classobj.result() + self.target.generate_assignment_code(self.class_result, code) + self.dict.generate_disposal_code(code) + self.dict.free_temps(code) + if self.metaclass: + self.metaclass.generate_disposal_code(code) + self.metaclass.free_temps(code) + if self.mkw: + self.mkw.generate_disposal_code(code) + self.mkw.free_temps(code) + if self.bases: + self.bases.generate_disposal_code(code) + self.bases.free_temps(code) + code.pyclass_stack.pop() + + +class CClassDefNode(ClassDefNode): + # An extension type definition. + # + # visibility 'private' or 'public' or 'extern' + # typedef_flag boolean + # api boolean + # module_name string or None For import of extern type objects + # class_name string Unqualified name of class + # as_name string or None Name to declare as in this scope # bases TupleNode Base class(es) - # objstruct_name string or None Specified C name of object struct - # typeobj_name string or None Specified C name of type object + # objstruct_name string or None Specified C name of object struct + # typeobj_name string or None Specified C name of type object # check_size 'warn', 'error', 'ignore' What to do if tp_basicsize does not match - # in_pxd boolean Is in a .pxd file - # decorators [DecoratorNode] list of decorators or None - # doc string or None - # body StatNode or None - # entry Symtab.Entry - # base_type PyExtensionType or None - # buffer_defaults_node DictNode or None Declares defaults for a buffer - # buffer_defaults_pos - - child_attrs = ["body"] - buffer_defaults_node = None - buffer_defaults_pos = None - typedef_flag = False - api = False - objstruct_name = None - typeobj_name = None + # in_pxd boolean Is in a .pxd file + # decorators [DecoratorNode] list of decorators or None + # doc string or None + # body StatNode or None + # entry Symtab.Entry + # base_type PyExtensionType or None + # buffer_defaults_node DictNode or None Declares defaults for a buffer + # buffer_defaults_pos + + child_attrs = ["body"] + buffer_defaults_node = None + buffer_defaults_pos = None + typedef_flag = False + api = False + objstruct_name = None + typeobj_name = None check_size = None - decorators = None - shadow = False - - def buffer_defaults(self, env): - if not hasattr(self, '_buffer_defaults'): - from . import Buffer - if self.buffer_defaults_node: - self._buffer_defaults = Buffer.analyse_buffer_options( - self.buffer_defaults_pos, - env, [], self.buffer_defaults_node, - need_complete=False) - else: - self._buffer_defaults = None - return self._buffer_defaults - - def declare(self, env): - if self.module_name and self.visibility != 'extern': - module_path = self.module_name.split(".") - home_scope = env.find_imported_module(module_path, self.pos) - if not home_scope: - return None - else: - home_scope = env - - self.entry = home_scope.declare_c_class( + decorators = None + shadow = False + + def buffer_defaults(self, env): + if not hasattr(self, '_buffer_defaults'): + from . import Buffer + if self.buffer_defaults_node: + self._buffer_defaults = Buffer.analyse_buffer_options( + self.buffer_defaults_pos, + env, [], self.buffer_defaults_node, + need_complete=False) + else: + self._buffer_defaults = None + return self._buffer_defaults + + def declare(self, env): + if self.module_name and self.visibility != 'extern': + module_path = self.module_name.split(".") + home_scope = env.find_imported_module(module_path, self.pos) + if not home_scope: + return None + else: + home_scope = env + + self.entry = home_scope.declare_c_class( name=self.class_name, pos=self.pos, defining=0, @@ -4711,29 +4711,29 @@ class CClassDefNode(ClassDefNode): api=self.api, buffer_defaults=self.buffer_defaults(env), shadow=self.shadow) - - def analyse_declarations(self, env): - #print "CClassDefNode.analyse_declarations:", self.class_name - #print "...visibility =", self.visibility - #print "...module_name =", self.module_name - - if env.in_cinclude and not self.objstruct_name: + + def analyse_declarations(self, env): + #print "CClassDefNode.analyse_declarations:", self.class_name + #print "...visibility =", self.visibility + #print "...module_name =", self.module_name + + if env.in_cinclude and not self.objstruct_name: error(self.pos, "Object struct name specification required for C class defined in 'extern from' block") - if self.decorators: + if self.decorators: error(self.pos, "Decorators not allowed on cdef classes (used on type '%s')" % self.class_name) - self.base_type = None - # Now that module imports are cached, we need to - # import the modules for extern classes. - if self.module_name: - self.module = None - for module in env.cimported_modules: - if module.name == self.module_name: - self.module = module - if self.module is None: - self.module = ModuleScope(self.module_name, None, env.context) - self.module.has_extern_class = 1 - env.add_imported_module(self.module) - + self.base_type = None + # Now that module imports are cached, we need to + # import the modules for extern classes. + if self.module_name: + self.module = None + for module in env.cimported_modules: + if module.name == self.module_name: + self.module = module + if self.module is None: + self.module = ModuleScope(self.module_name, None, env.context) + self.module.has_extern_class = 1 + env.add_imported_module(self.module) + if self.bases.args: base = self.bases.args[0] base_type = base.analyse_as_type(env) @@ -4758,33 +4758,33 @@ class CClassDefNode(ClassDefNode): base_type.name in ('tuple', 'str', 'bytes'): error(base.pos, "inheritance from PyVarObject types like '%s' is not currently supported" % base_type.name) - else: + else: self.base_type = base_type if env.directives.get('freelist', 0) > 0 and base_type != PyrexTypes.py_object_type: warning(self.pos, "freelists cannot be used on subtypes, only the base class can manage them", 1) - - has_body = self.body is not None - if has_body and self.base_type and not self.base_type.scope: - # To properly initialize inherited attributes, the base type must - # be analysed before this type. - self.base_type.defered_declarations.append(lambda : self.analyse_declarations(env)) - return - - if self.module_name and self.visibility != 'extern': - module_path = self.module_name.split(".") - home_scope = env.find_imported_module(module_path, self.pos) - if not home_scope: - return - else: - home_scope = env - - if self.visibility == 'extern': - if (self.module_name == '__builtin__' and + + has_body = self.body is not None + if has_body and self.base_type and not self.base_type.scope: + # To properly initialize inherited attributes, the base type must + # be analysed before this type. + self.base_type.defered_declarations.append(lambda : self.analyse_declarations(env)) + return + + if self.module_name and self.visibility != 'extern': + module_path = self.module_name.split(".") + home_scope = env.find_imported_module(module_path, self.pos) + if not home_scope: + return + else: + home_scope = env + + if self.visibility == 'extern': + if (self.module_name == '__builtin__' and self.class_name in Builtin.builtin_types and env.qualified_name[:8] != 'cpython.'): # allow overloaded names for cimporting from cpython - warning(self.pos, "%s already a builtin Cython type" % self.class_name, 1) - - self.entry = home_scope.declare_c_class( + warning(self.pos, "%s already a builtin Cython type" % self.class_name, 1) + + self.entry = home_scope.declare_c_class( name=self.class_name, pos=self.pos, defining=has_body and self.in_pxd, @@ -4799,28 +4799,28 @@ class CClassDefNode(ClassDefNode): api=self.api, buffer_defaults=self.buffer_defaults(env), shadow=self.shadow) - - if self.shadow: - home_scope.lookup(self.class_name).as_variable = self.entry - if home_scope is not env and self.visibility == 'extern': - env.add_imported_entry(self.class_name, self.entry, self.pos) - self.scope = scope = self.entry.type.scope - if scope is not None: - scope.directives = env.directives - - if self.doc and Options.docstrings: - scope.doc = embed_position(self.pos, self.doc) - - if has_body: - self.body.analyse_declarations(scope) + + if self.shadow: + home_scope.lookup(self.class_name).as_variable = self.entry + if home_scope is not env and self.visibility == 'extern': + env.add_imported_entry(self.class_name, self.entry, self.pos) + self.scope = scope = self.entry.type.scope + if scope is not None: + scope.directives = env.directives + + if self.doc and Options.docstrings: + scope.doc = embed_position(self.pos, self.doc) + + if has_body: + self.body.analyse_declarations(scope) dict_entry = self.scope.lookup_here("__dict__") if dict_entry and dict_entry.is_variable and (not scope.defined and not scope.implemented): dict_entry.getter_cname = self.scope.mangle_internal("__dict__getter") self.scope.declare_property("__dict__", dict_entry.doc, dict_entry.pos) - if self.in_pxd: - scope.defined = 1 - else: - scope.implemented = 1 + if self.in_pxd: + scope.defined = 1 + else: + scope.implemented = 1 if len(self.bases.args) > 1: if not has_body or self.in_pxd: @@ -4844,30 +4844,30 @@ class CClassDefNode(ClassDefNode): self.entry.type.early_init = 1 self.type_init_args = None - env.allocate_vtable_names(self.entry) - - for thunk in self.entry.type.defered_declarations: - thunk() - - def analyse_expressions(self, env): - if self.body: - scope = self.entry.type.scope - self.body = self.body.analyse_expressions(scope) + env.allocate_vtable_names(self.entry) + + for thunk in self.entry.type.defered_declarations: + thunk() + + def analyse_expressions(self, env): + if self.body: + scope = self.entry.type.scope + self.body = self.body.analyse_expressions(scope) if self.type_init_args: self.type_init_args.analyse_expressions(env) - return self - - def generate_function_definitions(self, env, code): - if self.body: - self.generate_lambda_definitions(self.scope, code) - self.body.generate_function_definitions(self.scope, code) - - def generate_execution_code(self, code): - # This is needed to generate evaluation code for - # default values of method arguments. + return self + + def generate_function_definitions(self, env, code): + if self.body: + self.generate_lambda_definitions(self.scope, code) + self.body.generate_function_definitions(self.scope, code) + + def generate_execution_code(self, code): + # This is needed to generate evaluation code for + # default values of method arguments. code.mark_pos(self.pos) - if self.body: - self.body.generate_execution_code(code) + if self.body: + self.body.generate_execution_code(code) if not self.entry.type.early_init: if self.type_init_args: self.type_init_args.generate_evaluation_code(code) @@ -4893,7 +4893,7 @@ class CClassDefNode(ClassDefNode): code.put_decref_clear(trial_type, PyrexTypes.py_object_type) self.type_init_args.generate_disposal_code(code) self.type_init_args.free_temps(code) - + self.generate_type_ready_code(self.entry, code, True) # Also called from ModuleNode for early init types. @@ -5034,101 +5034,101 @@ class CClassDefNode(ClassDefNode): "%s = &%s;" % ( type.typeptr_cname, type.typeobj_cname)) - def annotate(self, code): + def annotate(self, code): if self.type_init_args: self.type_init_args.annotate(code) - if self.body: - self.body.annotate(code) - - -class PropertyNode(StatNode): - # Definition of a property in an extension type. - # - # name string - # doc EncodedString or None Doc string - # entry Symtab.Entry - # body StatListNode - - child_attrs = ["body"] - - def analyse_declarations(self, env): - self.entry = env.declare_property(self.name, self.doc, self.pos) - self.entry.scope.directives = env.directives - self.body.analyse_declarations(self.entry.scope) - - def analyse_expressions(self, env): - self.body = self.body.analyse_expressions(env) - return self - - def generate_function_definitions(self, env, code): - self.body.generate_function_definitions(env, code) - - def generate_execution_code(self, code): - pass - - def annotate(self, code): - self.body.annotate(code) - - -class GlobalNode(StatNode): - # Global variable declaration. - # - # names [string] - - child_attrs = [] - - def analyse_declarations(self, env): - for name in self.names: - env.declare_global(name, self.pos) - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class NonlocalNode(StatNode): - # Nonlocal variable declaration via the 'nonlocal' keyword. - # - # names [string] - - child_attrs = [] - - def analyse_declarations(self, env): - for name in self.names: - env.declare_nonlocal(name, self.pos) - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class ExprStatNode(StatNode): - # Expression used as a statement. - # - # expr ExprNode - - child_attrs = ["expr"] - - def analyse_declarations(self, env): - from . import ExprNodes + if self.body: + self.body.annotate(code) + + +class PropertyNode(StatNode): + # Definition of a property in an extension type. + # + # name string + # doc EncodedString or None Doc string + # entry Symtab.Entry + # body StatListNode + + child_attrs = ["body"] + + def analyse_declarations(self, env): + self.entry = env.declare_property(self.name, self.doc, self.pos) + self.entry.scope.directives = env.directives + self.body.analyse_declarations(self.entry.scope) + + def analyse_expressions(self, env): + self.body = self.body.analyse_expressions(env) + return self + + def generate_function_definitions(self, env, code): + self.body.generate_function_definitions(env, code) + + def generate_execution_code(self, code): + pass + + def annotate(self, code): + self.body.annotate(code) + + +class GlobalNode(StatNode): + # Global variable declaration. + # + # names [string] + + child_attrs = [] + + def analyse_declarations(self, env): + for name in self.names: + env.declare_global(name, self.pos) + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class NonlocalNode(StatNode): + # Nonlocal variable declaration via the 'nonlocal' keyword. + # + # names [string] + + child_attrs = [] + + def analyse_declarations(self, env): + for name in self.names: + env.declare_nonlocal(name, self.pos) + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class ExprStatNode(StatNode): + # Expression used as a statement. + # + # expr ExprNode + + child_attrs = ["expr"] + + def analyse_declarations(self, env): + from . import ExprNodes expr = self.expr if isinstance(expr, ExprNodes.GeneralCallNode): func = expr.function.as_cython_attribute() - if func == u'declare': + if func == u'declare': args, kwds = expr.explicit_args_kwds() - if len(args): + if len(args): error(expr.pos, "Variable names must be specified.") - for var, type_node in kwds.key_value_pairs: - type = type_node.analyse_as_type(env) - if type is None: - error(type_node.pos, "Unknown type") - else: + for var, type_node in kwds.key_value_pairs: + type = type_node.analyse_as_type(env) + if type is None: + error(type_node.pos, "Unknown type") + else: env.declare_var(var.value, type, var.pos, is_cdef=True) - self.__class__ = PassStatNode + self.__class__ = PassStatNode elif getattr(expr, 'annotation', None) is not None: if expr.is_name: # non-code variable annotation, e.g. "name: type" @@ -5137,94 +5137,94 @@ class ExprStatNode(StatNode): elif expr.is_attribute or expr.is_subscript: # unused expression with annotation, e.g. "a[0]: type" or "a.xyz : type" self.__class__ = PassStatNode - - def analyse_expressions(self, env): + + def analyse_expressions(self, env): self.expr.result_is_used = False # hint that .result() may safely be left empty - self.expr = self.expr.analyse_expressions(env) + self.expr = self.expr.analyse_expressions(env) # Repeat in case of node replacement. self.expr.result_is_used = False # hint that .result() may safely be left empty - return self - - def nogil_check(self, env): - if self.expr.type.is_pyobject and self.expr.is_temp: - self.gil_error() - - gil_message = "Discarding owned Python object" - - def generate_execution_code(self, code): + return self + + def nogil_check(self, env): + if self.expr.type.is_pyobject and self.expr.is_temp: + self.gil_error() + + gil_message = "Discarding owned Python object" + + def generate_execution_code(self, code): code.mark_pos(self.pos) self.expr.result_is_used = False # hint that .result() may safely be left empty - self.expr.generate_evaluation_code(code) - if not self.expr.is_temp and self.expr.result(): + self.expr.generate_evaluation_code(code) + if not self.expr.is_temp and self.expr.result(): result = self.expr.result() if not self.expr.type.is_void: result = "(void)(%s)" % result code.putln("%s;" % result) - self.expr.generate_disposal_code(code) - self.expr.free_temps(code) - - def generate_function_definitions(self, env, code): - self.expr.generate_function_definitions(env, code) - - def annotate(self, code): - self.expr.annotate(code) - - -class AssignmentNode(StatNode): - # Abstract base class for assignment nodes. - # - # The analyse_expressions and generate_execution_code - # phases of assignments are split into two sub-phases - # each, to enable all the right hand sides of a - # parallel assignment to be evaluated before assigning - # to any of the left hand sides. - - def analyse_expressions(self, env): - node = self.analyse_types(env) + self.expr.generate_disposal_code(code) + self.expr.free_temps(code) + + def generate_function_definitions(self, env, code): + self.expr.generate_function_definitions(env, code) + + def annotate(self, code): + self.expr.annotate(code) + + +class AssignmentNode(StatNode): + # Abstract base class for assignment nodes. + # + # The analyse_expressions and generate_execution_code + # phases of assignments are split into two sub-phases + # each, to enable all the right hand sides of a + # parallel assignment to be evaluated before assigning + # to any of the left hand sides. + + def analyse_expressions(self, env): + node = self.analyse_types(env) if isinstance(node, AssignmentNode) and not isinstance(node, ParallelAssignmentNode): - if node.rhs.type.is_ptr and node.rhs.is_ephemeral(): - error(self.pos, "Storing unsafe C derivative of temporary Python reference") - return node - -# def analyse_expressions(self, env): -# self.analyse_expressions_1(env) -# self.analyse_expressions_2(env) - - def generate_execution_code(self, code): + if node.rhs.type.is_ptr and node.rhs.is_ephemeral(): + error(self.pos, "Storing unsafe C derivative of temporary Python reference") + return node + +# def analyse_expressions(self, env): +# self.analyse_expressions_1(env) +# self.analyse_expressions_2(env) + + def generate_execution_code(self, code): code.mark_pos(self.pos) - self.generate_rhs_evaluation_code(code) - self.generate_assignment_code(code) - - -class SingleAssignmentNode(AssignmentNode): - # The simplest case: - # - # a = b - # + self.generate_rhs_evaluation_code(code) + self.generate_assignment_code(code) + + +class SingleAssignmentNode(AssignmentNode): + # The simplest case: + # + # a = b + # # lhs ExprNode Left hand side # rhs ExprNode Right hand side # first bool Is this guaranteed the first assignment to lhs? # is_overloaded_assignment bool Is this assignment done via an overloaded operator= # exception_check # exception_value - - child_attrs = ["lhs", "rhs"] - first = False + + child_attrs = ["lhs", "rhs"] + first = False is_overloaded_assignment = False - declaration_only = False - - def analyse_declarations(self, env): - from . import ExprNodes - - # handle declarations of the form x = cython.foo() - if isinstance(self.rhs, ExprNodes.CallNode): - func_name = self.rhs.function.as_cython_attribute() - if func_name: - args, kwds = self.rhs.explicit_args_kwds() - if func_name in ['declare', 'typedef']: + declaration_only = False + + def analyse_declarations(self, env): + from . import ExprNodes + + # handle declarations of the form x = cython.foo() + if isinstance(self.rhs, ExprNodes.CallNode): + func_name = self.rhs.function.as_cython_attribute() + if func_name: + args, kwds = self.rhs.explicit_args_kwds() + if func_name in ['declare', 'typedef']: if len(args) > 2: error(args[2].pos, "Invalid positional argument.") - return + return if kwds is not None: kwdict = kwds.compile_time_value(None) if func_name == 'typedef' or 'visibility' not in kwdict: @@ -5233,85 +5233,85 @@ class SingleAssignmentNode(AssignmentNode): visibility = kwdict['visibility'] else: visibility = 'private' - type = args[0].analyse_as_type(env) - if type is None: - error(args[0].pos, "Unknown type") - return - lhs = self.lhs - if func_name == 'declare': - if isinstance(lhs, ExprNodes.NameNode): - vars = [(lhs.name, lhs.pos)] - elif isinstance(lhs, ExprNodes.TupleNode): - vars = [(var.name, var.pos) for var in lhs.args] - else: - error(lhs.pos, "Invalid declaration") - return - for var, pos in vars: + type = args[0].analyse_as_type(env) + if type is None: + error(args[0].pos, "Unknown type") + return + lhs = self.lhs + if func_name == 'declare': + if isinstance(lhs, ExprNodes.NameNode): + vars = [(lhs.name, lhs.pos)] + elif isinstance(lhs, ExprNodes.TupleNode): + vars = [(var.name, var.pos) for var in lhs.args] + else: + error(lhs.pos, "Invalid declaration") + return + for var, pos in vars: env.declare_var(var, type, pos, is_cdef=True, visibility=visibility) - if len(args) == 2: - # we have a value - self.rhs = args[1] - else: - self.declaration_only = True - else: - self.declaration_only = True - if not isinstance(lhs, ExprNodes.NameNode): - error(lhs.pos, "Invalid declaration.") - env.declare_typedef(lhs.name, type, self.pos, visibility='private') - - elif func_name in ['struct', 'union']: - self.declaration_only = True - if len(args) > 0 or kwds is None: - error(self.rhs.pos, "Struct or union members must be given by name.") - return - members = [] - for member, type_node in kwds.key_value_pairs: - type = type_node.analyse_as_type(env) - if type is None: - error(type_node.pos, "Unknown type") - else: - members.append((member.value, type, member.pos)) - if len(members) < len(kwds.key_value_pairs): - return - if not isinstance(self.lhs, ExprNodes.NameNode): - error(self.lhs.pos, "Invalid declaration.") - name = self.lhs.name - scope = StructOrUnionScope(name) - env.declare_struct_or_union(name, func_name, scope, False, self.rhs.pos) - for member, type, pos in members: - scope.declare_var(member, type, pos) - - elif func_name == 'fused_type': - # dtype = cython.fused_type(...) - self.declaration_only = True - if kwds: - error(self.rhs.function.pos, - "fused_type does not take keyword arguments") - - fusednode = FusedTypeNode(self.rhs.pos, + if len(args) == 2: + # we have a value + self.rhs = args[1] + else: + self.declaration_only = True + else: + self.declaration_only = True + if not isinstance(lhs, ExprNodes.NameNode): + error(lhs.pos, "Invalid declaration.") + env.declare_typedef(lhs.name, type, self.pos, visibility='private') + + elif func_name in ['struct', 'union']: + self.declaration_only = True + if len(args) > 0 or kwds is None: + error(self.rhs.pos, "Struct or union members must be given by name.") + return + members = [] + for member, type_node in kwds.key_value_pairs: + type = type_node.analyse_as_type(env) + if type is None: + error(type_node.pos, "Unknown type") + else: + members.append((member.value, type, member.pos)) + if len(members) < len(kwds.key_value_pairs): + return + if not isinstance(self.lhs, ExprNodes.NameNode): + error(self.lhs.pos, "Invalid declaration.") + name = self.lhs.name + scope = StructOrUnionScope(name) + env.declare_struct_or_union(name, func_name, scope, False, self.rhs.pos) + for member, type, pos in members: + scope.declare_var(member, type, pos) + + elif func_name == 'fused_type': + # dtype = cython.fused_type(...) + self.declaration_only = True + if kwds: + error(self.rhs.function.pos, + "fused_type does not take keyword arguments") + + fusednode = FusedTypeNode(self.rhs.pos, name=self.lhs.name, types=args) - fusednode.analyse_declarations(env) - - if self.declaration_only: - return - else: - self.lhs.analyse_target_declaration(env) - + fusednode.analyse_declarations(env) + + if self.declaration_only: + return + else: + self.lhs.analyse_target_declaration(env) + def analyse_types(self, env, use_temp=0): - from . import ExprNodes - - self.rhs = self.rhs.analyse_types(env) + from . import ExprNodes + + self.rhs = self.rhs.analyse_types(env) unrolled_assignment = self.unroll_rhs(env) if unrolled_assignment: return unrolled_assignment - self.lhs = self.lhs.analyse_target_types(env) - self.lhs.gil_assignment_check(env) + self.lhs = self.lhs.analyse_target_types(env) + self.lhs.gil_assignment_check(env) unrolled_assignment = self.unroll_lhs(env) if unrolled_assignment: return unrolled_assignment - + if isinstance(self.lhs, ExprNodes.MemoryViewIndexNode): self.lhs.analyse_broadcast_operation(self.rhs) self.lhs = self.lhs.analyse_as_memview_scalar_assignment(self.rhs) @@ -5320,7 +5320,7 @@ class SingleAssignmentNode(AssignmentNode): # cannot assign to C array, only to its full slice self.lhs = ExprNodes.SliceIndexNode(self.lhs.pos, base=self.lhs, start=None, stop=None) self.lhs = self.lhs.analyse_target_types(env) - + if self.lhs.type.is_cpp_class: op = env.lookup_operator_for_types(self.pos, '=', [self.lhs.type, self.rhs.type]) if op: @@ -5335,16 +5335,16 @@ class SingleAssignmentNode(AssignmentNode): else: rhs = self.rhs.coerce_to(self.lhs.type, env) - if use_temp or rhs.is_attribute or ( - not rhs.is_name and not rhs.is_literal and - rhs.type.is_pyobject): - # things like (cdef) attribute access are not safe (traverses pointers) - rhs = rhs.coerce_to_temp(env) - elif rhs.type.is_pyobject: - rhs = rhs.coerce_to_simple(env) - self.rhs = rhs - return self - + if use_temp or rhs.is_attribute or ( + not rhs.is_name and not rhs.is_literal and + rhs.type.is_pyobject): + # things like (cdef) attribute access are not safe (traverses pointers) + rhs = rhs.coerce_to_temp(env) + elif rhs.type.is_pyobject: + rhs = rhs.coerce_to_simple(env) + self.rhs = rhs + return self + def unroll(self, node, target_size, env): from . import ExprNodes, UtilNodes @@ -5478,9 +5478,9 @@ class SingleAssignmentNode(AssignmentNode): check_node, refs, lhs = unrolled return self.unroll_assignments(refs, check_node, lhs, self.rhs.args, env) - def generate_rhs_evaluation_code(self, code): - self.rhs.generate_evaluation_code(code) - + def generate_rhs_evaluation_code(self, code): + self.rhs.generate_evaluation_code(code) + def generate_assignment_code(self, code, overloaded_assignment=False): if self.is_overloaded_assignment: self.lhs.generate_assignment_code( @@ -5491,51 +5491,51 @@ class SingleAssignmentNode(AssignmentNode): exception_value=self.exception_value) else: self.lhs.generate_assignment_code(self.rhs, code) - - def generate_function_definitions(self, env, code): - self.rhs.generate_function_definitions(env, code) - - def annotate(self, code): - self.lhs.annotate(code) - self.rhs.annotate(code) - - -class CascadedAssignmentNode(AssignmentNode): - # An assignment with multiple left hand sides: - # - # a = b = c - # - # lhs_list [ExprNode] Left hand sides - # rhs ExprNode Right hand sides - # - # Used internally: - # + + def generate_function_definitions(self, env, code): + self.rhs.generate_function_definitions(env, code) + + def annotate(self, code): + self.lhs.annotate(code) + self.rhs.annotate(code) + + +class CascadedAssignmentNode(AssignmentNode): + # An assignment with multiple left hand sides: + # + # a = b = c + # + # lhs_list [ExprNode] Left hand sides + # rhs ExprNode Right hand sides + # + # Used internally: + # # coerced_values [ExprNode] RHS coerced to all distinct LHS types # cloned_values [ExprNode] cloned RHS value for each LHS # assignment_overloads [Bool] If each assignment uses a C++ operator= - + child_attrs = ["lhs_list", "rhs", "coerced_values", "cloned_values"] cloned_values = None - coerced_values = None + coerced_values = None assignment_overloads = None - - def analyse_declarations(self, env): - for lhs in self.lhs_list: - lhs.analyse_target_declaration(env) - - def analyse_types(self, env, use_temp=0): - from .ExprNodes import CloneNode, ProxyNode - + + def analyse_declarations(self, env): + for lhs in self.lhs_list: + lhs.analyse_target_declaration(env) + + def analyse_types(self, env, use_temp=0): + from .ExprNodes import CloneNode, ProxyNode + # collect distinct types used on the LHS - lhs_types = set() + lhs_types = set() for i, lhs in enumerate(self.lhs_list): lhs = self.lhs_list[i] = lhs.analyse_target_types(env) - lhs.gil_assignment_check(env) - lhs_types.add(lhs.type) - - rhs = self.rhs.analyse_types(env) + lhs.gil_assignment_check(env) + lhs_types.add(lhs.type) + + rhs = self.rhs.analyse_types(env) # common special case: only one type needed on the LHS => coerce only once - if len(lhs_types) == 1: + if len(lhs_types) == 1: # Avoid coercion for overloaded assignment operators. if next(iter(lhs_types)).is_cpp_class: op = env.lookup_operator('=', [lhs, self.rhs]) @@ -5543,462 +5543,462 @@ class CascadedAssignmentNode(AssignmentNode): rhs = rhs.coerce_to(lhs_types.pop(), env) else: rhs = rhs.coerce_to(lhs_types.pop(), env) - - if not rhs.is_name and not rhs.is_literal and ( - use_temp or rhs.is_attribute or rhs.type.is_pyobject): - rhs = rhs.coerce_to_temp(env) - else: - rhs = rhs.coerce_to_simple(env) - self.rhs = ProxyNode(rhs) if rhs.is_temp else rhs - + + if not rhs.is_name and not rhs.is_literal and ( + use_temp or rhs.is_attribute or rhs.type.is_pyobject): + rhs = rhs.coerce_to_temp(env) + else: + rhs = rhs.coerce_to_simple(env) + self.rhs = ProxyNode(rhs) if rhs.is_temp else rhs + # clone RHS and coerce it to all distinct LHS types - self.coerced_values = [] - coerced_values = {} + self.coerced_values = [] + coerced_values = {} self.assignment_overloads = [] - for lhs in self.lhs_list: + for lhs in self.lhs_list: overloaded = lhs.type.is_cpp_class and env.lookup_operator('=', [lhs, self.rhs]) self.assignment_overloads.append(overloaded) - if lhs.type not in coerced_values and lhs.type != rhs.type: + if lhs.type not in coerced_values and lhs.type != rhs.type: rhs = CloneNode(self.rhs) if not overloaded: rhs = rhs.coerce_to(lhs.type, env) - self.coerced_values.append(rhs) - coerced_values[lhs.type] = rhs - + self.coerced_values.append(rhs) + coerced_values[lhs.type] = rhs + # clone coerced values for all LHS assignments self.cloned_values = [] - for lhs in self.lhs_list: - rhs = coerced_values.get(lhs.type, self.rhs) + for lhs in self.lhs_list: + rhs = coerced_values.get(lhs.type, self.rhs) self.cloned_values.append(CloneNode(rhs)) - return self - - def generate_rhs_evaluation_code(self, code): - self.rhs.generate_evaluation_code(code) - + return self + + def generate_rhs_evaluation_code(self, code): + self.rhs.generate_evaluation_code(code) + def generate_assignment_code(self, code, overloaded_assignment=False): # prepare all coercions - for rhs in self.coerced_values: - rhs.generate_evaluation_code(code) + for rhs in self.coerced_values: + rhs.generate_evaluation_code(code) # assign clones to LHS for lhs, rhs, overload in zip(self.lhs_list, self.cloned_values, self.assignment_overloads): - rhs.generate_evaluation_code(code) + rhs.generate_evaluation_code(code) lhs.generate_assignment_code(rhs, code, overloaded_assignment=overload) # dispose of coerced values and original RHS for rhs_value in self.coerced_values: rhs_value.generate_disposal_code(code) rhs_value.free_temps(code) - self.rhs.generate_disposal_code(code) - self.rhs.free_temps(code) - - def generate_function_definitions(self, env, code): - self.rhs.generate_function_definitions(env, code) - - def annotate(self, code): - for rhs in self.coerced_values: - rhs.annotate(code) + self.rhs.generate_disposal_code(code) + self.rhs.free_temps(code) + + def generate_function_definitions(self, env, code): + self.rhs.generate_function_definitions(env, code) + + def annotate(self, code): + for rhs in self.coerced_values: + rhs.annotate(code) for lhs, rhs in zip(self.lhs_list, self.cloned_values): - lhs.annotate(code) - rhs.annotate(code) - self.rhs.annotate(code) - - -class ParallelAssignmentNode(AssignmentNode): - # A combined packing/unpacking assignment: - # - # a, b, c = d, e, f - # - # This has been rearranged by the parser into - # - # a = d ; b = e ; c = f - # - # but we must evaluate all the right hand sides - # before assigning to any of the left hand sides. - # - # stats [AssignmentNode] The constituent assignments - - child_attrs = ["stats"] - - def analyse_declarations(self, env): - for stat in self.stats: - stat.analyse_declarations(env) - - def analyse_expressions(self, env): + lhs.annotate(code) + rhs.annotate(code) + self.rhs.annotate(code) + + +class ParallelAssignmentNode(AssignmentNode): + # A combined packing/unpacking assignment: + # + # a, b, c = d, e, f + # + # This has been rearranged by the parser into + # + # a = d ; b = e ; c = f + # + # but we must evaluate all the right hand sides + # before assigning to any of the left hand sides. + # + # stats [AssignmentNode] The constituent assignments + + child_attrs = ["stats"] + + def analyse_declarations(self, env): + for stat in self.stats: + stat.analyse_declarations(env) + + def analyse_expressions(self, env): self.stats = [stat.analyse_types(env, use_temp=1) for stat in self.stats] - return self - -# def analyse_expressions(self, env): -# for stat in self.stats: + return self + +# def analyse_expressions(self, env): +# for stat in self.stats: # stat.analyse_expressions_1(env, use_temp=1) -# for stat in self.stats: -# stat.analyse_expressions_2(env) - - def generate_execution_code(self, code): +# for stat in self.stats: +# stat.analyse_expressions_2(env) + + def generate_execution_code(self, code): code.mark_pos(self.pos) - for stat in self.stats: - stat.generate_rhs_evaluation_code(code) - for stat in self.stats: - stat.generate_assignment_code(code) - - def generate_function_definitions(self, env, code): - for stat in self.stats: - stat.generate_function_definitions(env, code) - - def annotate(self, code): - for stat in self.stats: - stat.annotate(code) - - -class InPlaceAssignmentNode(AssignmentNode): - # An in place arithmetic operand: - # - # a += b - # a -= b - # ... - # - # lhs ExprNode Left hand side - # rhs ExprNode Right hand side - # operator char one of "+-*/%^&|" - # - # This code is a bit tricky because in order to obey Python - # semantics the sub-expressions (e.g. indices) of the lhs must - # not be evaluated twice. So we must re-use the values calculated - # in evaluation phase for the assignment phase as well. - # Fortunately, the type of the lhs node is fairly constrained - # (it must be a NameNode, AttributeNode, or IndexNode). - - child_attrs = ["lhs", "rhs"] - - def analyse_declarations(self, env): - self.lhs.analyse_target_declaration(env) - - def analyse_types(self, env): - self.rhs = self.rhs.analyse_types(env) - self.lhs = self.lhs.analyse_target_types(env) - - # When assigning to a fully indexed buffer or memoryview, coerce the rhs + for stat in self.stats: + stat.generate_rhs_evaluation_code(code) + for stat in self.stats: + stat.generate_assignment_code(code) + + def generate_function_definitions(self, env, code): + for stat in self.stats: + stat.generate_function_definitions(env, code) + + def annotate(self, code): + for stat in self.stats: + stat.annotate(code) + + +class InPlaceAssignmentNode(AssignmentNode): + # An in place arithmetic operand: + # + # a += b + # a -= b + # ... + # + # lhs ExprNode Left hand side + # rhs ExprNode Right hand side + # operator char one of "+-*/%^&|" + # + # This code is a bit tricky because in order to obey Python + # semantics the sub-expressions (e.g. indices) of the lhs must + # not be evaluated twice. So we must re-use the values calculated + # in evaluation phase for the assignment phase as well. + # Fortunately, the type of the lhs node is fairly constrained + # (it must be a NameNode, AttributeNode, or IndexNode). + + child_attrs = ["lhs", "rhs"] + + def analyse_declarations(self, env): + self.lhs.analyse_target_declaration(env) + + def analyse_types(self, env): + self.rhs = self.rhs.analyse_types(env) + self.lhs = self.lhs.analyse_target_types(env) + + # When assigning to a fully indexed buffer or memoryview, coerce the rhs if self.lhs.is_memview_index or self.lhs.is_buffer_access: - self.rhs = self.rhs.coerce_to(self.lhs.type, env) - elif self.lhs.type.is_string and self.operator in '+-': - # use pointer arithmetic for char* LHS instead of string concat - self.rhs = self.rhs.coerce_to(PyrexTypes.c_py_ssize_t_type, env) - return self - - def generate_execution_code(self, code): + self.rhs = self.rhs.coerce_to(self.lhs.type, env) + elif self.lhs.type.is_string and self.operator in '+-': + # use pointer arithmetic for char* LHS instead of string concat + self.rhs = self.rhs.coerce_to(PyrexTypes.c_py_ssize_t_type, env) + return self + + def generate_execution_code(self, code): code.mark_pos(self.pos) lhs, rhs = self.lhs, self.rhs rhs.generate_evaluation_code(code) lhs.generate_subexpr_evaluation_code(code) - c_op = self.operator - if c_op == "//": - c_op = "/" - elif c_op == "**": - error(self.pos, "No C inplace power operator") + c_op = self.operator + if c_op == "//": + c_op = "/" + elif c_op == "**": + error(self.pos, "No C inplace power operator") if lhs.is_buffer_access or lhs.is_memview_index: if lhs.type.is_pyobject: - error(self.pos, "In-place operators not allowed on object buffers in this release.") + error(self.pos, "In-place operators not allowed on object buffers in this release.") if c_op in ('/', '%') and lhs.type.is_int and not code.globalstate.directives['cdivision']: - error(self.pos, "In-place non-c divide operators not allowed on int buffers.") + error(self.pos, "In-place non-c divide operators not allowed on int buffers.") lhs.generate_buffer_setitem_code(rhs, code, c_op) elif lhs.is_memview_slice: error(self.pos, "Inplace operators not supported on memoryview slices") - else: - # C++ - # TODO: make sure overload is declared + else: + # C++ + # TODO: make sure overload is declared code.putln("%s %s= %s;" % (lhs.result(), c_op, rhs.result())) lhs.generate_subexpr_disposal_code(code) lhs.free_subexpr_temps(code) rhs.generate_disposal_code(code) rhs.free_temps(code) - - def annotate(self, code): - self.lhs.annotate(code) - self.rhs.annotate(code) - - def create_binop_node(self): - from . import ExprNodes - return ExprNodes.binop_node(self.pos, self.operator, self.lhs, self.rhs) - - -class PrintStatNode(StatNode): - # print statement - # - # arg_tuple TupleNode - # stream ExprNode or None (stdout) - # append_newline boolean - - child_attrs = ["arg_tuple", "stream"] - - def analyse_expressions(self, env): - if self.stream: - stream = self.stream.analyse_expressions(env) - self.stream = stream.coerce_to_pyobject(env) - arg_tuple = self.arg_tuple.analyse_expressions(env) - self.arg_tuple = arg_tuple.coerce_to_pyobject(env) - env.use_utility_code(printing_utility_code) - if len(self.arg_tuple.args) == 1 and self.append_newline: - env.use_utility_code(printing_one_utility_code) - return self - - nogil_check = Node.gil_error - gil_message = "Python print statement" - - def generate_execution_code(self, code): + + def annotate(self, code): + self.lhs.annotate(code) + self.rhs.annotate(code) + + def create_binop_node(self): + from . import ExprNodes + return ExprNodes.binop_node(self.pos, self.operator, self.lhs, self.rhs) + + +class PrintStatNode(StatNode): + # print statement + # + # arg_tuple TupleNode + # stream ExprNode or None (stdout) + # append_newline boolean + + child_attrs = ["arg_tuple", "stream"] + + def analyse_expressions(self, env): + if self.stream: + stream = self.stream.analyse_expressions(env) + self.stream = stream.coerce_to_pyobject(env) + arg_tuple = self.arg_tuple.analyse_expressions(env) + self.arg_tuple = arg_tuple.coerce_to_pyobject(env) + env.use_utility_code(printing_utility_code) + if len(self.arg_tuple.args) == 1 and self.append_newline: + env.use_utility_code(printing_one_utility_code) + return self + + nogil_check = Node.gil_error + gil_message = "Python print statement" + + def generate_execution_code(self, code): code.mark_pos(self.pos) - if self.stream: - self.stream.generate_evaluation_code(code) - stream_result = self.stream.py_result() - else: - stream_result = '0' - if len(self.arg_tuple.args) == 1 and self.append_newline: - arg = self.arg_tuple.args[0] - arg.generate_evaluation_code(code) - - code.putln( - "if (__Pyx_PrintOne(%s, %s) < 0) %s" % ( - stream_result, - arg.py_result(), - code.error_goto(self.pos))) - arg.generate_disposal_code(code) - arg.free_temps(code) - else: - self.arg_tuple.generate_evaluation_code(code) - code.putln( - "if (__Pyx_Print(%s, %s, %d) < 0) %s" % ( - stream_result, - self.arg_tuple.py_result(), - self.append_newline, - code.error_goto(self.pos))) - self.arg_tuple.generate_disposal_code(code) - self.arg_tuple.free_temps(code) - - if self.stream: - self.stream.generate_disposal_code(code) - self.stream.free_temps(code) - - def generate_function_definitions(self, env, code): - if self.stream: - self.stream.generate_function_definitions(env, code) - self.arg_tuple.generate_function_definitions(env, code) - - def annotate(self, code): - if self.stream: - self.stream.annotate(code) - self.arg_tuple.annotate(code) - - -class ExecStatNode(StatNode): - # exec statement - # - # args [ExprNode] - - child_attrs = ["args"] - - def analyse_expressions(self, env): - for i, arg in enumerate(self.args): - arg = arg.analyse_expressions(env) - arg = arg.coerce_to_pyobject(env) - self.args[i] = arg - env.use_utility_code(Builtin.pyexec_utility_code) - return self - - nogil_check = Node.gil_error - gil_message = "Python exec statement" - - def generate_execution_code(self, code): + if self.stream: + self.stream.generate_evaluation_code(code) + stream_result = self.stream.py_result() + else: + stream_result = '0' + if len(self.arg_tuple.args) == 1 and self.append_newline: + arg = self.arg_tuple.args[0] + arg.generate_evaluation_code(code) + + code.putln( + "if (__Pyx_PrintOne(%s, %s) < 0) %s" % ( + stream_result, + arg.py_result(), + code.error_goto(self.pos))) + arg.generate_disposal_code(code) + arg.free_temps(code) + else: + self.arg_tuple.generate_evaluation_code(code) + code.putln( + "if (__Pyx_Print(%s, %s, %d) < 0) %s" % ( + stream_result, + self.arg_tuple.py_result(), + self.append_newline, + code.error_goto(self.pos))) + self.arg_tuple.generate_disposal_code(code) + self.arg_tuple.free_temps(code) + + if self.stream: + self.stream.generate_disposal_code(code) + self.stream.free_temps(code) + + def generate_function_definitions(self, env, code): + if self.stream: + self.stream.generate_function_definitions(env, code) + self.arg_tuple.generate_function_definitions(env, code) + + def annotate(self, code): + if self.stream: + self.stream.annotate(code) + self.arg_tuple.annotate(code) + + +class ExecStatNode(StatNode): + # exec statement + # + # args [ExprNode] + + child_attrs = ["args"] + + def analyse_expressions(self, env): + for i, arg in enumerate(self.args): + arg = arg.analyse_expressions(env) + arg = arg.coerce_to_pyobject(env) + self.args[i] = arg + env.use_utility_code(Builtin.pyexec_utility_code) + return self + + nogil_check = Node.gil_error + gil_message = "Python exec statement" + + def generate_execution_code(self, code): code.mark_pos(self.pos) - args = [] - for arg in self.args: - arg.generate_evaluation_code(code) + args = [] + for arg in self.args: + arg.generate_evaluation_code(code) args.append(arg.py_result()) - args = tuple(args + ['0', '0'][:3-len(args)]) - temp_result = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True) + args = tuple(args + ['0', '0'][:3-len(args)]) + temp_result = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True) code.putln("%s = __Pyx_PyExec3(%s, %s, %s);" % ((temp_result,) + args)) - for arg in self.args: - arg.generate_disposal_code(code) - arg.free_temps(code) - code.putln( - code.error_goto_if_null(temp_result, self.pos)) - code.put_gotref(temp_result) - code.put_decref_clear(temp_result, py_object_type) - code.funcstate.release_temp(temp_result) - - def annotate(self, code): - for arg in self.args: - arg.annotate(code) - - -class DelStatNode(StatNode): - # del statement - # - # args [ExprNode] - - child_attrs = ["args"] - ignore_nonexisting = False - - def analyse_declarations(self, env): - for arg in self.args: - arg.analyse_target_declaration(env) - - def analyse_expressions(self, env): - for i, arg in enumerate(self.args): - arg = self.args[i] = arg.analyse_target_expression(env, None) + for arg in self.args: + arg.generate_disposal_code(code) + arg.free_temps(code) + code.putln( + code.error_goto_if_null(temp_result, self.pos)) + code.put_gotref(temp_result) + code.put_decref_clear(temp_result, py_object_type) + code.funcstate.release_temp(temp_result) + + def annotate(self, code): + for arg in self.args: + arg.annotate(code) + + +class DelStatNode(StatNode): + # del statement + # + # args [ExprNode] + + child_attrs = ["args"] + ignore_nonexisting = False + + def analyse_declarations(self, env): + for arg in self.args: + arg.analyse_target_declaration(env) + + def analyse_expressions(self, env): + for i, arg in enumerate(self.args): + arg = self.args[i] = arg.analyse_target_expression(env, None) if arg.type.is_pyobject or (arg.is_name and arg.type.is_memoryviewslice): - if arg.is_name and arg.entry.is_cglobal: - error(arg.pos, "Deletion of global C variable") - elif arg.type.is_ptr and arg.type.base_type.is_cpp_class: - self.cpp_check(env) - elif arg.type.is_cpp_class: - error(arg.pos, "Deletion of non-heap C++ object") - elif arg.is_subscript and arg.base.type is Builtin.bytearray_type: - pass # del ba[i] - else: - error(arg.pos, "Deletion of non-Python, non-C++ object") - #arg.release_target_temp(env) - return self - - def nogil_check(self, env): - for arg in self.args: - if arg.type.is_pyobject: - self.gil_error() - - gil_message = "Deleting Python object" - - def generate_execution_code(self, code): + if arg.is_name and arg.entry.is_cglobal: + error(arg.pos, "Deletion of global C variable") + elif arg.type.is_ptr and arg.type.base_type.is_cpp_class: + self.cpp_check(env) + elif arg.type.is_cpp_class: + error(arg.pos, "Deletion of non-heap C++ object") + elif arg.is_subscript and arg.base.type is Builtin.bytearray_type: + pass # del ba[i] + else: + error(arg.pos, "Deletion of non-Python, non-C++ object") + #arg.release_target_temp(env) + return self + + def nogil_check(self, env): + for arg in self.args: + if arg.type.is_pyobject: + self.gil_error() + + gil_message = "Deleting Python object" + + def generate_execution_code(self, code): code.mark_pos(self.pos) - for arg in self.args: - if (arg.type.is_pyobject or - arg.type.is_memoryviewslice or - arg.is_subscript and arg.base.type is Builtin.bytearray_type): - arg.generate_deletion_code( - code, ignore_nonexisting=self.ignore_nonexisting) - elif arg.type.is_ptr and arg.type.base_type.is_cpp_class: + for arg in self.args: + if (arg.type.is_pyobject or + arg.type.is_memoryviewslice or + arg.is_subscript and arg.base.type is Builtin.bytearray_type): + arg.generate_deletion_code( + code, ignore_nonexisting=self.ignore_nonexisting) + elif arg.type.is_ptr and arg.type.base_type.is_cpp_class: arg.generate_evaluation_code(code) - code.putln("delete %s;" % arg.result()) + code.putln("delete %s;" % arg.result()) arg.generate_disposal_code(code) arg.free_temps(code) - # else error reported earlier - - def annotate(self, code): - for arg in self.args: - arg.annotate(code) - - -class PassStatNode(StatNode): - # pass statement - - child_attrs = [] - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class IndirectionNode(StatListNode): - """ - This adds an indirection so that the node can be shared and a subtree can - be removed at any time by clearing self.stats. - """ - - def __init__(self, stats): - super(IndirectionNode, self).__init__(stats[0].pos, stats=stats) - - -class BreakStatNode(StatNode): - - child_attrs = [] - is_terminator = True - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): + # else error reported earlier + + def annotate(self, code): + for arg in self.args: + arg.annotate(code) + + +class PassStatNode(StatNode): + # pass statement + + child_attrs = [] + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class IndirectionNode(StatListNode): + """ + This adds an indirection so that the node can be shared and a subtree can + be removed at any time by clearing self.stats. + """ + + def __init__(self, stats): + super(IndirectionNode, self).__init__(stats[0].pos, stats=stats) + + +class BreakStatNode(StatNode): + + child_attrs = [] + is_terminator = True + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): code.mark_pos(self.pos) - if not code.break_label: - error(self.pos, "break statement not inside loop") - else: - code.put_goto(code.break_label) - - -class ContinueStatNode(StatNode): - - child_attrs = [] - is_terminator = True - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): + if not code.break_label: + error(self.pos, "break statement not inside loop") + else: + code.put_goto(code.break_label) + + +class ContinueStatNode(StatNode): + + child_attrs = [] + is_terminator = True + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): if not code.continue_label: error(self.pos, "continue statement not inside loop") return code.mark_pos(self.pos) code.put_goto(code.continue_label) - - -class ReturnStatNode(StatNode): - # return statement - # - # value ExprNode or None - # return_type PyrexType - # in_generator return inside of generator => raise StopIteration + + +class ReturnStatNode(StatNode): + # return statement + # + # value ExprNode or None + # return_type PyrexType + # in_generator return inside of generator => raise StopIteration # in_async_gen return inside of async generator - - child_attrs = ["value"] - is_terminator = True - in_generator = False + + child_attrs = ["value"] + is_terminator = True + in_generator = False in_async_gen = False - - # Whether we are in a parallel section - in_parallel = False - - def analyse_expressions(self, env): - return_type = env.return_type - self.return_type = return_type - if not return_type: - error(self.pos, "Return not inside a function body") - return self - if self.value: + + # Whether we are in a parallel section + in_parallel = False + + def analyse_expressions(self, env): + return_type = env.return_type + self.return_type = return_type + if not return_type: + error(self.pos, "Return not inside a function body") + return self + if self.value: if self.in_async_gen: error(self.pos, "Return with value in async generator") - self.value = self.value.analyse_types(env) - if return_type.is_void or return_type.is_returncode: + self.value = self.value.analyse_types(env) + if return_type.is_void or return_type.is_returncode: error(self.value.pos, "Return with value in void function") - else: - self.value = self.value.coerce_to(env.return_type, env) - else: - if (not return_type.is_void + else: + self.value = self.value.coerce_to(env.return_type, env) + else: + if (not return_type.is_void and not return_type.is_pyobject and not return_type.is_returncode): error(self.pos, "Return value required") - return self - - def nogil_check(self, env): - if self.return_type.is_pyobject: - self.gil_error() - - gil_message = "Returning Python object" - - def generate_execution_code(self, code): - code.mark_pos(self.pos) - if not self.return_type: - # error reported earlier - return + return self + + def nogil_check(self, env): + if self.return_type.is_pyobject: + self.gil_error() + + gil_message = "Returning Python object" + + def generate_execution_code(self, code): + code.mark_pos(self.pos) + if not self.return_type: + # error reported earlier + return value = self.value - if self.return_type.is_pyobject: + if self.return_type.is_pyobject: code.put_xdecref(Naming.retval_cname, self.return_type) if value and value.is_none: # Use specialised default handling for "return None". value = None - + if value: value.generate_evaluation_code(code) - if self.return_type.is_memoryviewslice: - from . import MemoryView - MemoryView.put_acquire_memoryviewslice( + if self.return_type.is_memoryviewslice: + from . import MemoryView + MemoryView.put_acquire_memoryviewslice( lhs_cname=Naming.retval_cname, lhs_type=self.return_type, lhs_pos=value.pos, @@ -6006,23 +6006,23 @@ class ReturnStatNode(StatNode): code=code, have_gil=self.in_nogil_context) value.generate_post_assignment_code(code) - elif self.in_generator: - # return value == raise StopIteration(value), but uncatchable + elif self.in_generator: + # return value == raise StopIteration(value), but uncatchable code.globalstate.use_utility_code( UtilityCode.load_cached("ReturnWithStopIteration", "Coroutine.c")) code.putln("%s = NULL; __Pyx_ReturnWithStopIteration(%s);" % ( Naming.retval_cname, value.py_result())) value.generate_disposal_code(code) - else: + else: value.make_owned_reference(code) code.putln("%s = %s;" % ( Naming.retval_cname, value.result_as(self.return_type))) value.generate_post_assignment_code(code) value.free_temps(code) - else: - if self.return_type.is_pyobject: + else: + if self.return_type.is_pyobject: if self.in_generator: if self.in_async_gen: code.globalstate.use_utility_code( @@ -6031,246 +6031,246 @@ class ReturnStatNode(StatNode): code.putln("%s = NULL;" % Naming.retval_cname) else: code.put_init_to_py_none(Naming.retval_cname, self.return_type) - elif self.return_type.is_returncode: - self.put_return(code, self.return_type.default_value) - - for cname, type in code.funcstate.temps_holding_reference(): - code.put_decref_clear(cname, type) - - code.put_goto(code.return_label) - - def put_return(self, code, value): - if self.in_parallel: - code.putln_openmp("#pragma omp critical(__pyx_returning)") - code.putln("%s = %s;" % (Naming.retval_cname, value)) - - def generate_function_definitions(self, env, code): - if self.value is not None: - self.value.generate_function_definitions(env, code) - - def annotate(self, code): - if self.value: - self.value.annotate(code) - - -class RaiseStatNode(StatNode): - # raise statement - # - # exc_type ExprNode or None - # exc_value ExprNode or None - # exc_tb ExprNode or None - # cause ExprNode or None - - child_attrs = ["exc_type", "exc_value", "exc_tb", "cause"] - is_terminator = True - - def analyse_expressions(self, env): - if self.exc_type: - exc_type = self.exc_type.analyse_types(env) - self.exc_type = exc_type.coerce_to_pyobject(env) - if self.exc_value: - exc_value = self.exc_value.analyse_types(env) - self.exc_value = exc_value.coerce_to_pyobject(env) - if self.exc_tb: - exc_tb = self.exc_tb.analyse_types(env) - self.exc_tb = exc_tb.coerce_to_pyobject(env) - if self.cause: - cause = self.cause.analyse_types(env) - self.cause = cause.coerce_to_pyobject(env) - # special cases for builtin exceptions - self.builtin_exc_name = None - if self.exc_type and not self.exc_value and not self.exc_tb: - exc = self.exc_type - from . import ExprNodes - if (isinstance(exc, ExprNodes.SimpleCallNode) and + elif self.return_type.is_returncode: + self.put_return(code, self.return_type.default_value) + + for cname, type in code.funcstate.temps_holding_reference(): + code.put_decref_clear(cname, type) + + code.put_goto(code.return_label) + + def put_return(self, code, value): + if self.in_parallel: + code.putln_openmp("#pragma omp critical(__pyx_returning)") + code.putln("%s = %s;" % (Naming.retval_cname, value)) + + def generate_function_definitions(self, env, code): + if self.value is not None: + self.value.generate_function_definitions(env, code) + + def annotate(self, code): + if self.value: + self.value.annotate(code) + + +class RaiseStatNode(StatNode): + # raise statement + # + # exc_type ExprNode or None + # exc_value ExprNode or None + # exc_tb ExprNode or None + # cause ExprNode or None + + child_attrs = ["exc_type", "exc_value", "exc_tb", "cause"] + is_terminator = True + + def analyse_expressions(self, env): + if self.exc_type: + exc_type = self.exc_type.analyse_types(env) + self.exc_type = exc_type.coerce_to_pyobject(env) + if self.exc_value: + exc_value = self.exc_value.analyse_types(env) + self.exc_value = exc_value.coerce_to_pyobject(env) + if self.exc_tb: + exc_tb = self.exc_tb.analyse_types(env) + self.exc_tb = exc_tb.coerce_to_pyobject(env) + if self.cause: + cause = self.cause.analyse_types(env) + self.cause = cause.coerce_to_pyobject(env) + # special cases for builtin exceptions + self.builtin_exc_name = None + if self.exc_type and not self.exc_value and not self.exc_tb: + exc = self.exc_type + from . import ExprNodes + if (isinstance(exc, ExprNodes.SimpleCallNode) and not (exc.args or (exc.arg_tuple is not None and exc.arg_tuple.args))): exc = exc.function # extract the exception type - if exc.is_name and exc.entry.is_builtin: - self.builtin_exc_name = exc.name - if self.builtin_exc_name == 'MemoryError': - self.exc_type = None # has a separate implementation - return self - - nogil_check = Node.gil_error - gil_message = "Raising exception" - - def generate_execution_code(self, code): + if exc.is_name and exc.entry.is_builtin: + self.builtin_exc_name = exc.name + if self.builtin_exc_name == 'MemoryError': + self.exc_type = None # has a separate implementation + return self + + nogil_check = Node.gil_error + gil_message = "Raising exception" + + def generate_execution_code(self, code): code.mark_pos(self.pos) - if self.builtin_exc_name == 'MemoryError': - code.putln('PyErr_NoMemory(); %s' % code.error_goto(self.pos)) - return - - if self.exc_type: - self.exc_type.generate_evaluation_code(code) - type_code = self.exc_type.py_result() + if self.builtin_exc_name == 'MemoryError': + code.putln('PyErr_NoMemory(); %s' % code.error_goto(self.pos)) + return + + if self.exc_type: + self.exc_type.generate_evaluation_code(code) + type_code = self.exc_type.py_result() if self.exc_type.is_name: code.globalstate.use_entry_utility_code(self.exc_type.entry) - else: - type_code = "0" - if self.exc_value: - self.exc_value.generate_evaluation_code(code) - value_code = self.exc_value.py_result() - else: - value_code = "0" - if self.exc_tb: - self.exc_tb.generate_evaluation_code(code) - tb_code = self.exc_tb.py_result() - else: - tb_code = "0" - if self.cause: - self.cause.generate_evaluation_code(code) - cause_code = self.cause.py_result() - else: - cause_code = "0" - code.globalstate.use_utility_code(raise_utility_code) - code.putln( - "__Pyx_Raise(%s, %s, %s, %s);" % ( - type_code, - value_code, - tb_code, - cause_code)) - for obj in (self.exc_type, self.exc_value, self.exc_tb, self.cause): - if obj: - obj.generate_disposal_code(code) - obj.free_temps(code) - code.putln( - code.error_goto(self.pos)) - - def generate_function_definitions(self, env, code): - if self.exc_type is not None: - self.exc_type.generate_function_definitions(env, code) - if self.exc_value is not None: - self.exc_value.generate_function_definitions(env, code) - if self.exc_tb is not None: - self.exc_tb.generate_function_definitions(env, code) - if self.cause is not None: - self.cause.generate_function_definitions(env, code) - - def annotate(self, code): - if self.exc_type: - self.exc_type.annotate(code) - if self.exc_value: - self.exc_value.annotate(code) - if self.exc_tb: - self.exc_tb.annotate(code) - if self.cause: - self.cause.annotate(code) - - -class ReraiseStatNode(StatNode): - - child_attrs = [] - is_terminator = True - - def analyse_expressions(self, env): - return self - - nogil_check = Node.gil_error - gil_message = "Raising exception" - - def generate_execution_code(self, code): + else: + type_code = "0" + if self.exc_value: + self.exc_value.generate_evaluation_code(code) + value_code = self.exc_value.py_result() + else: + value_code = "0" + if self.exc_tb: + self.exc_tb.generate_evaluation_code(code) + tb_code = self.exc_tb.py_result() + else: + tb_code = "0" + if self.cause: + self.cause.generate_evaluation_code(code) + cause_code = self.cause.py_result() + else: + cause_code = "0" + code.globalstate.use_utility_code(raise_utility_code) + code.putln( + "__Pyx_Raise(%s, %s, %s, %s);" % ( + type_code, + value_code, + tb_code, + cause_code)) + for obj in (self.exc_type, self.exc_value, self.exc_tb, self.cause): + if obj: + obj.generate_disposal_code(code) + obj.free_temps(code) + code.putln( + code.error_goto(self.pos)) + + def generate_function_definitions(self, env, code): + if self.exc_type is not None: + self.exc_type.generate_function_definitions(env, code) + if self.exc_value is not None: + self.exc_value.generate_function_definitions(env, code) + if self.exc_tb is not None: + self.exc_tb.generate_function_definitions(env, code) + if self.cause is not None: + self.cause.generate_function_definitions(env, code) + + def annotate(self, code): + if self.exc_type: + self.exc_type.annotate(code) + if self.exc_value: + self.exc_value.annotate(code) + if self.exc_tb: + self.exc_tb.annotate(code) + if self.cause: + self.cause.annotate(code) + + +class ReraiseStatNode(StatNode): + + child_attrs = [] + is_terminator = True + + def analyse_expressions(self, env): + return self + + nogil_check = Node.gil_error + gil_message = "Raising exception" + + def generate_execution_code(self, code): code.mark_pos(self.pos) - vars = code.funcstate.exc_vars - if vars: - code.globalstate.use_utility_code(restore_exception_utility_code) - code.put_giveref(vars[0]) - code.put_giveref(vars[1]) - # fresh exceptions may not have a traceback yet (-> finally!) - code.put_xgiveref(vars[2]) + vars = code.funcstate.exc_vars + if vars: + code.globalstate.use_utility_code(restore_exception_utility_code) + code.put_giveref(vars[0]) + code.put_giveref(vars[1]) + # fresh exceptions may not have a traceback yet (-> finally!) + code.put_xgiveref(vars[2]) code.putln("__Pyx_ErrRestoreWithState(%s, %s, %s);" % tuple(vars)) - for varname in vars: - code.put("%s = 0; " % varname) - code.putln() - code.putln(code.error_goto(self.pos)) - else: - code.globalstate.use_utility_code( - UtilityCode.load_cached("ReRaiseException", "Exceptions.c")) - code.putln("__Pyx_ReraiseException(); %s" % code.error_goto(self.pos)) - -class AssertStatNode(StatNode): - # assert statement - # - # cond ExprNode - # value ExprNode or None - - child_attrs = ["cond", "value"] - - def analyse_expressions(self, env): - self.cond = self.cond.analyse_boolean_expression(env) - if self.value: - value = self.value.analyse_types(env) - if value.type is Builtin.tuple_type or not value.type.is_builtin_type: - # prevent tuple values from being interpreted as argument value tuples - from .ExprNodes import TupleNode - value = TupleNode(value.pos, args=[value], slow=True) + for varname in vars: + code.put("%s = 0; " % varname) + code.putln() + code.putln(code.error_goto(self.pos)) + else: + code.globalstate.use_utility_code( + UtilityCode.load_cached("ReRaiseException", "Exceptions.c")) + code.putln("__Pyx_ReraiseException(); %s" % code.error_goto(self.pos)) + +class AssertStatNode(StatNode): + # assert statement + # + # cond ExprNode + # value ExprNode or None + + child_attrs = ["cond", "value"] + + def analyse_expressions(self, env): + self.cond = self.cond.analyse_boolean_expression(env) + if self.value: + value = self.value.analyse_types(env) + if value.type is Builtin.tuple_type or not value.type.is_builtin_type: + # prevent tuple values from being interpreted as argument value tuples + from .ExprNodes import TupleNode + value = TupleNode(value.pos, args=[value], slow=True) self.value = value.analyse_types(env, skip_children=True).coerce_to_pyobject(env) - else: - self.value = value.coerce_to_pyobject(env) - return self - - nogil_check = Node.gil_error - gil_message = "Raising exception" - - def generate_execution_code(self, code): - code.putln("#ifndef CYTHON_WITHOUT_ASSERTIONS") - code.putln("if (unlikely(!Py_OptimizeFlag)) {") + else: + self.value = value.coerce_to_pyobject(env) + return self + + nogil_check = Node.gil_error + gil_message = "Raising exception" + + def generate_execution_code(self, code): + code.putln("#ifndef CYTHON_WITHOUT_ASSERTIONS") + code.putln("if (unlikely(!Py_OptimizeFlag)) {") code.mark_pos(self.pos) - self.cond.generate_evaluation_code(code) - code.putln( + self.cond.generate_evaluation_code(code) + code.putln( "if (unlikely(!%s)) {" % self.cond.result()) - if self.value: - self.value.generate_evaluation_code(code) - code.putln( + if self.value: + self.value.generate_evaluation_code(code) + code.putln( "PyErr_SetObject(PyExc_AssertionError, %s);" % self.value.py_result()) - self.value.generate_disposal_code(code) - self.value.free_temps(code) - else: - code.putln( - "PyErr_SetNone(PyExc_AssertionError);") - code.putln( + self.value.generate_disposal_code(code) + self.value.free_temps(code) + else: + code.putln( + "PyErr_SetNone(PyExc_AssertionError);") + code.putln( code.error_goto(self.pos)) - code.putln( - "}") - self.cond.generate_disposal_code(code) - self.cond.free_temps(code) - code.putln( - "}") - code.putln("#endif") - - def generate_function_definitions(self, env, code): - self.cond.generate_function_definitions(env, code) - if self.value is not None: - self.value.generate_function_definitions(env, code) - - def annotate(self, code): - self.cond.annotate(code) - if self.value: - self.value.annotate(code) - - -class IfStatNode(StatNode): - # if statement - # - # if_clauses [IfClauseNode] - # else_clause StatNode or None - - child_attrs = ["if_clauses", "else_clause"] - - def analyse_declarations(self, env): - for if_clause in self.if_clauses: - if_clause.analyse_declarations(env) - if self.else_clause: - self.else_clause.analyse_declarations(env) - - def analyse_expressions(self, env): + code.putln( + "}") + self.cond.generate_disposal_code(code) + self.cond.free_temps(code) + code.putln( + "}") + code.putln("#endif") + + def generate_function_definitions(self, env, code): + self.cond.generate_function_definitions(env, code) + if self.value is not None: + self.value.generate_function_definitions(env, code) + + def annotate(self, code): + self.cond.annotate(code) + if self.value: + self.value.annotate(code) + + +class IfStatNode(StatNode): + # if statement + # + # if_clauses [IfClauseNode] + # else_clause StatNode or None + + child_attrs = ["if_clauses", "else_clause"] + + def analyse_declarations(self, env): + for if_clause in self.if_clauses: + if_clause.analyse_declarations(env) + if self.else_clause: + self.else_clause.analyse_declarations(env) + + def analyse_expressions(self, env): self.if_clauses = [if_clause.analyse_expressions(env) for if_clause in self.if_clauses] - if self.else_clause: - self.else_clause = self.else_clause.analyse_expressions(env) - return self - - def generate_execution_code(self, code): - code.mark_pos(self.pos) - end_label = code.new_label() + if self.else_clause: + self.else_clause = self.else_clause.analyse_expressions(env) + return self + + def generate_execution_code(self, code): + code.mark_pos(self.pos) + end_label = code.new_label() last = len(self.if_clauses) if self.else_clause: # If the 'else' clause is 'unlikely', then set the preceding 'if' clause to 'likely' to reflect that. @@ -6280,13 +6280,13 @@ class IfStatNode(StatNode): for i, if_clause in enumerate(self.if_clauses): self._set_branch_hint(if_clause, if_clause.body) if_clause.generate_execution_code(code, end_label, is_last=i == last) - if self.else_clause: + if self.else_clause: code.mark_pos(self.else_clause.pos) - code.putln("/*else*/ {") - self.else_clause.generate_execution_code(code) - code.putln("}") - code.put_label(end_label) - + code.putln("/*else*/ {") + self.else_clause.generate_execution_code(code) + code.putln("}") + code.put_label(end_label) + def _set_branch_hint(self, clause, statements_node, inverse=False): if not statements_node.is_terminator: return @@ -6302,223 +6302,223 @@ class IfStatNode(StatNode): return clause.branch_hint = 'likely' if inverse else 'unlikely' - def generate_function_definitions(self, env, code): - for clause in self.if_clauses: - clause.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - for if_clause in self.if_clauses: - if_clause.annotate(code) - if self.else_clause: - self.else_clause.annotate(code) - - -class IfClauseNode(Node): - # if or elif clause in an if statement - # - # condition ExprNode - # body StatNode - - child_attrs = ["condition", "body"] + def generate_function_definitions(self, env, code): + for clause in self.if_clauses: + clause.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + for if_clause in self.if_clauses: + if_clause.annotate(code) + if self.else_clause: + self.else_clause.annotate(code) + + +class IfClauseNode(Node): + # if or elif clause in an if statement + # + # condition ExprNode + # body StatNode + + child_attrs = ["condition", "body"] branch_hint = None - - def analyse_declarations(self, env): - self.body.analyse_declarations(env) - - def analyse_expressions(self, env): + + def analyse_declarations(self, env): + self.body.analyse_declarations(env) + + def analyse_expressions(self, env): self.condition = self.condition.analyse_temp_boolean_expression(env) - self.body = self.body.analyse_expressions(env) - return self - + self.body = self.body.analyse_expressions(env) + return self + def generate_execution_code(self, code, end_label, is_last): - self.condition.generate_evaluation_code(code) + self.condition.generate_evaluation_code(code) code.mark_pos(self.pos) condition = self.condition.result() if self.branch_hint: condition = '%s(%s)' % (self.branch_hint, condition) code.putln("if (%s) {" % condition) - self.condition.generate_disposal_code(code) - self.condition.free_temps(code) - self.body.generate_execution_code(code) + self.condition.generate_disposal_code(code) + self.condition.free_temps(code) + self.body.generate_execution_code(code) code.mark_pos(self.pos, trace=False) if not (is_last or self.body.is_terminator): - code.put_goto(end_label) - code.putln("}") - - def generate_function_definitions(self, env, code): - self.condition.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - - def annotate(self, code): - self.condition.annotate(code) - self.body.annotate(code) - - -class SwitchCaseNode(StatNode): - # Generated in the optimization of an if-elif-else node - # - # conditions [ExprNode] - # body StatNode - - child_attrs = ['conditions', 'body'] - + code.put_goto(end_label) + code.putln("}") + + def generate_function_definitions(self, env, code): + self.condition.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + + def annotate(self, code): + self.condition.annotate(code) + self.body.annotate(code) + + +class SwitchCaseNode(StatNode): + # Generated in the optimization of an if-elif-else node + # + # conditions [ExprNode] + # body StatNode + + child_attrs = ['conditions', 'body'] + def generate_condition_evaluation_code(self, code): - for cond in self.conditions: - cond.generate_evaluation_code(code) + for cond in self.conditions: + cond.generate_evaluation_code(code) def generate_execution_code(self, code): num_conditions = len(self.conditions) line_tracing_enabled = code.globalstate.directives['linetrace'] for i, cond in enumerate(self.conditions, 1): - code.putln("case %s:" % cond.result()) + code.putln("case %s:" % cond.result()) code.mark_pos(cond.pos) # Tracing code must appear *after* the 'case' statement. if line_tracing_enabled and i < num_conditions: # Allow fall-through after the line tracing code. code.putln('CYTHON_FALLTHROUGH;') - self.body.generate_execution_code(code) + self.body.generate_execution_code(code) code.mark_pos(self.pos, trace=False) - code.putln("break;") - - def generate_function_definitions(self, env, code): - for cond in self.conditions: - cond.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - - def annotate(self, code): - for cond in self.conditions: - cond.annotate(code) - self.body.annotate(code) - - -class SwitchStatNode(StatNode): - # Generated in the optimization of an if-elif-else node - # - # test ExprNode - # cases [SwitchCaseNode] - # else_clause StatNode or None - - child_attrs = ['test', 'cases', 'else_clause'] - - def generate_execution_code(self, code): - self.test.generate_evaluation_code(code) + code.putln("break;") + + def generate_function_definitions(self, env, code): + for cond in self.conditions: + cond.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + + def annotate(self, code): + for cond in self.conditions: + cond.annotate(code) + self.body.annotate(code) + + +class SwitchStatNode(StatNode): + # Generated in the optimization of an if-elif-else node + # + # test ExprNode + # cases [SwitchCaseNode] + # else_clause StatNode or None + + child_attrs = ['test', 'cases', 'else_clause'] + + def generate_execution_code(self, code): + self.test.generate_evaluation_code(code) # Make sure all conditions are evaluated before going into the switch() statement. # This is required in order to prevent any execution code from leaking into the space between the cases. for case in self.cases: case.generate_condition_evaluation_code(code) code.mark_pos(self.pos) - code.putln("switch (%s) {" % self.test.result()) - for case in self.cases: - case.generate_execution_code(code) - if self.else_clause is not None: - code.putln("default:") - self.else_clause.generate_execution_code(code) - code.putln("break;") - else: - # Always generate a default clause to prevent C compiler warnings - # about unmatched enum values (it was not the user who decided to - # generate the switch statement, so shouldn't be bothered). - code.putln("default: break;") - code.putln("}") + code.putln("switch (%s) {" % self.test.result()) + for case in self.cases: + case.generate_execution_code(code) + if self.else_clause is not None: + code.putln("default:") + self.else_clause.generate_execution_code(code) + code.putln("break;") + else: + # Always generate a default clause to prevent C compiler warnings + # about unmatched enum values (it was not the user who decided to + # generate the switch statement, so shouldn't be bothered). + code.putln("default: break;") + code.putln("}") self.test.generate_disposal_code(code) self.test.free_temps(code) - - def generate_function_definitions(self, env, code): - self.test.generate_function_definitions(env, code) - for case in self.cases: - case.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - self.test.annotate(code) - for case in self.cases: - case.annotate(code) - if self.else_clause is not None: - self.else_clause.annotate(code) - - -class LoopNode(object): - pass - - -class WhileStatNode(LoopNode, StatNode): - # while statement - # - # condition ExprNode - # body StatNode - # else_clause StatNode - - child_attrs = ["condition", "body", "else_clause"] - - def analyse_declarations(self, env): - self.body.analyse_declarations(env) - if self.else_clause: - self.else_clause.analyse_declarations(env) - - def analyse_expressions(self, env): - if self.condition: - self.condition = self.condition.analyse_temp_boolean_expression(env) - self.body = self.body.analyse_expressions(env) - if self.else_clause: - self.else_clause = self.else_clause.analyse_expressions(env) - return self - - def generate_execution_code(self, code): + + def generate_function_definitions(self, env, code): + self.test.generate_function_definitions(env, code) + for case in self.cases: + case.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + self.test.annotate(code) + for case in self.cases: + case.annotate(code) + if self.else_clause is not None: + self.else_clause.annotate(code) + + +class LoopNode(object): + pass + + +class WhileStatNode(LoopNode, StatNode): + # while statement + # + # condition ExprNode + # body StatNode + # else_clause StatNode + + child_attrs = ["condition", "body", "else_clause"] + + def analyse_declarations(self, env): + self.body.analyse_declarations(env) + if self.else_clause: + self.else_clause.analyse_declarations(env) + + def analyse_expressions(self, env): + if self.condition: + self.condition = self.condition.analyse_temp_boolean_expression(env) + self.body = self.body.analyse_expressions(env) + if self.else_clause: + self.else_clause = self.else_clause.analyse_expressions(env) + return self + + def generate_execution_code(self, code): code.mark_pos(self.pos) - old_loop_labels = code.new_loop_labels() - code.putln( - "while (1) {") - if self.condition: - self.condition.generate_evaluation_code(code) - self.condition.generate_disposal_code(code) - code.putln( + old_loop_labels = code.new_loop_labels() + code.putln( + "while (1) {") + if self.condition: + self.condition.generate_evaluation_code(code) + self.condition.generate_disposal_code(code) + code.putln( "if (!%s) break;" % self.condition.result()) - self.condition.free_temps(code) - self.body.generate_execution_code(code) - code.put_label(code.continue_label) - code.putln("}") - break_label = code.break_label - code.set_loop_labels(old_loop_labels) - if self.else_clause: - code.mark_pos(self.else_clause.pos) - code.putln("/*else*/ {") - self.else_clause.generate_execution_code(code) - code.putln("}") - code.put_label(break_label) - - def generate_function_definitions(self, env, code): - if self.condition: - self.condition.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - if self.condition: - self.condition.annotate(code) - self.body.annotate(code) - if self.else_clause: - self.else_clause.annotate(code) - - -class DictIterationNextNode(Node): - # Helper node for calling PyDict_Next() inside of a WhileStatNode - # and checking the dictionary size for changes. Created in - # Optimize.py. - child_attrs = ['dict_obj', 'expected_size', 'pos_index_var', - 'coerced_key_var', 'coerced_value_var', 'coerced_tuple_var', - 'key_target', 'value_target', 'tuple_target', 'is_dict_flag'] - - coerced_key_var = key_ref = None - coerced_value_var = value_ref = None - coerced_tuple_var = tuple_ref = None - - def __init__(self, dict_obj, expected_size, pos_index_var, - key_target, value_target, tuple_target, is_dict_flag): - Node.__init__( - self, dict_obj.pos, + self.condition.free_temps(code) + self.body.generate_execution_code(code) + code.put_label(code.continue_label) + code.putln("}") + break_label = code.break_label + code.set_loop_labels(old_loop_labels) + if self.else_clause: + code.mark_pos(self.else_clause.pos) + code.putln("/*else*/ {") + self.else_clause.generate_execution_code(code) + code.putln("}") + code.put_label(break_label) + + def generate_function_definitions(self, env, code): + if self.condition: + self.condition.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + if self.condition: + self.condition.annotate(code) + self.body.annotate(code) + if self.else_clause: + self.else_clause.annotate(code) + + +class DictIterationNextNode(Node): + # Helper node for calling PyDict_Next() inside of a WhileStatNode + # and checking the dictionary size for changes. Created in + # Optimize.py. + child_attrs = ['dict_obj', 'expected_size', 'pos_index_var', + 'coerced_key_var', 'coerced_value_var', 'coerced_tuple_var', + 'key_target', 'value_target', 'tuple_target', 'is_dict_flag'] + + coerced_key_var = key_ref = None + coerced_value_var = value_ref = None + coerced_tuple_var = tuple_ref = None + + def __init__(self, dict_obj, expected_size, pos_index_var, + key_target, value_target, tuple_target, is_dict_flag): + Node.__init__( + self, dict_obj.pos, dict_obj=dict_obj, expected_size=expected_size, pos_index_var=pos_index_var, @@ -6528,72 +6528,72 @@ class DictIterationNextNode(Node): is_dict_flag=is_dict_flag, is_temp=True, type=PyrexTypes.c_bint_type) - - def analyse_expressions(self, env): - from . import ExprNodes - self.dict_obj = self.dict_obj.analyse_types(env) - self.expected_size = self.expected_size.analyse_types(env) - if self.pos_index_var: - self.pos_index_var = self.pos_index_var.analyse_types(env) - if self.key_target: - self.key_target = self.key_target.analyse_target_types(env) - self.key_ref = ExprNodes.TempNode(self.key_target.pos, PyrexTypes.py_object_type) - self.coerced_key_var = self.key_ref.coerce_to(self.key_target.type, env) - if self.value_target: - self.value_target = self.value_target.analyse_target_types(env) - self.value_ref = ExprNodes.TempNode(self.value_target.pos, type=PyrexTypes.py_object_type) - self.coerced_value_var = self.value_ref.coerce_to(self.value_target.type, env) - if self.tuple_target: - self.tuple_target = self.tuple_target.analyse_target_types(env) - self.tuple_ref = ExprNodes.TempNode(self.tuple_target.pos, PyrexTypes.py_object_type) - self.coerced_tuple_var = self.tuple_ref.coerce_to(self.tuple_target.type, env) - self.is_dict_flag = self.is_dict_flag.analyse_types(env) - return self - - def generate_function_definitions(self, env, code): - self.dict_obj.generate_function_definitions(env, code) - - def generate_execution_code(self, code): - code.globalstate.use_utility_code(UtilityCode.load_cached("dict_iter", "Optimize.c")) - self.dict_obj.generate_evaluation_code(code) - - assignments = [] - temp_addresses = [] - for var, result, target in [(self.key_ref, self.coerced_key_var, self.key_target), - (self.value_ref, self.coerced_value_var, self.value_target), - (self.tuple_ref, self.coerced_tuple_var, self.tuple_target)]: - if target is None: - addr = 'NULL' - else: - assignments.append((var, result, target)) - var.allocate(code) - addr = '&%s' % var.result() - temp_addresses.append(addr) - - result_temp = code.funcstate.allocate_temp(PyrexTypes.c_int_type, False) - code.putln("%s = __Pyx_dict_iter_next(%s, %s, &%s, %s, %s, %s, %s);" % ( - result_temp, - self.dict_obj.py_result(), - self.expected_size.result(), - self.pos_index_var.result(), - temp_addresses[0], - temp_addresses[1], - temp_addresses[2], - self.is_dict_flag.result() - )) - code.putln("if (unlikely(%s == 0)) break;" % result_temp) - code.putln(code.error_goto_if("%s == -1" % result_temp, self.pos)) - code.funcstate.release_temp(result_temp) - - # evaluate all coercions before the assignments - for var, result, target in assignments: - code.put_gotref(var.result()) - for var, result, target in assignments: - result.generate_evaluation_code(code) - for var, result, target in assignments: - target.generate_assignment_code(result, code) - var.release(code) - + + def analyse_expressions(self, env): + from . import ExprNodes + self.dict_obj = self.dict_obj.analyse_types(env) + self.expected_size = self.expected_size.analyse_types(env) + if self.pos_index_var: + self.pos_index_var = self.pos_index_var.analyse_types(env) + if self.key_target: + self.key_target = self.key_target.analyse_target_types(env) + self.key_ref = ExprNodes.TempNode(self.key_target.pos, PyrexTypes.py_object_type) + self.coerced_key_var = self.key_ref.coerce_to(self.key_target.type, env) + if self.value_target: + self.value_target = self.value_target.analyse_target_types(env) + self.value_ref = ExprNodes.TempNode(self.value_target.pos, type=PyrexTypes.py_object_type) + self.coerced_value_var = self.value_ref.coerce_to(self.value_target.type, env) + if self.tuple_target: + self.tuple_target = self.tuple_target.analyse_target_types(env) + self.tuple_ref = ExprNodes.TempNode(self.tuple_target.pos, PyrexTypes.py_object_type) + self.coerced_tuple_var = self.tuple_ref.coerce_to(self.tuple_target.type, env) + self.is_dict_flag = self.is_dict_flag.analyse_types(env) + return self + + def generate_function_definitions(self, env, code): + self.dict_obj.generate_function_definitions(env, code) + + def generate_execution_code(self, code): + code.globalstate.use_utility_code(UtilityCode.load_cached("dict_iter", "Optimize.c")) + self.dict_obj.generate_evaluation_code(code) + + assignments = [] + temp_addresses = [] + for var, result, target in [(self.key_ref, self.coerced_key_var, self.key_target), + (self.value_ref, self.coerced_value_var, self.value_target), + (self.tuple_ref, self.coerced_tuple_var, self.tuple_target)]: + if target is None: + addr = 'NULL' + else: + assignments.append((var, result, target)) + var.allocate(code) + addr = '&%s' % var.result() + temp_addresses.append(addr) + + result_temp = code.funcstate.allocate_temp(PyrexTypes.c_int_type, False) + code.putln("%s = __Pyx_dict_iter_next(%s, %s, &%s, %s, %s, %s, %s);" % ( + result_temp, + self.dict_obj.py_result(), + self.expected_size.result(), + self.pos_index_var.result(), + temp_addresses[0], + temp_addresses[1], + temp_addresses[2], + self.is_dict_flag.result() + )) + code.putln("if (unlikely(%s == 0)) break;" % result_temp) + code.putln(code.error_goto_if("%s == -1" % result_temp, self.pos)) + code.funcstate.release_temp(result_temp) + + # evaluate all coercions before the assignments + for var, result, target in assignments: + code.put_gotref(var.result()) + for var, result, target in assignments: + result.generate_evaluation_code(code) + for var, result, target in assignments: + target.generate_assignment_code(result, code) + var.release(code) + class SetIterationNextNode(Node): # Helper node for calling _PySet_NextEntry() inside of a WhileStatNode @@ -6655,113 +6655,113 @@ class SetIterationNextNode(Node): value_ref.release(code) -def ForStatNode(pos, **kw): - if 'iterator' in kw: +def ForStatNode(pos, **kw): + if 'iterator' in kw: if kw['iterator'].is_async: return AsyncForStatNode(pos, **kw) else: return ForInStatNode(pos, **kw) - else: - return ForFromStatNode(pos, **kw) - + else: + return ForFromStatNode(pos, **kw) + class _ForInStatNode(LoopNode, StatNode): # Base class of 'for-in' statements. - # - # target ExprNode + # + # target ExprNode # iterator IteratorNode | AIterAwaitExprNode(AsyncIteratorNode) - # body StatNode - # else_clause StatNode + # body StatNode + # else_clause StatNode # item NextNode | AwaitExprNode(AsyncNextNode) # is_async boolean true for 'async for' statements - + child_attrs = ["target", "item", "iterator", "body", "else_clause"] - item = None + item = None is_async = False - + def _create_item_node(self): raise NotImplementedError("must be implemented by subclasses") - def analyse_declarations(self, env): - self.target.analyse_target_declaration(env) - self.body.analyse_declarations(env) - if self.else_clause: - self.else_clause.analyse_declarations(env) + def analyse_declarations(self, env): + self.target.analyse_target_declaration(env) + self.body.analyse_declarations(env) + if self.else_clause: + self.else_clause.analyse_declarations(env) self._create_item_node() - - def analyse_expressions(self, env): - self.target = self.target.analyse_target_types(env) - self.iterator = self.iterator.analyse_expressions(env) + + def analyse_expressions(self, env): + self.target = self.target.analyse_target_types(env) + self.iterator = self.iterator.analyse_expressions(env) self._create_item_node() # must rewrap self.item after analysis - self.item = self.item.analyse_expressions(env) + self.item = self.item.analyse_expressions(env) if (not self.is_async and (self.iterator.type.is_ptr or self.iterator.type.is_array) and self.target.type.assignable_from(self.iterator.type)): - # C array slice optimization. - pass - else: - self.item = self.item.coerce_to(self.target.type, env) - self.body = self.body.analyse_expressions(env) - if self.else_clause: - self.else_clause = self.else_clause.analyse_expressions(env) - return self - - def generate_execution_code(self, code): + # C array slice optimization. + pass + else: + self.item = self.item.coerce_to(self.target.type, env) + self.body = self.body.analyse_expressions(env) + if self.else_clause: + self.else_clause = self.else_clause.analyse_expressions(env) + return self + + def generate_execution_code(self, code): code.mark_pos(self.pos) - old_loop_labels = code.new_loop_labels() - self.iterator.generate_evaluation_code(code) - code.putln("for (;;) {") - self.item.generate_evaluation_code(code) - self.target.generate_assignment_code(self.item, code) - self.body.generate_execution_code(code) - code.mark_pos(self.pos) - code.put_label(code.continue_label) - code.putln("}") - break_label = code.break_label - code.set_loop_labels(old_loop_labels) - - if self.else_clause: - # in nested loops, the 'else' block can contain a - # 'continue' statement for the outer loop, but we may need - # to generate cleanup code before taking that path, so we - # intercept it here - orig_continue_label = code.continue_label - code.continue_label = code.new_label('outer_continue') - - code.putln("/*else*/ {") - self.else_clause.generate_execution_code(code) - code.putln("}") - - if code.label_used(code.continue_label): - code.put_goto(break_label) - code.mark_pos(self.pos) - code.put_label(code.continue_label) - self.iterator.generate_disposal_code(code) - code.put_goto(orig_continue_label) - code.set_loop_labels(old_loop_labels) - - code.mark_pos(self.pos) - if code.label_used(break_label): - code.put_label(break_label) - self.iterator.generate_disposal_code(code) - self.iterator.free_temps(code) - - def generate_function_definitions(self, env, code): - self.target.generate_function_definitions(env, code) - self.iterator.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - self.target.annotate(code) - self.iterator.annotate(code) - self.body.annotate(code) - if self.else_clause: - self.else_clause.annotate(code) - self.item.annotate(code) - - + old_loop_labels = code.new_loop_labels() + self.iterator.generate_evaluation_code(code) + code.putln("for (;;) {") + self.item.generate_evaluation_code(code) + self.target.generate_assignment_code(self.item, code) + self.body.generate_execution_code(code) + code.mark_pos(self.pos) + code.put_label(code.continue_label) + code.putln("}") + break_label = code.break_label + code.set_loop_labels(old_loop_labels) + + if self.else_clause: + # in nested loops, the 'else' block can contain a + # 'continue' statement for the outer loop, but we may need + # to generate cleanup code before taking that path, so we + # intercept it here + orig_continue_label = code.continue_label + code.continue_label = code.new_label('outer_continue') + + code.putln("/*else*/ {") + self.else_clause.generate_execution_code(code) + code.putln("}") + + if code.label_used(code.continue_label): + code.put_goto(break_label) + code.mark_pos(self.pos) + code.put_label(code.continue_label) + self.iterator.generate_disposal_code(code) + code.put_goto(orig_continue_label) + code.set_loop_labels(old_loop_labels) + + code.mark_pos(self.pos) + if code.label_used(break_label): + code.put_label(break_label) + self.iterator.generate_disposal_code(code) + self.iterator.free_temps(code) + + def generate_function_definitions(self, env, code): + self.target.generate_function_definitions(env, code) + self.iterator.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + self.target.annotate(code) + self.iterator.annotate(code) + self.body.annotate(code) + if self.else_clause: + self.else_clause.annotate(code) + self.item.annotate(code) + + class ForInStatNode(_ForInStatNode): # 'for' statement @@ -6792,55 +6792,55 @@ class AsyncForStatNode(_ForInStatNode): self.item.arg = ExprNodes.AsyncNextNode(self.iterator) -class ForFromStatNode(LoopNode, StatNode): - # for name from expr rel name rel expr - # - # target NameNode - # bound1 ExprNode - # relation1 string - # relation2 string - # bound2 ExprNode - # step ExprNode or None - # body StatNode - # else_clause StatNode or None - # - # Used internally: - # - # from_range bool - # is_py_target bool - # loopvar_node ExprNode (usually a NameNode or temp node) - # py_loopvar_node PyTempNode or None - child_attrs = ["target", "bound1", "bound2", "step", "body", "else_clause"] - - is_py_target = False - loopvar_node = None - py_loopvar_node = None - from_range = False - - gil_message = "For-loop using object bounds or target" - - def nogil_check(self, env): - for x in (self.target, self.bound1, self.bound2): - if x.type.is_pyobject: - self.gil_error() - - def analyse_declarations(self, env): - self.target.analyse_target_declaration(env) - self.body.analyse_declarations(env) - if self.else_clause: - self.else_clause.analyse_declarations(env) - - def analyse_expressions(self, env): - from . import ExprNodes - self.target = self.target.analyse_target_types(env) - self.bound1 = self.bound1.analyse_types(env) - self.bound2 = self.bound2.analyse_types(env) - if self.step is not None: - if isinstance(self.step, ExprNodes.UnaryMinusNode): +class ForFromStatNode(LoopNode, StatNode): + # for name from expr rel name rel expr + # + # target NameNode + # bound1 ExprNode + # relation1 string + # relation2 string + # bound2 ExprNode + # step ExprNode or None + # body StatNode + # else_clause StatNode or None + # + # Used internally: + # + # from_range bool + # is_py_target bool + # loopvar_node ExprNode (usually a NameNode or temp node) + # py_loopvar_node PyTempNode or None + child_attrs = ["target", "bound1", "bound2", "step", "body", "else_clause"] + + is_py_target = False + loopvar_node = None + py_loopvar_node = None + from_range = False + + gil_message = "For-loop using object bounds or target" + + def nogil_check(self, env): + for x in (self.target, self.bound1, self.bound2): + if x.type.is_pyobject: + self.gil_error() + + def analyse_declarations(self, env): + self.target.analyse_target_declaration(env) + self.body.analyse_declarations(env) + if self.else_clause: + self.else_clause.analyse_declarations(env) + + def analyse_expressions(self, env): + from . import ExprNodes + self.target = self.target.analyse_target_types(env) + self.bound1 = self.bound1.analyse_types(env) + self.bound2 = self.bound2.analyse_types(env) + if self.step is not None: + if isinstance(self.step, ExprNodes.UnaryMinusNode): warning(self.step.pos, "Probable infinite loop in for-from-by statement. " "Consider switching the directions of the relations.", 2) - self.step = self.step.analyse_types(env) - + self.step = self.step.analyse_types(env) + self.set_up_loop(env) target_type = self.target.type if not (target_type.is_pyobject or target_type.is_numeric): @@ -6857,64 +6857,64 @@ class ForFromStatNode(LoopNode, StatNode): target_type = self.target.type if target_type.is_numeric: loop_type = target_type - else: + else: if target_type.is_enum: warning(self.target.pos, "Integer loops over enum values are fragile. Please cast to a safe integer type instead.") loop_type = PyrexTypes.c_long_type if target_type.is_pyobject else PyrexTypes.c_int_type - if not self.bound1.type.is_pyobject: - loop_type = PyrexTypes.widest_numeric_type(loop_type, self.bound1.type) - if not self.bound2.type.is_pyobject: - loop_type = PyrexTypes.widest_numeric_type(loop_type, self.bound2.type) - if self.step is not None and not self.step.type.is_pyobject: - loop_type = PyrexTypes.widest_numeric_type(loop_type, self.step.type) - self.bound1 = self.bound1.coerce_to(loop_type, env) - self.bound2 = self.bound2.coerce_to(loop_type, env) - if not self.bound2.is_literal: - self.bound2 = self.bound2.coerce_to_temp(env) - if self.step is not None: - self.step = self.step.coerce_to(loop_type, env) - if not self.step.is_literal: - self.step = self.step.coerce_to_temp(env) - + if not self.bound1.type.is_pyobject: + loop_type = PyrexTypes.widest_numeric_type(loop_type, self.bound1.type) + if not self.bound2.type.is_pyobject: + loop_type = PyrexTypes.widest_numeric_type(loop_type, self.bound2.type) + if self.step is not None and not self.step.type.is_pyobject: + loop_type = PyrexTypes.widest_numeric_type(loop_type, self.step.type) + self.bound1 = self.bound1.coerce_to(loop_type, env) + self.bound2 = self.bound2.coerce_to(loop_type, env) + if not self.bound2.is_literal: + self.bound2 = self.bound2.coerce_to_temp(env) + if self.step is not None: + self.step = self.step.coerce_to(loop_type, env) + if not self.step.is_literal: + self.step = self.step.coerce_to_temp(env) + if target_type.is_numeric or target_type.is_enum: - self.is_py_target = False + self.is_py_target = False if isinstance(self.target, ExprNodes.BufferIndexNode): raise error(self.pos, "Buffer or memoryview slicing/indexing not allowed as for-loop target.") - self.loopvar_node = self.target - self.py_loopvar_node = None - else: - self.is_py_target = True - c_loopvar_node = ExprNodes.TempNode(self.pos, loop_type, env) - self.loopvar_node = c_loopvar_node + self.loopvar_node = self.target + self.py_loopvar_node = None + else: + self.is_py_target = True + c_loopvar_node = ExprNodes.TempNode(self.pos, loop_type, env) + self.loopvar_node = c_loopvar_node self.py_loopvar_node = ExprNodes.CloneNode(c_loopvar_node).coerce_to_pyobject(env) - - def generate_execution_code(self, code): + + def generate_execution_code(self, code): code.mark_pos(self.pos) - old_loop_labels = code.new_loop_labels() - from_range = self.from_range - self.bound1.generate_evaluation_code(code) - self.bound2.generate_evaluation_code(code) - offset, incop = self.relation_table[self.relation1] - if self.step is not None: - self.step.generate_evaluation_code(code) - step = self.step.result() + old_loop_labels = code.new_loop_labels() + from_range = self.from_range + self.bound1.generate_evaluation_code(code) + self.bound2.generate_evaluation_code(code) + offset, incop = self.relation_table[self.relation1] + if self.step is not None: + self.step.generate_evaluation_code(code) + step = self.step.result() incop = "%s=%s" % (incop[0], step) # e.g. '++' => '+= STEP' else: step = '1' - from . import ExprNodes - if isinstance(self.loopvar_node, ExprNodes.TempNode): - self.loopvar_node.allocate(code) - if isinstance(self.py_loopvar_node, ExprNodes.TempNode): - self.py_loopvar_node.allocate(code) + from . import ExprNodes + if isinstance(self.loopvar_node, ExprNodes.TempNode): + self.loopvar_node.allocate(code) + if isinstance(self.py_loopvar_node, ExprNodes.TempNode): + self.py_loopvar_node.allocate(code) loopvar_type = PyrexTypes.c_long_type if self.target.type.is_enum else self.target.type if from_range and not self.is_py_target: loopvar_name = code.funcstate.allocate_temp(loopvar_type, False) - else: - loopvar_name = self.loopvar_node.result() + else: + loopvar_name = self.loopvar_node.result() if loopvar_type.is_int and not loopvar_type.signed and self.relation2[0] == '>': # Handle the case where the endpoint of an unsigned int iteration # is within step of 0. @@ -6937,454 +6937,454 @@ class ForFromStatNode(LoopNode, StatNode): coerced_loopvar_node.generate_evaluation_code(code) self.target.generate_assignment_code(coerced_loopvar_node, code) - self.body.generate_execution_code(code) - code.put_label(code.continue_label) + self.body.generate_execution_code(code) + code.put_label(code.continue_label) if not from_range and self.py_loopvar_node: - # This mess is to make for..from loops with python targets behave - # exactly like those with C targets with regards to re-assignment - # of the loop variable. - if self.target.entry.is_pyglobal: - # We know target is a NameNode, this is the only ugly case. - target_node = ExprNodes.PyTempNode(self.target.pos, None) - target_node.allocate(code) - interned_cname = code.intern_identifier(self.target.entry.name) - if self.target.entry.scope.is_module_scope: - code.globalstate.use_utility_code( - UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) + # This mess is to make for..from loops with python targets behave + # exactly like those with C targets with regards to re-assignment + # of the loop variable. + if self.target.entry.is_pyglobal: + # We know target is a NameNode, this is the only ugly case. + target_node = ExprNodes.PyTempNode(self.target.pos, None) + target_node.allocate(code) + interned_cname = code.intern_identifier(self.target.entry.name) + if self.target.entry.scope.is_module_scope: + code.globalstate.use_utility_code( + UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) lookup_func = '__Pyx_GetModuleGlobalName(%s, %s); %s' - else: - code.globalstate.use_utility_code( - UtilityCode.load_cached("GetNameInClass", "ObjectHandling.c")) + else: + code.globalstate.use_utility_code( + UtilityCode.load_cached("GetNameInClass", "ObjectHandling.c")) lookup_func = '__Pyx_GetNameInClass(%s, {}, %s); %s'.format( - self.target.entry.scope.namespace_cname) + self.target.entry.scope.namespace_cname) code.putln(lookup_func % ( - target_node.result(), + target_node.result(), interned_cname, - code.error_goto_if_null(target_node.result(), self.target.pos))) - code.put_gotref(target_node.result()) - else: - target_node = self.target - from_py_node = ExprNodes.CoerceFromPyTypeNode( - self.loopvar_node.type, target_node, self.target.entry.scope) - from_py_node.temp_code = loopvar_name - from_py_node.generate_result_code(code) - if self.target.entry.is_pyglobal: - code.put_decref(target_node.result(), target_node.type) - target_node.release(code) - - code.putln("}") + code.error_goto_if_null(target_node.result(), self.target.pos))) + code.put_gotref(target_node.result()) + else: + target_node = self.target + from_py_node = ExprNodes.CoerceFromPyTypeNode( + self.loopvar_node.type, target_node, self.target.entry.scope) + from_py_node.temp_code = loopvar_name + from_py_node.generate_result_code(code) + if self.target.entry.is_pyglobal: + code.put_decref(target_node.result(), target_node.type) + target_node.release(code) + + code.putln("}") if not from_range and self.py_loopvar_node: - # This is potentially wasteful, but we don't want the semantics to - # depend on whether or not the loop is a python type. - self.py_loopvar_node.generate_evaluation_code(code) - self.target.generate_assignment_code(self.py_loopvar_node, code) + # This is potentially wasteful, but we don't want the semantics to + # depend on whether or not the loop is a python type. + self.py_loopvar_node.generate_evaluation_code(code) + self.target.generate_assignment_code(self.py_loopvar_node, code) if from_range and not self.is_py_target: - code.funcstate.release_temp(loopvar_name) - - break_label = code.break_label - code.set_loop_labels(old_loop_labels) - if self.else_clause: - code.putln("/*else*/ {") - self.else_clause.generate_execution_code(code) - code.putln("}") - code.put_label(break_label) - self.bound1.generate_disposal_code(code) - self.bound1.free_temps(code) - self.bound2.generate_disposal_code(code) - self.bound2.free_temps(code) - if isinstance(self.loopvar_node, ExprNodes.TempNode): - self.loopvar_node.release(code) - if isinstance(self.py_loopvar_node, ExprNodes.TempNode): - self.py_loopvar_node.release(code) - if self.step is not None: - self.step.generate_disposal_code(code) - self.step.free_temps(code) - - relation_table = { - # {relop : (initial offset, increment op)} - '<=': ("", "++"), - '<' : ("+1", "++"), - '>=': ("", "--"), + code.funcstate.release_temp(loopvar_name) + + break_label = code.break_label + code.set_loop_labels(old_loop_labels) + if self.else_clause: + code.putln("/*else*/ {") + self.else_clause.generate_execution_code(code) + code.putln("}") + code.put_label(break_label) + self.bound1.generate_disposal_code(code) + self.bound1.free_temps(code) + self.bound2.generate_disposal_code(code) + self.bound2.free_temps(code) + if isinstance(self.loopvar_node, ExprNodes.TempNode): + self.loopvar_node.release(code) + if isinstance(self.py_loopvar_node, ExprNodes.TempNode): + self.py_loopvar_node.release(code) + if self.step is not None: + self.step.generate_disposal_code(code) + self.step.free_temps(code) + + relation_table = { + # {relop : (initial offset, increment op)} + '<=': ("", "++"), + '<' : ("+1", "++"), + '>=': ("", "--"), '>' : ("-1", "--"), - } - - def generate_function_definitions(self, env, code): - self.target.generate_function_definitions(env, code) - self.bound1.generate_function_definitions(env, code) - self.bound2.generate_function_definitions(env, code) - if self.step is not None: - self.step.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - self.target.annotate(code) - self.bound1.annotate(code) - self.bound2.annotate(code) - if self.step: - self.step.annotate(code) - self.body.annotate(code) - if self.else_clause: - self.else_clause.annotate(code) - - -class WithStatNode(StatNode): - """ - Represents a Python with statement. - - Implemented by the WithTransform as follows: - - MGR = EXPR - EXIT = MGR.__exit__ - VALUE = MGR.__enter__() - EXC = True - try: - try: - TARGET = VALUE # optional - BODY - except: - EXC = False - if not EXIT(*EXCINFO): - raise - finally: - if EXC: - EXIT(None, None, None) - MGR = EXIT = VALUE = None - """ - # manager The with statement manager object - # target ExprNode the target lhs of the __enter__() call - # body StatNode - # enter_call ExprNode the call to the __enter__() method - # exit_var String the cname of the __exit__() method reference - - child_attrs = ["manager", "enter_call", "target", "body"] - - enter_call = None - target_temp = None - - def analyse_declarations(self, env): - self.manager.analyse_declarations(env) - self.enter_call.analyse_declarations(env) - self.body.analyse_declarations(env) - - def analyse_expressions(self, env): - self.manager = self.manager.analyse_types(env) - self.enter_call = self.enter_call.analyse_types(env) - if self.target: - # set up target_temp before descending into body (which uses it) - from .ExprNodes import TempNode - self.target_temp = TempNode(self.enter_call.pos, self.enter_call.type) - self.body = self.body.analyse_expressions(env) - return self - - def generate_function_definitions(self, env, code): - self.manager.generate_function_definitions(env, code) - self.enter_call.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - - def generate_execution_code(self, code): + } + + def generate_function_definitions(self, env, code): + self.target.generate_function_definitions(env, code) + self.bound1.generate_function_definitions(env, code) + self.bound2.generate_function_definitions(env, code) + if self.step is not None: + self.step.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + self.target.annotate(code) + self.bound1.annotate(code) + self.bound2.annotate(code) + if self.step: + self.step.annotate(code) + self.body.annotate(code) + if self.else_clause: + self.else_clause.annotate(code) + + +class WithStatNode(StatNode): + """ + Represents a Python with statement. + + Implemented by the WithTransform as follows: + + MGR = EXPR + EXIT = MGR.__exit__ + VALUE = MGR.__enter__() + EXC = True + try: + try: + TARGET = VALUE # optional + BODY + except: + EXC = False + if not EXIT(*EXCINFO): + raise + finally: + if EXC: + EXIT(None, None, None) + MGR = EXIT = VALUE = None + """ + # manager The with statement manager object + # target ExprNode the target lhs of the __enter__() call + # body StatNode + # enter_call ExprNode the call to the __enter__() method + # exit_var String the cname of the __exit__() method reference + + child_attrs = ["manager", "enter_call", "target", "body"] + + enter_call = None + target_temp = None + + def analyse_declarations(self, env): + self.manager.analyse_declarations(env) + self.enter_call.analyse_declarations(env) + self.body.analyse_declarations(env) + + def analyse_expressions(self, env): + self.manager = self.manager.analyse_types(env) + self.enter_call = self.enter_call.analyse_types(env) + if self.target: + # set up target_temp before descending into body (which uses it) + from .ExprNodes import TempNode + self.target_temp = TempNode(self.enter_call.pos, self.enter_call.type) + self.body = self.body.analyse_expressions(env) + return self + + def generate_function_definitions(self, env, code): + self.manager.generate_function_definitions(env, code) + self.enter_call.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + + def generate_execution_code(self, code): code.mark_pos(self.pos) - code.putln("/*with:*/ {") - self.manager.generate_evaluation_code(code) - self.exit_var = code.funcstate.allocate_temp(py_object_type, manage_ref=False) - code.globalstate.use_utility_code( - UtilityCode.load_cached("PyObjectLookupSpecial", "ObjectHandling.c")) - code.putln("%s = __Pyx_PyObject_LookupSpecial(%s, %s); %s" % ( - self.exit_var, - self.manager.py_result(), + code.putln("/*with:*/ {") + self.manager.generate_evaluation_code(code) + self.exit_var = code.funcstate.allocate_temp(py_object_type, manage_ref=False) + code.globalstate.use_utility_code( + UtilityCode.load_cached("PyObjectLookupSpecial", "ObjectHandling.c")) + code.putln("%s = __Pyx_PyObject_LookupSpecial(%s, %s); %s" % ( + self.exit_var, + self.manager.py_result(), code.intern_identifier(EncodedString('__aexit__' if self.is_async else '__exit__')), - code.error_goto_if_null(self.exit_var, self.pos), - )) - code.put_gotref(self.exit_var) - - # need to free exit_var in the face of exceptions during setup - old_error_label = code.new_error_label() - intermediate_error_label = code.error_label - - self.enter_call.generate_evaluation_code(code) - if self.target: - # The temp result will be cleaned up by the WithTargetAssignmentStatNode - # after assigning its result to the target of the 'with' statement. - self.target_temp.allocate(code) - self.enter_call.make_owned_reference(code) - code.putln("%s = %s;" % (self.target_temp.result(), self.enter_call.result())) - self.enter_call.generate_post_assignment_code(code) - else: - self.enter_call.generate_disposal_code(code) - self.enter_call.free_temps(code) - - self.manager.generate_disposal_code(code) - self.manager.free_temps(code) - - code.error_label = old_error_label - self.body.generate_execution_code(code) - - if code.label_used(intermediate_error_label): - step_over_label = code.new_label() - code.put_goto(step_over_label) - code.put_label(intermediate_error_label) - code.put_decref_clear(self.exit_var, py_object_type) - code.put_goto(old_error_label) - code.put_label(step_over_label) - - code.funcstate.release_temp(self.exit_var) - code.putln('}') - - -class WithTargetAssignmentStatNode(AssignmentNode): - # The target assignment of the 'with' statement value (return - # value of the __enter__() call). - # - # This is a special cased assignment that properly cleans up the RHS. - # - # lhs ExprNode the assignment target - # rhs ExprNode a (coerced) TempNode for the rhs (from WithStatNode) - # with_node WithStatNode the surrounding with-statement - - child_attrs = ["rhs", "lhs"] - with_node = None - rhs = None - - def analyse_declarations(self, env): - self.lhs.analyse_target_declaration(env) - - def analyse_expressions(self, env): - self.lhs = self.lhs.analyse_target_types(env) - self.lhs.gil_assignment_check(env) - self.rhs = self.with_node.target_temp.coerce_to(self.lhs.type, env) - return self - - def generate_execution_code(self, code): - self.rhs.generate_evaluation_code(code) - self.lhs.generate_assignment_code(self.rhs, code) - self.with_node.target_temp.release(code) - - def annotate(self, code): - self.lhs.annotate(code) - self.rhs.annotate(code) - - -class TryExceptStatNode(StatNode): - # try .. except statement - # - # body StatNode - # except_clauses [ExceptClauseNode] - # else_clause StatNode or None - - child_attrs = ["body", "except_clauses", "else_clause"] + code.error_goto_if_null(self.exit_var, self.pos), + )) + code.put_gotref(self.exit_var) + + # need to free exit_var in the face of exceptions during setup + old_error_label = code.new_error_label() + intermediate_error_label = code.error_label + + self.enter_call.generate_evaluation_code(code) + if self.target: + # The temp result will be cleaned up by the WithTargetAssignmentStatNode + # after assigning its result to the target of the 'with' statement. + self.target_temp.allocate(code) + self.enter_call.make_owned_reference(code) + code.putln("%s = %s;" % (self.target_temp.result(), self.enter_call.result())) + self.enter_call.generate_post_assignment_code(code) + else: + self.enter_call.generate_disposal_code(code) + self.enter_call.free_temps(code) + + self.manager.generate_disposal_code(code) + self.manager.free_temps(code) + + code.error_label = old_error_label + self.body.generate_execution_code(code) + + if code.label_used(intermediate_error_label): + step_over_label = code.new_label() + code.put_goto(step_over_label) + code.put_label(intermediate_error_label) + code.put_decref_clear(self.exit_var, py_object_type) + code.put_goto(old_error_label) + code.put_label(step_over_label) + + code.funcstate.release_temp(self.exit_var) + code.putln('}') + + +class WithTargetAssignmentStatNode(AssignmentNode): + # The target assignment of the 'with' statement value (return + # value of the __enter__() call). + # + # This is a special cased assignment that properly cleans up the RHS. + # + # lhs ExprNode the assignment target + # rhs ExprNode a (coerced) TempNode for the rhs (from WithStatNode) + # with_node WithStatNode the surrounding with-statement + + child_attrs = ["rhs", "lhs"] + with_node = None + rhs = None + + def analyse_declarations(self, env): + self.lhs.analyse_target_declaration(env) + + def analyse_expressions(self, env): + self.lhs = self.lhs.analyse_target_types(env) + self.lhs.gil_assignment_check(env) + self.rhs = self.with_node.target_temp.coerce_to(self.lhs.type, env) + return self + + def generate_execution_code(self, code): + self.rhs.generate_evaluation_code(code) + self.lhs.generate_assignment_code(self.rhs, code) + self.with_node.target_temp.release(code) + + def annotate(self, code): + self.lhs.annotate(code) + self.rhs.annotate(code) + + +class TryExceptStatNode(StatNode): + # try .. except statement + # + # body StatNode + # except_clauses [ExceptClauseNode] + # else_clause StatNode or None + + child_attrs = ["body", "except_clauses", "else_clause"] in_generator = False - - def analyse_declarations(self, env): - self.body.analyse_declarations(env) - for except_clause in self.except_clauses: - except_clause.analyse_declarations(env) - if self.else_clause: - self.else_clause.analyse_declarations(env) - - def analyse_expressions(self, env): - self.body = self.body.analyse_expressions(env) - default_clause_seen = 0 - for i, except_clause in enumerate(self.except_clauses): - except_clause = self.except_clauses[i] = except_clause.analyse_expressions(env) - if default_clause_seen: - error(except_clause.pos, "default 'except:' must be last") - if not except_clause.pattern: - default_clause_seen = 1 - self.has_default_clause = default_clause_seen - if self.else_clause: - self.else_clause = self.else_clause.analyse_expressions(env) - return self - - nogil_check = Node.gil_error - gil_message = "Try-except statement" - - def generate_execution_code(self, code): + + def analyse_declarations(self, env): + self.body.analyse_declarations(env) + for except_clause in self.except_clauses: + except_clause.analyse_declarations(env) + if self.else_clause: + self.else_clause.analyse_declarations(env) + + def analyse_expressions(self, env): + self.body = self.body.analyse_expressions(env) + default_clause_seen = 0 + for i, except_clause in enumerate(self.except_clauses): + except_clause = self.except_clauses[i] = except_clause.analyse_expressions(env) + if default_clause_seen: + error(except_clause.pos, "default 'except:' must be last") + if not except_clause.pattern: + default_clause_seen = 1 + self.has_default_clause = default_clause_seen + if self.else_clause: + self.else_clause = self.else_clause.analyse_expressions(env) + return self + + nogil_check = Node.gil_error + gil_message = "Try-except statement" + + def generate_execution_code(self, code): code.mark_pos(self.pos) # before changing the error label, in case of tracing errors code.putln("{") - old_return_label = code.return_label - old_break_label = code.break_label - old_continue_label = code.continue_label - old_error_label = code.new_error_label() - our_error_label = code.error_label - except_end_label = code.new_label('exception_handled') - except_error_label = code.new_label('except_error') - except_return_label = code.new_label('except_return') - try_return_label = code.new_label('try_return') + old_return_label = code.return_label + old_break_label = code.break_label + old_continue_label = code.continue_label + old_error_label = code.new_error_label() + our_error_label = code.error_label + except_end_label = code.new_label('exception_handled') + except_error_label = code.new_label('except_error') + except_return_label = code.new_label('except_return') + try_return_label = code.new_label('try_return') try_break_label = code.new_label('try_break') if old_break_label else None try_continue_label = code.new_label('try_continue') if old_continue_label else None - try_end_label = code.new_label('try_end') - - exc_save_vars = [code.funcstate.allocate_temp(py_object_type, False) + try_end_label = code.new_label('try_end') + + exc_save_vars = [code.funcstate.allocate_temp(py_object_type, False) for _ in range(3)] - save_exc = code.insertion_point() - code.putln( - "/*try:*/ {") - code.return_label = try_return_label - code.break_label = try_break_label - code.continue_label = try_continue_label - self.body.generate_execution_code(code) + save_exc = code.insertion_point() + code.putln( + "/*try:*/ {") + code.return_label = try_return_label + code.break_label = try_break_label + code.continue_label = try_continue_label + self.body.generate_execution_code(code) code.mark_pos(self.pos, trace=False) - code.putln( - "}") - temps_to_clean_up = code.funcstate.all_free_managed_temps() - can_raise = code.label_used(our_error_label) - - if can_raise: - # inject code before the try block to save away the exception state - code.globalstate.use_utility_code(reset_exception_utility_code) + code.putln( + "}") + temps_to_clean_up = code.funcstate.all_free_managed_temps() + can_raise = code.label_used(our_error_label) + + if can_raise: + # inject code before the try block to save away the exception state + code.globalstate.use_utility_code(reset_exception_utility_code) if not self.in_generator: save_exc.putln("__Pyx_PyThreadState_declare") save_exc.putln("__Pyx_PyThreadState_assign") save_exc.putln("__Pyx_ExceptionSave(%s);" % ( ', '.join(['&%s' % var for var in exc_save_vars]))) - for var in exc_save_vars: - save_exc.put_xgotref(var) - - def restore_saved_exception(): - for name in exc_save_vars: - code.put_xgiveref(name) - code.putln("__Pyx_ExceptionReset(%s);" % - ', '.join(exc_save_vars)) - else: - # try block cannot raise exceptions, but we had to allocate the temps above, - # so just keep the C compiler from complaining about them being unused + for var in exc_save_vars: + save_exc.put_xgotref(var) + + def restore_saved_exception(): + for name in exc_save_vars: + code.put_xgiveref(name) + code.putln("__Pyx_ExceptionReset(%s);" % + ', '.join(exc_save_vars)) + else: + # try block cannot raise exceptions, but we had to allocate the temps above, + # so just keep the C compiler from complaining about them being unused mark_vars_used = ["(void)%s;" % var for var in exc_save_vars] save_exc.putln("%s /* mark used */" % ' '.join(mark_vars_used)) - - def restore_saved_exception(): - pass - - code.error_label = except_error_label - code.return_label = except_return_label - normal_case_terminates = self.body.is_terminator - if self.else_clause: + + def restore_saved_exception(): + pass + + code.error_label = except_error_label + code.return_label = except_return_label + normal_case_terminates = self.body.is_terminator + if self.else_clause: code.mark_pos(self.else_clause.pos) - code.putln( - "/*else:*/ {") - self.else_clause.generate_execution_code(code) - code.putln( - "}") - if not normal_case_terminates: - normal_case_terminates = self.else_clause.is_terminator - - if can_raise: - if not normal_case_terminates: - for var in exc_save_vars: - code.put_xdecref_clear(var, py_object_type) - code.put_goto(try_end_label) - code.put_label(our_error_label) - for temp_name, temp_type in temps_to_clean_up: - code.put_xdecref_clear(temp_name, temp_type) + code.putln( + "/*else:*/ {") + self.else_clause.generate_execution_code(code) + code.putln( + "}") + if not normal_case_terminates: + normal_case_terminates = self.else_clause.is_terminator + + if can_raise: + if not normal_case_terminates: + for var in exc_save_vars: + code.put_xdecref_clear(var, py_object_type) + code.put_goto(try_end_label) + code.put_label(our_error_label) + for temp_name, temp_type in temps_to_clean_up: + code.put_xdecref_clear(temp_name, temp_type) outer_except = code.funcstate.current_except # Currently points to self, but the ExceptClauseNode would also be ok. Change if needed. code.funcstate.current_except = self - for except_clause in self.except_clauses: - except_clause.generate_handling_code(code, except_end_label) + for except_clause in self.except_clauses: + except_clause.generate_handling_code(code, except_end_label) code.funcstate.current_except = outer_except - if not self.has_default_clause: - code.put_goto(except_error_label) - - for exit_label, old_label in [(except_error_label, old_error_label), - (try_break_label, old_break_label), - (try_continue_label, old_continue_label), - (try_return_label, old_return_label), - (except_return_label, old_return_label)]: - if code.label_used(exit_label): - if not normal_case_terminates and not code.label_used(try_end_label): - code.put_goto(try_end_label) - code.put_label(exit_label) + if not self.has_default_clause: + code.put_goto(except_error_label) + + for exit_label, old_label in [(except_error_label, old_error_label), + (try_break_label, old_break_label), + (try_continue_label, old_continue_label), + (try_return_label, old_return_label), + (except_return_label, old_return_label)]: + if code.label_used(exit_label): + if not normal_case_terminates and not code.label_used(try_end_label): + code.put_goto(try_end_label) + code.put_label(exit_label) code.mark_pos(self.pos, trace=False) if can_raise: restore_saved_exception() - code.put_goto(old_label) - - if code.label_used(except_end_label): - if not normal_case_terminates and not code.label_used(try_end_label): - code.put_goto(try_end_label) - code.put_label(except_end_label) + code.put_goto(old_label) + + if code.label_used(except_end_label): + if not normal_case_terminates and not code.label_used(try_end_label): + code.put_goto(try_end_label) + code.put_label(except_end_label) if can_raise: restore_saved_exception() - if code.label_used(try_end_label): - code.put_label(try_end_label) - code.putln("}") - - for cname in exc_save_vars: - code.funcstate.release_temp(cname) - - code.return_label = old_return_label - code.break_label = old_break_label - code.continue_label = old_continue_label - code.error_label = old_error_label - - def generate_function_definitions(self, env, code): - self.body.generate_function_definitions(env, code) - for except_clause in self.except_clauses: - except_clause.generate_function_definitions(env, code) - if self.else_clause is not None: - self.else_clause.generate_function_definitions(env, code) - - def annotate(self, code): - self.body.annotate(code) - for except_node in self.except_clauses: - except_node.annotate(code) - if self.else_clause: - self.else_clause.annotate(code) - - -class ExceptClauseNode(Node): - # Part of try ... except statement. - # - # pattern [ExprNode] - # target ExprNode or None - # body StatNode - # excinfo_target TupleNode(3*ResultRefNode) or None optional target for exception info (not owned here!) - # match_flag string result of exception match - # exc_value ExcValueNode used internally - # function_name string qualified name of enclosing function - # exc_vars (string * 3) local exception variables - # is_except_as bool Py3-style "except ... as xyz" - - # excinfo_target is never set by the parser, but can be set by a transform - # in order to extract more extensive information about the exception as a - # sys.exc_info()-style tuple into a target variable - - child_attrs = ["pattern", "target", "body", "exc_value"] - - exc_value = None - excinfo_target = None - is_except_as = False - - def analyse_declarations(self, env): - if self.target: - self.target.analyse_target_declaration(env) - self.body.analyse_declarations(env) - - def analyse_expressions(self, env): - self.function_name = env.qualified_name - if self.pattern: - # normalise/unpack self.pattern into a list - for i, pattern in enumerate(self.pattern): - pattern = pattern.analyse_expressions(env) - self.pattern[i] = pattern.coerce_to_pyobject(env) - - if self.target: - from . import ExprNodes - self.exc_value = ExprNodes.ExcValueNode(self.pos) - self.target = self.target.analyse_target_expression(env, self.exc_value) - - self.body = self.body.analyse_expressions(env) - return self - - def generate_handling_code(self, code, end_label): - code.mark_pos(self.pos) - - if self.pattern: + if code.label_used(try_end_label): + code.put_label(try_end_label) + code.putln("}") + + for cname in exc_save_vars: + code.funcstate.release_temp(cname) + + code.return_label = old_return_label + code.break_label = old_break_label + code.continue_label = old_continue_label + code.error_label = old_error_label + + def generate_function_definitions(self, env, code): + self.body.generate_function_definitions(env, code) + for except_clause in self.except_clauses: + except_clause.generate_function_definitions(env, code) + if self.else_clause is not None: + self.else_clause.generate_function_definitions(env, code) + + def annotate(self, code): + self.body.annotate(code) + for except_node in self.except_clauses: + except_node.annotate(code) + if self.else_clause: + self.else_clause.annotate(code) + + +class ExceptClauseNode(Node): + # Part of try ... except statement. + # + # pattern [ExprNode] + # target ExprNode or None + # body StatNode + # excinfo_target TupleNode(3*ResultRefNode) or None optional target for exception info (not owned here!) + # match_flag string result of exception match + # exc_value ExcValueNode used internally + # function_name string qualified name of enclosing function + # exc_vars (string * 3) local exception variables + # is_except_as bool Py3-style "except ... as xyz" + + # excinfo_target is never set by the parser, but can be set by a transform + # in order to extract more extensive information about the exception as a + # sys.exc_info()-style tuple into a target variable + + child_attrs = ["pattern", "target", "body", "exc_value"] + + exc_value = None + excinfo_target = None + is_except_as = False + + def analyse_declarations(self, env): + if self.target: + self.target.analyse_target_declaration(env) + self.body.analyse_declarations(env) + + def analyse_expressions(self, env): + self.function_name = env.qualified_name + if self.pattern: + # normalise/unpack self.pattern into a list + for i, pattern in enumerate(self.pattern): + pattern = pattern.analyse_expressions(env) + self.pattern[i] = pattern.coerce_to_pyobject(env) + + if self.target: + from . import ExprNodes + self.exc_value = ExprNodes.ExcValueNode(self.pos) + self.target = self.target.analyse_target_expression(env, self.exc_value) + + self.body = self.body.analyse_expressions(env) + return self + + def generate_handling_code(self, code, end_label): + code.mark_pos(self.pos) + + if self.pattern: has_non_literals = not all( pattern.is_literal or pattern.is_simple() and not pattern.is_temp for pattern in self.pattern) @@ -7402,16 +7402,16 @@ class ExceptClauseNode(Node): code.globalstate.use_utility_code(UtilityCode.load_cached("PyErrExceptionMatches", "Exceptions.c")) exc_test_func = "__Pyx_PyErr_ExceptionMatches(%s)" - exc_tests = [] - for pattern in self.pattern: - pattern.generate_evaluation_code(code) + exc_tests = [] + for pattern in self.pattern: + pattern.generate_evaluation_code(code) exc_tests.append(exc_test_func % pattern.py_result()) - + match_flag = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False) code.putln("%s = %s;" % (match_flag, ' || '.join(exc_tests))) - for pattern in self.pattern: - pattern.generate_disposal_code(code) - pattern.free_temps(code) + for pattern in self.pattern: + pattern.generate_disposal_code(code) + pattern.free_temps(code) if has_non_literals: code.putln("__Pyx_ErrRestore(%s, %s, %s);" % tuple(exc_vars)) @@ -7419,247 +7419,247 @@ class ExceptClauseNode(Node): for temp in exc_vars: code.funcstate.release_temp(temp) - code.putln( - "if (%s) {" % - match_flag) - code.funcstate.release_temp(match_flag) - else: - code.putln("/*except:*/ {") - - if (not getattr(self.body, 'stats', True) - and self.excinfo_target is None - and self.target is None): - # most simple case: no exception variable, empty body (pass) - # => reset the exception state, done + code.putln( + "if (%s) {" % + match_flag) + code.funcstate.release_temp(match_flag) + else: + code.putln("/*except:*/ {") + + if (not getattr(self.body, 'stats', True) + and self.excinfo_target is None + and self.target is None): + # most simple case: no exception variable, empty body (pass) + # => reset the exception state, done code.globalstate.use_utility_code(UtilityCode.load_cached("PyErrFetchRestore", "Exceptions.c")) code.putln("__Pyx_ErrRestore(0,0,0);") - code.put_goto(end_label) - code.putln("}") - return - + code.put_goto(end_label) + code.putln("}") + return + exc_vars = [code.funcstate.allocate_temp(py_object_type, manage_ref=True) for _ in range(3)] - code.put_add_traceback(self.function_name) - # We always have to fetch the exception value even if - # there is no target, because this also normalises the - # exception and stores it in the thread state. - code.globalstate.use_utility_code(get_exception_utility_code) - exc_args = "&%s, &%s, &%s" % tuple(exc_vars) + code.put_add_traceback(self.function_name) + # We always have to fetch the exception value even if + # there is no target, because this also normalises the + # exception and stores it in the thread state. + code.globalstate.use_utility_code(get_exception_utility_code) + exc_args = "&%s, &%s, &%s" % tuple(exc_vars) code.putln("if (__Pyx_GetException(%s) < 0) %s" % ( exc_args, code.error_goto(self.pos))) for var in exc_vars: code.put_gotref(var) - if self.target: - self.exc_value.set_var(exc_vars[1]) - self.exc_value.generate_evaluation_code(code) - self.target.generate_assignment_code(self.exc_value, code) - if self.excinfo_target is not None: - for tempvar, node in zip(exc_vars, self.excinfo_target.args): - node.set_var(tempvar) - - old_break_label, old_continue_label = code.break_label, code.continue_label - code.break_label = code.new_label('except_break') - code.continue_label = code.new_label('except_continue') - - old_exc_vars = code.funcstate.exc_vars - code.funcstate.exc_vars = exc_vars - self.body.generate_execution_code(code) - code.funcstate.exc_vars = old_exc_vars - - if not self.body.is_terminator: - for var in exc_vars: + if self.target: + self.exc_value.set_var(exc_vars[1]) + self.exc_value.generate_evaluation_code(code) + self.target.generate_assignment_code(self.exc_value, code) + if self.excinfo_target is not None: + for tempvar, node in zip(exc_vars, self.excinfo_target.args): + node.set_var(tempvar) + + old_break_label, old_continue_label = code.break_label, code.continue_label + code.break_label = code.new_label('except_break') + code.continue_label = code.new_label('except_continue') + + old_exc_vars = code.funcstate.exc_vars + code.funcstate.exc_vars = exc_vars + self.body.generate_execution_code(code) + code.funcstate.exc_vars = old_exc_vars + + if not self.body.is_terminator: + for var in exc_vars: # FIXME: XDECREF() is needed to allow re-raising (which clears the exc_vars), # but I don't think it's the right solution. code.put_xdecref_clear(var, py_object_type) - code.put_goto(end_label) - - for new_label, old_label in [(code.break_label, old_break_label), - (code.continue_label, old_continue_label)]: - if code.label_used(new_label): - code.put_label(new_label) - for var in exc_vars: - code.put_decref_clear(var, py_object_type) - code.put_goto(old_label) - code.break_label = old_break_label - code.continue_label = old_continue_label - - for temp in exc_vars: - code.funcstate.release_temp(temp) - - code.putln( - "}") - - def generate_function_definitions(self, env, code): - if self.target is not None: - self.target.generate_function_definitions(env, code) - self.body.generate_function_definitions(env, code) - - def annotate(self, code): - if self.pattern: - for pattern in self.pattern: - pattern.annotate(code) - if self.target: - self.target.annotate(code) - self.body.annotate(code) - - -class TryFinallyStatNode(StatNode): - # try ... finally statement - # - # body StatNode - # finally_clause StatNode + code.put_goto(end_label) + + for new_label, old_label in [(code.break_label, old_break_label), + (code.continue_label, old_continue_label)]: + if code.label_used(new_label): + code.put_label(new_label) + for var in exc_vars: + code.put_decref_clear(var, py_object_type) + code.put_goto(old_label) + code.break_label = old_break_label + code.continue_label = old_continue_label + + for temp in exc_vars: + code.funcstate.release_temp(temp) + + code.putln( + "}") + + def generate_function_definitions(self, env, code): + if self.target is not None: + self.target.generate_function_definitions(env, code) + self.body.generate_function_definitions(env, code) + + def annotate(self, code): + if self.pattern: + for pattern in self.pattern: + pattern.annotate(code) + if self.target: + self.target.annotate(code) + self.body.annotate(code) + + +class TryFinallyStatNode(StatNode): + # try ... finally statement + # + # body StatNode + # finally_clause StatNode # finally_except_clause deep-copy of finally_clause for exception case # in_generator inside of generator => must store away current exception also in return case - # + # # Each of the continue, break, return and error gotos runs # into its own deep-copy of the finally block code. - # In addition, if we're doing an error, we save the - # exception on entry to the finally block and restore - # it on exit. - + # In addition, if we're doing an error, we save the + # exception on entry to the finally block and restore + # it on exit. + child_attrs = ["body", "finally_clause", "finally_except_clause"] - - preserve_exception = 1 - - # handle exception case, in addition to return/break/continue - handle_error_case = True - func_return_type = None + + preserve_exception = 1 + + # handle exception case, in addition to return/break/continue + handle_error_case = True + func_return_type = None finally_except_clause = None - - is_try_finally_in_nogil = False + + is_try_finally_in_nogil = False in_generator = False - + @staticmethod - def create_analysed(pos, env, body, finally_clause): - node = TryFinallyStatNode(pos, body=body, finally_clause=finally_clause) - return node - - def analyse_declarations(self, env): - self.body.analyse_declarations(env) + def create_analysed(pos, env, body, finally_clause): + node = TryFinallyStatNode(pos, body=body, finally_clause=finally_clause) + return node + + def analyse_declarations(self, env): + self.body.analyse_declarations(env) self.finally_except_clause = copy.deepcopy(self.finally_clause) self.finally_except_clause.analyse_declarations(env) - self.finally_clause.analyse_declarations(env) - - def analyse_expressions(self, env): - self.body = self.body.analyse_expressions(env) - self.finally_clause = self.finally_clause.analyse_expressions(env) + self.finally_clause.analyse_declarations(env) + + def analyse_expressions(self, env): + self.body = self.body.analyse_expressions(env) + self.finally_clause = self.finally_clause.analyse_expressions(env) self.finally_except_clause = self.finally_except_clause.analyse_expressions(env) - if env.return_type and not env.return_type.is_void: - self.func_return_type = env.return_type - return self - - nogil_check = Node.gil_error - gil_message = "Try-finally statement" - - def generate_execution_code(self, code): + if env.return_type and not env.return_type.is_void: + self.func_return_type = env.return_type + return self + + nogil_check = Node.gil_error + gil_message = "Try-finally statement" + + def generate_execution_code(self, code): code.mark_pos(self.pos) # before changing the error label, in case of tracing errors code.putln("/*try:*/ {") - old_error_label = code.error_label - old_labels = code.all_new_labels() - new_labels = code.get_all_labels() - new_error_label = code.error_label - if not self.handle_error_case: - code.error_label = old_error_label - catch_label = code.new_label() - + old_error_label = code.error_label + old_labels = code.all_new_labels() + new_labels = code.get_all_labels() + new_error_label = code.error_label + if not self.handle_error_case: + code.error_label = old_error_label + catch_label = code.new_label() + was_in_try_finally = code.funcstate.in_try_finally code.funcstate.in_try_finally = 1 - - self.body.generate_execution_code(code) - + + self.body.generate_execution_code(code) + code.funcstate.in_try_finally = was_in_try_finally - code.putln("}") - - temps_to_clean_up = code.funcstate.all_free_managed_temps() - code.mark_pos(self.finally_clause.pos) - code.putln("/*finally:*/ {") - + code.putln("}") + + temps_to_clean_up = code.funcstate.all_free_managed_temps() + code.mark_pos(self.finally_clause.pos) + code.putln("/*finally:*/ {") + # Reset labels only after writing out a potential line trace call for correct nogil error handling. code.set_all_labels(old_labels) - def fresh_finally_clause(_next=[self.finally_clause]): - # generate the original subtree once and always keep a fresh copy - node = _next[0] - node_copy = copy.deepcopy(node) - if node is self.finally_clause: - _next[0] = node_copy - else: - node = node_copy - return node - - preserve_error = self.preserve_exception and code.label_used(new_error_label) - needs_success_cleanup = not self.finally_clause.is_terminator - - if not self.body.is_terminator: - code.putln('/*normal exit:*/{') - fresh_finally_clause().generate_execution_code(code) - if not self.finally_clause.is_terminator: - code.put_goto(catch_label) - code.putln('}') - - if preserve_error: + def fresh_finally_clause(_next=[self.finally_clause]): + # generate the original subtree once and always keep a fresh copy + node = _next[0] + node_copy = copy.deepcopy(node) + if node is self.finally_clause: + _next[0] = node_copy + else: + node = node_copy + return node + + preserve_error = self.preserve_exception and code.label_used(new_error_label) + needs_success_cleanup = not self.finally_clause.is_terminator + + if not self.body.is_terminator: + code.putln('/*normal exit:*/{') + fresh_finally_clause().generate_execution_code(code) + if not self.finally_clause.is_terminator: + code.put_goto(catch_label) + code.putln('}') + + if preserve_error: code.put_label(new_error_label) - code.putln('/*exception exit:*/{') + code.putln('/*exception exit:*/{') if not self.in_generator: code.putln("__Pyx_PyThreadState_declare") - if self.is_try_finally_in_nogil: - code.declare_gilstate() - if needs_success_cleanup: - exc_lineno_cnames = tuple([ - code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False) - for _ in range(2)]) - exc_filename_cname = code.funcstate.allocate_temp( - PyrexTypes.CPtrType(PyrexTypes.c_const_type(PyrexTypes.c_char_type)), - manage_ref=False) - else: - exc_lineno_cnames = exc_filename_cname = None - exc_vars = tuple([ - code.funcstate.allocate_temp(py_object_type, manage_ref=False) - for _ in range(6)]) - self.put_error_catcher( - code, temps_to_clean_up, exc_vars, exc_lineno_cnames, exc_filename_cname) - finally_old_labels = code.all_new_labels() - - code.putln('{') - old_exc_vars = code.funcstate.exc_vars - code.funcstate.exc_vars = exc_vars[:3] + if self.is_try_finally_in_nogil: + code.declare_gilstate() + if needs_success_cleanup: + exc_lineno_cnames = tuple([ + code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False) + for _ in range(2)]) + exc_filename_cname = code.funcstate.allocate_temp( + PyrexTypes.CPtrType(PyrexTypes.c_const_type(PyrexTypes.c_char_type)), + manage_ref=False) + else: + exc_lineno_cnames = exc_filename_cname = None + exc_vars = tuple([ + code.funcstate.allocate_temp(py_object_type, manage_ref=False) + for _ in range(6)]) + self.put_error_catcher( + code, temps_to_clean_up, exc_vars, exc_lineno_cnames, exc_filename_cname) + finally_old_labels = code.all_new_labels() + + code.putln('{') + old_exc_vars = code.funcstate.exc_vars + code.funcstate.exc_vars = exc_vars[:3] self.finally_except_clause.generate_execution_code(code) - code.funcstate.exc_vars = old_exc_vars - code.putln('}') - - if needs_success_cleanup: - self.put_error_uncatcher(code, exc_vars, exc_lineno_cnames, exc_filename_cname) - if exc_lineno_cnames: - for cname in exc_lineno_cnames: - code.funcstate.release_temp(cname) - if exc_filename_cname: - code.funcstate.release_temp(exc_filename_cname) - code.put_goto(old_error_label) - - for new_label, old_label in zip(code.get_all_labels(), finally_old_labels): - if not code.label_used(new_label): - continue - code.put_label(new_label) - self.put_error_cleaner(code, exc_vars) - code.put_goto(old_label) - - for cname in exc_vars: - code.funcstate.release_temp(cname) - code.putln('}') - - code.set_all_labels(old_labels) - return_label = code.return_label + code.funcstate.exc_vars = old_exc_vars + code.putln('}') + + if needs_success_cleanup: + self.put_error_uncatcher(code, exc_vars, exc_lineno_cnames, exc_filename_cname) + if exc_lineno_cnames: + for cname in exc_lineno_cnames: + code.funcstate.release_temp(cname) + if exc_filename_cname: + code.funcstate.release_temp(exc_filename_cname) + code.put_goto(old_error_label) + + for new_label, old_label in zip(code.get_all_labels(), finally_old_labels): + if not code.label_used(new_label): + continue + code.put_label(new_label) + self.put_error_cleaner(code, exc_vars) + code.put_goto(old_label) + + for cname in exc_vars: + code.funcstate.release_temp(cname) + code.putln('}') + + code.set_all_labels(old_labels) + return_label = code.return_label exc_vars = () - for i, (new_label, old_label) in enumerate(zip(new_labels, old_labels)): - if not code.label_used(new_label): - continue - if new_label == new_error_label and preserve_error: - continue # handled above - + for i, (new_label, old_label) in enumerate(zip(new_labels, old_labels)): + if not code.label_used(new_label): + continue + if new_label == new_error_label and preserve_error: + continue # handled above + code.putln('%s: {' % new_label) - ret_temp = None + ret_temp = None if old_label == return_label: # return actually raises an (uncatchable) exception in generators that we must preserve if self.in_generator: @@ -7683,7 +7683,7 @@ class TryFinallyStatNode(StatNode): if old_label == return_label: if ret_temp: code.putln("%s = %s;" % (Naming.retval_cname, ret_temp)) - if self.func_return_type.is_pyobject: + if self.func_return_type.is_pyobject: code.putln("%s = 0;" % ret_temp) code.funcstate.release_temp(ret_temp) if self.in_generator: @@ -7691,232 +7691,232 @@ class TryFinallyStatNode(StatNode): for cname in exc_vars: code.funcstate.release_temp(cname) - if not self.finally_clause.is_terminator: - code.put_goto(old_label) - code.putln('}') - - # End finally - code.put_label(catch_label) - code.putln( - "}") - - def generate_function_definitions(self, env, code): - self.body.generate_function_definitions(env, code) - self.finally_clause.generate_function_definitions(env, code) - - def put_error_catcher(self, code, temps_to_clean_up, exc_vars, + if not self.finally_clause.is_terminator: + code.put_goto(old_label) + code.putln('}') + + # End finally + code.put_label(catch_label) + code.putln( + "}") + + def generate_function_definitions(self, env, code): + self.body.generate_function_definitions(env, code) + self.finally_clause.generate_function_definitions(env, code) + + def put_error_catcher(self, code, temps_to_clean_up, exc_vars, exc_lineno_cnames=None, exc_filename_cname=None): - code.globalstate.use_utility_code(restore_exception_utility_code) - code.globalstate.use_utility_code(get_exception_utility_code) - code.globalstate.use_utility_code(swap_exception_utility_code) - - if self.is_try_finally_in_nogil: - code.put_ensure_gil(declare_gilstate=False) + code.globalstate.use_utility_code(restore_exception_utility_code) + code.globalstate.use_utility_code(get_exception_utility_code) + code.globalstate.use_utility_code(swap_exception_utility_code) + + if self.is_try_finally_in_nogil: + code.put_ensure_gil(declare_gilstate=False) code.putln("__Pyx_PyThreadState_assign") - + code.putln(' '.join(["%s = 0;" % var for var in exc_vars])) - for temp_name, type in temps_to_clean_up: - code.put_xdecref_clear(temp_name, type) - - # not using preprocessor here to avoid warnings about - # unused utility functions and/or temps - code.putln("if (PY_MAJOR_VERSION >= 3)" - " __Pyx_ExceptionSwap(&%s, &%s, &%s);" % exc_vars[3:]) - code.putln("if ((PY_MAJOR_VERSION < 3) ||" - # if __Pyx_GetException() fails in Py3, - # store the newly raised exception instead - " unlikely(__Pyx_GetException(&%s, &%s, &%s) < 0)) " - "__Pyx_ErrFetch(&%s, &%s, &%s);" % (exc_vars[:3] * 2)) - for var in exc_vars: - code.put_xgotref(var) - if exc_lineno_cnames: - code.putln("%s = %s; %s = %s; %s = %s;" % ( - exc_lineno_cnames[0], Naming.lineno_cname, - exc_lineno_cnames[1], Naming.clineno_cname, - exc_filename_cname, Naming.filename_cname)) - - if self.is_try_finally_in_nogil: - code.put_release_ensured_gil() - + for temp_name, type in temps_to_clean_up: + code.put_xdecref_clear(temp_name, type) + + # not using preprocessor here to avoid warnings about + # unused utility functions and/or temps + code.putln("if (PY_MAJOR_VERSION >= 3)" + " __Pyx_ExceptionSwap(&%s, &%s, &%s);" % exc_vars[3:]) + code.putln("if ((PY_MAJOR_VERSION < 3) ||" + # if __Pyx_GetException() fails in Py3, + # store the newly raised exception instead + " unlikely(__Pyx_GetException(&%s, &%s, &%s) < 0)) " + "__Pyx_ErrFetch(&%s, &%s, &%s);" % (exc_vars[:3] * 2)) + for var in exc_vars: + code.put_xgotref(var) + if exc_lineno_cnames: + code.putln("%s = %s; %s = %s; %s = %s;" % ( + exc_lineno_cnames[0], Naming.lineno_cname, + exc_lineno_cnames[1], Naming.clineno_cname, + exc_filename_cname, Naming.filename_cname)) + + if self.is_try_finally_in_nogil: + code.put_release_ensured_gil() + def put_error_uncatcher(self, code, exc_vars, exc_lineno_cnames=None, exc_filename_cname=None): - code.globalstate.use_utility_code(restore_exception_utility_code) - code.globalstate.use_utility_code(reset_exception_utility_code) - - if self.is_try_finally_in_nogil: - code.put_ensure_gil(declare_gilstate=False) - - # not using preprocessor here to avoid warnings about - # unused utility functions and/or temps - code.putln("if (PY_MAJOR_VERSION >= 3) {") - for var in exc_vars[3:]: - code.put_xgiveref(var) - code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) - code.putln("}") - for var in exc_vars[:3]: - code.put_xgiveref(var) - code.putln("__Pyx_ErrRestore(%s, %s, %s);" % exc_vars[:3]) - - if self.is_try_finally_in_nogil: - code.put_release_ensured_gil() - + code.globalstate.use_utility_code(restore_exception_utility_code) + code.globalstate.use_utility_code(reset_exception_utility_code) + + if self.is_try_finally_in_nogil: + code.put_ensure_gil(declare_gilstate=False) + + # not using preprocessor here to avoid warnings about + # unused utility functions and/or temps + code.putln("if (PY_MAJOR_VERSION >= 3) {") + for var in exc_vars[3:]: + code.put_xgiveref(var) + code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) + code.putln("}") + for var in exc_vars[:3]: + code.put_xgiveref(var) + code.putln("__Pyx_ErrRestore(%s, %s, %s);" % exc_vars[:3]) + + if self.is_try_finally_in_nogil: + code.put_release_ensured_gil() + code.putln(' '.join(["%s = 0;" % var for var in exc_vars])) - if exc_lineno_cnames: - code.putln("%s = %s; %s = %s; %s = %s;" % ( - Naming.lineno_cname, exc_lineno_cnames[0], - Naming.clineno_cname, exc_lineno_cnames[1], - Naming.filename_cname, exc_filename_cname)) - - def put_error_cleaner(self, code, exc_vars): - code.globalstate.use_utility_code(reset_exception_utility_code) - if self.is_try_finally_in_nogil: - code.put_ensure_gil(declare_gilstate=False) - - # not using preprocessor here to avoid warnings about - # unused utility functions and/or temps - code.putln("if (PY_MAJOR_VERSION >= 3) {") - for var in exc_vars[3:]: - code.put_xgiveref(var) - code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) - code.putln("}") - for var in exc_vars[:3]: - code.put_xdecref_clear(var, py_object_type) - if self.is_try_finally_in_nogil: - code.put_release_ensured_gil() - code.putln(' '.join(["%s = 0;"]*3) % exc_vars[3:]) - - def annotate(self, code): - self.body.annotate(code) - self.finally_clause.annotate(code) - - -class NogilTryFinallyStatNode(TryFinallyStatNode): - """ - A try/finally statement that may be used in nogil code sections. - """ - - preserve_exception = False - nogil_check = None - - -class GILStatNode(NogilTryFinallyStatNode): - # 'with gil' or 'with nogil' statement - # - # state string 'gil' or 'nogil' - - state_temp = None - - def __init__(self, pos, state, body): - self.state = state - self.create_state_temp_if_needed(pos, state, body) + if exc_lineno_cnames: + code.putln("%s = %s; %s = %s; %s = %s;" % ( + Naming.lineno_cname, exc_lineno_cnames[0], + Naming.clineno_cname, exc_lineno_cnames[1], + Naming.filename_cname, exc_filename_cname)) + + def put_error_cleaner(self, code, exc_vars): + code.globalstate.use_utility_code(reset_exception_utility_code) + if self.is_try_finally_in_nogil: + code.put_ensure_gil(declare_gilstate=False) + + # not using preprocessor here to avoid warnings about + # unused utility functions and/or temps + code.putln("if (PY_MAJOR_VERSION >= 3) {") + for var in exc_vars[3:]: + code.put_xgiveref(var) + code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) + code.putln("}") + for var in exc_vars[:3]: + code.put_xdecref_clear(var, py_object_type) + if self.is_try_finally_in_nogil: + code.put_release_ensured_gil() + code.putln(' '.join(["%s = 0;"]*3) % exc_vars[3:]) + + def annotate(self, code): + self.body.annotate(code) + self.finally_clause.annotate(code) + + +class NogilTryFinallyStatNode(TryFinallyStatNode): + """ + A try/finally statement that may be used in nogil code sections. + """ + + preserve_exception = False + nogil_check = None + + +class GILStatNode(NogilTryFinallyStatNode): + # 'with gil' or 'with nogil' statement + # + # state string 'gil' or 'nogil' + + state_temp = None + + def __init__(self, pos, state, body): + self.state = state + self.create_state_temp_if_needed(pos, state, body) TryFinallyStatNode.__init__( self, pos, - body=body, - finally_clause=GILExitNode( - pos, state=state, state_temp=self.state_temp)) - - def create_state_temp_if_needed(self, pos, state, body): - from .ParseTreeTransforms import YieldNodeCollector - collector = YieldNodeCollector() - collector.visitchildren(body) + body=body, + finally_clause=GILExitNode( + pos, state=state, state_temp=self.state_temp)) + + def create_state_temp_if_needed(self, pos, state, body): + from .ParseTreeTransforms import YieldNodeCollector + collector = YieldNodeCollector() + collector.visitchildren(body) if not collector.yields: - return - - if state == 'gil': - temp_type = PyrexTypes.c_gilstate_type - else: - temp_type = PyrexTypes.c_threadstate_ptr_type - from . import ExprNodes - self.state_temp = ExprNodes.TempNode(pos, temp_type) - - def analyse_declarations(self, env): - env._in_with_gil_block = (self.state == 'gil') - if self.state == 'gil': - env.has_with_gil_block = True - - return super(GILStatNode, self).analyse_declarations(env) - - def analyse_expressions(self, env): - env.use_utility_code( - UtilityCode.load_cached("ForceInitThreads", "ModuleSetupCode.c")) - was_nogil = env.nogil - env.nogil = self.state == 'nogil' - node = TryFinallyStatNode.analyse_expressions(self, env) - env.nogil = was_nogil - return node - - def generate_execution_code(self, code): - code.mark_pos(self.pos) - code.begin_block() - if self.state_temp: - self.state_temp.allocate(code) - variable = self.state_temp.result() - else: - variable = None - + return + + if state == 'gil': + temp_type = PyrexTypes.c_gilstate_type + else: + temp_type = PyrexTypes.c_threadstate_ptr_type + from . import ExprNodes + self.state_temp = ExprNodes.TempNode(pos, temp_type) + + def analyse_declarations(self, env): + env._in_with_gil_block = (self.state == 'gil') + if self.state == 'gil': + env.has_with_gil_block = True + + return super(GILStatNode, self).analyse_declarations(env) + + def analyse_expressions(self, env): + env.use_utility_code( + UtilityCode.load_cached("ForceInitThreads", "ModuleSetupCode.c")) + was_nogil = env.nogil + env.nogil = self.state == 'nogil' + node = TryFinallyStatNode.analyse_expressions(self, env) + env.nogil = was_nogil + return node + + def generate_execution_code(self, code): + code.mark_pos(self.pos) + code.begin_block() + if self.state_temp: + self.state_temp.allocate(code) + variable = self.state_temp.result() + else: + variable = None + old_gil_config = code.funcstate.gil_owned - if self.state == 'gil': - code.put_ensure_gil(variable=variable) + if self.state == 'gil': + code.put_ensure_gil(variable=variable) code.funcstate.gil_owned = True - else: - code.put_release_gil(variable=variable) + else: + code.put_release_gil(variable=variable) code.funcstate.gil_owned = False - - TryFinallyStatNode.generate_execution_code(self, code) - - if self.state_temp: - self.state_temp.release(code) - + + TryFinallyStatNode.generate_execution_code(self, code) + + if self.state_temp: + self.state_temp.release(code) + code.funcstate.gil_owned = old_gil_config - code.end_block() - - -class GILExitNode(StatNode): - """ - Used as the 'finally' block in a GILStatNode - - state string 'gil' or 'nogil' - """ - - child_attrs = [] - state_temp = None - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - if self.state_temp: - variable = self.state_temp.result() - else: - variable = None - - if self.state == 'gil': - code.put_release_ensured_gil(variable) - else: - code.put_acquire_gil(variable) - - -class EnsureGILNode(GILExitNode): - """ - Ensure the GIL in nogil functions for cleanup before returning. - """ - - def generate_execution_code(self, code): - code.put_ensure_gil(declare_gilstate=False) - + code.end_block() + + +class GILExitNode(StatNode): + """ + Used as the 'finally' block in a GILStatNode + + state string 'gil' or 'nogil' + """ + + child_attrs = [] + state_temp = None + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + if self.state_temp: + variable = self.state_temp.result() + else: + variable = None + + if self.state == 'gil': + code.put_release_ensured_gil(variable) + else: + code.put_acquire_gil(variable) + + +class EnsureGILNode(GILExitNode): + """ + Ensure the GIL in nogil functions for cleanup before returning. + """ + + def generate_execution_code(self, code): + code.put_ensure_gil(declare_gilstate=False) + def cython_view_utility_code(): from . import MemoryView return MemoryView.view_utility_code -utility_code_for_cimports = { - # utility code (or inlining c) in a pxd (or pyx) file. - # TODO: Consider a generic user-level mechanism for importing +utility_code_for_cimports = { + # utility code (or inlining c) in a pxd (or pyx) file. + # TODO: Consider a generic user-level mechanism for importing 'cpython.array' : lambda : UtilityCode.load_cached("ArrayAPI", "arrayarray.h"), 'cpython.array.array' : lambda : UtilityCode.load_cached("ArrayAPI", "arrayarray.h"), 'cython.view' : cython_view_utility_code, -} - +} + utility_code_for_imports = { # utility code used when special modules are imported. # TODO: Consider a generic user-level mechanism for importing @@ -7925,1522 +7925,1522 @@ utility_code_for_imports = { } -class CImportStatNode(StatNode): - # cimport statement - # - # module_name string Qualified name of module being imported - # as_name string or None Name specified in "as" clause, if any +class CImportStatNode(StatNode): + # cimport statement + # + # module_name string Qualified name of module being imported + # as_name string or None Name specified in "as" clause, if any # is_absolute bool True for absolute imports, False otherwise - - child_attrs = [] + + child_attrs = [] is_absolute = False - - def analyse_declarations(self, env): - if not env.is_module_scope: - error(self.pos, "cimport only allowed at module level") - return + + def analyse_declarations(self, env): + if not env.is_module_scope: + error(self.pos, "cimport only allowed at module level") + return module_scope = env.find_module( self.module_name, self.pos, relative_level=0 if self.is_absolute else -1) - if "." in self.module_name: - names = [EncodedString(name) for name in self.module_name.split(".")] - top_name = names[0] - top_module_scope = env.context.find_submodule(top_name) - module_scope = top_module_scope - for name in names[1:]: - submodule_scope = module_scope.find_submodule(name) - module_scope.declare_module(name, submodule_scope, self.pos) - module_scope = submodule_scope - if self.as_name: - env.declare_module(self.as_name, module_scope, self.pos) - else: - env.add_imported_module(module_scope) - env.declare_module(top_name, top_module_scope, self.pos) - else: - name = self.as_name or self.module_name - env.declare_module(name, module_scope, self.pos) - if self.module_name in utility_code_for_cimports: + if "." in self.module_name: + names = [EncodedString(name) for name in self.module_name.split(".")] + top_name = names[0] + top_module_scope = env.context.find_submodule(top_name) + module_scope = top_module_scope + for name in names[1:]: + submodule_scope = module_scope.find_submodule(name) + module_scope.declare_module(name, submodule_scope, self.pos) + module_scope = submodule_scope + if self.as_name: + env.declare_module(self.as_name, module_scope, self.pos) + else: + env.add_imported_module(module_scope) + env.declare_module(top_name, top_module_scope, self.pos) + else: + name = self.as_name or self.module_name + env.declare_module(name, module_scope, self.pos) + if self.module_name in utility_code_for_cimports: env.use_utility_code(utility_code_for_cimports[self.module_name]()) - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class FromCImportStatNode(StatNode): - # from ... cimport statement - # - # module_name string Qualified name of module - # relative_level int or None Relative import: number of dots before module_name - # imported_names [(pos, name, as_name, kind)] Names to be imported - - child_attrs = [] - module_name = None - relative_level = None - imported_names = None - - def analyse_declarations(self, env): - if not env.is_module_scope: - error(self.pos, "cimport only allowed at module level") + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class FromCImportStatNode(StatNode): + # from ... cimport statement + # + # module_name string Qualified name of module + # relative_level int or None Relative import: number of dots before module_name + # imported_names [(pos, name, as_name, kind)] Names to be imported + + child_attrs = [] + module_name = None + relative_level = None + imported_names = None + + def analyse_declarations(self, env): + if not env.is_module_scope: + error(self.pos, "cimport only allowed at module level") + return + if self.relative_level and self.relative_level > env.qualified_name.count('.'): + error(self.pos, "relative cimport beyond main package is not allowed") return - if self.relative_level and self.relative_level > env.qualified_name.count('.'): - error(self.pos, "relative cimport beyond main package is not allowed") - return - module_scope = env.find_module(self.module_name, self.pos, relative_level=self.relative_level) - module_name = module_scope.qualified_name - env.add_imported_module(module_scope) - for pos, name, as_name, kind in self.imported_names: - if name == "*": + module_scope = env.find_module(self.module_name, self.pos, relative_level=self.relative_level) + module_name = module_scope.qualified_name + env.add_imported_module(module_scope) + for pos, name, as_name, kind in self.imported_names: + if name == "*": for local_name, entry in list(module_scope.entries.items()): - env.add_imported_entry(local_name, entry, pos) - else: - entry = module_scope.lookup(name) - if entry: - if kind and not self.declaration_matches(entry, kind): - entry.redeclared(pos) - entry.used = 1 - else: - if kind == 'struct' or kind == 'union': - entry = module_scope.declare_struct_or_union( - name, kind=kind, scope=None, typedef_flag=0, pos=pos) - elif kind == 'class': - entry = module_scope.declare_c_class(name, pos=pos, module_name=module_name) - else: + env.add_imported_entry(local_name, entry, pos) + else: + entry = module_scope.lookup(name) + if entry: + if kind and not self.declaration_matches(entry, kind): + entry.redeclared(pos) + entry.used = 1 + else: + if kind == 'struct' or kind == 'union': + entry = module_scope.declare_struct_or_union( + name, kind=kind, scope=None, typedef_flag=0, pos=pos) + elif kind == 'class': + entry = module_scope.declare_c_class(name, pos=pos, module_name=module_name) + else: submodule_scope = env.context.find_module( name, relative_to=module_scope, pos=self.pos, absolute_fallback=False) - if submodule_scope.parent_module is module_scope: - env.declare_module(as_name or name, submodule_scope, self.pos) - else: - error(pos, "Name '%s' not declared in module '%s'" % (name, module_name)) - - if entry: - local_name = as_name or name - env.add_imported_entry(local_name, entry, pos) - + if submodule_scope.parent_module is module_scope: + env.declare_module(as_name or name, submodule_scope, self.pos) + else: + error(pos, "Name '%s' not declared in module '%s'" % (name, module_name)) + + if entry: + local_name = as_name or name + env.add_imported_entry(local_name, entry, pos) + if module_name.startswith('cpython') or module_name.startswith('cython'): # enough for now - if module_name in utility_code_for_cimports: + if module_name in utility_code_for_cimports: env.use_utility_code(utility_code_for_cimports[module_name]()) - for _, name, _, _ in self.imported_names: - fqname = '%s.%s' % (module_name, name) - if fqname in utility_code_for_cimports: + for _, name, _, _ in self.imported_names: + fqname = '%s.%s' % (module_name, name) + if fqname in utility_code_for_cimports: env.use_utility_code(utility_code_for_cimports[fqname]()) - - def declaration_matches(self, entry, kind): - if not entry.is_type: - return 0 - type = entry.type - if kind == 'class': - if not type.is_extension_type: - return 0 - else: - if not type.is_struct_or_union: - return 0 - if kind != type.kind: - return 0 - return 1 - - def analyse_expressions(self, env): - return self - - def generate_execution_code(self, code): - pass - - -class FromImportStatNode(StatNode): - # from ... import statement - # - # module ImportNode - # items [(string, NameNode)] - # interned_items [(string, NameNode, ExprNode)] - # item PyTempNode used internally - # import_star boolean used internally - - child_attrs = ["module"] - import_star = 0 - - def analyse_declarations(self, env): - for name, target in self.items: - if name == "*": - if not env.is_module_scope: - error(self.pos, "import * only allowed at module level") - return - env.has_import_star = 1 - self.import_star = 1 - else: - target.analyse_target_declaration(env) - - def analyse_expressions(self, env): - from . import ExprNodes - self.module = self.module.analyse_expressions(env) - self.item = ExprNodes.RawCNameExprNode(self.pos, py_object_type) - self.interned_items = [] - for name, target in self.items: - if name == '*': - for _, entry in env.entries.items(): - if not entry.is_type and entry.type.is_extension_type: - env.use_utility_code(UtilityCode.load_cached("ExtTypeTest", "ObjectHandling.c")) - break - else: - entry = env.lookup(target.name) - # check whether or not entry is already cimported - if (entry.is_type and entry.type.name == name - and hasattr(entry.type, 'module_name')): - if entry.type.module_name == self.module.module_name.value: - # cimported with absolute name - continue - try: - # cimported with relative name - module = env.find_module(self.module.module_name.value, pos=self.pos, - relative_level=self.module.level) - if entry.type.module_name == module.qualified_name: - continue - except AttributeError: - pass - target = target.analyse_target_expression(env, None) # FIXME? - if target.type is py_object_type: - coerced_item = None - else: - coerced_item = self.item.coerce_to(target.type, env) - self.interned_items.append((name, target, coerced_item)) - return self - - def generate_execution_code(self, code): + + def declaration_matches(self, entry, kind): + if not entry.is_type: + return 0 + type = entry.type + if kind == 'class': + if not type.is_extension_type: + return 0 + else: + if not type.is_struct_or_union: + return 0 + if kind != type.kind: + return 0 + return 1 + + def analyse_expressions(self, env): + return self + + def generate_execution_code(self, code): + pass + + +class FromImportStatNode(StatNode): + # from ... import statement + # + # module ImportNode + # items [(string, NameNode)] + # interned_items [(string, NameNode, ExprNode)] + # item PyTempNode used internally + # import_star boolean used internally + + child_attrs = ["module"] + import_star = 0 + + def analyse_declarations(self, env): + for name, target in self.items: + if name == "*": + if not env.is_module_scope: + error(self.pos, "import * only allowed at module level") + return + env.has_import_star = 1 + self.import_star = 1 + else: + target.analyse_target_declaration(env) + + def analyse_expressions(self, env): + from . import ExprNodes + self.module = self.module.analyse_expressions(env) + self.item = ExprNodes.RawCNameExprNode(self.pos, py_object_type) + self.interned_items = [] + for name, target in self.items: + if name == '*': + for _, entry in env.entries.items(): + if not entry.is_type and entry.type.is_extension_type: + env.use_utility_code(UtilityCode.load_cached("ExtTypeTest", "ObjectHandling.c")) + break + else: + entry = env.lookup(target.name) + # check whether or not entry is already cimported + if (entry.is_type and entry.type.name == name + and hasattr(entry.type, 'module_name')): + if entry.type.module_name == self.module.module_name.value: + # cimported with absolute name + continue + try: + # cimported with relative name + module = env.find_module(self.module.module_name.value, pos=self.pos, + relative_level=self.module.level) + if entry.type.module_name == module.qualified_name: + continue + except AttributeError: + pass + target = target.analyse_target_expression(env, None) # FIXME? + if target.type is py_object_type: + coerced_item = None + else: + coerced_item = self.item.coerce_to(target.type, env) + self.interned_items.append((name, target, coerced_item)) + return self + + def generate_execution_code(self, code): code.mark_pos(self.pos) - self.module.generate_evaluation_code(code) - if self.import_star: - code.putln( - 'if (%s(%s) < 0) %s;' % ( - Naming.import_star, - self.module.py_result(), - code.error_goto(self.pos))) - item_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) - self.item.set_cname(item_temp) - if self.interned_items: - code.globalstate.use_utility_code( - UtilityCode.load_cached("ImportFrom", "ImportExport.c")) - for name, target, coerced_item in self.interned_items: - code.putln( - '%s = __Pyx_ImportFrom(%s, %s); %s' % ( - item_temp, - self.module.py_result(), - code.intern_identifier(name), - code.error_goto_if_null(item_temp, self.pos))) - code.put_gotref(item_temp) - if coerced_item is None: - target.generate_assignment_code(self.item, code) - else: - coerced_item.allocate_temp_result(code) - coerced_item.generate_result_code(code) - target.generate_assignment_code(coerced_item, code) - code.put_decref_clear(item_temp, py_object_type) - code.funcstate.release_temp(item_temp) - self.module.generate_disposal_code(code) - self.module.free_temps(code) - - -class ParallelNode(Node): - """ - Base class for cython.parallel constructs. - """ - - nogil_check = None - - -class ParallelStatNode(StatNode, ParallelNode): - """ - Base class for 'with cython.parallel.parallel():' and 'for i in prange():'. - - assignments { Entry(var) : (var.pos, inplace_operator_or_None) } - assignments to variables in this parallel section - - parent parent ParallelStatNode or None - is_parallel indicates whether this node is OpenMP parallel - (true for #pragma omp parallel for and - #pragma omp parallel) - - is_parallel is true for: - - #pragma omp parallel - #pragma omp parallel for - - sections, but NOT for - - #pragma omp for - - We need this to determine the sharing attributes. - - privatization_insertion_point a code insertion point used to make temps - private (esp. the "nsteps" temp) - - args tuple the arguments passed to the parallel construct - kwargs DictNode the keyword arguments passed to the parallel - construct (replaced by its compile time value) - """ - - child_attrs = ['body', 'num_threads'] - - body = None - - is_prange = False - is_nested_prange = False - - error_label_used = False - - num_threads = None - chunksize = None - - parallel_exc = ( - Naming.parallel_exc_type, - Naming.parallel_exc_value, - Naming.parallel_exc_tb, - ) - - parallel_pos_info = ( - Naming.parallel_filename, - Naming.parallel_lineno, - Naming.parallel_clineno, - ) - - pos_info = ( - Naming.filename_cname, - Naming.lineno_cname, - Naming.clineno_cname, - ) - - critical_section_counter = 0 - - def __init__(self, pos, **kwargs): - super(ParallelStatNode, self).__init__(pos, **kwargs) - - # All assignments in this scope - self.assignments = kwargs.get('assignments') or {} - - # All seen closure cnames and their temporary cnames - self.seen_closure_vars = set() - - # Dict of variables that should be declared (first|last|)private or - # reduction { Entry: (op, lastprivate) }. - # If op is not None, it's a reduction. - self.privates = {} - - # [NameNode] - self.assigned_nodes = [] - - def analyse_declarations(self, env): - self.body.analyse_declarations(env) - - self.num_threads = None - - if self.kwargs: - # Try to find num_threads and chunksize keyword arguments - pairs = [] + self.module.generate_evaluation_code(code) + if self.import_star: + code.putln( + 'if (%s(%s) < 0) %s;' % ( + Naming.import_star, + self.module.py_result(), + code.error_goto(self.pos))) + item_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) + self.item.set_cname(item_temp) + if self.interned_items: + code.globalstate.use_utility_code( + UtilityCode.load_cached("ImportFrom", "ImportExport.c")) + for name, target, coerced_item in self.interned_items: + code.putln( + '%s = __Pyx_ImportFrom(%s, %s); %s' % ( + item_temp, + self.module.py_result(), + code.intern_identifier(name), + code.error_goto_if_null(item_temp, self.pos))) + code.put_gotref(item_temp) + if coerced_item is None: + target.generate_assignment_code(self.item, code) + else: + coerced_item.allocate_temp_result(code) + coerced_item.generate_result_code(code) + target.generate_assignment_code(coerced_item, code) + code.put_decref_clear(item_temp, py_object_type) + code.funcstate.release_temp(item_temp) + self.module.generate_disposal_code(code) + self.module.free_temps(code) + + +class ParallelNode(Node): + """ + Base class for cython.parallel constructs. + """ + + nogil_check = None + + +class ParallelStatNode(StatNode, ParallelNode): + """ + Base class for 'with cython.parallel.parallel():' and 'for i in prange():'. + + assignments { Entry(var) : (var.pos, inplace_operator_or_None) } + assignments to variables in this parallel section + + parent parent ParallelStatNode or None + is_parallel indicates whether this node is OpenMP parallel + (true for #pragma omp parallel for and + #pragma omp parallel) + + is_parallel is true for: + + #pragma omp parallel + #pragma omp parallel for + + sections, but NOT for + + #pragma omp for + + We need this to determine the sharing attributes. + + privatization_insertion_point a code insertion point used to make temps + private (esp. the "nsteps" temp) + + args tuple the arguments passed to the parallel construct + kwargs DictNode the keyword arguments passed to the parallel + construct (replaced by its compile time value) + """ + + child_attrs = ['body', 'num_threads'] + + body = None + + is_prange = False + is_nested_prange = False + + error_label_used = False + + num_threads = None + chunksize = None + + parallel_exc = ( + Naming.parallel_exc_type, + Naming.parallel_exc_value, + Naming.parallel_exc_tb, + ) + + parallel_pos_info = ( + Naming.parallel_filename, + Naming.parallel_lineno, + Naming.parallel_clineno, + ) + + pos_info = ( + Naming.filename_cname, + Naming.lineno_cname, + Naming.clineno_cname, + ) + + critical_section_counter = 0 + + def __init__(self, pos, **kwargs): + super(ParallelStatNode, self).__init__(pos, **kwargs) + + # All assignments in this scope + self.assignments = kwargs.get('assignments') or {} + + # All seen closure cnames and their temporary cnames + self.seen_closure_vars = set() + + # Dict of variables that should be declared (first|last|)private or + # reduction { Entry: (op, lastprivate) }. + # If op is not None, it's a reduction. + self.privates = {} + + # [NameNode] + self.assigned_nodes = [] + + def analyse_declarations(self, env): + self.body.analyse_declarations(env) + + self.num_threads = None + + if self.kwargs: + # Try to find num_threads and chunksize keyword arguments + pairs = [] seen = set() - for dictitem in self.kwargs.key_value_pairs: + for dictitem in self.kwargs.key_value_pairs: if dictitem.key.value in seen: error(self.pos, "Duplicate keyword argument found: %s" % dictitem.key.value) seen.add(dictitem.key.value) - if dictitem.key.value == 'num_threads': + if dictitem.key.value == 'num_threads': if not dictitem.value.is_none: self.num_threads = dictitem.value - elif self.is_prange and dictitem.key.value == 'chunksize': + elif self.is_prange and dictitem.key.value == 'chunksize': if not dictitem.value.is_none: self.chunksize = dictitem.value - else: - pairs.append(dictitem) - - self.kwargs.key_value_pairs = pairs - - try: - self.kwargs = self.kwargs.compile_time_value(env) + else: + pairs.append(dictitem) + + self.kwargs.key_value_pairs = pairs + + try: + self.kwargs = self.kwargs.compile_time_value(env) except Exception as e: - error(self.kwargs.pos, "Only compile-time values may be " - "supplied as keyword arguments") - else: - self.kwargs = {} - + error(self.kwargs.pos, "Only compile-time values may be " + "supplied as keyword arguments") + else: + self.kwargs = {} + for kw, val in self.kwargs.items(): - if kw not in self.valid_keyword_arguments: - error(self.pos, "Invalid keyword argument: %s" % kw) - else: - setattr(self, kw, val) - - def analyse_expressions(self, env): - if self.num_threads: - self.num_threads = self.num_threads.analyse_expressions(env) - - if self.chunksize: - self.chunksize = self.chunksize.analyse_expressions(env) - - self.body = self.body.analyse_expressions(env) - self.analyse_sharing_attributes(env) - - if self.num_threads is not None: + if kw not in self.valid_keyword_arguments: + error(self.pos, "Invalid keyword argument: %s" % kw) + else: + setattr(self, kw, val) + + def analyse_expressions(self, env): + if self.num_threads: + self.num_threads = self.num_threads.analyse_expressions(env) + + if self.chunksize: + self.chunksize = self.chunksize.analyse_expressions(env) + + self.body = self.body.analyse_expressions(env) + self.analyse_sharing_attributes(env) + + if self.num_threads is not None: if self.parent and self.parent.num_threads is not None and not self.parent.is_prange: error(self.pos, "num_threads already declared in outer section") - elif self.parent and not self.parent.is_prange: + elif self.parent and not self.parent.is_prange: error(self.pos, "num_threads must be declared in the parent parallel section") - elif (self.num_threads.type.is_int and + elif (self.num_threads.type.is_int and self.num_threads.is_literal and self.num_threads.compile_time_value(env) <= 0): error(self.pos, "argument to num_threads must be greater than 0") - + if not self.num_threads.is_simple() or self.num_threads.type.is_pyobject: - self.num_threads = self.num_threads.coerce_to( - PyrexTypes.c_int_type, env).coerce_to_temp(env) - return self - - def analyse_sharing_attributes(self, env): - """ - Analyse the privates for this block and set them in self.privates. - This should be called in a post-order fashion during the - analyse_expressions phase - """ + self.num_threads = self.num_threads.coerce_to( + PyrexTypes.c_int_type, env).coerce_to_temp(env) + return self + + def analyse_sharing_attributes(self, env): + """ + Analyse the privates for this block and set them in self.privates. + This should be called in a post-order fashion during the + analyse_expressions phase + """ for entry, (pos, op) in self.assignments.items(): - - if self.is_prange and not self.is_parallel: - # closely nested prange in a with parallel block, disallow - # assigning to privates in the with parallel block (we - # consider it too implicit and magicky for users) - if entry in self.parent.assignments: + + if self.is_prange and not self.is_parallel: + # closely nested prange in a with parallel block, disallow + # assigning to privates in the with parallel block (we + # consider it too implicit and magicky for users) + if entry in self.parent.assignments: error(pos, "Cannot assign to private of outer parallel block") - continue - - if not self.is_prange and op: - # Again possible, but considered to magicky - error(pos, "Reductions not allowed for parallel blocks") - continue - - # By default all variables should have the same values as if - # executed sequentially - lastprivate = True - self.propagate_var_privatization(entry, pos, op, lastprivate) - - def propagate_var_privatization(self, entry, pos, op, lastprivate): - """ - Propagate the sharing attributes of a variable. If the privatization is - determined by a parent scope, done propagate further. - - If we are a prange, we propagate our sharing attributes outwards to - other pranges. If we are a prange in parallel block and the parallel - block does not determine the variable private, we propagate to the - parent of the parent. Recursion stops at parallel blocks, as they have - no concept of lastprivate or reduction. - - So the following cases propagate: - - sum is a reduction for all loops: - - for i in prange(n): - for j in prange(n): - for k in prange(n): - sum += i * j * k - - sum is a reduction for both loops, local_var is private to the - parallel with block: - - for i in prange(n): - with parallel: - local_var = ... # private to the parallel - for j in prange(n): - sum += i * j - - Nested with parallel blocks are disallowed, because they wouldn't - allow you to propagate lastprivates or reductions: - - #pragma omp parallel for lastprivate(i) - for i in prange(n): - - sum = 0 - - #pragma omp parallel private(j, sum) - with parallel: - - #pragma omp parallel - with parallel: - - #pragma omp for lastprivate(j) reduction(+:sum) - for j in prange(n): - sum += i - - # sum and j are well-defined here - - # sum and j are undefined here - - # sum and j are undefined here - """ - self.privates[entry] = (op, lastprivate) - - if entry.type.is_memoryviewslice: - error(pos, "Memoryview slices can only be shared in parallel sections") - return - - if self.is_prange: - if not self.is_parallel and entry not in self.parent.assignments: - # Parent is a parallel with block - parent = self.parent.parent - else: - parent = self.parent - - # We don't need to propagate privates, only reductions and - # lastprivates - if parent and (op or lastprivate): - parent.propagate_var_privatization(entry, pos, op, lastprivate) - - def _allocate_closure_temp(self, code, entry): - """ - Helper function that allocate a temporary for a closure variable that - is assigned to. - """ - if self.parent: - return self.parent._allocate_closure_temp(code, entry) - - if entry.cname in self.seen_closure_vars: - return entry.cname - - cname = code.funcstate.allocate_temp(entry.type, True) - - # Add both the actual cname and the temp cname, as the actual cname - # will be replaced with the temp cname on the entry - self.seen_closure_vars.add(entry.cname) - self.seen_closure_vars.add(cname) - - self.modified_entries.append((entry, entry.cname)) - code.putln("%s = %s;" % (cname, entry.cname)) - entry.cname = cname - - def initialize_privates_to_nan(self, code, exclude=None): - first = True - + continue + + if not self.is_prange and op: + # Again possible, but considered to magicky + error(pos, "Reductions not allowed for parallel blocks") + continue + + # By default all variables should have the same values as if + # executed sequentially + lastprivate = True + self.propagate_var_privatization(entry, pos, op, lastprivate) + + def propagate_var_privatization(self, entry, pos, op, lastprivate): + """ + Propagate the sharing attributes of a variable. If the privatization is + determined by a parent scope, done propagate further. + + If we are a prange, we propagate our sharing attributes outwards to + other pranges. If we are a prange in parallel block and the parallel + block does not determine the variable private, we propagate to the + parent of the parent. Recursion stops at parallel blocks, as they have + no concept of lastprivate or reduction. + + So the following cases propagate: + + sum is a reduction for all loops: + + for i in prange(n): + for j in prange(n): + for k in prange(n): + sum += i * j * k + + sum is a reduction for both loops, local_var is private to the + parallel with block: + + for i in prange(n): + with parallel: + local_var = ... # private to the parallel + for j in prange(n): + sum += i * j + + Nested with parallel blocks are disallowed, because they wouldn't + allow you to propagate lastprivates or reductions: + + #pragma omp parallel for lastprivate(i) + for i in prange(n): + + sum = 0 + + #pragma omp parallel private(j, sum) + with parallel: + + #pragma omp parallel + with parallel: + + #pragma omp for lastprivate(j) reduction(+:sum) + for j in prange(n): + sum += i + + # sum and j are well-defined here + + # sum and j are undefined here + + # sum and j are undefined here + """ + self.privates[entry] = (op, lastprivate) + + if entry.type.is_memoryviewslice: + error(pos, "Memoryview slices can only be shared in parallel sections") + return + + if self.is_prange: + if not self.is_parallel and entry not in self.parent.assignments: + # Parent is a parallel with block + parent = self.parent.parent + else: + parent = self.parent + + # We don't need to propagate privates, only reductions and + # lastprivates + if parent and (op or lastprivate): + parent.propagate_var_privatization(entry, pos, op, lastprivate) + + def _allocate_closure_temp(self, code, entry): + """ + Helper function that allocate a temporary for a closure variable that + is assigned to. + """ + if self.parent: + return self.parent._allocate_closure_temp(code, entry) + + if entry.cname in self.seen_closure_vars: + return entry.cname + + cname = code.funcstate.allocate_temp(entry.type, True) + + # Add both the actual cname and the temp cname, as the actual cname + # will be replaced with the temp cname on the entry + self.seen_closure_vars.add(entry.cname) + self.seen_closure_vars.add(cname) + + self.modified_entries.append((entry, entry.cname)) + code.putln("%s = %s;" % (cname, entry.cname)) + entry.cname = cname + + def initialize_privates_to_nan(self, code, exclude=None): + first = True + for entry, (op, lastprivate) in sorted(self.privates.items()): - if not op and (not exclude or entry != exclude): - invalid_value = entry.type.invalid_value() - - if invalid_value: - if first: - code.putln("/* Initialize private variables to " - "invalid values */") - first = False - code.putln("%s = %s;" % (entry.cname, - entry.type.cast_code(invalid_value))) - - def evaluate_before_block(self, code, expr): - c = self.begin_of_parallel_control_block_point_after_decls - # we need to set the owner to ourselves temporarily, as - # allocate_temp may generate a comment in the middle of our pragma - # otherwise when DebugFlags.debug_temp_code_comments is in effect - owner = c.funcstate.owner - c.funcstate.owner = c - expr.generate_evaluation_code(c) - c.funcstate.owner = owner - - return expr.result() - - def put_num_threads(self, code): - """ - Write self.num_threads if set as the num_threads OpenMP directive - """ - if self.num_threads is not None: + if not op and (not exclude or entry != exclude): + invalid_value = entry.type.invalid_value() + + if invalid_value: + if first: + code.putln("/* Initialize private variables to " + "invalid values */") + first = False + code.putln("%s = %s;" % (entry.cname, + entry.type.cast_code(invalid_value))) + + def evaluate_before_block(self, code, expr): + c = self.begin_of_parallel_control_block_point_after_decls + # we need to set the owner to ourselves temporarily, as + # allocate_temp may generate a comment in the middle of our pragma + # otherwise when DebugFlags.debug_temp_code_comments is in effect + owner = c.funcstate.owner + c.funcstate.owner = c + expr.generate_evaluation_code(c) + c.funcstate.owner = owner + + return expr.result() + + def put_num_threads(self, code): + """ + Write self.num_threads if set as the num_threads OpenMP directive + """ + if self.num_threads is not None: code.put(" num_threads(%s)" % self.evaluate_before_block(code, self.num_threads)) - - - def declare_closure_privates(self, code): - """ - If a variable is in a scope object, we need to allocate a temp and - assign the value from the temp to the variable in the scope object - after the parallel section. This kind of copying should be done only - in the outermost parallel section. - """ - self.modified_entries = [] - + + + def declare_closure_privates(self, code): + """ + If a variable is in a scope object, we need to allocate a temp and + assign the value from the temp to the variable in the scope object + after the parallel section. This kind of copying should be done only + in the outermost parallel section. + """ + self.modified_entries = [] + for entry in sorted(self.assignments): - if entry.from_closure or entry.in_closure: - self._allocate_closure_temp(code, entry) - - def release_closure_privates(self, code): - """ - Release any temps used for variables in scope objects. As this is the - outermost parallel block, we don't need to delete the cnames from - self.seen_closure_vars. - """ - for entry, original_cname in self.modified_entries: - code.putln("%s = %s;" % (original_cname, entry.cname)) - code.funcstate.release_temp(entry.cname) - entry.cname = original_cname - - def privatize_temps(self, code, exclude_temps=()): - """ - Make any used temporaries private. Before the relevant code block - code.start_collecting_temps() should have been called. - """ + if entry.from_closure or entry.in_closure: + self._allocate_closure_temp(code, entry) + + def release_closure_privates(self, code): + """ + Release any temps used for variables in scope objects. As this is the + outermost parallel block, we don't need to delete the cnames from + self.seen_closure_vars. + """ + for entry, original_cname in self.modified_entries: + code.putln("%s = %s;" % (original_cname, entry.cname)) + code.funcstate.release_temp(entry.cname) + entry.cname = original_cname + + def privatize_temps(self, code, exclude_temps=()): + """ + Make any used temporaries private. Before the relevant code block + code.start_collecting_temps() should have been called. + """ c = self.privatization_insertion_point self.privatization_insertion_point = None - if self.is_parallel: - self.temps = temps = code.funcstate.stop_collecting_temps() - privates, firstprivates = [], [] + if self.is_parallel: + self.temps = temps = code.funcstate.stop_collecting_temps() + privates, firstprivates = [], [] for temp, type in sorted(temps): - if type.is_pyobject or type.is_memoryviewslice: - firstprivates.append(temp) - else: - privates.append(temp) - - if privates: - c.put(" private(%s)" % ", ".join(privates)) - if firstprivates: - c.put(" firstprivate(%s)" % ", ".join(firstprivates)) - - if self.breaking_label_used: - shared_vars = [Naming.parallel_why] - if self.error_label_used: - shared_vars.extend(self.parallel_exc) - c.put(" private(%s, %s, %s)" % self.pos_info) - - c.put(" shared(%s)" % ', '.join(shared_vars)) - - def cleanup_temps(self, code): - # Now clean up any memoryview slice and object temporaries - if self.is_parallel and not self.is_nested_prange: - code.putln("/* Clean up any temporaries */") + if type.is_pyobject or type.is_memoryviewslice: + firstprivates.append(temp) + else: + privates.append(temp) + + if privates: + c.put(" private(%s)" % ", ".join(privates)) + if firstprivates: + c.put(" firstprivate(%s)" % ", ".join(firstprivates)) + + if self.breaking_label_used: + shared_vars = [Naming.parallel_why] + if self.error_label_used: + shared_vars.extend(self.parallel_exc) + c.put(" private(%s, %s, %s)" % self.pos_info) + + c.put(" shared(%s)" % ', '.join(shared_vars)) + + def cleanup_temps(self, code): + # Now clean up any memoryview slice and object temporaries + if self.is_parallel and not self.is_nested_prange: + code.putln("/* Clean up any temporaries */") for temp, type in sorted(self.temps): - if type.is_memoryviewslice: - code.put_xdecref_memoryviewslice(temp, have_gil=False) - elif type.is_pyobject: - code.put_xdecref(temp, type) - code.putln("%s = NULL;" % temp) - - def setup_parallel_control_flow_block(self, code): - """ - Sets up a block that surrounds the parallel block to determine - how the parallel section was exited. Any kind of return is - trapped (break, continue, return, exceptions). This is the idea: - - { - int why = 0; - - #pragma omp parallel - { - return # -> goto new_return_label; - goto end_parallel; - - new_return_label: - why = 3; - goto end_parallel; - - end_parallel:; - #pragma omp flush(why) # we need to flush for every iteration - } - - if (why == 3) - goto old_return_label; - } - """ - self.old_loop_labels = code.new_loop_labels() - self.old_error_label = code.new_error_label() - self.old_return_label = code.return_label - code.return_label = code.new_label(name="return") - - code.begin_block() # parallel control flow block - self.begin_of_parallel_control_block_point = code.insertion_point() - self.begin_of_parallel_control_block_point_after_decls = code.insertion_point() - - self.undef_builtin_expect_apple_gcc_bug(code) - - def begin_parallel_block(self, code): - """ - Each OpenMP thread in a parallel section that contains a with gil block - must have the thread-state initialized. The call to - PyGILState_Release() then deallocates our threadstate. If we wouldn't - do this, each with gil block would allocate and deallocate one, thereby - losing exception information before it can be saved before leaving the - parallel section. - """ - self.begin_of_parallel_block = code.insertion_point() - - def end_parallel_block(self, code): - """ - To ensure all OpenMP threads have thread states, we ensure the GIL - in each thread (which creates a thread state if it doesn't exist), - after which we release the GIL. - On exit, reacquire the GIL and release the thread state. - - If compiled without OpenMP support (at the C level), then we still have - to acquire the GIL to decref any object temporaries. - """ + if type.is_memoryviewslice: + code.put_xdecref_memoryviewslice(temp, have_gil=False) + elif type.is_pyobject: + code.put_xdecref(temp, type) + code.putln("%s = NULL;" % temp) + + def setup_parallel_control_flow_block(self, code): + """ + Sets up a block that surrounds the parallel block to determine + how the parallel section was exited. Any kind of return is + trapped (break, continue, return, exceptions). This is the idea: + + { + int why = 0; + + #pragma omp parallel + { + return # -> goto new_return_label; + goto end_parallel; + + new_return_label: + why = 3; + goto end_parallel; + + end_parallel:; + #pragma omp flush(why) # we need to flush for every iteration + } + + if (why == 3) + goto old_return_label; + } + """ + self.old_loop_labels = code.new_loop_labels() + self.old_error_label = code.new_error_label() + self.old_return_label = code.return_label + code.return_label = code.new_label(name="return") + + code.begin_block() # parallel control flow block + self.begin_of_parallel_control_block_point = code.insertion_point() + self.begin_of_parallel_control_block_point_after_decls = code.insertion_point() + + self.undef_builtin_expect_apple_gcc_bug(code) + + def begin_parallel_block(self, code): + """ + Each OpenMP thread in a parallel section that contains a with gil block + must have the thread-state initialized. The call to + PyGILState_Release() then deallocates our threadstate. If we wouldn't + do this, each with gil block would allocate and deallocate one, thereby + losing exception information before it can be saved before leaving the + parallel section. + """ + self.begin_of_parallel_block = code.insertion_point() + + def end_parallel_block(self, code): + """ + To ensure all OpenMP threads have thread states, we ensure the GIL + in each thread (which creates a thread state if it doesn't exist), + after which we release the GIL. + On exit, reacquire the GIL and release the thread state. + + If compiled without OpenMP support (at the C level), then we still have + to acquire the GIL to decref any object temporaries. + """ begin_code = self.begin_of_parallel_block self.begin_of_parallel_block = None - if self.error_label_used: - end_code = code - - begin_code.putln("#ifdef _OPENMP") - begin_code.put_ensure_gil(declare_gilstate=True) - begin_code.putln("Py_BEGIN_ALLOW_THREADS") - begin_code.putln("#endif /* _OPENMP */") - - end_code.putln("#ifdef _OPENMP") - end_code.putln("Py_END_ALLOW_THREADS") - end_code.putln("#else") - end_code.put_safe("{\n") - end_code.put_ensure_gil() - end_code.putln("#endif /* _OPENMP */") - self.cleanup_temps(end_code) - end_code.put_release_ensured_gil() - end_code.putln("#ifndef _OPENMP") - end_code.put_safe("}\n") - end_code.putln("#endif /* _OPENMP */") - - def trap_parallel_exit(self, code, should_flush=False): - """ - Trap any kind of return inside a parallel construct. 'should_flush' - indicates whether the variable should be flushed, which is needed by - prange to skip the loop. It also indicates whether we need to register - a continue (we need this for parallel blocks, but not for prange - loops, as it is a direct jump there). - - It uses the same mechanism as try/finally: - 1 continue - 2 break - 3 return - 4 error - """ - save_lastprivates_label = code.new_label() - dont_return_label = code.new_label() - - self.any_label_used = False - self.breaking_label_used = False - self.error_label_used = False - - self.parallel_private_temps = [] - - all_labels = code.get_all_labels() - - # Figure this out before starting to generate any code - for label in all_labels: - if code.label_used(label): - self.breaking_label_used = (self.breaking_label_used or - label != code.continue_label) - self.any_label_used = True - - if self.any_label_used: - code.put_goto(dont_return_label) - - for i, label in enumerate(all_labels): - if not code.label_used(label): - continue - - is_continue_label = label == code.continue_label - - code.put_label(label) - - if not (should_flush and is_continue_label): - if label == code.error_label: - self.error_label_used = True - self.fetch_parallel_exception(code) - - code.putln("%s = %d;" % (Naming.parallel_why, i + 1)) - - if (self.breaking_label_used and self.is_prange and not - is_continue_label): - code.put_goto(save_lastprivates_label) - else: - code.put_goto(dont_return_label) - - if self.any_label_used: - if self.is_prange and self.breaking_label_used: - # Don't rely on lastprivate, save our lastprivates - code.put_label(save_lastprivates_label) - self.save_parallel_vars(code) - - code.put_label(dont_return_label) - - if should_flush and self.breaking_label_used: - code.putln_openmp("#pragma omp flush(%s)" % Naming.parallel_why) - - def save_parallel_vars(self, code): - """ - The following shenanigans are instated when we break, return or - propagate errors from a prange. In this case we cannot rely on - lastprivate() to do its job, as no iterations may have executed yet - in the last thread, leaving the values undefined. It is most likely - that the breaking thread has well-defined values of the lastprivate - variables, so we keep those values. - """ + if self.error_label_used: + end_code = code + + begin_code.putln("#ifdef _OPENMP") + begin_code.put_ensure_gil(declare_gilstate=True) + begin_code.putln("Py_BEGIN_ALLOW_THREADS") + begin_code.putln("#endif /* _OPENMP */") + + end_code.putln("#ifdef _OPENMP") + end_code.putln("Py_END_ALLOW_THREADS") + end_code.putln("#else") + end_code.put_safe("{\n") + end_code.put_ensure_gil() + end_code.putln("#endif /* _OPENMP */") + self.cleanup_temps(end_code) + end_code.put_release_ensured_gil() + end_code.putln("#ifndef _OPENMP") + end_code.put_safe("}\n") + end_code.putln("#endif /* _OPENMP */") + + def trap_parallel_exit(self, code, should_flush=False): + """ + Trap any kind of return inside a parallel construct. 'should_flush' + indicates whether the variable should be flushed, which is needed by + prange to skip the loop. It also indicates whether we need to register + a continue (we need this for parallel blocks, but not for prange + loops, as it is a direct jump there). + + It uses the same mechanism as try/finally: + 1 continue + 2 break + 3 return + 4 error + """ + save_lastprivates_label = code.new_label() + dont_return_label = code.new_label() + + self.any_label_used = False + self.breaking_label_used = False + self.error_label_used = False + + self.parallel_private_temps = [] + + all_labels = code.get_all_labels() + + # Figure this out before starting to generate any code + for label in all_labels: + if code.label_used(label): + self.breaking_label_used = (self.breaking_label_used or + label != code.continue_label) + self.any_label_used = True + + if self.any_label_used: + code.put_goto(dont_return_label) + + for i, label in enumerate(all_labels): + if not code.label_used(label): + continue + + is_continue_label = label == code.continue_label + + code.put_label(label) + + if not (should_flush and is_continue_label): + if label == code.error_label: + self.error_label_used = True + self.fetch_parallel_exception(code) + + code.putln("%s = %d;" % (Naming.parallel_why, i + 1)) + + if (self.breaking_label_used and self.is_prange and not + is_continue_label): + code.put_goto(save_lastprivates_label) + else: + code.put_goto(dont_return_label) + + if self.any_label_used: + if self.is_prange and self.breaking_label_used: + # Don't rely on lastprivate, save our lastprivates + code.put_label(save_lastprivates_label) + self.save_parallel_vars(code) + + code.put_label(dont_return_label) + + if should_flush and self.breaking_label_used: + code.putln_openmp("#pragma omp flush(%s)" % Naming.parallel_why) + + def save_parallel_vars(self, code): + """ + The following shenanigans are instated when we break, return or + propagate errors from a prange. In this case we cannot rely on + lastprivate() to do its job, as no iterations may have executed yet + in the last thread, leaving the values undefined. It is most likely + that the breaking thread has well-defined values of the lastprivate + variables, so we keep those values. + """ section_name = "__pyx_parallel_lastprivates%d" % self.critical_section_counter - code.putln_openmp("#pragma omp critical(%s)" % section_name) - ParallelStatNode.critical_section_counter += 1 - - code.begin_block() # begin critical section - - c = self.begin_of_parallel_control_block_point - - temp_count = 0 + code.putln_openmp("#pragma omp critical(%s)" % section_name) + ParallelStatNode.critical_section_counter += 1 + + code.begin_block() # begin critical section + + c = self.begin_of_parallel_control_block_point + + temp_count = 0 for entry, (op, lastprivate) in sorted(self.privates.items()): - if not lastprivate or entry.type.is_pyobject: - continue - + if not lastprivate or entry.type.is_pyobject: + continue + type_decl = entry.type.empty_declaration_code() - temp_cname = "__pyx_parallel_temp%d" % temp_count - private_cname = entry.cname - - temp_count += 1 - - invalid_value = entry.type.invalid_value() - if invalid_value: + temp_cname = "__pyx_parallel_temp%d" % temp_count + private_cname = entry.cname + + temp_count += 1 + + invalid_value = entry.type.invalid_value() + if invalid_value: init = ' = ' + entry.type.cast_code(invalid_value) - else: - init = '' - # Declare the parallel private in the outer block - c.putln("%s %s%s;" % (type_decl, temp_cname, init)) - - # Initialize before escaping - code.putln("%s = %s;" % (temp_cname, private_cname)) - - self.parallel_private_temps.append((temp_cname, private_cname)) - - code.end_block() # end critical section - - def fetch_parallel_exception(self, code): - """ - As each OpenMP thread may raise an exception, we need to fetch that - exception from the threadstate and save it for after the parallel - section where it can be re-raised in the master thread. - - Although it would seem that __pyx_filename, __pyx_lineno and - __pyx_clineno are only assigned to under exception conditions (i.e., - when we have the GIL), and thus should be allowed to be shared without - any race condition, they are in fact subject to the same race - conditions that they were previously when they were global variables - and functions were allowed to release the GIL: - - thread A thread B - acquire - set lineno - release - acquire - set lineno - release - acquire - fetch exception - release - skip the fetch - - deallocate threadstate deallocate threadstate - """ - code.begin_block() - code.put_ensure_gil(declare_gilstate=True) - - code.putln_openmp("#pragma omp flush(%s)" % Naming.parallel_exc_type) - code.putln( - "if (!%s) {" % Naming.parallel_exc_type) - + else: + init = '' + # Declare the parallel private in the outer block + c.putln("%s %s%s;" % (type_decl, temp_cname, init)) + + # Initialize before escaping + code.putln("%s = %s;" % (temp_cname, private_cname)) + + self.parallel_private_temps.append((temp_cname, private_cname)) + + code.end_block() # end critical section + + def fetch_parallel_exception(self, code): + """ + As each OpenMP thread may raise an exception, we need to fetch that + exception from the threadstate and save it for after the parallel + section where it can be re-raised in the master thread. + + Although it would seem that __pyx_filename, __pyx_lineno and + __pyx_clineno are only assigned to under exception conditions (i.e., + when we have the GIL), and thus should be allowed to be shared without + any race condition, they are in fact subject to the same race + conditions that they were previously when they were global variables + and functions were allowed to release the GIL: + + thread A thread B + acquire + set lineno + release + acquire + set lineno + release + acquire + fetch exception + release + skip the fetch + + deallocate threadstate deallocate threadstate + """ + code.begin_block() + code.put_ensure_gil(declare_gilstate=True) + + code.putln_openmp("#pragma omp flush(%s)" % Naming.parallel_exc_type) + code.putln( + "if (!%s) {" % Naming.parallel_exc_type) + code.putln("__Pyx_ErrFetchWithState(&%s, &%s, &%s);" % self.parallel_exc) - pos_info = chain(*zip(self.parallel_pos_info, self.pos_info)) - code.funcstate.uses_error_indicator = True - code.putln("%s = %s; %s = %s; %s = %s;" % tuple(pos_info)) - code.put_gotref(Naming.parallel_exc_type) - - code.putln( - "}") - - code.put_release_ensured_gil() - code.end_block() - - def restore_parallel_exception(self, code): - "Re-raise a parallel exception" - code.begin_block() - code.put_ensure_gil(declare_gilstate=True) - - code.put_giveref(Naming.parallel_exc_type) + pos_info = chain(*zip(self.parallel_pos_info, self.pos_info)) + code.funcstate.uses_error_indicator = True + code.putln("%s = %s; %s = %s; %s = %s;" % tuple(pos_info)) + code.put_gotref(Naming.parallel_exc_type) + + code.putln( + "}") + + code.put_release_ensured_gil() + code.end_block() + + def restore_parallel_exception(self, code): + "Re-raise a parallel exception" + code.begin_block() + code.put_ensure_gil(declare_gilstate=True) + + code.put_giveref(Naming.parallel_exc_type) code.putln("__Pyx_ErrRestoreWithState(%s, %s, %s);" % self.parallel_exc) - pos_info = chain(*zip(self.pos_info, self.parallel_pos_info)) - code.putln("%s = %s; %s = %s; %s = %s;" % tuple(pos_info)) - - code.put_release_ensured_gil() - code.end_block() - - def restore_labels(self, code): - """ - Restore all old labels. Call this before the 'else' clause to for - loops and always before ending the parallel control flow block. - """ - code.set_all_labels(self.old_loop_labels + (self.old_return_label, - self.old_error_label)) - + pos_info = chain(*zip(self.pos_info, self.parallel_pos_info)) + code.putln("%s = %s; %s = %s; %s = %s;" % tuple(pos_info)) + + code.put_release_ensured_gil() + code.end_block() + + def restore_labels(self, code): + """ + Restore all old labels. Call this before the 'else' clause to for + loops and always before ending the parallel control flow block. + """ + code.set_all_labels(self.old_loop_labels + (self.old_return_label, + self.old_error_label)) + def end_parallel_control_flow_block( self, code, break_=False, continue_=False, return_=False): - """ - This ends the parallel control flow block and based on how the parallel - section was exited, takes the corresponding action. The break_ and - continue_ parameters indicate whether these should be propagated - outwards: - - for i in prange(...): - with cython.parallel.parallel(): - continue - - Here break should be trapped in the parallel block, and propagated to - the for loop. - """ - c = self.begin_of_parallel_control_block_point + """ + This ends the parallel control flow block and based on how the parallel + section was exited, takes the corresponding action. The break_ and + continue_ parameters indicate whether these should be propagated + outwards: + + for i in prange(...): + with cython.parallel.parallel(): + continue + + Here break should be trapped in the parallel block, and propagated to + the for loop. + """ + c = self.begin_of_parallel_control_block_point self.begin_of_parallel_control_block_point = None self.begin_of_parallel_control_block_point_after_decls = None - + if self.num_threads is not None: # FIXME: is it the right place? should not normally produce code. self.num_threads.generate_disposal_code(code) self.num_threads.free_temps(code) - # Firstly, always prefer errors over returning, continue or break - if self.error_label_used: + # Firstly, always prefer errors over returning, continue or break + if self.error_label_used: c.putln("const char *%s = NULL; int %s = 0, %s = 0;" % self.parallel_pos_info) c.putln("PyObject *%s = NULL, *%s = NULL, *%s = NULL;" % self.parallel_exc) - - code.putln( - "if (%s) {" % Naming.parallel_exc_type) - code.putln("/* This may have been overridden by a continue, " - "break or return in another thread. Prefer the error. */") - code.putln("%s = 4;" % Naming.parallel_why) - code.putln( - "}") - - if continue_: - any_label_used = self.any_label_used - else: - any_label_used = self.breaking_label_used - - if any_label_used: - # __pyx_parallel_why is used, declare and initialize - c.putln("int %s;" % Naming.parallel_why) - c.putln("%s = 0;" % Naming.parallel_why) - - code.putln( - "if (%s) {" % Naming.parallel_why) - - for temp_cname, private_cname in self.parallel_private_temps: - code.putln("%s = %s;" % (private_cname, temp_cname)) - - code.putln("switch (%s) {" % Naming.parallel_why) - if continue_: - code.put(" case 1: ") - code.put_goto(code.continue_label) - - if break_: - code.put(" case 2: ") - code.put_goto(code.break_label) - + + code.putln( + "if (%s) {" % Naming.parallel_exc_type) + code.putln("/* This may have been overridden by a continue, " + "break or return in another thread. Prefer the error. */") + code.putln("%s = 4;" % Naming.parallel_why) + code.putln( + "}") + + if continue_: + any_label_used = self.any_label_used + else: + any_label_used = self.breaking_label_used + + if any_label_used: + # __pyx_parallel_why is used, declare and initialize + c.putln("int %s;" % Naming.parallel_why) + c.putln("%s = 0;" % Naming.parallel_why) + + code.putln( + "if (%s) {" % Naming.parallel_why) + + for temp_cname, private_cname in self.parallel_private_temps: + code.putln("%s = %s;" % (private_cname, temp_cname)) + + code.putln("switch (%s) {" % Naming.parallel_why) + if continue_: + code.put(" case 1: ") + code.put_goto(code.continue_label) + + if break_: + code.put(" case 2: ") + code.put_goto(code.break_label) + if return_: code.put(" case 3: ") code.put_goto(code.return_label) - - if self.error_label_used: - code.globalstate.use_utility_code(restore_exception_utility_code) - code.putln(" case 4:") - self.restore_parallel_exception(code) - code.put_goto(code.error_label) - - code.putln("}") # end switch - code.putln( - "}") # end if - - code.end_block() # end parallel control flow block - self.redef_builtin_expect_apple_gcc_bug(code) - - # FIXME: improve with version number for OS X Lion - buggy_platform_macro_condition = "(defined(__APPLE__) || defined(__OSX__))" - have_expect_condition = "(defined(__GNUC__) && " \ - "(__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))))" - redef_condition = "(%s && %s)" % (buggy_platform_macro_condition, have_expect_condition) - - def undef_builtin_expect_apple_gcc_bug(self, code): - """ - A bug on OS X Lion disallows __builtin_expect macros. This code avoids them - """ - if not self.parent: - code.undef_builtin_expect(self.redef_condition) - - def redef_builtin_expect_apple_gcc_bug(self, code): - if not self.parent: - code.redef_builtin_expect(self.redef_condition) - - -class ParallelWithBlockNode(ParallelStatNode): - """ - This node represents a 'with cython.parallel.parallel():' block - """ - - valid_keyword_arguments = ['num_threads'] - - num_threads = None - - def analyse_declarations(self, env): - super(ParallelWithBlockNode, self).analyse_declarations(env) - if self.args: - error(self.pos, "cython.parallel.parallel() does not take " - "positional arguments") - - def generate_execution_code(self, code): - self.declare_closure_privates(code) - self.setup_parallel_control_flow_block(code) - - code.putln("#ifdef _OPENMP") - code.put("#pragma omp parallel ") - - if self.privates: - privates = [e.cname for e in self.privates + + if self.error_label_used: + code.globalstate.use_utility_code(restore_exception_utility_code) + code.putln(" case 4:") + self.restore_parallel_exception(code) + code.put_goto(code.error_label) + + code.putln("}") # end switch + code.putln( + "}") # end if + + code.end_block() # end parallel control flow block + self.redef_builtin_expect_apple_gcc_bug(code) + + # FIXME: improve with version number for OS X Lion + buggy_platform_macro_condition = "(defined(__APPLE__) || defined(__OSX__))" + have_expect_condition = "(defined(__GNUC__) && " \ + "(__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))))" + redef_condition = "(%s && %s)" % (buggy_platform_macro_condition, have_expect_condition) + + def undef_builtin_expect_apple_gcc_bug(self, code): + """ + A bug on OS X Lion disallows __builtin_expect macros. This code avoids them + """ + if not self.parent: + code.undef_builtin_expect(self.redef_condition) + + def redef_builtin_expect_apple_gcc_bug(self, code): + if not self.parent: + code.redef_builtin_expect(self.redef_condition) + + +class ParallelWithBlockNode(ParallelStatNode): + """ + This node represents a 'with cython.parallel.parallel():' block + """ + + valid_keyword_arguments = ['num_threads'] + + num_threads = None + + def analyse_declarations(self, env): + super(ParallelWithBlockNode, self).analyse_declarations(env) + if self.args: + error(self.pos, "cython.parallel.parallel() does not take " + "positional arguments") + + def generate_execution_code(self, code): + self.declare_closure_privates(code) + self.setup_parallel_control_flow_block(code) + + code.putln("#ifdef _OPENMP") + code.put("#pragma omp parallel ") + + if self.privates: + privates = [e.cname for e in self.privates if not e.type.is_pyobject] code.put('private(%s)' % ', '.join(sorted(privates))) - - self.privatization_insertion_point = code.insertion_point() - self.put_num_threads(code) - code.putln("") - - code.putln("#endif /* _OPENMP */") - + + self.privatization_insertion_point = code.insertion_point() + self.put_num_threads(code) + code.putln("") + + code.putln("#endif /* _OPENMP */") + code.begin_block() # parallel block - self.begin_parallel_block(code) - self.initialize_privates_to_nan(code) - code.funcstate.start_collecting_temps() - self.body.generate_execution_code(code) - self.trap_parallel_exit(code) - self.privatize_temps(code) - self.end_parallel_block(code) + self.begin_parallel_block(code) + self.initialize_privates_to_nan(code) + code.funcstate.start_collecting_temps() + self.body.generate_execution_code(code) + self.trap_parallel_exit(code) + self.privatize_temps(code) + self.end_parallel_block(code) code.end_block() # end parallel block - - continue_ = code.label_used(code.continue_label) - break_ = code.label_used(code.break_label) + + continue_ = code.label_used(code.continue_label) + break_ = code.label_used(code.break_label) return_ = code.label_used(code.return_label) - - self.restore_labels(code) - self.end_parallel_control_flow_block(code, break_=break_, + + self.restore_labels(code) + self.end_parallel_control_flow_block(code, break_=break_, continue_=continue_, return_=return_) - self.release_closure_privates(code) - - -class ParallelRangeNode(ParallelStatNode): - """ - This node represents a 'for i in cython.parallel.prange():' construct. - - target NameNode the target iteration variable - else_clause Node or None the else clause of this loop - """ - - child_attrs = ['body', 'target', 'else_clause', 'args', 'num_threads', - 'chunksize'] - - body = target = else_clause = args = None - - start = stop = step = None - - is_prange = True - - nogil = None - schedule = None - - valid_keyword_arguments = ['schedule', 'nogil', 'num_threads', 'chunksize'] - - def __init__(self, pos, **kwds): - super(ParallelRangeNode, self).__init__(pos, **kwds) - # Pretend to be a ForInStatNode for control flow analysis - self.iterator = PassStatNode(pos) - - def analyse_declarations(self, env): - super(ParallelRangeNode, self).analyse_declarations(env) - self.target.analyse_target_declaration(env) - if self.else_clause is not None: - self.else_clause.analyse_declarations(env) - - if not self.args or len(self.args) > 3: - error(self.pos, "Invalid number of positional arguments to prange") - return - - if len(self.args) == 1: - self.stop, = self.args - elif len(self.args) == 2: - self.start, self.stop = self.args - else: - self.start, self.stop, self.step = self.args - - if hasattr(self.schedule, 'decode'): - self.schedule = self.schedule.decode('ascii') - + self.release_closure_privates(code) + + +class ParallelRangeNode(ParallelStatNode): + """ + This node represents a 'for i in cython.parallel.prange():' construct. + + target NameNode the target iteration variable + else_clause Node or None the else clause of this loop + """ + + child_attrs = ['body', 'target', 'else_clause', 'args', 'num_threads', + 'chunksize'] + + body = target = else_clause = args = None + + start = stop = step = None + + is_prange = True + + nogil = None + schedule = None + + valid_keyword_arguments = ['schedule', 'nogil', 'num_threads', 'chunksize'] + + def __init__(self, pos, **kwds): + super(ParallelRangeNode, self).__init__(pos, **kwds) + # Pretend to be a ForInStatNode for control flow analysis + self.iterator = PassStatNode(pos) + + def analyse_declarations(self, env): + super(ParallelRangeNode, self).analyse_declarations(env) + self.target.analyse_target_declaration(env) + if self.else_clause is not None: + self.else_clause.analyse_declarations(env) + + if not self.args or len(self.args) > 3: + error(self.pos, "Invalid number of positional arguments to prange") + return + + if len(self.args) == 1: + self.stop, = self.args + elif len(self.args) == 2: + self.start, self.stop = self.args + else: + self.start, self.stop, self.step = self.args + + if hasattr(self.schedule, 'decode'): + self.schedule = self.schedule.decode('ascii') + if self.schedule not in (None, 'static', 'dynamic', 'guided', 'runtime'): error(self.pos, "Invalid schedule argument to prange: %s" % (self.schedule,)) - - def analyse_expressions(self, env): - was_nogil = env.nogil - if self.nogil: - env.nogil = True - - if self.target is None: - error(self.pos, "prange() can only be used as part of a for loop") - return self - - self.target = self.target.analyse_target_types(env) - - if not self.target.type.is_numeric: - # Not a valid type, assume one for now anyway - - if not self.target.type.is_pyobject: - # nogil_check will catch the is_pyobject case - error(self.target.pos, - "Must be of numeric type, not %s" % self.target.type) - - self.index_type = PyrexTypes.c_py_ssize_t_type - else: - self.index_type = self.target.type - - # Setup start, stop and step, allocating temps if needed - self.names = 'start', 'stop', 'step' - start_stop_step = self.start, self.stop, self.step - - for node, name in zip(start_stop_step, self.names): - if node is not None: - node.analyse_types(env) - if not node.type.is_numeric: - error(node.pos, "%s argument must be numeric" % name) - continue - - if not node.is_literal: - node = node.coerce_to_temp(env) - setattr(self, name, node) - - # As we range from 0 to nsteps, computing the index along the - # way, we need a fitting type for 'i' and 'nsteps' - self.index_type = PyrexTypes.widest_numeric_type( + + def analyse_expressions(self, env): + was_nogil = env.nogil + if self.nogil: + env.nogil = True + + if self.target is None: + error(self.pos, "prange() can only be used as part of a for loop") + return self + + self.target = self.target.analyse_target_types(env) + + if not self.target.type.is_numeric: + # Not a valid type, assume one for now anyway + + if not self.target.type.is_pyobject: + # nogil_check will catch the is_pyobject case + error(self.target.pos, + "Must be of numeric type, not %s" % self.target.type) + + self.index_type = PyrexTypes.c_py_ssize_t_type + else: + self.index_type = self.target.type + + # Setup start, stop and step, allocating temps if needed + self.names = 'start', 'stop', 'step' + start_stop_step = self.start, self.stop, self.step + + for node, name in zip(start_stop_step, self.names): + if node is not None: + node.analyse_types(env) + if not node.type.is_numeric: + error(node.pos, "%s argument must be numeric" % name) + continue + + if not node.is_literal: + node = node.coerce_to_temp(env) + setattr(self, name, node) + + # As we range from 0 to nsteps, computing the index along the + # way, we need a fitting type for 'i' and 'nsteps' + self.index_type = PyrexTypes.widest_numeric_type( self.index_type, node.type) - - if self.else_clause is not None: - self.else_clause = self.else_clause.analyse_expressions(env) - - # Although not actually an assignment in this scope, it should be - # treated as such to ensure it is unpacked if a closure temp, and to - # ensure lastprivate behaviour and propagation. If the target index is - # not a NameNode, it won't have an entry, and an error was issued by - # ParallelRangeTransform - if hasattr(self.target, 'entry'): - self.assignments[self.target.entry] = self.target.pos, None - - node = super(ParallelRangeNode, self).analyse_expressions(env) - - if node.chunksize: - if not node.schedule: - error(node.chunksize.pos, - "Must provide schedule with chunksize") - elif node.schedule == 'runtime': - error(node.chunksize.pos, - "Chunksize not valid for the schedule runtime") - elif (node.chunksize.type.is_int and - node.chunksize.is_literal and - node.chunksize.compile_time_value(env) <= 0): - error(node.chunksize.pos, "Chunksize must not be negative") - - node.chunksize = node.chunksize.coerce_to( - PyrexTypes.c_int_type, env).coerce_to_temp(env) - - if node.nogil: - env.nogil = was_nogil - - node.is_nested_prange = node.parent and node.parent.is_prange - if node.is_nested_prange: - parent = node - while parent.parent and parent.parent.is_prange: - parent = parent.parent - - parent.assignments.update(node.assignments) - parent.privates.update(node.privates) - parent.assigned_nodes.extend(node.assigned_nodes) - return node - - def nogil_check(self, env): - names = 'start', 'stop', 'step', 'target' - nodes = self.start, self.stop, self.step, self.target - for name, node in zip(names, nodes): - if node is not None and node.type.is_pyobject: - error(node.pos, "%s may not be a Python object " - "as we don't have the GIL" % name) - - def generate_execution_code(self, code): - """ - Generate code in the following steps - - 1) copy any closure variables determined thread-private - into temporaries - - 2) allocate temps for start, stop and step - - 3) generate a loop that calculates the total number of steps, - which then computes the target iteration variable for every step: - - for i in prange(start, stop, step): - ... - - becomes - - nsteps = (stop - start) / step; - i = start; - - #pragma omp parallel for lastprivate(i) - for (temp = 0; temp < nsteps; temp++) { - i = start + step * temp; - ... - } - - Note that accumulation of 'i' would have a data dependency - between iterations. - - Also, you can't do this - - for (i = start; i < stop; i += step) - ... - - as the '<' operator should become '>' for descending loops. - 'for i from x < i < y:' does not suffer from this problem - as the relational operator is known at compile time! - - 4) release our temps and write back any private closure variables - """ - self.declare_closure_privates(code) - - # This can only be a NameNode - target_index_cname = self.target.entry.cname - - # This will be used as the dict to format our code strings, holding - # the start, stop , step, temps and target cnames - fmt_dict = { - 'target': target_index_cname, + + if self.else_clause is not None: + self.else_clause = self.else_clause.analyse_expressions(env) + + # Although not actually an assignment in this scope, it should be + # treated as such to ensure it is unpacked if a closure temp, and to + # ensure lastprivate behaviour and propagation. If the target index is + # not a NameNode, it won't have an entry, and an error was issued by + # ParallelRangeTransform + if hasattr(self.target, 'entry'): + self.assignments[self.target.entry] = self.target.pos, None + + node = super(ParallelRangeNode, self).analyse_expressions(env) + + if node.chunksize: + if not node.schedule: + error(node.chunksize.pos, + "Must provide schedule with chunksize") + elif node.schedule == 'runtime': + error(node.chunksize.pos, + "Chunksize not valid for the schedule runtime") + elif (node.chunksize.type.is_int and + node.chunksize.is_literal and + node.chunksize.compile_time_value(env) <= 0): + error(node.chunksize.pos, "Chunksize must not be negative") + + node.chunksize = node.chunksize.coerce_to( + PyrexTypes.c_int_type, env).coerce_to_temp(env) + + if node.nogil: + env.nogil = was_nogil + + node.is_nested_prange = node.parent and node.parent.is_prange + if node.is_nested_prange: + parent = node + while parent.parent and parent.parent.is_prange: + parent = parent.parent + + parent.assignments.update(node.assignments) + parent.privates.update(node.privates) + parent.assigned_nodes.extend(node.assigned_nodes) + return node + + def nogil_check(self, env): + names = 'start', 'stop', 'step', 'target' + nodes = self.start, self.stop, self.step, self.target + for name, node in zip(names, nodes): + if node is not None and node.type.is_pyobject: + error(node.pos, "%s may not be a Python object " + "as we don't have the GIL" % name) + + def generate_execution_code(self, code): + """ + Generate code in the following steps + + 1) copy any closure variables determined thread-private + into temporaries + + 2) allocate temps for start, stop and step + + 3) generate a loop that calculates the total number of steps, + which then computes the target iteration variable for every step: + + for i in prange(start, stop, step): + ... + + becomes + + nsteps = (stop - start) / step; + i = start; + + #pragma omp parallel for lastprivate(i) + for (temp = 0; temp < nsteps; temp++) { + i = start + step * temp; + ... + } + + Note that accumulation of 'i' would have a data dependency + between iterations. + + Also, you can't do this + + for (i = start; i < stop; i += step) + ... + + as the '<' operator should become '>' for descending loops. + 'for i from x < i < y:' does not suffer from this problem + as the relational operator is known at compile time! + + 4) release our temps and write back any private closure variables + """ + self.declare_closure_privates(code) + + # This can only be a NameNode + target_index_cname = self.target.entry.cname + + # This will be used as the dict to format our code strings, holding + # the start, stop , step, temps and target cnames + fmt_dict = { + 'target': target_index_cname, 'target_type': self.target.type.empty_declaration_code() - } - - # Setup start, stop and step, allocating temps if needed - start_stop_step = self.start, self.stop, self.step - defaults = '0', '0', '1' - for node, name, default in zip(start_stop_step, self.names, defaults): - if node is None: - result = default - elif node.is_literal: - result = node.get_constant_c_result_code() - else: - node.generate_evaluation_code(code) - result = node.result() - - fmt_dict[name] = result - - fmt_dict['i'] = code.funcstate.allocate_temp(self.index_type, False) - fmt_dict['nsteps'] = code.funcstate.allocate_temp(self.index_type, False) - - # TODO: check if the step is 0 and if so, raise an exception in a - # 'with gil' block. For now, just abort + } + + # Setup start, stop and step, allocating temps if needed + start_stop_step = self.start, self.stop, self.step + defaults = '0', '0', '1' + for node, name, default in zip(start_stop_step, self.names, defaults): + if node is None: + result = default + elif node.is_literal: + result = node.get_constant_c_result_code() + else: + node.generate_evaluation_code(code) + result = node.result() + + fmt_dict[name] = result + + fmt_dict['i'] = code.funcstate.allocate_temp(self.index_type, False) + fmt_dict['nsteps'] = code.funcstate.allocate_temp(self.index_type, False) + + # TODO: check if the step is 0 and if so, raise an exception in a + # 'with gil' block. For now, just abort code.putln("if ((%(step)s == 0)) abort();" % fmt_dict) - - self.setup_parallel_control_flow_block(code) # parallel control flow block - - # Note: nsteps is private in an outer scope if present + + self.setup_parallel_control_flow_block(code) # parallel control flow block + + # Note: nsteps is private in an outer scope if present code.putln("%(nsteps)s = (%(stop)s - %(start)s + %(step)s - %(step)s/abs(%(step)s)) / %(step)s;" % fmt_dict) - - # The target iteration variable might not be initialized, do it only if - # we are executing at least 1 iteration, otherwise we should leave the - # target unaffected. The target iteration variable is firstprivate to - # shut up compiler warnings caused by lastprivate, as the compiler - # erroneously believes that nsteps may be <= 0, leaving the private - # target index uninitialized - code.putln("if (%(nsteps)s > 0)" % fmt_dict) - code.begin_block() # if block - self.generate_loop(code, fmt_dict) - code.end_block() # end if block - - self.restore_labels(code) - - if self.else_clause: - if self.breaking_label_used: - code.put("if (%s < 2)" % Naming.parallel_why) - - code.begin_block() # else block - code.putln("/* else */") - self.else_clause.generate_execution_code(code) - code.end_block() # end else block - - # ------ cleanup ------ - self.end_parallel_control_flow_block(code) # end parallel control flow block - - # And finally, release our privates and write back any closure - # variables + + # The target iteration variable might not be initialized, do it only if + # we are executing at least 1 iteration, otherwise we should leave the + # target unaffected. The target iteration variable is firstprivate to + # shut up compiler warnings caused by lastprivate, as the compiler + # erroneously believes that nsteps may be <= 0, leaving the private + # target index uninitialized + code.putln("if (%(nsteps)s > 0)" % fmt_dict) + code.begin_block() # if block + self.generate_loop(code, fmt_dict) + code.end_block() # end if block + + self.restore_labels(code) + + if self.else_clause: + if self.breaking_label_used: + code.put("if (%s < 2)" % Naming.parallel_why) + + code.begin_block() # else block + code.putln("/* else */") + self.else_clause.generate_execution_code(code) + code.end_block() # end else block + + # ------ cleanup ------ + self.end_parallel_control_flow_block(code) # end parallel control flow block + + # And finally, release our privates and write back any closure + # variables for temp in start_stop_step + (self.chunksize,): - if temp is not None: - temp.generate_disposal_code(code) - temp.free_temps(code) - - code.funcstate.release_temp(fmt_dict['i']) - code.funcstate.release_temp(fmt_dict['nsteps']) - - self.release_closure_privates(code) - - def generate_loop(self, code, fmt_dict): - if self.is_nested_prange: - code.putln("#if 0") - else: - code.putln("#ifdef _OPENMP") - - if not self.is_parallel: - code.put("#pragma omp for") - self.privatization_insertion_point = code.insertion_point() - reduction_codepoint = self.parent.privatization_insertion_point - else: - code.put("#pragma omp parallel") - self.privatization_insertion_point = code.insertion_point() - reduction_codepoint = self.privatization_insertion_point - code.putln("") - code.putln("#endif /* _OPENMP */") - - code.begin_block() # pragma omp parallel begin block - - # Initialize the GIL if needed for this thread - self.begin_parallel_block(code) - - if self.is_nested_prange: - code.putln("#if 0") - else: - code.putln("#ifdef _OPENMP") - code.put("#pragma omp for") - + if temp is not None: + temp.generate_disposal_code(code) + temp.free_temps(code) + + code.funcstate.release_temp(fmt_dict['i']) + code.funcstate.release_temp(fmt_dict['nsteps']) + + self.release_closure_privates(code) + + def generate_loop(self, code, fmt_dict): + if self.is_nested_prange: + code.putln("#if 0") + else: + code.putln("#ifdef _OPENMP") + + if not self.is_parallel: + code.put("#pragma omp for") + self.privatization_insertion_point = code.insertion_point() + reduction_codepoint = self.parent.privatization_insertion_point + else: + code.put("#pragma omp parallel") + self.privatization_insertion_point = code.insertion_point() + reduction_codepoint = self.privatization_insertion_point + code.putln("") + code.putln("#endif /* _OPENMP */") + + code.begin_block() # pragma omp parallel begin block + + # Initialize the GIL if needed for this thread + self.begin_parallel_block(code) + + if self.is_nested_prange: + code.putln("#if 0") + else: + code.putln("#ifdef _OPENMP") + code.put("#pragma omp for") + for entry, (op, lastprivate) in sorted(self.privates.items()): - # Don't declare the index variable as a reduction - if op and op in "+*-&^|" and entry != self.target.entry: - if entry.type.is_pyobject: - error(self.pos, "Python objects cannot be reductions") - else: - #code.put(" reduction(%s:%s)" % (op, entry.cname)) - # This is the only way reductions + nesting works in gcc4.5 - reduction_codepoint.put( - " reduction(%s:%s)" % (op, entry.cname)) - else: - if entry == self.target.entry: - code.put(" firstprivate(%s)" % entry.cname) - code.put(" lastprivate(%s)" % entry.cname) - continue - - if not entry.type.is_pyobject: - if lastprivate: - private = 'lastprivate' - else: - private = 'private' - - code.put(" %s(%s)" % (private, entry.cname)) - - if self.schedule: - if self.chunksize: + # Don't declare the index variable as a reduction + if op and op in "+*-&^|" and entry != self.target.entry: + if entry.type.is_pyobject: + error(self.pos, "Python objects cannot be reductions") + else: + #code.put(" reduction(%s:%s)" % (op, entry.cname)) + # This is the only way reductions + nesting works in gcc4.5 + reduction_codepoint.put( + " reduction(%s:%s)" % (op, entry.cname)) + else: + if entry == self.target.entry: + code.put(" firstprivate(%s)" % entry.cname) + code.put(" lastprivate(%s)" % entry.cname) + continue + + if not entry.type.is_pyobject: + if lastprivate: + private = 'lastprivate' + else: + private = 'private' + + code.put(" %s(%s)" % (private, entry.cname)) + + if self.schedule: + if self.chunksize: chunksize = ", %s" % self.evaluate_before_block(code, self.chunksize) - else: - chunksize = "" - - code.put(" schedule(%s%s)" % (self.schedule, chunksize)) - - self.put_num_threads(reduction_codepoint) - - code.putln("") - code.putln("#endif /* _OPENMP */") - - code.put("for (%(i)s = 0; %(i)s < %(nsteps)s; %(i)s++)" % fmt_dict) + else: + chunksize = "" + + code.put(" schedule(%s%s)" % (self.schedule, chunksize)) + + self.put_num_threads(reduction_codepoint) + + code.putln("") + code.putln("#endif /* _OPENMP */") + + code.put("for (%(i)s = 0; %(i)s < %(nsteps)s; %(i)s++)" % fmt_dict) code.begin_block() # for loop block - - guard_around_body_codepoint = code.insertion_point() - - # Start if guard block around the body. This may be unnecessary, but - # at least it doesn't spoil indentation - code.begin_block() - + + guard_around_body_codepoint = code.insertion_point() + + # Start if guard block around the body. This may be unnecessary, but + # at least it doesn't spoil indentation + code.begin_block() + code.putln("%(target)s = (%(target_type)s)(%(start)s + %(step)s * %(i)s);" % fmt_dict) - self.initialize_privates_to_nan(code, exclude=self.target.entry) - + self.initialize_privates_to_nan(code, exclude=self.target.entry) + if self.is_parallel and not self.is_nested_prange: # nested pranges are not omp'ified, temps go to outer loops - code.funcstate.start_collecting_temps() - - self.body.generate_execution_code(code) - self.trap_parallel_exit(code, should_flush=True) + code.funcstate.start_collecting_temps() + + self.body.generate_execution_code(code) + self.trap_parallel_exit(code, should_flush=True) if self.is_parallel and not self.is_nested_prange: # nested pranges are not omp'ified, temps go to outer loops self.privatize_temps(code) - - if self.breaking_label_used: - # Put a guard around the loop body in case return, break or - # exceptions might be used - guard_around_body_codepoint.putln("if (%s < 2)" % Naming.parallel_why) - + + if self.breaking_label_used: + # Put a guard around the loop body in case return, break or + # exceptions might be used + guard_around_body_codepoint.putln("if (%s < 2)" % Naming.parallel_why) + code.end_block() # end guard around loop body code.end_block() # end for loop block - - if self.is_parallel: - # Release the GIL and deallocate the thread state - self.end_parallel_block(code) + + if self.is_parallel: + # Release the GIL and deallocate the thread state + self.end_parallel_block(code) code.end_block() # pragma omp parallel end block - - -class CnameDecoratorNode(StatNode): - """ - This node is for the cname decorator in CythonUtilityCode: - - @cname('the_cname') - cdef func(...): - ... - - In case of a cdef class the cname specifies the objstruct_cname. - - node the node to which the cname decorator is applied - cname the cname the node should get - """ - - child_attrs = ['node'] - - def analyse_declarations(self, env): - self.node.analyse_declarations(env) - - node = self.node - if isinstance(node, CompilerDirectivesNode): - node = node.body.stats[0] - - self.is_function = isinstance(node, FuncDefNode) + + +class CnameDecoratorNode(StatNode): + """ + This node is for the cname decorator in CythonUtilityCode: + + @cname('the_cname') + cdef func(...): + ... + + In case of a cdef class the cname specifies the objstruct_cname. + + node the node to which the cname decorator is applied + cname the cname the node should get + """ + + child_attrs = ['node'] + + def analyse_declarations(self, env): + self.node.analyse_declarations(env) + + node = self.node + if isinstance(node, CompilerDirectivesNode): + node = node.body.stats[0] + + self.is_function = isinstance(node, FuncDefNode) is_struct_or_enum = isinstance(node, (CStructOrUnionDefNode, CEnumDefNode)) - e = node.entry - - if self.is_function: - e.cname = self.cname - e.func_cname = self.cname - e.used = True - if e.pyfunc_cname and '.' in e.pyfunc_cname: - e.pyfunc_cname = self.mangle(e.pyfunc_cname) - elif is_struct_or_enum: - e.cname = e.type.cname = self.cname - else: - scope = node.scope - - e.cname = self.cname - e.type.objstruct_cname = self.cname + '_obj' - e.type.typeobj_cname = Naming.typeobj_prefix + self.cname - e.type.typeptr_cname = self.cname + '_type' - e.type.scope.namespace_cname = e.type.typeptr_cname - + e = node.entry + + if self.is_function: + e.cname = self.cname + e.func_cname = self.cname + e.used = True + if e.pyfunc_cname and '.' in e.pyfunc_cname: + e.pyfunc_cname = self.mangle(e.pyfunc_cname) + elif is_struct_or_enum: + e.cname = e.type.cname = self.cname + else: + scope = node.scope + + e.cname = self.cname + e.type.objstruct_cname = self.cname + '_obj' + e.type.typeobj_cname = Naming.typeobj_prefix + self.cname + e.type.typeptr_cname = self.cname + '_type' + e.type.scope.namespace_cname = e.type.typeptr_cname + e.as_variable.cname = e.type.typeptr_cname - - scope.scope_prefix = self.cname + "_" - + + scope.scope_prefix = self.cname + "_" + for name, entry in scope.entries.items(): - if entry.func_cname: - entry.func_cname = self.mangle(entry.cname) - if entry.pyfunc_cname: - entry.pyfunc_cname = self.mangle(entry.pyfunc_cname) - - def mangle(self, cname): - if '.' in cname: - # remove __pyx_base from func_cname - cname = cname.split('.')[-1] - return '%s_%s' % (self.cname, cname) - - def analyse_expressions(self, env): - self.node = self.node.analyse_expressions(env) - return self - - def generate_function_definitions(self, env, code): - "Ensure a prototype for every @cname method in the right place" - if self.is_function and env.is_c_class_scope: - # method in cdef class, generate a prototype in the header - h_code = code.globalstate['utility_code_proto'] - - if isinstance(self.node, DefNode): - self.node.generate_function_header( + if entry.func_cname: + entry.func_cname = self.mangle(entry.cname) + if entry.pyfunc_cname: + entry.pyfunc_cname = self.mangle(entry.pyfunc_cname) + + def mangle(self, cname): + if '.' in cname: + # remove __pyx_base from func_cname + cname = cname.split('.')[-1] + return '%s_%s' % (self.cname, cname) + + def analyse_expressions(self, env): + self.node = self.node.analyse_expressions(env) + return self + + def generate_function_definitions(self, env, code): + "Ensure a prototype for every @cname method in the right place" + if self.is_function and env.is_c_class_scope: + # method in cdef class, generate a prototype in the header + h_code = code.globalstate['utility_code_proto'] + + if isinstance(self.node, DefNode): + self.node.generate_function_header( h_code, with_pymethdef=False, proto_only=True) - else: - from . import ModuleNode - entry = self.node.entry - cname = entry.cname - entry.cname = entry.func_cname - - ModuleNode.generate_cfunction_declaration( + else: + from . import ModuleNode + entry = self.node.entry + cname = entry.cname + entry.cname = entry.func_cname + + ModuleNode.generate_cfunction_declaration( entry, env.global_scope(), h_code, definition=True) - - entry.cname = cname - - self.node.generate_function_definitions(env, code) - - def generate_execution_code(self, code): - self.node.generate_execution_code(code) - - -#------------------------------------------------------------------------------------ -# -# Runtime support code -# -#------------------------------------------------------------------------------------ - -if Options.gcc_branch_hints: - branch_prediction_macros = """ -/* Test for GCC > 2.95 */ -#if defined(__GNUC__) \ - && (__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))) - #define likely(x) __builtin_expect(!!(x), 1) - #define unlikely(x) __builtin_expect(!!(x), 0) -#else /* !__GNUC__ or GCC < 2.95 */ - #define likely(x) (x) - #define unlikely(x) (x) -#endif /* __GNUC__ */ -""" -else: - branch_prediction_macros = """ -#define likely(x) (x) -#define unlikely(x) (x) -""" - -#------------------------------------------------------------------------------------ - -printing_utility_code = UtilityCode.load_cached("Print", "Printing.c") -printing_one_utility_code = UtilityCode.load_cached("PrintOne", "Printing.c") - -#------------------------------------------------------------------------------------ - -# Exception raising code -# -# Exceptions are raised by __Pyx_Raise() and stored as plain -# type/value/tb in PyThreadState->curexc_*. When being caught by an -# 'except' statement, curexc_* is moved over to exc_* by -# __Pyx_GetException() - -restore_exception_utility_code = UtilityCode.load_cached("PyErrFetchRestore", "Exceptions.c") -raise_utility_code = UtilityCode.load_cached("RaiseException", "Exceptions.c") -get_exception_utility_code = UtilityCode.load_cached("GetException", "Exceptions.c") -swap_exception_utility_code = UtilityCode.load_cached("SwapException", "Exceptions.c") -reset_exception_utility_code = UtilityCode.load_cached("SaveResetException", "Exceptions.c") -traceback_utility_code = UtilityCode.load_cached("AddTraceback", "Exceptions.c") - -#------------------------------------------------------------------------------------ - + + entry.cname = cname + + self.node.generate_function_definitions(env, code) + + def generate_execution_code(self, code): + self.node.generate_execution_code(code) + + +#------------------------------------------------------------------------------------ +# +# Runtime support code +# +#------------------------------------------------------------------------------------ + +if Options.gcc_branch_hints: + branch_prediction_macros = """ +/* Test for GCC > 2.95 */ +#if defined(__GNUC__) \ + && (__GNUC__ > 2 || (__GNUC__ == 2 && (__GNUC_MINOR__ > 95))) + #define likely(x) __builtin_expect(!!(x), 1) + #define unlikely(x) __builtin_expect(!!(x), 0) +#else /* !__GNUC__ or GCC < 2.95 */ + #define likely(x) (x) + #define unlikely(x) (x) +#endif /* __GNUC__ */ +""" +else: + branch_prediction_macros = """ +#define likely(x) (x) +#define unlikely(x) (x) +""" + +#------------------------------------------------------------------------------------ + +printing_utility_code = UtilityCode.load_cached("Print", "Printing.c") +printing_one_utility_code = UtilityCode.load_cached("PrintOne", "Printing.c") + +#------------------------------------------------------------------------------------ + +# Exception raising code +# +# Exceptions are raised by __Pyx_Raise() and stored as plain +# type/value/tb in PyThreadState->curexc_*. When being caught by an +# 'except' statement, curexc_* is moved over to exc_* by +# __Pyx_GetException() + +restore_exception_utility_code = UtilityCode.load_cached("PyErrFetchRestore", "Exceptions.c") +raise_utility_code = UtilityCode.load_cached("RaiseException", "Exceptions.c") +get_exception_utility_code = UtilityCode.load_cached("GetException", "Exceptions.c") +swap_exception_utility_code = UtilityCode.load_cached("SwapException", "Exceptions.c") +reset_exception_utility_code = UtilityCode.load_cached("SaveResetException", "Exceptions.c") +traceback_utility_code = UtilityCode.load_cached("AddTraceback", "Exceptions.c") + +#------------------------------------------------------------------------------------ + get_exception_tuple_utility_code = UtilityCode( proto=""" static PyObject *__Pyx_GetExceptionTuple(PyThreadState *__pyx_tstate); /*proto*/ -""", +""", # I doubt that calling __Pyx_GetException() here is correct as it moves # the exception from tstate->curexc_* to tstate->exc_*, which prevents # exception handlers later on from receiving it. # NOTE: "__pyx_tstate" may be used by __Pyx_GetException() macro impl = """ static PyObject *__Pyx_GetExceptionTuple(CYTHON_UNUSED PyThreadState *__pyx_tstate) { - PyObject *type = NULL, *value = NULL, *tb = NULL; - if (__Pyx_GetException(&type, &value, &tb) == 0) { - PyObject* exc_info = PyTuple_New(3); - if (exc_info) { - Py_INCREF(type); - Py_INCREF(value); - Py_INCREF(tb); - PyTuple_SET_ITEM(exc_info, 0, type); - PyTuple_SET_ITEM(exc_info, 1, value); - PyTuple_SET_ITEM(exc_info, 2, tb); - return exc_info; - } - } - return NULL; -} -""", + PyObject *type = NULL, *value = NULL, *tb = NULL; + if (__Pyx_GetException(&type, &value, &tb) == 0) { + PyObject* exc_info = PyTuple_New(3); + if (exc_info) { + Py_INCREF(type); + Py_INCREF(value); + Py_INCREF(tb); + PyTuple_SET_ITEM(exc_info, 0, type); + PyTuple_SET_ITEM(exc_info, 1, value); + PyTuple_SET_ITEM(exc_info, 2, tb); + return exc_info; + } + } + return NULL; +} +""", requires=[get_exception_utility_code]) |