genbindings: generate bindings for protected methods

This commit is contained in:
mappu 2025-02-06 13:30:37 +13:00
parent 73c30809d2
commit c1bf148a5a
4 changed files with 171 additions and 4 deletions

View File

@ -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

View File

@ -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<>

View File

@ -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+`)`) + `
}
`)
`)
}

View File

@ -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