From 297dd6de683ed94c48d0e5fb41a5002fa294eb48 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 23 Oct 2024 21:15:13 +0700 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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 2460630b5fb9b3d15441e1da2d49475b03811f34 Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Wed, 30 Oct 2024 20:58:39 +0700 Subject: [PATCH 14/20] fix: return the same error as go in case of assignment with no returned value function --- gnovm/pkg/gnolang/preprocess.go | 8 ++++++-- gnovm/tests/files/assign32.gno | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 gnovm/tests/files/assign32.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 95ed7dcdb46..e496bf1c2cd 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2386,8 +2386,12 @@ func generalParseTypeVals( 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)) + if ok { + if len(tt.Elts) == 0 { + panic(fmt.Sprintf("%s() (no value) used as value", cx.Func.String())) + } else if len(tt.Elts) != 1 { + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", cx.Func.String(), tt.Elts)) + } } } } diff --git a/gnovm/tests/files/assign32.gno b/gnovm/tests/files/assign32.gno new file mode 100644 index 00000000000..61e32e26c90 --- /dev/null +++ b/gnovm/tests/files/assign32.gno @@ -0,0 +1,10 @@ +package main + +func foo() {} + +func main() { + x := foo() +} + +// Error: +// main/files/assign32.gno:6:2: foo() (no value) used as value From af393687bd463d57871e8a1f1339fb6db386761d Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Thu, 21 Nov 2024 22:36:02 +0700 Subject: [PATCH 15/20] chore: refactor code to more readable version --- 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 b7c22e0b9f6..68c4c8a35b2 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2373,7 +2373,7 @@ func defineOrDecl( sts := make([]Type, numNames) // static types tvs := make([]TypedValue, numNames) - if numNames > 1 && len(valueExprs) == 1 { + if numVals == 1 && numNames > 1 { parseMultipleAssignFromOneExpr(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) } else { parseAssignFromExprList(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) From 5ee3df2a914f084fba96528461679c907e9cd90f Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 22 Nov 2024 15:14:56 +0700 Subject: [PATCH 16/20] feat: handle error when f() not return value like in Golang --- gnovm/pkg/gnolang/type_check.go | 8 ++++++++ gnovm/tests/files/assign37.gno | 12 ++++++++++++ gnovm/tests/files/assign37b.gno | 12 ++++++++++++ gnovm/tests/files/var34.gno | 10 ++++++++++ gnovm/tests/files/var34b.gno | 11 +++++++++++ gnovm/tests/files/var34c.gno | 10 ++++++++++ 6 files changed, 63 insertions(+) create mode 100644 gnovm/tests/files/assign37.gno create mode 100644 gnovm/tests/files/assign37b.gno create mode 100644 gnovm/tests/files/var34.gno create mode 100644 gnovm/tests/files/var34b.gno create mode 100644 gnovm/tests/files/var34c.gno diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index c62d67375ee..e786bed683f 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -1029,6 +1029,14 @@ func assertValidAssignRhs(store Store, last BlockNode, n Node) { tt = evalStaticType(store, last, exp) panic(fmt.Sprintf("%s (type) is not an expression", tt.String())) } + + // Ensures that function used in ValueDecl or AssignStmt must return at least 1 value. + if cx, ok := exp.(*CallExpr); ok { + tType, ok := tt.(*tupleType) + if ok && len(tType.Elts) == 0 { + panic(fmt.Sprintf("%s (no value) used as value", cx.Func.String())) + } + } } } diff --git a/gnovm/tests/files/assign37.gno b/gnovm/tests/files/assign37.gno new file mode 100644 index 00000000000..6d3512c5f2c --- /dev/null +++ b/gnovm/tests/files/assign37.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func f() { } + +func main() { + a := f() +} + +// Error: +// main/files/assign37.gno:8:2: f (no value) used as value diff --git a/gnovm/tests/files/assign37b.gno b/gnovm/tests/files/assign37b.gno new file mode 100644 index 00000000000..42e3dbe6e1d --- /dev/null +++ b/gnovm/tests/files/assign37b.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func f() { } + +func main() { + a, b := f(), f() +} + +// Error: +// main/files/assign37b.gno:8:2: f (no value) used as value diff --git a/gnovm/tests/files/var34.gno b/gnovm/tests/files/var34.gno new file mode 100644 index 00000000000..4f1dff6183f --- /dev/null +++ b/gnovm/tests/files/var34.gno @@ -0,0 +1,10 @@ +package main + +func f() {} + +func main() { + var t = f() +} + +// Error: +// main/files/var34.gno:6:6: f (no value) used as value diff --git a/gnovm/tests/files/var34b.gno b/gnovm/tests/files/var34b.gno new file mode 100644 index 00000000000..21e8a89bb14 --- /dev/null +++ b/gnovm/tests/files/var34b.gno @@ -0,0 +1,11 @@ +package main + +func f() {} + +func main() { + var a int + a = f() +} + +// Error: +// main/files/var34b.gno:7:2: f (no value) used as value diff --git a/gnovm/tests/files/var34c.gno b/gnovm/tests/files/var34c.gno new file mode 100644 index 00000000000..de53fcc1f28 --- /dev/null +++ b/gnovm/tests/files/var34c.gno @@ -0,0 +1,10 @@ +package main + +func f() {} + +func main() { + var a, b int = f(), 1 +} + +// Error: +// main/files/var34b.gno:7:2: f (no value) used as value From 56505b42fb5324ed9d06c9e11f903143d5050838 Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 22 Nov 2024 15:59:07 +0700 Subject: [PATCH 17/20] chore: make the check more explicit --- 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 b7c22e0b9f6..c5b81ce1d20 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2410,7 +2410,7 @@ func parseAssignFromExprList( for _, v := range valueExprs { if cx, ok := v.(*CallExpr); ok { tt, ok := evalStaticTypeOfRaw(store, bn, cx).(*tupleType) - if ok && len(tt.Elts) != 1 { + 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)) } } From 6edd434a1a544c65635768f4245799252a683ccf Mon Sep 17 00:00:00 2001 From: "hieu.ha" Date: Fri, 22 Nov 2024 16:13:40 +0700 Subject: [PATCH 18/20] fix: fix test var34c --- gnovm/tests/files/var34c.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/tests/files/var34c.gno b/gnovm/tests/files/var34c.gno index de53fcc1f28..18ab996eefd 100644 --- a/gnovm/tests/files/var34c.gno +++ b/gnovm/tests/files/var34c.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/var34b.gno:7:2: f (no value) used as value +// main/files/var34c.gno:6:6: f (no value) used as value From ce5717a83ce62969ba90d97b5398a9223d6db98b Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 22 Nov 2024 21:34:18 +0700 Subject: [PATCH 19/20] fix: refactor --- gnovm/pkg/gnolang/preprocess.go | 9 ++++++--- gnovm/pkg/gnolang/type_check.go | 8 -------- gnovm/tests/files/assign37.gno | 4 +--- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 4724f5f6330..c2d997d3b78 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3133,13 +3133,16 @@ func gnoTypeOf(store Store, t Type) Type { func evalStaticTypeOf(store Store, last BlockNode, x Expr) Type { t := evalStaticTypeOfRaw(store, last, x) if tt, ok := t.(*tupleType); ok { - if len(tt.Elts) != 1 { + switch len(tt.Elts) { + case 1: + return tt.Elts[0] + case 0: + panic(fmt.Sprintf("%s (no value) used as value", x.(*CallExpr).Func.String())) + default: panic(fmt.Sprintf( "evalStaticTypeOf() only supports *CallExpr with 1 result, got %s", tt.String(), )) - } else { - return tt.Elts[0] } } else { return t diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index e786bed683f..c62d67375ee 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -1029,14 +1029,6 @@ func assertValidAssignRhs(store Store, last BlockNode, n Node) { tt = evalStaticType(store, last, exp) panic(fmt.Sprintf("%s (type) is not an expression", tt.String())) } - - // Ensures that function used in ValueDecl or AssignStmt must return at least 1 value. - if cx, ok := exp.(*CallExpr); ok { - tType, ok := tt.(*tupleType) - if ok && len(tType.Elts) == 0 { - panic(fmt.Sprintf("%s (no value) used as value", cx.Func.String())) - } - } } } diff --git a/gnovm/tests/files/assign37.gno b/gnovm/tests/files/assign37.gno index 6d3512c5f2c..d96aab42070 100644 --- a/gnovm/tests/files/assign37.gno +++ b/gnovm/tests/files/assign37.gno @@ -1,7 +1,5 @@ package main -import "fmt" - func f() { } func main() { @@ -9,4 +7,4 @@ func main() { } // Error: -// main/files/assign37.gno:8:2: f (no value) used as value +// main/files/assign37.gno:6:2: f (no value) used as value From fc2b59ea6510b9aa196c7765c3c6563f14f5deae Mon Sep 17 00:00:00 2001 From: hthieu1110 Date: Fri, 22 Nov 2024 22:23:52 +0700 Subject: [PATCH 20/20] fix: revert and adjust version before refactor --- gnovm/pkg/gnolang/preprocess.go | 19 +++++-------------- gnovm/pkg/gnolang/type_check.go | 8 ++++++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index c2d997d3b78..05956ebd153 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3132,21 +3132,12 @@ func gnoTypeOf(store Store, t Type) Type { // but rather computes the type OF x. func evalStaticTypeOf(store Store, last BlockNode, x Expr) Type { t := evalStaticTypeOfRaw(store, last, x) - if tt, ok := t.(*tupleType); ok { - switch len(tt.Elts) { - case 1: - return tt.Elts[0] - case 0: - panic(fmt.Sprintf("%s (no value) used as value", x.(*CallExpr).Func.String())) - default: - panic(fmt.Sprintf( - "evalStaticTypeOf() only supports *CallExpr with 1 result, got %s", - tt.String(), - )) - } - } else { - return t + + if tt, ok := t.(*tupleType); ok && len(tt.Elts) == 1 { + return tt.Elts[0] } + + return t } // like evalStaticTypeOf() but returns the raw *tupleType for *CallExpr. diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index c62d67375ee..e786bed683f 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -1029,6 +1029,14 @@ func assertValidAssignRhs(store Store, last BlockNode, n Node) { tt = evalStaticType(store, last, exp) panic(fmt.Sprintf("%s (type) is not an expression", tt.String())) } + + // Ensures that function used in ValueDecl or AssignStmt must return at least 1 value. + if cx, ok := exp.(*CallExpr); ok { + tType, ok := tt.(*tupleType) + if ok && len(tType.Elts) == 0 { + panic(fmt.Sprintf("%s (no value) used as value", cx.Func.String())) + } + } } }