diff --git a/loader/cgo.go b/loader/cgo.go index abb5847c..dc217f96 100644 --- a/loader/cgo.go +++ b/loader/cgo.go @@ -17,11 +17,12 @@ import ( type fileInfo struct { *ast.File *Package - filename string - functions map[string]*functionInfo - globals map[string]*globalInfo - typedefs map[string]*typedefInfo - importCPos token.Pos + filename string + functions map[string]*functionInfo + globals map[string]*globalInfo + typedefs map[string]*typedefInfo + elaboratedTypes map[string]ast.Expr + importCPos token.Pos } // functionInfo stores some information about a Cgo function found by libclang @@ -81,12 +82,13 @@ typedef unsigned long long _Cgo_ulonglong; // comment with libclang, and modifies the AST to use this information. func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error { info := &fileInfo{ - File: f, - Package: p, - filename: filename, - functions: map[string]*functionInfo{}, - globals: map[string]*globalInfo{}, - typedefs: map[string]*typedefInfo{}, + File: f, + Package: p, + filename: filename, + functions: map[string]*functionInfo{}, + globals: map[string]*globalInfo{}, + typedefs: map[string]*typedefInfo{}, + elaboratedTypes: map[string]ast.Expr{}, } // Find `import "C"` statements in the file. @@ -142,9 +144,12 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []er // Forward C types to Go types (like C.uint32_t -> uint32). info.addTypeAliases() - // Add type declarations for C types, declared using typeef in C. + // Add type declarations for C types, declared using typedef in C. info.addTypedefs() + // Add elaborated types for C structs and unions. + info.addElaboratedTypes() + // Patch the AST to use the declared types and functions. f = astutil.Apply(f, info.walker, nil).(*ast.File) @@ -376,6 +381,42 @@ func (info *fileInfo) addTypedefs() { info.Decls = append(info.Decls, gen) } +// addElaboratedTypes adds C elaborated types as aliases. These are the "struct +// foo" or "union foo" types, often used in a typedef. +// +// See also: +// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier +func (info *fileInfo) addElaboratedTypes() { + gen := &ast.GenDecl{ + TokPos: info.importCPos, + Tok: token.TYPE, + } + names := make([]string, 0, len(info.elaboratedTypes)) + for name := range info.elaboratedTypes { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + typ := info.elaboratedTypes[name] + typeName := "C.struct_" + name + obj := &ast.Object{ + Kind: ast.Typ, + Name: typeName, + } + typeSpec := &ast.TypeSpec{ + Name: &ast.Ident{ + NamePos: info.importCPos, + Name: typeName, + Obj: obj, + }, + Type: typ, + } + obj.Decl = typeSpec + gen.Specs = append(gen.Specs, typeSpec) + } + info.Decls = append(info.Decls, gen) +} + // walker replaces all "C". expressions to literal "C." // expressions. Such expressions are impossible to write in Go (a dot cannot be // used in the middle of a name) so in practice all C identifiers live in a diff --git a/loader/libclang.go b/loader/libclang.go index 4adf141a..4c79fcaf 100644 --- a/loader/libclang.go +++ b/loader/libclang.go @@ -332,7 +332,25 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr { } case C.CXType_Elaborated: underlying := C.clang_Type_getNamedType(typ) - return info.makeASTType(underlying) + switch underlying.kind { + case C.CXType_Record: + cursor := C.tinygo_clang_getTypeDeclaration(typ) + name := getString(C.tinygo_clang_getCursorSpelling(cursor)) + // It is possible that this is a recursive definition, for example + // in linked lists (structs contain a pointer to the next element + // of the same type). If the name exists in info.elaboratedTypes, + // it is being processed, although it may not be fully defined yet. + if _, ok := info.elaboratedTypes[name]; !ok { + info.elaboratedTypes[name] = nil // predeclare (to avoid endless recursion) + info.elaboratedTypes[name] = info.makeASTType(underlying) + } + return &ast.Ident{ + NamePos: info.importCPos, + Name: "C.struct_" + name, + } + default: + panic("unknown elaborated type") + } case C.CXType_Record: cursor := C.tinygo_clang_getTypeDeclaration(typ) fieldList := &ast.FieldList{ diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index 3f1da8b6..b722ce10 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -53,6 +53,14 @@ func main() { C.unionSetData(5, 8, 1) println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2]) println("union field:", printUnion(C.globalUnion).f) + + // recursive types, test using a linked list + lastElement := &C.list_t{n: 7, next: nil} + list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: (*C.struct_list_t)(lastElement)}} + for list != nil { + println("n in chain:", list.n) + list = (*C.list_t)(list.next) + } } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 6330b223..4cdf10ea 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -12,6 +12,12 @@ typedef struct collection { unsigned char c; } collection_t; +// linked list +typedef struct list_t { + int n; + struct list_t *next; +} list_t; + typedef union joined { myint s; float f; diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 6fd5ee86..a5766e6d 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -24,3 +24,6 @@ union local data: 5 8 1 union s method: -33 false union f: +6.280000e+000 union field: +6.280000e+000 +n in chain: 3 +n in chain: 6 +n in chain: 7