From 1180def181d7c74dc8229e4d2534054ed2d41d9b Mon Sep 17 00:00:00 2001
From: piux2 <90544084+piux2@users.noreply.github.com>
Date: Fri, 19 Jul 2024 10:54:59 -0700
Subject: [PATCH] feat: named and unnamed type assignment 3 of 3 (#2367)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This is the last part of the solution for issue #1141.
The 1 of 3 of the solution can be found in PR #1143.
The 2 of 3 of the solution can be found in PR #1246
It decomposes function calls that return multiple values in the
preprocess.
### Here is the problem to solve:
` u1, n2 = x() `
How do we ensure that the returned multiple values from a function call
adhere to named and unnamed type assignment specifications?
Additionally, we want to solve this problem during preprocessing instead
of at runtime to minimize the impact on runtime performance.
### The main ideas:
u1, n2 = x() << decompose the statement to the following two lines
// .tmp_1, .tmp_2 := x()
// u1, n2 = .tmp_1, .tmp_2
then we can apply name and unname type conversion specs to the second
line.
u1, n2 = _tmp_1, _tmp_2
### Here are the example code and the explanation
```
// decompose_filetest.gno
package main
type nat []int
func x() (nat, []int) {
a := nat{1}
b := []int{2}
return a, b
}
func main() {
var u1 []int
var n2 nat
u1, n2 = x()
// .tmp_1, .tmp_2 := x()
// u1, n2 = .tmp_1, .tmp_2
println(u1)
println(n2)
}
// Output:
// slice[(1 int)]
// (slice[(2 int)] main.nat)
```
### Here is the simplified recursive tree of the transformation in the
preprocess
### Here are the major steps involved in this decomposition during
preprocessing:
- Create hidden temporary name expressions .tmp1, .tmp2. In Go, a
leading dot is not valid in variable names, ensuring that users cannot
create names that clash with these hidden variables.
- Create two statements in the block: one for defining and one for
assigning.
```
.tmp1, .tmp2 := x()
u1, n2 = .tmp_1, .tmp_2
```
- Preprocess each newly created statements
- Replace the original statement with the two newly created statements.
### Here are two additional changes to facilitate above.
- Update the FuncValue's body in `updates := pn.PrepareNewValues(pv)
`since its source Body has been changed during preprocessing.
- Replace all ` for index := range Body` with `for i:=0; i < len(Body);
i++` in transcribe.go since the body length might change due to the
decomposition.
Contributors' checklist...
- [x] Added new tests, or not needed, or not feasible
- [x] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [x] No breaking changes were made
- [x] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Co-authored-by: Miloš Živković
Co-authored-by: Morgan
---
gnovm/Makefile | 2 +-
gnovm/pkg/gnolang/nodes.go | 28 +++++
gnovm/pkg/gnolang/preprocess.go | 105 +++++++++++++++++-
gnovm/pkg/gnolang/transcribe.go | 27 +++--
gnovm/pkg/gnolang/values.go | 9 ++
.../decompose_unnamed1a_filetest.gno | 26 +++++
.../decompose_unnamed1b_filetest.gno | 26 +++++
.../decompose/decompose_unnamed2_filetest.gno | 30 +++++
.../decompose_unnamed2b_filetest.gno | 48 ++++++++
.../decompose_unnamed2c_filetest.gno | 69 ++++++++++++
.../decompose_unnamed2d_filetest.gno | 71 ++++++++++++
.../decompose_unnamed2e_filetest.gno | 52 +++++++++
.../decompose_unnamed2f2_filetest.gno | 46 ++++++++
.../decompose_unnamed2f_filetest.gno | 49 ++++++++
.../decompose_unnamed2g_filetest.gno | 63 +++++++++++
.../decompose/decompose_unnamed3_filetest.gno | 50 +++++++++
.../decompose/decompose_unnamed4_filetest.gno | 40 +++++++
.../decompose_unnamed4b_filetest.gno | 19 ++++
.../decompose_unnamed4c_filetest.gno | 19 ++++
.../decompose/decompose_unnamed_filetest.gno | 26 +++++
20 files changed, 793 insertions(+), 12 deletions(-)
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno
create mode 100644 gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno
diff --git a/gnovm/Makefile b/gnovm/Makefile
index 6e939289fb8..5ff3af9c253 100644
--- a/gnovm/Makefile
+++ b/gnovm/Makefile
@@ -53,7 +53,7 @@ lint:
$(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint run --config ../.github/golangci.yml ./...
.PHONY: fmt
-fmt:
+fmt:
go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/...
$(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) .
diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go
index a68e3ba9d0b..c578d2a3ec3 100644
--- a/gnovm/pkg/gnolang/nodes.go
+++ b/gnovm/pkg/gnolang/nodes.go
@@ -672,6 +672,10 @@ func (ss Body) GetBody() Body {
return ss
}
+func (ss *Body) SetBody(nb Body) {
+ *ss = nb
+}
+
func (ss Body) GetLabeledStmt(label Name) (stmt Stmt, idx int) {
for idx, stmt = range ss {
if label == stmt.GetLabel() {
@@ -1375,6 +1379,13 @@ func (x *PackageNode) PrepareNewValues(pv *PackageValue) []TypedValue {
panic("PackageNode.PrepareNewValues() package mismatch")
}
}
+ // The FuncValue Body may have been altered during the preprocessing.
+ // We need to update body field from the source in the FuncValue accordingly.
+ for _, tv := range x.Values {
+ if fv, ok := tv.V.(*FuncValue); ok {
+ fv.UpdateBodyFromSource()
+ }
+ }
pvl := len(block.Values)
pnl := len(x.Values)
// copy new top-level defined values/types.
@@ -1480,6 +1491,7 @@ type BlockNode interface {
Define(Name, TypedValue)
Define2(bool, Name, Type, TypedValue)
GetBody() Body
+ SetBody(Body)
}
// ----------------------------------------
@@ -1873,18 +1885,34 @@ func (x *IfStmt) GetBody() Body {
panic("IfStmt has no body (but .Then and .Else do)")
}
+func (x *IfStmt) SetBody(b Body) {
+ panic("IfStmt has no body (but .Then and .Else do)")
+}
+
func (x *SwitchStmt) GetBody() Body {
panic("SwitchStmt has no body (but its cases do)")
}
+func (x *SwitchStmt) SetBody(b Body) {
+ panic("SwitchStmt has no body (but its cases do)")
+}
+
func (x *FileNode) GetBody() Body {
panic("FileNode has no body (but it does have .Decls)")
}
+func (x *FileNode) SetBody(b Body) {
+ panic("FileNode has no body (but it does have .Decls)")
+}
+
func (x *PackageNode) GetBody() Body {
panic("PackageNode has no body")
}
+func (x *PackageNode) SetBody(b Body) {
+ panic("PackageNode has no body")
+}
+
// ----------------------------------------
// Value Path
diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go
index 11c2708ff80..d21e9bf0efd 100644
--- a/gnovm/pkg/gnolang/preprocess.go
+++ b/gnovm/pkg/gnolang/preprocess.go
@@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"reflect"
+ "strings"
"sync/atomic"
"github.com/gnolang/gno/tm2/pkg/errors"
@@ -426,6 +427,24 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
switch n := n.(type) {
// TRANS_ENTER -----------------------
case *AssignStmt:
+ if n.Op == DEFINE {
+ for _, lx := range n.Lhs {
+ ln := lx.(*NameExpr).Name
+ if ln == blankIdentifier {
+ // ignore.
+ } else if strings.HasPrefix(string(ln), ".decompose_") {
+ _, ok := last.GetLocalIndex(ln)
+ if !ok {
+ // initial declaration to be re-defined.
+ last.Predefine(false, ln)
+ } else {
+ // do not redeclare.
+ }
+ }
+ }
+ } else {
+ // nothing defined.
+ }
// TRANS_ENTER -----------------------
case *ImportDecl, *ValueDecl, *TypeDecl, *FuncDecl:
@@ -1872,7 +1891,88 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node {
} else { // ASSIGN, or assignment operation (+=, -=, <<=, etc.)
// NOTE: Keep in sync with DEFINE above.
if len(n.Lhs) > len(n.Rhs) {
- // check is done in assertCompatible
+ // check is done in assertCompatible where we also
+ // asserted we have at lease one element in Rhs
+ if cx, ok := n.Rhs[0].(*CallExpr); ok {
+ // we decompose the a,b = x(...) for named and unamed
+ // type value return in an assignments
+ // Call case: a, b = x(...)
+ ift := evalStaticTypeOf(store, last, cx.Func)
+ cft := getGnoFuncTypeOf(store, ift)
+ // check if we we need to decompose for named typed conversion in the function return results
+ var decompose bool
+
+ for i, rhsType := range cft.Results {
+ lt := evalStaticTypeOf(store, last, n.Lhs[i])
+ if lt != nil && isNamedConversion(rhsType.Type, lt) {
+ decompose = true
+ break
+ }
+ }
+ if decompose {
+ // only enter this section if cft.Results to be converted to Lhs type for named type conversion.
+ // decompose a,b = x()
+ // .decompose1, .decompose2 := x() assignment statement expression (Op=DEFINE)
+ // a,b = .decompose1, .decompose2 assignment statement expression ( Op=ASSIGN )
+ // add the new statement to last.Body
+
+ // step1:
+ // create a hidden var with leading . (dot) the curBodyLen increase every time when there is a decomposition
+ // because there could be multiple decomposition happens
+ // we use both stmt index and return result number to differentiate the .decompose variables created in each assignment decompostion
+ // ex. .decompose_3_2: this variable is created as the 3rd statement in the block, the 2nd parameter returned from x(),
+ // create .decompose_1_1, .decompose_1_2 .... based on number of result from x()
+ tmpExprs := make(Exprs, 0, len(cft.Results))
+ for i := range cft.Results {
+ rn := fmt.Sprintf(".decompose_%d_%d", index, i)
+ tmpExprs = append(tmpExprs, Nx(rn))
+ }
+ // step2:
+ // .decompose1, .decompose2 := x()
+ dsx := &AssignStmt{
+ Lhs: tmpExprs,
+ Op: DEFINE,
+ Rhs: n.Rhs,
+ }
+ dsx.SetLine(n.Line)
+ dsx = Preprocess(store, last, dsx).(*AssignStmt)
+
+ // step3:
+
+ // a,b = .decompose1, .decompose2
+ // assign stmt expression
+ // The right-hand side will be converted to a call expression for named/unnamed conversion.
+ // tmpExprs is a []Expr; we make a copy of tmpExprs to prevent dsx.Lhs in the previous statement (dsx) from being changed by side effects.
+ // If we don't copy tmpExprs, when asx.Rhs is converted to a const call expression during the preprocessing of the AssignStmt asx,
+ // dsx.Lhs will change from []NameExpr to []CallExpr.
+ // This side effect would cause a panic when the machine executes the dsx statement, as it expects Lhs to be []NameExpr.
+
+ asx := &AssignStmt{
+ Lhs: n.Lhs,
+ Op: ASSIGN,
+ Rhs: copyExprs(tmpExprs),
+ }
+ asx.SetLine(n.Line)
+ asx = Preprocess(store, last, asx).(*AssignStmt)
+
+ // step4:
+ // replace the original stmt with two new stmts
+ body := last.GetBody()
+ // we need to do an in-place replacement while leaving the current node
+ n.Attributes = dsx.Attributes
+ n.Lhs = dsx.Lhs
+ n.Op = dsx.Op
+ n.Rhs = dsx.Rhs
+
+ // insert a assignment statement a,b = .decompose1,.decompose2 AFTER the current statement in the last.Body.
+ body = append(body[:index+1], append(Body{asx}, body[index+1:]...)...)
+ last.SetBody(body)
+ } // end of the decomposition
+
+ // Last step: we need to insert the statements to FuncValue.body of PackageNopde.Values[i].V
+ // updating FuncValue.body=FuncValue.Source.Body in updates := pn.PrepareNewValues(pv) during preprocess.
+ // we updated FuncValue from source.
+ }
} else { // len(Lhs) == len(Rhs)
if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN {
if len(n.Lhs) != 1 || len(n.Rhs) != 1 {
@@ -3160,7 +3260,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De
} else {
panic("should not happen")
}
-
+ // The body may get altered during preprocessing later.
if !dt.TryDefineMethod(&FuncValue{
Type: ft,
IsMethod: true,
@@ -3388,6 +3488,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) {
pkg := skipFile(last).(*PackageNode)
// define a FuncValue w/ above type as d.Name.
// fill in later during *FuncDecl:BLOCK.
+ // The body may get altered during preprocessing later.
fv := &FuncValue{
Type: ft,
IsMethod: false,
diff --git a/gnovm/pkg/gnolang/transcribe.go b/gnovm/pkg/gnolang/transcribe.go
index c5b72336c83..28e97ff2b5b 100644
--- a/gnovm/pkg/gnolang/transcribe.go
+++ b/gnovm/pkg/gnolang/transcribe.go
@@ -271,7 +271,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
} else {
cnn = cnn2.(*FuncLitExpr)
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_FUNCLIT_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -383,7 +384,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
} else {
cnn = cnn2.(*BlockStmt)
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_BLOCK_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -393,7 +395,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
}
case *BranchStmt:
case *DeclStmt:
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_DECL_BODY, idx, cnn.Body[idx], &c).(SimpleDeclStmt)
if isBreak(c) {
break
@@ -438,7 +441,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
return
}
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_FOR_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -488,7 +492,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
} else {
cnn = cnn2.(*IfCaseStmt)
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_IF_CASE_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -525,7 +530,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
return
}
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_RANGE_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -565,7 +571,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
if isStopOrSkip(nc, c) {
return
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_SELECTCASE_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -640,7 +647,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
return
}
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_SWITCHCASE_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
@@ -666,7 +674,8 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc
} else {
cnn = cnn2.(*FuncDecl)
}
- for idx := range cnn.Body {
+ // iterate over Body; its length can change if a statement is decomposed.
+ for idx := 0; idx < len(cnn.Body); idx++ {
cnn.Body[idx] = transcribe(t, nns, TRANS_FUNC_BODY, idx, cnn.Body[idx], &c).(Stmt)
if isBreak(c) {
break
diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go
index 9b44a3c71e7..5da7c15bb05 100644
--- a/gnovm/pkg/gnolang/values.go
+++ b/gnovm/pkg/gnolang/values.go
@@ -601,6 +601,15 @@ func (fv *FuncValue) GetBodyFromSource(store Store) []Stmt {
return fv.body
}
+func (fv *FuncValue) UpdateBodyFromSource() {
+ if fv.Source == nil {
+ panic(fmt.Sprintf(
+ "Source is missing for FuncValue %q",
+ fv.Name))
+ }
+ fv.body = fv.Source.GetBody()
+}
+
func (fv *FuncValue) GetSource(store Store) BlockNode {
if rn, ok := fv.Source.(RefNode); ok {
source := store.GetBlockNode(rn.GetLocation())
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno
new file mode 100644
index 00000000000..0e999ca130f
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1a_filetest.gno
@@ -0,0 +1,26 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+
+func main() {
+ var u1 []int
+ var n2 nat
+
+ _, n2 = x()
+ // .tmp1, .tmp_2 := x()
+ // _, u2 = .tmp1, .tmp_2
+
+ println(u1)
+ println(n2)
+
+}
+
+// Output:
+// (nil []int)
+// (slice[(2 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno
new file mode 100644
index 00000000000..9fed8f29cc6
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed1b_filetest.gno
@@ -0,0 +1,26 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+
+func main() {
+ var u1 []int
+ var n2 nat
+
+ u1, _ = x()
+ // .tmp1, .tmp_2 := x()
+ // u1, _ = .tmp1, .tmp_2
+
+ println(u1)
+ println(n2)
+
+}
+
+// Output:
+// slice[(1 int)]
+// (nil main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno
new file mode 100644
index 00000000000..db8a0838c7a
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2_filetest.gno
@@ -0,0 +1,30 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+
+func main() {
+ var u1 []int
+ var n2 nat
+ // BlockStmt
+ {
+ u1, n2 = x()
+ // .tmp0_1, .tmp0_2 := x()
+ // u1, n2 = .tmp0_1, .tmp0_2
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno
new file mode 100644
index 00000000000..6d8f05807c7
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2b_filetest.gno
@@ -0,0 +1,48 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ var u1 []int
+ var n2 nat
+ // if block
+ if true {
+ u1, n2 = x()
+ // .tmp_1, .tmp_2 := x()
+ // u1, n2 = .tmp_1, .tmp_2
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+ // else block
+ if false {
+
+ } else {
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno
new file mode 100644
index 00000000000..cae371f3821
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2c_filetest.gno
@@ -0,0 +1,69 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ var u1 []int
+ var n2 nat
+
+ // if, for, range block
+
+ if true {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+
+ for i := 0; i < 2; i++ {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+
+ zeros := []int{0, 0}
+ for _, _ = range zeros {
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno
new file mode 100644
index 00000000000..4905b3d58de
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2d_filetest.gno
@@ -0,0 +1,71 @@
+package main
+
+type nat []int
+
+// gloabal variables
+var u1 []int
+var n2 nat
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+
+ // else-if, for block
+ if false {
+
+ } else if true {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ for i := 0; i < 2; i++ {
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno
new file mode 100644
index 00000000000..d73e908f6c6
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2e_filetest.gno
@@ -0,0 +1,52 @@
+package main
+
+type nat []int
+
+// gloabal variables
+var u1 []int
+var n2 nat
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ // function block
+ fn()
+}
+
+func fn() {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ for i := 0; i < 2; i++ {
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno
new file mode 100644
index 00000000000..ea8e042d18b
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f2_filetest.gno
@@ -0,0 +1,46 @@
+package main
+
+type nat []int
+
+// gloabal variables
+var u1 []int
+var n2 nat
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+
+func main() {
+ fn()
+}
+
+func fn() {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ // function literal block
+ u1, n2 = func() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+ }()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno
new file mode 100644
index 00000000000..05201a6bd2b
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2f_filetest.gno
@@ -0,0 +1,49 @@
+package main
+
+type nat []int
+
+// gloabal variables
+var u1 []int
+var n2 nat
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ fn()
+}
+
+func fn() {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ // function literal block
+ func() {
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ }()
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno
new file mode 100644
index 00000000000..c8f9badc380
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed2g_filetest.gno
@@ -0,0 +1,63 @@
+package main
+
+type nat []int
+
+// gloabal variables
+var u1 []int
+var n2 nat
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ fn()
+}
+
+func fn() {
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ // for block
+ // switch case block
+
+ for i := 0; i < 2; i++ {
+ switch i {
+ case 0:
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ default:
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+ }
+ }
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno
new file mode 100644
index 00000000000..c8749cd862a
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed3_filetest.gno
@@ -0,0 +1,50 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+ var u1 []int
+ var n2 nat
+
+ // multiple statements
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ u1, n2 = y()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+ u1, n2 = x()
+ println(u1)
+ println(n2)
+ println(u1)
+ println(n2)
+
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(4 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno
new file mode 100644
index 00000000000..06617a90ba6
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4_filetest.gno
@@ -0,0 +1,40 @@
+package main
+
+type nat []int
+
+// package block
+var n1, u2 = x()
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+func y() (nat, []int) {
+ a := nat{3}
+ b := []int{4}
+ return a, b
+}
+func main() {
+
+ // multiple statements
+ println(n1)
+ println(u2)
+
+ u2, n1 = y()
+ println(n1)
+ println(u2)
+
+ n1, u2 = x()
+ println(n1)
+ println(u2)
+
+}
+
+// Output:
+// (slice[(1 int)] main.nat)
+// slice[(2 int)]
+// (slice[(4 int)] main.nat)
+// slice[(3 int)]
+// (slice[(1 int)] main.nat)
+// slice[(2 int)]
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno
new file mode 100644
index 00000000000..e84fdf99567
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4b_filetest.gno
@@ -0,0 +1,19 @@
+package main
+
+type nat []int
+
+// package block
+var n1, n2 nat = x()
+
+func x() (nat, []int) {
+ return nat{1}, nat{2}
+}
+
+func main() {
+ println(n1)
+ println(n2)
+}
+
+// Output:
+// (slice[(1 int)] main.nat)
+// (slice[(2 int)] main.nat)
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno
new file mode 100644
index 00000000000..863649f3705
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed4c_filetest.gno
@@ -0,0 +1,19 @@
+package main
+
+type nat []int
+
+// package block
+var u1, u2 []int = x()
+
+func x() (nat, []int) {
+ return []int{1}, nat{2}
+}
+
+func main() {
+ println(u1)
+ println(u2)
+}
+
+// Output:
+// slice[(1 int)]
+// slice[(2 int)]
diff --git a/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno
new file mode 100644
index 00000000000..44f9992a269
--- /dev/null
+++ b/gnovm/tests/files/assign_unnamed_type/decompose/decompose_unnamed_filetest.gno
@@ -0,0 +1,26 @@
+package main
+
+type nat []int
+
+func x() (nat, []int) {
+ a := nat{1}
+ b := []int{2}
+ return a, b
+}
+
+func main() {
+ var u1 []int
+ var n2 nat
+
+ u1, n2 = x()
+ // .tmp_1, .tmp_2 := x()
+ // u1, n2 = .tmp_1, .tmp_2
+
+ println(u1)
+ println(n2)
+
+}
+
+// Output:
+// slice[(1 int)]
+// (slice[(2 int)] main.nat)