diff --git a/builtin_test.go b/builtin_test.go index 0c2d51c..d7a3cd5 100644 --- a/builtin_test.go +++ b/builtin_test.go @@ -774,7 +774,7 @@ func TestTypeEx(t *testing.T) { subst, &TyOverloadFunc{}, &TyOverloadMethod{}, - &TyStaticMethod{}, + &TyStaticMember{}, &TyTemplateRecvMethod{}, &TyInstruction{}, &TyOverloadNamed{Obj: types.NewTypeName(0, pkg.Types, "bar", tyInt)}, diff --git a/codebuild.go b/codebuild.go index 1646112..f0fe091 100644 --- a/codebuild.go +++ b/codebuild.go @@ -1128,6 +1128,51 @@ func aliasNameOf(name string, flag MemberFlag) (string, MemberFlag) { return "", flag } +func staticMemberRecv(typ types.Type) *types.Named { + typ = types.Unalias(typ) + if ptr, ok := typ.(*types.Pointer); ok { + typ = types.Unalias(ptr.Elem()) + } + if named, ok := typ.(*types.Named); ok { + return named.Origin() + } + return nil +} + +func (p *CodeBuilder) staticMember(typ types.Type, name string, flag MemberFlag, src ast.Node) (MemberKind, error) { + named := staticMemberRecv(typ) + if named == nil { + return MemberInvalid, nil + } + p.ensureLoaded(named) + method, obj := lookupStaticMember(named, name) + if obj == nil { + return MemberInvalid, nil + } + if !p.allowAccess(method.Pkg(), method.Name()) { + return MemberInvalid, nil + } + if _, ok := obj.(*types.Func); ok { + return MemberInvalid, nil + } + if flag == MemberFlagRef { + if v, ok := obj.(*types.Var); ok { + p.stk.Ret(1, &internal.Elem{ + Val: toObjectExpr(p.pkg, v), Type: &refType{typ: v.Type()}, Src: src, + }) + } else { + code, pos, end := p.loadExpr(src) + return MemberInvalid, p.newCodeError(pos, end, fmt.Sprintf("%s is not a variable", code)) + } + } else { + p.stk.Ret(1, toObject(p.pkg, obj, src)) + } + if p.rec != nil { + p.rec.Member(src, obj) + } + return MemberField, nil +} + // Member access member by its name. // src should point to the full source node `x.sel` func (p *CodeBuilder) Member(name string, lhs int, flag MemberFlag, src ...ast.Node) (kind MemberKind, err error) { @@ -1141,11 +1186,16 @@ func (p *CodeBuilder) Member(name string, lhs int, flag MemberFlag, src ...ast.N case p.pkg.utBigInt, p.pkg.utBigRat, p.pkg.utBigFlt: at = DefaultConv(p.pkg, arg.Type, arg) } + var t, isType = at.(*TypeType) + if isType { + if kind, err = p.staticMember(t.Type(), name, flag, srcExpr); kind != MemberInvalid || err != nil { + return + } + } if flag == MemberFlagRef { kind = p.refMember(at, name, arg.Val, srcExpr, nil) } else { var aliasName string - var t, isType = at.(*TypeType) if isType { // (T).method or (*T).method at = t.Type() flag = memberFlagMethodToFunc @@ -1344,6 +1394,9 @@ func (p *CodeBuilder) method( var exact bool for i, n := 0, o.NumMethods(); i < n; i++ { method := o.Method(i) + if isStaticValueMember(method) { + continue + } v := method.Name() if !p.allowAccess(method.Pkg(), v) { continue diff --git a/codebuild_test.go b/codebuild_test.go index b61bf2b..565e4e5 100644 --- a/codebuild_test.go +++ b/codebuild_test.go @@ -16,6 +16,7 @@ package gogen import ( + "go/constant" "go/token" "go/types" "testing" @@ -96,3 +97,52 @@ func TestFindMember(t *testing.T) { t.Fatalf("expected MemberMethod (1), got %v", kind) } } + +func TestStaticMemberInternalHelpers(t *testing.T) { + pkg := NewPackage("", "foo", nil) + fn := types.NewFunc(token.NoPos, pkg.Types, "XGos_foo_name", types.NewSignatureType(nil, nil, nil, nil, nil, false)) + sm := &TyStaticMember{Member: fn} + if got := sm.Obj(); got != fn { + t.Fatalf("TyStaticMember.Obj() = %v, want %v", got, fn) + } + if got := sm.Underlying(); got != sm { + t.Fatalf("TyStaticMember.Underlying() = %v, want self", got) + } + if got := sm.String(); got != "TyStaticMember" { + t.Fatalf("TyStaticMember.String() = %q, want %q", got, "TyStaticMember") + } + method := newMethodEx(pkg.NewType("foo").InitType(pkg, types.Typ[types.Int]), token.NoPos, pkg.Types, "name", sm) + if got, ok := staticMemberObj(method); !ok || got != fn { + t.Fatalf("staticMemberObj(TyStaticMember) = %v, %v, want %v, true", got, ok, fn) + } + sm.funcEx() + + var trm TyTemplateRecvMethod + trm.funcEx() +} + +func TestStaticMemberLoadsDelayedNamedType(t *testing.T) { + var loaded bool + pkg := NewPackage("", "foo", &Config{ + LoadNamed: func(at *Package, typ *types.Named) { + loaded = true + typ.SetUnderlying(types.Typ[types.Int]) + obj := types.NewConst( + token.NoPos, at.Types, "XGos_T_name", types.Typ[types.String], constant.MakeString("xgo"), + ) + NewStaticMember(typ, token.NoPos, at.Types, "name", obj) + }, + }) + delayed := types.NewNamed(types.NewTypeName(token.NoPos, pkg.Types, "T", nil), nil, nil) + cb := pkg.CB().Typ(delayed) + kind, err := cb.Member("name", 0, MemberFlagVal) + if err != nil { + t.Fatal("Member:", err) + } + if kind != MemberField { + t.Fatalf("Member(name) = %v, want %v", kind, MemberField) + } + if !loaded { + t.Fatal("LoadNamed was not called") + } +} diff --git a/error_msg_test.go b/error_msg_test.go index cd09b36..c0d27ca 100644 --- a/error_msg_test.go +++ b/error_msg_test.go @@ -1742,6 +1742,68 @@ func TestErrMemberRef(t *testing.T) { }) } +func TestErrStaticMember(t *testing.T) { + codeErrorTest(t, + `./foo.gop:1:5: int.name undefined (type int has no method name)`, + func(pkg *gogen.Package) { + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + Typ(types.Typ[types.Int]).MemberVal("name", 0, source("int.name", 1, 5)). + EndStmt(). + End() + }) + codeErrorTest(t, + `./foo.gop:1:5: T.name is not a variable`, + func(pkg *gogen.Package) { + scope := pkg.Types.Scope() + typ := pkg.NewType("T").InitType(pkg, types.Typ[types.Int]) + pkg.NewConstStart(scope, token.NoPos, nil, "XGos_T_name"). + Val("xgo").EndInit(1) + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", scope.Lookup("XGos_T_name")) + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + Typ(typ).MemberRef("name", source("T.name", 1, 5)). + EndStmt(). + End() + }) + codeErrorTest(t, + `./foo.gop:1:7: v.name undefined (type T has no field or method name)`, + func(pkg *gogen.Package) { + scope := pkg.Types.Scope() + typ := pkg.NewType("T").InitType(pkg, types.Typ[types.Int]) + pkg.NewConstStart(scope, token.NoPos, nil, "XGos_T_name"). + Val("xgo").EndInit(1) + gogen.NewStaticMember(typ, token.NoPos, pkg.Types, "name", scope.Lookup("XGos_T_name")) + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + NewVar(typ, "v"). + VarVal("v").MemberVal("name", 0, source("v.name", 1, 7)). + 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, + `./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). + Typ(typ).MemberVal("name", 0, source("T.name", 1, 5)). + EndStmt(). + End() + }) +} + func TestErrUnsafe(t *testing.T) { codeErrorTest(t, `./foo.gop:6:15: missing argument to function call: unsafe.Sizeof()`, diff --git a/func_ext.go b/func_ext.go index a7f6dc1..3b57b06 100644 --- a/func_ext.go +++ b/func_ext.go @@ -224,17 +224,48 @@ func (p *TyTypeAsParams) funcEx() {} // ---------------------------------------------------------------------------- -type TyStaticMethod struct { - Func types.Object +type TyStaticMember struct { + Member types.Object } -func (p *TyStaticMethod) Obj() types.Object { return p.Func } -func (p *TyStaticMethod) Underlying() types.Type { return p } -func (p *TyStaticMethod) String() string { return "TyStaticMethod" } -func (p *TyStaticMethod) funcEx() {} +func (p *TyStaticMember) Obj() types.Object { return p.Member } +func (p *TyStaticMember) Underlying() types.Type { return p } +func (p *TyStaticMember) String() string { return "TyStaticMember" } +func (p *TyStaticMember) funcEx() {} + +func staticMemberObj(method *types.Func) (types.Object, bool) { + if sig, ok := method.Type().(*types.Signature); ok { + if ext, ok := CheckFuncEx(sig); ok { + if member, ok := ext.(*TyStaticMember); ok { + return member.Obj(), true + } + } + } + return nil, false +} -func NewStaticMethod(typ *types.Named, pos token.Pos, pkg *types.Package, name string, fn types.Object) *types.Func { - return newMethodEx(typ, pos, pkg, name, &TyStaticMethod{fn}) +func isStaticValueMember(method *types.Func) bool { + if obj, ok := staticMemberObj(method); ok { + _, isFunc := obj.(*types.Func) + return !isFunc + } + return false +} + +// NewStaticMember associates a package-level object with a named type. +// Function objects resolve as static methods; other objects resolve as static fields. +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}) +} + +func lookupStaticMember(typ *types.Named, name string) (*types.Func, types.Object) { + typ = typ.Origin() + if method := lookupMethod(typ, name); method != nil { + if obj, ok := staticMemberObj(method); ok { + return method, obj + } + } + return nil, nil } // ---------------------------------------------------------------------------- diff --git a/import.go b/import.go index d4407d6..0c734fc 100644 --- a/import.go +++ b/import.go @@ -262,9 +262,9 @@ func checkXGotsx(pkg *types.Package, scope *types.Scope, name string, o types.Ob NewTemplateRecvMethod(m.typ, token.NoPos, pkg, m.name, o) } else { if debugImport { - log.Println("==> NewStaticMethod", tname, m.name) + log.Println("==> NewStaticMember", tname, m.name) } - NewStaticMethod(m.typ, token.NoPos, pkg, m.name, o) + NewStaticMember(m.typ, token.NoPos, pkg, m.name, o) } } case xgoxCh: // XGox_xxx diff --git a/util_gengo.go b/util_gengo.go index c88b213..f45a794 100644 --- a/util_gengo.go +++ b/util_gengo.go @@ -606,8 +606,8 @@ func (p *CodeBuilder) methodSigOf(typ types.Type, flag MemberFlag, arg, ret *Ele sig := typ.(*types.Signature) if t, ok := CheckFuncEx(sig); ok { switch ext := t.(type) { - case *TyStaticMethod: - return p.funcExSigOf(ext.Func, ret) + case *TyStaticMember: + return p.funcExSigOf(ext.Member, ret) case *TyTemplateRecvMethod: return p.funcExSigOf(ext.Func, ret) } diff --git a/xgo_test.go b/xgo_test.go index ab77065..5716677 100644 --- a/xgo_test.go +++ b/xgo_test.go @@ -1158,6 +1158,59 @@ func main() { `) } +func TestStaticMember(t *testing.T) { + pkg := newMainPackage() + scope := pkg.Types.Scope() + foo := pkg.NewType("foo").InitType(pkg, types.Typ[types.Int]) + pkg.NewConstStart(scope, token.NoPos, nil, "XGos_foo_name"). + Val("xgo").EndInit(1) + pkg.NewVarStart(token.NoPos, types.Typ[types.Int], "XGos_foo_count"). + Val(100).EndInit(1) + gogen.NewStaticMember(foo, token.NoPos, pkg.Types, "name", scope.Lookup("XGos_foo_name")) + gogen.NewStaticMember(foo, token.NoPos, pkg.Types, "count", scope.Lookup("XGos_foo_count")) + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + DefineVarStart(token.NoPos, "a").Typ(foo).MemberVal("name", 0).EndInit(1). + Typ(foo).MemberRef("count").IncDec(token.INC).EndStmt(). + End() + domTest(t, pkg, `package main + +type foo int + +const XGos_foo_name = "xgo" + +var XGos_foo_count int = 100 + +func main() { + a := XGos_foo_name + XGos_foo_count++ +} +`) +} + +func TestStaticMemberInstantiatedGenericType(t *testing.T) { + pkg := newMainPackage() + scope := pkg.Types.Scope() + tparam := types.NewTypeParam(types.NewTypeName(token.NoPos, pkg.Types, "T", nil), types.Universe.Lookup("any").Type()) + box := pkg.NewType("Box").InitType(pkg, types.Typ[types.Int], tparam) + boxInt := pkg.Instantiate(box, []types.Type{types.Typ[types.Int]}) + pkg.NewConstStart(scope, token.NoPos, nil, "XGos_Box_name"). + Val("box").EndInit(1) + gogen.NewStaticMember(box, token.NoPos, pkg.Types, "name", scope.Lookup("XGos_Box_name")) + pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). + DefineVarStart(token.NoPos, "name").Typ(boxInt).MemberVal("name", 0).EndInit(1). + End() + domTest(t, pkg, `package main + +type Box[T any] int + +const XGos_Box_name = "box" + +func main() { + name := XGos_Box_name +} +`) +} + func TestTemplateRecvMethod(t *testing.T) { pkg := newMainPackage() bar := pkg.Import("github.com/goplus/gogen/internal/bar")