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 a7a1e15bbf3..c8a4fad2a14 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2296,6 +2296,7 @@ func preprocess1(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 { @@ -2376,6 +2377,20 @@ func preprocess1(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 len(n.Values) == 0 { + panic(fmt.Sprintf("missing init expr for %s", n.NameExprs[0].Name)) + } + + 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 } @@ -2387,6 +2402,13 @@ func preprocess1(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("%s (value of type %s) is not constant", vx.String(), vt.String())) + } sts[i] = vt } } else { // T is nil, n not const diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index f86c44e7921..4b0a2490a0b 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -215,6 +215,122 @@ func assertAssignableTo(xt, dt Type, autoNative bool) { } } +func checkConstantExpr(store Store, last BlockNode, vx Expr) { +Main: + switch vx := vx.(type) { + case *NameExpr: + t := evalStaticTypeOf(store, last, vx) + if _, ok := t.(*ArrayType); ok { + break Main + } + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.Name, t)) + 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 { + // TODO: should support min, max, real, imag + switch { + case fv.Name == "len": + checkConstantExpr(store, last, vx.Args[0]) + break Main + case fv.Name == "cap": + checkConstantExpr(store, last, vx.Args[0]) + 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: + // Todo: should add a test after the fix of https://github.com/gnolang/gno/issues/3006 + ty := evalStaticType(store, last, vx.Func) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), ty)) + 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: + 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 + } + + tt := pv.GetBlock(store).Source.GetStaticTypeOf(store, vx.Sel) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), tt)) + 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: + ty := evalStaticTypeOf(store, last, vx.X) + panic(fmt.Sprintf("%s (variable of type %s) is not constant", vx.String(), ty)) + default: + panic(fmt.Sprintf( + "unexpected selector expression type %v", + reflect.TypeOf(xt))) + } + + case *ArrayTypeExpr: + case *ConstExpr: + case *BasicLitExpr: + case *CompositeLitExpr: + checkConstantExpr(store, last, vx.Type) + default: + ift := evalStaticTypeOf(store, last, vx) + if _, ok := ift.(*TypeType); ok { + ift = evalStaticType(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..7282c959b02 --- /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..c9cb14ef369 --- /dev/null +++ b/gnovm/tests/files/const24.gno @@ -0,0 +1,79 @@ +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 + ars := [10]string{} + const v = len(ars) + const w = cap(ars) + const x = time.Second + + 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) + fmt.Println(v) + fmt.Println(w) + println(x) +} + +// Output: +// 1000000 +// 1 +// 1e+06 +// Hello, World! +// 97 +// true +// 1000 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1 +// 1e+06 +// 1e+06 +// 1 +// 5 +// 10 +// 10 +// 1s diff --git a/gnovm/tests/files/const25.gno b/gnovm/tests/files/const25.gno new file mode 100644 index 00000000000..c83495a4d20 --- /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) (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..75acf5fb446 --- /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 diff --git a/gnovm/tests/files/const33.gno b/gnovm/tests/files/const33.gno new file mode 100644 index 00000000000..989fe7dda5b --- /dev/null +++ b/gnovm/tests/files/const33.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 +var y = 1 + +const b = x == y + +func main() { + println("ok") +} +// Error: +// main/files/const33.gno:6:7: x (variable of type int) is not constant diff --git a/gnovm/tests/files/const34.gno b/gnovm/tests/files/const34.gno new file mode 100644 index 00000000000..9c79cf86b88 --- /dev/null +++ b/gnovm/tests/files/const34.gno @@ -0,0 +1,10 @@ +package main + +const a = func() { println("hey") } + +func main() { + println("ok") +} + +// Error: +// main/files/const34.gno:3:7: func func(){ (const (println func(xs ...interface{})()))((const ("hey" string))) } (variable of type func()()) is not constant diff --git a/gnovm/tests/files/const35.gno b/gnovm/tests/files/const35.gno new file mode 100644 index 00000000000..e684edb7263 --- /dev/null +++ b/gnovm/tests/files/const35.gno @@ -0,0 +1,12 @@ +package main + +var x = 1 + +const ff = +(1 << x) + +func main() { + println("ok") +} + +// Error: +// main/files/const35.gno:5:7: +(const (1 bigint)) << (const-type uint)(x) (variable of type bigint) is not constant diff --git a/gnovm/tests/files/const36.gno b/gnovm/tests/files/const36.gno new file mode 100644 index 00000000000..c6cf82c6f6b --- /dev/null +++ b/gnovm/tests/files/const36.gno @@ -0,0 +1,13 @@ +package main + +type s struct { + x int +} + +func main() { + s := s{1} + const v = s.x +} + +// Error: +// main/files/const36.gno:9:8: s.x (variable of type main.s) is not constant diff --git a/gnovm/tests/files/const37_native.gno b/gnovm/tests/files/const37_native.gno new file mode 100644 index 00000000000..413fa011151 --- /dev/null +++ b/gnovm/tests/files/const37_native.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_native.gno:6:8: time.UTC (variable of type gonative{*time.Location}) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const37_stdlibs.gno b/gnovm/tests/files/const37_stdlibs.gno new file mode 100644 index 00000000000..9ec614f2538 --- /dev/null +++ b/gnovm/tests/files/const37_stdlibs.gno @@ -0,0 +1,10 @@ +package main + +import "time" + +func main() { + const v = time.UTC +} + +// Error: +// main/files/const37_stdlibs.gno:6:8: time.UTC (variable of type *time.Location) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const38.gno b/gnovm/tests/files/const38.gno new file mode 100644 index 00000000000..1aee945d434 --- /dev/null +++ b/gnovm/tests/files/const38.gno @@ -0,0 +1,11 @@ +package main + +import "net" + +func main() { + v := net.TCPAddr{} + const c = v.IP +} + +// Error: +// main/files/const38.gno:7:8: v.IP (variable of type gonative{net.TCPAddr}) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const39.gno b/gnovm/tests/files/const39.gno new file mode 100644 index 00000000000..fa3cdb5f517 --- /dev/null +++ b/gnovm/tests/files/const39.gno @@ -0,0 +1,14 @@ +package main + +type T struct { + a int +} + +func (tv T) Mv(a int) int { return 0 } + +func main() { + const t = T.Mv +} + +// Error: +// main/files/const39.gno:10:8: T.Mv (variable of type main.T) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const40.gno b/gnovm/tests/files/const40.gno new file mode 100644 index 00000000000..f0eeedeaeb9 --- /dev/null +++ b/gnovm/tests/files/const40.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t = [0]string{} +} + +// Error: +// main/files/const40.gno:4:8: (const (array[] [0]string)) (value of type [0]string) is not constant \ No newline at end of file diff --git a/gnovm/tests/files/const41.gno b/gnovm/tests/files/const41.gno new file mode 100644 index 00000000000..b4424dcef94 --- /dev/null +++ b/gnovm/tests/files/const41.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t [0]string = [0]string{} +} + +// Error: +// main/files/const41.gno:4:8: invalid constant type [0]string \ No newline at end of file diff --git a/gnovm/tests/files/const42.gno b/gnovm/tests/files/const42.gno new file mode 100644 index 00000000000..5763a2fc121 --- /dev/null +++ b/gnovm/tests/files/const42.gno @@ -0,0 +1,8 @@ +package main + +func main() { + const t int +} + +// Error: +// main/files/const42.gno:4:8: missing init expr for t \ No newline at end of file