miqt/cmd/genbindings/emitgo.go
Jacek Sieka 187c0a02ec Move go name mangling to emitgo
This change helps keep rules for each language separate by moving `go`
rules to `emitgo` while the C bindings stay closer to the original Qt
naming (that already is mostly C-safe).

Although it doesn't practically matter for go, it makes it slightly
easier to reuse the generated code in other languages which have
different keywords and naming conventions.

The cabi generator also gains a few helpers to help keep names
consistent across files which hopefully aids reading the generator code
- it did for me at least;)

The rule that converts under_score to CamelCase is left for another day
since moving it turns out to be more invasive due to name collision
handling - when underscores are kept, there are fewer name conflicts
which ends up causing name changes in  the public go api when done
naively.
2025-02-01 13:44:32 +13:00

1149 lines
36 KiB
Go

package main
import (
"C"
"fmt"
"go/format"
"log"
"math"
"path"
"sort"
"strconv"
"strings"
)
func goReservedWord(s string) bool {
switch s {
case "default", "const", "func", "var", "type", "len", "new", "copy", "import", "range", "string", "map", "int", "select",
"super", "ret": // not language-reserved words, but a binding-reserved words
return true
default:
return false
}
}
func (nm CppMethod) goMethodName() string {
// Also make the first letter uppercase so it becomes public in Go
tmp := nm.SafeMethodName()
tmp = titleCase(tmp)
return tmp
}
func (p CppParameter) goParameterName() string {
// Also make the first letter uppercase so it becomes public in Go
parmName := p.ParameterName
if goReservedWord(parmName) {
parmName += "Val"
}
return parmName
}
func (p CppParameter) RenderTypeGo(gfs *goFileState) string {
if p.Pointer && p.ParameterType == "char" {
return "string"
}
if p.ParameterType == "QString" {
return "string"
}
if p.ParameterType == "QByteArray" {
return "[]byte"
}
if t, ok := p.QListOf(); ok {
return "[]" + t.RenderTypeGo(gfs)
}
if t, ok := p.QSetOf(); ok {
return "map[" + t.RenderTypeGo(gfs) + "]struct{}"
}
if t1, t2, ok := p.QMapOf(); ok {
return "map[" + t1.RenderTypeGo(gfs) + "]" + t2.RenderTypeGo(gfs)
}
if t1, t2, ok := p.QPairOf(); ok {
// Design QPair using capital-named members, in case it gets passed
// across packages
return "struct { First " + t1.RenderTypeGo(gfs) + " ; Second " + t2.RenderTypeGo(gfs) + " }"
}
if p.ParameterType == "void" && p.Pointer {
return "unsafe.Pointer"
}
ret := ""
switch p.ParameterType {
case "unsigned char", "uchar", "quint8":
// Go byte is unsigned
ret += "byte"
case "char", "qint8", "signed char":
ret += "int8" // Signed
case "short", "qint16", "int16_t":
ret += "int16"
case "ushort", "quint16", "unsigned short", "uint16_t":
ret += "uint16"
case "long":
// Windows ILP32 - 32-bits
// Linux LP64 - 64-bits
if C.sizeof_long == 4 {
ret += "int32"
} else {
ret += "int64"
}
case "ulong", "unsigned long":
if C.sizeof_long == 4 {
ret += "uint32"
} else {
ret += "uint64"
}
case "unsigned int":
ret += "uint"
case "qint32":
ret += "int32"
case "quint32":
ret += "uint32"
case "qlonglong", "qint64", "long long":
ret += "int64"
case "qulonglong", "quint64", "unsigned long long":
ret += "uint64"
case "float":
ret += "float32"
case "double", "qreal":
ret += "float64"
case "size_t": // size_t is unsigned
if C.sizeof_size_t == 4 {
ret += "uint32"
} else {
ret += "uint64"
}
case "qsizetype", "QIntegerForSizeof<std::size_t>::Signed", "qptrdiff", "ptrdiff_t": // all signed
if C.sizeof_size_t == 4 {
ret += "int32"
} else {
ret += "int64"
}
case "qintptr", "uintptr_t", "intptr_t", "quintptr", "QIntegerForSizeof<void *>::Unsigned", "QIntegerForSizeof<void *>::Signed":
ret += "uintptr"
default:
if ft, ok := p.QFlagsOf(); ok {
if enumInfo, ok := KnownEnums[ft.ParameterType]; ok && enumInfo.PackageName != gfs.currentPackageName {
// Cross-package
ret += path.Base(enumInfo.PackageName) + "." + cabiClassName(ft.ParameterType)
gfs.imports[importPathForQtPackage(enumInfo.PackageName)] = struct{}{}
} else {
// Same package
ret += cabiClassName(ft.ParameterType)
}
} else if enumInfo, ok := KnownEnums[p.ParameterType]; ok {
if enumInfo.PackageName != gfs.currentPackageName {
// Cross-package
ret += path.Base(enumInfo.PackageName) + "." + cabiClassName(p.ParameterType)
gfs.imports[importPathForQtPackage(enumInfo.PackageName)] = struct{}{}
} else {
// Same package
ret += cabiClassName(p.ParameterType)
}
} else if strings.Contains(p.ParameterType, `::`) {
// Inner class
ret += cabiClassName(p.ParameterType)
} else {
// Do not transform this type
ret += p.ParameterType
}
}
if pkg, ok := KnownClassnames[p.ParameterType]; ok && pkg.PackageName != gfs.currentPackageName {
ret = path.Base(pkg.PackageName) + "." + ret
gfs.imports[importPathForQtPackage(pkg.PackageName)] = struct{}{}
}
if p.ByRef || p.Pointer {
ret = "*" + ret
}
return ret // ignore const
}
func (p CppParameter) renderReturnTypeGo(gfs *goFileState) string {
ret := p.RenderTypeGo(gfs)
if ret == "void" {
ret = ""
}
if p.QtClassType() && p.ParameterType != "QString" && p.ParameterType != "QByteArray" && !(p.Pointer || p.ByRef) {
// FIXME normalize this part
ret = "*" + ret
}
return ret
}
func (p CppParameter) parameterTypeCgo() string {
if p.ParameterType == "QString" {
return "C.struct_miqt_string"
}
if p.ParameterType == "QByteArray" {
return "C.struct_miqt_string"
}
if _, ok := p.QListOf(); ok {
return "C.struct_miqt_array"
}
if _, ok := p.QSetOf(); ok {
return "C.struct_miqt_array"
}
if _, _, ok := p.QMapOf(); ok {
return "C.struct_miqt_map"
}
if _, _, ok := p.QPairOf(); ok {
return "C.struct_miqt_map"
}
// Cgo internally binds void* as unsafe.Pointer{}
if p.ParameterType == "void" && p.Pointer {
return "unsafe.Pointer"
}
tmp := strings.Replace(p.RenderTypeCabi(), `*`, "", -1)
if strings.HasPrefix(tmp, "const ") && tmp != "const char" { // Special typedef to make this work for const char* signal parameters
tmp = tmp[6:] // Constness doesn't survive the CABI boundary
}
if strings.HasPrefix(tmp, "unsigned ") {
tmp = "u" + tmp[9:] // Cgo uses uchar, uint instead of full name
}
if strings.HasPrefix(tmp, "signed ") {
tmp = "s" + tmp[7:] // Cgo uses schar
}
tmp = strings.Replace(tmp, `long long`, `longlong`, -1)
tmp = "C." + strings.Replace(tmp, " ", "_", -1)
if p.QtClassType() || p.Pointer || p.ByRef {
return "*" + tmp
} else {
return tmp
}
}
func (p CppParameter) mallocSizeCgoExpression() string {
if p.ParameterType == "QString" || p.ParameterType == "QByteArray" {
return "int(unsafe.Sizeof(C.struct_miqt_string{}))"
}
// Default (sizeof pointer)
return "8"
}
func (gfs *goFileState) emitParametersGo(params []CppParameter) string {
tmp := make([]string, 0, len(params))
skipNext := false
for i, p := range params {
if IsArgcArgv(params, i) {
skipNext = true
tmp = append(tmp, "args []string")
} else if skipNext {
// Skip this parameter, already handled
skipNext = false
} else {
// Ordinary parameter
tmp = append(tmp, p.goParameterName()+" "+p.RenderTypeGo(gfs))
}
}
return strings.Join(tmp, ", ")
}
type goFileState struct {
imports map[string]struct{}
currentPackageName string
}
func (gfs *goFileState) emitParametersGo2CABIForwarding(m CppMethod) (preamble string, forwarding string) {
tmp := make([]string, 0, len(m.Parameters)+2)
if !m.IsStatic {
tmp = append(tmp, "this.h")
}
skipNext := false
for i, p := range m.Parameters {
if IsArgcArgv(m.Parameters, i) {
skipNext = true
// QApplication constructor. Convert 'args' into Qt's wanted types
// Qt has a warning in the docs saying these pointers must be valid
// for the entire lifetype of QApplication, so, malloc + never free
// This transformation only affects the Go side. The CABI side is
// projected naturally
preamble += "// Convert []string to long-lived int& argc, char** argv, never call free()\n"
preamble += "argc := (*C.int)(C.malloc(8))\n"
preamble += "*argc = C.int(len(args))\n"
preamble += "argv := (*[0xffff]*C.char)(C.malloc(C.size_t(8 * len(args))))\n"
preamble += "for i := range args {\n"
preamble += "argv[i] = C.CString(args[i])\n"
preamble += "}\n"
tmp = append(tmp, "argc, &argv[0]")
// Additional quirk for QApplication constructor: bind to OS thread
gfs.imports["runtime"] = struct{}{}
preamble += "\n"
preamble += "runtime.LockOSThread() // Prevent Go from migrating the main Qt thread\n"
preamble += "\n"
} else if skipNext {
// Skip this parameter, already handled
skipNext = false
} else {
addPreamble, rvalue := gfs.emitParameterGo2CABIForwarding(p)
preamble += addPreamble
tmp = append(tmp, rvalue)
}
}
return preamble, strings.Join(tmp, ", ")
}
func (gfs *goFileState) emitParameterGo2CABIForwarding(p CppParameter) (preamble string, rvalue string) {
nameprefix := makeNamePrefix(p.goParameterName())
if p.ParameterType == "QString" {
// Go: convert string -> miqt_string*
// CABI: convert miqt_string* -> real QString
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_ms := C.struct_miqt_string{}\n"
preamble += nameprefix + "_ms.data = C.CString(" + p.goParameterName() + ")\n"
preamble += nameprefix + "_ms.len = C.size_t(len(" + p.goParameterName() + "))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_ms.data))\n"
rvalue = nameprefix + "_ms"
} else if p.ParameterType == "QByteArray" {
// Go: convert []byte -> miqt_string
// CABI: convert miqt_string -> QByteArray
// n.b. This can ALIAS the existing []byte data
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_alias := C.struct_miqt_string{}\n"
preamble += "if len(" + p.goParameterName() + ") > 0 {\n"
preamble += nameprefix + "_alias.data = (*C.char)(unsafe.Pointer(&" + p.goParameterName() + "[0]))\n"
preamble += "} else {\n"
preamble += nameprefix + "_alias.data = (*C.char)(unsafe.Pointer(nil))\n"
preamble += "}\n"
preamble += nameprefix + "_alias.len = C.size_t(len(" + p.goParameterName() + "))\n"
rvalue = nameprefix + "_alias"
} else if listType, ok := p.QListOf(); ok {
// QList<T>
// Go: convert T[] -> t* and len
// CABI: create a real QList<>
gfs.imports["unsafe"] = struct{}{}
mallocSize := listType.mallocSizeCgoExpression()
preamble += nameprefix + "_CArray := (*[0xffff]" + listType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + mallocSize + " * len(" + p.goParameterName() + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_CArray))\n"
preamble += "for i := range " + p.goParameterName() + "{\n"
listType.ParameterName = p.goParameterName() + "[i]"
addPreamble, innerRvalue := gfs.emitParameterGo2CABIForwarding(listType)
preamble += addPreamble
preamble += nameprefix + "_CArray[i] = " + innerRvalue + "\n"
preamble += "}\n"
preamble += nameprefix + "_ma := C.struct_miqt_array{len: C.size_t(len(" + p.goParameterName() + ")), data: unsafe.Pointer(" + nameprefix + "_CArray)}\n"
rvalue = nameprefix + "_ma"
} else if _, ok := p.QSetOf(); ok {
panic("QSet<> arguments are not yet implemented") // n.b. doesn't seem to exist in QtCore/QtGui/QtWidgets at all
} else if kType, vType, ok := p.QMapOf(); ok {
// QMap<T>
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_Keys_CArray := (*[0xffff]" + kType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + kType.mallocSizeCgoExpression() + " * len(" + p.goParameterName() + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Keys_CArray))\n"
preamble += nameprefix + "_Values_CArray := (*[0xffff]" + vType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + vType.mallocSizeCgoExpression() + " * len(" + p.goParameterName() + "))))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Values_CArray))\n"
preamble += nameprefix + "_ctr := 0\n"
preamble += "for " + nameprefix + "_k, " + nameprefix + "_v := range " + p.goParameterName() + "{\n"
kType.ParameterName = nameprefix + "_k"
addPreamble, innerRvalue := gfs.emitParameterGo2CABIForwarding(kType)
preamble += addPreamble
preamble += nameprefix + "_Keys_CArray[" + nameprefix + "_ctr] = " + innerRvalue + "\n"
vType.ParameterName = nameprefix + "_v"
addPreamble, innerRvalue = gfs.emitParameterGo2CABIForwarding(vType)
preamble += addPreamble
preamble += nameprefix + "_Values_CArray[" + nameprefix + "_ctr] = " + innerRvalue + "\n"
preamble += nameprefix + "_ctr++\n"
preamble += "}\n"
preamble += nameprefix + "_mm := C.struct_miqt_map{\nlen: C.size_t(len(" + p.goParameterName() + ")),\nkeys: unsafe.Pointer(" + nameprefix + "_Keys_CArray),\nvalues: unsafe.Pointer(" + nameprefix + "_Values_CArray),\n}\n"
rvalue = nameprefix + "_mm"
} else if kType, vType, ok := p.QPairOf(); ok {
// QPair<T>
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_First_CArray := (*[0xffff]" + kType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + kType.mallocSizeCgoExpression() + ")))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_First_CArray))\n"
preamble += nameprefix + "_Second_CArray := (*[0xffff]" + vType.parameterTypeCgo() + ")(C.malloc(C.size_t(" + vType.mallocSizeCgoExpression() + ")))\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Second_CArray))\n"
kType.ParameterName = p.goParameterName() + ".First"
addPreamble, innerRvalue := gfs.emitParameterGo2CABIForwarding(kType)
preamble += addPreamble
preamble += nameprefix + "_First_CArray[0] = " + innerRvalue + "\n"
vType.ParameterName = p.goParameterName() + ".Second"
addPreamble, innerRvalue = gfs.emitParameterGo2CABIForwarding(vType)
preamble += addPreamble
preamble += nameprefix + "_Second_CArray[0] = " + innerRvalue + "\n"
preamble += nameprefix + "_pair := C.struct_miqt_map{\nlen: 1,\nkeys: unsafe.Pointer(" + nameprefix + "_First_CArray),\nvalues: unsafe.Pointer(" + nameprefix + "_Second_CArray),\n}\n"
rvalue = nameprefix + "_pair"
} else if p.Pointer && p.ParameterType == "char" {
// Single char* argument
gfs.imports["unsafe"] = struct{}{}
preamble += nameprefix + "_Cstring := C.CString(" + p.goParameterName() + ")\n"
preamble += "defer C.free(unsafe.Pointer(" + nameprefix + "_Cstring))\n"
rvalue = nameprefix + "_Cstring"
} else if /*(p.Pointer || p.ByRef) &&*/ p.QtClassType() {
// The C++ type is a pointer to Qt class
// We want our functions to accept the Go wrapper type, and forward as cPointer()
// cPointer() returns the cgo pointer which only works in the same package -
// anything cross-package needs to go via unsafe.Pointer
if classInfo, ok := KnownClassnames[p.ParameterType]; ok && gfs.currentPackageName != classInfo.PackageName {
// Cross-package
rvalue = "(" + p.parameterTypeCgo() + ")(" + p.goParameterName() + ".UnsafePointer())"
} else {
// Same package
rvalue = p.goParameterName() + ".cPointer()"
}
} else if p.IntType() || p.IsFlagType() || p.IsKnownEnum() || p.ParameterType == "bool" {
if p.Pointer || p.ByRef {
gfs.imports["unsafe"] = struct{}{}
rvalue = "(" + p.parameterTypeCgo() + ")(unsafe.Pointer(" + p.goParameterName() + "))" // n.b. This may not work if the integer type conversion was wrong
} else {
rvalue = "(" + p.parameterTypeCgo() + ")(" + p.goParameterName() + ")"
}
} else {
// Default
rvalue = p.goParameterName()
}
return preamble, rvalue
}
func (gfs *goFileState) emitCabiToGo(assignExpr string, rt CppParameter, rvalue string) string {
shouldReturn := assignExpr // "return "
afterword := ""
namePrefix := makeNamePrefix(rt.goParameterName())
if rt.Void() {
shouldReturn = ""
return shouldReturn + " " + rvalue + "\n" + afterword
} else if rt.ParameterType == "void" && rt.Pointer {
gfs.imports["unsafe"] = struct{}{}
return assignExpr + " (unsafe.Pointer)(" + rvalue + ")\n"
} else if rt.ParameterType == "char" && rt.Pointer {
// Qt functions normally return QString - anything returning char*
// is something like QByteArray.Data() where it returns an unsafe
// internal pointer
// However in case this is a signal, we need to be able to marshal both
// forwards and backwards with the same types, this has to be a string
// in both cases
// This is not a miqt_string and therefore MIQT did not allocate it,
// and therefore we don't have to free it either
gfs.imports["unsafe"] = struct{}{}
shouldReturn = namePrefix + "_ret := "
afterword += assignExpr + " C.GoString(" + namePrefix + "_ret)\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if rt.ParameterType == "QString" {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_ms C.struct_miqt_string = "
afterword += namePrefix + "_ret := C.GoStringN(" + namePrefix + "_ms.data, C.int(int64(" + namePrefix + "_ms.len)))\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_ms.data))\n"
afterword += assignExpr + namePrefix + "_ret"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if rt.ParameterType == "QByteArray" {
// We receive the CABI type of a miqt_string. Convert it into []byte
// We must free the miqt_string data pointer - this is a data copy,
// not an alias
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_bytearray C.struct_miqt_string = "
afterword += namePrefix + "_ret := C.GoBytes(unsafe.Pointer(" + namePrefix + "_bytearray.data), C.int(int64(" + namePrefix + "_bytearray.len)))\n"
afterword += "C.free(unsafe.Pointer(" + namePrefix + "_bytearray.data))\n"
afterword += assignExpr + namePrefix + "_ret"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if t, ok := rt.QListOf(); ok {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_ma C.struct_miqt_array = "
afterword += namePrefix + "_ret := make([]" + t.RenderTypeGo(gfs) + ", int(" + namePrefix + "_ma.len))\n"
afterword += namePrefix + "_outCast := (*[0xffff]" + t.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_ma.data)) // hey ya\n"
afterword += "for i := 0; i < int(" + namePrefix + "_ma.len); i++ {\n"
afterword += gfs.emitCabiToGo(namePrefix+"_ret[i] = ", t, namePrefix+"_outCast[i]")
afterword += "}\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if t, ok := rt.QSetOf(); ok {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_ma C.struct_miqt_array = "
afterword += namePrefix + "_ret := make(map[" + t.RenderTypeGo(gfs) + "]struct{}, int(" + namePrefix + "_ma.len))\n"
afterword += namePrefix + "_outCast := (*[0xffff]" + t.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_ma.data)) // hey ya\n"
afterword += "for i := 0; i < int(" + namePrefix + "_ma.len); i++ {\n"
afterword += gfs.emitCabiToGo(namePrefix+"_element := ", t, namePrefix+"_outCast[i]") + "\n"
afterword += namePrefix + "_ret[" + namePrefix + "_element] = struct{}{}\n"
afterword += "}\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if kType, vType, ok := rt.QMapOf(); ok {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_mm C.struct_miqt_map = "
afterword += namePrefix + "_ret := make(map[" + kType.RenderTypeGo(gfs) + "]" + vType.RenderTypeGo(gfs) + ", int(" + namePrefix + "_mm.len))\n"
afterword += namePrefix + "_Keys := (*[0xffff]" + kType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.keys))\n"
afterword += namePrefix + "_Values := (*[0xffff]" + vType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.values))\n"
afterword += "for i := 0; i < int(" + namePrefix + "_mm.len); i++ {\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_Key := ", kType, namePrefix+"_Keys[i]") + "\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_Value := ", vType, namePrefix+"_Values[i]") + "\n"
afterword += namePrefix + "_ret[" + namePrefix + "_entry_Key] = " + namePrefix + "_entry_Value\n"
afterword += "}\n"
afterword += assignExpr + " " + namePrefix + "_ret\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if kType, vType, ok := rt.QPairOf(); ok {
gfs.imports["unsafe"] = struct{}{}
shouldReturn = "var " + namePrefix + "_mm C.struct_miqt_map = "
afterword += namePrefix + "_First_CArray := (*[0xffff]" + kType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.keys))\n"
afterword += namePrefix + "_Second_CArray := (*[0xffff]" + vType.parameterTypeCgo() + ")(unsafe.Pointer(" + namePrefix + "_mm.values))\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_First := ", kType, namePrefix+"_First_CArray[0]") + "\n"
afterword += gfs.emitCabiToGo(namePrefix+"_entry_Second := ", vType, namePrefix+"_Second_CArray[0]") + "\n"
afterword += assignExpr + " " + rt.RenderTypeGo(gfs) + " { First: " + namePrefix + "_entry_First , Second: " + namePrefix + "_entry_Second }\n"
return shouldReturn + " " + rvalue + "\n" + afterword
} else if rt.QtClassType() {
// Construct our Go type based on this inner CABI type
crossPackage := ""
pkg, ok := KnownClassnames[rt.ParameterType]
if !ok {
panic("emitCabiToGo: Encountered an unknown Qt class")
}
if pkg.PackageName != gfs.currentPackageName {
crossPackage = path.Base(pkg.PackageName) + "."
gfs.imports[importPathForQtPackage(pkg.PackageName)] = struct{}{}
}
// We can only reference the rvalue once, in case it is a complex
// expression
if crossPackage == "" {
rvalue = "new" + cabiClassName(rt.ParameterType) + "(" + rvalue + ")"
} else {
gfs.imports["unsafe"] = struct{}{}
rvalue = crossPackage + "UnsafeNew" + cabiClassName(rt.ParameterType) + "(unsafe.Pointer(" + rvalue + "))"
}
if !(rt.Pointer || rt.ByRef) {
// This is return by value, but CABI has new'd it into a
// heap type for us
// To preserve Qt's approximate semantics, add a runtime
// finalizer to automatically Delete once the type goes out
// of Go scope
afterword += namePrefix + "_goptr := " + rvalue + "\n"
afterword += namePrefix + "_goptr.GoGC() // Qt uses pass-by-value semantics for this type. Mimic with finalizer\n"
// If this is a function return, we have converted value-returned Qt types to pointers
// If this is a slot return, we haven't
// TODO standardize this
// e.g. QStringListModel::ItemData
if strings.Contains(assignExpr, `return`) {
afterword += assignExpr + "" + namePrefix + "_goptr\n"
} else {
afterword += assignExpr + " *" + namePrefix + "_goptr\n"
}
} else {
// No need for temporary _goptr variable
afterword += assignExpr + "" + rvalue + "\n"
}
return afterword
} else if rt.IntType() || rt.IsKnownEnum() || rt.IsFlagType() || rt.ParameterType == "bool" || rt.QtCppOriginalType != nil {
if rt.Pointer || rt.ByRef {
// Cast must go via unsafe.Pointer
gfs.imports["unsafe"] = struct{}{}
return assignExpr + "(" + rt.RenderTypeGo(gfs) + ")(unsafe.Pointer(" + rvalue + "))\n"
}
// Need to cast Cgo type to Go int type
// Optimize assignment to avoid temporary
return assignExpr + "(" + rt.RenderTypeGo(gfs) + ")(" + rvalue + ")\n"
} else {
return "int /* TODO */" //panic(fmt.Sprintf("emitgo::emitCabiToGo missing type handler for parameter %+v", rt))
}
}
func emitGo(src *CppParsedHeader, headerName string, packageName string) (string, string, error) {
ret := strings.Builder{}
ret.WriteString(`package ` + path.Base(packageName) + `
/*
#include "gen_` + headerName + `"
#include <stdlib.h>
*/
import "C"
%%_IMPORTLIBS_%%
`)
gfs := goFileState{
imports: map[string]struct{}{},
currentPackageName: packageName,
}
var bigints []string
// Check if short-named enums are allowed.
// We only allow short names if there are no conflicts anywhere in the whole
// file. This doesn't fully defend against cross-file conflicts but those
// should hopefully be rare enough
preventShortNames := map[string]struct{}{}
{
nameTest := map[string]string{}
nextEnum:
for _, e := range src.Enums {
shortEnumName := e.ShortEnumName()
// Disallow entry<-->entry collisions
for _, ee := range e.Entries {
if other, ok := nameTest[shortEnumName+"::"+ee.EntryName]; ok {
preventShortNames[e.EnumName] = struct{}{} // Our full enum name
preventShortNames[other] = struct{}{} // Their full enum name
continue nextEnum
}
nameTest[shortEnumName+"::"+ee.EntryName] = e.EnumName
if _, ok := KnownClassnames[shortEnumName+"::"+ee.EntryName]; ok {
preventShortNames[e.EnumName] = struct{}{}
continue nextEnum
}
if _, ok := KnownEnums[shortEnumName+"::"+ee.EntryName]; ok {
preventShortNames[e.EnumName] = struct{}{}
continue nextEnum
}
}
}
}
for _, e := range src.Enums {
if e.EnumName == "" {
continue // Removed by transformRedundant AST pass
}
goEnumName := cabiClassName(e.EnumName) // Fully qualified name of the enum itself
goEnumShortName := goEnumName // Shorter name, so that enum elements are reachable from the surrounding namespace
if _, ok := preventShortNames[e.EnumName]; !ok {
goEnumShortName = cabiClassName(e.ShortEnumName())
}
ret.WriteString(`
type ` + goEnumName + ` ` + e.UnderlyingType.RenderTypeGo(&gfs) + `
`)
if len(e.Entries) > 0 {
var smallvalues []string
for _, ee := range e.Entries {
isBigInt := false
if e.UnderlyingType.IntType() {
// Int-type enums need special handling in case of 64-bit
// overflow, that would prevent using miqt on 32-bit platforms
entryValueI64, err := strconv.ParseInt(ee.EntryValue, 10, 64)
if err != nil {
panic("Enum " + ee.EntryName + " has non-parseable integer value")
}
if entryValueI64 > math.MaxInt32 || entryValueI64 < math.MinInt32 {
isBigInt = true
}
}
enumConstDeclaration := titleCase(cabiClassName(goEnumShortName+"::"+ee.EntryName)) + " " + goEnumName + " = " + ee.EntryValue
if isBigInt {
bigints = append(bigints, "const "+enumConstDeclaration+"\n")
} else {
smallvalues = append(smallvalues, enumConstDeclaration+"\n")
}
}
if len(smallvalues) > 0 {
ret.WriteString("const (\n")
ret.WriteString(strings.Join(smallvalues, ""))
ret.WriteString("\n)\n\n")
}
}
}
for _, c := range src.Classes {
goClassName := cabiClassName(c.ClassName)
// Type definition
ret.WriteString(`
type ` + goClassName + ` struct {
h *C.` + goClassName + `
`)
// Embed all inherited types to directly allow calling inherited methods
// Only include the direct inherits; the recursive inherits will exist
// on these types already
for _, base := range c.DirectInherits {
if strings.HasPrefix(base, `QList<`) {
ret.WriteString("/* Also inherits unprojectable " + base + " */\n")
} else if pkg, ok := KnownClassnames[base]; ok && pkg.PackageName != gfs.currentPackageName {
// Cross-package parent class
ret.WriteString("*" + path.Base(pkg.PackageName) + "." + cabiClassName(base) + "\n")
gfs.imports[importPathForQtPackage(pkg.PackageName)] = struct{}{}
} else {
// Same-package parent class
ret.WriteString("*" + cabiClassName(base) + "\n")
}
}
ret.WriteString(`
}
func (this *` + goClassName + `) cPointer() *C.` + goClassName + ` {
if this == nil {
return nil
}
return this.h
}
func (this *` + goClassName + `) UnsafePointer() unsafe.Pointer {
if this == nil {
return nil
}
return unsafe.Pointer(this.h)
}
`)
// CGO types only exist within the same Go file, so other Go files can't
// call this same private ctor function, unless it goes through unsafe.Pointer{}.
// This is probably because C types can possibly violate the ODR whereas
// that never happens in Go's type system.
gfs.imports["unsafe"] = struct{}{}
localInit := "h: h"
ret.WriteString(`
// new` + goClassName + ` constructs the type using only CGO pointers.
func new` + goClassName + `(h *C.` + goClassName + `) *` + goClassName + ` {
if h == nil {
return nil
}
`)
if len(c.DirectInheritClassInfo()) > 0 {
xbaseParams := ""
for _, pkg := range c.DirectInheritClassInfo() {
base := pkg.Class.ClassName
// Make extra CGO call to get base pointers from C++ space
outptrVar := "outptr_" + cabiClassName(base)
ret.WriteString("var " + outptrVar + " *C." + cabiClassName(base) + " = nil\n")
xbaseParams += ", &" + outptrVar
// Set up how we would pass the pointer to its own make function
if pkg.PackageName != gfs.currentPackageName {
localInit += ",\n" + cabiClassName(base) + ": " + path.Base(pkg.PackageName) + "." + "UnsafeNew" + cabiClassName(base) + "(unsafe.Pointer(" + outptrVar + "))"
} else {
localInit += ",\n" + cabiClassName(base) + ": new" + cabiClassName(base) + "(" + outptrVar + ")"
}
}
// Populate outptr pointers
ret.WriteString("C." + cabiVirtBaseName(c) + "(h" + xbaseParams + ")\n")
}
ret.WriteString(`
return &` + goClassName + `{` + localInit + `}
}
// UnsafeNew` + goClassName + ` constructs the type using only unsafe pointers.
func UnsafeNew` + goClassName + `(h unsafe.Pointer) *` + goClassName + ` {
return new` + goClassName + `( (*C.` + goClassName + `)(h) )
}
`)
//
for i, ctor := range c.Ctors {
preamble, forwarding := gfs.emitParametersGo2CABIForwarding(ctor)
ret.WriteString(`
// New` + goClassName + maybeSuffix(i) + ` constructs a new ` + c.ClassName + ` object.
func New` + goClassName + maybeSuffix(i) + `(` + gfs.emitParametersGo(ctor.Parameters) + `) *` + goClassName + ` {
`,
)
if ctor.LinuxOnly {
gfs.imports["runtime"] = struct{}{}
ret.WriteString(`
if runtime.GOOS != "linux" {
panic("Unsupported OS")
}
`)
}
ret.WriteString(preamble)
// Call Cgo constructor
ret.WriteString(`
return new` + goClassName + `(C.` + cabiNewName(c, i) + `(` + forwarding + `))
}
`)
}
for _, m := range c.Methods {
if m.IsProtected {
continue // Don't add a direct call for it
}
preamble, forwarding := gfs.emitParametersGo2CABIForwarding(m)
returnTypeDecl := m.ReturnType.renderReturnTypeGo(&gfs)
rvalue := `C.` + cabiMethodName(c, m) + `(` + forwarding + `)`
returnFunc := gfs.emitCabiToGo("return ", m.ReturnType, rvalue)
receiverAndMethod := `(this *` + goClassName + `) ` + m.goMethodName()
if m.IsStatic {
receiverAndMethod = goClassName + `_` + m.goMethodName()
}
ret.WriteString(`
func ` + receiverAndMethod + `(` + gfs.emitParametersGo(m.Parameters) + `) ` + returnTypeDecl + ` {`)
if m.LinuxOnly {
gfs.imports["runtime"] = struct{}{}
ret.WriteString(`
if runtime.GOOS != "linux" {
panic("Unsupported OS")
}
`)
}
ret.WriteString(`
` + preamble +
returnFunc + `}
`)
// Add Connect() wrappers for signal functions
if m.IsSignal {
gfs.imports["unsafe"] = struct{}{}
gfs.imports["runtime/cgo"] = struct{}{}
var cgoNamedParams []string
var paramNames []string
conversion := ""
if len(m.Parameters) > 0 {
conversion = "// Convert all CABI parameters to Go parameters\n"
}
for i, pp := range m.Parameters {
cgoNamedParams = append(cgoNamedParams, pp.goParameterName()+" "+pp.parameterTypeCgo())
paramNames = append(paramNames, fmt.Sprintf("slotval%d", i+1))
conversion += gfs.emitCabiToGo(fmt.Sprintf("slotval%d := ", i+1), pp, pp.goParameterName()) + "\n"
}
goCbType := `func(` + gfs.emitParametersGo(m.Parameters) + `)`
callbackName := cabiCallbackName(c, m)
ret.WriteString(`func (this *` + goClassName + `) On` + m.goMethodName() + `(slot ` + goCbType + `) {
C.` + cabiConnectName(c, m) + `(this.h, C.intptr_t(cgo.NewHandle(slot)) )
}
//export ` + callbackName + `
func ` + callbackName + `(cb C.intptr_t` + ifv(len(m.Parameters) > 0, ", ", "") + strings.Join(cgoNamedParams, `, `) + `) {
gofunc, ok := cgo.Handle(cb).Value().(` + goCbType + `)
if !ok {
panic("miqt: callback of non-callback type (heap corruption?)")
}
` + conversion + `
gofunc(` + strings.Join(paramNames, `, `) + ` )
}
`)
}
}
for _, m := range c.VirtualMethods() {
gfs.imports["unsafe"] = struct{}{}
gfs.imports["runtime/cgo"] = struct{}{}
// Add a package-private function to call the C++ base class method
// QWidget_virtualbase_PaintEvent
// This is only possible if the function is not pure-virtual
if !m.IsPureVirtual {
preamble, forwarding := gfs.emitParametersGo2CABIForwarding(m)
forwarding = "unsafe.Pointer(this.h)" + strings.TrimPrefix(forwarding, `this.h`) // TODO integrate properly
returnTypeDecl := m.ReturnType.renderReturnTypeGo(&gfs)
ret.WriteString(`
func (this *` + goClassName + `) callVirtualBase_` + m.goMethodName() + `(` + gfs.emitParametersGo(m.Parameters) + `) ` + returnTypeDecl + ` {
` + preamble + `
` + gfs.emitCabiToGo("return ", m.ReturnType, `C.`+cabiVirtualBaseName(c, m)+`(`+forwarding+`)`) + `
}
`)
}
// Add a function to set the virtual override handle
// It must be possible to call the base class version, so pass
// that a as a 'super' callback as an extra parameter
{
var cgoNamedParams []string
var paramNames []string
if !m.IsPureVirtual {
paramNames = append(paramNames, "(&"+goClassName+"{h: self}).callVirtualBase_"+m.goMethodName())
}
conversion := ""
if len(m.Parameters) > 0 {
conversion = "// Convert all CABI parameters to Go parameters\n"
}
for i, pp := range m.Parameters {
cgoNamedParams = append(cgoNamedParams, pp.goParameterName()+" "+pp.parameterTypeCgo())
paramNames = append(paramNames, fmt.Sprintf("slotval%d", i+1))
conversion += gfs.emitCabiToGo(fmt.Sprintf("slotval%d := ", i+1), pp, pp.goParameterName()) + "\n"
}
cabiReturnType := m.ReturnType.parameterTypeCgo()
if cabiReturnType == "C.void" {
cabiReturnType = ""
}
superCbType := `func(` + gfs.emitParametersGo(m.Parameters) + `) ` + m.ReturnType.renderReturnTypeGo(&gfs)
goCbType := `func(`
if !m.IsPureVirtual {
goCbType += `super ` + superCbType
if len(m.Parameters) > 0 {
goCbType += `, `
}
}
goCbType += gfs.emitParametersGo(m.Parameters)
goCbType += `) ` + m.ReturnType.renderReturnTypeGo(&gfs)
callbackName := cabiCallbackName(c, m)
ret.WriteString(`func (this *` + goClassName + `) On` + m.SafeMethodName() + `(slot ` + goCbType + `) {
ok := C.` + cabiOverrideVirtualName(c, m) + `(unsafe.Pointer(this.h), C.intptr_t(cgo.NewHandle(slot)) )
if !ok {
panic("miqt: can only override virtual methods for directly constructed types")
}
}
//export ` + callbackName + `
func ` + callbackName + `(self *C.` + goClassName + `, cb C.intptr_t` + ifv(len(m.Parameters) > 0, ", ", "") + strings.Join(cgoNamedParams, `, `) + `) ` + cabiReturnType + `{
gofunc, ok := cgo.Handle(cb).Value().(` + goCbType + `)
if !ok {
panic("miqt: callback of non-callback type (heap corruption?)")
}
`)
ret.WriteString(conversion + "\n")
if cabiReturnType == "" {
ret.WriteString(`gofunc(` + strings.Join(paramNames, `, `) + " )\n")
} else {
ret.WriteString(`virtualReturn := gofunc(` + strings.Join(paramNames, `, `) + " )\n")
virtualRetP := m.ReturnType // copy
virtualRetP.ParameterName = "virtualReturn"
binding, rvalue := gfs.emitParameterGo2CABIForwarding(virtualRetP)
ret.WriteString(binding + "\n")
ret.WriteString("return " + rvalue + "\n")
}
ret.WriteString(`
}
`)
}
}
if c.CanDelete {
gfs.imports["runtime"] = struct{}{} // Finalizer
ret.WriteString(`
// Delete this object from C++ memory.
func (this *` + goClassName + `) Delete() {
C.` + cabiDeleteName(c) + `(this.h)
}
// GoGC adds a Go Finalizer to this pointer, so that it will be deleted
// from C++ memory once it is unreachable from Go memory.
func (this *` + goClassName + `) GoGC() {
runtime.SetFinalizer(this, func(this *` + goClassName + `) {
this.Delete()
runtime.KeepAlive(this.h)
})
}
`)
}
}
goSrc := ret.String()
// Fixup imports
if len(gfs.imports) > 0 {
allImports := make([]string, 0, len(gfs.imports))
for k := range gfs.imports {
if k == "libmiqt" {
allImports = append(allImports, `"`+BaseModule+`/libmiqt"`)
} else {
allImports = append(allImports, `"`+k+`"`)
}
}
sort.Strings(allImports)
goSrc = strings.Replace(goSrc, `%%_IMPORTLIBS_%%`, "import (\n\t"+strings.Join(allImports, "\n\t")+"\n)", 1)
} else {
goSrc = strings.Replace(goSrc, `%%_IMPORTLIBS_%%`, "", 1)
}
// Run gofmt over the result
formattedSrc, err := format.Source([]byte(goSrc))
if err != nil {
log.Printf("gofmt failure: %v", err)
formattedSrc = []byte(goSrc)
}
// Determine if we need to produce a _64bit.go file
bigintSrc := ""
if len(bigints) > 0 {
bigintSrc = `//go:build !386 && !arm
// +build !386,!arm
package ` + path.Base(packageName) + "\n\n" + strings.Join(bigints, "") + "\n"
}
return string(formattedSrc), bigintSrc, nil
}