diff --git a/codebuild.go b/codebuild.go index f0fe0919..50ebd83f 100644 --- a/codebuild.go +++ b/codebuild.go @@ -1145,11 +1145,11 @@ func (p *CodeBuilder) staticMember(typ types.Type, name string, flag MemberFlag, return MemberInvalid, nil } p.ensureLoaded(named) - method, obj := lookupStaticMember(named, name) + _, obj := lookupStaticMember(named, name) if obj == nil { return MemberInvalid, nil } - if !p.allowAccess(method.Pkg(), method.Name()) { + if !p.allowAccess(obj.Pkg(), obj.Name()) { return MemberInvalid, nil } if _, ok := obj.(*types.Func); ok { diff --git a/error_msg_test.go b/error_msg_test.go index c0d27cab..e8189f06 100644 --- a/error_msg_test.go +++ b/error_msg_test.go @@ -76,6 +76,21 @@ func codeErrorTestDo(t *testing.T, pkg *gogen.Package, msg string, source func(p gogen.WriteTo(&b, pkg, "") } +func panicErrorTest(t *testing.T, name, msg string, source func()) { + t.Run(name, func(t *testing.T) { + defer func() { + if e := recover(); e != nil { + if ret := fmt.Sprint(e); ret != msg { + t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) + } + } else { + t.Fatal("no error?") + } + }() + source() + }) +} + func newFunc( pkg *gogen.Package, line, column int, rline, rcolumn int, recv *types.Var, name string, params, results *types.Tuple, variadic bool) *gogen.Func { @@ -1778,23 +1793,11 @@ func TestErrStaticMember(t *testing.T) { EndStmt(). End() }) - const src = `package foo - -const XGoPackage = true - -type T int - -const XGos_T_name = "xgo" -` - gt := newGoxTest() - _, err := gt.LoadGoPackage("foo", "foo.go", src) - if err != nil { - t.Fatal(err) - } - pkg := gt.NewPackage("", "main") - foo := pkg.Import("foo") - typ := foo.Ref("T").Type() - codeErrorTestEx(t, pkg, + fooPkg := types.NewPackage("foo", "foo") + typ := types.NewNamed(types.NewTypeName(token.NoPos, fooPkg, "T", nil), types.Typ[types.Int], nil) + gogen.NewStaticMember(typ, token.NoPos, fooPkg, "name", + types.NewVar(token.NoPos, fooPkg, "xgos_T_name", types.Typ[types.String])) + codeErrorTest(t, `./foo.gop:1:5: T.name undefined (type foo.T has no method name)`, func(pkg *gogen.Package) { pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). @@ -1804,6 +1807,69 @@ const XGos_T_name = "xgo" }) } +func TestErrStaticMemberConflict(t *testing.T) { + newType := func() (*gogen.Package, *types.Named) { + pkg := newMainPackage() + return pkg, pkg.NewType("T").InitType(pkg, types.Typ[types.Int]) + } + newMethod := func(pkg *gogen.Package, name string) *types.Func { + return types.NewFunc(token.NoPos, pkg.Types, name, + types.NewSignatureType(nil, nil, nil, nil, nil, false)) + } + newValue := func(pkg *gogen.Package, name string) *types.Var { + return types.NewVar(token.NoPos, pkg.Types, name, types.Typ[types.String]) + } + + pkg, typ := newType() + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newMethod(pkg, "XGos_T_name")) + panicErrorTest(t, "method then value", + fmt.Sprintf("NewStaticMember: %v.%s conflicts with existing static method\n", typ, "name"), + func() { + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name")) + }) + + pkg, typ = newType() + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name")) + panicErrorTest(t, "value then method", + fmt.Sprintf("NewStaticMember: %v.%s conflicts with existing static value member\n", typ, "name"), + func() { + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newMethod(pkg, "XGos_T_name")) + }) + + pkg, typ = newType() + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name")) + panicErrorTest(t, "duplicate value", + fmt.Sprintf("NewStaticMember: %v.%s redeclared as static value member\n", typ, "name"), + func() { + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name2")) + }) + + pkg, typ = newType() + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newMethod(pkg, "XGos_T_name")) + panicErrorTest(t, "duplicate method", + fmt.Sprintf("NewStaticMember: %v.%s redeclared as static method\n", typ, "name"), + func() { + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newMethod(pkg, "XGos_T_name2")) + }) + + pkg, typ = newType() + recv := newParam(pkg, token.NoPos, "t", typ) + pkg.NewFunc(recv, "name", nil, nil, false).BodyStart(pkg).End() + panicErrorTest(t, "method then static member", + fmt.Sprintf("NewStaticMember: %v.%s conflicts with existing method\n", typ, "name"), + func() { + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name")) + }) + codeErrorTest(t, + "./foo.gop:1:5: method name conflicts with existing static value member", + func(pkg *gogen.Package) { + typ := pkg.NewType("T").InitType(pkg, types.Typ[types.Int]) + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", newValue(pkg, "XGos_T_name")) + recv := newParam(pkg, token.NoPos, "t", typ) + newFunc(pkg, 1, 5, 1, 10, recv, "name", nil, nil, false).BodyStart(pkg).End() + }) +} + func TestErrUnsafe(t *testing.T) { codeErrorTest(t, `./foo.gop:6:15: missing argument to function call: unsafe.Sizeof()`, diff --git a/func.go b/func.go index 8369358b..16c2db98 100644 --- a/func.go +++ b/func.go @@ -218,6 +218,9 @@ func (p *Package) NewFuncWith( return nil, cb.newCodeErrorf(posErr, posErr, "invalid receiver type %v (%v is a pointer type)", typ, typ) } if name != "_" { // skip underscore + if _, obj := lookupStaticMember(t, name); obj != nil { + return nil, cb.newCodeErrorf(pos, pos, "method %s conflicts with existing %s", name, staticMemberKind(obj)) + } t.AddMethod(fn.Func) } } else if name == "init" { // init is not a normal func diff --git a/func_ext.go b/func_ext.go index 3b57b069..48ac39ef 100644 --- a/func_ext.go +++ b/func_ext.go @@ -252,10 +252,29 @@ func isStaticValueMember(method *types.Func) bool { return false } +func staticMemberKind(obj types.Object) string { + if _, ok := obj.(*types.Func); ok { + return "static method" + } + return "static value member" +} + // NewStaticMember associates a package-level object with a named type. -// Function objects resolve as static methods; other objects resolve as static fields. +// Function objects resolve as static methods; other objects resolve as static value members. +// It panics if the registration conflicts with an existing static member or method. func NewStaticMember(typ *types.Named, pos token.Pos, pkg *types.Package, name string, obj types.Object) *types.Func { - return newMethodEx(typ.Origin(), pos, pkg, name, &TyStaticMember{obj}) + typ = typ.Origin() + if method := lookupMethod(typ, name); method != nil { + if old, ok := staticMemberObj(method); ok { + oldKind := staticMemberKind(old) + if newKind := staticMemberKind(obj); newKind == oldKind { + log.Panicf("NewStaticMember: %v.%s redeclared as %s\n", typ, name, newKind) + } + log.Panicf("NewStaticMember: %v.%s conflicts with existing %s\n", typ, name, oldKind) + } + log.Panicf("NewStaticMember: %v.%s conflicts with existing method\n", typ, name) + } + return newMethodEx(typ, pos, pkg, name, &TyStaticMember{obj}) } func lookupStaticMember(typ *types.Named, name string) (*types.Func, types.Object) { diff --git a/internal/bar/bar.go b/internal/bar/bar.go index f4149417..c13fab20 100644 --- a/internal/bar/bar.go +++ b/internal/bar/bar.go @@ -39,6 +39,10 @@ func XGos_Game_New() *Game { return nil } +const XGos_Game_name = "game" + +var XGos_Game_count int + type Info struct { id int } diff --git a/xgo_test.go b/xgo_test.go index 5716677b..156df4ec 100644 --- a/xgo_test.go +++ b/xgo_test.go @@ -1158,6 +1158,25 @@ func main() { `) } +func TestImportedStaticMember(t *testing.T) { + pkg := newMainPackage() + bar := pkg.Import("github.com/goplus/gogen/internal/bar") + game := bar.Ref("Game").Type() + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + DefineVarStart(token.NoPos, "name").Typ(game).MemberVal("name", 0).EndInit(1). + Typ(game).MemberRef("count").IncDec(token.INC).EndStmt(). + End() + domTest(t, pkg, `package main + +import "github.com/goplus/gogen/internal/bar" + +func main() { + name := bar.XGos_Game_name + bar.XGos_Game_count++ +} +`) +} + func TestStaticMember(t *testing.T) { pkg := newMainPackage() scope := pkg.Types.Scope()