Skip to content

Commit

Permalink
fix(gnovm): assignment operators require 1 expression on both sides (g…
Browse files Browse the repository at this point in the history
…nolang#1943)

> This PR fixes a bug in the GnoVM which executes incorrect statements
like `s, ok <<= m["a"]`.

The following code currently executes on gno:

```go
package main

func main() {
	m := map[string]int{"a": 1}
	var s int
	var ok bool
	s, ok <<= m["a"]
	println(s, ok)
}
```

![wat](https://github.com/gnolang/gno/assets/4681308/4545aadb-f255-49b0-863f-2506311f11e7)

This PR matches the behaviour on assignment statements to the Go
specification:

> An assignment operation x op= y where op is a binary [arithmetic
operator](https://go.dev/ref/spec#Arithmetic_operators) is equivalent to
x = x op (y) but evaluates x only once. The op= construct is a single
token. **In assignment operations, both the left- and right-hand
expression lists must contain exactly one single-valued expression,**
and the left-hand expression must not be the blank identifier.
  • Loading branch information
thehowl authored and omarsy committed Apr 21, 2024
1 parent 07f72cf commit a369d1e
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 8 deletions.
21 changes: 13 additions & 8 deletions gnovm/pkg/gnolang/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -1607,9 +1607,20 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
}
}
}
} else { // ASSIGN.
} else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.)
// If this is an assignment operation, ensure there's only 1
// expr on lhs/rhs.
if n.Op != ASSIGN &&
(len(n.Lhs) != 1 || len(n.Rhs) != 1) {
panic("assignment operator " + n.Op.TokenString() +
" requires only one expression on lhs and rhs")
}

// NOTE: Keep in sync with DEFINE above.
if len(n.Lhs) > len(n.Rhs) {
if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN {
// Special case if shift assign <<= or >>=.
checkOrConvertType(store, last, &n.Rhs[0], UintType, false)
} else if len(n.Lhs) > len(n.Rhs) {
// TODO dry code w/ above.
// Unpack n.Rhs[0] to n.Lhs[:]
if len(n.Rhs) != 1 {
Expand Down Expand Up @@ -1641,12 +1652,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
default:
panic("should not happen")
}
} else if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN {
if len(n.Lhs) != 1 || len(n.Rhs) != 1 {
panic("should not happen")
}
// Special case if shift assign <<= or >>=.
checkOrConvertType(store, last, &n.Rhs[0], UintType, false)
} else {
// General case: a, b = x, y.
for i, lx := range n.Lhs {
Expand Down
12 changes: 12 additions & 0 deletions gnovm/tests/files/assign22.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

func main() {
m := map[string]int{"a": 1}
var s int
var ok bool
s, ok <<= m["a"]
println(s, ok)
}

// Error:
// main/files/assign22.gno:7: assignment operator <<= requires only one expression on lhs and rhs
12 changes: 12 additions & 0 deletions gnovm/tests/files/assign23.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

func main() {
m := map[string]int{"a": 1}
var s int
var ok bool
s, ok += m["a"]
println(s, ok)
}

// Error:
// main/files/assign23.gno:7: assignment operator += requires only one expression on lhs and rhs

0 comments on commit a369d1e

Please sign in to comment.