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
108 changes: 54 additions & 54 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,66 +181,24 @@
if _, ok := tv.T.(*DeclaredType); ok {
return tv.String()
}

// otherwise, default behavior.
switch bt := baseOf(tv.T).(type) {
case PrimitiveType:
switch bt {
case UntypedBoolType, BoolType:
return fmt.Sprintf("%t", tv.GetBool())
case UntypedStringType, StringType:
return tv.GetString()
case IntType:
return fmt.Sprintf("%d", tv.GetInt())
case Int8Type:
return fmt.Sprintf("%d", tv.GetInt8())
case Int16Type:
return fmt.Sprintf("%d", tv.GetInt16())
case UntypedRuneType, Int32Type:
return fmt.Sprintf("%d", tv.GetInt32())
case Int64Type:
return fmt.Sprintf("%d", tv.GetInt64())
case UintType:
return fmt.Sprintf("%d", tv.GetUint())
case Uint8Type:
return fmt.Sprintf("%d", tv.GetUint8())
case Uint16Type:
return fmt.Sprintf("%d", tv.GetUint16())
case Uint32Type:
return fmt.Sprintf("%d", tv.GetUint32())
case Uint64Type:
return fmt.Sprintf("%d", tv.GetUint64())
case Float32Type:
return fmt.Sprintf("%v", tv.GetFloat32())
case Float64Type:
return fmt.Sprintf("%v", tv.GetFloat64())
case UntypedBigintType, BigintType:
return tv.V.(BigintValue).V.String()
case UntypedBigdecType, BigdecType:
return tv.V.(BigdecValue).V.String()
default:
panic("should not happen")
}
return printPrimitive(bt, *tv)
notJoon marked this conversation as resolved.
Show resolved Hide resolved
case *PointerType:
if tv.V == nil {
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 200 in gnovm/pkg/gnolang/values_string.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values_string.go#L200

Added line #L200 was not covered by tests
case *FuncValue, *BoundMethodValue:
return fv.String()
default:
panic(fmt.Sprintf(
Expand All @@ -253,18 +212,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 +230,52 @@
}
}

func printPrimitive(bt PrimitiveType, tv TypedValue) string {
switch bt {
case UntypedBoolType, BoolType:
return fmt.Sprintf("%t", tv.GetBool())
case UntypedStringType, StringType:
return tv.GetString()
case IntType:
return fmt.Sprintf("%d", tv.GetInt())
case Int8Type:
return fmt.Sprintf("%d", tv.GetInt8())
case Int16Type:
return fmt.Sprintf("%d", tv.GetInt16())

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

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values_string.go#L241-L244

Added lines #L241 - L244 were not covered by tests
case UntypedRuneType, Int32Type:
return fmt.Sprintf("%d", tv.GetInt32())
case Int64Type:
return fmt.Sprintf("%d", tv.GetInt64())
case UintType:
return fmt.Sprintf("%d", tv.GetUint())
case Uint8Type:
return fmt.Sprintf("%d", tv.GetUint8())
case Uint16Type:
return fmt.Sprintf("%d", tv.GetUint16())
case Uint32Type:
return fmt.Sprintf("%d", tv.GetUint32())
case Uint64Type:
return fmt.Sprintf("%d", tv.GetUint64())
case Float32Type:
return fmt.Sprintf("%v", tv.GetFloat32())

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

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values_string.go#L249-L260

Added lines #L249 - L260 were not covered by tests
case Float64Type:
return fmt.Sprintf("%v", tv.GetFloat64())
case UntypedBigintType, BigintType:
return tv.V.(BigintValue).V.String()
case UntypedBigdecType, BigdecType:
return tv.V.(BigdecValue).V.String()
default:
panic("should not happen")

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

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values_string.go#L263-L268

Added lines #L263 - L268 were not covered by tests
}
}

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