[pypy-commit] pypy reflex-support: make sure that typedefs point to the same python class (this includes a full overhaul of the caching of cpp classes)

wlav noreply at buildbot.pypy.org
Thu Mar 8 01:44:41 CET 2012


Author: Wim Lavrijsen <WLavrijsen at lbl.gov>
Branch: reflex-support
Changeset: r53265:0055136f2633
Date: 2012-03-07 15:10 -0800
http://bitbucket.org/pypy/pypy/changeset/0055136f2633/

Log:	make sure that typedefs point to the same python class (this
	includes a full overhaul of the caching of cpp classes)

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
@@ -5,6 +5,7 @@
 
     interpleveldefs = {
         '_load_dictionary'       : 'interp_cppyy.load_dictionary',
+        '_resolve_name'          : 'interp_cppyy.resolve_name',
         '_type_byname'           : 'interp_cppyy.type_byname',
         '_template_byname'       : 'interp_cppyy.template_byname',
         'CPPInstance'            : 'interp_cppyy.W_CPPInstance',
diff --git a/pypy/module/cppyy/capi/__init__.py b/pypy/module/cppyy/capi/__init__.py
--- a/pypy/module/cppyy/capi/__init__.py
+++ b/pypy/module/cppyy/capi/__init__.py
@@ -33,6 +33,10 @@
 c_load_dictionary = backend.c_load_dictionary
 
 # name to opaque C++ scope representation ------------------------------------
+c_resolve_name = rffi.llexternal(
+    "cppyy_resolve_name",
+    [rffi.CCHARP], rffi.CCHARP,
+    compilation_info=backend.eci)
 c_get_scope = rffi.llexternal(
     "cppyy_get_scope",
     [rffi.CCHARP], C_SCOPE,
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
@@ -592,7 +592,7 @@
     #   5) generalized cases (covers basically all user classes)
     #   6) void converter, which fails on use
 
-    from pypy.module.cppyy import interp_cppyy
+    name = capi.charp2str_free(capi.c_resolve_name(name))
 
     #   1) full, exact match
     try:
@@ -624,6 +624,7 @@
             pass
 
     #   5) generalized cases (covers basically all user classes)
+    from pypy.module.cppyy import interp_cppyy
     cpptype = interp_cppyy.type_byname(space, clean_name)
     if cpptype:
         # type check for the benefit of the annotator
@@ -665,16 +666,10 @@
 _converters["void*&"]                   = VoidPtrRefConverter
 
 # special cases
-_converters["std::string"]                       = StdStringConverter
-_converters["string"]                            = _converters["std::string"]
 _converters["std::basic_string<char>"]           = StdStringConverter
 _converters["basic_string<char>"]                = _converters["std::basic_string<char>"]
-_converters["const std::string&"]                = StdStringConverter     # TODO: shouldn't copy
-_converters["const string&"]                     = _converters["const std::string&"]
-_converters["const std::basic_string<char>&"]    = StdStringConverter
+_converters["const std::basic_string<char>&"]    = StdStringConverter     # TODO: shouldn't copy
 _converters["const basic_string<char>&"]         = _converters["const std::basic_string<char>&"]
-_converters["std::string&"]                      = StdStringRefConverter
-_converters["string&"]                           = _converters["std::string&"]
 _converters["std::basic_string<char>&"]          = StdStringRefConverter
 _converters["basic_string<char>&"]               = _converters["std::basic_string<char>&"]
 
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
@@ -292,7 +292,7 @@
     #
     # If all fails, a default is used, which can be ignored at least until use.
 
-    from pypy.module.cppyy import interp_cppyy
+    name = capi.charp2str_free(capi.c_resolve_name(name))
 
     #   1) full, qualified match
     try:
@@ -318,6 +318,7 @@
             pass
 
     #   3) types/classes, either by ref/ptr or by value
+    from pypy.module.cppyy import interp_cppyy
     cpptype = interp_cppyy.type_byname(space, clean_name)
     if cpptype:
         # type check for the benefit of the annotator
diff --git a/pypy/module/cppyy/helper.py b/pypy/module/cppyy/helper.py
--- a/pypy/module/cppyy/helper.py
+++ b/pypy/module/cppyy/helper.py
@@ -99,14 +99,12 @@
         # is put at the end only as it is unlikely and may trigger unwanted
         # errors in class loaders in the backend, because a typical operator
         # name is illegal as a class name)
-        handle = capi.c_get_scope(op)
-        if handle:
-            op = capi.charp2str_free(capi.c_final_name(handle))
+        true_op = capi.charp2str_free(capi.c_resolve_name(op))
 
-            try:
-                return _operator_mappings[op]
-            except KeyError:
-                pass
+        try:
+            return _operator_mappings[true_op]
+        except KeyError:
+            pass
 
     # might get here, as not all operator methods handled (although some with
     # no python equivalent, such as new, delete, etc., are simply retained)
diff --git a/pypy/module/cppyy/include/capi.h b/pypy/module/cppyy/include/capi.h
--- a/pypy/module/cppyy/include/capi.h
+++ b/pypy/module/cppyy/include/capi.h
@@ -14,6 +14,7 @@
     typedef void* (*cppyy_methptrgetter_t)(cppyy_object_t);
 
     /* name to opaque C++ scope representation -------------------------------- */
+    char* cppyy_resolve_name(const char* cppitem_name);
     cppyy_scope_t cppyy_get_scope(const char* scope_name);
     cppyy_type_t cppyy_get_template(const char* template_name);
 
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
@@ -32,14 +32,20 @@
         self.r_cpptemplate_cache = {}
 
 @unwrap_spec(name=str)
+def resolve_name(space, name=str):
+    return space.wrap(capi.charp2str_free(capi.c_resolve_name(name)))
+
+ at unwrap_spec(name=str)
 def type_byname(space, name):
+    true_name = capi.charp2str_free(capi.c_resolve_name(name))
+
     state = space.fromcache(State)
     try:
-        return state.r_cppscope_cache[name]
+        return state.r_cppscope_cache[true_name]
     except KeyError:
         pass
 
-    cppscope = capi.c_get_scope(name)
+    cppscope = capi.c_get_scope(true_name)
     assert lltype.typeOf(cppscope) == capi.C_SCOPE
     if cppscope:
         final_name = capi.charp2str_free(capi.c_final_name(cppscope))
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
@@ -7,12 +7,11 @@
 # classes for inheritance. Both are python classes, though, and refactoring
 # may be in order at some point.
 class CppyyScopeMeta(type):
-    def __getattr__(self, attr):
+    def __getattr__(self, name):
         try:
-            cppitem = get_cppitem(attr, self)
-            return cppitem
+            return get_cppitem(self, name)  # will cache on self
         except TypeError:
-            raise AttributeError("%s object has no attribute '%s'" % (self, attr))
+            raise AttributeError("%s object has no attribute '%s'" % (self, name))
 
 class CppyyNamespaceMeta(CppyyScopeMeta):
     pass
@@ -98,7 +97,7 @@
     metans = type(CppyyNamespaceMeta)(namespace_name+'_meta', (CppyyNamespaceMeta,), {})
 
     if cppns:
-        nsdct = {"_cpp_proxy" : cppns }
+        nsdct = {"_cpp_proxy" : cppns}
     else:
         nsdct = dict()
         def cpp_proxy_loader(cls):
@@ -123,13 +122,7 @@
             setattr(metans, dm, pydm)
 
     # create the python-side C++ namespace representation
-    pycppns = metans(namespace_name, (object,), nsdct)
-
-    # cache result and return
-    _existing_cppitems[namespace_name] = pycppns
-
-    return pycppns
-
+    return metans(namespace_name, (object,), nsdct)
 
 def _drop_cycles(bases):
     # TODO: figure this out, as it seems to be a PyPy bug?!
@@ -152,12 +145,19 @@
             return constructor_overload.call(None, cls, *args)
     return __new__
 
-def make_cppclass(class_name, cpptype):
+def make_cppclass(scope, class_name, final_class_name, cpptype):
 
     # get a list of base classes for class creation
     bases = [get_cppclass(base) for base in cpptype.get_base_names()]
     if not bases:
         bases = [CPPObject,]
+    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
+        try:
+            return scope.__dict__[final_class_name]
+        except KeyError:
+            pass
 
     # create a meta class to allow properties (for static data write access)
     metabases = [type(base) for base in bases]
@@ -170,7 +170,7 @@
     pycpptype = metacpp(class_name, _drop_cycles(bases), d)
  
     # cache result early so that the class methods can find the class itself
-    _existing_cppitems[class_name] = pycpptype
+    setattr(scope, final_class_name, pycpptype)
 
     # insert (static) methods into the class dictionary
     for meth_name in cpptype.get_method_names():
@@ -193,25 +193,17 @@
     _pythonize(pycpptype)
     return pycpptype
 
-def make_cpptemplatetype(template_name, scope):
+def make_cpptemplatetype(scope, template_name):
     return CppyyTemplateType(scope, template_name)
 
 
-_existing_cppitems = {}               # TODO: to merge with gbl.__dict__ (?)
-def get_cppitem(name, scope=None):
-    if scope and not scope is gbl:
-        fullname = scope.__name__+"::"+name
-    else:
-        scope = gbl
-        fullname = name
+def get_cppitem(scope, name):
+    # 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_cppclass(true_name)
 
-    # lookup if already created (e.g. as a function return type)
-    try:
-        return _existing_cppitems[fullname]
-    except KeyError:
-        pass
-
-    # ... if lookup failed, create as appropriate
     pycppitem = None
 
     # namespaces are "open"; TODO: classes are too (template methods, inner classes ...)
@@ -222,22 +214,20 @@
             _loaded_dictionaries_isdirty = False
 
     # classes
-    cppitem = cppyy._type_byname(fullname)
+    cppitem = cppyy._type_byname(true_name)
     if cppitem:
         if cppitem.is_namespace():
-            pycppitem = make_cppnamespace(fullname, cppitem)
+            pycppitem = make_cppnamespace(true_name, cppitem)
+            setattr(scope, name, pycppitem)
         else:
-            pycppitem = make_cppclass(fullname, cppitem)
-        _existing_cppitems[fullname] = pycppitem
-        scope.__dict__[name] = pycppitem
+            pycppitem = make_cppclass(scope, true_name, name, cppitem)
 
     # templates
     if not cppitem:
-        cppitem = cppyy._template_byname(fullname)
+        cppitem = cppyy._template_byname(true_name)
         if cppitem:
-            pycppitem = make_cpptemplatetype(name, scope)
-            _existing_cppitems[fullname] = pycppitem
-            scope.__dict__[name] = pycppitem
+            pycppitem = make_cpptemplatetype(scope, name)
+            setattr(scope, name, pycppitem)
 
     # functions
     if not cppitem:
@@ -266,7 +256,28 @@
 
     raise AttributeError("'%s' has no attribute '%s'" % (str(scope), name))
 
-get_cppclass = get_cppitem         # TODO: restrict to classes only (?)
+
+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_cppclass(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
 
 
 def _pythonize(pyclass):
@@ -289,7 +300,7 @@
         pyclass.__iter__ = __iter__
 
     # string comparisons
-    if pyclass.__name__ == 'std::string' or pyclass.__name__ == 'string':
+    if pyclass.__name__ == 'std::basic_string<char>':
         def eq(self, other):
             if type(other) == pyclass:
                 return self.c_str() == other.c_str()
diff --git a/pypy/module/cppyy/src/reflexcwrapper.cxx b/pypy/module/cppyy/src/reflexcwrapper.cxx
--- a/pypy/module/cppyy/src/reflexcwrapper.cxx
+++ b/pypy/module/cppyy/src/reflexcwrapper.cxx
@@ -48,6 +48,16 @@
 
 
 /* name to opaque C++ scope representation -------------------------------- */
+char* cppyy_resolve_name(const char* cppitem_name) {
+    Reflex::Scope s = Reflex::Scope::ByName(cppitem_name);
+    if (s.IsEnum())
+        return cppstring_to_cstring("unsigned int");
+    const std::string& name = s.Name(Reflex::SCOPED|Reflex::QUALIFIED|Reflex::FINAL);
+    if (name.empty())
+        return cppstring_to_cstring(cppitem_name);
+    return cppstring_to_cstring(name);
+}
+
 cppyy_scope_t cppyy_get_scope(const char* scope_name) {
     Reflex::Scope s = Reflex::Scope::ByName(scope_name);
     if (s.IsEnum())     // pretend to be builtin by returning 0
diff --git a/pypy/module/cppyy/test/example01.h b/pypy/module/cppyy/test/example01.h
--- a/pypy/module/cppyy/test/example01.h
+++ b/pypy/module/cppyy/test/example01.h
@@ -91,6 +91,10 @@
 };
 
 
+// typedefs
+typedef example01 example01_t;
+
+
 // special case naming
 class z_ {
 public:
diff --git a/pypy/module/cppyy/test/example01.xml b/pypy/module/cppyy/test/example01.xml
--- a/pypy/module/cppyy/test/example01.xml
+++ b/pypy/module/cppyy/test/example01.xml
@@ -2,6 +2,7 @@
 
   <class name="payload" />
   <class name="example01" />
+  <class name="example01_t" />
   <class name="std::string" />
   <class name="z_" />
 
diff --git a/pypy/module/cppyy/test/test_pythonify.py b/pypy/module/cppyy/test/test_pythonify.py
--- a/pypy/module/cppyy/test/test_pythonify.py
+++ b/pypy/module/cppyy/test/test_pythonify.py
@@ -290,7 +290,14 @@
         assert e.overloadedAddDataToInt(4, 5)    == 10
         assert e.overloadedAddDataToInt(6, 7, 8) == 22
 
-    def test12_underscore_in_class_name(self):
+    def test12_typedefs(self):
+        """Test access and use of typedefs"""
+
+        import cppyy
+
+        assert cppyy.gbl.example01 == cppyy.gbl.example01_t
+
+    def test13_underscore_in_class_name(self):
         """Test recognition of '_' as part of a valid class name"""
 
         import cppyy


More information about the pypy-commit mailing list