diff --git a/cl/compile.go b/cl/compile.go index 31805f50c..16dd732e9 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -17,6 +17,7 @@ package cl import ( + "fmt" "go/token" "go/types" "maps" @@ -146,6 +147,9 @@ type Config struct { // Go const names (optional). MacroPrefix []string + // Logical-Type Method Detection (optional). See https://github.com/xgo-dev/llcppg/issues/945. + MethodCheck []string + // FuncPrefix specifies the prefix to remove from C/C++ global function names when // generating Go function names (optional). FuncPrefix []string @@ -212,6 +216,10 @@ func NewPackage(pkgPath, pkgName string, files []Source, conf *Config) (ret Pack if conf == nil { conf = &Config{} } + methodCheck, err := newMethodChecks(conf.MethodCheck) + if err != nil { + return + } confGox := &gogen.Config{ Fset: conf.Fset, Importer: conf.Importer, @@ -249,8 +257,8 @@ func NewPackage(pkgPath, pkgName string, files []Source, conf *Config) (ret Pack keepDoc: !conf.DontKeepDoc, stdRecvName: conf.UseStdRecvName, cflags: conf.CFlags, wrapFileHeader: conf.WrapFileHeader, forceCamelCase: conf.ForceCamelCase, typeAbbr: conf.TypeAbbr, typeAbbrSuffix: conf.TypeAbbrSuffix, - typePrefix: conf.TypePrefix, typeSuffix: conf.TypeSuffix, typeAlias: conf.TypeAlias, typeAliasCache: make(map[string]types.Type), + mthdCheck: methodCheck, typePrefix: conf.TypePrefix, typeSuffix: conf.TypeSuffix, fnPrefix: conf.FuncPrefix, enumPrefix: conf.EnumPrefix, rename: rename, nsPrefix: conf.NSPrefix, macroPrefix: conf.MacroPrefix, varPrefix: conf.VarPrefix, nsIgnore: conf.NSIgnore, macroIgnore: conf.MacroIgnore, typeIgnore: conf.TypeIgnore, @@ -273,6 +281,10 @@ func NewPackage(pkgPath, pkgName string, files []Source, conf *Config) (ret Pack ret.Package = pkg ret.Wrap = ctx.wrap ret.Public = ctx.pubs + + if ctx.errCnt > 0 { + err = fmt.Errorf("compilation failed with %d errors", ctx.errCnt) + } return } diff --git a/cl/ctx.go b/cl/ctx.go index 60f4f16b0..d0382a8a5 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -120,6 +120,7 @@ type pkgCtx struct { typeAbbrSuffix []string + mthdCheck []*mthdCheck nsPrefix []string fnPrefix []string varPrefix []string diff --git a/cl/func.go b/cl/func.go index e30158d27..c58abe1ee 100644 --- a/cl/func.go +++ b/cl/func.go @@ -128,27 +128,35 @@ func compileFuncOrMethod(ctx *pkgCtx, obj *overloadObj, this *classCtx) { var recv *types.Var var typRecv *types.Named // if tryToMethod succeeded, this is the recv - var typName, typCName string + var typName, typCName, clsName string var nameInPkg string if this == nil { if ctx.lang == LanguageC { // try to method for C global functions - params, recv, typRecv, typName = tryToMethod(ctx, pkgTypes, params) - if typRecv != nil { - recvCType := fn.Argument(0).Type() - if recvCType.Kind == lc.Type_Pointer { - recvCType = recvCType.Pointee() + if cls, mayClass := ctx.methodCheck(cName); mayClass { + params, recv, typRecv, typName = tryToMethod(ctx, pkgTypes, params) + if typRecv != nil { + recvCType := fn.Argument(0).Type() + if recvCType.Kind == lc.Type_Pointer { + recvCType = recvCType.Pointee() + } + typCName = trimTypeTag(clang.String(recvCType.Unqualified())) + if cls != "" { + clsName = cls + } else { + clsName = typName + } } - typCName = trimTypeTag(clang.String(recvCType.Unqualified())) } } } else { typNamed := this.typNamed recv = types.NewParam(goNodePos(ctx, fn), pkgTypes, "this", types.NewPointer(typNamed)) typName = typNamed.Obj().Name() + clsName = typName } - fnName := ctx.funcName(name, obj.order(), typName, typCName, this == nil, true) + fnName := ctx.funcName(name, obj.order(), clsName, typCName, this == nil, true) if recv == nil { nameInPkg = fnName diff --git a/cl/name.go b/cl/name.go index 5cd1c3bb2..c00fd859e 100644 --- a/cl/name.go +++ b/cl/name.go @@ -43,6 +43,84 @@ func objUSR(decl clang.Cursor) string { // ----------------------------------------------------------------------------- +type mthdCheck struct { + prefix string + pattern []string + result string +} + +func newMethodCheck(check string) (*mthdCheck, error) { + pos := strings.IndexByte(check, ':') + if pos < 0 { + return nil, fmt.Errorf("invalid MethodCheck: %s", check) + } + pattern := strings.TrimSpace(check[:pos]) + parts := strings.Split(pattern, "*") + result := strings.TrimSpace(check[pos+1:]) + return &mthdCheck{ + prefix: parts[0], + pattern: parts[1:], + result: result, + }, nil +} + +func newMethodChecks(check []string) ([]*mthdCheck, error) { + ret := make([]*mthdCheck, len(check)) + for i, c := range check { + m, err := newMethodCheck(c) + if err != nil { + return nil, err + } + ret[i] = m + } + return ret, nil +} + +func (p *mthdCheck) check(name string) (cls string, ok bool) { + name, ok = strings.CutPrefix(name, p.prefix) + if !ok { + return + } + n := 0 + match := make([]string, len(p.pattern)) + for i, p := range p.pattern { + pos := strings.Index(name, p) + if pos < 0 { + return + } + n += pos + match[i] = name[:pos] + name = name[pos+len(p):] + } + cls = matchResult(p.result, match, n) + ok = cls != "" + return +} + +func matchResult(result string, match []string, n int) string { + b := make([]byte, 0, len(result)+n) + for i := 0; i < len(result); i++ { + if result[i] == '$' { + if i+1 < len(result) { + i++ + c := result[i] + if c >= '1' && c <= '9' { + if index := int(c - '1'); index < len(match) { + b = append(b, match[index]...) + continue + } + } + b = append(b, '$', c) + continue + } + } + b = append(b, result[i]) + } + return string(b) +} + +// ----------------------------------------------------------------------------- + func cNameSplit(cName string) (parts []string, hasNS bool) { for { pos := strings.IndexAny(cName, "_:") @@ -169,6 +247,18 @@ func (p *pkgCtx) typeName(cName string, _ bool) string { return p.cstyleToGo(name, underscoreStart, true) } +func (p *pkgCtx) methodCheck(cName string) (cls string, mayClass bool) { + if len(p.mthdCheck) == 0 { + return "", true + } + for _, m := range p.mthdCheck { + if cls, mayClass = m.check(cName); mayClass { + return + } + } + return +} + func (p *pkgCtx) funcName(cName string, order int, typName, typCName string, global, _ bool) string { if v, ok := p.rename[cName]; ok { return v // special case diff --git a/tool/_testc/python-3.14.8/include/pythread.h b/tool/_testc/python-3.14.8/include/pythread.h index a6ab0122a..cc043e3d3 100644 --- a/tool/_testc/python-3.14.8/include/pythread.h +++ b/tool/_testc/python-3.14.8/include/pythread.h @@ -17,6 +17,13 @@ typedef enum Py_Lock_Status { typedef struct _Py_tss_t Py_tss_t; /* opaque */ +struct PyObject {}; + +// Test if an object is the True singleton, the same as "x is True" in Python. +PyAPI_FUNC(int) Py_IsTrue(PyObject *x); + +PyAPI_FUNC(int) PyObject_IsTrue(PyObject *x); + #ifndef Py_LIMITED_API # define Py_CPYTHON_PYTHREAD_H # include "cpython/pythread.h" diff --git a/tool/_testc/python-3.14.8/llcppg.cfg b/tool/_testc/python-3.14.8/llcppg.cfg index 8bdde9c70..d73d1be3f 100644 --- a/tool/_testc/python-3.14.8/llcppg.cfg +++ b/tool/_testc/python-3.14.8/llcppg.cfg @@ -8,6 +8,7 @@ "Class": [], "NonClass": [], "TypePrefix": ["Py_", "Py"], + "MethodCheck": ["Py*_: $1"], "FuncPrefix": ["Py_"], "MacroPrefix": ["PY_", "Py_"], "EnumPrefix": ["PY_"], diff --git a/tool/_testc/python-3.14.8/pythread.go b/tool/_testc/python-3.14.8/pythread.go index 59aa9a159..6044669cf 100644 --- a/tool/_testc/python-3.14.8/pythread.go +++ b/tool/_testc/python-3.14.8/pythread.go @@ -28,6 +28,18 @@ type X_tssT struct { X_key pthread.Key } type TssT = X_tssT +type Object struct { +} + +// Test if an object is the True singleton, the same as "x is True" in Python. +// +//go:linkname IsTrue C.Py_IsTrue +func IsTrue(x *Object) c.Int + +// llgo:link (*Object).IsTrue C.PyObject_IsTrue +func (self *Object) IsTrue() c.Int { + return 0 +} // PY_TIMEOUT_MAX is the highest usable value (in microseconds) of PY_TIMEOUT_T // type, and depends on the system threading API. diff --git a/tool/config.go b/tool/config.go index a9f94a656..45f78fee8 100644 --- a/tool/config.go +++ b/tool/config.go @@ -39,6 +39,7 @@ type Config struct { Class []string `json:"Class"` // typedef names to be treated as classes NonClass []string `json:"NonClass"` // typedef names to be treated as non-classes NSPrefix []string `json:"NSPrefix"` // C/C++ namespace prefix to remove + MethodCheck []string `json:"MethodCheck"` // C/C++ method check list FuncPrefix []string `json:"FuncPrefix"` // C/C++ function name prefix to remove VarPrefix []string `json:"VarPrefix"` // C/C++ variable name prefix to remove EnumPrefix []string `json:"EnumPrefix"` // C/C++ enum value prefix to remove diff --git a/tool/gen.go b/tool/gen.go index 09ac5d1e7..d21bbee1f 100644 --- a/tool/gen.go +++ b/tool/gen.go @@ -199,6 +199,7 @@ func (cfg *Config) NewPackage(pkgPath, pkgName, workDir string, index clang.Inde Class: cfg.Class, NonClass: cfg.NonClass, NSPrefix: cfg.NSPrefix, + MethodCheck: cfg.MethodCheck, FuncPrefix: cfg.FuncPrefix, VarPrefix: cfg.VarPrefix, EnumPrefix: cfg.EnumPrefix, diff --git a/tool/gen_test.go b/tool/gen_test.go index 932cd400d..e017a0237 100644 --- a/tool/gen_test.go +++ b/tool/gen_test.go @@ -142,6 +142,7 @@ func testSingleFile(t *testing.T, idx clang.Index, pkgDir, headerDir, headerFile Class: conf.Class, NonClass: conf.NonClass, NSPrefix: conf.NSPrefix, + MethodCheck: conf.MethodCheck, FuncPrefix: conf.FuncPrefix, EnumPrefix: conf.EnumPrefix, MacroPrefix: conf.MacroPrefix,