diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 78e4488b2a0..612f1989229 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1970,60 +1970,12 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { convertIfConst(store, last, rx) } - 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") - } - } else { - // 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) - } - } + nameExprs := make(NameExprs, len(n.Lhs)) + for i := range len(n.Lhs) { + nameExprs[i] = *n.Lhs[i].(*NameExpr) } + + 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) { @@ -2309,147 +2261,9 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // the implementation differ from // runDeclaration(), as this uses OpStaticTypeOf. } - numNames := len(n.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, - ), - ) - } + defineOrDecl(store, last, n.Const, n.NameExprs, n.Type, n.Values) - 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) - } - } - } 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)) - } - } - } - // 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) - } - } - // define. - 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) - } - } - } // TODO make note of constance in static block for // future use, or consider "const paths". set as // preprocessed. @@ -2520,6 +2334,219 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { return nn } +// defineOrDecl merges the code logic from op define (:=) and declare (var/const). +func defineOrDecl( + store Store, + bn BlockNode, + isConst bool, + nameExprs []NameExpr, + typeExpr Expr, + valueExprs []Expr, +) { + numNames := len(nameExprs) + numVals := len(valueExprs) + + if numVals > 1 && numNames != numVals { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numVals)) + } + + 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 { + parseMultipleAssignFromOneExpr(sts, tvs, store, bn, nameExprs, typeExpr, valueExprs[0]) + } else { + parseAssignFromExprList(sts, tvs, store, bn, isConst, nameExprs, typeExpr, valueExprs) + } + + node := skipFile(bn) + + 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) + } + } +} + +// parseAssignFromExprList parses assignment to multiple variables from a list of expressions. +func parseAssignFromExprList( + sts []Type, + tvs []TypedValue, + store Store, + bn BlockNode, + isConst bool, + nameExprs []NameExpr, + typeExpr Expr, + valueExprs []Expr, +) { + 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. + 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) + } +} + +// 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 := f() +// - a, b := n.(T) +// - a, b := n[i], where n is a map +func parseMultipleAssignFromOneExpr( + sts []Type, + tvs []TypedValue, + store Store, + bn BlockNode, + nameExprs []NameExpr, + typeExpr Expr, + valueExpr Expr, +) ([]Type, []TypedValue) { + var tuple *tupleType + numNames := len(nameExprs) + switch expr := valueExpr.(type) { + case *CallExpr: + // 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) + 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 + 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)) + } + + 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) + } + + for i := 0; i < numNames; i++ { + if st != nil { + tt := tuple.Elts[i] + + if checkAssignableTo(tt, st, false) != nil { + panic( + fmt.Sprintf( + "cannot use %v (value of type %s) as %s value in assignment", + valueExpr.String(), + tt.String(), + st.String(), + ), + ) + } + + sts[i] = st + } else { + // Set types as return types. + sts[i] = tuple.Elts[i] + } + + tvs[i] = anyValue(sts[i]) + } + + 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/assign25c.gno b/gnovm/tests/files/assign25c.gno new file mode 100644 index 00000000000..6fe9415787b --- /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, f() + 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..793369c3d78 --- /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 := f(), 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/assign33.gno b/gnovm/tests/files/assign33.gno new file mode 100644 index 00000000000..29b6be8d1f8 --- /dev/null +++ b/gnovm/tests/files/assign33.gno @@ -0,0 +1,13 @@ +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 + diff --git a/gnovm/tests/files/assign35.gno b/gnovm/tests/files/assign35.gno new file mode 100644 index 00000000000..53f7eb367f5 --- /dev/null +++ b/gnovm/tests/files/assign35.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/assign35.gno:8:2: multiple-value foo (value of type [int bool]) in single-value context diff --git a/gnovm/tests/files/assign36.gno b/gnovm/tests/files/assign36.gno new file mode 100644 index 00000000000..963a4480b8d --- /dev/null +++ b/gnovm/tests/files/assign36.gno @@ -0,0 +1,19 @@ +package main + +import "fmt" + +func f() (int) { + return 2 +} + +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/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