From b6c5edc5203c5dd0262b4df038223ad796536986 Mon Sep 17 00:00:00 2001 From: Omar Sy Date: Sat, 21 Sep 2024 16:08:23 +0200 Subject: [PATCH] feat(gnovm): align Gno constant handling with Go specifications --- .../r/demo/keystore/keystore_test.gno | 2 +- .../r/demo/microblog/microblog_test.gno | 2 +- gnovm/pkg/gnolang/preprocess.go | 18 +++ gnovm/pkg/gnolang/type_check.go | 106 ++++++++++++++++++ gnovm/tests/files/const23.gno | 11 ++ gnovm/tests/files/const24.gno | 69 ++++++++++++ gnovm/tests/files/const25.gno | 11 ++ gnovm/tests/files/const26.gno | 15 +++ gnovm/tests/files/const27.gno | 16 +++ gnovm/tests/files/const28.gno | 12 ++ gnovm/tests/files/const29.gno | 12 ++ gnovm/tests/files/const30.gno | 15 +++ gnovm/tests/files/const31.gno | 15 +++ gnovm/tests/files/const32.gno | 11 ++ 14 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 gnovm/tests/files/const23.gno create mode 100644 gnovm/tests/files/const24.gno create mode 100644 gnovm/tests/files/const25.gno create mode 100644 gnovm/tests/files/const26.gno create mode 100644 gnovm/tests/files/const27.gno create mode 100644 gnovm/tests/files/const28.gno create mode 100644 gnovm/tests/files/const29.gno create mode 100644 gnovm/tests/files/const30.gno create mode 100644 gnovm/tests/files/const31.gno create mode 100644 gnovm/tests/files/const32.gno diff --git a/examples/gno.land/r/demo/keystore/keystore_test.gno b/examples/gno.land/r/demo/keystore/keystore_test.gno index ffd8e60936f..9b5fafa2f95 100644 --- a/examples/gno.land/r/demo/keystore/keystore_test.gno +++ b/examples/gno.land/r/demo/keystore/keystore_test.gno @@ -11,7 +11,7 @@ import ( ) func TestRender(t *testing.T) { - const ( + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/examples/gno.land/r/demo/microblog/microblog_test.gno b/examples/gno.land/r/demo/microblog/microblog_test.gno index a3c8f04ee7f..9ad98d3cbfe 100644 --- a/examples/gno.land/r/demo/microblog/microblog_test.gno +++ b/examples/gno.land/r/demo/microblog/microblog_test.gno @@ -10,7 +10,7 @@ import ( ) func TestMicroblog(t *testing.T) { - const ( + var ( author1 std.Address = testutils.TestAddress("author1") author2 std.Address = testutils.TestAddress("author2") ) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 9168fc6f7c1..8a3ad82d9f6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2188,6 +2188,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // NOTE: may or may not be a *ConstExpr, // but if not, make one now. for i, vx := range n.Values { + checkConstantExpr(store, last, vx) n.Values[i] = evalConst(store, last, vx) } } else { @@ -2268,6 +2269,16 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if n.Type != nil { // only a single type can be specified. nt := evalStaticType(store, last, n.Type) + if n.Const { + if xnt, ok := nt.(*NativeType); ok { + nt = go2GnoBaseType(xnt.Type) + } + + if _, ok := baseOf(nt).(PrimitiveType); !ok { + panic(fmt.Sprintf("invalid constant type %s", nt.String())) + } + } + for i := 0; i < numNames; i++ { sts[i] = nt } @@ -2279,6 +2290,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // derive static type from values. for i, vx := range n.Values { vt := evalStaticTypeOf(store, last, vx) + if xnt, ok := vt.(*NativeType); ok { + vt = go2GnoBaseType(xnt.Type) + } + + if _, ok := baseOf(vt).(PrimitiveType); !ok { + panic(fmt.Sprintf("invalid constant type %s", vt.String())) + } sts[i] = vt } } else { diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 31025fef152..f6e0e610595 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,112 @@ func assertAssignableTo(xt, dt Type, autoNative bool) { } } +func checkConstantExpr(store Store, last BlockNode, vx Expr) { +Main: + switch vx := vx.(type) { + case *NameExpr: + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.Name, evalStaticTypeOf(store, last, vx))) + case *TypeAssertExpr: + panic(fmt.Sprintf("%s (comma, ok expression of type %s) is not constant", vx.String(), vx.Type)) + case *IndexExpr: + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), vx.X)) + case *CallExpr: + ift := evalStaticTypeOf(store, last, vx.Func) + switch baseOf(ift).(type) { + case *FuncType: + tup := evalStaticTypeOfRaw(store, last, vx).(*tupleType) + + // check for built-in functions + if cx, ok := vx.Func.(*ConstExpr); ok { + if fv, ok := cx.V.(*FuncValue); ok { + if fv.PkgPath == uversePkgPath { + switch { + case fv.Name == "len": + checkConstantExpr(store, last, vx.Args[0]) + break Main + case fv.Name == "min": + checkConstantExpr(store, last, vx.Args[0]) + checkConstantExpr(store, last, vx.Args[1]) + break Main + case fv.Name == "max": + checkConstantExpr(store, last, vx.Args[0]) + checkConstantExpr(store, last, vx.Args[1]) + break Main + } + } + } + } + + switch { + case len(tup.Elts) == 0: + panic(fmt.Sprintf("%s (no value) used as value", vx.String())) + case len(tup.Elts) == 1: + panic(fmt.Sprintf("%s (value of type %s) is not constant", vx.String(), tup.Elts[0])) + default: + panic(fmt.Sprintf("multiple-value %s (value of type %s) in single-value context", vx.String(), tup.Elts)) + } + case *TypeType: + for _, arg := range vx.Args { + checkConstantExpr(store, last, arg) + } + case *NativeType: + panic("NativeType\n") + default: + panic(fmt.Sprintf( + "unexpected func type %v (%v)", + ift, reflect.TypeOf(ift))) + } + case *BinaryExpr: + checkConstantExpr(store, last, vx.Left) + checkConstantExpr(store, last, vx.Right) + case *SelectorExpr: + xt := evalStaticTypeOf(store, last, vx.X) + switch xt := xt.(type) { + case *PackageType: + // Todo: check if the package is const after the fix of https://github.com/gnolang/gno/issues/2836 + // var pv *PackageValue + // if cx, ok := vx.X.(*ConstExpr); ok { + // // NOTE: *Machine.TestMemPackage() needs this + // // to pass in an imported package as *ConstEzpr. + // pv = cx.V.(*PackageValue) + // } else { + // // otherwise, packages can only be referred to by + // // *NameExprs, and cannot be copied. + // pvc := evalConst(store, last, vx.X) + // pv_, ok := pvc.V.(*PackageValue) + // if !ok { + // panic(fmt.Sprintf( + // "missing package in selector expr %s", + // vx.String())) + // } + // pv = pv_ + // } + // if pv.GetBlock(store).Source.GetIsConst(store, vx.Sel) { + // break Main + // } + // panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), xt)) + case *PointerType, *DeclaredType, *StructType, *InterfaceType: + ty := evalStaticTypeOf(store, last, vx.X) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), ty)) + case *TypeType: + ty := evalStaticType(store, last, vx.X) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), ty)) + case *NativeType: + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), xt)) + default: + panic(fmt.Sprintf( + "unexpected selector expression type %v", + reflect.TypeOf(xt))) + } + + case *ConstExpr: + case *BasicLitExpr: + default: + ift := evalStaticTypeOf(store, last, vx) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), ift)) + } +} + // checkValDefineMismatch checks for mismatch between the number of variables and values in a ValueDecl or AssignStmt. func checkValDefineMismatch(n Node) { var ( diff --git a/gnovm/tests/files/const23.gno b/gnovm/tests/files/const23.gno new file mode 100644 index 00000000000..bb6464fd88a --- /dev/null +++ b/gnovm/tests/files/const23.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t []string = []string{} + fmt.Println(t) +} + +// Error: +// main/files/const23.gno:6:8: [](const-type string){} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const24.gno b/gnovm/tests/files/const24.gno new file mode 100644 index 00000000000..263264b27d6 --- /dev/null +++ b/gnovm/tests/files/const24.gno @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "time" +) + +func main() { + const a int = 1_000_000 + const b byte = byte(1) + const c float64 = 1_000_000.000 + const d string = "Hello, World!" + const e rune = 'a' + const g bool = true + const h uint = 1_000 + const i int8 = 1 + const j int16 = 1 + const k int32 = 1 + const l int64 = 1 + const m uint8 = 1 + const n uint16 = 1 + const o uint32 = 1 + const p uint64 = 1 + const r float32 = 1_000_000.000 + const s = r + const t = len("s") + const u = 1 + len("s") + 3 + + fmt.Println(a) + fmt.Println(b) + fmt.Println(c) + fmt.Println(d) + fmt.Println(e) + fmt.Println(g) + fmt.Println(h) + fmt.Println(i) + fmt.Println(j) + fmt.Println(k) + fmt.Println(l) + fmt.Println(m) + fmt.Println(n) + fmt.Println(o) + fmt.Println(p) + fmt.Println(r) + fmt.Println(s) + fmt.Println(t) + fmt.Println(u) +} + +// Output: +// 1000000 +// 1 +// 1e+06 +// Hello, World! +// 97 +// true +// 1000 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1e+06 +// 1e+06 +// 1 +// 5 diff --git a/gnovm/tests/files/const25.gno b/gnovm/tests/files/const25.gno new file mode 100644 index 00000000000..64e0358bdef --- /dev/null +++ b/gnovm/tests/files/const25.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = []string{"1"} + fmt.Println(t) +} + +// Error: +// main/files/const25.gno:6:8: [](const-type string){(const ("1" string))} (variable of type []string) is not constant diff --git a/gnovm/tests/files/const26.gno b/gnovm/tests/files/const26.gno new file mode 100644 index 00000000000..a1533e98c57 --- /dev/null +++ b/gnovm/tests/files/const26.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const26.gno:10:8: v() (value of type string) is not constant diff --git a/gnovm/tests/files/const27.gno b/gnovm/tests/files/const27.gno new file mode 100644 index 00000000000..4be731e16a7 --- /dev/null +++ b/gnovm/tests/files/const27.gno @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func v() string { + return "" +} + +func main() { + var i interface{} = 1 + const t, ok = i.(int) + fmt.Println(t, ok) +} + +// Error: +// main/files/const27.gno:11:8: i.((const-type int)) (comma, ok expression of type (const-type int)) is not constant diff --git a/gnovm/tests/files/const28.gno b/gnovm/tests/files/const28.gno new file mode 100644 index 00000000000..7b6b5648bf1 --- /dev/null +++ b/gnovm/tests/files/const28.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + var s []string = []string{"1"} + const t, ok = s[0] + fmt.Println(t, ok) +} + +// Error: +// main/files/const28.gno:7:8: s[(const (0 int))] (variable of type s) is not constant diff --git a/gnovm/tests/files/const29.gno b/gnovm/tests/files/const29.gno new file mode 100644 index 00000000000..0ebd8bcab6c --- /dev/null +++ b/gnovm/tests/files/const29.gno @@ -0,0 +1,12 @@ +package main + +import "fmt" + +func main() { + s := "1" + const t = s + fmt.Println(t) +} + +// Error: +// main/files/const29.gno:7:8: s (variable of type string) is not constant diff --git a/gnovm/tests/files/const30.gno b/gnovm/tests/files/const30.gno new file mode 100644 index 00000000000..3908ee26ee3 --- /dev/null +++ b/gnovm/tests/files/const30.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() { + return +} + +func main() { + const t = v() + fmt.Println(t) +} + +// Error: +// main/files/const30.gno:10:8: v() (no value) used as value diff --git a/gnovm/tests/files/const31.gno b/gnovm/tests/files/const31.gno new file mode 100644 index 00000000000..a192bd8ab86 --- /dev/null +++ b/gnovm/tests/files/const31.gno @@ -0,0 +1,15 @@ +package main + +import "fmt" + +func v() (string, string) { + return "", "" +} + +func main() { + const t, v = v() + fmt.Println(t) +} + +// Error: +// main/files/const31.gno:10:8: multiple-value (const (v func()( string, string)))() (value of type [string string]) in single-value context diff --git a/gnovm/tests/files/const32.gno b/gnovm/tests/files/const32.gno new file mode 100644 index 00000000000..83d3ae5e73c --- /dev/null +++ b/gnovm/tests/files/const32.gno @@ -0,0 +1,11 @@ +package main + +import "fmt" + +func main() { + const t = 1 + 2 + len([]string{}) + fmt.Println(t) +} + +// Error: +// main/files/const32.gno:6:8: [](const-type string){} (variable of type []string) is not constant