Skip to content

Commit

Permalink
interp: fix handling generic types with multiple type parameters
Browse files Browse the repository at this point in the history
Those declarations involve the indexListExpr AST token, which was not handled in type.go. The same processing as for a single type parameter is applied.

Fixes #1460.
  • Loading branch information
mvertes authored Sep 22, 2022
1 parent 0218249 commit dfeddbe
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 33 deletions.
79 changes: 79 additions & 0 deletions _test/issue-1460.go
Original file line number Diff line number Diff line change
@@ -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
5 changes: 3 additions & 2 deletions interp/gta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
109 changes: 78 additions & 31 deletions interp/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,45 +821,49 @@ 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)
}

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
}
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
}
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 != "" {
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit dfeddbe

Please sign in to comment.