diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index d21e9bf0efd..5a710723b86 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -427,6 +427,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { switch n := n.(type) { // TRANS_ENTER ----------------------- case *AssignStmt: + checkValDefineMismatch(n) + if n.Op == DEFINE { for _, lx := range n.Lhs { ln := lx.(*NameExpr).Name @@ -445,7 +447,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // nothing defined. } - // TRANS_ENTER ----------------------- case *ImportDecl, *ValueDecl, *TypeDecl, *FuncDecl: // NOTE func decl usually must happen with a @@ -457,8 +458,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // skip declarations already predefined // (e.g. through recursion for a dependent) } else { + d := n.(Decl) + if cd, ok := d.(*ValueDecl); ok { + checkValDefineMismatch(cd) + } // recursively predefine dependencies. - d2, ppd := predefineNow(store, last, n.(Decl)) + d2, ppd := predefineNow(store, last, d) if ppd { return d2, TRANS_SKIP } else { @@ -2162,8 +2167,8 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // special case if `var a, b, c T? = f()` form. cx := n.Values[0].(*CallExpr) tt := evalStaticTypeOfRaw(store, last, cx).(*tupleType) - if len(tt.Elts) != numNames { - panic("should not happen") + if rLen := len(tt.Elts); rLen != numNames { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %s returns %d value(s)", numNames, cx.Func.String(), rLen)) } if n.Type != nil { // only a single type can be specified. @@ -2182,8 +2187,16 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } else if len(n.Values) != 0 && numNames != len(n.Values) { - panic("should not happen") + 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. diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 3f25667f353..31025fef152 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,70 @@ func assertAssignableTo(xt, dt Type, autoNative bool) { } } +// checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. +func checkValDefineMismatch(n Node) { + var ( + valueDecl *ValueDecl + assign *AssignStmt + values []Expr + numNames int + numValues int + ) + + switch x := n.(type) { + case *ValueDecl: + valueDecl = x + numNames = len(valueDecl.NameExprs) + numValues = len(valueDecl.Values) + values = valueDecl.Values + case *AssignStmt: + if x.Op != DEFINE { + return + } + + assign = x + numNames = len(assign.Lhs) + numValues = len(assign.Rhs) + values = assign.Rhs + default: + panic(fmt.Sprintf("unexpected node type %T", n)) + } + + if numValues == 0 || numValues == numNames { + return + } + + // Special case for single value. + // If the value is a call expression, type assertion, or index expression, + // it can be assigned to multiple variables. + if numValues == 1 { + switch values[0].(type) { + case *CallExpr: + return + case *TypeAssertExpr: + if numNames != 2 { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) + } + return + case *IndexExpr: + if numNames != 2 { + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) + } + return + } + } + + if valueDecl != nil { + if numNames > numValues { + panic(fmt.Sprintf("missing init expr for %s", valueDecl.NameExprs[numValues].String())) + } + + panic(fmt.Sprintf("extra init expr %s", values[numNames].String())) + } + + panic(fmt.Sprintf("assignment mismatch: %d variable(s) but %d value(s)", numNames, numValues)) +} + // Assert that xt can be assigned as dt (dest type). // If autoNative is true, a broad range of xt can match against // a target native dt type, if and only if dt is a native type. diff --git a/gnovm/tests/files/assign24.gno b/gnovm/tests/files/assign24.gno new file mode 100644 index 00000000000..408258def92 --- /dev/null +++ b/gnovm/tests/files/assign24.gno @@ -0,0 +1,8 @@ +package main + +func main() { + a, b := 1 +} + +// Error: +// main/files/assign24.gno:4:2: assignment mismatch: 2 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign25.gno b/gnovm/tests/files/assign25.gno new file mode 100644 index 00000000000..b945afc3b1f --- /dev/null +++ b/gnovm/tests/files/assign25.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + a, b, c := 2, foo() + + println(a, b, c) +} + +// Error: +// main/files/assign25.gno:8:2: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/assign25b.gno b/gnovm/tests/files/assign25b.gno new file mode 100644 index 00000000000..886777324f5 --- /dev/null +++ b/gnovm/tests/files/assign25b.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + a, b, c := 2, 3, 4, foo() + + println(a, b, c) +} + +// Error: +// main/files/assign25b.gno:8:2: assignment mismatch: 3 variable(s) but 4 value(s) diff --git a/gnovm/tests/files/assign26.gno b/gnovm/tests/files/assign26.gno new file mode 100644 index 00000000000..f974ae0b174 --- /dev/null +++ b/gnovm/tests/files/assign26.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i interface{} = 1 + a, b, c := i.(int) +} + +// Error: +// main/files/assign26.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign27.gno b/gnovm/tests/files/assign27.gno new file mode 100644 index 00000000000..14fc8bf4e37 --- /dev/null +++ b/gnovm/tests/files/assign27.gno @@ -0,0 +1,9 @@ +package main + +func main() { + s := []string{"1", "2"} + a, b, c := s[0] +} + +// Error: +// main/files/assign27.gno:5:2: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/assign28.gno b/gnovm/tests/files/assign28.gno new file mode 100644 index 00000000000..a0afe4f0e36 --- /dev/null +++ b/gnovm/tests/files/assign28.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + a, c := 1, 2, 3 + fmt.Println(a, c) +} + +// Error: +// main/files/assign28.gno:6:2: assignment mismatch: 2 variable(s) but 3 value(s) diff --git a/gnovm/tests/files/var18.gno b/gnovm/tests/files/var18.gno index 771ddcdffb5..f01d3871d39 100644 --- a/gnovm/tests/files/var18.gno +++ b/gnovm/tests/files/var18.gno @@ -1,8 +1,8 @@ package main func main() { - a, b, c := 1, 2 + var a, b, c = 1, 2 } // Error: -// main/files/var18.gno:4:2: assignment mismatch: 3 variables but 2 values +// main/files/var18.gno:4:6: missing init expr for c diff --git a/gnovm/tests/files/var19.gno b/gnovm/tests/files/var19.gno new file mode 100644 index 00000000000..cbdce802e0a --- /dev/null +++ b/gnovm/tests/files/var19.gno @@ -0,0 +1,11 @@ +package main + +func main() { + var a, b, c = 1, a+1 + println(a) + println(b) + println(c) +} + +// Error: +// main/files/var19.gno:4:6: missing init expr for c diff --git a/gnovm/tests/files/var20.gno b/gnovm/tests/files/var20.gno new file mode 100644 index 00000000000..e2455cbaed8 --- /dev/null +++ b/gnovm/tests/files/var20.gno @@ -0,0 +1,12 @@ +package main + +func r() int { + return 1 +} + +func main() { + var a, b, c = r() +} + +// Error: +// main/files/var20.gno:8:6: assignment mismatch: 3 variable(s) but r returns 1 value(s) diff --git a/gnovm/tests/files/var21.gno b/gnovm/tests/files/var21.gno new file mode 100644 index 00000000000..b593984aa87 --- /dev/null +++ b/gnovm/tests/files/var21.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b = 2, foo() + + println(a, b) +} + +// Error: +// main/files/var21.gno:8:6: multiple-value foo (value of type [int bool]) in single-value context diff --git a/gnovm/tests/files/var22.gno b/gnovm/tests/files/var22.gno new file mode 100644 index 00000000000..3f85f0f156d --- /dev/null +++ b/gnovm/tests/files/var22.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b, c = 2, foo() + + println(a, b, c) +} + +// Error: +// main/files/var22.gno:8:6: missing init expr for c diff --git a/gnovm/tests/files/var22b.gno b/gnovm/tests/files/var22b.gno new file mode 100644 index 00000000000..22a52fcc811 --- /dev/null +++ b/gnovm/tests/files/var22b.gno @@ -0,0 +1,14 @@ +package main + +func foo() (int, bool) { + return 1, true +} + +func main() { + var a, b, c = 2, 3, 4, foo() + + println(a, b, c) +} + +// Error: +// main/files/var22b.gno:8:6: extra init expr foo() diff --git a/gnovm/tests/files/var23.gno b/gnovm/tests/files/var23.gno new file mode 100644 index 00000000000..b9f98311411 --- /dev/null +++ b/gnovm/tests/files/var23.gno @@ -0,0 +1,9 @@ +package main + +var a, b, c = 1, 2 + +func main() { +} + +// Error: +// main/files/var23.gno:3:5: assignment mismatch: 3 variable(s) but 2 value(s) diff --git a/gnovm/tests/files/var24.gno b/gnovm/tests/files/var24.gno new file mode 100644 index 00000000000..ddfa82a1e4e --- /dev/null +++ b/gnovm/tests/files/var24.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var i interface{} = 1 + var a, b, c = i.(int) +} + +// Error: +// main/files/var24.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/var25.gno b/gnovm/tests/files/var25.gno new file mode 100644 index 00000000000..999979dcf71 --- /dev/null +++ b/gnovm/tests/files/var25.gno @@ -0,0 +1,9 @@ +package main + +func main() { + s := []string{"1", "2"} + var a, b, c = s[0] +} + +// Error: +// main/files/var25.gno:5:6: assignment mismatch: 3 variable(s) but 1 value(s) diff --git a/gnovm/tests/files/var26.gno b/gnovm/tests/files/var26.gno new file mode 100644 index 00000000000..71b6f3a4eb0 --- /dev/null +++ b/gnovm/tests/files/var26.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + var a, c = 1, 2, 3 + fmt.Println(a, c) +} + +// Error: +// main/files/var26.gno:6:6: extra init expr 3