Author: Wim Lavrijsen <wlavrij...@lbl.gov> Branch: cppyy-packaging Changeset: r92722:b547e0ca6aba Date: 2017-10-11 11:34 -0700 http://bitbucket.org/pypy/pypy/changeset/b547e0ca6aba/
Log: naming consistency and equivalence with CPython diff --git a/pypy/module/_cppyy/__init__.py b/pypy/module/_cppyy/__init__.py --- a/pypy/module/_cppyy/__init__.py +++ b/pypy/module/_cppyy/__init__.py @@ -7,7 +7,7 @@ interpleveldefs = { '_resolve_name' : 'interp_cppyy.resolve_name', '_scope_byname' : 'interp_cppyy.scope_byname', - '_template_byname' : 'interp_cppyy.template_byname', + '_is_template' : 'interp_cppyy.is_template', '_std_string_name' : 'interp_cppyy.std_string_name', '_set_class_generator' : 'interp_cppyy.set_class_generator', '_set_function_generator': 'interp_cppyy.set_function_generator', @@ -15,6 +15,7 @@ '_get_nullptr' : 'interp_cppyy.get_nullptr', 'CPPClassBase' : 'interp_cppyy.W_CPPClass', 'addressof' : 'interp_cppyy.addressof', + '_bind_object' : 'interp_cppyy._bind_object', 'bind_object' : 'interp_cppyy.bind_object', } diff --git a/pypy/module/_cppyy/converter.py b/pypy/module/_cppyy/converter.py --- a/pypy/module/_cppyy/converter.py +++ b/pypy/module/_cppyy/converter.py @@ -495,9 +495,9 @@ def _unwrap_object(self, space, w_obj): from pypy.module._cppyy.interp_cppyy import W_CPPClass if isinstance(w_obj, W_CPPClass): - if capi.c_is_subtype(space, w_obj.cppclass, self.clsdecl): + if capi.c_is_subtype(space, w_obj.clsdecl, self.clsdecl): rawobject = w_obj.get_rawobject() - offset = capi.c_base_offset(space, w_obj.cppclass, self.clsdecl, rawobject, 1) + offset = capi.c_base_offset(space, w_obj.clsdecl, self.clsdecl, rawobject, 1) obj_address = capi.direct_ptradd(rawobject, offset) return rffi.cast(capi.C_OBJECT, obj_address) raise oefmt(space.w_TypeError, @@ -527,7 +527,7 @@ def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) from pypy.module._cppyy import interp_cppyy - return interp_cppyy.wrap_cppobject(space, address, self.clsdecl, do_cast=False) + return interp_cppyy.wrap_cppinstance(space, address, self.clsdecl, do_cast=False) def to_memory(self, space, w_obj, w_value, offset): self._is_abstract(space) @@ -548,7 +548,7 @@ def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) from pypy.module._cppyy import interp_cppyy - return interp_cppyy.wrap_cppobject(space, address, self.clsdecl, do_cast=False) + return interp_cppyy.wrap_cppinstance(space, address, self.clsdecl, do_cast=False) def to_memory(self, space, w_obj, w_value, offset): address = rffi.cast(rffi.VOIDPP, self._get_raw_address(space, w_obj, offset)) @@ -582,8 +582,8 @@ def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) from pypy.module._cppyy import interp_cppyy - return interp_cppyy.wrap_cppobject(space, address, self.clsdecl, - do_cast=False, is_ref=True) + return interp_cppyy.wrap_cppinstance( + space, address, self.clsdecl, do_cast=False, is_ref=True) class StdStringConverter(InstanceConverter): @@ -606,7 +606,7 @@ assign = self.clsdecl.get_overload("__assign__") from pypy.module._cppyy import interp_cppyy assign.call( - interp_cppyy.wrap_cppobject(space, address, self.clsdecl, do_cast=False), [w_value]) + interp_cppyy.wrap_cppinstance(space, address, self.clsdecl, do_cast=False), [w_value]) except Exception: InstanceConverter.to_memory(self, space, w_obj, w_value, offset) diff --git a/pypy/module/_cppyy/executor.py b/pypy/module/_cppyy/executor.py --- a/pypy/module/_cppyy/executor.py +++ b/pypy/module/_cppyy/executor.py @@ -159,7 +159,7 @@ from pypy.module._cppyy import interp_cppyy long_result = capi.c_call_l(space, cppmethod, cppthis, num_args, args) ptr_result = rffi.cast(capi.C_OBJECT, long_result) - pyres = interp_cppyy.wrap_cppobject(space, ptr_result, self.cppclass) + pyres = interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) return pyres def execute_libffi(self, space, cif_descr, funcaddr, buffer): @@ -167,7 +167,7 @@ result = rffi.ptradd(buffer, cif_descr.exchange_result) from pypy.module._cppyy import interp_cppyy ptr_result = rffi.cast(capi.C_OBJECT, rffi.cast(rffi.VOIDPP, result)[0]) - return interp_cppyy.wrap_cppobject(space, ptr_result, self.cppclass) + return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) class InstancePtrPtrExecutor(InstancePtrExecutor): @@ -176,7 +176,7 @@ voidp_result = capi.c_call_r(space, cppmethod, cppthis, num_args, args) ref_address = rffi.cast(rffi.VOIDPP, voidp_result) ptr_result = rffi.cast(capi.C_OBJECT, ref_address[0]) - return interp_cppyy.wrap_cppobject(space, ptr_result, self.cppclass) + return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass) def execute_libffi(self, space, cif_descr, funcaddr, buffer): from pypy.module._cppyy.interp_cppyy import FastCallNotPossible @@ -188,8 +188,8 @@ from pypy.module._cppyy import interp_cppyy long_result = capi.c_call_o(space, cppmethod, cppthis, num_args, args, self.cppclass) ptr_result = rffi.cast(capi.C_OBJECT, long_result) - return interp_cppyy.wrap_cppobject(space, ptr_result, self.cppclass, - do_cast=False, python_owns=True, fresh=True) + return interp_cppyy.wrap_cppinstance(space, ptr_result, self.cppclass, + do_cast=False, python_owns=True, fresh=True) def execute_libffi(self, space, cif_descr, funcaddr, buffer): from pypy.module._cppyy.interp_cppyy import FastCallNotPossible diff --git a/pypy/module/_cppyy/interp_cppyy.py b/pypy/module/_cppyy/interp_cppyy.py --- a/pypy/module/_cppyy/interp_cppyy.py +++ b/pypy/module/_cppyy/interp_cppyy.py @@ -2,7 +2,7 @@ from pypy.interpreter.error import OperationError, oefmt from pypy.interpreter.gateway import interp2app, unwrap_spec -from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty, interp_attrproperty_w +from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty from pypy.interpreter.baseobjspace import W_Root from rpython.rtyper.lltypesystem import rffi, lltype, llmemory @@ -33,16 +33,21 @@ class State(object): def __init__(self, space): + # final scoped name -> opaque handle self.cppscope_cache = { - "void" : W_CPPClassDecl(space, "void", capi.C_NULL_TYPE) } + 'void' : W_CPPClassDecl(space, capi.C_NULL_TYPE, 'void') } + # opaque handle -> app-level python class + self.cppclass_registry = {} + # app-level class generator callback + self.w_clgen_callback = None + # app-level function generator callback (currently not used) + self.w_fngen_callback = None + # C++11's nullptr self.w_nullptr = None - self.cpptemplate_cache = {} - self.cppclass_registry = {} - self.w_clgen_callback = None - self.w_fngen_callback = None def get_nullptr(space): - if hasattr(space, "fake"): + # construct a unique address that compares to NULL, serves as nullptr + if hasattr(space, 'fake'): raise NotImplementedError state = space.fromcache(State) if state.w_nullptr is None: @@ -58,52 +63,48 @@ state.w_nullptr = nullarr return state.w_nullptr -@unwrap_spec(name='text') -def resolve_name(space, name): - return space.newtext(capi.c_resolve_name(space, name)) +@unwrap_spec(scoped_name='text') +def resolve_name(space, scoped_name): + return space.newtext(capi.c_resolve_name(space, scoped_name)) -@unwrap_spec(name='text') -def scope_byname(space, name): - true_name = capi.c_resolve_name(space, name) +# memoized lookup of handles by final, scoped, name of classes/namespaces +@unwrap_spec(final_scoped_name='text') +def scope_byname(space, final_scoped_name): state = space.fromcache(State) try: - return state.cppscope_cache[true_name] + return state.cppscope_cache[final_scoped_name] except KeyError: pass - opaque_handle = capi.c_get_scope_opaque(space, true_name) + opaque_handle = capi.c_get_scope_opaque(space, final_scoped_name) assert lltype.typeOf(opaque_handle) == capi.C_SCOPE if opaque_handle: - final_name = capi.c_final_name(space, opaque_handle) - if capi.c_is_namespace(space, opaque_handle): - cppscope = W_CPPNamespaceDecl(space, final_name, opaque_handle) - elif capi.c_has_complex_hierarchy(space, opaque_handle): - cppscope = W_CPPComplexClassDecl(space, final_name, opaque_handle) + isns = capi.c_is_namespace(space, opaque_handle) + if isns: + cppscope = W_CPPNamespaceDecl(space, opaque_handle, final_scoped_name) else: - cppscope = W_CPPClassDecl(space, final_name, opaque_handle) - state.cppscope_cache[name] = cppscope + if capi.c_has_complex_hierarchy(space, opaque_handle): + cppscope = W_CPPComplexClassDecl(space, opaque_handle, final_scoped_name) + else: + cppscope = W_CPPClassDecl(space, opaque_handle, final_scoped_name) - cppscope._build_methods() - cppscope._find_datamembers() + # store in the cache to prevent recursion + state.cppscope_cache[final_scoped_name] = cppscope + + if not isns: + # build methods/data; TODO: also defer this for classes (a functional __dir__ + # and instrospection for help() is enough and allows more lazy loading) + cppscope._build_methods() + cppscope._find_datamembers() + return cppscope return None -@unwrap_spec(name='text') -def template_byname(space, name): - state = space.fromcache(State) - try: - return state.cpptemplate_cache[name] - except KeyError: - pass - - if capi.c_is_template(space, name): - cpptemplate = W_CPPTemplateType(space, name) - state.cpptemplate_cache[name] = cpptemplate - return cpptemplate - - return None +@unwrap_spec(final_scoped_name='text') +def is_template(space, final_scoped_name): + return space.wrap(capi.c_is_template(space, final_scoped_name)) def std_string_name(space): return space.newtext(capi.std_string_name) @@ -591,15 +592,17 @@ @jit.unroll_safe @unwrap_spec(args_w='args_w') def call(self, w_cppinstance, args_w): + # TODO: factor out the following: + if capi.c_is_abstract(self.space, self.scope.handle): + raise oefmt(self.space.w_TypeError, + "cannot instantiate abstract class '%s'", + self.scope.name) w_result = W_CPPOverload.call(self, w_cppinstance, args_w) newthis = rffi.cast(capi.C_OBJECT, self.space.uint_w(w_result)) cppinstance = self.space.interp_w(W_CPPClass, w_cppinstance, can_be_None=True) if cppinstance is not None: cppinstance._rawobject = newthis memory_regulator.register(cppinstance) - return w_cppinstance - return wrap_cppobject(self.space, newthis, self.functions[0].scope, - do_cast=False, python_owns=True, fresh=True) def __repr__(self): return "W_CPPConstructorOverload(%s)" % [f.signature() for f in self.functions] @@ -643,8 +646,8 @@ def _get_offset(self, cppinstance): if cppinstance: - assert lltype.typeOf(cppinstance.cppclass.handle) == lltype.typeOf(self.scope.handle) - offset = self.offset + cppinstance.cppclass.get_base_offset(cppinstance, self.scope) + assert lltype.typeOf(cppinstance.clsdecl.handle) == lltype.typeOf(self.scope.handle) + offset = self.offset + cppinstance.clsdecl.get_base_offset(cppinstance, self.scope) else: offset = self.offset return offset @@ -705,12 +708,12 @@ return space.w_False class W_CPPScopeDecl(W_Root): - _attrs_ = ['space', 'name', 'handle', 'methods', 'datamembers'] + _attrs_ = ['space', 'handle', 'name', 'methods', 'datamembers'] _immutable_fields_ = ['handle', 'name'] - def __init__(self, space, name, opaque_handle): + def __init__(self, space, opaque_handle, final_scoped_name): self.space = space - self.name = name + self.name = final_scoped_name assert lltype.typeOf(opaque_handle) == capi.C_SCOPE self.handle = opaque_handle self.methods = {} @@ -769,6 +772,9 @@ # classes for inheritance. Both are python classes, though, and refactoring # may be in order at some point. class W_CPPNamespaceDecl(W_CPPScopeDecl): + _attrs_ = ['space', 'handle', 'name', 'methods', 'datamembers'] + _immutable_fields_ = ['handle', 'name'] + def _make_cppfunction(self, pyname, index): num_args = capi.c_method_num_args(self.space, self, index) args_required = capi.c_method_req_args(self.space, self, index) @@ -779,9 +785,6 @@ arg_defs.append((arg_type, arg_dflt)) return CPPFunction(self.space, self, index, arg_defs, args_required) - def _build_methods(self): - pass # force lazy lookups in namespaces - def _make_datamember(self, dm_name, dm_idx): type_name = capi.c_datamember_type(self.space, self, dm_idx) offset = capi.c_datamember_offset(self.space, self, dm_idx) @@ -791,9 +794,6 @@ self.datamembers[dm_name] = datamember return datamember - def _find_datamembers(self): - pass # force lazy lookups in namespaces - def find_overload(self, meth_name): indices = capi.c_method_indices_from_name(self.space, self, meth_name) if not indices: @@ -855,18 +855,21 @@ class W_CPPClassDecl(W_CPPScopeDecl): - _attrs_ = ['space', 'name', 'handle', 'methods', 'datamembers'] - _immutable_fields_ = ['handle', 'constructor', 'methods[*]', 'datamembers[*]'] + _attrs_ = ['space', 'handle', 'name', 'methods', 'datamembers'] + _immutable_fields_ = ['handle', 'name', 'methods[*]', 'datamembers[*]'] def _build_methods(self): assert len(self.methods) == 0 methods_temp = {} for i in range(capi.c_num_methods(self.space, self)): idx = capi.c_method_index_at(self.space, self, i) - pyname = helper.map_operator_name(self.space, - capi.c_method_name(self.space, self, idx), - capi.c_method_num_args(self.space, self, idx), - capi.c_method_result_type(self.space, self, idx)) + if capi.c_is_constructor(self.space, self, idx): + pyname = '__init__' + else: + pyname = helper.map_operator_name(self.space, + capi.c_method_name(self.space, self, idx), + capi.c_method_num_args(self.space, self, idx), + capi.c_method_result_type(self.space, self, idx)) cppmethod = self._make_cppfunction(pyname, idx) methods_temp.setdefault(pyname, []).append(cppmethod) # the following covers the case where the only kind of operator[](idx) @@ -883,7 +886,7 @@ # create the overload methods from the method sets for pyname, methods in methods_temp.iteritems(): CPPMethodSort(methods).sort() - if pyname == self.name: + if pyname == '__init__': overload = W_CPPConstructorOverload(self.space, self, methods[:]) else: overload = W_CPPOverload(self.space, self, methods[:]) @@ -934,11 +937,11 @@ raise self.missing_attribute_error(name) def get_base_offset(self, cppinstance, calling_scope): - assert self == cppinstance.cppclass + assert self == cppinstance.clsdecl return 0 def get_cppthis(self, cppinstance, calling_scope): - assert self == cppinstance.cppclass + assert self == cppinstance.clsdecl return cppinstance.get_rawobject() def is_namespace(self): @@ -973,13 +976,13 @@ class W_CPPComplexClassDecl(W_CPPClassDecl): def get_base_offset(self, cppinstance, calling_scope): - assert self == cppinstance.cppclass + assert self == cppinstance.clsdecl offset = capi.c_base_offset(self.space, self, calling_scope, cppinstance.get_rawobject(), 1) return offset def get_cppthis(self, cppinstance, calling_scope): - assert self == cppinstance.cppclass + assert self == cppinstance.clsdecl offset = self.get_base_offset(cppinstance, calling_scope) return capi.direct_ptradd(cppinstance.get_rawobject(), offset) @@ -997,37 +1000,16 @@ W_CPPComplexClassDecl.typedef.acceptable_as_base_class = False -class W_CPPTemplateType(W_Root): - _attrs_ = ['space', 'name'] - _immutable_fields = ['name'] - - def __init__(self, space, name): - self.space = space - self.name = name - - @unwrap_spec(args_w='args_w') - def __call__(self, args_w): - # TODO: this is broken but unused (see pythonify.py) - fullname = "".join([self.name, '<', self.space.text_w(args_w[0]), '>']) - return scope_byname(self.space, fullname) - -W_CPPTemplateType.typedef = TypeDef( - 'CPPTemplateType', - __call__ = interp2app(W_CPPTemplateType.__call__), -) -W_CPPTemplateType.typedef.acceptable_as_base_class = False - - class W_CPPClass(W_Root): - _attrs_ = ['space', 'cppclass', '_rawobject', 'isref', 'python_owns', + _attrs_ = ['space', 'clsdecl', '_rawobject', 'isref', 'python_owns', 'finalizer_registered'] - _immutable_fields_ = ["cppclass", "isref"] + _immutable_fields_ = ['clsdecl', 'isref'] finalizer_registered = False - def __init__(self, space, cppclass, rawobject, isref, python_owns): + def __init__(self, space, decl, rawobject, isref, python_owns): self.space = space - self.cppclass = cppclass + self.clsdecl = decl assert lltype.typeOf(rawobject) == capi.C_OBJECT assert not isref or rawobject self._rawobject = rawobject @@ -1057,7 +1039,7 @@ self._opt_register_finalizer() def get_cppthis(self, calling_scope): - return self.cppclass.get_cppthis(self, calling_scope) + return self.clsdecl.get_cppthis(self, calling_scope) def get_rawobject(self): if not self.isref: @@ -1078,12 +1060,9 @@ return None def instance__init__(self, args_w): - if capi.c_is_abstract(self.space, self.cppclass.handle): - raise oefmt(self.space.w_TypeError, - "cannot instantiate abstract class '%s'", - self.cppclass.name) - constructor_overload = self.cppclass.get_overload(self.cppclass.name) - constructor_overload.call(self, args_w) + raise oefmt(self.space.w_TypeError, + "cannot instantiate abstract class '%s'", + self.clsdecl.name) def instance__eq__(self, w_other): # special case: if other is None, compare pointer-style @@ -1099,7 +1078,7 @@ for name in ["", "__gnu_cxx", "__1"]: nss = scope_byname(self.space, name) meth_idx = capi.c_get_global_operator( - self.space, nss, self.cppclass, other.cppclass, "operator==") + self.space, nss, self.clsdecl, other.clsdecl, "operator==") if meth_idx != -1: f = nss._make_cppfunction("operator==", meth_idx) ol = W_CPPOverload(self.space, nss, [f]) @@ -1118,7 +1097,7 @@ # fallback 2: direct pointer comparison (the class comparison is needed since # the first data member in a struct and the struct have the same address) other = self.space.interp_w(W_CPPClass, w_other, can_be_None=False) # TODO: factor out - iseq = (self._rawobject == other._rawobject) and (self.cppclass == other.cppclass) + iseq = (self._rawobject == other._rawobject) and (self.clsdecl == other.clsdecl) return self.space.newbool(iseq) def instance__ne__(self, w_other): @@ -1134,26 +1113,26 @@ if w_as_builtin is not None: return self.space.len(w_as_builtin) raise oefmt(self.space.w_TypeError, - "'%s' has no length", self.cppclass.name) + "'%s' has no length", self.clsdecl.name) def instance__cmp__(self, w_other): w_as_builtin = self._get_as_builtin() if w_as_builtin is not None: return self.space.cmp(w_as_builtin, w_other) raise oefmt(self.space.w_AttributeError, - "'%s' has no attribute __cmp__", self.cppclass.name) + "'%s' has no attribute __cmp__", self.clsdecl.name) def instance__repr__(self): w_as_builtin = self._get_as_builtin() if w_as_builtin is not None: return self.space.repr(w_as_builtin) return self.space.newtext("<%s object at 0x%x>" % - (self.cppclass.name, rffi.cast(rffi.ULONG, self.get_rawobject()))) + (self.clsdecl.name, rffi.cast(rffi.ULONG, self.get_rawobject()))) def destruct(self): if self._rawobject and not self.isref: memory_regulator.unregister(self) - capi.c_destruct(self.space, self.cppclass, self._rawobject) + capi.c_destruct(self.space, self.clsdecl, self._rawobject) self._rawobject = capi.C_NULL_OBJECT def _finalize_(self): @@ -1162,7 +1141,6 @@ W_CPPClass.typedef = TypeDef( 'CPPClass', - cppclass = interp_attrproperty_w('cppclass', cls=W_CPPClass), _python_owns = GetSetProperty(W_CPPClass.fget_python_owns, W_CPPClass.fset_python_owns), __init__ = interp2app(W_CPPClass.instance__init__), __eq__ = interp2app(W_CPPClass.instance__eq__), @@ -1220,21 +1198,21 @@ state = space.fromcache(State) return space.call_function(state.w_fngen_callback, w_callable, space.newint(npar)) -def wrap_cppobject(space, rawobject, cppclass, - do_cast=True, python_owns=False, is_ref=False, fresh=False): +def wrap_cppinstance(space, rawobject, clsdecl, + do_cast=True, python_owns=False, is_ref=False, fresh=False): rawobject = rffi.cast(capi.C_OBJECT, rawobject) # cast to actual if requested and possible w_pycppclass = None if do_cast and rawobject: - actual = capi.c_actual_class(space, cppclass, rawobject) - if actual != cppclass.handle: + actual = capi.c_actual_class(space, clsdecl, rawobject) + if actual != clsdecl.handle: try: w_pycppclass = get_pythonized_cppclass(space, actual) - offset = capi.c_base_offset1(space, actual, cppclass, rawobject, -1) + offset = capi.c_base_offset1(space, actual, clsdecl, rawobject, -1) rawobject = capi.direct_ptradd(rawobject, offset) - w_cppclass = space.findattr(w_pycppclass, space.newtext("__cppdecl__")) - cppclass = space.interp_w(W_CPPClassDecl, w_cppclass, can_be_None=False) + w_cppdecl = space.findattr(w_pycppclass, space.newtext("__cppdecl__")) + clsdecl = space.interp_w(W_CPPClassDecl, w_cppdecl, can_be_None=False) except Exception: # failed to locate/build the derived class, so stick to the base (note # that only get_pythonized_cppclass is expected to raise, so none of @@ -1242,18 +1220,18 @@ pass if w_pycppclass is None: - w_pycppclass = get_pythonized_cppclass(space, cppclass.handle) + w_pycppclass = get_pythonized_cppclass(space, clsdecl.handle) # try to recycle existing object if this one is not newly created if not fresh and rawobject: obj = memory_regulator.retrieve(rawobject) - if obj is not None and obj.cppclass is cppclass: + if obj is not None and obj.clsdecl is clsdecl: return obj # fresh creation w_cppinstance = space.allocate_instance(W_CPPClass, w_pycppclass) cppinstance = space.interp_w(W_CPPClass, w_cppinstance, can_be_None=False) - cppinstance.__init__(space, cppclass, rawobject, is_ref, python_owns) + cppinstance.__init__(space, clsdecl, rawobject, is_ref, python_owns) memory_regulator.register(cppinstance) return w_cppinstance @@ -1273,19 +1251,24 @@ return space.newlong(address) @unwrap_spec(owns=bool, cast=bool) -def bind_object(space, w_obj, w_pycppclass, owns=False, cast=False): - """Takes an address and a bound C++ class proxy, returns a bound instance.""" +def _bind_object(space, w_obj, w_clsdecl, owns=False, cast=False): try: # attempt address from array or C++ instance rawobject = rffi.cast(capi.C_OBJECT, _addressof(space, w_obj)) except Exception: # accept integer value as address rawobject = rffi.cast(capi.C_OBJECT, space.uint_w(w_obj)) - w_cppclass = space.findattr(w_pycppclass, space.newtext("__cppdecl__")) - if not w_cppclass: - w_cppclass = scope_byname(space, space.text_w(w_pycppclass)) - if not w_cppclass: + decl = space.interp_w(W_CPPClassDecl, w_clsdecl, can_be_None=False) + return wrap_cppinstance(space, rawobject, decl, python_owns=owns, do_cast=cast) + +@unwrap_spec(owns=bool, cast=bool) +def bind_object(space, w_obj, w_pycppclass, owns=False, cast=False): + """Takes an address and a bound C++ class proxy, returns a bound instance.""" + w_clsdecl = space.findattr(w_pycppclass, space.newtext("__cppdecl__")) + if not w_clsdecl: + w_clsdecl = scope_byname(space, space.text_w(w_pycppclass)) + if not w_clsdecl: raise oefmt(space.w_TypeError, "no such class: %s", space.text_w(w_pycppclass)) - cppclass = space.interp_w(W_CPPClassDecl, w_cppclass, can_be_None=False) - return wrap_cppobject(space, rawobject, cppclass, do_cast=cast, python_owns=owns) + return _bind_object(space, w_obj, w_clsdecl, owns, cast) + diff --git a/pypy/module/_cppyy/pythonify.py b/pypy/module/_cppyy/pythonify.py --- a/pypy/module/_cppyy/pythonify.py +++ b/pypy/module/_cppyy/pythonify.py @@ -10,7 +10,7 @@ class CPPMetaScope(type): def __getattr__(self, name): try: - return get_pycppitem(self, name) # will cache on self + return get_scoped_pycppitem(self, name) # will cache on self except Exception as e: raise AttributeError("%s object has no attribute '%s' (details: %s)" % (self, name, str(e))) @@ -36,11 +36,14 @@ self._scope = scope def _arg_to_str(self, arg): - if arg == str: - import _cppyy - arg = _cppyy._std_string_name() - elif type(arg) != str: - arg = arg.__name__ + try: + arg = arg.__cppname__ + except AttributeError: + if arg == str: + import _cppyy + arg = _cppyy._std_string_name() + elif type(arg) != str: + arg = arg.__name__ return arg def __call__(self, *args): @@ -58,8 +61,36 @@ return self.__call__(*args) -def clgen_callback(name): - return get_pycppclass(name) +def scope_splitter(name): + is_open_template, scope = 0, "" + for c in name: + if c == ':' and not is_open_template: + if scope: + yield scope + scope = "" + continue + elif c == '<': + is_open_template += 1 + elif c == '>': + is_open_template -= 1 + scope += c + yield scope + +def get_pycppitem(final_scoped_name): + # walk scopes recursively down from global namespace ("::") to get the + # actual (i.e. not typedef'ed) class, triggering all necessary creation + scope = gbl + for name in scope_splitter(final_scoped_name): + scope = getattr(scope, name) + return scope +get_pycppclass = get_pycppitem # currently no distinction, but might + # in future for performance + + +# callbacks (originating from interp_cppyy.py) to allow interp-level to +# initiate creation of app-level classes and function +def clgen_callback(final_scoped_name): + return get_pycppclass(final_scoped_name) def fngen_callback(func, npar): # todo, some kind of arg transform spec if npar == 0: @@ -75,6 +106,12 @@ return wrapper +# construction of namespaces and classes, and their helpers +def make_module_name(scope): + if scope: + return scope.__module__ + '.' + scope.__name__ + return 'cppyy' + def make_static_function(func_name, cppol): def function(*args): return cppol.call(None, *args) @@ -82,13 +119,6 @@ function.__doc__ = cppol.signature() return staticmethod(function) -def make_method(meth_name, cppol): - def method(self, *args): - return cppol.call(self, *args) - method.__name__ = meth_name - method.__doc__ = cppol.signature() - return method - def make_cppnamespace(scope, name, decl): # build up a representation of a C++ namespace (namespaces are classes) @@ -98,20 +128,19 @@ ns_meta = type(name+'_meta', (CPPMetaNamespace,), {}) # create the python-side C++ namespace representation, cache in scope if given - d = {"__cppdecl__" : decl, "__cppname__" : decl.__cppname__ } + d = {"__cppdecl__" : decl, + "__module__" : make_module_name(scope), + "__cppname__" : decl.__cppname__ } pyns = ns_meta(name, (CPPNamespace,), d) if scope: setattr(scope, name, pyns) # install as modules to allow importing from (note naming: cppyy) - modname = 'cppyy.gbl' - if scope: - modname = 'cppyy.gbl.'+pyns.__cppname__.replace('::', '.') - sys.modules[modname] = pyns + sys.modules[make_module_name(pyns)] = pyns return pyns def _drop_cycles(bases): - # TODO: figure this out, as it seems to be a PyPy bug?! + # TODO: figure out why this is necessary? for b1 in bases: for b2 in bases: if not (b1 is b2) and issubclass(b2, b1): @@ -119,27 +148,37 @@ break return tuple(bases) -def make_new(class_name): + +def make_new(decl): def __new__(cls, *args): # create a place-holder only as there may be a derived class defined + # TODO: get rid of the import and add user-land bind_object that uses + # _bind_object (see interp_cppyy.py) import _cppyy - instance = _cppyy.bind_object(0, class_name, True) + instance = _cppyy._bind_object(0, decl, True) if not instance.__class__ is cls: instance.__class__ = cls # happens for derived class return instance return __new__ -def make_cppclass(scope, class_name, final_class_name, decl): +def make_method(meth_name, cppol): + def method(self, *args): + return cppol.call(self, *args) + method.__name__ = meth_name + method.__doc__ = cppol.signature() + return method + +def make_cppclass(scope, cl_name, decl): # get a list of base classes for class creation bases = [get_pycppclass(base) for base in decl.get_base_names()] if not bases: bases = [CPPClass,] else: - # it's technically possible that the required class now has been built - # if one of the base classes uses it in e.g. a function interface + # it's possible that the required class now has been built if one of + # the base classes uses it in e.g. a function interface try: - return scope.__dict__[final_class_name] + return scope.__dict__[cl_name] except KeyError: pass @@ -147,39 +186,41 @@ d_meta = {} # prepare dictionary for python-side C++ class representation - def dispatch(self, name, signature): - cppol = decl.dispatch(name, signature) - return types.MethodType(make_method(name, cppol), self, type(self)) + def dispatch(self, m_name, signature): + cppol = decl.__dispatch__(m_name, signature) + return types.MethodType(make_method(m_name, cppol), self, type(self)) d_class = {"__cppdecl__" : decl, + "__new__" : make_new(decl), + "__module__" : make_module_name(scope), "__cppname__" : decl.__cppname__, - "__new__" : make_new(class_name), + "__dispatch__" : dispatch, } # insert (static) methods into the class dictionary - for name in decl.get_method_names(): - cppol = decl.get_overload(name) + for m_name in decl.get_method_names(): + cppol = decl.get_overload(m_name) if cppol.is_static(): - d_class[name] = make_static_function(name, cppol) + d_class[m_name] = make_static_function(m_name, cppol) else: - d_class[name] = make_method(name, cppol) + d_class[m_name] = make_method(m_name, cppol) # add all data members to the dictionary of the class to be created, and # static ones also to the metaclass (needed for property setters) - for name in decl.get_datamember_names(): - cppdm = decl.get_datamember(name) - d_class[name] = cppdm + for d_name in decl.get_datamember_names(): + cppdm = decl.get_datamember(d_name) + d_class[d_name] = cppdm if cppdm.is_static(): - d_meta[name] = cppdm + d_meta[d_name] = cppdm # create a metaclass to allow properties (for static data write access) metabases = [type(base) for base in bases] - metacpp = type(CPPMetaScope)(class_name+'_meta', _drop_cycles(metabases), d_meta) + metacpp = type(CPPMetaScope)(cl_name+'_meta', _drop_cycles(metabases), d_meta) # create the python-side C++ class - pycls = metacpp(class_name, _drop_cycles(bases), d_class) + pycls = metacpp(cl_name, _drop_cycles(bases), d_class) # store the class on its outer scope - setattr(scope, final_class_name, pycls) + setattr(scope, cl_name, pycls) # the call to register will add back-end specific pythonizations and thus # needs to run first, so that the generic pythonizations can use them @@ -192,32 +233,32 @@ return CPPTemplate(template_name, scope) -def get_pycppitem(scope, name): +def get_scoped_pycppitem(scope, name): import _cppyy - # resolve typedefs/aliases - full_name = (scope == gbl) and name or (scope.__name__+'::'+name) - true_name = _cppyy._resolve_name(full_name) - if true_name != full_name: - return get_pycppclass(true_name) + # resolve typedefs/aliases: these may cross namespaces, in which case + # the lookup must trigger the creation of all necessary scopes + scoped_name = (scope == gbl) and name or (scope.__cppname__+'::'+name) + final_scoped_name = _cppyy._resolve_name(scoped_name) + if final_scoped_name != scoped_name: + pycppitem = get_pycppitem(final_scoped_name) + # also store on the requested scope (effectively a typedef or pointer copy) + setattr(scope, name, pycppitem) + return pycppitem pycppitem = None - # classes - cppitem = _cppyy._scope_byname(true_name) + # scopes (classes and namespaces) + cppitem = _cppyy._scope_byname(final_scoped_name) if cppitem: - name = true_name - if scope != gbl: - name = true_name[len(scope.__cppname__)+2:] if cppitem.is_namespace(): pycppitem = make_cppnamespace(scope, name, cppitem) - setattr(scope, name, pycppitem) else: - pycppitem = make_cppclass(scope, name, true_name, cppitem) + pycppitem = make_cppclass(scope, name, cppitem) # templates if not cppitem: - cppitem = _cppyy._template_byname(true_name) + cppitem = _cppyy._is_template(final_scoped_name) if cppitem: pycppitem = make_cpptemplatetype(scope, name) setattr(scope, name, pycppitem) @@ -249,29 +290,6 @@ raise AttributeError("'%s' has no attribute '%s'" % (str(scope), name)) -def scope_splitter(name): - is_open_template, scope = 0, "" - for c in name: - if c == ':' and not is_open_template: - if scope: - yield scope - scope = "" - continue - elif c == '<': - is_open_template += 1 - elif c == '>': - is_open_template -= 1 - scope += c - yield scope - -def get_pycppclass(name): - # break up the name, to walk the scopes and get the class recursively - scope = gbl - for part in scope_splitter(name): - scope = getattr(scope, part) - return scope - - # pythonization by decoration (move to their own file?) def python_style_getitem(self, idx): # python-style indexing: check for size and allow indexing from the back @@ -346,8 +364,8 @@ # also the fallback on the indexed __getitem__, but that is slower) if not 'vector' in pyclass.__name__[:11] and \ ('begin' in pyclass.__dict__ and 'end' in pyclass.__dict__): - if _cppyy._scope_byname(pyclass.__name__+'::iterator') or \ - _cppyy._scope_byname(pyclass.__name__+'::const_iterator'): + if _cppyy._scope_byname(pyclass.__cppname__+'::iterator') or \ + _cppyy._scope_byname(pyclass.__cppname__+'::const_iterator'): def __iter__(self): i = self.begin() while i != self.end(): diff --git a/pypy/module/_cppyy/test/test_cppyy.py b/pypy/module/_cppyy/test/test_cppyy.py --- a/pypy/module/_cppyy/test/test_cppyy.py +++ b/pypy/module/_cppyy/test/test_cppyy.py @@ -31,12 +31,16 @@ spaceconfig = dict(usemodules=['_cppyy', '_rawffi', 'itertools']) def setup_class(cls): - cls.w_lib, cls.w_example01, cls.w_payload = \ + cls.w_lib, cls.w_instantiate, cls.w_example01, cls.w_payload = \ cls.space.unpackiterable(cls.space.appexec([], """(): import _cppyy, ctypes lib = ctypes.CDLL(%r, ctypes.RTLD_GLOBAL) - return lib, _cppyy._scope_byname('example01'), _cppyy._scope_byname('payload')"""\ - % (test_dct, ))) + def cpp_instantiate(tt, *args): + inst = _cppyy._bind_object(0, tt, True) + tt.get_overload("__init__").call(inst, *args) + return inst + return lib, cpp_instantiate, _cppyy._scope_byname('example01'),\ + _cppyy._scope_byname('payload')""" % (test_dct, ))) def test01_static_int(self): """Test passing of an int, returning of an int, and overloading on a @@ -95,7 +99,7 @@ assert t.get_overload("getCount").call(None) == 0 - e1 = t.get_overload(t.__cppname__).call(None, 7) + e1 = self.instantiate(t, 7) assert t.get_overload("getCount").call(None) == 1 res = t.get_overload("addDataToInt").call(e1, 4) assert res == 11 @@ -105,8 +109,8 @@ assert t.get_overload("getCount").call(None) == 0 raises(ReferenceError, 't.get_overload("addDataToInt").call(e1, 4)') - e1 = t.get_overload(t.__cppname__).call(None, 7) - e2 = t.get_overload(t.__cppname__).call(None, 8) + e1 = self.instantiate(t, 7) + e2 = self.instantiate(t, 8) assert t.get_overload("getCount").call(None) == 2 e1.__destruct__() assert t.get_overload("getCount").call(None) == 1 @@ -128,7 +132,7 @@ assert t.get_overload("getCount").call(None) == 0 - e1 = t.get_overload(t.__cppname__).call(None, 7) + e1 = self.instantiate(t, 7) assert t.get_overload("getCount").call(None) == 1 res = t.get_overload("addDataToInt").call(e1, 4) assert res == 11 @@ -138,8 +142,8 @@ gc.collect() assert t.get_overload("getCount").call(None) == 0 - e1 = t.get_overload(t.__cppname__).call(None, 7) - e2 = t.get_overload(t.__cppname__).call(None, 8) + e1 = self.instantiate(t, 7) + e2 = self.instantiate(t, 8) assert t.get_overload("getCount").call(None) == 2 e1 = None gc.collect() @@ -159,7 +163,7 @@ assert t.get_overload("getCount").call(None) == 0 - e1 = t.get_overload(t.__cppname__).call(None, 7) + e1 = self.instantiate(t, 7) assert t.get_overload("getCount").call(None) == 1 assert e1._python_owns == True e1._python_owns = False @@ -178,12 +182,12 @@ t = self.example01 - e = t.get_overload(t.__cppname__).call(None, 13) + e = self.instantiate(t, 13) res = t.get_overload("addDataToDouble").call(e, 16) assert round(res-29, 8) == 0. e.__destruct__() - e = t.get_overload(t.__cppname__).call(None, -13) + e = self.instantiate(t, -13) res = t.get_overload("addDataToDouble").call(e, 16) assert round(res-3, 8) == 0. e.__destruct__() @@ -196,7 +200,7 @@ t = self.example01 - e = t.get_overload(t.__cppname__).call(None, 42) + e = self.instantiate(t, 42) res = t.get_overload("addDataToAtoi").call(e, "13") assert res == 55 res = t.get_overload("addToStringValue").call(e, "12") # TODO: this leaks @@ -213,12 +217,12 @@ t1 = self.example01 t2 = self.payload - pl = t2.get_overload(t2.__cppname__).call(None, 3.14) + pl = self.instantiate(t2, 3.14) assert round(t2.get_overload("getData").call(pl)-3.14, 8) == 0 t1.get_overload("staticSetPayload").call(None, pl, 41.) assert t2.get_overload("getData").call(pl) == 41. - e = t1.get_overload(t1.__cppname__).call(None, 50) + e = self.instantiate(t1, 50) t1.get_overload("setPayload").call(e, pl); assert round(t2.get_overload("getData").call(pl)-50., 8) == 0 @@ -233,12 +237,12 @@ t1 = self.example01 t2 = self.payload - pl1 = t2.get_overload(t2.__cppname__).call(None, 3.14) + pl1 = self.instantiate(t2, 3.14) assert round(t2.get_overload("getData").call(pl1)-3.14, 8) == 0 pl2 = t1.get_overload("staticCyclePayload").call(None, pl1, 38.) assert t2.get_overload("getData").call(pl2) == 38. - e = t1.get_overload(t1.__cppname__).call(None, 50) + e = self.instantiate(t1, 50) pl2 = t1.get_overload("cyclePayload").call(e, pl1); assert round(t2.get_overload("getData").call(pl2)-50., 8) == 0 diff --git a/pypy/module/_cppyy/test/test_fragile.py b/pypy/module/_cppyy/test/test_fragile.py --- a/pypy/module/_cppyy/test/test_fragile.py +++ b/pypy/module/_cppyy/test/test_fragile.py @@ -189,7 +189,7 @@ o = fragile.O() # raises TypeError assert 0 except TypeError as e: - assert "cannot instantiate abstract class 'O'" in str(e) + assert "cannot instantiate abstract class 'fragile::O'" in str(e) def test10_dir(self): """Test __dir__ method""" @@ -239,7 +239,7 @@ assert _cppyy.gbl.fragile.nested1 is nested1 assert nested1.__name__ == 'nested1' assert nested1.__module__ == 'cppyy.gbl.fragile' - assert nested1.__cppname__ == 'nested1' + assert nested1.__cppname__ == 'fragile::nested1' from cppyy.gbl.fragile.nested1 import A, nested2 assert _cppyy.gbl.fragile.nested1.A is A @@ -254,17 +254,17 @@ from cppyy.gbl.fragile.nested1.nested2 import A, nested3 assert _cppyy.gbl.fragile.nested1.nested2.A is A assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1' + assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2' assert A.__cppname__ == 'fragile::nested1::nested2::A' assert _cppyy.gbl.fragile.nested1.nested2.nested3 is nested3 assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1' + assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2' assert nested3.__cppname__ == 'fragile::nested1::nested2::nested3' from cppyy.gbl.fragile.nested1.nested2.nested3 import A assert _cppyy.gbl.fragile.nested1.nested2.nested3.A is nested3.A assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1' + assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2.nested3' assert A.__cppname__ == 'fragile::nested1::nested2::nested3::A' def test12_missing_casts(self): diff --git a/pypy/module/_cppyy/test/test_zjit.py b/pypy/module/_cppyy/test/test_zjit.py --- a/pypy/module/_cppyy/test/test_zjit.py +++ b/pypy/module/_cppyy/test/test_zjit.py @@ -279,7 +279,8 @@ drv = jit.JitDriver(greens=[], reds=["i", "inst", "cppmethod"]) def f(): cls = interp_cppyy.scope_byname(space, "example01") - inst = cls.get_overload("example01").call(None, [FakeInt(0)]) + inst = interp_cppyy._bind_object(space, FakeInt(0), cls, True) + cls.get_overload("__init__").call(inst, [FakeInt(0)]) cppmethod = cls.get_overload(method_name) assert isinstance(inst, interp_cppyy.W_CPPClass) i = 10 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit