From c1bf148a5a662e1b618458c11a62fd6a034ed7ae Mon Sep 17 00:00:00 2001 From: mappu Date: Thu, 6 Feb 2025 13:30:37 +1300 Subject: [PATCH] genbindings: generate bindings for protected methods --- cmd/genbindings/clang2il.go | 12 +++- cmd/genbindings/emitcabi.go | 110 +++++++++++++++++++++++++++++++- cmd/genbindings/emitgo.go | 39 ++++++++++- cmd/genbindings/intermediate.go | 14 ++++ 4 files changed, 171 insertions(+), 4 deletions(-) diff --git a/cmd/genbindings/clang2il.go b/cmd/genbindings/clang2il.go index 1cd425fc..2c734b3f 100644 --- a/cmd/genbindings/clang2il.go +++ b/cmd/genbindings/clang2il.go @@ -464,12 +464,20 @@ nextMethod: mm.IsSignal = isSignal && !mm.IsStatic && AllowSignal(mm) mm.IsProtected = (visibility == VsProtected) - if mm.IsProtected && !mm.IsVirtual { - // Protected method, so we can't call it + if mm.IsProtected && mm.IsStatic && !mm.IsVirtual { + // Protected static method, so there is no possible subclass to call it from + // e.g. QBitmap::fromImageInPlace // Non-virtual, so we can't override it // There is nothing we can do with this function continue nextMethod } + if mm.IsProtected && strings.HasPrefix(mm.MethodName, `operator`) { + // The subclass system causes problems here with e.g. QStandardItem::operator= + // This is a protected method, but the assignment `other` ends up + // with the child MiqtVirtual class instead of the original one + // Just skip them as well + continue nextMethod + } // Once all processing is complete, pass to exceptions for final decision diff --git a/cmd/genbindings/emitcabi.go b/cmd/genbindings/emitcabi.go index ae250d91..905db302 100644 --- a/cmd/genbindings/emitcabi.go +++ b/cmd/genbindings/emitcabi.go @@ -62,6 +62,10 @@ func cabiVirtualBaseName(c CppClass, m CppMethod) string { return cabiClassName(c.ClassName) + `_virtualbase_` + m.SafeMethodName() } +func cabiProtectedBaseName(c CppClass, m CppMethod) string { + return cabiClassName(c.ClassName) + `_protectedbase_` + m.SafeMethodName() +} + func cabiOverrideVirtualName(c CppClass, m CppMethod) string { return cabiClassName(c.ClassName) + `_override_virtual_` + m.SafeMethodName() } @@ -600,11 +604,51 @@ func getCppZeroValue(p CppParameter) string { return "0" } else if p.ParameterType == "bool" { return "false" + } else if p.ParameterType == "void" { + return "" } else { return p.RenderTypeQtCpp() + "()" } } +func getCabiZeroValue(p CppParameter) string { + // n.b. Identical to getCppZeroValue in most cases + + if p.Pointer { + return getCppZeroValue(p) + } else if p.IsKnownEnum() { + return getCppZeroValue(p) + } else if p.IntType() { + return getCppZeroValue(p) + } else if p.ParameterType == "bool" { + return getCppZeroValue(p) + } else if p.ParameterType == "void" { + return getCppZeroValue(p) + + } else if p.ParameterType == "QString" || p.ParameterType == "QByteArray" { + return "(struct miqt_string){}" + + } else if _, ok := p.QListOf(); ok { + return "(struct miqt_array){}" + + } else if _, ok := p.QSetOf(); ok { + return "(struct miqt_array){}" + + } else if _, _, ok := p.QMapOf(); ok { + return "(struct miqt_map){}" + + } else if _, _, ok := p.QPairOf(); ok { + return "(struct miqt_map){}" + + } else { + // Difference for Qt classes: Qt C++ can expect to return them by value, + // but CABI always needs to return them by pointer + + return "nullptr" + } + +} + // getReferencedTypes finds all referenced Qt types in this file. func getReferencedTypes(src *CppParsedHeader) []string { @@ -656,6 +700,12 @@ func getReferencedTypes(src *CppParsedHeader) []string { } maybeAddType(vm.ReturnType) } + for _, vm := range c.ProtectedMethods() { + for _, p := range vm.Parameters { + maybeAddType(p) + } + maybeAddType(vm.ReturnType) + } for _, cn := range c.AllInheritsClassInfo() { maybeAddType(CppParameter{ ParameterType: cn.Class.ClassName, @@ -773,6 +823,8 @@ extern "C" { for _, c := range src.Classes { className := cabiClassName(c.ClassName) + virtualMethods := c.VirtualMethods() + protectedMethods := c.ProtectedMethods() for i, ctor := range c.Ctors { ret.WriteString(fmt.Sprintf("%s* %s(%s);\n", className, cabiNewName(c, i), emitParametersCabiConstructor(&c, &ctor))) @@ -789,6 +841,10 @@ extern "C" { } for _, m := range c.Methods { + if m.IsProtected && !m.IsVirtual { + continue // Can't call directly, have to go through our wrapper + } + ret.WriteString(fmt.Sprintf("%s %s(%s);\n", m.ReturnType.RenderTypeCabi(), cabiMethodName(c, m), emitParametersCabi(m, ifv(m.IsConst, "const ", "")+className+"*"))) if m.IsSignal { @@ -796,12 +852,18 @@ extern "C" { } } - for _, m := range c.VirtualMethods() { + for _, m := range virtualMethods { ret.WriteString(fmt.Sprintf("bool %s(%s* self, intptr_t slot);\n", cabiOverrideVirtualName(c, m), "void" /*methodPrefixName*/)) ret.WriteString(fmt.Sprintf("%s %s(%s);\n", m.ReturnType.RenderTypeCabi(), cabiVirtualBaseName(c, m), emitParametersCabi(m, ifv(m.IsConst, "const ", "")+"void" /*className*/ +"*"))) } + if len(virtualMethods) > 0 { + for _, m := range protectedMethods { + ret.WriteString(fmt.Sprintf("%s %s(bool* _dynamic_cast_ok, %s);\n", m.ReturnType.RenderTypeCabi(), cabiProtectedBaseName(c, m), emitParametersCabi(m, ifv(m.IsConst, "const ", "")+"void" /*className*/ +"*"))) + } + } + // delete if c.CanDelete { ret.WriteString(fmt.Sprintf("void %s(%s* self);\n", cabiDeleteName(c), className)) @@ -908,6 +970,7 @@ extern "C" { methodPrefixName := cabiClassName(c.ClassName) cppClassName := c.ClassName virtualMethods := c.VirtualMethods() + protectedMethods := c.ProtectedMethods() if len(virtualMethods) > 0 { @@ -1037,6 +1100,21 @@ extern "C" { } } + if len(protectedMethods) > 0 { + ret.WriteString("\t// Wrappers to allow calling protected methods:\n") + } + + for _, m := range protectedMethods { + + // The protectedbase wrapper needs to take CABI parameters, not + // real Qt parameters, in case there are protected enum types + // (e.g. QAbstractItemView::CursorAction) + + ret.WriteString( + "\tfriend " + m.ReturnType.RenderTypeCabi() + " " + cabiProtectedBaseName(c, m) + "(bool* _dynamic_cast_ok, " + emitParametersCabi(m, ifv(m.IsConst, "const ", "")+"void*") + ");\n", + ) + } + ret.WriteString( "};\n" + "\n") @@ -1262,6 +1340,36 @@ extern "C" { } + if len(virtualMethods) > 0 { + // This is a subclassed class. In that case, we allow calling + // protected methods + // This is a standalone function, but it can access the protected + // method via a friend declaration + + for _, m := range protectedMethods { + + vbpreamble, vbforwarding := emitParametersCABI2CppForwarding(m.Parameters, "\t\t") + vbCallTarget := "self_cast->" + m.CppCallTarget() + "(" + vbforwarding + ")" + + ret.WriteString( + m.ReturnType.RenderTypeCabi() + " " + cabiProtectedBaseName(c, m) + "(bool* _dynamic_cast_ok, " + emitParametersCabi(m, ifv(m.IsConst, "const ", "")+"void*") + ") {\n" + + + "\t" + cppClassName + "* self_cast = dynamic_cast<" + cppClassName + "*>( (" + cabiClassName(c.ClassName) + "*)(self) );\n" + + "\tif (self_cast == nullptr) {\n" + + "\t\t*_dynamic_cast_ok = false;\n" + + "\t\treturn " + getCabiZeroValue(m.ReturnType) + ";\n" + + "\t}\n" + + "\t\n" + + "\t*_dynamic_cast_ok = true;\n" + + "\t" + vbpreamble + "\n" + + emitAssignCppToCabi("\treturn ", m.ReturnType, vbCallTarget) + "\n" + + "}\n" + + "\n", + ) + + } + } + // Delete // If we subclassed, our class destructor is always virtual. Therefore // we can delete from the self ptr without any dynamic_cast<> diff --git a/cmd/genbindings/emitgo.go b/cmd/genbindings/emitgo.go index 6c1a108f..612dc5d5 100644 --- a/cmd/genbindings/emitgo.go +++ b/cmd/genbindings/emitgo.go @@ -988,6 +988,43 @@ import "C" } } + if len(c.VirtualMethods()) > 0 { + // We only do protected methods if we are subclassing, and we only subclass if there are virtuals + // FIXME should we subclass in either case...? + for _, m := range c.ProtectedMethods() { + + preamble, forwarding := gfs.emitParametersGo2CABIForwarding(m) + + forwarding = "unsafe.Pointer(this.h)" + strings.TrimPrefix(forwarding, `this.h`) // TODO integrate properly + + returnTypeDecl := m.ReturnType.renderReturnTypeGo(&gfs) + + gfs.imports["unsafe"] = struct{}{} + + ret.WriteString(` + // ` + m.goMethodName() + ` can only be called from a ` + goClassName + ` that was directly constructed. + func (this *` + goClassName + `) ` + m.goMethodName() + ` (` + gfs.emitParametersGo(m.Parameters) + `) ` + returnTypeDecl + ` { + ` + preamble + ` + var _dynamic_cast_ok C.bool = false + ` + gfs.emitCabiToGo("_ret := ", m.ReturnType, `C.`+cabiProtectedBaseName(c, m)+`(&_dynamic_cast_ok, `+forwarding+`)`) + ` + if !_dynamic_cast_ok { + panic("miqt: can only call protected methods for directly constructed types") + } + `) + + if !m.ReturnType.Void() { + ret.WriteString(` + return _ret + `) + } + + ret.WriteString(` + } + `) + + } + } + for _, m := range c.VirtualMethods() { gfs.imports["unsafe"] = struct{}{} gfs.imports["runtime/cgo"] = struct{}{} @@ -1008,7 +1045,7 @@ import "C" ` + preamble + ` ` + gfs.emitCabiToGo("return ", m.ReturnType, `C.`+cabiVirtualBaseName(c, m)+`(`+forwarding+`)`) + ` } - `) + `) } diff --git a/cmd/genbindings/intermediate.go b/cmd/genbindings/intermediate.go index 1104188c..6e8a98d5 100644 --- a/cmd/genbindings/intermediate.go +++ b/cmd/genbindings/intermediate.go @@ -516,13 +516,25 @@ func (c *CppClass) ProtectedMethods() []CppMethod { var block = slice_to_set(c.PrivateMethods) for _, m := range c.Methods { + + // A public method with the same name blocks-out any protected methods + // with the same name (even if the signature is different). + // e.g. + // QTextList::setFormat(const QTextListFormat&) [PUBLIC] inherits + // QTextBlockGroup inherits + // QTextObject::setFormat(const QTextFormat&) [PROTECTED] + // FIXME support selecting the parent overload(?) + if !m.IsProtected { + block[m.MethodName] = struct{}{} continue } if m.IsVirtual { + block[m.MethodName] = struct{}{} continue } if m.IsSignal { + block[m.MethodName] = struct{}{} continue } @@ -535,6 +547,7 @@ func (c *CppClass) ProtectedMethods() []CppMethod { } for _, cinfo := range c.AllInheritsClassInfo() { + for _, m := range cinfo.Class.Methods { if !m.IsProtected { continue @@ -582,6 +595,7 @@ func (c *CppClass) ProtectedMethods() []CppMethod { } // AllInheritsClassInfo recursively finds and lists all the parent classes of this class. +// It returns closest-ancestor-first. func (c *CppClass) AllInheritsClassInfo() []lookupResultClass { var ret []lookupResultClass