diff --git a/chore/gogensig/convert/convert_test.go b/chore/gogensig/convert/convert_test.go index 7df1a5e13..e9bb984e9 100644 --- a/chore/gogensig/convert/convert_test.go +++ b/chore/gogensig/convert/convert_test.go @@ -31,10 +31,10 @@ import ( _ "unsafe" ) -type __u struct { - b c.Long +type X__u struct { + B c.Long } -type u __u +type U X__u `, nil) } @@ -50,7 +50,7 @@ package size_t import _ "unsafe" //go:linkname TestSize C.testSize -func TestSize(a size_t) +func TestSize(a Size_t) `, nil) } @@ -78,9 +78,9 @@ import ( ) /// Foo comment type Foo struct { - a c.Int - b float64 - c c.Int + A c.Int + B float64 + C c.Int } /// ExecuteFoo comment //go:linkname CustomExecuteFoo C.ExecuteFoo @@ -93,6 +93,10 @@ func TestEnum(t *testing.T) { Cplusplus: true, }, ` + enum{ + enum1, + enum2 + }; enum spectrum { red, @@ -142,6 +146,11 @@ import ( _ "unsafe" ) +const ( + Enum1 c.Int = 0 + Enum2 c.Int = 1 +) + type Spectrum c.Int const ( @@ -219,9 +228,9 @@ import ( ) type Foo struct { - a c.Int - b float64 - c c.Int + A c.Int + B float64 + C c.Int } //go:linkname CustomExecuteFoo C.ExecuteFoo func CustomExecuteFoo(a c.Int, b Foo) c.Int @@ -267,6 +276,31 @@ func Resetthread(L *State) c.Int `, nil) } +func TestAvoidKeyword(t *testing.T) { + cmptest.RunTest(t, "avoid", false, []config.SymbolEntry{ + {MangleName: "lua_sethook", CppName: "lua_sethook", GoName: "Sethook"}, + }, &cppgtypes.Config{}, ` + typedef struct lua_State lua_State; + typedef void (*lua_Hook)(lua_State *L, lua_Debug *ar); + void(lua_sethook)(lua_State *L, lua_Hook func, int mask, int count); + `, ` +package avoid + +import ( + "github.com/goplus/llgo/c" + _ "unsafe" +) + +type Lua_State struct { + Unused [8]uint8 +} +// llgo:type C +type Lua_Hook func(*Lua_State, *c.Int) +//go:linkname Sethook C.lua_sethook +func Sethook(L *Lua_State, func_ Lua_Hook, mask c.Int, count c.Int) + `, nil) +} + // todo(zzy): https://github.com/luoliwoshang/llgo/issues/78 error in linux // Test if it can properly skip types from packages that have already been confirmed to be mapped // The _int8_t, _int16_t, _int32_t, _int64_t below are types that have already been confirmed to be mapped (macos). diff --git a/chore/gogensig/convert/package.go b/chore/gogensig/convert/package.go index 836900560..7b0787761 100644 --- a/chore/gogensig/convert/package.go +++ b/chore/gogensig/convert/package.go @@ -157,38 +157,41 @@ func (p *Package) NewTypeDecl(typeDecl *ast.TypeDecl) error { } return nil } - name := p.cvt.RemovePrefixedName(typeDecl.Name.Name) - - // for a type name, it should be unique - if obj := p.p.Types.Scope().Lookup(name); obj != nil { - return fmt.Errorf("type %s already defined", name) + // every type name should be public + name, changed, err := p.DeclName(typeDecl.Name.Name, true) + if err != nil { + return err } if debug { - log.Printf("NewTypeDecl: %v\n", typeDecl.Name) + log.Printf("NewTypeDecl: %v\n", name) } structType, err := p.cvt.RecordTypeToStruct(typeDecl.Type) if err != nil { return err } + typeBlock := p.p.NewTypeDefs() typeBlock.SetComments(CommentGroup(typeDecl.Doc).CommentGroup) decl := typeBlock.NewType(name) decl.InitType(p.p, structType) + + if changed { + substObj(p.p.Types, p.p.Types.Scope(), typeDecl.Name.Name, decl.Type().Obj()) + } return nil } func (p *Package) NewTypedefDecl(typedefDecl *ast.TypedefDecl) error { - name := p.cvt.RemovePrefixedName(typedefDecl.Name.Name) - - // for a typedef ,always appear same name like - // typedef struct foo { int a; } foo; - // For this typedef, we only need skip this - if obj := p.p.Types.Scope().Lookup(name); obj != nil { + name, changed, err := p.DeclName(typedefDecl.Name.Name, true) + if err != nil { + // for a typedef ,always appear same name like + // typedef struct foo { int a; } foo; + // For this typedef, we only need skip this return nil } if debug { - log.Printf("NewTypedefDecl: %s\n", typedefDecl.Name.Name) + log.Printf("NewTypedefDecl: %s\n", name) } genDecl := p.p.NewTypeDefs() typ, err := p.ToType(typedefDecl.Type) @@ -200,6 +203,9 @@ func (p *Package) NewTypedefDecl(typedefDecl *ast.TypedefDecl) error { if _, ok := typ.(*types.Signature); ok { genDecl.SetComments(NewTypecDocComments()) } + if changed { + substObj(p.p.Types, p.p.Types.Scope(), typedefDecl.Name.Name, typeSpecdecl.Type().Obj()) + } return nil } @@ -208,11 +214,12 @@ func (p *Package) ToType(expr ast.Expr) (types.Type, error) { return p.cvt.ToType(expr) } -func (p *Package) NewTypedefs(name string, typ types.Type) *types.Named { +func (p *Package) NewTypedefs(name string, typ types.Type) *gogen.TypeDecl { def := p.p.NewTypeDefs() - named := def.NewType(name).InitType(def.Pkg(), typ) + t := def.NewType(name) + t.InitType(def.Pkg(), typ) def.Complete() - return named + return t } func (p *Package) NewEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) error { @@ -234,15 +241,22 @@ func (p *Package) NewEnumTypeDecl(enumTypeDecl *ast.EnumTypeDecl) error { func (p *Package) createEnumType(enumName *ast.Ident) (types.Type, string, error) { var name string + var changed bool + var err error + var t *gogen.TypeDecl if enumName != nil { - name = ToTitle(p.cvt.RemovePrefixedName(enumName.Name)) - } - if obj := p.p.Types.Scope().Lookup(name); obj != nil { - return nil, "", fmt.Errorf("enum type %s already defined", name) + name, changed, err = p.DeclName(enumName.Name, true) + if err != nil { + return nil, "", fmt.Errorf("enum type %s already defined", enumName.Name) + } } enumType := p.cvt.ToDefaultEnumType() if name != "" { - enumType = p.NewTypedefs(name, enumType) + t = p.NewTypedefs(name, enumType) + enumType = p.p.Types.Scope().Lookup(name).Type() + } + if changed { + substObj(p.p.Types, p.p.Types.Scope(), enumName.Name, t.Type().Obj()) } return enumType, name, nil } @@ -251,14 +265,15 @@ func (p *Package) createEnumItems(items []*ast.EnumItem, enumType types.Type, en constDefs := p.p.NewConstDefs(p.p.Types.Scope()) for _, item := range items { var constName string + // maybe get a new name,because the after executed name,have some situation will found same name if enumTypeName != "" { constName = enumTypeName + "_" + item.Name.Name } else { constName = item.Name.Name } - // maybe get a new name,because the after executed name,have lots situation will found same name - if obj := p.p.Types.Scope().Lookup(constName); obj != nil { - return fmt.Errorf("enum item %s already defined", constName) + name, changed, err := p.DeclName(constName, true) + if err != nil { + return fmt.Errorf("enum item %s already defined %w", name, err) } val, err := Expr(item.Value).ToInt() if err != nil { @@ -267,7 +282,12 @@ func (p *Package) createEnumItems(items []*ast.EnumItem, enumType types.Type, en constDefs.New(func(cb *gogen.CodeBuilder) int { cb.Val(val) return 1 - }, 0, token.NoPos, enumType, constName) + }, 0, token.NoPos, enumType, name) + if changed { + if obj := p.p.Types.Scope().Lookup(name); obj != nil { + substObj(p.p.Types, p.p.Types.Scope(), item.Name.Name, obj) + } + } } return nil } @@ -365,6 +385,20 @@ func (p *Package) initDepPkgs() { p.depIncs = allDepIncs } +// For a decl name, if it's a current package, remove the prefixed name +// For a decl name, it should be unique +// todo(zzy): not current converter package file,need not remove prefixed name +func (p *Package) DeclName(name string, curPkg bool) (pubName string, changed bool, err error) { + if curPkg { + pubName = p.cvt.RemovePrefixedName(name) + } + pubName = CPubName(pubName) + if obj := p.p.Types.Scope().Lookup(pubName); obj != nil { + return "", false, fmt.Errorf("type %s already defined,original name is %s", pubName, name) + } + return pubName, name != pubName, nil +} + // AllDepIncs returns all std include paths of dependent packages func (p *Package) AllDepIncs() []string { return p.depIncs diff --git a/chore/gogensig/convert/package_test.go b/chore/gogensig/convert/package_test.go index 0c4562f9f..ab7192a2e 100644 --- a/chore/gogensig/convert/package_test.go +++ b/chore/gogensig/convert/package_test.go @@ -80,8 +80,8 @@ import ( "github.com/goplus/llgo/c" _ "unsafe" ) -type u struct { - b c.Long +type U struct { + B c.Long }`, }, } @@ -861,9 +861,9 @@ _ "unsafe" ) type Foo struct { - a c.Int - b float64 - c bool + A c.Int + B float64 + C bool }`, }, // struct Foo { int* a; double* b; bool* c;void* d; } @@ -920,10 +920,10 @@ import ( ) type Foo struct { - a *c.Int - b *float64 - c *bool - d unsafe.Pointer + A *c.Int + B *float64 + C *bool + D unsafe.Pointer }`}, // struct Foo { char a[4]; int b[3][4]; } { @@ -972,8 +972,8 @@ _ "unsafe" ) type Foo struct { - a [4]int8 - b [3][4]c.Int + A [4]int8 + B [3][4]c.Int }`}, { name: "struct array field", @@ -1021,8 +1021,8 @@ _ "unsafe" ) type Foo struct { - a [4]int8 - b [3][4]c.Int + A [4]int8 + B [3][4]c.Int }`}, { name: "anonymous struct", @@ -1308,7 +1308,7 @@ package testpkg import _ "unsafe" -type name [5]int8`, +type Name [5]int8`, }, // typedef void* ctx; { @@ -1326,7 +1326,7 @@ package testpkg import "unsafe" -type ctx unsafe.Pointer`, +type Ctx unsafe.Pointer`, }, // typedef char* name; @@ -1344,7 +1344,7 @@ type ctx unsafe.Pointer`, expected: ` package testpkg import _ "unsafe" -type name *int8`, +type Name *int8`, }, { name: "typedef invalid pointer", @@ -1403,9 +1403,9 @@ const ( Name: nil, Type: &ast.EnumType{ Items: []*ast.EnumItem{ - {Name: &ast.Ident{Name: "Red"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "0"}}, - {Name: &ast.Ident{Name: "Green"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "1"}}, - {Name: &ast.Ident{Name: "Blue"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "2"}}, + {Name: &ast.Ident{Name: "red"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "0"}}, + {Name: &ast.Ident{Name: "green"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "1"}}, + {Name: &ast.Ident{Name: "blue"}, Value: &ast.BasicLit{Kind: ast.IntLit, Value: "2"}}, }, }, }, @@ -1505,9 +1505,9 @@ func TestIdentRefer(t *testing.T) { comparePackageOutput(t, pkg, ` package testpkg import _ "unsafe" - type int8_t int8 + type Int8_t int8 type Foo struct { - a int8_t + A Int8_t } `) }) diff --git a/chore/gogensig/convert/type.go b/chore/gogensig/convert/type.go index 8f629add7..e4dd0bbbb 100644 --- a/chore/gogensig/convert/type.go +++ b/chore/gogensig/convert/type.go @@ -7,6 +7,7 @@ import ( "fmt" "go/token" "go/types" + "log" "strings" "unsafe" @@ -44,7 +45,6 @@ func NewConv(conf *TypeConfig) *TypeConv { conf: conf, } typeConv.Types = conf.Types - return typeConv } @@ -164,7 +164,7 @@ func (p *TypeConv) fieldListToParams(params *ast.FieldList) (*types.Tuple, bool, if params == nil { return types.NewTuple(), false, nil } - vars, err := p.fieldListToVars(params) + vars, err := p.fieldListToVars(params, false) if err != nil { return nil, false, err } @@ -192,13 +192,13 @@ func (p *TypeConv) retToResult(ret ast.Expr) (*types.Tuple, error) { } // Convert ast.FieldList to []types.Var -func (p *TypeConv) fieldListToVars(params *ast.FieldList) ([]*types.Var, error) { +func (p *TypeConv) fieldListToVars(params *ast.FieldList, isRecord bool) ([]*types.Var, error) { var vars []*types.Var if params == nil || params.List == nil { return vars, nil } for _, field := range params.List { - fieldVar, err := p.fieldToVar(field) + fieldVar, err := p.fieldToVar(field, isRecord) if err != nil { return nil, err } @@ -216,7 +216,7 @@ func (p *TypeConv) defaultRecordField() []*types.Var { } } -func (p *TypeConv) fieldToVar(field *ast.Field) (*types.Var, error) { +func (p *TypeConv) fieldToVar(field *ast.Field, isRecord bool) (*types.Var, error) { if field == nil { return nil, fmt.Errorf("unexpected nil field") } @@ -225,13 +225,17 @@ func (p *TypeConv) fieldToVar(field *ast.Field) (*types.Var, error) { var name string if len(field.Names) > 0 { name = field.Names[0].Name - } else if _, ok := field.Type.(*ast.Variadic); ok { - name = "__llgo_va_list" } typ, err := p.ToType(field.Type) if err != nil { return nil, err } + + isVariadic := false + if _, ok := field.Type.(*ast.Variadic); ok { + isVariadic = true + } + name = checkFieldName(name, isRecord, isVariadic) return types.NewVar(token.NoPos, p.Types, name, typ), nil } @@ -240,7 +244,7 @@ func (p *TypeConv) RecordTypeToStruct(recordType *ast.RecordType) (types.Type, e if recordType.Fields != nil && len(recordType.Fields.List) == 0 { fields = p.defaultRecordField() } else { - flds, err := p.fieldListToVars(recordType.Fields) + flds, err := p.fieldListToVars(recordType.Fields, true) if err != nil { return nil, err } @@ -293,6 +297,45 @@ func (p *TypeConv) RemovePrefixedName(name string) string { return name } -func ToTitle(s string) string { - return strings.ToUpper(s[:1]) + s[1:] +// isVariadic determines if the field is a variadic parameter +// The field or param name should be public if it's a record field +// and they will not record to the public symbol table +func checkFieldName(name string, isRecord bool, isVariadic bool) string { + if isVariadic { + return "__llgo_va_list" + } + // every field name should be public,will not be a keyword + if isRecord { + return CPubName(name) + } + return avoidKeyword(name) +} + +// from gogen@1.15.2 +func CPubName(name string) string { + if r := name[0]; 'a' <= r && r <= 'z' { + r -= 'a' - 'A' + return string(r) + name[1:] + } else if r == '_' { + return "X" + name + } + return name +} + +func avoidKeyword(name string) string { + if token.IsKeyword(name) { + return name + "_" + } + return name +} + +func substObj(pkg *types.Package, scope *types.Scope, origName string, real types.Object) { + old := scope.Insert(gogen.NewSubst(token.NoPos, pkg, origName, real)) + if old != nil { + if t, ok := old.Type().(*gogen.SubstType); ok { + t.Real = real + } else { + log.Panicln(origName, "redefined") + } + } } diff --git a/chore/gogensig/convert/type_builtin_test.go b/chore/gogensig/convert/type_builtin_test.go index 0be765434..d756e9f9c 100644 --- a/chore/gogensig/convert/type_builtin_test.go +++ b/chore/gogensig/convert/type_builtin_test.go @@ -1,9 +1,14 @@ package convert import ( + "go/token" + "go/types" "testing" + "github.com/goplus/gogen" + "github.com/goplus/llgo/chore/gogensig/config" "github.com/goplus/llgo/chore/llcppg/ast" + cppgtypes "github.com/goplus/llgo/chore/llcppg/types" ) func TestIdentRef(t *testing.T) { @@ -21,3 +26,38 @@ func TestLookupSymbolError(t *testing.T) { t.Fatal("Expect Error") } } + +func TestSubstObj(t *testing.T) { + pkg := NewPackage(&PackageConfig{ + PkgPath: ".", + Name: "testpkg", + GenConf: &gogen.Config{}, + OutputDir: "", + SymbolTable: &config.SymbolTable{}, + CppgConf: &cppgtypes.Config{}, + }) + if pkg == nil { + t.Fatal("NewPackage failed") + } + + corg := types.NewNamed(types.NewTypeName(token.NoPos, nil, "origin", nil), types.Typ[types.Int], nil) + corg2 := types.NewNamed(types.NewTypeName(token.NoPos, nil, "origin2", nil), types.Typ[types.Int], nil) + substObj(pkg.p.Types, pkg.p.Types.Scope(), "GoPub", corg.Obj()) + name := gogen.Lookup(pkg.p.Types.Scope(), "GoPub") + if name == nil { + t.Fatal("Lookup failed") + } + if name.Type().String() != corg.String() { + t.Fatal("Type not equal") + } + + // reassign + substObj(pkg.p.Types, pkg.p.Types.Scope(), "GoPub", corg2.Obj()) + name2 := gogen.Lookup(pkg.p.Types.Scope(), "GoPub") + if name2 == nil { + t.Fatal("Lookup failed") + } + if name2.Type().String() != corg2.String() { + t.Fatal("Type not equal") + } +}