From 297dd6de683ed94c48d0e5fb41a5002fa294eb48 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 23 Oct 2024 21:15:13 +0700 Subject: [PATCH 01/16] chore: refactor DRY code --- gnovm/pkg/gnolang/preprocess.go | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index e1dc3671333..878e125c16f 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2425,29 +2425,23 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { tvs[i] = anyValue(st) } } + // define. + node := last if fn, ok := last.(*FileNode); ok { - pn := fn.GetParentNode(nil).(*PackageNode) - for i := 0; i < numNames; i++ { - nx := &n.NameExprs[i] - if nx.Name == blankIdentifier { - nx.Path = NewValuePathBlock(0, 0, blankIdentifier) - } else { - pn.Define2(n.Const, nx.Name, sts[i], tvs[i]) - nx.Path = last.GetPathForName(nil, nx.Name) - } - } - } else { - for i := 0; i < numNames; i++ { - nx := &n.NameExprs[i] - if nx.Name == blankIdentifier { - nx.Path = NewValuePathBlock(0, 0, blankIdentifier) - } else { - last.Define2(n.Const, nx.Name, sts[i], tvs[i]) - nx.Path = last.GetPathForName(nil, nx.Name) - } + node = fn.GetParentNode(nil).(*PackageNode) + } + + for i := 0; i < numNames; i++ { + nx := &n.NameExprs[i] + if nx.Name == blankIdentifier { + nx.Path = NewValuePathBlock(0, 0, blankIdentifier) + } else { + node.Define2(n.Const, nx.Name, sts[i], tvs[i]) + nx.Path = last.GetPathForName(nil, nx.Name) } } + // TODO make note of constance in static block for // future use, or consider "const paths". set as // preprocessed. From 6e6bcae5d27561aeb4488f2c84d7f44ecd5cb014 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Thu, 24 Oct 2024 20:43:31 +0700 Subject: [PATCH 02/16] wip --- gnovm/pkg/gnolang/preprocess.go | 72 +++++++++++++++++++++++++ gnovm/tests/files/assign30_filetest.gno | 11 ++++ 2 files changed, 83 insertions(+) create mode 100644 gnovm/tests/files/assign30_filetest.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 878e125c16f..f27ae9abeb3 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1970,6 +1970,68 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertIfConst(store, last, rx) } + numNames := len(n.Lhs) + sts := make([]Type, numNames) // static types + tvs := make([]TypedValue, numNames) + valueExprs := n.Rhs + + // NOTE: ValDecl has n.Type, AssignStmt not + var nType Expr + + // len(n.Lhs) > len(n.Rhs) and panic when len(n.Rhs) != 1 + // so this is the same + if numNames > 1 && len(valueExprs) == 1 { + var tuple *tupleType + println(sts, tvs, tuple, "=============") + + valueExpr := valueExprs[0] + valueType := evalStaticTypeOfRaw(store, last, valueExpr) + + switch expr := valueExpr.(type) { + case *CallExpr: + // Call case: a, b := x(...) + tuple = valueType.(*tupleType) + case *TypeAssertExpr: + // Type assert case: v, ok := x.(int) + tuple = &tupleType{Elts: []Type{valueType, BoolType}} + expr.HasOK = true + case *IndexExpr: + // Map index case: v, ok := m[idx] + tuple = &tupleType{Elts: []Type{valueType, BoolType}} + expr.HasOK = true + default: + panic("should not happen") + } + + if numValues := len(tuple.Elts); numValues != numNames { + panic( + fmt.Sprintf( + "assignment mismatch: %d variable(s) but %s returns %d value(s)", + numNames, + valueExpr.String(), + numValues, + ), + ) + } + + var t Type = nil + if nType != nil { + // only a single type can be specified. + t = evalStaticType(store, last, nType) + } + + // TODO check tt and nt compat. + for i := 0; i < numNames; i++ { + // set types as return types if needed. + sts[i] = t + if t == nil { + sts[i] = tuple.Elts[i] + } + + tvs[i] = anyValue(t) + } + } + if len(n.Lhs) > len(n.Rhs) { switch cx := n.Rhs[0].(type) { case *CallExpr: @@ -2292,6 +2354,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *ValueDecl: + fmt.Printf("[VavlueDecl] n: %+v\n", n) + fmt.Printf("[VavlueDecl] n.NameExprs: %v\n", n.NameExprs) + fmt.Printf("[VavlueDecl] n.Const: %v\n", n.Const) + fmt.Printf("[VavlueDecl] n.Values: %v\n", n.Values) + fmt.Printf("[VavlueDecl] n.Type: %v\n", n.Type) + // panic("----") // evaluate value if const expr. if n.Const { // NOTE: may or may not be a *ConstExpr, @@ -2426,6 +2494,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } } + fmt.Printf("[VavlueDecl] sts: %v\n", sts) + fmt.Printf("[VavlueDecl] tvs: %v\n", tvs) + // define. node := last if fn, ok := last.(*FileNode); ok { @@ -2439,6 +2510,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } else { node.Define2(n.Const, nx.Name, sts[i], tvs[i]) nx.Path = last.GetPathForName(nil, nx.Name) + fmt.Printf("[VavlueDecl] nx.Path: %v\n", nx.Path) } } diff --git a/gnovm/tests/files/assign30_filetest.gno b/gnovm/tests/files/assign30_filetest.gno new file mode 100644 index 00000000000..2583148472b --- /dev/null +++ b/gnovm/tests/files/assign30_filetest.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func t() (int, string) { return 1, "str" } + +func main() { + var v interface{} = 1 + + i := v.(int) +} From f196044b25739d07308d42c65c5166af7b15cdea Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 24 Oct 2024 22:43:14 +0700 Subject: [PATCH 03/16] wip: sync AssignStmt DEFINE with ValueDecl --- gnovm/pkg/gnolang/preprocess.go | 292 ++++++++++-------------- gnovm/tests/files/assign30_filetest.gno | 2 +- 2 files changed, 127 insertions(+), 167 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index f27ae9abeb3..02006a4d04c 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1970,103 +1970,21 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertIfConst(store, last, rx) } - numNames := len(n.Lhs) + nameExprs := make([]*NameExpr, len(n.Lhs)) + for i := range n.Lhs { + nameExprs[i] = n.Lhs[i].(*NameExpr) + } + + numNames := len(nameExprs) sts := make([]Type, numNames) // static types tvs := make([]TypedValue, numNames) - valueExprs := n.Rhs - - // NOTE: ValDecl has n.Type, AssignStmt not - var nType Expr - - // len(n.Lhs) > len(n.Rhs) and panic when len(n.Rhs) != 1 - // so this is the same - if numNames > 1 && len(valueExprs) == 1 { - var tuple *tupleType - println(sts, tvs, tuple, "=============") - - valueExpr := valueExprs[0] - valueType := evalStaticTypeOfRaw(store, last, valueExpr) - - switch expr := valueExpr.(type) { - case *CallExpr: - // Call case: a, b := x(...) - tuple = valueType.(*tupleType) - case *TypeAssertExpr: - // Type assert case: v, ok := x.(int) - tuple = &tupleType{Elts: []Type{valueType, BoolType}} - expr.HasOK = true - case *IndexExpr: - // Map index case: v, ok := m[idx] - tuple = &tupleType{Elts: []Type{valueType, BoolType}} - expr.HasOK = true - default: - panic("should not happen") - } - - if numValues := len(tuple.Elts); numValues != numNames { - panic( - fmt.Sprintf( - "assignment mismatch: %d variable(s) but %s returns %d value(s)", - numNames, - valueExpr.String(), - numValues, - ), - ) - } - - var t Type = nil - if nType != nil { - // only a single type can be specified. - t = evalStaticType(store, last, nType) - } - - // TODO check tt and nt compat. - for i := 0; i < numNames; i++ { - // set types as return types if needed. - sts[i] = t - if t == nil { - sts[i] = tuple.Elts[i] - } - - tvs[i] = anyValue(t) - } - } - if len(n.Lhs) > len(n.Rhs) { - switch cx := n.Rhs[0].(type) { - case *CallExpr: - // Call case: a, b := x(...) - ift := evalStaticTypeOf(store, last, cx.Func) - cft := getGnoFuncTypeOf(store, ift) - for i, lx := range n.Lhs { - ln := lx.(*NameExpr).Name - rf := cft.Results[i] - // re-definition - last.Define(ln, anyValue(rf.Type)) - } - case *TypeAssertExpr: - lhs0 := n.Lhs[0].(*NameExpr).Name - lhs1 := n.Lhs[1].(*NameExpr).Name - tt := evalStaticType(store, last, cx.Type) - // re-definitions - last.Define(lhs0, anyValue(tt)) - last.Define(lhs1, anyValue(BoolType)) - case *IndexExpr: - lhs0 := n.Lhs[0].(*NameExpr).Name - lhs1 := n.Lhs[1].(*NameExpr).Name - - var mt *MapType - dt := evalStaticTypeOf(store, last, cx.X) - mt, ok := baseOf(dt).(*MapType) - if !ok { - panic(fmt.Sprintf("invalid index expression on %T", dt)) - } - // re-definitions - last.Define(lhs0, anyValue(mt.Value)) - last.Define(lhs1, anyValue(BoolType)) - default: - panic("should not happen") - } + if numNames > len(n.Rhs) { + // - `a, b, c T := f()` + // - `a, b := n.(T)` + // - `a, b := n[i], where n is a map` + parseTypesAndValues(sts, tvs, store, last, nameExprs, nil, n.Rhs[0]) + defineOrDecl(sts, tvs, last, nameExprs, false) } else { // General case: a, b := x, y for i, lx := range n.Lhs { @@ -2354,12 +2272,6 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *ValueDecl: - fmt.Printf("[VavlueDecl] n: %+v\n", n) - fmt.Printf("[VavlueDecl] n.NameExprs: %v\n", n.NameExprs) - fmt.Printf("[VavlueDecl] n.Const: %v\n", n.Const) - fmt.Printf("[VavlueDecl] n.Values: %v\n", n.Values) - fmt.Printf("[VavlueDecl] n.Type: %v\n", n.Type) - // panic("----") // evaluate value if const expr. if n.Const { // NOTE: may or may not be a *ConstExpr, @@ -2375,61 +2287,21 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // the implementation differ from // runDeclaration(), as this uses OpStaticTypeOf. } - numNames := len(n.NameExprs) + + nameExprs := make([]*NameExpr, len(n.NameExprs)) + for i := range n.NameExprs { + nameExprs[i] = &n.NameExprs[i] + } + + numNames := len(nameExprs) sts := make([]Type, numNames) // static types tvs := make([]TypedValue, numNames) if numNames > 1 && len(n.Values) == 1 { - // Special cases if one of the following: // - `var a, b, c T = f()` // - `var a, b = n.(T)` // - `var a, b = n[i], where n is a map` - - var tuple *tupleType - valueExpr := n.Values[0] - valueType := evalStaticTypeOfRaw(store, last, valueExpr) - - switch expr := valueExpr.(type) { - case *CallExpr: - tuple = valueType.(*tupleType) - case *TypeAssertExpr, *IndexExpr: - tuple = &tupleType{Elts: []Type{valueType, BoolType}} - if ex, ok := expr.(*TypeAssertExpr); ok { - ex.HasOK = true - break - } - expr.(*IndexExpr).HasOK = true - default: - panic(fmt.Sprintf("unexpected ValueDecl value expression type %T", expr)) - } - - if rLen := len(tuple.Elts); rLen != numNames { - panic( - fmt.Sprintf( - "assignment mismatch: %d variable(s) but %s returns %d value(s)", - numNames, - valueExpr.String(), - rLen, - ), - ) - } - - if n.Type != nil { - // only a single type can be specified. - nt := evalStaticType(store, last, n.Type) - // TODO check tt and nt compat. - for i := 0; i < numNames; i++ { - sts[i] = nt - tvs[i] = anyValue(nt) - } - } else { - // set types as return types. - for i := 0; i < numNames; i++ { - et := tuple.Elts[i] - sts[i] = et - tvs[i] = anyValue(et) - } - } + parseTypesAndValues(sts, tvs, store, last, nameExprs, n.Type, n.Values[0]) } else if len(n.Values) != 0 && numNames != len(n.Values) { panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values))) } else { // general case @@ -2494,25 +2366,8 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } } - fmt.Printf("[VavlueDecl] sts: %v\n", sts) - fmt.Printf("[VavlueDecl] tvs: %v\n", tvs) - // define. - node := last - if fn, ok := last.(*FileNode); ok { - node = fn.GetParentNode(nil).(*PackageNode) - } - - for i := 0; i < numNames; i++ { - nx := &n.NameExprs[i] - if nx.Name == blankIdentifier { - nx.Path = NewValuePathBlock(0, 0, blankIdentifier) - } else { - node.Define2(n.Const, nx.Name, sts[i], tvs[i]) - nx.Path = last.GetPathForName(nil, nx.Name) - fmt.Printf("[VavlueDecl] nx.Path: %v\n", nx.Path) - } - } + defineOrDecl(sts, tvs, last, nameExprs, n.Const) // TODO make note of constance in static block for // future use, or consider "const paths". set as @@ -2584,6 +2439,111 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { return nn } +// This func aims at syncing logic between op define (:=) and declare(var/const) +// This will define or declare the variables +func defineOrDecl( + sts []Type, + tvs []TypedValue, + bn BlockNode, + nameExprs []*NameExpr, + isConst bool, +) { + node := bn + if fn, ok := bn.(*FileNode); ok { + node = fn.GetParentNode(nil).(*PackageNode) + } + + for i := 0; i < len(sts); i++ { + nx := nameExprs[i] + if nx.Name == blankIdentifier { + nx.Path = NewValuePathBlock(0, 0, blankIdentifier) + } else { + node.Define2(isConst, nx.Name, sts[i], tvs[i]) + nx.Path = bn.GetPathForName(nil, nx.Name) + } + } +} + +// This func aims at syncing logic between op define (:=) and declare(var/const) +// This will parse Type and TypedValue for these cases +// Declare: +// - `var a, b, c T = f()` +// - `var a, b = n.(T)` +// - `var a, b = n[i], where n is a map` +// +// Assign +// - `a, b, c T := f()` +// - `a, b := n.(T)` +// - `a, b := n[i], where n is a map` +func parseTypesAndValues( + sts []Type, + tvs []TypedValue, + store Store, + bn BlockNode, + nameExprs []*NameExpr, + typeExpr Expr, + valueExpr Expr, +) ([]Type, []TypedValue) { + var tuple *tupleType + valueType := evalStaticTypeOfRaw(store, bn, valueExpr) + + numNames := len(nameExprs) + + switch expr := valueExpr.(type) { + case *CallExpr: + // Call case: + // var a, b, c T = f() + // a, b, c := f() + tuple = valueType.(*tupleType) + case *TypeAssertExpr: + // Type assert case: + // var a, b = n.(T) + // a, b := n.(T) + tuple = &tupleType{Elts: []Type{valueType, BoolType}} + expr.HasOK = true + case *IndexExpr: + // Map index case: + // var a, b = n[i], where n is a map + // a, b := n[i], where n is a map + tuple = &tupleType{Elts: []Type{valueType, BoolType}} + expr.HasOK = true + default: + panic(fmt.Sprintf("unexpected value expression type %T", expr)) + } + + if numValues := len(tuple.Elts); numValues != numNames { + panic( + fmt.Sprintf( + "assignment mismatch: %d variable(s) but %s returns %d value(s)", + numNames, + valueExpr.String(), + numValues, + ), + ) + } + + var st Type = nil + if typeExpr != nil { + // only a single type can be specified. + st = evalStaticType(store, bn, typeExpr) + } + + // TODO check tt and nt compat. + for i := 0; i < numNames; i++ { + if st != nil { + // TODO check tt and nt compat. + sts[i] = st + } else { + // set types as return types + sts[i] = tuple.Elts[i] + } + + tvs[i] = anyValue(st) + } + + return sts, tvs +} + // Identifies NameExprTypeHeapDefines. // Also finds GotoLoopStmts, XXX but probably remove, not needed. func findGotoLoopDefines(ctx BlockNode, bn BlockNode) { diff --git a/gnovm/tests/files/assign30_filetest.gno b/gnovm/tests/files/assign30_filetest.gno index 2583148472b..a327c7ae8b6 100644 --- a/gnovm/tests/files/assign30_filetest.gno +++ b/gnovm/tests/files/assign30_filetest.gno @@ -7,5 +7,5 @@ func t() (int, string) { return 1, "str" } func main() { var v interface{} = 1 - i := v.(int) + i, x := v.(int), 2 } From 26792b5a62a79467c582b8330be833a40cc77bb0 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 25 Oct 2024 14:53:25 +0700 Subject: [PATCH 04/16] fix: use tupletype for evalStaticTypeOfRaw for CallExpr --- gnovm/pkg/gnolang/preprocess.go | 18 ++++++++++---- gnovm/tests/files/assign30_filetest.gno | 31 ++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 8e3f41b145a..0ada11fe66e 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2480,8 +2480,6 @@ func parseTypesAndValues( valueExpr Expr, ) ([]Type, []TypedValue) { var tuple *tupleType - valueType := evalStaticTypeOfRaw(store, bn, valueExpr) - numNames := len(nameExprs) switch expr := valueExpr.(type) { @@ -2489,18 +2487,26 @@ func parseTypesAndValues( // Call case: // var a, b, c T = f() // a, b, c := f() + valueType := evalStaticTypeOfRaw(store, bn, valueExpr) tuple = valueType.(*tupleType) case *TypeAssertExpr: // Type assert case: // var a, b = n.(T) // a, b := n.(T) - tuple = &tupleType{Elts: []Type{valueType, BoolType}} + tt := evalStaticType(store, bn, expr.Type) + tuple = &tupleType{Elts: []Type{tt, BoolType}} expr.HasOK = true case *IndexExpr: // Map index case: // var a, b = n[i], where n is a map // a, b := n[i], where n is a map - tuple = &tupleType{Elts: []Type{valueType, BoolType}} + var mt *MapType + dt := evalStaticTypeOf(store, bn, expr.X) + mt, ok := baseOf(dt).(*MapType) + if !ok { + panic(fmt.Sprintf("invalid index expression on %T", dt)) + } + tuple = &tupleType{Elts: []Type{mt.Value, BoolType}} expr.HasOK = true default: panic(fmt.Sprintf("unexpected value expression type %T", expr)) @@ -2533,9 +2539,11 @@ func parseTypesAndValues( sts[i] = tuple.Elts[i] } - tvs[i] = anyValue(st) + tvs[i] = anyValue(sts[i]) } + // fmt.Printf("%v - %v", sts, tvs) + return sts, tvs } diff --git a/gnovm/tests/files/assign30_filetest.gno b/gnovm/tests/files/assign30_filetest.gno index a327c7ae8b6..9704b3b66eb 100644 --- a/gnovm/tests/files/assign30_filetest.gno +++ b/gnovm/tests/files/assign30_filetest.gno @@ -2,10 +2,35 @@ package main import "fmt" -func t() (int, string) { return 1, "str" } +func t() (int, int) { return 1, 2 } func main() { - var v interface{} = 1 + // v1, v2 := t() + // fmt.Println("DEFINE call:", v1, v2) + + // var i interface{} = 1 + // iv, ok := i.(int) + // fmt.Println("DEFINE index:", iv, ok) + + // m := map[int]string{1: "test"} + // mv, ok := m[1] + // fmt.Println("DEFINE map:", mv, ok) + + + // var v1, v2 = t() + // fmt.Println("VAR call:", v1, v2) + + // var i interface{} = 1 + // var iv, ok = i.(int) + // fmt.Println("VAR index:", iv, ok) + + m := map[int]string{1: "test"} + var mv, ok = m[1] + fmt.Println("VAR map:", mv, ok) + + panic("done") - i, x := v.(int), 2 } + +// Error: +// test \ No newline at end of file From 551ab25ef2e1c5d882559b2224463089b88f4993 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 25 Oct 2024 17:28:22 +0700 Subject: [PATCH 05/16] wip: sync the general case --- gnovm/pkg/gnolang/preprocess.go | 132 +++++++++++++++++++------------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 0ada11fe66e..6a2b2b653fd 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1975,12 +1975,19 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { tvs := make([]TypedValue, numNames) if numNames > len(n.Rhs) { + // Special cases: // - `a, b, c T := f()` // - `a, b := n.(T)` // - `a, b := n[i], where n is a map` - parseTypesAndValues(sts, tvs, store, last, nameExprs, nil, n.Rhs[0]) + specialParseTypeVals(sts, tvs, store, last, nameExprs, nil, n.Rhs[0]) defineOrDecl(sts, tvs, last, nameExprs, false) } else { + // General case: a, b := x, y + var nType Expr = nil + var st Type + var valueExprs []*Expr + var isConst bool = true + // General case: a, b := x, y for i, lx := range n.Lhs { ln := lx.(*NameExpr).Name @@ -2296,7 +2303,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // - `var a, b, c T = f()` // - `var a, b = n.(T)` // - `var a, b = n[i], where n is a map` - parseTypesAndValues(sts, tvs, store, last, nameExprs, n.Type, n.Values[0]) + specialParseTypeVals(sts, tvs, store, last, nameExprs, n.Type, n.Values[0]) } else if len(n.Values) != 0 && numNames != len(n.Values) { panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values))) } else { // general case @@ -2308,57 +2315,8 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { } } } - // evaluate types and convert consts. - if n.Type != nil { - // only a single type can be specified. - nt := evalStaticType(store, last, n.Type) - for i := 0; i < numNames; i++ { - sts[i] = nt - } - // convert if const to nt. - for i := range n.Values { - checkOrConvertType(store, last, &n.Values[i], nt, false) - } - } else if n.Const { - // derive static type from values. - for i, vx := range n.Values { - vt := evalStaticTypeOf(store, last, vx) - sts[i] = vt - } - } else { // T is nil, n not const - // convert n.Value to default type. - for i, vx := range n.Values { - if cx, ok := vx.(*ConstExpr); ok { - convertConst(store, last, cx, nil) - // convertIfConst(store, last, vx) - } else { - checkOrConvertType(store, last, &vx, nil, false) - } - vt := evalStaticTypeOf(store, last, vx) - sts[i] = vt - } - } - // evaluate typed value for static definition. - for i := range n.NameExprs { - // consider value if specified. - if len(n.Values) > 0 { - vx := n.Values[i] - if cx, ok := vx.(*ConstExpr); ok && - !cx.TypedValue.IsUndefined() { - if n.Const { - // const _ = : static block should contain value - tvs[i] = cx.TypedValue - } else { - // var _ = : static block should NOT contain value - tvs[i] = anyValue(cx.TypedValue.T) - } - continue - } - } - // for var decls of non-const expr. - st := sts[i] - tvs[i] = anyValue(st) - } + + generalParseTypeVals(sts, tvs, store, last, n.Const, n.NameExprs, n.Type, n.Values) } // define. @@ -2459,6 +2417,72 @@ func defineOrDecl( } } +func generalParseTypeVals( + sts []Type, + tvs []TypedValue, + store Store, + bn BlockNode, + isConst bool, + nameExprs []NameExpr, + typeExpr Expr, + valueExprs []Expr, +) { + numNames := len(nameExprs) + + // evaluate types and convert consts. + if typeExpr != nil { + // only a single type can be specified. + nt := evalStaticType(store, bn, typeExpr) + for i := 0; i < numNames; i++ { + sts[i] = nt + } + // convert if const to nt. + for i := range valueExprs { + checkOrConvertType(store, bn, &valueExprs[i], nt, false) + } + } else if isConst { + // derive static type from values. + for i, vx := range valueExprs { + vt := evalStaticTypeOf(store, bn, vx) + sts[i] = vt + } + } else { // T is nil, n not const => same as AssignStmt DEFINE + // convert n.Value to default type. + for i, vx := range valueExprs { + if cx, ok := vx.(*ConstExpr); ok { + convertConst(store, bn, cx, nil) + // convertIfConst(store, last, vx) + } else { + checkOrConvertType(store, bn, &vx, nil, false) + } + vt := evalStaticTypeOf(store, bn, vx) + sts[i] = vt + } + } + + // evaluate typed value for static definition. + for i := range nameExprs { + // consider value if specified. + if len(valueExprs) > 0 { + vx := valueExprs[i] + if cx, ok := vx.(*ConstExpr); ok && + !cx.TypedValue.IsUndefined() { + if isConst { + // const _ = : static block should contain value + tvs[i] = cx.TypedValue + } else { + // var _ = : static block should NOT contain value + tvs[i] = anyValue(cx.TypedValue.T) + } + continue + } + } + // for var decls of non-const expr. + st := sts[i] + tvs[i] = anyValue(st) + } +} + // This func aims at syncing logic between op define (:=) and declare(var/const) // This will parse Type and TypedValue for these cases // Declare: @@ -2470,7 +2494,7 @@ func defineOrDecl( // - `a, b, c T := f()` // - `a, b := n.(T)` // - `a, b := n[i], where n is a map` -func parseTypesAndValues( +func specialParseTypeVals( sts []Type, tvs []TypedValue, store Store, From d7401c0c63e2fd92417282b55461abcc1e713b54 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 25 Oct 2024 20:00:15 +0700 Subject: [PATCH 06/16] feat: refactor general case code for AssignStmt vs ValueDecl --- gnovm/pkg/gnolang/preprocess.go | 92 +++++++++++++-------------------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 6a2b2b653fd..69e96a0deec 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1965,47 +1965,15 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertIfConst(store, last, rx) } - nameExprs := make([]*NameExpr, len(n.Lhs)) - for i := range n.Lhs { - nameExprs[i] = n.Lhs[i].(*NameExpr) - } - - numNames := len(nameExprs) - sts := make([]Type, numNames) // static types - tvs := make([]TypedValue, numNames) + numNames := len(n.Lhs) + sts, tvs, nameExprs := prepareTypeVals(n, numNames) - if numNames > len(n.Rhs) { - // Special cases: - // - `a, b, c T := f()` - // - `a, b := n.(T)` - // - `a, b := n[i], where n is a map` + if numNames > len(n.Rhs) { // Special cases specialParseTypeVals(sts, tvs, store, last, nameExprs, nil, n.Rhs[0]) - defineOrDecl(sts, tvs, last, nameExprs, false) - } else { - // General case: a, b := x, y - var nType Expr = nil - var st Type - var valueExprs []*Expr - var isConst bool = true - - // General case: a, b := x, y - for i, lx := range n.Lhs { - ln := lx.(*NameExpr).Name - rx := n.Rhs[i] - rt := evalStaticTypeOf(store, last, rx) - // re-definition - if rt == nil { - // e.g. (interface{})(nil), becomes ConstExpr(undefined). - // last.Define(ln, undefined) complains, since redefinition. - } else { - last.Define(ln, anyValue(rt)) - } - // if rhs is untyped - if isUntyped(rt) { - checkOrConvertType(store, last, &n.Rhs[i], nil, false) - } - } + } else { // General case + generalParseTypeVals(sts, tvs, store, last, false, nameExprs, nil, n.Rhs) } + defineOrDecl(sts, tvs, last, nameExprs, false) } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) // NOTE: Keep in sync with DEFINE above. if len(n.Lhs) > len(n.Rhs) { @@ -2290,19 +2258,10 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // runDeclaration(), as this uses OpStaticTypeOf. } - nameExprs := make([]*NameExpr, len(n.NameExprs)) - for i := range n.NameExprs { - nameExprs[i] = &n.NameExprs[i] - } - - numNames := len(nameExprs) - sts := make([]Type, numNames) // static types - tvs := make([]TypedValue, numNames) + numNames := len(n.NameExprs) + sts, tvs, nameExprs := prepareTypeVals(n, numNames) - if numNames > 1 && len(n.Values) == 1 { - // - `var a, b, c T = f()` - // - `var a, b = n.(T)` - // - `var a, b = n[i], where n is a map` + if numNames > 1 && len(n.Values) == 1 { // special cases specialParseTypeVals(sts, tvs, store, last, nameExprs, n.Type, n.Values[0]) } else if len(n.Values) != 0 && numNames != len(n.Values) { panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values))) @@ -2319,7 +2278,6 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { generalParseTypeVals(sts, tvs, store, last, n.Const, n.NameExprs, n.Type, n.Values) } - // define. defineOrDecl(sts, tvs, last, nameExprs, n.Const) // TODO make note of constance in static block for @@ -2398,7 +2356,7 @@ func defineOrDecl( sts []Type, tvs []TypedValue, bn BlockNode, - nameExprs []*NameExpr, + nameExprs []NameExpr, isConst bool, ) { node := bn @@ -2417,6 +2375,31 @@ func defineOrDecl( } } +// Generate the placeholders for types, typeValues, nameExprs for all vars +func prepareTypeVals(n Node, numNames int) ([]Type, []TypedValue, NameExprs) { + if numNames == 0 { + panic("cannot assign to 0 name") + } + + nameExprs := make(NameExprs, numNames) + + switch n := n.(type) { + case *AssignStmt: + for i := range numNames { + nameExprs[i] = *n.Lhs[i].(*NameExpr) + } + case *ValueDecl: + nameExprs = n.NameExprs + } + + sts := make([]Type, numNames) // static types + tvs := make([]TypedValue, numNames) + + return sts, tvs, nameExprs +} + +// This func aims at syncing logic between op define (:=) and declare(var/const) +// for general cases (not handled by specialParseTypeVals) func generalParseTypeVals( sts []Type, tvs []TypedValue, @@ -2484,12 +2467,11 @@ func generalParseTypeVals( } // This func aims at syncing logic between op define (:=) and declare(var/const) -// This will parse Type and TypedValue for these cases +// for special cases where rhs is single and lhs is single/multi // Declare: // - `var a, b, c T = f()` // - `var a, b = n.(T)` // - `var a, b = n[i], where n is a map` -// // Assign // - `a, b, c T := f()` // - `a, b := n.(T)` @@ -2499,7 +2481,7 @@ func specialParseTypeVals( tvs []TypedValue, store Store, bn BlockNode, - nameExprs []*NameExpr, + nameExprs []NameExpr, typeExpr Expr, valueExpr Expr, ) ([]Type, []TypedValue) { From 96f279c4e785d314721093f23e22cabe333f25be Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 25 Oct 2024 23:02:39 +0700 Subject: [PATCH 07/16] chore: remove unused codes + file --- gnovm/pkg/gnolang/preprocess.go | 3 --- gnovm/tests/files/assign30_filetest.gno | 36 ------------------------- 2 files changed, 39 deletions(-) delete mode 100644 gnovm/tests/files/assign30_filetest.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 69e96a0deec..70092dd10eb 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2535,7 +2535,6 @@ func specialParseTypeVals( st = evalStaticType(store, bn, typeExpr) } - // TODO check tt and nt compat. for i := 0; i < numNames; i++ { if st != nil { // TODO check tt and nt compat. @@ -2548,8 +2547,6 @@ func specialParseTypeVals( tvs[i] = anyValue(sts[i]) } - // fmt.Printf("%v - %v", sts, tvs) - return sts, tvs } diff --git a/gnovm/tests/files/assign30_filetest.gno b/gnovm/tests/files/assign30_filetest.gno deleted file mode 100644 index 9704b3b66eb..00000000000 --- a/gnovm/tests/files/assign30_filetest.gno +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import "fmt" - -func t() (int, int) { return 1, 2 } - -func main() { - // v1, v2 := t() - // fmt.Println("DEFINE call:", v1, v2) - - // var i interface{} = 1 - // iv, ok := i.(int) - // fmt.Println("DEFINE index:", iv, ok) - - // m := map[int]string{1: "test"} - // mv, ok := m[1] - // fmt.Println("DEFINE map:", mv, ok) - - - // var v1, v2 = t() - // fmt.Println("VAR call:", v1, v2) - - // var i interface{} = 1 - // var iv, ok = i.(int) - // fmt.Println("VAR index:", iv, ok) - - m := map[int]string{1: "test"} - var mv, ok = m[1] - fmt.Println("VAR map:", mv, ok) - - panic("done") - -} - -// Error: -// test \ No newline at end of file From 81b6be5b64cc49f2e50d2173d66a354b9b1aa16d Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Sat, 26 Oct 2024 08:10:58 -0700 Subject: [PATCH 08/16] chore: optimize numNames check Co-authored-by: Mikael VALLENET --- gnovm/pkg/gnolang/preprocess.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index df7f3898239..b739d789b93 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2373,8 +2373,8 @@ func defineOrDecl( // Generate the placeholders for types, typeValues, nameExprs for all vars func prepareTypeVals(n Node, numNames int) ([]Type, []TypedValue, NameExprs) { - if numNames == 0 { - panic("cannot assign to 0 name") + if numNames < 1 { + panic("must have at least one name to assign") } nameExprs := make(NameExprs, numNames) From 1d95d48db19f18446e45ca5658f5ba64ae947377 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Sat, 26 Oct 2024 22:31:25 +0700 Subject: [PATCH 09/16] chore: add more tests --- gnovm/tests/files/assign30.gno | 15 +++++++++++++++ gnovm/tests/files/assign31.gno | 15 +++++++++++++++ gnovm/tests/files/assign32.gno | 14 ++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 gnovm/tests/files/assign30.gno create mode 100644 gnovm/tests/files/assign31.gno create mode 100644 gnovm/tests/files/assign32.gno diff --git a/gnovm/tests/files/assign30.gno b/gnovm/tests/files/assign30.gno new file mode 100644 index 00000000000..c2a5ec76595 --- /dev/null +++ b/gnovm/tests/files/assign30.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func f() (int) { + return 2 +} + +func main() { + var a, b, c = 1, f(), 3 + fmt.Println(a, b, c) +} + +// Output: +// 1 2 3 diff --git a/gnovm/tests/files/assign31.gno b/gnovm/tests/files/assign31.gno new file mode 100644 index 00000000000..ffbfbe96707 --- /dev/null +++ b/gnovm/tests/files/assign31.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func f() (int) { + return 2 +} + +func main() { + a, b, c := 1, f(), 3 + fmt.Println(a, b, c) +} + +// Output: +// 1 2 3 diff --git a/gnovm/tests/files/assign32.gno b/gnovm/tests/files/assign32.gno new file mode 100644 index 00000000000..868b15d515b --- /dev/null +++ b/gnovm/tests/files/assign32.gno @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func f() (int, int) { + return 1, 2 +} + +func main() { + a, b, c := 1, f(), 3 +} + +// Error: +// main/files/assign32.gno:10:2: evalStaticTypeOf() only supports *CallExpr with 1 result, got (int,int) From edd22877e2e6a38940b1abc03aa437e9bcdf7b51 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Mon, 28 Oct 2024 21:34:17 +0700 Subject: [PATCH 10/16] chore: refactor tests --- gnovm/tests/files/assign30.gno | 4 ++++ gnovm/tests/files/assign31.gno | 15 --------------- 2 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 gnovm/tests/files/assign31.gno diff --git a/gnovm/tests/files/assign30.gno b/gnovm/tests/files/assign30.gno index c2a5ec76595..963a4480b8d 100644 --- a/gnovm/tests/files/assign30.gno +++ b/gnovm/tests/files/assign30.gno @@ -9,7 +9,11 @@ func f() (int) { func main() { var a, b, c = 1, f(), 3 fmt.Println(a, b, c) + + x, y, z := 1, f(), 3 + fmt.Println(x, y, z) } // Output: // 1 2 3 +// 1 2 3 diff --git a/gnovm/tests/files/assign31.gno b/gnovm/tests/files/assign31.gno deleted file mode 100644 index ffbfbe96707..00000000000 --- a/gnovm/tests/files/assign31.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import "fmt" - -func f() (int) { - return 2 -} - -func main() { - a, b, c := 1, f(), 3 - fmt.Println(a, b, c) -} - -// Output: -// 1 2 3 From 5ecc039986904507091bc6712c2779751091de5c Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Mon, 28 Oct 2024 22:23:55 +0700 Subject: [PATCH 11/16] feat: refactor defineOrDecl to raise the same error + add more tests --- gnovm/pkg/gnolang/preprocess.go | 90 ++++++++++++++------------------- gnovm/tests/files/assign31.gno | 14 +++++ gnovm/tests/files/assign32.gno | 14 ----- 3 files changed, 51 insertions(+), 67 deletions(-) create mode 100644 gnovm/tests/files/assign31.gno delete mode 100644 gnovm/tests/files/assign32.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index b739d789b93..4db1188d2a5 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1961,15 +1961,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertIfConst(store, last, rx) } - numNames := len(n.Lhs) - sts, tvs, nameExprs := prepareTypeVals(n, numNames) - - if numNames > len(n.Rhs) { // Special cases - specialParseTypeVals(sts, tvs, store, last, nameExprs, nil, n.Rhs[0]) - } else { // General case - generalParseTypeVals(sts, tvs, store, last, false, nameExprs, nil, n.Rhs) + nameExprs := make(NameExprs, len(n.Lhs)) + for i := range len(n.Lhs) { + nameExprs[i] = *n.Lhs[i].(*NameExpr) } - defineOrDecl(sts, tvs, last, nameExprs, false) + + defineOrDecl(store, last, false, nameExprs, nil, n.Rhs) } else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.) // NOTE: Keep in sync with DEFINE above. if len(n.Lhs) > len(n.Rhs) { @@ -2254,27 +2251,11 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // runDeclaration(), as this uses OpStaticTypeOf. } - numNames := len(n.NameExprs) - sts, tvs, nameExprs := prepareTypeVals(n, numNames) - - if numNames > 1 && len(n.Values) == 1 { // special cases - specialParseTypeVals(sts, tvs, store, last, nameExprs, n.Type, n.Values[0]) - } else if len(n.Values) != 0 && numNames != len(n.Values) { - panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, len(n.Values))) - } else { // general case - for _, v := range n.Values { - if cx, ok := v.(*CallExpr); ok { - tt, ok := evalStaticTypeOfRaw(store, last, cx).(*tupleType) - if ok && len(tt.Elts) != 1 { - panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", cx.Func.String(), tt.Elts)) - } - } - } - - generalParseTypeVals(sts, tvs, store, last, n.Const, n.NameExprs, n.Type, n.Values) + if len(n.Values) != 0 && len(n.NameExprs) != len(n.Values) { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", len(n.NameExprs), len(n.Values))) } - defineOrDecl(sts, tvs, last, nameExprs, n.Const) + defineOrDecl(store, last, n.Const, n.NameExprs, n.Type, n.Values) // TODO make note of constance in static block for // future use, or consider "const paths". set as @@ -2349,12 +2330,28 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // This func aims at syncing logic between op define (:=) and declare(var/const) // This will define or declare the variables func defineOrDecl( - sts []Type, - tvs []TypedValue, + store Store, bn BlockNode, - nameExprs []NameExpr, isConst bool, + nameExprs []NameExpr, + typeExpr Expr, + valueExprs []Expr, ) { + numNames := len(nameExprs) + + if numNames < 1 { + panic("must have at least one name to assign") + } + + sts := make([]Type, numNames) // static types + tvs := make([]TypedValue, numNames) + + if numNames > 1 && len(valueExprs) == 1 { // special cases + specialParseTypeVals(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) + } else { // general case + generalParseTypeVals(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) + } + node := bn if fn, ok := bn.(*FileNode); ok { node = fn.GetParentNode(nil).(*PackageNode) @@ -2371,29 +2368,6 @@ func defineOrDecl( } } -// Generate the placeholders for types, typeValues, nameExprs for all vars -func prepareTypeVals(n Node, numNames int) ([]Type, []TypedValue, NameExprs) { - if numNames < 1 { - panic("must have at least one name to assign") - } - - nameExprs := make(NameExprs, numNames) - - switch n := n.(type) { - case *AssignStmt: - for i := range numNames { - nameExprs[i] = *n.Lhs[i].(*NameExpr) - } - case *ValueDecl: - nameExprs = n.NameExprs - } - - sts := make([]Type, numNames) // static types - tvs := make([]TypedValue, numNames) - - return sts, tvs, nameExprs -} - // This func aims at syncing logic between op define (:=) and declare(var/const) // for general cases (not handled by specialParseTypeVals) func generalParseTypeVals( @@ -2408,6 +2382,16 @@ func generalParseTypeVals( ) { numNames := len(nameExprs) + // ensure that function only return 1 value + for _, v := range valueExprs { + if cx, ok := v.(*CallExpr); ok { + tt, ok := evalStaticTypeOfRaw(store, bn, cx).(*tupleType) + if ok && len(tt.Elts) != 1 { + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", cx.Func.String(), tt.Elts)) + } + } + } + // evaluate types and convert consts. if typeExpr != nil { // only a single type can be specified. diff --git a/gnovm/tests/files/assign31.gno b/gnovm/tests/files/assign31.gno new file mode 100644 index 00000000000..42a88b7e571 --- /dev/null +++ b/gnovm/tests/files/assign31.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + a, b := 2, foo() + + println(a, b) +} + +// Error: +// main/files/assign31.gno:8:6: multiple-value foo (value of type [int bool]) in single-value context diff --git a/gnovm/tests/files/assign32.gno b/gnovm/tests/files/assign32.gno deleted file mode 100644 index 868b15d515b..00000000000 --- a/gnovm/tests/files/assign32.gno +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import "fmt" - -func f() (int, int) { - return 1, 2 -} - -func main() { - a, b, c := 1, f(), 3 -} - -// Error: -// main/files/assign32.gno:10:2: evalStaticTypeOf() only supports *CallExpr with 1 result, got (int,int) From 0e80d12b23fa13dc3dafb222152f2f19eacc562e Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Mon, 28 Oct 2024 22:53:08 +0700 Subject: [PATCH 12/16] fix: fix tests --- gnovm/pkg/gnolang/preprocess.go | 2 +- gnovm/tests/files/assign31.gno | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 4db1188d2a5..60bb704c0f6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2251,7 +2251,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // runDeclaration(), as this uses OpStaticTypeOf. } - if len(n.Values) != 0 && len(n.NameExprs) != len(n.Values) { + if len(n.Values) != 1 && len(n.Values) != 0 && len(n.NameExprs) != len(n.Values) { panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", len(n.NameExprs), len(n.Values))) } diff --git a/gnovm/tests/files/assign31.gno b/gnovm/tests/files/assign31.gno index 42a88b7e571..0b6205200a5 100644 --- a/gnovm/tests/files/assign31.gno +++ b/gnovm/tests/files/assign31.gno @@ -11,4 +11,4 @@ func main() { } // Error: -// main/files/assign31.gno:8:6: multiple-value foo (value of type [int bool]) in single-value context +// main/files/assign31.gno:8:2: multiple-value foo (value of type [int bool]) in single-value context From f3e021fd912fdf241fcf1ad67b5858357ac6085d Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 30 Oct 2024 07:49:52 +0700 Subject: [PATCH 13/16] chore: optimize code --- gnovm/pkg/gnolang/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 60bb704c0f6..95ed7dcdb46 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2251,7 +2251,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // runDeclaration(), as this uses OpStaticTypeOf. } - if len(n.Values) != 1 && len(n.Values) != 0 && len(n.NameExprs) != len(n.Values) { + if len(n.Values) > 1 && len(n.NameExprs) != len(n.Values) { panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", len(n.NameExprs), len(n.Values))) } From 234e028ea15c05774c7403737317ed9272799d12 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Wed, 6 Nov 2024 15:42:32 +0700 Subject: [PATCH 14/16] fix: addresses all comments + add tests --- gnovm/pkg/gnolang/preprocess.go | 52 ++++++++++++++++----------------- gnovm/tests/files/assign25c.gno | 15 ++++++++++ gnovm/tests/files/assign25d.gno | 15 ++++++++++ gnovm/tests/files/assign32.gno | 22 ++++++++++++++ gnovm/tests/files/var22c.gno | 15 ++++++++++ 5 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 gnovm/tests/files/assign25c.gno create mode 100644 gnovm/tests/files/assign25d.gno create mode 100644 gnovm/tests/files/assign32.gno create mode 100644 gnovm/tests/files/var22c.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 971cbceef83..5537da2ba74 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2335,8 +2335,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { return nn } -// This func aims at syncing logic between op define (:=) and declare(var/const) -// This will define or declare the variables +// defineOrDecl merges the code logic from op define (:=) and declare (var/const). func defineOrDecl( store Store, bn BlockNode, @@ -2355,9 +2354,9 @@ func defineOrDecl( tvs := make([]TypedValue, numNames) if numNames > 1 && len(valueExprs) == 1 { // special cases - specialParseTypeVals(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) + parseMultipleAssignFromOneExpr(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) } else { // general case - generalParseTypeVals(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) + parseAssignFromExprList(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) } node := bn @@ -2376,9 +2375,8 @@ func defineOrDecl( } } -// This func aims at syncing logic between op define (:=) and declare(var/const) -// for general cases (not handled by specialParseTypeVals) -func generalParseTypeVals( +// parseAssignFromExprList parses assignment to multiple variables from a list of expressions. +func parseAssignFromExprList( sts []Type, tvs []TypedValue, store Store, @@ -2390,7 +2388,7 @@ func generalParseTypeVals( ) { numNames := len(nameExprs) - // ensure that function only return 1 value + // Ensure that function only return 1 value. for _, v := range valueExprs { if cx, ok := v.(*CallExpr); ok { tt, ok := evalStaticTypeOfRaw(store, bn, cx).(*tupleType) @@ -2400,25 +2398,25 @@ func generalParseTypeVals( } } - // evaluate types and convert consts. + // Evaluate types and convert consts. if typeExpr != nil { - // only a single type can be specified. + // Only a single type can be specified. nt := evalStaticType(store, bn, typeExpr) for i := 0; i < numNames; i++ { sts[i] = nt } - // convert if const to nt. + // Convert if const to nt. for i := range valueExprs { checkOrConvertType(store, bn, &valueExprs[i], nt, false) } } else if isConst { - // derive static type from values. + // Derive static type from values. for i, vx := range valueExprs { vt := evalStaticTypeOf(store, bn, vx) sts[i] = vt } } else { // T is nil, n not const => same as AssignStmt DEFINE - // convert n.Value to default type. + // Convert n.Value to default type. for i, vx := range valueExprs { if cx, ok := vx.(*ConstExpr); ok { convertConst(store, bn, cx, nil) @@ -2454,17 +2452,16 @@ func generalParseTypeVals( } } -// This func aims at syncing logic between op define (:=) and declare(var/const) -// for special cases where rhs is single and lhs is single/multi +// parseMultipleAssignFromOneExpr parses assignment to multiple variables from a single expression. // Declare: -// - `var a, b, c T = f()` -// - `var a, b = n.(T)` -// - `var a, b = n[i], where n is a map` -// Assign -// - `a, b, c T := f()` -// - `a, b := n.(T)` -// - `a, b := n[i], where n is a map` -func specialParseTypeVals( +// - var a, b, c T = f() +// - var a, b = n.(T) +// - var a, b = n[i], where n is a map +// Assign: +// - a, b, c := f() +// - a, b := n.(T) +// - a, b := n[i], where n is a map +func parseMultipleAssignFromOneExpr( sts []Type, tvs []TypedValue, store Store, @@ -2475,7 +2472,6 @@ func specialParseTypeVals( ) ([]Type, []TypedValue) { var tuple *tupleType numNames := len(nameExprs) - switch expr := valueExpr.(type) { case *CallExpr: // Call case: @@ -2519,16 +2515,18 @@ func specialParseTypeVals( var st Type = nil if typeExpr != nil { - // only a single type can be specified. + // Only a single type can be specified. st = evalStaticType(store, bn, typeExpr) } for i := 0; i < numNames; i++ { if st != nil { - // TODO check tt and nt compat. sts[i] = st + + // Convert if const to nt. + checkOrConvertType(store, bn, &valueExpr, st, false) } else { - // set types as return types + // Set types as return types. sts[i] = tuple.Elts[i] } diff --git a/gnovm/tests/files/assign25c.gno b/gnovm/tests/files/assign25c.gno new file mode 100644 index 00000000000..29e32602365 --- /dev/null +++ b/gnovm/tests/files/assign25c.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func f() (a, b int) { + return 1, 2 +} + +func main() { + x, y, z := 1, bar() + fmt.Println(x, y, z) +} + +// Error: +// main/files/assign25c.gno:10:2: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/assign25d.gno b/gnovm/tests/files/assign25d.gno new file mode 100644 index 00000000000..18aab7282f8 --- /dev/null +++ b/gnovm/tests/files/assign25d.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func f() (a, b int) { + return 1, 2 +} + +func main() { + x, y, z := bar(), 1, 2, 3 + fmt.Println(x, y, z) +} + +// Error: +// main/files/assign25d.gno:10:2: assignment mismatch: 3 variable(s) but 4 value(s) diff --git a/gnovm/tests/files/assign32.gno b/gnovm/tests/files/assign32.gno new file mode 100644 index 00000000000..094b08cc713 --- /dev/null +++ b/gnovm/tests/files/assign32.gno @@ -0,0 +1,22 @@ +package main + +func foo() int { + return 2 +} + +func main() { + var mp map[string]int = map[string]int{"idx": 4} + var sl []int = []int{4, 5, 6} + arr := [1]int{7} + var num interface{} = 5 + + a, b, c, d, e, f, g := int(1), foo(), 3, mp["idx"], num.(int), sl[2], arr[0] + println(a, b, c, d, e, f, g) + + var h, i, j, k, l, m, n int = int(1), foo(), 3, mp["idx"], num.(int), sl[2], arr[0] + println(h, i, j, k, l, m, n) +} + +// Output: +// 1 2 3 4 5 6 7 +// 1 2 3 4 5 6 7 diff --git a/gnovm/tests/files/var22c.gno b/gnovm/tests/files/var22c.gno new file mode 100644 index 00000000000..0723dfa279e --- /dev/null +++ b/gnovm/tests/files/var22c.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func f() (a, b int) { + return 1, 2 +} + +func main() { + var x, y, z = 1, f() + fmt.Println(x, y, z) +} + +// Error: +// main/files/var22c.gno:10:6: missing init expr for z \ No newline at end of file From 41450831b86c440043187e0f91d04ac1eccada6e Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Wed, 6 Nov 2024 16:14:48 +0700 Subject: [PATCH 15/16] fix: check compat with given type vs tuple type --- gnovm/pkg/gnolang/preprocess.go | 15 ++++++++++++--- gnovm/tests/files/assign33.gno | 14 ++++++++++++++ gnovm/tests/files/assign34.gno | 14 ++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 gnovm/tests/files/assign33.gno create mode 100644 gnovm/tests/files/assign34.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 5537da2ba74..34cc005a3a5 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2521,10 +2521,19 @@ func parseMultipleAssignFromOneExpr( for i := 0; i < numNames; i++ { if st != nil { - sts[i] = st + tt := tuple.Elts[i] + if tt.String() != st.String() { + panic( + fmt.Sprintf( + "cannot use %v (value of type %s) as %s value in assignment", + valueExpr.String(), + tt.String(), + st.String(), + ), + ) + } - // Convert if const to nt. - checkOrConvertType(store, bn, &valueExpr, st, false) + sts[i] = st } else { // Set types as return types. sts[i] = tuple.Elts[i] diff --git a/gnovm/tests/files/assign33.gno b/gnovm/tests/files/assign33.gno new file mode 100644 index 00000000000..35d8275f828 --- /dev/null +++ b/gnovm/tests/files/assign33.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b int = foo() + println(a, b) +} + +// Error: +// main/files/assign33.gno:8:6: cannot use foo() (value of type bool) as int value in assignment + diff --git a/gnovm/tests/files/assign34.gno b/gnovm/tests/files/assign34.gno new file mode 100644 index 00000000000..a289c602028 --- /dev/null +++ b/gnovm/tests/files/assign34.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b = foo() + println(a, b) +} + +// Output: +// 1 true + From daf976626982436fb9e1d61210c3f144807f02d1 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Wed, 6 Nov 2024 16:51:25 +0700 Subject: [PATCH 16/16] fix: fix type compare using kind to cover custom type --- gnovm/pkg/gnolang/preprocess.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 34cc005a3a5..a5e438a14cb 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2522,7 +2522,8 @@ func parseMultipleAssignFromOneExpr( for i := 0; i < numNames; i++ { if st != nil { tt := tuple.Elts[i] - if tt.String() != st.String() { + + if tt.Kind() != st.Kind() { panic( fmt.Sprintf( "cannot use %v (value of type %s) as %s value in assignment",