Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnolang): print nil slices as undefined #1380

Merged
merged 15 commits into from
Dec 5, 2023
160 changes: 160 additions & 0 deletions gnovm/pkg/gnolang/uverse_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
3 changes: 2 additions & 1 deletion gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ func (*Block) assertValue() {}
func (RefValue) assertValue() {}

const (
nilStr = "nil"
nilStr = "nil"
undefinedStr = "undefined"
)

var (
Expand Down
32 changes: 14 additions & 18 deletions gnovm/pkg/gnolang/values_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@
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")))
Expand All @@ -180,6 +181,7 @@
if _, ok := tv.T.(*DeclaredType); ok {
return tv.String()
}

// otherwise, default behavior.
switch bt := baseOf(tv.T).(type) {
case PrimitiveType:
Expand Down Expand Up @@ -224,22 +226,14 @@
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

Check warning on line 235 in gnovm/pkg/gnolang/values_string.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values_string.go#L235

Added line #L235 was not covered by tests
case *FuncValue, *BoundMethodValue:
return fv.String()
default:
panic(fmt.Sprintf(
Expand All @@ -253,18 +247,13 @@
}
}
return nilStr
case *TypeType:
return tv.V.(TypeValue).String()
case *DeclaredType:
panic("should not happen")
case *PackageType:
return tv.V.(*PackageValue).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(
Expand All @@ -276,6 +265,13 @@
}
}

func printNilOrValue(tv *TypedValue, valueType interface{}) string {
if tv.V == nil {
return nilStr + " " + tv.T.String()
}
return fmt.Sprintf("%v", valueType)
}

// ----------------------------------------
// TypedValue.String()

Expand Down
9 changes: 9 additions & 0 deletions gnovm/tests/files/print1.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

func main() {
var a []string
println(a)
}

// Output:
// nil []string
Loading