From dfa713040a7b3be012dc229996440005734aa9b4 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 16 May 2019 22:16:58 +0200 Subject: [PATCH] cgo: add support for enum types Enum types are implemented as named types (with possible accompanying typedefs as type aliases). The constants inside the enums are treated as Go constants like in the Go toolchain. --- cgo/cgo.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ cgo/libclang.go | 44 +++++++++++++++++++++++++++++++++ cgo/libclang_stubs.c | 8 ++++++ testdata/cgo/main.c | 1 + testdata/cgo/main.go | 18 ++++++++++++++ testdata/cgo/main.h | 19 ++++++++++++++ testdata/cgo/out.txt | 10 ++++++++ 7 files changed, 159 insertions(+) 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