diff --git a/gnovm/pkg/gnolang/uverse_test.go b/gnovm/pkg/gnolang/uverse_test.go new file mode 100644 index 00000000000..7280d131ec5 --- /dev/null +++ b/gnovm/pkg/gnolang/uverse_test.go @@ -0,0 +1,160 @@ +package gnolang + +import ( + "testing" +) + +type printlnTestCases struct { + name string + code string + expected string +} + +func TestIssue1337PrintNilSliceAsUndefined(t *testing.T) { + test := []printlnTestCases{ + { + name: "print empty slice", + code: `package test + func main() { + emptySlice1 := make([]int, 0) + emptySlice2 := []int{} + + println(emptySlice1) + println(emptySlice2) + }`, + expected: "slice[]\nslice[]\n", + }, + { + name: "nil slice", + code: `package test + func main() { + println(nil) + }`, + expected: "undefined\n", + }, + { + name: "print empty string slice", + code: `package test + func main() { + var a []string + println(a) + }`, + expected: "nil []string\n", + }, + { + name: "print non-empty slice", + code: `package test + func main() { + a := []string{"a", "b"} + println(a) + }`, + expected: "slice[(\"a\" string),(\"b\" string)]\n", + }, + { + name: "print empty map", + code: `package test + func main() { + var a map[string]string + println(a) + }`, + expected: "nil map[string]string\n", + }, + { + name: "print non-empty map", + code: `package test + func main() { + a := map[string]string{"a": "b"} + println(a) + }`, + expected: "map{(\"a\" string):(\"b\" string)}\n", + }, + { + name: "print nil struct", + code: `package test + func main() { + var a struct{} + println(a) + }`, + expected: "struct{}\n", + }, + { + name: "print function", + code: `package test + func foo(a, b int) int { + return a + b + } + func main() { + println(foo(1, 3)) + }`, + expected: "4\n", + }, + { + name: "print composite slice", + code: `package test + func main() { + a, b, c, d := 1, 2, 3, 4 + x := []int{ + a: b, + c: d, + } + println(x) + }`, + expected: "slice[(0 int),(2 int),(0 int),(4 int)]\n", + }, + { + name: "simple recover case", + code: `package test + + func main() { + defer func() { println("recover", recover()) }() + println("simple panic") + }`, + expected: "simple panic\nrecover undefined\n", + }, + { + name: "nested recover", + code: `package test + + func main() { + defer func() { println("outer recover", recover()) }() + defer func() { println("nested panic") }() + println("simple panic") + }`, + expected: "simple panic\nnested panic\nouter recover undefined\n", + }, + { + name: "print non-nil function", + code: `package test + func f() int { + return 1 + } + + func main() { + g := f + println(g) + }`, + expected: "f\n", + }, + { + name: "print primitive types", + code: `package test + func main() { + println(1) + println(1.1) + println(true) + println("hello") + }`, + expected: "1\n1.1\ntrue\nhello\n", + }, + } + + for _, tc := range test { + t.Run(tc.name, func(t *testing.T) { + m := NewMachine("test", nil) + n := MustParseFile("main.go", tc.code) + m.RunFiles(n) + m.RunMain() + assertOutput(t, tc.code, tc.expected) + }) + } +} diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3bdd3332e08..7105de1fedf 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -43,7 +43,8 @@ func (*Block) assertValue() {} func (RefValue) assertValue() {} const ( - nilStr = "nil" + nilStr = "nil" + undefinedStr = "undefined" ) var ( diff --git a/gnovm/pkg/gnolang/values_string.go b/gnovm/pkg/gnolang/values_string.go index f9a0128d7f9..b64b491cbb8 100644 --- a/gnovm/pkg/gnolang/values_string.go +++ b/gnovm/pkg/gnolang/values_string.go @@ -164,8 +164,9 @@ func (v RefValue) String() string { func (tv *TypedValue) Sprint(m *Machine) string { // if undefined, just "undefined". if tv == nil || tv.T == nil { - return "undefined" + return undefinedStr } + // if implements .String(), return it. if IsImplementedBy(gStringerType, tv.T) { res := m.Eval(Call(Sel(&ConstExpr{TypedValue: *tv}, "String"))) @@ -180,6 +181,7 @@ func (tv *TypedValue) Sprint(m *Machine) string { if _, ok := tv.T.(*DeclaredType); ok { return tv.String() } + // otherwise, default behavior. switch bt := baseOf(tv.T).(type) { case PrimitiveType: @@ -224,22 +226,14 @@ func (tv *TypedValue) Sprint(m *Machine) string { return "invalid-pointer" } return tv.V.(PointerValue).String() - case *ArrayType: - return tv.V.(*ArrayValue).String() - case *SliceType: - return tv.V.(*SliceValue).String() - case *StructType: - return tv.V.(*StructValue).String() - case *MapType: - return tv.V.(*MapValue).String() + case *ArrayType, *SliceType, *StructType, *MapType, *TypeType, *NativeType: + return printNilOrValue(tv, tv.V) case *FuncType: switch fv := tv.V.(type) { case nil: ft := tv.T.String() - return "nil " + ft - case *FuncValue: - return fv.String() - case *BoundMethodValue: + return nilStr + " " + ft + case *FuncValue, *BoundMethodValue: return fv.String() default: panic(fmt.Sprintf( @@ -253,8 +247,6 @@ func (tv *TypedValue) Sprint(m *Machine) string { } } return nilStr - case *TypeType: - return tv.V.(TypeValue).String() case *DeclaredType: panic("should not happen") case *PackageType: @@ -262,9 +254,6 @@ func (tv *TypedValue) Sprint(m *Machine) string { case *ChanType: panic("not yet implemented") // return tv.V.(*ChanValue).String() - case *NativeType: - return fmt.Sprintf("%v", - tv.V.(*NativeValue).Value.Interface()) default: if debug { panic(fmt.Sprintf( @@ -276,6 +265,13 @@ func (tv *TypedValue) Sprint(m *Machine) string { } } +func printNilOrValue(tv *TypedValue, valueType interface{}) string { + if tv.V == nil { + return nilStr + " " + tv.T.String() + } + return fmt.Sprintf("%v", valueType) +} + // ---------------------------------------- // TypedValue.String() diff --git a/gnovm/tests/files/print1.gno b/gnovm/tests/files/print1.gno new file mode 100644 index 00000000000..606759a5c05 --- /dev/null +++ b/gnovm/tests/files/print1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a []string + println(a) +} + +// Output: +// nil []string