diff --git a/_test/issue-1460.go b/_test/issue-1460.go new file mode 100644 index 00000000..ae504045 --- /dev/null +++ b/_test/issue-1460.go @@ -0,0 +1,79 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "reflect" +) + +func unmarshalJSON[T any](b []byte, x *[]T) error { + if *x != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + return json.Unmarshal(b, x) +} + +type StructView[T any] interface { + Valid() bool + AsStruct() T +} + +type SliceView[T ViewCloner[T, V], V StructView[T]] struct { + ж []T +} + +type ViewCloner[T any, V StructView[T]] interface { + View() V + Clone() T +} + +func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] { + return SliceView[T, V]{x} +} + +func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) } + +type Slice[T any] struct { + ж []T +} + +func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *Slice[T]) UnmarshalJSON(b []byte) error { return unmarshalJSON(b, &v.ж) } + +func SliceOf[T any](x []T) Slice[T] { + return Slice[T]{x} +} + +type viewStruct struct { + Int int + Strings Slice[string] + StringsPtr *Slice[string] `json:",omitempty"` +} + +func main() { + ss := SliceOf([]string{"bar"}) + in := viewStruct{ + Int: 1234, + Strings: ss, + StringsPtr: &ss, + } + + var buf bytes.Buffer + encoder := json.NewEncoder(&buf) + encoder.SetIndent("", "") + err1 := encoder.Encode(&in) + b := buf.Bytes() + var got viewStruct + err2 := json.Unmarshal(b, &got) + println(err1 == nil, err2 == nil, reflect.DeepEqual(got, in)) +} + +// Output: +// true true true diff --git a/interp/gta.go b/interp/gta.go index d2cc6176..c86767d1 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -161,8 +161,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ switch rtn.kind { case starExpr: typPtr = true - if rtn.child[0].kind == indexExpr { - typName = rtn.child[0].child[0].ident + switch c := rtn.child[0]; c.kind { + case indexExpr, indexListExpr: + typName = c.child[0].ident genericMethod = true } case indexExpr: diff --git a/interp/type.go b/interp/type.go index 71827430..cf0dee17 100644 --- a/interp/type.go +++ b/interp/type.go @@ -821,46 +821,50 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, break } // A generic type is being instantiated. Generate it. - g, err := genAST(sc, lt.node.anc, []*node{t1.node}) + t, err = genType(interp, sc, name, lt, []*node{t1.node}, seen) if err != nil { return nil, err } - t, err = nodeType2(interp, sc, g.lastChild(), seen) - if err != nil { - return nil, err - } - sc.sym[name] = &symbol{index: -1, kind: typeSym, typ: t, node: g} - - // Instantiate type methods (if any). - var pt *itype - if len(lt.method) > 0 { - pt = ptrOf(t, withNode(g), withScope(sc)) - } - for _, nod := range lt.method { - gm, err := genAST(sc, nod, []*node{t1.node}) - if err != nil { - return nil, err - } - if gm.typ, err = nodeType(interp, sc, gm.child[2]); err != nil { - return nil, err - } - t.addMethod(gm) - if rtn := gm.child[0].child[0].lastChild(); rtn.kind == starExpr { - // The receiver is a pointer on a generic type. - pt.addMethod(gm) - rtn.typ = pt - } - // Compile method CFG. - if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { - return nil, err - } - // Generate closures for function body. - if err = genRun(gm); err != nil { - return nil, err - } - } } + case indexListExpr: + // Similar to above indexExpr for generic types, but handle multiple type parameters. + var lt *itype + if lt, err = nodeType2(interp, sc, n.child[0], seen); err != nil { + return nil, err + } + // Index list expressions can be used only in context of generic types. + if lt.cat != genericT { + err = n.cfgErrorf("not a generic type: %s", lt.id()) + return nil, err + } + name := lt.id() + "[" + out := false + tnodes := []*node{} + for _, c := range n.child[1:] { + t1, err := nodeType2(interp, sc, c, seen) + if err != nil { + return nil, err + } + if t1.cat == genericT || t1.incomplete { + t = lt + out = true + break + } + tnodes = append(tnodes, t1.node) + name += t1.id() + "," + } + if out { + break + } + name += "]" + if sym, _, found := sc.lookup(name); found { + t = sym.typ + break + } + // A generic type is being instantiated. Generate it. + t, err = genType(interp, sc, name, lt, tnodes, seen) + case interfaceType: if sname := typeName(n); sname != "" { if sym, _, found := sc.lookup(sname); found && sym.kind == typeSym { @@ -1083,6 +1087,49 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, return t, err } +func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, seen []*node) (t *itype, err error) { + // A generic type is being instantiated. Generate it. + g, err := genAST(sc, lt.node.anc, tnodes) + if err != nil { + return nil, err + } + t, err = nodeType2(interp, sc, g.lastChild(), seen) + if err != nil { + return nil, err + } + sc.sym[name] = &symbol{index: -1, kind: typeSym, typ: t, node: g} + + // Instantiate type methods (if any). + var pt *itype + if len(lt.method) > 0 { + pt = ptrOf(t, withNode(g), withScope(sc)) + } + for _, nod := range lt.method { + gm, err := genAST(sc, nod, tnodes) + if err != nil { + return nil, err + } + if gm.typ, err = nodeType(interp, sc, gm.child[2]); err != nil { + return nil, err + } + t.addMethod(gm) + if rtn := gm.child[0].child[0].lastChild(); rtn.kind == starExpr { + // The receiver is a pointer on a generic type. + pt.addMethod(gm) + rtn.typ = pt + } + // Compile method CFG. + if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { + return nil, err + } + // Generate closures for function body. + if err = genRun(gm); err != nil { + return nil, err + } + } + return t, err +} + // findPackageType searches the top level scope for a package type. func findPackageType(interp *Interpreter, sc *scope, n *node) *itype { // Find the root scope, the package symbols will exist there.