def _VirtualFunctionCall(fname, f, pyname, abstract, postconvinit): """Generate virtual redirector call wrapper from AST.FuncDecl f.""" name = f.name.cpp_name ret = astutils.FuncReturnType(f, true_cpp_type=True) arg = astutils.FuncParamStr(f, 'a', true_cpp_type=True) mod = [''] if f.cpp_const_method: mod.append('const') if f.cpp_noexcept: mod.append('noexcept') yield '' yield I + '%s %s%s%s override {' % (ret, fname, arg, ' '.join(mod)) params = astutils.TupleStr( 'std::move(a%i)' % i for i in range(len(f.params) + len(f.returns) - (ret != 'void'))) yield I + I + 'SafeAttr impl(self(), "%s");' % f.name.native yield I + I + 'if (impl.get()) {' ret_st = 'return ' if ret != 'void' else '' yield I + I + I + '%s::clif::callback::Func<%s>(impl.get(), {%s})%s;' % ( ret_st, ', '.join([ret] + list(astutils.Type(a) for a in f.params) + list(astutils.FuncReturns(f))), ', '.join( postconvinit(a.type) for a in f.params), params) yield I + I + '} else {' if abstract: # This is only called from C++. Since f has no info if it is pure virtual, # we can't always generate the call, so we always fail in an abstract class. yield I + I + I + ( 'Py_FatalError("@virtual method %s.%s has no Python ' 'implementation.");' % (pyname, f.name.native)) # In Python 2 Py_FatalError is not marked __attribute__((__noreturn__)), # so to avoid -Wreturn-type warning add extra abort(). It does not hurt ;) yield I + I + I + 'abort();' else: yield I + I + I + ret_st + name + params + ';' yield I + I + '}' yield I + '}'
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 '}'