diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index c578d2a3ec3..b8f89d3416f 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -403,7 +403,7 @@ type IndexExpr struct { // X[Index] Attributes X Expr // expression Index Expr // index expression - HasOK bool // if true, is form: `value, ok := [] + HasOK bool // if true, is form: `value, ok := []` } type SelectorExpr struct { // X.Sel diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index d21e9bf0efd..2f2f8540aa6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2449,16 +2449,33 @@ 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 { - panic(fmt.Sprintf( - "evalStaticTypeOf() only supports *CallExpr with 1 result, got %s", - tt.String(), - )) - } else { - return tt.Elts[0] + loc := last.GetLocation() + funcName := getFunctionName(x) + valueCount := len(tt.Elts) + + var msg string + if valueCount == 0 { + msg = fmt.Sprintf("%s: %s() (no value) used as value", loc, funcName) + } else { + msg = fmt.Sprintf("%s: %s() (%d values) used as single value", loc, funcName, valueCount) + } + + panic(fmt.Errorf("%s\nHint: Ensure the function returns a single value, or use multiple assignment", msg)) + } + return tt.Elts[0] + } + return t +} + +// getFunctionName attempts to extract the function name from the expression +func getFunctionName(x Expr) string { + switch expr := x.(type) { + case *CallExpr: + if funcName, ok := expr.Func.(*NameExpr); ok { + return string(funcName.Name) } - } else { - return t } + return "" } // like evalStaticTypeOf() but returns the raw *tupleType for *CallExpr. diff --git a/gnovm/pkg/gnolang/preprocess_test.go b/gnovm/pkg/gnolang/preprocess_test.go index 73e1318b062..4c5567db163 100644 --- a/gnovm/pkg/gnolang/preprocess_test.go +++ b/gnovm/pkg/gnolang/preprocess_test.go @@ -60,3 +60,62 @@ func main() { initStaticBlocks(store, pn, n) Preprocess(store, pn, n) } + +func TestEvalStaticTypeOf_MultipleValues(t *testing.T) { + pn := NewPackageNode("main", "main", nil) + pv := pn.NewPackage() + store := gonativeTestStore(pn, pv) + store.SetBlockNode(pn) + + const src = `package main +func multipleReturns() (int, string) { + return 1, "hello" +} +func main() { + x := multipleReturns() +}` + n := MustParseFile("main.go", src) + + initStaticBlocks(store, pn, n) + + defer func() { + err := recover() + assert.NotNil(t, err, "Expected panic") + errMsg := fmt.Sprint(err) + assert.Contains(t, errMsg, "multipleReturns() (2 values) used as single value", "Unexpected error message") + assert.Contains(t, errMsg, "Hint: Ensure the function returns a single value, or use multiple assignment", "Missing hint in error message") + }() + + Preprocess(store, pn, n) +} + +func TestEvalStaticTypeOf_NoValue(t *testing.T) { + pn := NewPackageNode("main", "main", nil) + pv := pn.NewPackage() + store := gonativeTestStore(pn, pv) + store.SetBlockNode(pn) + + const src = `package main + +func main() { + n := f() +} + +func f() { + println("hello!") +} +` + n := MustParseFile("main.go", src) + + initStaticBlocks(store, pn, n) + + defer func() { + err := recover() + assert.NotNil(t, err, "Expected panic") + errMsg := fmt.Sprint(err) + assert.Contains(t, errMsg, "f() (no value) used as value", "Unexpected error message") + assert.Contains(t, errMsg, "Hint: Ensure the function returns a single value, or use multiple assignment", "Missing hint in error message") + }() + + Preprocess(store, pn, n) +}