diff --git a/cgo/cgo.go b/cgo/cgo.go index 0ef32127..d31fe57a 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -35,6 +35,7 @@ type cgoPackage struct { globals map[string]globalInfo typedefs map[string]*typedefInfo elaboratedTypes map[string]*elaboratedTypeInfo + enums map[string]enumInfo } // constantInfo stores some information about a CGo constant found by libclang @@ -71,6 +72,12 @@ type elaboratedTypeInfo struct { pos token.Pos } +// enumInfo contains information about an enum in the C. +type enumInfo struct { + typeExpr ast.Expr + pos token.Pos +} + // globalInfo contains information about a declared global variable in C. type globalInfo struct { typeExpr ast.Expr @@ -140,6 +147,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string globals: map[string]globalInfo{}, typedefs: map[string]*typedefInfo{}, elaboratedTypes: map[string]*elaboratedTypeInfo{}, + enums: map[string]enumInfo{}, } // Add a new location for the following file. @@ -243,6 +251,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Add elaborated types for C structs and unions. p.addElaboratedTypes() + // Add enum types and enum constants for C enums. + p.addEnumTypes() + // Patch the AST to use the declared types and functions. for _, f := range files { astutil.Apply(f, p.walker, nil) @@ -574,6 +585,54 @@ func (p *cgoPackage) addElaboratedTypes() { p.generated.Decls = append(p.generated.Decls, gen) } +// addEnumTypes adds C enums to the AST. For example, the following C code: +// +// enum option { +// optionA, +// optionB = 5, +// }; +// +// is translated to the following Go code equivalent: +// +// type C.enum_option int32 +// +// The constants are treated just like macros so are inserted into the AST by +// addConstDecls. +// See also: https://en.cppreference.com/w/c/language/enum +func (p *cgoPackage) addEnumTypes() { + if len(p.enums) == 0 { + return + } + gen := &ast.GenDecl{ + TokPos: token.NoPos, + Tok: token.TYPE, + } + names := make([]string, 0, len(p.enums)) + for name := range p.enums { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + typ := p.enums[name] + typeName := "C.enum_" + name + obj := &ast.Object{ + Kind: ast.Typ, + Name: typeName, + } + typeSpec := &ast.TypeSpec{ + Name: &ast.Ident{ + NamePos: typ.pos, + Name: typeName, + Obj: obj, + }, + Type: typ.typeExpr, + } + obj.Decl = typeSpec + gen.Specs = append(gen.Specs, typeSpec) + } + p.generated.Decls = append(p.generated.Decls, gen) +} + // findMissingCGoNames traverses the AST and finds all C.something names. Only // these symbols are extracted from the parsed C AST and converted to the Go // equivalent. diff --git a/cgo/libclang.go b/cgo/libclang.go index 24dac8c3..cca4487e 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -49,9 +49,12 @@ GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); +long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); +CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); */ import "C" @@ -501,6 +504,8 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { switch underlying.kind { case C.CXType_Record: return p.makeASTType(underlying, pos) + case C.CXType_Enum: + return p.makeASTType(underlying, pos) default: panic("unknown elaborated type") } @@ -580,6 +585,32 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { NamePos: pos, Name: "C." + cgoName, } + case C.CXType_Enum: + cursor := C.tinygo_clang_getTypeDeclaration(typ) + name := getString(C.tinygo_clang_getCursorSpelling(cursor)) + underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor) + if name == "" { + // anonymous enum + ref := storedRefs.Put(p) + defer storedRefs.Remove(ref) + C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref)) + return p.makeASTType(underlying, pos) + } else { + // named enum + if _, ok := p.enums[name]; !ok { + ref := storedRefs.Put(p) + defer storedRefs.Remove(ref) + C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref)) + p.enums[name] = enumInfo{ + typeExpr: p.makeASTType(underlying, pos), + pos: pos, + } + } + return &ast.Ident{ + NamePos: pos, + Name: "C.enum_" + name, + } + } } if typeName == "" { // Fallback, probably incorrect but at least the error points to an odd @@ -622,3 +653,16 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD fieldList.List = append(fieldList.List, field) return C.CXChildVisit_Continue } + +//export tinygo_clang_enum_visitor +func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { + p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage) + name := getString(C.tinygo_clang_getCursorSpelling(c)) + pos := p.getCursorPosition(c) + value := C.tinygo_clang_getEnumConstantDeclValue(c) + p.constants[name] = constantInfo{ + expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)}, + pos: pos, + } + return C.CXChildVisit_Continue +} diff --git a/cgo/libclang_stubs.c b/cgo/libclang_stubs.c index c282540a..0ccfd4ab 100644 --- a/cgo/libclang_stubs.c +++ b/cgo/libclang_stubs.c @@ -56,3 +56,11 @@ CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) { CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) { return clang_Cursor_getTranslationUnit(c); } + +long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) { + return clang_getEnumConstantDeclValue(c); +} + +CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) { + return clang_getEnumDeclIntegerType(c); +} diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index d62c1dea..9d1e3c6a 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -17,6 +17,7 @@ int globalStructSize = sizeof(globalStruct); short globalArray[3] = {5, 6, 7}; joined_t globalUnion; int globalUnionSize = sizeof(globalUnion); +option_t globalOption = optionG; int fortytwo() { return 42; diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index e1e75700..7837a779 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -75,6 +75,24 @@ func main() { println("n in chain:", list.n) list = (*C.list_t)(list.next) } + + // named enum + var _ C.enum_option = C.optionA + var _ C.option_t = C.optionA + println("option:", C.globalOption) + println("option A:", C.optionA) + println("option B:", C.optionB) + println("option C:", C.optionC) + println("option D:", C.optionD) + println("option E:", C.optionE) + println("option F:", C.optionF) + println("option G:", C.optionG) + + // anonymous enum + var _ C.option2_t = C.option2A + var _ C.option3_t = C.option3A + println("option 2A:", C.option2A) + println("option 3A:", C.option3A) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 9b325efd..3e97f1ad 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -47,6 +47,24 @@ void unionSetShort(short s); void unionSetFloat(float f); void unionSetData(short f0, short f1, short f2); +typedef enum option { + optionA, + optionB, + optionC = -5, + optionD, + optionE = 10, + optionF, + optionG, +} option_t; + +typedef enum { + option2A = 20, +} option2_t; + +typedef enum { + option3A = 21, +} option3_t; + // test globals and datatypes extern int global; extern int unusedGlobal; @@ -66,6 +84,7 @@ extern int globalStructSize; extern short globalArray[3]; extern joined_t globalUnion; extern int globalUnionSize; +extern option_t globalOption; // test duplicate definitions int add(int a, int b); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 70c093b1..e06d79ac 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -35,3 +35,13 @@ struct: 3 5 n in chain: 3 n in chain: 6 n in chain: 7 +option: 12 +option A: 0 +option B: 1 +option C: -5 +option D: -4 +option E: 10 +option F: 11 +option G: 12 +option 2A: 20 +option 3A: 21