diff --git a/examples/foo.h b/examples/foo.h index 191b9fc..e073d95 100644 --- a/examples/foo.h +++ b/examples/foo.h @@ -589,5 +589,21 @@ private: AbstractBaseClass2 (const AbstractBaseClass2 &other) { other.invoke_protected_virtual(0); } }; +class AbstractXpto +// -#- allow_subclassing=true -#- +{ +public: + AbstractXpto () {} + + virtual void something (int x) const = 0; +}; + +class AbstractXptoImpl : public AbstractXpto +{ +public: + AbstractXptoImpl () {} + + virtual void something (int x) const {} +}; #endif /* !FOO_H_ */ diff --git a/examples/foomodulegen.py b/examples/foomodulegen.py index 20a3074..1206d13 100755 --- a/examples/foomodulegen.py +++ b/examples/foomodulegen.py @@ -351,7 +351,7 @@ int %s::custom_method_added_by_a_hook(int x) ## A class the cannot be constructed; this will cause late CodeGenerationError's cls = CppClass('CannotBeConstructed') mod.add_class(cls) - cls.set_cannot_be_constructed(True) + cls.set_cannot_be_constructed("no reason") cls.add_method(CppMethod(ReturnValue.new('CannotBeConstructed'), 'get_value', [], is_static=True)) cls.add_method(CppMethod(ReturnValue.new('CannotBeConstructed*', caller_owns_return=True), @@ -395,10 +395,29 @@ int %s::custom_method_added_by_a_hook(int x) is_virtual=True, is_pure_virtual=True, visibility='private', is_const=True)) + + + AbstractXpto = CppClass('AbstractXpto', allow_subclassing=True) + mod.add_class(AbstractXpto) + AbstractXpto.add_method(CppMethod(ReturnValue.new('void'), 'something', + [Parameter.new('int', 'x')], is_const=True, + is_virtual=True, is_pure_virtual=True)) + AbstractXpto.add_constructor(CppConstructor([])) + + AbstractXptoImpl = CppClass('AbstractXptoImpl', parent=AbstractXpto) + mod.add_class(AbstractXptoImpl) + AbstractXptoImpl.add_method(CppMethod(ReturnValue.new('void'), 'something', + [Parameter.new('int', 'x')], is_const=True, + is_virtual=True, is_pure_virtual=False)) + AbstractXptoImpl.add_constructor(CppConstructor([])) + + + #### --- error handler --- class MyErrorHandler(pybindgen.settings.ErrorHandler): def __init__(self): + super(MyErrorHandler, self).__init__() self.num_errors = 0 - def handle_error(self, wrapper, exception, traceback_): + def handle_error(self, wrapper, exception, dummy_traceback_): print >> sys.stderr, "exception %s in wrapper %s" % (exception, wrapper) self.num_errors += 1 return True diff --git a/examples/footest.py b/examples/footest.py index bc665fb..85ada64 100644 --- a/examples/footest.py +++ b/examples/footest.py @@ -667,5 +667,12 @@ class TestFoo(unittest.TestCase): b = foo.Bar() self.assert_(b.is_initialized()) + def test_pure_virtual(self): + self.assertRaises(TypeError, foo.AbstractXpto) + try: + foo.AbstractXptoImpl() + except TypeError, ex: + self.fail(str(ex)) + if __name__ == '__main__': unittest.main() diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index 611aaa5..c305180 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -21,6 +21,15 @@ from cppattribute import (CppInstanceAttributeGetter, CppInstanceAttributeSetter import settings import utils +try: + all +except NameError: # for compatibility with Python < 2.5 + def all(iterable): + "Returns True if all elements are true" + for element in iterable: + if not element: + return False + return True def default_instance_creation_function(cpp_class, code_block, lvalue, parameters, construct_type_name): @@ -358,6 +367,7 @@ class CppClass(object): self.is_singleton = is_singleton self.full_name = None # full name with C++ namespaces attached and template parameters self.methods = {} # name => OverloadedMethod + self.nonpublic_methods = [] self.constructors = [] # (name, wrapper) pairs self.slots = dict() self.helper_class = None @@ -365,9 +375,9 @@ class CppClass(object): ## set to True when we become aware generating the helper ## class is not going to be possible self.helper_class_disabled = False - self.cannot_be_constructed = False + self.cannot_be_constructed = '' # reason self.has_trivial_constructor = False - self.have_pure_virtual_methods = False + self._have_pure_virtual_methods = None ## list of CppClasses from which a value of this class can be ## implicitly generated; corresponds to a ## operator ThisClass(); in the other class. @@ -492,6 +502,68 @@ class CppClass(object): def __repr__(self): return "" % self.full_name + def get_all_methods(self): + """Returns an iterator to iterate over all methods of the class""" + for overload in self.methods.itervalues(): + for method in overload.wrappers: + yield method + for method in self.nonpublic_methods: + yield method + + def get_have_pure_virtual_methods(self): + """ + Returns True if the class has pure virtual methods with no + implementation (which would mean the type is not instantiable + directly, only through a helper class). + """ + if self._have_pure_virtual_methods is not None: + return self._have_pure_virtual_methods + mro = [] + cls = self + while cls is not None: + mro.append(cls) + cls = cls.parent + mro_reversed = list(mro) + mro_reversed.reverse() + + def method_matches_signature(meth1, meth2): + return (meth1.mangled_name == meth2.mangled_name + and meth1.return_value.ctype == meth2.return_value.ctype + and all([param1.ctype == param2.ctype + for param1, param2 in zip(meth1.parameters, meth2.parameters)]) + and ((not not meth1.is_const) == (not not meth2.is_const))) + self._have_pure_virtual_methods = False + for pos, cls in enumerate(mro_reversed): + for method in cls.get_all_methods(): + if not isinstance(method, CppMethod): + continue + + if method.is_pure_virtual: + ## found a pure virtual method; now go see in the + ## child classes, check if any of them implements + ## this pure virtual method. + implemented = False + for child_cls in mro_reversed[pos+1:]: + for child_method in child_cls.get_all_methods(): + if not isinstance(child_method, CppMethod): + continue + if not child_method.is_virtual: + continue + if not method_matches_signature(child_method, method): + continue + if not child_method.is_pure_virtual: + implemented = True + break + if implemented: + break + if not implemented: + self._have_pure_virtual_methods = True + + return self._have_pure_virtual_methods + + have_pure_virtual_methods = property(get_have_pure_virtual_methods) + + def is_subclass(self, other): """Return True if this CppClass instance represents a class that is a subclass of another class represented by the CppClasss object `other'.""" @@ -596,14 +668,9 @@ class CppClass(object): """Get a name usable for new %s construction, or raise CodeGenerationError if none found""" if self.cannot_be_constructed: - raise CodeGenerationError("%s cannot be constructed" % self.full_name) - have_pure_virtual_methods = False - cls = self - while cls is not None: - have_pure_virtual_methods = (have_pure_virtual_methods or cls.have_pure_virtual_methods) - cls = cls.parent - if have_pure_virtual_methods: - raise CodeGenerationError("%s cannot be constructed" % self.full_name) + raise CodeGenerationError("%s cannot be constructed (%s)" % (self.full_name, self.cannot_be_constructed)) + if self.have_pure_virtual_methods: + raise CodeGenerationError("%s cannot be constructed (class has pure virtual methods)" % self.full_name) else: return self.full_name @@ -843,6 +910,7 @@ public: from Python side """ assert name is None or isinstance(name, str) + if isinstance(method, CppMethod): if name is None: name = method.mangled_name @@ -872,9 +940,10 @@ public: method.class_ = self overload.add(method) - if method.is_pure_virtual: - self.have_pure_virtual_methods = True + else: + self.nonpublic_methods.append(method) if method.is_virtual: + self._have_pure_virtual_methods = None if not self.allow_subclassing: raise ValueError("Cannot add virtual methods if subclassing " "support was not enabled for this class") @@ -889,16 +958,15 @@ public: proxy = CppVirtualMethodProxy(method) proxy.main_wrapper = method helper_class.add_virtual_proxy(proxy) - if self.have_pure_virtual_methods and self.helper_class is None: - self.cannot_be_constructed = True def set_helper_class_disabled(self, flag=True): self.helper_class_disabled = flag if flag: self.helper_class = None - def set_cannot_be_constructed(self, flag=True): - self.cannot_be_constructed = flag + def set_cannot_be_constructed(self, reason): + assert isinstance(reason, basestring) + self.cannot_be_constructed = reason def add_constructor(self, wrapper): """ @@ -988,7 +1056,7 @@ typedef struct { self.helper_class = None self.helper_class_disabled = True if self.have_pure_virtual_methods and self.helper_class is None: - self.cannot_be_constructed = True + self.cannot_be_constructed = "have pure virtual methods but no helper class" if self.typeid_map_name is not None: self._generate_typeid_map(code_sink, module) @@ -1140,7 +1208,7 @@ typedef struct { ## tp_init that will allocate an instance of the ## parent class instead of this class. code_sink.writeln() - constructor = CppNoConstructor().generate(code_sink, self) + constructor = CppNoConstructor(self.cannot_be_constructed).generate(code_sink, self) have_constructor = False code_sink.writeln() diff --git a/pybindgen/cppmethod.py b/pybindgen/cppmethod.py index ac5403b..f8d0e38 100644 --- a/pybindgen/cppmethod.py +++ b/pybindgen/cppmethod.py @@ -365,13 +365,14 @@ class CppNoConstructor(ForwardWrapperBase): """ - def __init__(self): + def __init__(self, reason): """ - Constructor. + reason -- string indicating reason why the class cannot be constructed. """ super(CppNoConstructor, self).__init__( None, [], "return -1;", "return -1;") + self.reason = reason def generate_call(self): "dummy method, not really called" @@ -394,7 +395,7 @@ class CppNoConstructor(ForwardWrapperBase): code_sink.writeln('{') code_sink.indent() code_sink.writeln('PyErr_SetString(PyExc_TypeError, "class \'%s\' ' - 'cannot be constructed");' % class_.name) + 'cannot be constructed (%s)");' % (class_.name, self.reason)) code_sink.writeln('return -1;') code_sink.unindent() code_sink.writeln('}') diff --git a/pybindgen/gccxmlparser.py b/pybindgen/gccxmlparser.py index 658aa68..6e8ea21 100644 --- a/pybindgen/gccxmlparser.py +++ b/pybindgen/gccxmlparser.py @@ -809,7 +809,7 @@ class ModuleParser(object): % (member.return_type.decl_string, member, ex), Warning, member.location.file_name, member.location.line) if pure_virtual: - class_wrapper.set_cannot_be_constructed(True) + class_wrapper.set_cannot_be_constructed("pure virtual method not wrapped") class_wrapper.set_helper_class_disabled(True) continue arguments = [] @@ -825,12 +825,12 @@ class ModuleParser(object): ok = False if not ok: if pure_virtual: - class_wrapper.set_cannot_be_constructed(True) + class_wrapper.set_cannot_be_constructed("pure virtual method not wrapped") class_wrapper.set_helper_class_disabled(True) continue if pure_virtual and not class_wrapper.allow_subclassing: - class_wrapper.set_cannot_be_constructed(True) + class_wrapper.set_cannot_be_constructed("pure virtual method and subclassing disabled") custom_template_method_name = None if templates.is_instantiation(member.demangled_name): @@ -849,7 +849,7 @@ class ModuleParser(object): method_wrapper = CppMethod(return_type, member.name, arguments, is_const=member.has_const, is_static=member.has_static, - is_virtual=(is_virtual and class_wrapper.allow_subclassing), + is_virtual=is_virtual, is_pure_virtual=pure_virtual, template_parameters=template_parameters, custom_template_method_name=custom_template_method_name, @@ -859,7 +859,7 @@ class ModuleParser(object): class_wrapper.add_method(method_wrapper) except NotSupportedError, ex: if pure_virtual: - class_wrapper.set_cannot_be_constructed(True) + class_wrapper.set_cannot_be_constructed("pure virtual method not wrapped") class_wrapper.set_helper_class_disabled(True) warnings.warn_explicit("Error adding method %s: %r" % (member, ex),