From 4b05873384dae93f39e38ac0c279387f4b76a177 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Wed, 16 Nov 2022 15:44:02 +0100 Subject: [PATCH 01/14] interp: improve handling of generic types When generating a new type, the parameter type was not correctly duplicated in the new AST. This is fixed by making copyNode recursive if needed. The out of order processing of generic types has also been fixed. Fixes #1488. --- _test/issue-1460.go | 13 +++++++++---- _test/issue-1488.go | 23 +++++++++++++++++++++++ interp/generic.go | 13 +++++++++---- interp/type.go | 15 ++++++++++++++- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 _test/issue-1488.go diff --git a/_test/issue-1460.go b/_test/issue-1460.go index ae5040454..44e14c8ff 100644 --- a/_test/issue-1460.go +++ b/_test/issue-1460.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "errors" + "net/netip" "reflect" ) @@ -17,6 +18,10 @@ func unmarshalJSON[T any](b []byte, x *[]T) error { return json.Unmarshal(b, x) } +func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] { + return SliceView[T, V]{x} +} + type StructView[T any] interface { Valid() bool AsStruct() T @@ -31,10 +36,6 @@ type ViewCloner[T any, V StructView[T]] interface { 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.ж) } @@ -51,6 +52,10 @@ func SliceOf[T any](x []T) Slice[T] { return Slice[T]{x} } +type IPPrefixSlice struct { + ж Slice[netip.Prefix] +} + type viewStruct struct { Int int Strings Slice[string] diff --git a/_test/issue-1488.go b/_test/issue-1488.go new file mode 100644 index 000000000..d26302a64 --- /dev/null +++ b/_test/issue-1488.go @@ -0,0 +1,23 @@ +package main + +import "fmt" + +type vector interface { + []int | [3]int +} + +func sum[V vector](v V) (out int) { + for i := 0; i < len(v); i++ { + out += v[i] + } + return +} + +func main() { + va := [3]int{1, 2, 3} + vs := []int{1, 2, 3} + fmt.Println(sum[[3]int](va), sum[[]int](vs)) +} + +// Output: +// 6 6 diff --git a/interp/generic.go b/interp/generic.go index ec9ff3e40..671cf7438 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -16,7 +16,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { var gtree func(*node, *node) (*node, error) gtree = func(n, anc *node) (*node, error) { - nod := copyNode(n, anc) + nod := copyNode(n, anc, false) switch n.kind { case funcDecl, funcType: nod.val = nod @@ -27,7 +27,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { if !ok { break } - nod = copyNode(nt, anc) + nod = copyNode(nt, anc, true) case indexExpr: // Catch a possible recursive generic type definition @@ -37,7 +37,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { if root.child[0].ident != n.child[0].ident { break } - nod := copyNode(n.child[0], anc) + nod := copyNode(n.child[0], anc, false) fixNodes = append(fixNodes, nod) return nod, nil @@ -149,7 +149,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { return r, nil } -func copyNode(n, anc *node) *node { +func copyNode(n, anc *node, recursive bool) *node { var i interface{} nindex := atomic.AddInt64(&n.interp.nindex, 1) nod := &node{ @@ -170,6 +170,11 @@ func copyNode(n, anc *node) *node { meta: n.meta, } nod.start = nod + if recursive { + for _, c := range n.child { + nod.child = append(nod.child, copyNode(c, nod, true)) + } + } return nod } diff --git a/interp/type.go b/interp/type.go index fefa57117..7121ea083 100644 --- a/interp/type.go +++ b/interp/type.go @@ -807,7 +807,11 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, return nil, err } if lt.incomplete { - t.incomplete = true + if t == nil { + t = lt + } else { + t.incomplete = true + } break } switch lt.cat { @@ -840,6 +844,15 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, if lt, err = nodeType2(interp, sc, n.child[0], seen); err != nil { return nil, err } + if lt.incomplete { + if t == nil { + t = lt + } else { + t.incomplete = true + } + break + } + // 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()) From ee89ec0d047a69a54e10320ae747b38e38b3e93d Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 5 Dec 2022 10:53:26 +0100 Subject: [PATCH 02/14] Handle the case where a generic type is instantiated before method declaration, which was leading to missing methods in the generated type. --- interp/generic.go | 1 + interp/gta.go | 9 +++++++++ interp/interp.go | 1 + interp/type.go | 50 ++++++++++++++++++++++++----------------------- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/interp/generic.go b/interp/generic.go index 671cf7438..6d21825b4 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -129,6 +129,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { if err != nil { return nil, err } + r.param = append(r.param, types...) if tname != "" { for _, nod := range fixNodes { nod.ident = tname diff --git a/interp/gta.go b/interp/gta.go index 39e5c79dc..1bbd60bd4 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -189,6 +189,15 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ } rcvrtype.addMethod(n) rtn.typ = rcvrtype + if rcvrtype.cat == genericT { + // generate methods for already instantiated receivers + for _, it := range rcvrtype.instance { + err = genMethod(interp, sc, it, n, it.node.anc.param) + if err != nil { + return false + } + } + } case ident == "init": // init functions do not get declared as per the Go spec. default: diff --git a/interp/interp.go b/interp/interp.go index 3803d6fe9..5ed7ea953 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -28,6 +28,7 @@ type node struct { debug *nodeDebugData // debug info child []*node // child subtrees (AST) anc *node // ancestor (AST) + param []*node // generic parameter nodes (AST) start *node // entry point in subtree (CFG) tnext *node // true branch successor (CFG) fnext *node // false branch successor (CFG) diff --git a/interp/type.go b/interp/type.go index 7121ea083..de1fc3ea8 100644 --- a/interp/type.go +++ b/interp/type.go @@ -126,6 +126,7 @@ type itype struct { method []*node // Associated methods or nil constraint []*itype // For interfaceT: list of types part of interface set ulconstraint []*itype // For interfaceT: list of underlying types part of interface set + instance []*itype // For genericT: list of instantiated types name string // name of type within its package for a defined type path string // for a defined type, the package import path length int // length of array if ArrayT @@ -1117,39 +1118,40 @@ func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, see if err != nil { return nil, err } + lt.instance = append(lt.instance, t) 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 { + if err := genMethod(interp, sc, t, nod, tnodes); err != nil { return nil, err } } return t, err } +func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*node) error { + gm, err := genAST(sc, nod, tnodes) + if err != nil { + return err + } + if gm.typ, err = nodeType(interp, sc, gm.child[2]); err != nil { + return 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 := ptrOf(t, withNode(t.node), withScope(sc)) + pt.addMethod(gm) + rtn.typ = pt + } + // Compile method CFG. + if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { + return err + } + // Generate closures for function body. + return genRun(gm) +} + // 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. From 17b18858b96a50e283fa3a539f6be7283740016b Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 5 Dec 2022 10:57:37 +0100 Subject: [PATCH 03/14] add test with method declared after generic instanciation --- _test/gen11.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 _test/gen11.go diff --git a/_test/gen11.go b/_test/gen11.go new file mode 100644 index 000000000..82100f0d0 --- /dev/null +++ b/_test/gen11.go @@ -0,0 +1,33 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/netip" +) + +type Slice[T any] struct { + x []T +} + +type IPPrefixSlice struct { + x Slice[netip.Prefix] +} + +func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) } + +// MarshalJSON implements json.Marshaler. +func (v IPPrefixSlice) MarshalJSON() ([]byte, error) { + return v.x.MarshalJSON() +} + +func main() { + t := IPPrefixSlice{} + fmt.Println(t) + b, e := t.MarshalJSON() + fmt.Println(string(b), e) +} + +// Output: +// {{[]}} +// null From c6f95279ec921d09626b7e9e34b904c1ee358b18 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 22 Dec 2022 19:12:11 +0100 Subject: [PATCH 04/14] additional fixes for more complex use cases --- _test/p6.go | 14 ++++++++++++++ _test/p6/p6.go | 21 +++++++++++++++++++++ interp/cfg.go | 15 ++++++++++++++- interp/dot.go | 4 ++++ interp/generic.go | 14 +++++++++++++- interp/gta.go | 2 +- interp/type.go | 15 ++++++++++----- 7 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 _test/p6.go create mode 100644 _test/p6/p6.go diff --git a/_test/p6.go b/_test/p6.go new file mode 100644 index 000000000..92436bc68 --- /dev/null +++ b/_test/p6.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + + "github.com/traefik/yaegi/_test/p6" +) + +func main() { + t := p6.IPPrefixSlice{} + fmt.Println(t) + b, e := t.MarshalJSON() + fmt.Println(string(b), e) +} diff --git a/_test/p6/p6.go b/_test/p6/p6.go new file mode 100644 index 000000000..52cb50b3f --- /dev/null +++ b/_test/p6/p6.go @@ -0,0 +1,21 @@ +package p6 + +import ( + "encoding/json" + "net/netip" +) + +type Slice[T any] struct { + x []T +} + +type IPPrefixSlice struct { + x Slice[netip.Prefix] +} + +func (v Slice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.x) } + +// MarshalJSON implements json.Marshaler. +func (v IPPrefixSlice) MarshalJSON() ([]byte, error) { + return v.x.MarshalJSON() +} diff --git a/interp/cfg.go b/interp/cfg.go index a805e9ba7..2ef6af84c 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -439,6 +439,19 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string if typ, err = nodeType(interp, sc, recvTypeNode); err != nil { return false } + if typ.cat == nilT { + // This may happen when instantiating generic methods. + s2, _, ok := sc.lookup(typ.id()) + if !ok { + err = n.cfgErrorf("type not found: %s", typ.id()) + break + } + typ = s2.typ + if typ.cat == nilT { + err = n.cfgErrorf("nil type: %s", typ.id()) + break + } + } recvTypeNode.typ = typ n.child[2].typ.recv = typ n.typ.recv = typ @@ -1916,7 +1929,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string err = n.cfgErrorf("undefined selector: %s", n.child[1].ident) } } - if err == nil && n.findex != -1 { + if err == nil && n.findex != -1 && n.typ.cat != genericT { n.findex = sc.add(n.typ) } diff --git a/interp/dot.go b/interp/dot.go index d24f0745f..e8eeb2910 100644 --- a/interp/dot.go +++ b/interp/dot.go @@ -35,6 +35,10 @@ func (n *node) astDot(out io.Writer, name string) { fmt.Fprintf(out, "}\n") } +func (n *node) adot() { + n.astDot(dotWriter(n.interp.dotCmd), n.ident) +} + // cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process. func (n *node) cfgDot(out io.Writer) { fmt.Fprintf(out, "digraph cfg {\n") diff --git a/interp/generic.go b/interp/generic.go index 6d21825b4..48f26f8b1 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -15,6 +15,17 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { fixNodes := []*node{} var gtree func(*node, *node) (*node, error) + // Input type parameters must be resolved prior AST generation, so they can be forwarded. + for _, nt := range types { + if nt == nil || nt.typ != nil { + continue + } + var err error + if nt.typ, err = nodeType(root.interp, sc, nt); err != nil { + return nil, err + } + } + gtree = func(n, anc *node) (*node, error) { nod := copyNode(n, anc, false) switch n.kind { @@ -28,6 +39,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { break } nod = copyNode(nt, anc, true) + nod.typ = nt.typ case indexExpr: // Catch a possible recursive generic type definition @@ -146,7 +158,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { nod.ident = rtname nod.child = nil } - // r.astDot(dotWriter(root.interp.dotCmd), root.child[1].ident) // Used for debugging only. + //r.adot() // Used for debugging only. return r, nil } diff --git a/interp/gta.go b/interp/gta.go index 1bbd60bd4..110bdc687 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -192,7 +192,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ if rcvrtype.cat == genericT { // generate methods for already instantiated receivers for _, it := range rcvrtype.instance { - err = genMethod(interp, sc, it, n, it.node.anc.param) + err = genMethod(interp, typName, sc, it, n, it.node.anc.param) if err != nil { return false } diff --git a/interp/type.go b/interp/type.go index de1fc3ea8..3ccb9ed1d 100644 --- a/interp/type.go +++ b/interp/type.go @@ -833,7 +833,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, break } // A generic type is being instantiated. Generate it. - t, err = genType(interp, sc, name, lt, []*node{t1.node}, seen) + t, err = genType(interp, sc, name, lt, []*node{n.child[1]}, seen) if err != nil { return nil, err } @@ -1119,17 +1119,21 @@ func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, see return nil, err } lt.instance = append(lt.instance, t) + // Add generated symbol in the scope of generic source and user. sc.sym[name] = &symbol{index: -1, kind: typeSym, typ: t, node: g} + if lt.scope.sym[name] == nil { + lt.scope.sym[name] = sc.sym[name] + } for _, nod := range lt.method { - if err := genMethod(interp, sc, t, nod, tnodes); err != nil { + if err := genMethod(interp, name, sc, t, nod, tnodes); err != nil { return nil, err } } return t, err } -func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*node) error { +func genMethod(interp *Interpreter, name string, sc *scope, t *itype, nod *node, tnodes []*node) error { gm, err := genAST(sc, nod, tnodes) if err != nil { return err @@ -1144,8 +1148,9 @@ func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*no pt.addMethod(gm) rtn.typ = pt } - // Compile method CFG. - if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { + // Compile the method AST in the scope of the generic type. + scop := nod.typ.scope + if _, err = interp.cfg(gm, scop, scop.pkgID, scop.pkgName); err != nil { return err } // Generate closures for function body. From d381f0f752290590398447a1504d44c1cfd0080e Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Thu, 22 Dec 2022 19:24:05 +0100 Subject: [PATCH 05/14] lint --- interp/dot.go | 4 ---- interp/generic.go | 5 +++-- interp/gta.go | 3 +-- interp/type.go | 4 ++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/interp/dot.go b/interp/dot.go index e8eeb2910..d24f0745f 100644 --- a/interp/dot.go +++ b/interp/dot.go @@ -35,10 +35,6 @@ func (n *node) astDot(out io.Writer, name string) { fmt.Fprintf(out, "}\n") } -func (n *node) adot() { - n.astDot(dotWriter(n.interp.dotCmd), n.ident) -} - // cfgDot displays a CFG in graphviz dot(1) format using dotty(1) co-process. func (n *node) cfgDot(out io.Writer) { fmt.Fprintf(out, "digraph cfg {\n") diff --git a/interp/generic.go b/interp/generic.go index 48f26f8b1..8a93ac25b 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -15,7 +15,8 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { fixNodes := []*node{} var gtree func(*node, *node) (*node, error) - // Input type parameters must be resolved prior AST generation, so they can be forwarded. + // Input type parameters must be resolved prior AST generation, as compilation + // of generated AST may occur in a different scope. for _, nt := range types { if nt == nil || nt.typ != nil { continue @@ -158,7 +159,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { nod.ident = rtname nod.child = nil } - //r.adot() // Used for debugging only. + // r.astDot(dotWriter(r.interp.dotCmd), r.ident) // Used for debugging only. return r, nil } diff --git a/interp/gta.go b/interp/gta.go index 110bdc687..77daa0585 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -192,8 +192,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ if rcvrtype.cat == genericT { // generate methods for already instantiated receivers for _, it := range rcvrtype.instance { - err = genMethod(interp, typName, sc, it, n, it.node.anc.param) - if err != nil { + if err = genMethod(interp, sc, it, n, it.node.anc.param); err != nil { return false } } diff --git a/interp/type.go b/interp/type.go index 3ccb9ed1d..931d235af 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1126,14 +1126,14 @@ func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, see } for _, nod := range lt.method { - if err := genMethod(interp, name, sc, t, nod, tnodes); err != nil { + if err := genMethod(interp, sc, t, nod, tnodes); err != nil { return nil, err } } return t, err } -func genMethod(interp *Interpreter, name string, sc *scope, t *itype, nod *node, tnodes []*node) error { +func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*node) error { gm, err := genAST(sc, nod, tnodes) if err != nil { return err From 22f0e88f6ec3eaa97aea5e4ab066ba44fcc9e1f2 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 10 Jan 2023 16:05:49 +0100 Subject: [PATCH 06/14] Several fixes to handle call of recursive type infered generics --- interp/cfg.go | 57 +++++++++++++++++++++++++++-------------------- interp/generic.go | 32 +++++++++++++++++++++----- interp/gta.go | 3 +++ interp/interp.go | 2 ++ interp/type.go | 11 ++++++--- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index 2ef6af84c..921d381c4 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -884,16 +884,18 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string n.typ = t return } - g, err := genAST(sc, t.node.anc, []*node{c1}) + g, found, err := genAST(sc, t.node.anc, []*node{c1}) if err != nil { return } - if _, err = interp.cfg(g, nil, importPath, pkgName); err != nil { - return - } - // Generate closures for function body. - if err = genRun(g.child[3]); err != nil { - return + if !found { + if _, err = interp.cfg(g, t.node.anc.scope, importPath, pkgName); err != nil { + return + } + // Generate closures for function body. + if err = genRun(g.child[3]); err != nil { + return + } } // Replace generic func node by instantiated one. n.anc.child[childPos(n)] = g @@ -1043,17 +1045,19 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string case c0.kind == indexListExpr: // Instantiate a generic function then call it. fun := c0.child[0].sym.node - g, err := genAST(sc, fun, c0.child[1:]) - if err != nil { - return - } - _, err = interp.cfg(g, nil, importPath, pkgName) + g, found, err := genAST(sc, fun, c0.child[1:]) if err != nil { return } - err = genRun(g.child[3]) // Generate closures for function body. - if err != nil { - return + if !found { + _, err = interp.cfg(g, fun.scope, importPath, pkgName) + if err != nil { + return + } + err = genRun(g.child[3]) // Generate closures for function body. + if err != nil { + return + } } n.child[0] = g c0 = n.child[0] @@ -1226,22 +1230,25 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string fun := c0.typ.node.anc var g *node var types []*node + var found bool // Infer type parameter from function call arguments. if types, err = inferTypesFromCall(sc, fun, n.child[1:]); err != nil { break } // Generate an instantiated AST from the generic function one. - if g, err = genAST(sc, fun, types); err != nil { - break - } - // Compile the generated function AST, so it becomes part of the scope. - if _, err = interp.cfg(g, nil, importPath, pkgName); err != nil { + if g, found, err = genAST(sc, fun, types); err != nil { break } - // AST compilation part 2: Generate closures for function body. - if err = genRun(g.child[3]); err != nil { - break + if !found { + // Compile the generated function AST, so it becomes part of the scope. + if _, err = interp.cfg(g, fun.scope, importPath, pkgName); err != nil { + break + } + // AST compilation part 2: Generate closures for function body. + if err = genRun(g.child[3]); err != nil { + break + } } n.child[0] = g c0 = n.child[0] @@ -2388,11 +2395,13 @@ func (n *node) cfgErrorf(format string, a ...interface{}) *cfgError { func genRun(nod *node) error { var err error + seen := map[*node]bool{} nod.Walk(func(n *node) bool { - if err != nil { + if err != nil || seen[n] { return false } + seen[n] = true switch n.kind { case funcType: if len(n.anc.child) == 4 { diff --git a/interp/generic.go b/interp/generic.go index 8a93ac25b..51e2f7bb9 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -6,7 +6,7 @@ import ( ) // genAST returns a new AST where generic types are replaced by instantiated types. -func genAST(sc *scope, root *node, types []*node) (*node, error) { +func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { typeParam := map[string]*node{} pindex := 0 tname := "" @@ -14,18 +14,24 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { recvrPtr := false fixNodes := []*node{} var gtree func(*node, *node) (*node, error) + sname := root.child[0].ident + "[" + if root.kind == funcDecl { + sname = root.child[1].ident + "[" + } // Input type parameters must be resolved prior AST generation, as compilation // of generated AST may occur in a different scope. for _, nt := range types { - if nt == nil || nt.typ != nil { + if nt == nil { continue } var err error if nt.typ, err = nodeType(root.interp, sc, nt); err != nil { - return nil, err + return nil, false, err } + sname += nt.typ.id() + "," } + sname = strings.TrimSuffix(sname, ",") + "]" gtree = func(n, anc *node) (*node, error) { nod := copyNode(n, anc, false) @@ -138,10 +144,15 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { return nod, nil } + if nod, found := root.interp.generic[sname]; found { + return nod, true, nil + } + r, err := gtree(root, root.anc) if err != nil { - return nil, err + return nil, false, err } + root.interp.generic[sname] = r r.param = append(r.param, types...) if tname != "" { for _, nod := range fixNodes { @@ -160,7 +171,7 @@ func genAST(sc *scope, root *node, types []*node) (*node, error) { nod.child = nil } // r.astDot(dotWriter(r.interp.dotCmd), r.ident) // Used for debugging only. - return r, nil + return r, false, nil } func copyNode(n, anc *node, recursive bool) *node { @@ -237,6 +248,9 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { case funcT: nods := []*node{} for i, t := range param.arg { + if i >= len(input.arg) { + break + } nl, err := inferTypes(t, input.arg[i]) if err != nil { return nil, err @@ -244,6 +258,9 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { nods = append(nods, nl...) } for i, t := range param.ret { + if i >= len(input.ret) { + break + } nl, err := inferTypes(t, input.ret[i]) if err != nil { return nil, err @@ -252,6 +269,11 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { } return nods, nil + case nilT: + if types[param.name] != nil { + return []*node{input.node}, nil + } + case genericT: return []*node{input.node}, nil } diff --git a/interp/gta.go b/interp/gta.go index 77daa0585..39f0606a2 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -21,6 +21,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ if err != nil { return false } + if n.scope == nil { + n.scope = sc + } switch n.kind { case constDecl: // Early parse of constDecl subtree, to compute all constant diff --git a/interp/interp.go b/interp/interp.go index 5ed7ea953..4b7471e2d 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -216,6 +216,7 @@ type Interpreter struct { pkgNames map[string]string // package names, indexed by import path done chan struct{} // for cancellation of channel operations roots []*node + generic map[string]*node hooks *hooks // symbol hooks @@ -336,6 +337,7 @@ func New(options Options) *Interpreter { pkgNames: map[string]string{}, rdir: map[string]bool{}, hooks: &hooks{}, + generic: map[string]*node{}, } if i.opt.stdin = options.Stdin; i.opt.stdin == nil { diff --git a/interp/type.go b/interp/type.go index 931d235af..3433e6d4a 100644 --- a/interp/type.go +++ b/interp/type.go @@ -787,6 +787,11 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, } else { t = sym.typ } + if t == nil { + if t, err = nodeType2(interp, sc, sym.node, seen); err != nil { + return nil, err + } + } if t.incomplete && t.cat == linkedT && t.val != nil && t.val.cat != nilT { t.incomplete = false } @@ -1030,7 +1035,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, sname := structName(n) if sname != "" { sym, _, found = sc.lookup(sname) - if found && sym.kind == typeSym { + if found && sym.kind == typeSym && sym.typ != nil { t = structOf(sym.typ, sym.typ.field, withNode(n), withScope(sc)) } else { t = structOf(nil, nil, withNode(n), withScope(sc)) @@ -1110,7 +1115,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, 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) + g, _, err := genAST(sc, lt.node.anc, tnodes) if err != nil { return nil, err } @@ -1134,7 +1139,7 @@ func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, see } func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*node) error { - gm, err := genAST(sc, nod, tnodes) + gm, _, err := genAST(sc, nod, tnodes) if err != nil { return err } From e5219b018baff9d8e084c4b84b7922c80a113179 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Wed, 18 Jan 2023 15:54:10 +0100 Subject: [PATCH 07/14] passing types instead of nodes when generating AST for generics. WIP. --- interp/cfg.go | 10 +++-- interp/generic.go | 104 +++++++++++++++++++++++++--------------------- interp/interp.go | 2 +- interp/type.go | 18 ++++---- 4 files changed, 73 insertions(+), 61 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index 921d381c4..3f0612d77 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -884,7 +884,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string n.typ = t return } - g, found, err := genAST(sc, t.node.anc, []*node{c1}) + g, found, err := genAST(sc, t.node.anc, []*itype{c1.typ}) if err != nil { return } @@ -1045,7 +1045,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string case c0.kind == indexListExpr: // Instantiate a generic function then call it. fun := c0.child[0].sym.node - g, found, err := genAST(sc, fun, c0.child[1:]) + lt := []*itype{} + for _, c := range c0.child[1:] { + lt = append(lt, c.typ) + } + g, found, err := genAST(sc, fun, lt) if err != nil { return } @@ -1229,7 +1233,7 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string if isGeneric(c0.typ) { fun := c0.typ.node.anc var g *node - var types []*node + var types []*itype var found bool // Infer type parameter from function call arguments. diff --git a/interp/generic.go b/interp/generic.go index 51e2f7bb9..30f94792e 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -5,8 +5,11 @@ import ( "sync/atomic" ) +// adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. +// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident); } + // genAST returns a new AST where generic types are replaced by instantiated types. -func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { +func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { typeParam := map[string]*node{} pindex := 0 tname := "" @@ -21,15 +24,8 @@ func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { // Input type parameters must be resolved prior AST generation, as compilation // of generated AST may occur in a different scope. - for _, nt := range types { - if nt == nil { - continue - } - var err error - if nt.typ, err = nodeType(root.interp, sc, nt); err != nil { - return nil, false, err - } - sname += nt.typ.id() + "," + for _, t := range types { + sname += t.id() + "," } sname = strings.TrimSuffix(sname, ",") + "]" @@ -70,10 +66,20 @@ func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { if pindex >= len(types) { return nil, cc.cfgErrorf("undefined type for %s", cc.ident) } - if err := checkConstraint(sc, types[pindex], c.child[l]); err != nil { + t, err := nodeType(c.interp, sc, c.child[l]) + if err != nil { return nil, err } - typeParam[cc.ident] = types[pindex] + if err := checkConstraint(sc, types[pindex], t); err != nil { + return nil, err + } + if types[pindex].node == nil { + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = types[pindex].id() + typeParam[cc.ident].typ = types[pindex] + } else { + typeParam[cc.ident] = types[pindex].node + } pindex++ } } @@ -96,11 +102,14 @@ func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { if pindex >= len(types) { return nil, cc.cfgErrorf("undefined type for %s", cc.ident) } - it, err := nodeType(n.interp, sc, types[pindex]) - if err != nil { - return nil, err + it := types[pindex] + if types[pindex].node == nil { + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = types[pindex].id() + typeParam[cc.ident].typ = types[pindex] + } else { + typeParam[cc.ident] = types[pindex].node } - typeParam[cc.ident] = types[pindex] rtname += it.id() + "," pindex++ } @@ -118,14 +127,21 @@ func genAST(sc *scope, root *node, types []*node) (*node, bool, error) { if pindex >= len(types) { return nil, cc.cfgErrorf("undefined type for %s", cc.ident) } - it, err := nodeType(n.interp, sc, types[pindex]) + it := types[pindex] + t, err := nodeType(c.interp, sc, c.child[l]) if err != nil { return nil, err } - if err := checkConstraint(sc, types[pindex], c.child[l]); err != nil { + if err := checkConstraint(sc, types[pindex], t); err != nil { return nil, err } - typeParam[cc.ident] = types[pindex] + if types[pindex].node == nil { + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = types[pindex].id() + typeParam[cc.ident].typ = types[pindex] + } else { + typeParam[cc.ident] = types[pindex].node + } tname += it.id() + "," pindex++ } @@ -203,22 +219,22 @@ func copyNode(n, anc *node, recursive bool) *node { return nod } -func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { +func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*itype, error) { ftn := fun.typ.node // Fill the map of parameter types, indexed by type param ident. - types := map[string]*itype{} + paramTypes := map[string]*itype{} for _, c := range ftn.child[0].child { typ, err := nodeType(fun.interp, sc, c.lastChild()) if err != nil { return nil, err } for _, cc := range c.child[:len(c.child)-1] { - types[cc.ident] = typ + paramTypes[cc.ident] = typ } } - var inferTypes func(*itype, *itype) ([]*node, error) - inferTypes = func(param, input *itype) ([]*node, error) { + var inferTypes func(*itype, *itype) ([]*itype, error) + inferTypes = func(param, input *itype) ([]*itype, error) { switch param.cat { case chanT, ptrT, sliceT: return inferTypes(param.val, input.val) @@ -235,18 +251,18 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { return append(k, v...), nil case structT: - nods := []*node{} + lt := []*itype{} for i, f := range param.field { nl, err := inferTypes(f.typ, input.field[i].typ) if err != nil { return nil, err } - nods = append(nods, nl...) + lt = append(lt, nl...) } - return nods, nil + return lt, nil case funcT: - nods := []*node{} + lt := []*itype{} for i, t := range param.arg { if i >= len(input.arg) { break @@ -255,7 +271,7 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { if err != nil { return nil, err } - nods = append(nods, nl...) + lt = append(lt, nl...) } for i, t := range param.ret { if i >= len(input.ret) { @@ -265,46 +281,38 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*node, error) { if err != nil { return nil, err } - nods = append(nods, nl...) + lt = append(lt, nl...) } - return nods, nil + return lt, nil case nilT: - if types[param.name] != nil { - return []*node{input.node}, nil + if paramTypes[param.name] != nil { + return []*itype{input}, nil } case genericT: - return []*node{input.node}, nil + return []*itype{input}, nil } return nil, nil } - nodes := []*node{} + types := []*itype{} for i, c := range ftn.child[1].child { typ, err := nodeType(fun.interp, sc, c.lastChild()) if err != nil { return nil, err } - nods, err := inferTypes(typ, args[i].typ) + lt, err := inferTypes(typ, args[i].typ) if err != nil { return nil, err } - nodes = append(nodes, nods...) + types = append(types, lt...) } - return nodes, nil + return types, nil } -func checkConstraint(sc *scope, input, constraint *node) error { - ct, err := nodeType(constraint.interp, sc, constraint) - if err != nil { - return err - } - it, err := nodeType(input.interp, sc, input) - if err != nil { - return err - } +func checkConstraint(sc *scope, it, ct *itype) error { if len(ct.constraint) == 0 && len(ct.ulconstraint) == 0 { return nil } @@ -318,5 +326,5 @@ func checkConstraint(sc *scope, input, constraint *node) error { return nil } } - return input.cfgErrorf("%s does not implement %s", input.typ.id(), ct.id()) + return it.node.cfgErrorf("%s does not implement %s", it.id(), ct.id()) } diff --git a/interp/interp.go b/interp/interp.go index 4b7471e2d..11650a80b 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -28,7 +28,7 @@ type node struct { debug *nodeDebugData // debug info child []*node // child subtrees (AST) anc *node // ancestor (AST) - param []*node // generic parameter nodes (AST) + param []*itype // generic parameter nodes (AST) start *node // entry point in subtree (CFG) tnext *node // true branch successor (CFG) fnext *node // false branch successor (CFG) diff --git a/interp/type.go b/interp/type.go index 3433e6d4a..12ab1f759 100644 --- a/interp/type.go +++ b/interp/type.go @@ -838,7 +838,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, break } // A generic type is being instantiated. Generate it. - t, err = genType(interp, sc, name, lt, []*node{n.child[1]}, seen) + t, err = genType(interp, sc, name, lt, []*itype{t1}, seen) if err != nil { return nil, err } @@ -866,7 +866,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, } name := lt.id() + "[" out := false - tnodes := []*node{} + types := []*itype{} for _, c := range n.child[1:] { t1, err := nodeType2(interp, sc, c, seen) if err != nil { @@ -877,7 +877,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, out = true break } - tnodes = append(tnodes, t1.node) + types = append(types, t1) name += t1.id() + "," } if out { @@ -889,7 +889,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, break } // A generic type is being instantiated. Generate it. - t, err = genType(interp, sc, name, lt, tnodes, seen) + t, err = genType(interp, sc, name, lt, types, seen) case interfaceType: if sname := typeName(n); sname != "" { @@ -1113,9 +1113,9 @@ 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) { +func genType(interp *Interpreter, sc *scope, name string, lt *itype, types []*itype, seen []*node) (t *itype, err error) { // A generic type is being instantiated. Generate it. - g, _, err := genAST(sc, lt.node.anc, tnodes) + g, _, err := genAST(sc, lt.node.anc, types) if err != nil { return nil, err } @@ -1131,15 +1131,15 @@ func genType(interp *Interpreter, sc *scope, name string, lt *itype, tnodes, see } for _, nod := range lt.method { - if err := genMethod(interp, sc, t, nod, tnodes); err != nil { + if err := genMethod(interp, sc, t, nod, types); err != nil { return nil, err } } return t, err } -func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, tnodes []*node) error { - gm, _, err := genAST(sc, nod, tnodes) +func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, types []*itype) error { + gm, _, err := genAST(sc, nod, types) if err != nil { return err } From a71ac85d2a02b21260833fae0513b552e621f469 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 24 Jan 2023 17:20:46 +0100 Subject: [PATCH 08/14] add missing substitution of generic type by instance --- interp/cfg.go | 4 ++++ interp/generic.go | 54 ++++++++++++++++++++++++++++------------------- interp/type.go | 3 +++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index 3f0612d77..fbc0d0c7a 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -1511,6 +1511,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string sym, level, found := sc.lookup(n.ident) if !found { + if n.typ != nil { + // Node is a generic instance with an already populated type. + break + } // retry with the filename, in case ident is a package name. sym, level, found = sc.lookup(filepath.Join(n.ident, baseName)) if !found { diff --git a/interp/generic.go b/interp/generic.go index 30f94792e..cacbd158c 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -6,7 +6,7 @@ import ( ) // adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. -// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident); } +//func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } // genAST returns a new AST where generic types are replaced by instantiated types. func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { @@ -73,13 +73,9 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { if err := checkConstraint(sc, types[pindex], t); err != nil { return nil, err } - if types[pindex].node == nil { - typeParam[cc.ident] = copyNode(cc, cc.anc, false) - typeParam[cc.ident].ident = types[pindex].id() - typeParam[cc.ident].typ = types[pindex] - } else { - typeParam[cc.ident] = types[pindex].node - } + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = types[pindex].id() + typeParam[cc.ident].typ = types[pindex] pindex++ } } @@ -103,13 +99,9 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { return nil, cc.cfgErrorf("undefined type for %s", cc.ident) } it := types[pindex] - if types[pindex].node == nil { - typeParam[cc.ident] = copyNode(cc, cc.anc, false) - typeParam[cc.ident].ident = types[pindex].id() - typeParam[cc.ident].typ = types[pindex] - } else { - typeParam[cc.ident] = types[pindex].node - } + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = it.id() + typeParam[cc.ident].typ = it rtname += it.id() + "," pindex++ } @@ -135,13 +127,9 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { if err := checkConstraint(sc, types[pindex], t); err != nil { return nil, err } - if types[pindex].node == nil { - typeParam[cc.ident] = copyNode(cc, cc.anc, false) - typeParam[cc.ident].ident = types[pindex].id() - typeParam[cc.ident].typ = types[pindex] - } else { - typeParam[cc.ident] = types[pindex].node - } + typeParam[cc.ident] = copyNode(cc, cc.anc, false) + typeParam[cc.ident].ident = it.id() + typeParam[cc.ident].typ = it tname += it.id() + "," pindex++ } @@ -149,7 +137,29 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { tname = strings.TrimSuffix(tname, ",") + "]" return nod, nil } + + case indexListExpr: + // This generic node should be substituted by instance. + tname2 := "" + for i, c := range n.child { + gn, err := gtree(c, nod) + if err != nil { + return nil, err + } + if i == 0 { + tname2 = gn.ident + "[" + } else { + tname2 += gn.ident + "," + } + } + tname2 = strings.TrimSuffix(tname2, ",") + "]" + n2 := n.interp.generic[tname2] + if n2 != nil && n2.kind == typeSpec { + return n2.lastChild(), nil + } + return n2, nil } + for _, c := range n.child { gn, err := gtree(c, nod) if err != nil { diff --git a/interp/type.go b/interp/type.go index 12ab1f759..35fc8c50b 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1081,6 +1081,9 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, t = structOf(t, fields, withNode(n), withScope(sc)) t.incomplete = incomplete if sname != "" { + if sc.sym[sname] == nil { + sc.sym[sname] = &symbol{index: -1, kind: typeSym, node: n} + } sc.sym[sname].typ = t } From 30c56534252a37981ecb4001134d4d3fcd8957d8 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 24 Jan 2023 17:27:56 +0100 Subject: [PATCH 09/14] add test for previous --- _test/gen12.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 _test/gen12.go diff --git a/_test/gen12.go b/_test/gen12.go new file mode 100644 index 000000000..d93298e2e --- /dev/null +++ b/_test/gen12.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" +) + +func MapOf[K comparable, V any](m map[K]V) Map[K, V] { + return Map[K, V]{m} +} + +type Map[K comparable, V any] struct { + ж map[K]V +} + +func (v MapView) Int() Map[string, int] { return MapOf(v.ж.Int) } + +type VMap struct { + Int map[string]int +} + +type MapView struct { + ж *VMap +} + +func main() { + mv := MapView{&VMap{}} + fmt.Println(mv.ж) +} + +// Output: +// &{map[]} From 631dfa868029cf9e49288adde51b6aa269ded60c Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Wed, 25 Jan 2023 11:48:55 +0100 Subject: [PATCH 10/14] fix lint, fix generic methods with multiple type parameters --- interp/generic.go | 8 ++++---- interp/gta.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interp/generic.go b/interp/generic.go index cacbd158c..253928eba 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -6,7 +6,7 @@ import ( ) // adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. -//func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } +// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } // genAST returns a new AST where generic types are replaced by instantiated types. func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { @@ -70,7 +70,7 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { if err != nil { return nil, err } - if err := checkConstraint(sc, types[pindex], t); err != nil { + if err := checkConstraint(types[pindex], t); err != nil { return nil, err } typeParam[cc.ident] = copyNode(cc, cc.anc, false) @@ -124,7 +124,7 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { if err != nil { return nil, err } - if err := checkConstraint(sc, types[pindex], t); err != nil { + if err := checkConstraint(types[pindex], t); err != nil { return nil, err } typeParam[cc.ident] = copyNode(cc, cc.anc, false) @@ -322,7 +322,7 @@ func inferTypesFromCall(sc *scope, fun *node, args []*node) ([]*itype, error) { return types, nil } -func checkConstraint(sc *scope, it, ct *itype) error { +func checkConstraint(it, ct *itype) error { if len(ct.constraint) == 0 && len(ct.ulconstraint) == 0 { return nil } diff --git a/interp/gta.go b/interp/gta.go index 39f0606a2..28f84aee2 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -169,7 +169,7 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ typName = c.child[0].ident genericMethod = true } - case indexExpr: + case indexExpr, indexListExpr: genericMethod = true } } From 1914c33ed5ba8c35caae8a138160f4642870a455 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 31 Jan 2023 17:12:27 +0100 Subject: [PATCH 11/14] Support generic type in composite literal expression --- _test/gen13.go | 18 +++++++++++++++ interp/cfg.go | 57 +++++++++++++++++++++++++++++++++++++++++++++-- interp/generic.go | 6 ++--- interp/type.go | 7 ++++-- 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 _test/gen13.go diff --git a/_test/gen13.go b/_test/gen13.go new file mode 100644 index 000000000..11c17ddb9 --- /dev/null +++ b/_test/gen13.go @@ -0,0 +1,18 @@ +package main + +type Map[K comparable, V any] struct { + ж map[K]V +} + +func (m Map[K, V]) Has(k K) bool { + _, ok := m.ж[k] + return ok +} + +func main() { + m := Map[string, float64]{} + println(m.Has("test")) +} + +// Output: +// false diff --git a/interp/cfg.go b/interp/cfg.go index fbc0d0c7a..46c9678ff 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -322,8 +322,61 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } } if n.typ == nil { - err = n.cfgErrorf("undefined type") - return false + // A nil type indicates either an error or a generic type. + // A child indexExpr or indexListExpr is used for type parameters, + // it indicates an instanciated generic. + if n.child[0].kind != indexExpr && n.child[0].kind != indexListExpr { + err = n.cfgErrorf("undefined type") + return false + } + t0, err := nodeType(interp, sc, n.child[0].child[0]) + if err != nil { + return false + } + if t0.cat != genericT { + err = n.cfgErrorf("undefined type") + return false + } + // We have a composite literal of generic type, instantiate it. + lt := []*itype{} + for _, n1 := range n.child[0].child[1:] { + t1, err1 := nodeType(interp, sc, n1) + if err1 != nil { + err = err1 + return false + } + lt = append(lt, t1) + } + var g *node + g, _, err = genAST(sc, t0.node.anc, lt) + if err != nil { + return false + } + n.child[0] = g.lastChild() + n.typ, err = nodeType(interp, sc, n.child[0]) + if err != nil { + return false + } + // Generate methods if any. + for _, nod := range t0.method { + gm, _, err2 := genAST(nod.scope, nod, lt) + if err2 != nil { + err = err2 + return false + } + gm.typ, err = nodeType(interp, nod.scope, gm.child[2]) + if err != nil { + return false + } + if _, err = interp.cfg(gm, sc, sc.pkgID, sc.pkgName); err != nil { + return false + } + if err = genRun(gm); err != nil { + return false + } + n.typ.addMethod(gm) + } + n.nleft = 1 // Indictate the type of composite literal. } } diff --git a/interp/generic.go b/interp/generic.go index 253928eba..cf522be5f 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -6,7 +6,7 @@ import ( ) // adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. -// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } +func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } // genAST returns a new AST where generic types are replaced by instantiated types. func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { @@ -86,7 +86,7 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { // Node is the receiver of a generic method. if root.kind == funcDecl && n.anc == root && childPos(n) == 0 && len(n.child) > 0 { rtn := n.child[0].child[1] - if rtn.kind == indexExpr || (rtn.kind == starExpr && rtn.child[0].kind == indexExpr) { + if rtn.kind == indexExpr || rtn.kind == indexListExpr || (rtn.kind == starExpr && (rtn.child[0].kind == indexExpr || rtn.child[0].kind == indexListExpr)) { // Method receiver is a generic type. if rtn.kind == starExpr && rtn.child[0].kind == indexExpr { // Method receiver is a pointer on a generic type. @@ -196,7 +196,7 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { nod.ident = rtname nod.child = nil } - // r.astDot(dotWriter(r.interp.dotCmd), r.ident) // Used for debugging only. + // r.adot() // Used for debugging only. return r, false, nil } diff --git a/interp/type.go b/interp/type.go index 35fc8c50b..61380baa1 100644 --- a/interp/type.go +++ b/interp/type.go @@ -1150,17 +1150,20 @@ func genMethod(interp *Interpreter, sc *scope, t *itype, nod *node, types []*ity return err } t.addMethod(gm) - if rtn := gm.child[0].child[0].lastChild(); rtn.kind == starExpr { - // The receiver is a pointer on a generic type. + + // If the receiver is a pointer to a generic type, generate also the pointer type. + if rtn := gm.child[0].child[0].lastChild(); rtn != nil && rtn.kind == starExpr { pt := ptrOf(t, withNode(t.node), withScope(sc)) pt.addMethod(gm) rtn.typ = pt } + // Compile the method AST in the scope of the generic type. scop := nod.typ.scope if _, err = interp.cfg(gm, scop, scop.pkgID, scop.pkgName); err != nil { return err } + // Generate closures for function body. return genRun(gm) } From b153bb4cc26f73e6f8e6c2b080d7a1fa50110e71 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 31 Jan 2023 17:16:49 +0100 Subject: [PATCH 12/14] lint --- interp/generic.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interp/generic.go b/interp/generic.go index cf522be5f..85dd29cbe 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -6,7 +6,7 @@ import ( ) // adot produces an AST dot(1) directed acyclic graph for the given node. For debugging only. -func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } +// func (n *node) adot() { n.astDot(dotWriter(n.interp.dotCmd), n.ident) } // genAST returns a new AST where generic types are replaced by instantiated types. func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { From a192ae3b687d70f528de40a6051a7093c8a4d4ee Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Tue, 31 Jan 2023 17:21:16 +0100 Subject: [PATCH 13/14] lint --- interp/cfg.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/interp/cfg.go b/interp/cfg.go index 46c9678ff..47e646c66 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -329,8 +329,8 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string err = n.cfgErrorf("undefined type") return false } - t0, err := nodeType(interp, sc, n.child[0].child[0]) - if err != nil { + t0, err1 := nodeType(interp, sc, n.child[0].child[0]) + if err1 != nil { return false } if t0.cat != genericT { @@ -342,7 +342,6 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string for _, n1 := range n.child[0].child[1:] { t1, err1 := nodeType(interp, sc, n1) if err1 != nil { - err = err1 return false } lt = append(lt, t1) From 8c5a398bd3e440dd76b283cc39357c962d6585a0 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Fri, 3 Feb 2023 10:47:36 +0100 Subject: [PATCH 14/14] fix generic instance name, revert previous fix attempt of the same issue --- interp/generic.go | 25 ++----------------------- interp/type.go | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/interp/generic.go b/interp/generic.go index 85dd29cbe..da135642f 100644 --- a/interp/generic.go +++ b/interp/generic.go @@ -86,9 +86,9 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { // Node is the receiver of a generic method. if root.kind == funcDecl && n.anc == root && childPos(n) == 0 && len(n.child) > 0 { rtn := n.child[0].child[1] + // Method receiver is a generic type if it takes some type parameters. if rtn.kind == indexExpr || rtn.kind == indexListExpr || (rtn.kind == starExpr && (rtn.child[0].kind == indexExpr || rtn.child[0].kind == indexListExpr)) { - // Method receiver is a generic type. - if rtn.kind == starExpr && rtn.child[0].kind == indexExpr { + if rtn.kind == starExpr { // Method receiver is a pointer on a generic type. rtn = rtn.child[0] recvrPtr = true @@ -137,27 +137,6 @@ func genAST(sc *scope, root *node, types []*itype) (*node, bool, error) { tname = strings.TrimSuffix(tname, ",") + "]" return nod, nil } - - case indexListExpr: - // This generic node should be substituted by instance. - tname2 := "" - for i, c := range n.child { - gn, err := gtree(c, nod) - if err != nil { - return nil, err - } - if i == 0 { - tname2 = gn.ident + "[" - } else { - tname2 += gn.ident + "," - } - } - tname2 = strings.TrimSuffix(tname2, ",") + "]" - n2 := n.interp.generic[tname2] - if n2 != nil && n2.kind == typeSpec { - return n2.lastChild(), nil - } - return n2, nil } for _, c := range n.child { diff --git a/interp/type.go b/interp/type.go index 61380baa1..7b036f9cb 100644 --- a/interp/type.go +++ b/interp/type.go @@ -883,7 +883,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, if out { break } - name += "]" + name = strings.TrimSuffix(name, ",") + "]" if sym, _, found := sc.lookup(name); found { t = sym.typ break