float(self.value),
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
- else:
- self.c99_complex = code.globalstate.directives['c99_complex']
+class NewExprNode(AtomicExprNode):
+
+ # C++ new statement
+ #
+ # cppclass string c++ class to create
+ # template_parameters None or [ExprNode] temlate parameters, if any
+
+ def analyse_types(self, env):
+ entry = env.lookup(self.cppclass)
+ if entry is None or not entry.is_cpp_class:
+ error(self.pos, "new operator can only be applied to a C++ class")
+ return
+ self.cpp_check(env)
+ if self.template_parameters is not None:
+ template_types = [v.analyse_as_type(env) for v in self.template_parameters]
+ type = entry.type.specialize_here(self.pos, template_types)
+ else:
+ type = entry.type
+ constructor = type.scope.lookup(u'<init>')
+ if constructor is None:
+ return_type = PyrexTypes.CFuncType(type, [])
+ return_type = PyrexTypes.CPtrType(return_type)
+ type.scope.declare_cfunction(u'<init>', return_type, self.pos)
+ constructor = type.scope.lookup(u'<init>')
+ self.class_type = type
+ self.entry = constructor
+ self.type = constructor.type
+
+ def generate_result_code(self, code):
+ pass
+
+ def calculate_result_code(self):
+ return "new " + self.class_type.declaration_code("")
+
class NameNode(AtomicExprNode):
# Reference to a local or global variable name.
entry = self.entry
type = entry.type
self.type = type
- if entry.is_pyglobal or entry.is_builtin:
- assert type.is_pyobject, "Python global or builtin not a Python object"
- self.interned_cname = self.entry.interned_cname = \
- env.intern_identifier(self.entry.name)
def check_identifier_kind(self):
- #print "NameNode.check_identifier_kind:", self.entry.name ###
- #print self.entry.__dict__ ###
+ # Check that this is an appropriate kind of name for use in an
+ # expression. Also finds the variable entry associated with
+ # an extension type.
entry = self.entry
- #entry.used = 1
+ if entry.is_type and entry.type.is_extension_type:
+ self.type_entry = entry
if not (entry.is_const or entry.is_variable
- or entry.is_builtin or entry.is_cfunction):
+ or entry.is_builtin or entry.is_cfunction
+ or entry.is_cpp_class):
if self.entry.as_variable:
self.entry = self.entry.as_variable
else:
def analyse_as_type(self, env):
base_type = self.base.analyse_as_type(env)
if base_type and not base_type.is_pyobject:
- return PyrexTypes.CArrayType(base_type, int(self.index.compile_time_value(env)))
+ if base_type.is_cpp_class:
+ if isinstance(self.index, TupleExprNode):
+ template_values = self.index.args
+ else:
+ template_values = [self.index]
+ import Nodes
+ type_node = Nodes.TemplatedTypeNode(
+ pos = self.pos,
+ positional_args = template_values,
+ keyword_args = None)
+ return type_node.analyse(env, base_type = base_type)
+ else:
+ return PyrexTypes.CArrayType(base_type, int(self.index.compile_time_value(env)))
return None
+ def type_dependencies(self, env):
+ return self.base.type_dependencies(env)
+
+ def infer_type(self, env):
+ if isinstance(self.base, (StringNode, UnicodeNode)): # FIXME: BytesNode?
+ return py_object_type
+ base_type = self.base.infer_type(env)
+ if base_type.is_ptr or base_type.is_array:
+ return base_type.base_type
+ else:
+ # TODO: Handle buffers (hopefully without too much redundancy).
+ return py_object_type
+
def analyse_types(self, env):
self.analyse_base_and_index_types(env, getting = 1)
max_nargs = len(func_type.args)
expected_nargs = max_nargs - func_type.optional_arg_count
actual_nargs = len(self.args)
- if actual_nargs < expected_nargs \
- or (not func_type.has_varargs and actual_nargs > max_nargs):
- expected_str = str(expected_nargs)
- if func_type.has_varargs:
- expected_str = "at least " + expected_str
- elif func_type.optional_arg_count:
- if actual_nargs < max_nargs:
- expected_str = "at least " + expected_str
- else:
- expected_str = "at most " + str(max_nargs)
- error(self.pos,
- "Call with wrong number of arguments (expected %s, got %s)"
- % (expected_str, actual_nargs))
- self.args = None
- self.type = PyrexTypes.error_type
- self.result_code = "<error>"
- return
+ if func_type.optional_arg_count and expected_nargs != actual_nargs:
+ self.has_optional_args = 1
+ self.is_temp = 1
# Coerce arguments
for i in range(min(max_nargs, actual_nargs)):
formal_type = func_type.args[i].type
if self.is_py_operation():
self.coerce_operand_to_pyobject(env)
self.type = py_object_type
- self.gil_check(env)
self.is_temp = 1
+ elif self.is_cpp_operation():
+ self.analyse_cpp_operation(env)
else:
self.analyse_c_operation(env)
def is_py_operation(self):
return self.operand.type.is_pyobject
+ def nogil_check(self, env):
+ if self.is_py_operation():
+ self.gil_error()
+
++
+ def is_cpp_operation(self):
+ return self.operand.type.is_cpp_class
+
def coerce_operand_to_pyobject(self, env):
self.operand = self.operand.coerce_to_pyobject(env)
if self.is_py_operation():
self.coerce_operands_to_pyobjects(env)
self.type = py_object_type
- self.gil_check(env)
self.is_temp = 1
- if Options.incref_local_binop and self.operand1.type.is_pyobject:
- self.operand1 = self.operand1.coerce_to_temp(env)
+ elif self.is_cpp_operation():
+ self.analyse_cpp_operation(env)
else:
self.analyse_c_operation(env)
def is_py_operation(self):
- return (self.operand1.type.is_pyobject
- or self.operand2.type.is_pyobject)
+ return self.is_py_operation_types(self.operand1.type, self.operand2.type)
+ def is_cpp_operation(self):
+ type1 = self.operand1.type
+ type2 = self.operand2.type
+ if type1.is_ptr:
+ type1 = type1.base_type
+ if type2.is_ptr:
+ type2 = type2.base_type
+ return (type1.is_cpp_class
+ or type2.is_cpp_class)
+
+ def is_py_operation_types(self, type1, type2):
+ return type1.is_pyobject or type2.is_pyobject
+
+ def result_type(self, type1, type2):
+ if self.is_py_operation_types(type1, type2):
+ return py_object_type
+ else:
+ return self.compute_c_result_type(type1, type2)
+
+ def nogil_check(self, env):
+ if self.is_py_operation():
+ self.gil_error()
+
def coerce_operands_to_pyobjects(self, env):
self.operand1 = self.operand1.coerce_to_pyobject(env)
self.operand2 = self.operand2.coerce_to_pyobject(env)
result = result and cascade.compile_time_value(operand2, denv)
return result
- def is_python_comparison(self):
- return (self.has_python_operands()
- or (self.cascade and self.cascade.is_python_comparison())
- or self.operator in ('in', 'not_in'))
+ def is_cpp_comparison(self):
+ type1 = self.operand1.type
+ type2 = self.operand2.type
+ if type1.is_ptr:
+ type1 = type1.base_type
+ if type2.is_ptr:
+ type2 = type2.base_type
+ return type1.is_cpp_class or type2.is_cpp_class
+
+ def find_common_int_type(self, env, op, operand1, operand2):
+ # type1 != type2 and at least one of the types is not a C int
+ type1 = operand1.type
+ type2 = operand2.type
+ type1_can_be_int = False
+ type2_can_be_int = False
+
+ if isinstance(operand1, (StringNode, BytesNode)) \
+ and operand1.can_coerce_to_char_literal():
+ type1_can_be_int = True
+ if isinstance(operand2, (StringNode, BytesNode)) \
+ and operand2.can_coerce_to_char_literal():
+ type2_can_be_int = True
+
+ if type1.is_int:
+ if type2_can_be_int:
+ return type1
+ elif type2.is_int:
+ if type1_can_be_int:
+ return type2
+ elif type1_can_be_int:
+ if type2_can_be_int:
+ return PyrexTypes.c_uchar_type
- def is_python_result(self):
- return ((self.has_python_operands() and self.operator not in ('is', 'is_not', 'in', 'not_in'))
- or (self.cascade and self.cascade.is_python_result()))
+ return None
- def check_types(self, env, operand1, op, operand2):
- if operand1.type.is_complex or operand2.type.is_complex:
- if op not in ('==', '!='):
- error(self.pos, "complex types unordered")
- common_type = PyrexTypes.widest_numeric_type(operand1.type, operand2.type)
- self.operand1 = operand1.coerce_to(common_type, env)
- self.operand2 = operand2.coerce_to(common_type, env)
- elif not self.types_okay(operand1, op, operand2):
- error(self.pos, "Invalid types for '%s' (%s, %s)" %
- (self.operator, operand1.type, operand2.type))
-
- def types_okay(self, operand1, op, operand2):
+ def find_common_type(self, env, op, operand1, common_type=None):
+ operand2 = self.operand2
type1 = operand1.type
type2 = operand2.type
- if type1.is_error or type2.is_error:
- return 1
- if type1.is_pyobject: # type2 will be, too
- return 1
- elif type1.is_ptr or type1.is_array:
- return type1.is_null_ptr or type2.is_null_ptr \
- or ((type2.is_ptr or type2.is_array)
- and type1.base_type.same_as(type2.base_type))
- elif ((type1.is_numeric and type2.is_numeric
- or type1.is_enum and (type1 is type2 or type2.is_int)
- or type1.is_int and type2.is_enum)
- and op not in ('is', 'is_not')):
- return 1
+
+ new_common_type = None
+
+ # catch general errors
+ if type1 == str_type and (type2.is_string or type2 in (bytes_type, unicode_type)) or \
+ type2 == str_type and (type1.is_string or type1 in (bytes_type, unicode_type)):
+ error(self.pos, "Comparisons between bytes/unicode and str are not portable to Python 3")
+ new_common_type = error_type
+
+ # try to use numeric comparisons where possible
+ elif type1.is_complex or type2.is_complex:
+ if op not in ('==', '!='):
+ error(self.pos, "complex types are unordered")
+ new_common_type = error_type
+ if type1.is_pyobject:
+ new_common_type = type1
+ elif type2.is_pyobject:
+ new_common_type = type2
+ else:
+ new_common_type = PyrexTypes.widest_numeric_type(type1, type2)
+ elif type1.is_numeric and type2.is_numeric:
+ new_common_type = PyrexTypes.widest_numeric_type(type1, type2)
+ elif common_type is None or not common_type.is_pyobject:
+ new_common_type = self.find_common_int_type(env, op, operand1, operand2)
+
+ if new_common_type is None:
+ # fall back to generic type compatibility tests
+ if type1 == type2:
+ new_common_type = type1
+ elif type1.is_pyobject or type2.is_pyobject:
+ if type2.is_numeric or type2.is_string:
+ if operand2.check_for_coercion_error(type1):
+ new_common_type = error_type
+ else:
+ new_common_type = py_object_type
+ elif type1.is_numeric or type1.is_string:
+ if operand1.check_for_coercion_error(type2):
+ new_common_type = error_type
+ else:
+ new_common_type = py_object_type
+ elif py_object_type.assignable_from(type1) and py_object_type.assignable_from(type2):
+ new_common_type = py_object_type
+ else:
+ # one Python type and one non-Python type, not assignable
+ self.invalid_types_error(operand1, op, operand2)
+ new_common_type = error_type
+ elif type1.assignable_from(type2):
+ new_common_type = type1
+ elif type2.assignable_from(type1):
+ new_common_type = type2
+ else:
+ # C types that we couldn't handle up to here are an error
+ self.invalid_types_error(operand1, op, operand2)
+ new_common_type = error_type
+
+ # recursively merge types
+ if common_type is None or new_common_type.is_error:
+ common_type = new_common_type
else:
- return type1.is_cfunction and type1.is_cfunction and type1 == type2
+ # we could do a lot better by splitting the comparison
+ # into a non-Python part and a Python part, but this is
+ # safer for now
+ common_type = PyrexTypes.spanning_type(common_type, new_common_type)
+
+ if self.cascade:
+ common_type = self.cascade.find_common_type(env, self.operator, operand2, common_type)
+
+ return common_type
+
+ def invalid_types_error(self, operand1, op, operand2):
+ error(self.pos, "Invalid types for '%s' (%s, %s)" %
+ (op, operand1.type, operand2.type))
+
+ def is_python_comparison(self):
+ return (self.has_python_operands()
+ or (self.cascade and self.cascade.is_python_comparison())
+ or self.operator in ('in', 'not_in'))
+
+ def coerce_operands_to(self, dst_type, env):
+ operand2 = self.operand2
+ if operand2.type != dst_type:
+ self.operand2 = operand2.coerce_to(dst_type, env)
+ if self.cascade:
+ self.cascade.coerce_operands_to(dst_type, env)
+
+ def is_python_result(self):
+ return ((self.has_python_operands() and
+ self.operator not in ('is', 'is_not', 'in', 'not_in'))
+ or (self.cascade and self.cascade.is_python_result()))
def generate_operation_code(self, code, result_code,
operand1, op , operand2):
def analyse_types(self, env):
self.operand1.analyse_types(env)
self.operand2.analyse_types(env)
+ if self.is_cpp_comparison():
+ self.analyse_cpp_comparison(env)
+ return
if self.cascade:
- self.cascade.analyse_types(env, self.operand2)
- self.is_pycmp = self.is_python_comparison()
- if self.is_pycmp:
- self.coerce_operands_to_pyobjects(env)
- if self.has_int_operands():
- self.coerce_chars_to_ints(env)
+ self.cascade.analyse_types(env)
+
+ if self.operator in ('in', 'not_in'):
+ common_type = py_object_type
+ self.is_pycmp = True
+ else:
+ common_type = self.find_common_type(env, self.operator, self.operand1)
+ self.is_pycmp = common_type.is_pyobject
+
+ if not common_type.is_error:
+ if self.operand1.type != common_type:
+ self.operand1 = self.operand1.coerce_to(common_type, env)
+ self.coerce_operands_to(common_type, env)
+
if self.cascade:
self.operand2 = self.operand2.coerce_to_simple(env)
self.cascade.coerce_cascaded_operands_to_temp(env)
cdr = cdr.cascade
if self.is_pycmp or self.cascade:
self.is_temp = 1
-
+
+ def analyse_cpp_comparison(self, env):
+ type1 = self.operand1.type
+ type2 = self.operand2.type
+ if type1.is_ptr:
+ type1 = type1.base_type
+ if type2.is_ptr:
+ type2 = type2.base_type
+ entry = env.lookup(type1.name)
+ function = entry.type.scope.lookup("operator%s" % self.operator)
+ if not function:
+ error(self.pos, "Invalid types for '%s' (%s, %s)" %
+ (self.operator, type1, type2))
+ return
+ entry = PyrexTypes.best_match([self.operand1, self.operand2], function.all_alternatives(), self.pos)
+ if entry is None:
+ self.type = PyrexTypes.error_type
+ self.result_code = "<error>"
+ return
+ if (entry.type.is_ptr):
+ self.type = entry.type.base_type.return_type
+ else:
+ self.type = entry.type.return_type
+
- def check_operand_types(self, env):
- self.check_types(env,
- self.operand1, self.operator, self.operand2)
- if self.cascade:
- self.cascade.check_operand_types(env, self.operand2)
-
def has_python_operands(self):
return (self.operand1.type.is_pyobject
or self.operand2.type.is_pyobject)
import Naming
import PyrexTypes
import TypeSlots
- from PyrexTypes import py_object_type, error_type, CTypedefType, CFuncType
+ from PyrexTypes import py_object_type, error_type, CFuncType
from Symtab import ModuleScope, LocalScope, GeneratorLocalScope, \
- StructOrUnionScope, PyClassScope, CClassScope
+ StructOrUnionScope, PyClassScope, CClassScope, CppClassScope
- from Cython.Utils import open_new_file, replace_suffix, UtilityCode
+ from Cython.Utils import open_new_file, replace_suffix
+ from Code import UtilityCode
from StringEncoding import EncodedString, escape_byte_string, split_docstring
import Options
import ControlFlow
gil_message = "Operation"
- def gil_check(self, env):
- if env.nogil:
- self.gil_error()
+ nogil_check = None
- def gil_error(self):
+ 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
name = None
- def analyse(self, env, could_be_name = False):
- base_type = self.base_type_node.analyse(env)
+ 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
- import Buffer
+
+ if base_type.is_cpp_class:
+ if len(self.keyword_args.key_value_pairs) != 0:
+ error(self.pos, "c++ templates cannot take keyword arguments");
+ self.type = PyrexTypes.error_type
+ else:
+ template_types = []
+ for template_node in self.positional_args:
+ template_types.append(template_node.analyse_as_type(env))
+ self.type = base_type.specialize_here(self.pos, template_types)
+
+ else:
+
+ import Buffer
- options = Buffer.analyse_buffer_options(
- self.pos,
- env,
- self.positional_args,
- self.keyword_args,
- base_type.buffer_defaults)
+ 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.iteritems() ])
++ 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.iteritems() ])
+
- self.type = PyrexTypes.BufferType(base_type, **options)
+ self.type = PyrexTypes.BufferType(base_type, **options)
-
return self.type
class CComplexBaseTypeNode(CBaseTypeNode):
def analyse_expressions(self, env):
for arg in self.args:
arg.analyse_target_expression(env, None)
- if not arg.type.is_pyobject:
- error(arg.pos, "Deletion of non-Python object")
+ if arg.type.is_pyobject:
+ self.gil_check(env)
+ 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")
+ else:
+ error(arg.pos, "Deletion of non-Python, non-C++ object")
#arg.release_target_temp(env)
+ 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):
self.type = type
self.pos = pos
self.init = init
+ self.overloaded_alternatives = []
+ self.assignments = []
+
+ def __repr__(self):
+ return "Entry(name=%s, type=%s)" % (self.name, self.type)
def redeclared(self, pos):
error(pos, "'%s' does not match previous declaration" % self.name)
return 1
return 0
+ def infer_types(self):
+ from TypeInference import get_type_inferer
+ get_type_inferer().infer_types(self)
++
+ def is_cpp(self):
+ outer = self.outer_scope
+ if outer is None:
+ return False
+ else:
+ return outer.is_cpp()
class PreImportScope(Scope):
# included_files [string] Cython sources included with 'include'
# pxd_file_loaded boolean Corresponding .pxd file has been processed
# cimported_modules [ModuleScope] Modules imported with cimport
- # new_interned_string_entries [Entry] New interned strings waiting to be declared
- # interned_nums [int/long] Interned numeric constants
- # all_pystring_entries [Entry] Python string consts from all scopes
# types_imported {PyrexType : 1} Set of types for which import code generated
# has_import_star boolean Module contains import *
+ # cpp boolean Compiling a C++ file
is_module_scope = 1
has_import_star = 0
var_entry.is_readonly = 1
entry.as_variable = var_entry
- class LocalScope(Scope):
+ def is_cpp(self):
+ return self.cpp
+
+ def infer_types(self):
+ from TypeInference import PyObjectTypeInferer
+ PyObjectTypeInferer().infer_types(self)
+
+ class LocalScope(Scope):
- def __init__(self, name, outer_scope):
- Scope.__init__(self, name, outer_scope, outer_scope)
+ def __init__(self, name, outer_scope, parent_scope = None):
+ if parent_scope is None:
+ parent_scope = outer_scope
+ Scope.__init__(self, name, outer_scope, parent_scope)
def mangle(self, prefix, name):
return prefix + name
base_entry.visibility, base_entry.func_modifiers)
entry.is_inherited = 1
- def allocate_temp(self, type):
- return Scope.allocate_temp(self.global_scope(), type)
-
- def release_temp(self, cname):
- return Scope.release_temp(self.global_scope(), cname)
+
+class CppClassScope(Scope):
+ # Namespace of a C++ class.
+ inherited_var_entries = []
+
+ def __init__(self, name, outer_scope):
+ Scope.__init__(self, name, outer_scope, None)
+ self.directives = outer_scope.directives
+
+ def declare_var(self, name, type, pos,
+ cname = None, visibility = 'extern', is_cdef = 0, allow_pyobject = 0):
+ # Add an entry for an attribute.
+ if not cname:
+ cname = name
+ if type.is_cfunction:
+ type = PyrexTypes.CPtrType(type)
+ entry = self.declare(name, cname, type, pos, visibility)
+ entry.is_variable = 1
+ self.var_entries.append(entry)
+ if type.is_pyobject and not allow_pyobject:
+ error(pos,
+ "C++ class member cannot be a Python object")
+ return entry
+
+ def declare_cfunction(self, name, type, pos,
+ cname = None, visibility = 'extern', defining = 0,
+ api = 0, in_pxd = 0, modifiers = ()):
+ if name == self.name.split('::')[-1] and cname is None:
+ name = '<init>'
+ entry = self.declare_var(name, type, pos, cname, visibility)
+
+ def declare_inherited_cpp_attributes(self, base_scope):
+ # Declare entries for all the C++ attributes of an
+ # inherited type, with cnames modified appropriately
+ # to work with this type.
+ for base_entry in \
+ base_scope.inherited_var_entries + base_scope.var_entries:
+ entry = self.declare(base_entry.name, base_entry.cname,
+ base_entry.type, None, 'extern')
+ entry.is_variable = 1
+ self.inherited_var_entries.append(entry)
+ for base_entry in base_scope.cfunc_entries:
+ entry = self.declare_cfunction(base_entry.name, base_entry.type,
+ base_entry.pos, base_entry.cname,
+ base_entry.visibility, base_entry.func_modifiers)
+ entry.is_inherited = 1
+
+ def specialize(self, values):
+ scope = CppClassScope(self.name, self.outer_scope)
+ for entry in self.entries.values():
+ scope.declare_var(entry.name,
+ entry.type.specialize(values),
+ entry.pos,
+ entry.cname,
+ entry.visibility)
+ return scope
+
+
class PropertyScope(Scope):
# Scope holding the __get__, __set__ and __del__ methods for
# a property of an extension type.