def WrapVar(self, v, unused_ln, unused_ns): """Process AST.VarDecl v.""" assert self.nested, 'C++ global vars not allowed, use const' assert '::' not in v.name.cpp_name vname = v.name.cpp_name if v.cpp_set and not v.cpp_get: raise NameError('Property %s has setter, but no getter' % v.name.native) getter = 'get_' + vname setter = 'set_' + vname if self.final: base = None cobj = _GetCppObj() + '->' else: base = 'auto cpp = ThisPtr(self); if (!cpp) ' cobj = 'cpp->' is_property = False if v.cpp_get.name.cpp_name: # It's a property var (with cpp only `getter`, `setter`). assert not v.cpp_get.name.native is_property = True if v.cpp_get.classmethod or v.cpp_set.classmethod: raise ValueError('Static properties not supported') if v.cpp_set.name.cpp_name: assert not v.cpp_set.name.native else: setter = 'nullptr' vname = Ident(v.cpp_get.name.cpp_name) + '()' cvar = cobj + vname unproperty = False if v.cpp_get.name.native: # It's an unproperty var (@getter pyname / @setter pyname). assert not v.cpp_get.name.cpp_name unproperty = True self.methods.append((v.cpp_get.name.native, getter, NOARGS, '%s()->%s C++ %s.%s getter' % (v.cpp_get.name.native, v.type.lang_type, self.name, v.name.cpp_name))) if v.cpp_set.name.native: assert not v.cpp_set.name.cpp_name self.methods.append((v.cpp_set.name.native, setter, 'METH_O', '%s(%s) C++ %s.%s setter' % (v.cpp_set.name.native, v.type.lang_type, self.name, v.name.cpp_name))) else: setter = 'nullptr' else: self.properties.append((v.name.native, getter, setter, 'C++ %s %s.%s' % ( v.type.cpp_type, self.CppName(), vname))) # For a nested container we'll try to return it (we use cpp_toptr_conversion # as an indicator for a custom container). for s in gen.VarGetter(getter, unproperty, base, cvar, _GetCppObj(), pc=postconv.Initializer(v.type, self.typemap), get_nested=(not is_property and not unproperty and v.type.cpp_toptr_conversion)): yield s if setter != 'nullptr': for s in gen.VarSetter(setter, unproperty, base, cvar, v, cobj + Ident(v.cpp_set.name.cpp_name) if v.cpp_set.name.cpp_name else '', as_str=('PyUnicode_AsUTF8' if self.py3output else 'PyString_AS_STRING')): yield s
def testPatternStr(self): """str -> _1""" index = {'str': '_1', 'ztype': '_2'} ast_type = ast_pb2.Type() text_format.Parse(""" lang_type: "str" """, ast_type) self.assertEqual(postconv.Initializer(ast_type, index), '_1')
def testPatternInt(self): """int -> {}""" index = {'str': 1, 'ztype': 2} ast_type = ast_pb2.Type() text_format.Parse(""" lang_type: "int" """, ast_type) self.assertEqual(postconv.Initializer(ast_type, index), '{}')
def testPatternListStr(self): """list<str> -> {_1}""" index = {'str': '_1', 'ztype': '_2'} ast_type = ast_pb2.Type() text_format.Parse(""" lang_type: "list<str>" params { lang_type: "str" } """, ast_type) self.assertEqual(postconv.Initializer(ast_type, index), '{_1}')
def _WrapIterSubclass(members, typemap): """Special-case nested __iter__ class.""" assert len(members) == 1, ('__iter__ class must have only one "def",' ' %d members found' % len(members)) d = members[0] assert d.decltype == d.FUNC, ('__iter__ class must have only one "def",' ' %s member found' % d.decltype) assert d.func.name.native == '__next__', ( '__iter__ class must have only one "def __next__", "def %s" found' % d.func.name.native) for s in gen.IterNext(_GetCppObj('iter'), not d.func.py_keep_gil, postconv.Initializer(d.func.returns[0].type, typemap)): yield s
def testPatternNested(self): """dict<int, tuple<str, ztype>> -> {_0,{_1,_2}}""" index = {'str': '_1', 'ztype': '_2'} ast_type = ast_pb2.Type() text_format.Parse(""" lang_type: "dict<int, tuple<str, ztype>>" params { lang_type: "int" } params { lang_type: "tuple<str, ztype>" params { lang_type: "str" } params { lang_type: "ztype" } } """, ast_type) self.assertEqual(postconv.Initializer(ast_type, index), '{_0,{_1,_2}}')
def WrapClass(self, c, unused_ln, cpp_namespace, unused_class_ns=''): """Process AST.ClassDecl c.""" cname = Ident(c.name.cpp_name) pyname = c.name.native self.Add(Context(cname, pyname, c.name.cpp_name)) ns = self.class_namespace yield '' yield 'namespace %s {' % ns virtual, has_iterator = _IsSpecialClass(c, pyname) if virtual: for s in gen.VirtualOverriderClass( VIRTUAL_OVERRIDER_CLASS, pyname, c.name.cpp_name, c.name.cpp_name + '::' + cname, c.cpp_abstract, Ident, vfuncs=virtual, pcfunc=lambda t, tm=self.typemap: postconv.Initializer( t, tm)): yield s c.bases.add().cpp_name = c.name.cpp_name # Flag that we're now in the nested __iter__ class. iter_class = (pyname == _ITER_KW) for s in gen.WrapperClassDef( WRAPPER_CLASS_NAME, is_iter=iter_class, has_iter=has_iterator, iter_ns=_ClassNamespace(_ITER_KW), cname=c.name.cpp_name, ctype=VIRTUAL_OVERRIDER_CLASS if virtual else c.name.cpp_name, enable_instance_dict=c.enable_instance_dict): yield s tracked_slot_groups = {} cpp_has_ext_def_ctor = False if iter_class: # Special-case nested __iter__ class. for s in _WrapIterSubclass(c.members, self.typemap): yield s tp_slots = { 'tp_flags': ['Py_TPFLAGS_DEFAULT'], 'tp_iter': 'PyObject_SelfIter', 'tp_iternext': gen.IterNext.name } ctor = None else: # Normal class generator. if c.final: self.final = True else: yield '' yield 'static %s* ThisPtr(PyObject*);' % c.name.cpp_name ctor = ('DEF' if c.cpp_has_def_ctor and (not c.cpp_abstract or virtual) else None) for d in c.members: if d.decltype == d.FUNC: f = d.func if f.name.native == '__init__': if virtual: # Constructors are not virtual, but we mark the constructor as # virtual to indicate that there exist virtual functions in the # Clif class declaration. f.virtual = True if not f.params: if f.is_extend_method: cpp_has_ext_def_ctor = True else: # Skip generating wrapper function for unextended default # ctor. But wrapper function for extended default ctor is still # necessary. continue ctor = 'wrap%s_as___init__' % types.Mangle(cname) elif c.cpp_abstract and f.virtual: continue # Skip calling virtual func from the abstract base class. for s in self.WrapDecl(d, parent_ns=cpp_namespace, class_ns=ns): yield s if virtual and not ctor: raise ValueError( 'A constructor should be declared in the Clif wrapper for %s as it ' 'has virtual method declarations.' % c.name.cpp_name) # For Py2 slots.py relies on Py_TPFLAGS_DEFAULT being set. tp_slots = {'tp_flags': ['Py_TPFLAGS_DEFAULT']} if c.cpp_abstract: tp_slots['tp_flags'].append('Py_TPFLAGS_IS_ABSTRACT') if not self.py3output: tp_slots['tp_flags'].append('Py_TPFLAGS_CHECKTYPES') if not c.final: tp_slots['tp_flags'].append('Py_TPFLAGS_BASETYPE') if has_iterator: n = _ClassNamespace(_ITER_KW) + '::' w = n + WRAPPER_CLASS_NAME # Python convention to have a type struct named FOO_Type. for s in gen.NewIter(_GetCppObj(), n, w, w + '_Type'): yield s tp_slots['tp_iter'] = gen.NewIter.name if self.properties or c.enable_instance_dict: yield '' for s in gen.GetSetDef(self.properties, c.enable_instance_dict): yield s tp_slots['tp_getset'] = gen.GetSetDef.name for b in c.bases: if b.cpp_name and not b.native: p = b.cpp_name w = 'as_' + types.Mangle(p) # pyname == cname == w for s in gen.CastAsCapsule(_GetCppObj(), p, w): yield s self.methods.append((w, w, NOARGS, 'Upcast to %s*' % p)) _AppendReduceExIfNeeded(self.methods) if self.methods: for s in slots.GenSlots(self.methods, tp_slots, py3=self.py3output, tracked_groups=tracked_slot_groups): yield s if self.methods: # If all methods are slots, it's empty. for s in gen.MethodDef(self.methods): yield s tp_slots['tp_methods'] = gen.MethodDef.name qualname = '.'.join(f.pyname for f in self.nested) tp_slots['tp_name'] = '"%s.%s"' % (self.path, qualname) if c.docstring: docstring = c.docstring.strip() # Escape characters for inclusion in the raw C string. if str is bytes: # PY2 docstring = docstring.encode('unicode-escape') docstring = docstring.replace('\n', r'\n') docstring = docstring.replace('"', r'\"') tp_slots['tp_doc'] = '"%s"' % docstring if c.async_dtor and c.cpp_has_trivial_dtor: raise ValueError( 'Class %s has a trivial dtor yet @async__del__ decorator' % pyname) # Python convention to have a type struct named FOO_Type. # Generate wrapper Type object in wname+'_Type' static var. for s in gen.TypeObject( qualname, tracked_slot_groups, tp_slots, pyname, ctor, wname=WRAPPER_CLASS_NAME, fqclassname=c.name.cpp_name, abstract=c.cpp_abstract, iterator=_GetCppObj('iter') if iter_class else None, trivial_dtor=c.cpp_has_trivial_dtor, subst_cpp_ptr=VIRTUAL_OVERRIDER_CLASS if virtual else '', enable_instance_dict=c.enable_instance_dict, cpp_has_ext_def_ctor=cpp_has_ext_def_ctor): yield s if not iter_class: for s in types.GenThisPointerFunc(c.name.cpp_name, WRAPPER_CLASS_NAME, c.final): yield s yield '' yield '} // namespace ' + ns type_dict = self.dict wrapns = '::'.join(f.class_namespace for f in self.nested) + '::' wclass = wrapns + WRAPPER_CLASS_NAME vclass = wrapns + VIRTUAL_OVERRIDER_CLASS # Python convention to have a type struct named FOO_Type. wtype = wclass + '_Type' self.DropContext() base, wrapped_base = _ProcessInheritance( c.bases, '::%s_Type' % WRAPPER_CLASS_NAME, self.namemap) self.types_init.append((wtype, base, wrapped_base, type_dict)) if iter_class: if base: raise TypeError( "__iter__ class can't be derived, base '%s' found" % base) else: self.dict.append((pyname, types.AsPyObj(wtype))) self.types.append( types.ClassType(c.name.cpp_name, qualname, wclass, wtype, wrapns, can_copy=c.cpp_copyable and not c.cpp_abstract, can_move=c.cpp_movable and not c.cpp_abstract, can_destruct=c.cpp_has_public_dtor, virtual=vclass if virtual else '', ns=cpp_namespace))
def WrapVar(self, v, unused_ln, unused_ns, unused_class_ns=''): """Process AST.VarDecl v.""" assert self.nested, 'C++ global vars not allowed, use const' assert '::' not in v.name.cpp_name vname = v.name.cpp_name ctype = v.type.cpp_type if v.cpp_set and not v.cpp_get: raise NameError('Property %s has setter, but no getter' % v.name.native) getter = 'get_' + vname setter = 'set_' + vname if self.final: base = None cobj = _GetCppObj() + '->' else: base = 'auto cpp = ThisPtr(self); if (!cpp) ' cobj = 'cpp->' is_property = False if v.cpp_get.name.cpp_name: # It's a property var (with cpp only `getter`, `setter`). assert not v.cpp_get.name.native is_property = True if v.cpp_get.classmethod or v.cpp_set.classmethod: raise ValueError('Static properties not supported') if v.cpp_set.name.cpp_name: assert not v.cpp_set.name.native else: setter = 'nullptr' vname = Ident(v.cpp_get.name.cpp_name) + '()' nested_container = not is_property cvar = cobj + vname getval = cvar setval = (cobj + Ident(v.cpp_set.name.cpp_name) if v.cpp_set.name.cpp_name else '') if v.is_extend_variable: orig_getter_name = v.cpp_get.name.cpp_name.split( ast_manipulations.EXTEND_INFIX)[-1] cname = Ident(v.cpp_get.name.cpp_name) wrapper_name = 'wrap' + types.Mangle(cname) getval = wrapper_name + '_as_' + orig_getter_name + '(self)' if v.cpp_set: setval = v.cpp_set.name.cpp_name unproperty = False if v.cpp_get.name.native: # It's an unproperty var (@getter pyname / @setter pyname). assert not v.cpp_get.name.cpp_name unproperty = True nested_container = False self.methods.append((v.cpp_get.name.native, getter, NOARGS, '%s()->%s C++ %s.%s getter' % (v.cpp_get.name.native, v.type.lang_type, self.name, v.name.cpp_name))) if v.cpp_set.name.native: assert not v.cpp_set.name.cpp_name self.methods.append((v.cpp_set.name.native, setter, 'METH_O', '%s(%s) C++ %s.%s setter' % (v.cpp_set.name.native, v.type.lang_type, self.name, v.name.cpp_name))) else: setter = 'nullptr' else: self.properties.append( (v.name.native, getter, setter, 'C++ %s %s.%s' % (ctype, self.CppName(), vname))) if not v.type.cpp_abstract and not ctype.startswith( '::std::shared_ptr'): if v.type.cpp_raw_pointer or ctype.startswith( '::std::unique_ptr'): getval = '*' + cvar nested_container = False elif nested_container and v.type.cpp_toptr_conversion: # For a nested container we'll try to return it (we use # cpp_toptr_conversion as an indicator for a custom container). getval = '::clif::MakeStdShared(%s, &%s)' % (_GetCppObj(), cvar) for s in gen.VarGetter(getter, unproperty, base, getval, postconv.Initializer(v.type, self.typemap), is_extend=v.is_extend_variable): yield s if setter != 'nullptr': for s in gen.VarSetter(setter, unproperty, base, cvar, v, setval, as_str=('PyUnicode_AsUTF8' if self.py3output else 'PyString_AS_STRING'), is_extend=v.is_extend_variable): yield s
def WrapConst(self, c, unused_ln, unused_ns, unused_class_ns=''): """Process AST.ConstDecl c.""" self.dict.append((c.name.native, 'Clif_PyObjFrom(%s, %s)' % (types.AsType(c.type.cpp_type, c.name.cpp_name), postconv.Initializer(c.type, self.typemap)))) return []
def FunctionCall(pyname, wrapper, doc, catch, call, postcall_init, typepostconversion, func_ast, lineno, prepend_self=None): """Generate PyCFunction wrapper from AST.FuncDecl func_ast. Args: pyname: str - Python function name (may be special: ends with @) wrapper: str - generated function name doc: str - C++ sinature catch: bool - catch C++ exceptions call: str | [str] - C++ command(s) to call the wrapped function (without "(params);" part). postcall_init: str - C++ command; to (re)set ret0. typepostconversion: dict(pytype, index) to convert to pytype func_ast: AST.FuncDecl protobuf lineno: int - .clif line number where func_ast defined prepend_self: AST.Param - Use self as 1st parameter. Yields: Source code for wrapped function. Raises: ValueError: for non-supported default arguments """ ctxmgr = pyname.endswith('@') if ctxmgr: ctxmgr = pyname assert ctxmgr in ('__enter__@', '__exit__@'), ('Invalid context manager name ' + pyname) pyname = pyname.rstrip('@') nret = len(func_ast.returns) return_type = astutils.FuncReturnType( func_ast) # Can't use cpp_exact_type. # return_type mangled to FQN and drop &, sadly it also drop const. void_return_type = 'void' == return_type # Has extra func parameters for output values. xouts = nret > (0 if void_return_type else 1) params = [] # C++ parameter names. nargs = len(func_ast.params) yield '' if func_ast.classmethod: yield '// @classmethod ' + doc arg0 = 'cls' # Extra protection that generated code does not use 'self'. else: yield '// ' + doc arg0 = 'self' yield 'static PyObject* %s(PyObject* %s%s) {' % ( wrapper, arg0, ', PyObject* args, PyObject* kw' if nargs else '') if prepend_self: yield I + _CreateInputParameter(pyname + ' line %d' % lineno, prepend_self, 'arg0', params) yield I + 'if (!Clif_PyObjAs(self, &arg0)) return nullptr;' minargs = sum(1 for p in func_ast.params if not p.default_value) if nargs: yield I + 'PyObject* a[%d]%s;' % (nargs, '' if minargs == nargs else '{}') yield I + 'char* names[] = {' for p in func_ast.params: yield I + I + I + 'C("%s"),' % p.name.native yield I + I + I + 'nullptr' yield I + '};' yield I + ( 'if (!PyArg_ParseTupleAndKeywords(args, kw, "%s:%s", names, %s)) ' 'return nullptr;' % ('O' * nargs if minargs == nargs else 'O' * minargs + '|' + 'O' * (nargs - minargs), pyname, ', '.join('&a[%d]' % i for i in range(nargs)))) if minargs < nargs and not xouts: yield I + 'int nargs; // Find how many args actually passed in.' yield I + 'for (nargs = %d; nargs > %d; --nargs) {' % (nargs, minargs) yield I + I + 'if (a[nargs-1] != nullptr) break;' yield I + '}' # Convert input parameters from Python. for i, p in enumerate(func_ast.params): n = i + 1 arg = 'arg%d' % n yield I + _CreateInputParameter(pyname + ' line %d' % lineno, p, arg, params) cvt = ( 'if (!Clif_PyObjAs(a[{i}], &{cvar}{postconv})) return ArgError' '("{func_name}", names[{i}], "{ctype}", a[{i}]);').format( i=i, cvar=arg, func_name=pyname, ctype=astutils.Type(p), # Add post conversion parameter for std::function. postconv='' if p.type.cpp_type else ', {%s}' % ', '.join( postconv.Initializer(t.type, typepostconversion) for t in p.type.callable.params)) if i < minargs: # Non-default parameter. yield I + cvt else: if xouts: _I = '' # pylint: disable=invalid-name else: _I = I # pylint: disable=invalid-name yield I + 'if (nargs > %d) {' % i # Check if we're passed kw args, skipping some default C++ args. # In this case we must substitute missed default args with default_value if (p.default_value == 'default' # Matcher could not find the default. or 'inf' in p.default_value): # W/A for b/29437257 if xouts: raise ValueError( "Can't supply the default for C++ function" ' argument. Drop =default in def %s(%s).' % (pyname, p.name.native)) if n < nargs: yield I + I + ( 'if (!a[{i}]) return DefaultArgMissedError(' '"{}", names[{i}]);'.format(pyname, i=i)) yield I + I + cvt elif (p.default_value and params[-1].startswith('&') and p.type.cpp_raw_pointer): # Special case for a pointer to an integral type param (like int*). raise ValueError( 'A default for integral type pointer argument is ' ' not supported. Drop =default in def %s(%s).' % (pyname, p.name.native)) else: # C-cast takes care of the case where |arg| is an enum value, while # the matcher would return an integral literal. Using static_cast # would be ideal, but its argument should be an expression, which a # struct value like {1, 2, 3} is not. yield _I + I + 'if (!a[%d]) %s = (%s)%s;' % ( i, arg, astutils.Type(p), p.default_value) yield _I + I + 'else ' + cvt if not xouts: yield I + '}' # Create input parameters for extra return values. for n, p in enumerate(func_ast.returns): if n or void_return_type: yield I + '%s ret%d{};' % (astutils.Type(p), n) params.append('&ret%d' % n) yield I + '// Call actual C++ method.' if isinstance(call, list): for s in call[:-1]: yield I + s call = call[-1] if not func_ast.py_keep_gil: if nargs: yield I + 'Py_INCREF(args);' yield I + 'Py_XINCREF(kw);' yield I + 'PyThreadState* _save;' yield I + 'Py_UNBLOCK_THREADS' optional_ret0 = False if (minargs < nargs or catch) and not void_return_type: if func_ast.returns[0].type.cpp_has_def_ctor: yield I + return_type + ' ret0;' else: # Using optional<> requires T be have T(x) and T::op=(x) available. # While we need only t=x, implementing it will be a pain we skip for now. yield I + '::gtl::optional<%s> ret0;' % return_type optional_ret0 = True if catch: for s in _GenExceptionTry(): yield s if minargs < nargs and not xouts: if not void_return_type: call = 'ret0 = ' + call yield I + 'switch (nargs) {' for n in range(minargs, nargs + 1): yield I + 'case %d:' % n yield I + I + '%s; break;' % (call + astutils.TupleStr(params[:n])) yield I + '}' else: call += astutils.TupleStr(params) _I = I if catch else '' # pylint: disable=invalid-name if void_return_type: yield _I + I + call + ';' elif catch: yield _I + I + 'ret0 = ' + call + ';' else: yield _I + I + return_type + ' ret0 = ' + call + ';' if catch: for s in _GenExceptionCatch(): yield s if postcall_init: if void_return_type: yield I + postcall_init else: yield I + 'ret0' + postcall_init if not func_ast.py_keep_gil: yield I + 'Py_BLOCK_THREADS' if nargs: yield I + 'Py_DECREF(args);' yield I + 'Py_XDECREF(kw);' if catch: for s in _GenExceptionRaise(): yield s if func_ast.postproc == '->self': func_ast.postproc = '' return_self = True assert nret == 0, '-> self must have no other output parameters' else: return_self = False # If ctxmgr, force return self on enter, None on exit. if nret > 1 or (func_ast.postproc or ctxmgr) and nret: yield I + '// Convert return values to Python.' yield I + 'PyObject* p, * result_tuple = PyTuple_New(%d);' % nret yield I + 'if (result_tuple == nullptr) return nullptr;' for i in range(nret): yield I + 'if ((p=Clif_PyObjFrom(std::move(ret%d), %s)) == nullptr) {' % ( i, postconv.Initializer(func_ast.returns[i].type, typepostconversion)) yield I + I + 'Py_DECREF(result_tuple);' yield I + I + 'return nullptr;' yield I + '}' yield I + 'PyTuple_SET_ITEM(result_tuple, %d, p);' % i if func_ast.postproc: yield I + 'PyObject* pyproc = ImportFQName("%s");' % func_ast.postproc yield I + 'if (pyproc == nullptr) {' yield I + I + 'Py_DECREF(result_tuple);' yield I + I + 'return nullptr;' yield I + '}' yield I + 'p = PyObject_CallObject(pyproc, result_tuple);' yield I + 'Py_DECREF(pyproc);' yield I + 'Py_CLEAR(result_tuple);' if ctxmgr: yield I + 'if (p == nullptr) return nullptr;' yield I + 'Py_DECREF(p); // Not needed by the context manager.' else: yield I + 'result_tuple = p;' if ctxmgr == '__enter__@': yield I + 'Py_XDECREF(result_tuple);' yield I + 'Py_INCREF(self);' yield I + 'return self;' elif ctxmgr == '__exit__@': yield I + 'Py_XDECREF(result_tuple);' yield I + 'Py_RETURN_NONE;' else: yield I + 'return result_tuple;' elif nret: yield I + 'return Clif_PyObjFrom(std::move(ret0%s), %s);' % ( ('.value()' if optional_ret0 else ''), postconv.Initializer(func_ast.returns[0].type, typepostconversion)) elif return_self or ctxmgr == '__enter__@': yield I + 'Py_INCREF(self);' yield I + 'return self;' else: yield I + 'Py_RETURN_NONE;' yield '}'
def WrapClass(self, c, unused_ln, cpp_namespace): """Process AST.ClassDecl c.""" cname = Ident(c.name.cpp_name) pyname = c.name.native self.Add(Context(cname, pyname, c.name.cpp_name)) ns = self.class_namespace yield '' yield 'namespace %s {' % ns virtual = False # If c has @virtual members, generate a derived redirector class. for d in c.members: if d.decltype == d.FUNC and d.func.virtual: if not virtual: yield '' yield 'struct %s : PyObj, %s {' % (VIRTUAL_OVERRIDER_CLASS, c.name.cpp_name) yield I + 'using %s::%s;' % (c.name.cpp_name, cname) virtual = True for s in gen.VirtualFunctionCall( Ident(d.func.name.cpp_name), d.func, pyname, c.cpp_abstract, lambda atype: postconv.Initializer( atype, self.typemap)): yield s if virtual: if c.final: raise ValueError("Final class %s can't have virtual methods." % pyname) yield '};' b = c.bases.add() b.cpp_name = c.name.cpp_name yield '' yield 'struct %s {' % self.wrapper_class_name yield I + 'PyObject_HEAD' yield I + '::clif::SharedPtr<%s> cpp;' % (VIRTUAL_OVERRIDER_CLASS if virtual else c.name.cpp_name) yield '};' if c.final: self.final = True else: yield 'static %s* ThisPtr(PyObject*);' % c.name.cpp_name ctor = ('DEF' if c.cpp_has_def_ctor and (not c.cpp_abstract or virtual) else None) for d in c.members: if d.decltype == d.FUNC: f = d.func if f.name.native == '__init__': if virtual: # Constructors are not virtual, but we mark the constructor as # virtual to indicate that there exist virtual functions in the # Clif class declaration. f.virtual = True if not f.params: continue # Skip generating wrapper function for default ctor. ctor = 'wrap%s_as___init__' % types.Mangle(cname) elif c.cpp_abstract and f.virtual: continue # Skip calling virtual func from the abstract base class. for s in (self.WrapDecl(d, cpp_namespace)): yield s if virtual and not ctor: raise ValueError( 'A constructor should be declared in the Clif wrapper for %s as it ' 'has virtual method declarations.' % c.name.cpp_name) # For Py2 slots.py relies on Py_TPFLAGS_DEFAULT being set. tp_slots = { 'tp_flags': ['Py_TPFLAGS_DEFAULT', 'Py_TPFLAGS_TYPE_SUBCLASS'] } if c.cpp_abstract: tp_slots['tp_flags'].append('Py_TPFLAGS_IS_ABSTRACT') if not self.py3output: tp_slots['tp_flags'].append('Py_TPFLAGS_CHECKTYPES') if not c.final: tp_slots['tp_flags'].append('Py_TPFLAGS_BASETYPE') if self.properties: for s in (gen.GetSetDef(self.properties)): yield s tp_slots['tp_getset'] = gen.GetSetDef.name for b in c.bases: if b.cpp_name and not b.native: p = b.cpp_name w = 'as_' + types.Mangle(p) # pyname == cname == w yield '' yield '// Implicit cast this as %s*' % p yield 'static PyObject* %s(PyObject* self) {' % w yield I + ('%s* p = ::clif::python::Get(reinterpret_cast<%s*>(' 'self)->cpp);' % (p, self.wrapper_class_name)) yield I + 'if (p == nullptr) return nullptr;' yield I + ('return PyCapsule_New(p, C("%s"), nullptr);') % p yield '}' self.methods.append((w, w, NOARGS, 'Upcast to %s*' % p)) if self.methods: for s in (slots.GenSlots(self.methods, tp_slots, py3=self.py3output)): yield s if self.methods: # If all methods are slots, it's empty. for s in (gen.MethodDef(self.methods)): yield s tp_slots['tp_methods'] = gen.MethodDef.name pypath = '.'.join(f.pyname for f in self.nested) tp_slots['tp_name'] = '"%s.%s"' % (self.path, pypath) for s in ( # Generate wrapper Type object in wname+'_Type' static var. gen.TypeObject( tp_slots, slots.GenTypeSlots, pyname, ctor=ctor, wname=self.wrapper_class_name, fqclassname=c.name.cpp_name, abstract=c.cpp_abstract, subst_cpp_ptr=VIRTUAL_OVERRIDER_CLASS if virtual else '', async_dtor=c.async_dtor, )): yield s for s in types.GenThisPointerFunc(c.name.cpp_name, self.wrapper_class_name, c.final): yield s yield '} // namespace ' + ns type_dict = self.dict wrapns = '::'.join(f.class_namespace for f in self.nested) + '::' wclass = wrapns + self.wrapper_class_name vclass = wrapns + VIRTUAL_OVERRIDER_CLASS wrap = '::' + self.wrapper_class_name wtype = wclass + '_Type' wrapt = wrap + '_Type' self.DropContext() base = cpp_replacement = None if c.bases: # Pytd fills bases with native name (cpp_name='') for Python inheritance. # Matcher extends it with cpp_name (native=='') for C++ parent classes. base = [b.native for b in c.bases if b.native and not b.cpp_name] if base: if len(base) > 1: raise ValueError('Multiple inheritance not supported') base = base[0] if '.' not in base: # base defined in the same .clif wrapper base = _ClassNamespace(base) + wrapt elif c.bases[0].native == 'replacement': assert c.bases[0].cpp_name cpp_replacement = c.bases[0].cpp_name self.types_init.append((wtype, base, type_dict)) self.dict.append( (pyname, types.AsPyObj(self.wrap_namespace + '::' + wtype))) if cpp_replacement: # Find replacement class namespace. for b in c.cpp_bases: if b.name == cpp_replacement: cpp_namespace = b.namespace break else: cpp_namespace = '' self.types.add( types.ClassType(c.name.cpp_name, pypath, wclass, wtype, wrapns, can_copy=c.cpp_copyable and not c.cpp_abstract, can_destruct=c.cpp_has_public_dtor, down_cast=cpp_replacement, virtual=vclass if virtual else '', ns=cpp_namespace))
def WrapVar(self, v, unused_ln, unused_ns): """Process AST.VarDecl v.""" assert self.nested, 'C++ global vars not allowed, use const' assert '::' not in v.name.cpp_name vname = v.name.cpp_name if v.cpp_set and not v.cpp_get: raise NameError('Property %s has setter, but no getter' % v.name.native) getter = 'get_' + vname setter = 'set_' + vname cpp = 'reinterpret_cast<%s*>(self)->cpp' % self.wrapper_class_name if self.final: base = None cobj = cpp + '->' else: base = 'auto cpp = ThisPtr(self); if (!cpp) ' cobj = 'cpp->' is_property = False if v.cpp_get.name.cpp_name: # It's a property var (with cpp only `getter`, `setter`). assert not v.cpp_get.name.native is_property = True if v.cpp_get.classmethod or v.cpp_set.classmethod: raise ValueError('Static properties not supported') if v.cpp_set.name.cpp_name: assert not v.cpp_set.name.native else: setter = 'nullptr' vname = v.cpp_get.name.cpp_name + '()' cvar = cobj + vname cfunc_getset = False if v.cpp_get.name.native: # It's an unproperty var (@getter pyname / @setter pyname). assert not v.cpp_get.name.cpp_name cfunc_getset = True self.methods.append((v.cpp_get.name.native, getter, NOARGS, '%s()->%s C++ %s.%s getter' % (v.cpp_get.name.native, v.type.lang_type, self.name, v.name.cpp_name))) if v.cpp_set.name.native: assert not v.cpp_set.name.cpp_name self.methods.append((v.cpp_set.name.native, setter, 'METH_O', '%s(%s) C++ %s.%s setter' % (v.cpp_set.name.native, v.type.lang_type, self.name, v.name.cpp_name))) else: setter = 'nullptr' else: self.properties.append( (v.name.native, getter, setter, 'C++ %s %s.%s' % (v.type.cpp_type, self.CppName(), vname))) yield '' yield 'static PyObject* %s(PyObject* self%s) {' % (getter, ( '' if cfunc_getset else ', void* xdata')) if base: yield I + base + 'return nullptr;' # Try to return a nested container (we use cpp_toptr_conversion as # an indicator for a custom container). if not is_property and not cfunc_getset and v.type.cpp_toptr_conversion: yield I + 'auto var_sp = ::clif::MakeStdShared(%s, &%s);' % (cpp, cvar) c = 'var_sp' else: c = cvar yield I + 'return Clif_PyObjFrom(%s, %s);' % ( c, postconv.Initializer(v.type, self.typemap)) yield '}' if setter == 'nullptr': return yield '' if cfunc_getset: yield 'static PyObject* %s(PyObject* self, PyObject* value) {' % setter else: yield ( 'static int %s(PyObject* self, PyObject* value, void* xdata) {' % setter) yield I + 'if (value == nullptr) {' yield I + I + ( 'PyErr_SetString(PyExc_TypeError, "Cannot delete the' ' %s attribute");' % v.name.native) yield I + I + 'return -1;' yield I + '}' if v.cpp_set.name.cpp_name: # Workaround BUG "v.type.cpp_type not updated by Matcher", so get p[0]. yield I + '%s cval;' % v.cpp_set.params[0].type.cpp_type yield I + 'if (Clif_PyObjAs(value, &cval)) {' if base: yield I + I + base + 'return -1;' yield I + I + cobj + v.cpp_set.name.cpp_name + '(cval);' yield I + I + 'return 0;' yield I + '}' ret_error = 'return ' + ('nullptr' if cfunc_getset else '-1') + ';' if not v.cpp_set.name.cpp_name: ret_ok = 'Py_RETURN_NONE' if cfunc_getset else 'return 0' if base: yield I + base + ret_error yield I + 'if (Clif_PyObjAs(value, &%s)) %s;' % (cvar, ret_ok) yield I + 'PyObject* s = PyObject_Repr(value);' yield I + ( 'PyErr_Format(PyExc_ValueError, "%s is not valid for {}:{}", ' 's? {}(s): "input");').format( v.name.native, v.type.lang_type, 'PyUnicode_AsUTF8' if self.py3output else 'PyString_AS_STRING') yield I + 'Py_XDECREF(s);' yield I + ret_error yield '}'