From 85f62bad851e4cfdd167c14fde4539e08dac01a0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 14:09:15 +0100 Subject: [PATCH 01/67] refactor(gnovm): rename precompiler to transpiler, move to own package --- PLAN.md | 2 +- docs/gno-tooling/cli/gno.md | 6 +- docs/reference/go-gno-compatibility.md | 2 +- examples/Makefile | 10 +-- gno.land/pkg/gnoclient/client_txs.go | 8 +- gno.land/pkg/keyscli/addpkg.go | 5 +- gno.land/pkg/keyscli/run.go | 5 +- gnovm/cmd/gno/README.md | 2 +- gnovm/cmd/gno/lint_test.go | 2 +- gnovm/cmd/gno/main.go | 2 +- gnovm/cmd/gno/precompile.go | 90 +++++++++---------- gnovm/cmd/gno/precompile_test.go | 4 +- gnovm/cmd/gno/test.go | 23 ++--- .../testdata/gno_precompile/01_no_args.txtar | 6 +- .../gno_precompile/02_empty_dir.txtar | 4 +- .../03_gno_files_parse_error.txtar | 6 +- .../gno_precompile/04_valid_gno_files.txtar | 4 +- .../gno_precompile/05_skip_fmt_flag.txtar | 6 +- .../gno_precompile/06_build_flag.txtar | 4 +- .../07_build_flag_with_build_error.txtar | 6 +- .../08_build_flag_with_parse_error.txtar | 4 +- .../09_gno_files_whitelist_error.txtar | 6 +- .../gno/testdata/gno_test/empty_gno1.txtar | 2 +- .../gno/testdata/gno_test/empty_gno2.txtar | 2 +- .../gno/testdata/gno_test/empty_gno3.txtar | 2 +- .../testdata/gno_test/failing_filetest.txtar | 2 +- .../gno/testdata/gno_test/failing_test.txtar | 2 +- gnovm/cmd/gno/util.go | 10 +-- gnovm/pkg/gnolang/realm.go | 13 +-- .../transpiler.go} | 44 ++++----- .../transpiler_test.go} | 8 +- gnovm/pkg/gnomod/file.go | 8 +- gnovm/pkg/gnomod/gnomod.go | 25 +++--- 33 files changed, 165 insertions(+), 160 deletions(-) rename gnovm/pkg/gnolang/{precompile.go => transpiler/transpiler.go} (87%) rename gnovm/pkg/gnolang/{precompile_test.go => transpiler/transpiler_test.go} (96%) diff --git a/PLAN.md b/PLAN.md index dfcae264727..138eee35bb8 100644 --- a/PLAN.md +++ b/PLAN.md @@ -16,7 +16,7 @@ the Discord server. ### Gnolang * Get basic Go tests working _COMPLETE_ -* Implement minimal toolsuite (precompile, gnodev) +* Implement minimal toolsuite (transpile, gnodev) * Tweak/enforce gas limitations * Implement flat-as-struct supported * Implement ownership/realm logic; phase 1: no cycles diff --git a/docs/gno-tooling/cli/gno.md b/docs/gno-tooling/cli/gno.md index e72f1414102..5c73a5cee34 100644 --- a/docs/gno-tooling/cli/gno.md +++ b/docs/gno-tooling/cli/gno.md @@ -19,7 +19,7 @@ gno {SUB_COMMAND} | Name | Description | | ------------ | ------------------------------------------ | | `test` | Tests a gno package. | -| `precompile` | Precompiles a `.gno` file to a `.go` file. | +| `transpile` | Transpiles a `.gno` file to a `.go` file. | | `repl` | Starts a GnoVM REPL. | ### `test` @@ -32,9 +32,9 @@ gno {SUB_COMMAND} | `root-dir` | String | Clones location of github.com/gnolang/gno (gno tries to guess it). | | `run` | String | Test name filtering pattern. | | `timeout` | time.Duration | The maximum execution time in ns. | -| `precompile` | Boolean | Precompiles a `.gno` file to a `.go` file before testing. | +| `transpile` | Boolean | Transpiles a `.gno` file to a `.go` file before testing. | -### `precompile` +### `transpile` #### **Options** diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 71f46adf836..4b65b6c32ef 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -303,7 +303,7 @@ Legend: | + go mod init | gno mod init | same behavior | | + go mod download | gno mod download | same behavior | | + go mod tidy | gno mod tidy | same behavior | -| | gno precompile | | +| | gno transpile | | | go work | | | | | gno repl | | | go run | gno run | | diff --git a/examples/Makefile b/examples/Makefile index 56ad4b30b42..2fd5c70daf4 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -9,13 +9,13 @@ help: @echo "Available make commands:" @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' -.PHONY: precompile -precompile: - go run ../gnovm/cmd/gno precompile --verbose . +.PHONY: transpile +transpile: + go run ../gnovm/cmd/gno transpile --verbose . .PHONY: build -build: precompile - go run ../gnovm/cmd/gno build --verbose . +build: + go run ../gnovm/cmd/gno transpile --verbose --gobuild . .PHONY: test test: diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 39088bc30d5..34759b565e3 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,7 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -208,10 +208,10 @@ func (c *Client) Run(cfg RunCfg) (*ctypes.ResultBroadcastTxCommit, error) { caller := c.Signer.Info().GetAddress() - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { - return nil, errors.Wrap(err, "precompile and check") + return nil, errors.Wrap(err, "transpile and check") } memPkg.Name = "main" memPkg.Path = "" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 1882f6de3d0..82aef1315d4 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,6 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -101,8 +102,8 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { panic(err) } diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 7d329c18566..3dd1879bcfa 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -108,8 +109,8 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - // precompile and validate syntax - err = gno.PrecompileAndCheckMempkg(memPkg) + // transpile and validate syntax + err = transpiler.TranspileAndCheckMempkg(memPkg) if err != nil { panic(err) } diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md index 97285684785..d49ca85736e 100644 --- a/gnovm/cmd/gno/README.md +++ b/gnovm/cmd/gno/README.md @@ -10,7 +10,7 @@ * `gno run` - run a Gno file * `gno build` - build a gno package -* `gno precompile` - precompile .gno to .go +* `gno transpile` - transpile .gno to .go * `gno test` - test a gno package * `gno mod` - manages dependencies * `gno repl` start a GnoVM REPL diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index d700467965d..1966eab7c9e 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -31,7 +31,7 @@ func TestLintApp(t *testing.T) { // TODO: is gno source valid? // TODO: are dependencies valid? // TODO: is gno source using unsafe/discouraged features? - // TODO: consider making `gno precompile; go lint *gen.go` + // TODO: consider making `gno transpile; go lint *gen.go` // TODO: check for imports of native libs from non _test.gno files } testMainCaseRun(t, tc) diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index aecb5d0f1e5..5271bcb1e9f 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -28,7 +28,7 @@ func newGnocliCmd(io commands.IO) *commands.Command { newTestCmd(io), newLintCmd(io), newRunCmd(io), - newPrecompileCmd(io), + newTranspileCmd(io), newCleanCmd(io), newReplCmd(), newDocCmd(io), diff --git a/gnovm/cmd/gno/precompile.go b/gnovm/cmd/gno/precompile.go index 2d7a727a13a..7b32df556b5 100644 --- a/gnovm/cmd/gno/precompile.go +++ b/gnovm/cmd/gno/precompile.go @@ -10,13 +10,13 @@ import ( "os" "path/filepath" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) type importPath string -type precompileCfg struct { +type transpileCfg struct { verbose bool skipFmt bool skipImports bool @@ -26,52 +26,52 @@ type precompileCfg struct { output string } -type precompileOptions struct { - cfg *precompileCfg - // precompiled is the set of packages already - // precompiled from .gno to .go. - precompiled map[importPath]struct{} +type transpileOptions struct { + cfg *transpileCfg + // transpiled is the set of packages already + // transpiled from .gno to .go. + transpiled map[importPath]struct{} } -var defaultPrecompileCfg = &precompileCfg{ +var defaultTranspileCfg = &transpileCfg{ verbose: false, goBinary: "go", } -func newPrecompileOptions(cfg *precompileCfg) *precompileOptions { - return &precompileOptions{cfg, map[importPath]struct{}{}} +func newTranspileOptions(cfg *transpileCfg) *transpileOptions { + return &transpileOptions{cfg, map[importPath]struct{}{}} } -func (p *precompileOptions) getFlags() *precompileCfg { +func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *precompileOptions) isPrecompiled(pkg importPath) bool { - _, precompiled := p.precompiled[pkg] - return precompiled +func (p *transpileOptions) isTranspiled(pkg importPath) bool { + _, transpiled := p.transpiled[pkg] + return transpiled } -func (p *precompileOptions) markAsPrecompiled(pkg importPath) { - p.precompiled[pkg] = struct{}{} +func (p *transpileOptions) markAsTranspiled(pkg importPath) { + p.transpiled[pkg] = struct{}{} } -func newPrecompileCmd(io commands.IO) *commands.Command { - cfg := &precompileCfg{} +func newTranspileCmd(io commands.IO) *commands.Command { + cfg := &transpileCfg{} return commands.NewCommand( commands.Metadata{ - Name: "precompile", - ShortUsage: "precompile [flags] [...]", - ShortHelp: "Precompiles .gno files to .go", + Name: "transpile", + ShortUsage: "transpile [flags] [...]", + ShortHelp: "Transpiles .gno files to .go", }, cfg, func(_ context.Context, args []string) error { - return execPrecompile(cfg, args, io) + return execTranspile(cfg, args, io) }, ) } -func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { +func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { fs.BoolVar( &c.verbose, "verbose", @@ -90,7 +90,7 @@ func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { &c.skipImports, "skip-imports", false, - "do not precompile imports recursively", + "do not transpile imports recursively", ) fs.BoolVar( @@ -122,25 +122,25 @@ func (c *precompileCfg) RegisterFlags(fs *flag.FlagSet) { ) } -func execPrecompile(cfg *precompileCfg, args []string, io commands.IO) error { +func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp } - // precompile .gno files. + // transpile .gno files. paths, err := gnoFilesFromArgs(args) if err != nil { return fmt.Errorf("list paths: %w", err) } - opts := newPrecompileOptions(cfg) + opts := newTranspileOptions(cfg) var errlist scanner.ErrorList for _, filepath := range paths { - if err := precompileFile(filepath, opts); err != nil { + if err := transpileFile(filepath, opts); err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: precompile: %w", filepath, err) + return fmt.Errorf("%s: transpile: %w", filepath, err) } errlist = append(errlist, fileErrlist...) } @@ -169,16 +169,16 @@ func execPrecompile(cfg *precompileCfg, args []string, io commands.IO) error { for _, err := range errlist { io.ErrPrintfln(err.Error()) } - return fmt.Errorf("%d precompile error(s)", errlist.Len()) + return fmt.Errorf("%d transpile error(s)", errlist.Len()) } return nil } -func precompilePkg(pkgPath importPath, opts *precompileOptions) error { - if opts.isPrecompiled(pkgPath) { +func transpilePkg(pkgPath importPath, opts *transpileOptions) error { + if opts.isTranspiled(pkgPath) { return nil } - opts.markAsPrecompiled(pkgPath) + opts.markAsTranspiled(pkgPath) files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) if err != nil { @@ -186,7 +186,7 @@ func precompilePkg(pkgPath importPath, opts *precompileOptions) error { } for _, file := range files { - if err = precompileFile(file, opts); err != nil { + if err = transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -194,7 +194,7 @@ func precompilePkg(pkgPath importPath, opts *precompileOptions) error { return nil } -func precompileFile(srcPath string, opts *precompileOptions) error { +func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() gofmt := flags.gofmtBinary if gofmt == "" { @@ -212,12 +212,12 @@ func precompileFile(srcPath string, opts *precompileOptions) error { } // compute attributes based on filename. - targetFilename, tags := gno.GetPrecompileFilenameAndTags(srcPath) + targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) // preprocess. - precompileRes, err := gno.Precompile(string(source), tags, srcPath) + transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) if err != nil { - return fmt.Errorf("precompile: %w", err) + return fmt.Errorf("transpile: %w", err) } // resolve target path @@ -233,31 +233,31 @@ func precompileFile(srcPath string, opts *precompileOptions) error { } // write .go file. - err = WriteDirFile(targetPath, []byte(precompileRes.Translated)) + err = WriteDirFile(targetPath, []byte(transpileRes.Translated)) if err != nil { return fmt.Errorf("write .go file: %w", err) } // check .go fmt, if `SkipFmt` sets to false. if !flags.skipFmt { - err = gno.PrecompileVerifyFile(targetPath, gofmt) + err = transpiler.TranspileVerifyFile(targetPath, gofmt) if err != nil { return fmt.Errorf("check .go file: %w", err) } } - // precompile imported packages, if `SkipImports` sets to false + // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports { - importPaths := getPathsFromImportSpec(precompileRes.Imports) + importPaths := getPathsFromImportSpec(transpileRes.Imports) for _, path := range importPaths { - precompilePkg(path, opts) + transpilePkg(path, opts) } } return nil } -func goBuildFileOrPkg(fileOrPkg string, cfg *precompileCfg) error { +func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { verbose := cfg.verbose goBinary := cfg.goBinary @@ -265,5 +265,5 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *precompileCfg) error { fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) } - return gno.PrecompileBuildPackage(fileOrPkg, goBinary) + return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) } diff --git a/gnovm/cmd/gno/precompile_test.go b/gnovm/cmd/gno/precompile_test.go index f8436fad768..2770026a01a 100644 --- a/gnovm/cmd/gno/precompile_test.go +++ b/gnovm/cmd/gno/precompile_test.go @@ -9,9 +9,9 @@ import ( "github.com/gnolang/gno/gnovm/pkg/integration" ) -func Test_ScriptsPrecompile(t *testing.T) { +func Test_ScriptsTranspile(t *testing.T) { p := testscript.Params{ - Dir: "testdata/gno_precompile", + Dir: "testdata/gno_transpile", } if coverdir, ok := integration.ResolveCoverageDir(); ok { diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 1deac5aac48..082e2066ff5 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,6 +19,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" @@ -33,7 +34,7 @@ type testCfg struct { rootDir string run string timeout time.Duration - precompile bool // TODO: precompile should be the default, but it needs to automatically precompile dependencies in memory. + transpile bool // TODO: transpile should be the default, but it needs to automatically transpile dependencies in memory. updateGoldenTests bool printRuntimeMetrics bool withNativeFallback bool @@ -110,10 +111,10 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) { ) fs.BoolVar( - &c.precompile, - "precompile", + &c.transpile, + "transpile", false, - "precompile gno to go before testing", + "transpile gno to go before testing", ) fs.BoolVar( @@ -166,7 +167,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { verbose := cfg.verbose - tempdirRoot, err := os.MkdirTemp("", "gno-precompile") + tempdirRoot, err := os.MkdirTemp("", "gno-transpile") if err != nil { log.Fatal(err) } @@ -174,7 +175,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { // go.mod modPath := filepath.Join(tempdirRoot, "go.mod") - err = makeTestGoMod(modPath, gno.ImportPrefix, "1.21") + err = makeTestGoMod(modPath, transpiler.ImportPrefix, "1.21") if err != nil { return fmt.Errorf("write .mod file: %w", err) } @@ -208,14 +209,14 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { buildErrCount := 0 testErrCount := 0 for _, pkg := range subPkgs { - if cfg.precompile { + if cfg.transpile { if verbose { io.ErrPrintfln("=== PREC %s", pkg.Dir) } - precompileOpts := newPrecompileOptions(&precompileCfg{ + transpileOpts := newTranspileOptions(&transpileCfg{ output: tempdirRoot, }) - err := precompilePkg(importPath(pkg.Dir), precompileOpts) + err := transpilePkg(importPath(pkg.Dir), transpileOpts) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") @@ -233,7 +234,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if err != nil { return errors.New("cannot resolve build dir") } - err = goBuildFileOrPkg(tempDir, defaultPrecompileCfg) + err = goBuildFileOrPkg(tempDir, defaultTranspileCfg) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") @@ -317,7 +318,7 @@ func gnoTestPkg( gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) if gnoPkgPath == "" { // unable to read pkgPath from gno.mod, generate a random realm path - gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = transpiler.GnoRealmPkgsPrefixBefore + random.RandStr(8) } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) diff --git a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar index 239d3860e11..6d8592d4e3b 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar @@ -1,6 +1,6 @@ -# Run gno precompile without args +# Run gno transpile without args -! gno precompile +! gno transpile -! stdout .+ +! stdout .+ stderr 'USAGE' diff --git a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar index 7afc7ed0683..2bd1841d2b4 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar @@ -1,6 +1,6 @@ -# Run gno precompile on an empty dir +# Run gno transpile on an empty dir -gno precompile . +gno transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar index 6e2475e5962..b94b86992af 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar @@ -1,12 +1,12 @@ -# Run gno precompile with gno files with parse errors +# Run gno transpile with gno files with parse errors -! gno precompile . +! gno transpile . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' stderr '^main.gno:7:2: expected declaration, found wrong$' stderr '^sub/sub.gno:3:1: expected declaration, found invalid$' -stderr '^3 precompile error\(s\)$' +stderr '^3 transpile error\(s\)$' # no *.gen.go files are created ! exec test -f main.gno.gen.go diff --git a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar index 0f9d909f627..4770386e7f5 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with valid gno files +# Run gno transpile with valid gno files -gno precompile . +gno transpile . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar index 0f7780efdad..6f7a5e16665 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar @@ -1,8 +1,8 @@ -# Run gno precompile with -skip-fmt flag +# Run gno transpile with -skip-fmt flag # NOTE(tb): this flag doesn't actually prevent the code format, because -# `gnolang.Precompile()` calls `format.Node()`. +# `gnolang.Transpile()` calls `format.Node()`. -gno precompile -skip-fmt . +gno transpile -skip-fmt . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar index b93fae95fcf..23eac452058 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with -gobuild flag +# Run gno transpile with -gobuild flag -gno precompile -gobuild . +gno transpile -gobuild . ! stdout .+ ! stderr .+ diff --git a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar index 85b411e9db1..1c3727e2072 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar @@ -1,11 +1,11 @@ -# Run gno precompile with -gobuild flag +# Run gno transpile with -gobuild flag -! gno precompile -gobuild . +! gno transpile -gobuild . ! stdout .+ stderr '^./main.gno:4:6: x declared and not used$' stderr '^./main.gno:5:6: y declared and not used$' -stderr '^2 precompile error\(s\)$' +stderr '^2 transpile error\(s\)$' cmp main.gno.gen.go main.gno.gen.go.golden cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden diff --git a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar index 74dbfa5d06f..629de2b7f3d 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar @@ -1,6 +1,6 @@ -# Run gno precompile with -gobuild flag on file with parse error +# Run gno transpile with -gobuild flag on file with parse error -! gno precompile -gobuild . +! gno transpile -gobuild . ! stdout .+ stderr '^main.gno:3:1: expected declaration, found invalid$' diff --git a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar index a64a44f466c..79d4d6a4a2c 100644 --- a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar @@ -1,11 +1,11 @@ -# Run gno precompile with gno files with whitelist errors +# Run gno transpile with gno files with whitelist errors -! gno precompile . +! gno transpile . ! stdout .+ stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' stderr '^sub/sub.gno:3:8: import "xxx" is not in the whitelist$' -stderr '^2 precompile error\(s\)$' +stderr '^2 transpile error\(s\)$' # no *.gen.go files are created ! exec test -f main.gno.gen.go diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar index cc673bb38ff..6d7d73a5e20 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar @@ -5,7 +5,7 @@ gno test . ! stdout .+ stderr '\? \. \[no test files\]' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar index 0ea13294a1b..fa14c412548 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar @@ -5,7 +5,7 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar index c095de6c358..3ffe7adcedd 100644 --- a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar @@ -5,7 +5,7 @@ ! stdout .+ stderr 'expected ''package'', found ''EOF''' -! gno test --precompile . +! gno test --transpile . ! stdout .+ stderr 'expected ''package'', found ''EOF''' diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar index c739c1ce328..ae1db2b67e5 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar @@ -6,7 +6,7 @@ stdout 'Machine.RunMain\(\) panic: beep boop' stderr '=== RUN file/failing_filetest.gno' stderr 'panic: fail on failing_filetest.gno: got unexpected error: beep boop' -! gno test -verbose --precompile . +! gno test -verbose --transpile . stdout 'Machine.RunMain\(\) panic: beep boop' stderr '=== PREC \.' diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar index 0bf4aab21ff..e5c103fd95b 100644 --- a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar @@ -7,7 +7,7 @@ stderr '=== RUN TestAlwaysFailing' stderr '--- FAIL: TestAlwaysFailing' stderr 'FAIL: 0 build errors, 1 test errors' -! gno test -verbose --precompile . +! gno test -verbose --transpile . ! stdout .+ stderr '=== RUN TestAlwaysFailing' diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index a8e835d4759..c46e5d4e8f0 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" ) func isGnoFile(f fs.DirEntry) bool { @@ -203,8 +203,8 @@ func makeTestGoMod(path string, packageName string, goversion string) error { func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) { for _, i := range importSpec { path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"` - if strings.HasPrefix(path, gno.ImportPrefix) { - res := strings.TrimPrefix(path, gno.ImportPrefix) + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) importPaths = append(importPaths, importPath("."+res)) } } @@ -213,9 +213,9 @@ func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importP // ResolvePath joins the output dir with relative pkg path // e.g -// Output Dir: Temp/gno-precompile +// Output Dir: Temp/gno-transpile // Pkg Path: ../example/gno.land/p/pkg -// Returns -> Temp/gno-precompile/example/gno.land/p/pkg +// Returns -> Temp/gno-transpile/example/gno.land/p/pkg func ResolvePath(output string, path importPath) (string, error) { absOutput, err := filepath.Abs(output) if err != nil { diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 519b183ad3a..d146b3fa7db 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1511,13 +1511,14 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } +// realmPathPrefix is the prefix used to identify pkgpaths which are meant to +// be realms and as such to have their state persisted. This is used by [IsRealmPath]. +const realmPathPrefix = "gno.land/r/" + +// IsRealmPath determines whether the given pkgpath is for a realm, and as such +// should persist the global state. func IsRealmPath(pkgPath string) bool { - // TODO: make it more distinct to distinguish from normal paths. - if strings.HasPrefix(pkgPath, GnoRealmPkgsPrefixBefore) { - return true - } else { - return false - } + return strings.HasPrefix(pkgPath, realmPathPrefix) } func prettyJSON(jstr []byte) []byte { diff --git a/gnovm/pkg/gnolang/precompile.go b/gnovm/pkg/gnolang/transpiler/transpiler.go similarity index 87% rename from gnovm/pkg/gnolang/precompile.go rename to gnovm/pkg/gnolang/transpiler/transpiler.go index af51e962416..d9a3398f2f1 100644 --- a/gnovm/pkg/gnolang/precompile.go +++ b/gnovm/pkg/gnolang/transpiler/transpiler.go @@ -1,4 +1,4 @@ -package gnolang +package transpiler import ( "bytes" @@ -81,13 +81,13 @@ var importPrefixWhitelist = []string{ const ImportPrefix = "github.com/gnolang/gno" -type precompileResult struct { +type transpileResult struct { Imports []*ast.ImportSpec Translated string } -// TODO: func PrecompileFile: supports caching. -// TODO: func PrecompilePkg: supports directories. +// TODO: func TranspileFile: supports caching. +// TODO: func TranspilePkg: supports directories. func guessRootDir(fileOrPkg string, goBinary string) (string, error) { abs, err := filepath.Abs(fileOrPkg) @@ -105,8 +105,8 @@ func guessRootDir(fileOrPkg string, goBinary string) (string, error) { return rootDir, nil } -// GetPrecompileFilenameAndTags returns the filename and tags for precompiled files. -func GetPrecompileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { +// GetTranspileFilenameAndTags returns the filename and tags for transpiled files. +func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { nameNoExtension := strings.TrimSuffix(filepath.Base(gnoFilePath), ".gno") switch { case strings.HasSuffix(gnoFilePath, "_filetest.gno"): @@ -122,7 +122,7 @@ func GetPrecompileFilenameAndTags(gnoFilePath string) (targetFilename, tags stri return } -func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { +func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { gofmt := "gofmt" tmpDir, err := os.MkdirTemp("", mempkg.Name) @@ -136,7 +136,7 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { if !strings.HasSuffix(mfile.Name, ".gno") { continue // skip spurious file. } - res, err := Precompile(mfile.Body, "gno,tmp", mfile.Name) + res, err := Transpile(mfile.Body, "gno,tmp", mfile.Name) if err != nil { errs = multierr.Append(errs, err) continue @@ -147,7 +147,7 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { errs = multierr.Append(errs, err) continue } - err = PrecompileVerifyFile(tmpFile, gofmt) + err = TranspileVerifyFile(tmpFile, gofmt) if err != nil { errs = multierr.Append(errs, err) continue @@ -155,12 +155,12 @@ func PrecompileAndCheckMempkg(mempkg *std.MemPackage) error { } if errs != nil { - return fmt.Errorf("precompile package: %w", errs) + return fmt.Errorf("transpile package: %w", errs) } return nil } -func Precompile(source string, tags string, filename string) (*precompileResult, error) { +func Transpile(source string, tags string, filename string) (*transpileResult, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) if err != nil { @@ -170,9 +170,9 @@ func Precompile(source string, tags string, filename string) (*precompileResult, isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") shouldCheckWhitelist := !isTestFile - transformed, err := precompileAST(fset, f, shouldCheckWhitelist) + transformed, err := transpileAST(fset, f, shouldCheckWhitelist) if err != nil { - return nil, fmt.Errorf("precompileAST: %w", err) + return nil, fmt.Errorf("transpileAST: %w", err) } header := "// Code generated by github.com/gnolang/gno. DO NOT EDIT.\n\n" @@ -186,17 +186,17 @@ func Precompile(source string, tags string, filename string) (*precompileResult, return nil, fmt.Errorf("format.Node: %w", err) } - res := &precompileResult{ + res := &transpileResult{ Imports: f.Imports, Translated: out.String(), } return res, nil } -// PrecompileVerifyFile tries to run `go fmt` against a precompiled .go file. +// TranspileVerifyFile tries to run `go fmt` against a transpiled .go file. // // This is fast and won't look the imports. -func PrecompileVerifyFile(path string, gofmtBinary string) error { +func TranspileVerifyFile(path string, gofmtBinary string) error { // TODO: use cmd/parser instead of exec? args := strings.Split(gofmtBinary, " ") @@ -210,16 +210,16 @@ func PrecompileVerifyFile(path string, gofmtBinary string) error { return nil } -// PrecompileBuildPackage tries to run `go build` against the precompiled .go files. +// TranspileBuildPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that // all the import are valid and available. -func PrecompileBuildPackage(fileOrPkg, goBinary string) error { +func TranspileBuildPackage(fileOrPkg, goBinary string) error { // TODO: use cmd/compile instead of exec? // TODO: find the nearest go.mod file, chdir in the same folder, rim prefix? // TODO: temporarily create an in-memory go.mod or disable go modules for gno? // TODO: ignore .go files that were not generated from gno? - // TODO: automatically precompile if not yet done. + // TODO: automatically transpile if not yet done. files := []string{} @@ -272,7 +272,7 @@ var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) // Each errors are translated into their correlated gno files, by: // - changing the filename from *.gno.gen.go to *.gno // - shifting line number according to the added header in generated go files -// (see [Precompile] for that header). +// (see [Transpile] for that header). func parseGoBuildErrors(out string) error { var errList goscanner.ErrorList matches := errorRe.FindAllStringSubmatch(out, -1) @@ -293,7 +293,7 @@ func parseGoBuildErrors(out string) error { Filename: strings.TrimSuffix(filename, ".gen.go"), // Shift the 4 lines header added in *.gen.go files. // NOTE(tb): the 4 lines shift below assumes there's always a //go:build - // directive. But the tags are optional in the Precompile() function + // directive. But the tags are optional in the Transpile() function // so that leaves some doubts... We might want something more reliable than // constants to shift lines. Line: line - 4, @@ -303,7 +303,7 @@ func parseGoBuildErrors(out string) error { return errList.Err() } -func precompileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { +func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) diff --git a/gnovm/pkg/gnolang/precompile_test.go b/gnovm/pkg/gnolang/transpiler/transpiler_test.go similarity index 96% rename from gnovm/pkg/gnolang/precompile_test.go rename to gnovm/pkg/gnolang/transpiler/transpiler_test.go index 3f36a16a5e8..03659be6c37 100644 --- a/gnovm/pkg/gnolang/precompile_test.go +++ b/gnovm/pkg/gnolang/transpiler/transpiler_test.go @@ -1,4 +1,4 @@ -package gnolang +package transpiler import ( "go/ast" @@ -11,7 +11,7 @@ import ( "github.com/jaekwon/testify/require" ) -func TestPrecompile(t *testing.T) { +func TestTranspile(t *testing.T) { t.Parallel() cases := []struct { @@ -196,7 +196,7 @@ import "reflect" func foo() { _ = reflect.ValueOf } `, - expectedError: `precompileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, + expectedError: `transpileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, }, { name: "syntax-error", @@ -268,7 +268,7 @@ func foo() { _ = regexp.MatchString } // "\n" is added for better test case readability, now trim it source := strings.TrimPrefix(c.source, "\n") - res, err := Precompile(source, c.tags, "foo.gno") + res, err := Transpile(source, c.tags, "foo.gno") if c.expectedError != "" { require.EqualError(t, err, c.expectedError) diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index 25189ebc71d..d1dff4ee816 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -184,12 +184,12 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { continue } // skip if `std`, special case. - if path == gnolang.GnoStdPkgAfter { + if path == transpiler.GnoStdPkgAfter { continue } - if strings.HasPrefix(path, gnolang.ImportPrefix) { - path = strings.TrimPrefix(path, gnolang.ImportPrefix+"/examples/") + if strings.HasPrefix(path, transpiler.ImportPrefix) { + path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index d750b7c9f29..54a30df73dd 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -9,6 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" "golang.org/x/mod/module" @@ -61,21 +62,21 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err } } else { // Is File - // Precompile and write generated go file + // Transpile and write generated go file if strings.HasSuffix(fileName, ".gno") { filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := gnolang.GetPrecompileFilenameAndTags(filePath) - precompileRes, err := gnolang.Precompile(string(res.Data), "", fileName) + targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) + transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) if err != nil { - return nil, fmt.Errorf("precompile: %w", err) + return nil, fmt.Errorf("transpile: %w", err) } - for _, i := range precompileRes.Imports { + for _, i := range transpileRes.Imports { requirements = append(requirements, i.Path.Value) } targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) - err = os.WriteFile(targetFileNameWithPath, []byte(precompileRes.Translated), 0o644) + err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) if err != nil { return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) } @@ -97,9 +98,9 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err func GnoToGoMod(f File) (*File, error) { gnoModPath := GetGnoModPath() - if strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Module.Mod.Path, gnolang.GnoPackagePrefixBefore) { - f.AddModuleStmt(gnolang.ImportPrefix + "/examples/" + f.Module.Mod.Path) + if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || + strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPackagePrefixBefore) { + f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) } for i := range f.Require { @@ -110,10 +111,10 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Require[i].Mod.Path, gnolang.GnoPackagePrefixBefore) { + if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || + strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPackagePrefixBefore) { // Add dependency with a modified import path - f.AddRequire(gnolang.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) + f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) } f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") // Remove the old require since the new dependency was added above From 9409285591d21bddc3a9c6f7f0d1d6122d0843f4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 14:17:00 +0100 Subject: [PATCH 02/67] move transpiler to pkg --- gno.land/pkg/gnoclient/client_txs.go | 2 +- gno.land/pkg/keyscli/addpkg.go | 2 +- gno.land/pkg/keyscli/run.go | 2 +- gnovm/cmd/gno/test.go | 2 +- .../testdata/{gno_precompile => gno_transpile}/01_no_args.txtar | 0 .../{gno_precompile => gno_transpile}/02_empty_dir.txtar | 0 .../03_gno_files_parse_error.txtar | 0 .../{gno_precompile => gno_transpile}/04_valid_gno_files.txtar | 0 .../{gno_precompile => gno_transpile}/05_skip_fmt_flag.txtar | 0 .../{gno_precompile => gno_transpile}/06_build_flag.txtar | 0 .../07_build_flag_with_build_error.txtar | 0 .../08_build_flag_with_parse_error.txtar | 0 .../09_gno_files_whitelist_error.txtar | 0 gnovm/cmd/gno/{precompile.go => transpile.go} | 2 +- gnovm/cmd/gno/{precompile_test.go => transpile_test.go} | 0 gnovm/cmd/gno/util.go | 2 +- gnovm/pkg/gnomod/file.go | 2 +- gnovm/pkg/gnomod/gnomod.go | 2 +- gnovm/pkg/{gnolang => }/transpiler/transpiler.go | 0 gnovm/pkg/{gnolang => }/transpiler/transpiler_test.go | 0 20 files changed, 8 insertions(+), 8 deletions(-) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/01_no_args.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/02_empty_dir.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/03_gno_files_parse_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/04_valid_gno_files.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/05_skip_fmt_flag.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/06_build_flag.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/07_build_flag_with_build_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/08_build_flag_with_parse_error.txtar (100%) rename gnovm/cmd/gno/testdata/{gno_precompile => gno_transpile}/09_gno_files_whitelist_error.txtar (100%) rename gnovm/cmd/gno/{precompile.go => transpile.go} (99%) rename gnovm/cmd/gno/{precompile_test.go => transpile_test.go} (100%) rename gnovm/pkg/{gnolang => }/transpiler/transpiler.go (100%) rename gnovm/pkg/{gnolang => }/transpiler/transpiler_test.go (100%) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 34759b565e3..ece7507d569 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,7 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 82aef1315d4..1f89b2c2b82 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,7 +7,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 3dd1879bcfa..6fcee7434eb 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 082e2066ff5..a02c1abf192 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,7 +19,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" diff --git a/gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/01_no_args.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/02_empty_dir.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/03_gno_files_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/04_valid_gno_files.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/05_skip_fmt_flag.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/06_build_flag.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/07_build_flag_with_build_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/08_build_flag_with_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_precompile/09_gno_files_whitelist_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar diff --git a/gnovm/cmd/gno/precompile.go b/gnovm/cmd/gno/transpile.go similarity index 99% rename from gnovm/cmd/gno/precompile.go rename to gnovm/cmd/gno/transpile.go index 7b32df556b5..b7708469e9d 100644 --- a/gnovm/cmd/gno/precompile.go +++ b/gnovm/cmd/gno/transpile.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) diff --git a/gnovm/cmd/gno/precompile_test.go b/gnovm/cmd/gno/transpile_test.go similarity index 100% rename from gnovm/cmd/gno/precompile_test.go rename to gnovm/cmd/gno/transpile_test.go diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index c46e5d4e8f0..30d8f808d04 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -12,7 +12,7 @@ import ( "time" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func isGnoFile(f fs.DirEntry) bool { diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index d1dff4ee816..fda9263914e 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 54a30df73dd..af946f2bf39 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -9,7 +9,7 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/gnolang/transpiler" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/std" "golang.org/x/mod/modfile" "golang.org/x/mod/module" diff --git a/gnovm/pkg/gnolang/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go similarity index 100% rename from gnovm/pkg/gnolang/transpiler/transpiler.go rename to gnovm/pkg/transpiler/transpiler.go diff --git a/gnovm/pkg/gnolang/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go similarity index 100% rename from gnovm/pkg/gnolang/transpiler/transpiler_test.go rename to gnovm/pkg/transpiler/transpiler_test.go From 209a233e99c6d2c9ebd71ec276fcc8fa6701286b Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 16:56:00 +0100 Subject: [PATCH 03/67] part 1: precompile also standard libraries --- docs/getting-started/local-setup.md | 2 +- gnovm/cmd/gno/test.go | 4 +- gnovm/cmd/gno/transpile.go | 4 +- gnovm/pkg/gnolang/realm.go | 6 +- gnovm/pkg/gnomod/file.go | 4 - gnovm/pkg/gnomod/gnomod.go | 18 +- gnovm/pkg/transpiler/transpiler.go | 240 ++++++------------ gnovm/stdlibs/bytes/boundary_test.gno | 2 - .../crypto/chacha20/chacha/chacha_ref.gno | 2 - .../internal/bytealg/compare_generic.gno | 3 - .../internal/bytealg/count_generic.gno | 2 - .../internal/bytealg/index_generic.gno | 2 - .../internal/bytealg/indexbyte_generic.gno | 2 - gnovm/stdlibs/io/io.gno | 37 +-- 14 files changed, 111 insertions(+), 217 deletions(-) diff --git a/docs/getting-started/local-setup.md b/docs/getting-started/local-setup.md index cbef4522477..753b6688b1a 100644 --- a/docs/getting-started/local-setup.md +++ b/docs/getting-started/local-setup.md @@ -28,7 +28,7 @@ git clone https://github.com/gnolang/gno.git ## 2. Installing the `gno` development toolkit Next, we are going to build and install the `gno` development toolkit. -`gno` provides ample functionality to the user, among which is running, precompiling, testing and building `.gno` files. +`gno` provides ample functionality to the user, among which is running, transpiling, testing and building `.gno` files. To install the toolkit, navigate to the `gnovm` folder from the repository root, and run the `build` make directive: diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index a02c1abf192..57ed1bdc6be 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,8 +19,8 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" @@ -318,7 +318,7 @@ func gnoTestPkg( gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) if gnoPkgPath == "" { // unable to read pkgPath from gno.mod, generate a random realm path - gnoPkgPath = transpiler.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = gno.RealmPathPrefix + random.RandStr(8) } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index b7708469e9d..0f0967b9dba 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -212,7 +212,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { } // compute attributes based on filename. - targetFilename, tags := transpiler.GetTranspileFilenameAndTags(srcPath) + targetFilename, tags := transpiler.TranspiledFilenameAndTags(srcPath) // preprocess. transpileRes, err := transpiler.Transpile(string(source), tags, srcPath) @@ -240,7 +240,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // check .go fmt, if `SkipFmt` sets to false. if !flags.skipFmt { - err = transpiler.TranspileVerifyFile(targetPath, gofmt) + err = transpiler.StaticCheck([]byte(transpileRes.Translated)) if err != nil { return fmt.Errorf("check .go file: %w", err) } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index d146b3fa7db..b544f1d3350 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1511,14 +1511,14 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } -// realmPathPrefix is the prefix used to identify pkgpaths which are meant to +// RealmPathPrefix is the prefix used to identify pkgpaths which are meant to // be realms and as such to have their state persisted. This is used by [IsRealmPath]. -const realmPathPrefix = "gno.land/r/" +const RealmPathPrefix = "gno.land/r/" // IsRealmPath determines whether the given pkgpath is for a realm, and as such // should persist the global state. func IsRealmPath(pkgPath string) bool { - return strings.HasPrefix(pkgPath, realmPathPrefix) + return strings.HasPrefix(pkgPath, RealmPathPrefix) } func prettyJSON(jstr []byte) []byte { diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index fda9263914e..744ef5faf7f 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -183,10 +183,6 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { if strings.HasSuffix(path, modFile.Module.Mod.Path) { continue } - // skip if `std`, special case. - if path == transpiler.GnoStdPkgAfter { - continue - } if strings.HasPrefix(path, transpiler.ImportPrefix) { path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index af946f2bf39..3f8636ea2f6 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -65,7 +65,7 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err // Transpile and write generated go file if strings.HasSuffix(fileName, ".gno") { filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := transpiler.GetTranspileFilenameAndTags(filePath) + targetFilename, _ := transpiler.TranspiledFilenameAndTags(filePath) transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) if err != nil { return nil, fmt.Errorf("transpile: %w", err) @@ -96,11 +96,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err // GnoToGoMod make necessary modifications in the gno.mod // and return go.mod file. func GnoToGoMod(f File) (*File, error) { + // TODO(morgan): good candidate to move to pkg/transpiler. + gnoModPath := GetGnoModPath() - if strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Module.Mod.Path, transpiler.GnoPackagePrefixBefore) { - f.AddModuleStmt(transpiler.ImportPrefix + "/examples/" + f.Module.Mod.Path) + if !transpiler.IsStdlib(f.Module.Mod.Path) { + f.AddModuleStmt(transpiler.TranspileImportPath(f.Module.Mod.Path)) } for i := range f.Require { @@ -111,14 +112,13 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoRealmPkgsPrefixBefore) || - strings.HasPrefix(f.Require[i].Mod.Path, transpiler.GnoPackagePrefixBefore) { + if !transpiler.IsStdlib(path) { // Add dependency with a modified import path - f.AddRequire(transpiler.ImportPrefix+"/examples/"+f.Require[i].Mod.Path, f.Require[i].Mod.Version) + f.AddRequire(transpiler.TranspileImportPath(path), f.Require[i].Mod.Version) } - f.AddReplace(f.Require[i].Mod.Path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") + f.AddReplace(path, f.Require[i].Mod.Version, filepath.Join(gnoModPath, path), "") // Remove the old require since the new dependency was added above - f.DropRequire(f.Require[i].Mod.Path) + f.DropRequire(path) } // Remove replacements that are not replaced by directories. diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index d9a3398f2f1..2872b94ccc6 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -1,3 +1,5 @@ +// Package transpiler implements a source-to-source compiler for translating Gno +// code into Go code. package transpiler import ( @@ -19,69 +21,40 @@ import ( "go.uber.org/multierr" "golang.org/x/tools/go/ast/astutil" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/tm2/pkg/std" ) -const ( - GnoRealmPkgsPrefixBefore = "gno.land/r/" - GnoRealmPkgsPrefixAfter = "github.com/gnolang/gno/examples/gno.land/r/" - GnoPackagePrefixBefore = "gno.land/p/demo/" - GnoPackagePrefixAfter = "github.com/gnolang/gno/examples/gno.land/p/demo/" - GnoStdPkgBefore = "std" - GnoStdPkgAfter = "github.com/gnolang/gno/gnovm/stdlibs/stdshim" -) - -var stdlibWhitelist = []string{ - // go - "bufio", - "bytes", - "compress/gzip", - "context", - "crypto/md5", - "crypto/sha1", - "crypto/chacha20", - "crypto/cipher", - "crypto/sha256", - "encoding/base64", - "encoding/binary", - "encoding/hex", - "encoding/json", - "encoding/xml", - "errors", - "hash", - "hash/adler32", - "internal/bytealg", - "internal/os", - "flag", - "fmt", - "io", - "io/util", - "math", - "math/big", - "math/bits", - "math/rand", - "net/url", - "path", - "regexp", - "sort", - "strconv", - "strings", - "text/template", - "time", - "unicode", - "unicode/utf8", +// ImportPrefix is the import path to the root of the gno repository, which should +// be used to create go import paths. +const ImportPrefix = "github.com/gnolang/gno" - // gno - "std", +// TranspileImportPath takes an import path s, and converts it into the full +// import path relative to the Gno repository. +func TranspileImportPath(s string) string { + return ImportPrefix + "/" + packageDirLocation(s) } -var importPrefixWhitelist = []string{ - "github.com/gnolang/gno/_test", +// IsStdlib determines whether s is a pkgpath for a standard library. +func IsStdlib(s string) bool { + // NOTE(morgan): this is likely to chagne in the future as we add support for + // IBC/ICS and we allow import paths to other chains. It might be good to + // follow the same rule as Go, which is: does the first element of the + // import path contain a dot? + return !strings.HasPrefix(s, "gno.land/") } -const ImportPrefix = "github.com/gnolang/gno" +// packageDirLocation provides the supposed directory of the package, relative to the root dir. +func packageDirLocation(s string) string { + switch { + case !IsStdlib(s): + return "examples/" + s + default: + return "gnovm/stdlibs/" + s + } +} -type transpileResult struct { +type result struct { Imports []*ast.ImportSpec Translated string } @@ -89,24 +62,8 @@ type transpileResult struct { // TODO: func TranspileFile: supports caching. // TODO: func TranspilePkg: supports directories. -func guessRootDir(fileOrPkg string, goBinary string) (string, error) { - abs, err := filepath.Abs(fileOrPkg) - if err != nil { - return "", err - } - args := []string{"list", "-m", "-mod=mod", "-f", "{{.Dir}}", ImportPrefix} - cmd := exec.Command(goBinary, args...) - cmd.Dir = abs - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("can't guess --root-dir") - } - rootDir := strings.TrimSpace(string(out)) - return rootDir, nil -} - -// GetTranspileFilenameAndTags returns the filename and tags for transpiled files. -func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { +// TranspiledFilenameAndTags returns the filename and tags for transpiled files. +func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) { nameNoExtension := strings.TrimSuffix(filepath.Base(gnoFilePath), ".gno") switch { case strings.HasSuffix(gnoFilePath, "_filetest.gno"): @@ -122,15 +79,10 @@ func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags strin return } +// TranspileAndCheckMempkg converts each of the files in mempkg to Go, and +// performs basic static checking through the [StaticCheck] function on each +// of these. func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { - gofmt := "gofmt" - - tmpDir, err := os.MkdirTemp("", mempkg.Name) - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) //nolint: errcheck - var errs error for _, mfile := range mempkg.Files { if !strings.HasSuffix(mfile.Name, ".gno") { @@ -141,13 +93,7 @@ func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { errs = multierr.Append(errs, err) continue } - tmpFile := filepath.Join(tmpDir, mfile.Name) - err = os.WriteFile(tmpFile, []byte(res.Translated), 0o644) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - err = TranspileVerifyFile(tmpFile, gofmt) + err = StaticCheck([]byte(res.Translated)) if err != nil { errs = multierr.Append(errs, err) continue @@ -160,7 +106,7 @@ func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { return nil } -func Transpile(source string, tags string, filename string) (*transpileResult, error) { +func Transpile(source string, tags string, filename string) (*result, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) if err != nil { @@ -168,9 +114,17 @@ func Transpile(source string, tags string, filename string) (*transpileResult, e } isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") - shouldCheckWhitelist := !isTestFile + rootDir := gnoenv.RootDir() + if isTestFile { + // XXX(morgan): this disables checking that a package exists (in examples or stdlibs) + // when transpiling a test file. After all Gno functions, including those in + // tests/imports.go are converted to native bindings, support should + // be added for transpiling stdlibs only available in tests/stdlibs, and + // enable as such "package checking" also on test files. + rootDir = "" + } - transformed, err := transpileAST(fset, f, shouldCheckWhitelist) + transformed, err := transpileAST(fset, f, rootDir) if err != nil { return nil, fmt.Errorf("transpileAST: %w", err) } @@ -186,28 +140,22 @@ func Transpile(source string, tags string, filename string) (*transpileResult, e return nil, fmt.Errorf("format.Node: %w", err) } - res := &transpileResult{ + res := &result{ Imports: f.Imports, Translated: out.String(), } return res, nil } -// TranspileVerifyFile tries to run `go fmt` against a transpiled .go file. -// -// This is fast and won't look the imports. -func TranspileVerifyFile(path string, gofmtBinary string) error { - // TODO: use cmd/parser instead of exec? - - args := strings.Split(gofmtBinary, " ") - args = append(args, []string{"-l", "-e", path}...) - cmd := exec.Command(args[0], args[1:]...) - out, err := cmd.CombinedOutput() - if err != nil { - fmt.Fprintln(os.Stderr, string(out)) - return fmt.Errorf("%s: %w", gofmtBinary, err) - } - return nil +// StaticCheck runs checks similar to gofmt on the given file. This will perform +// a basic round of static analysis, without checking the imports. +func StaticCheck(source []byte) error { + // TODO(morgan): we ignore the output of format.Source because we're only + // interested in errors that may occur in the static analysis of format.Source. + // If all we care is errors, could we use something else that avoids the "formatting" + // part and only does a non-typed Go static check on the source? + _, err := format.Source(source) + return err } // TranspileBuildPackage tries to run `go build` against the transpiled .go files. @@ -253,7 +201,7 @@ func TranspileBuildPackage(fileOrPkg, goBinary string) error { sort.Strings(files) args := append([]string{"build", "-v", "-tags=gno"}, files...) cmd := exec.Command(goBinary, args...) - rootDir, err := guessRootDir(fileOrPkg, goBinary) + rootDir := gnoenv.RootDir() if err == nil { cmd.Dir = rootDir } @@ -303,79 +251,35 @@ func parseGoBuildErrors(out string) error { return errList.Err() } -func transpileAST(fset *token.FileSet, f *ast.File, checkWhitelist bool) (ast.Node, error) { +// If rootDir is given, we will check that the directory of the import path exists. +func transpileAST(fset *token.FileSet, f *ast.File, rootDir string) (ast.Node, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) - // import whitelist - if checkWhitelist { - for _, paragraph := range imports { - for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - continue - } - - if strings.HasPrefix(importPath, GnoPackagePrefixBefore) { - continue - } - - valid := false - for _, whitelisted := range stdlibWhitelist { - if importPath == whitelisted { - valid = true - break - } - } - if valid { - continue - } - - for _, whitelisted := range importPrefixWhitelist { - if strings.HasPrefix(importPath, whitelisted) { - valid = true - break - } - } - if valid { - continue - } - - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q is not in the whitelist", importPath)) - } - } - } - // rewrite imports for _, paragraph := range imports { for _, importSpec := range paragraph { - importPath := strings.TrimPrefix(strings.TrimSuffix(importSpec.Path.Value, `"`), `"`) - - // std package - if importPath == GnoStdPkgBefore { - if !astutil.RewriteImport(fset, f, GnoStdPkgBefore, GnoStdPkgAfter) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", GnoStdPkgBefore, GnoStdPkgAfter)) - } + importPath, err := strconv.Unquote(importSpec.Path.Value) + if err != nil { + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("can't unquote import path %s: %w", importSpec.Path.Value, err)) + continue } - // p/pkg packages - if strings.HasPrefix(importPath, GnoPackagePrefixBefore) { - target := GnoPackagePrefixAfter + strings.TrimPrefix(importPath, GnoPackagePrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) + if rootDir != "" { + dirPath := filepath.Join(rootDir, packageDirLocation(importPath)) + if _, err := os.Stat(dirPath); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("import %q does not exist", importPath)) + continue } } - // r/realm packages - if strings.HasPrefix(importPath, GnoRealmPkgsPrefixBefore) { - target := GnoRealmPkgsPrefixAfter + strings.TrimPrefix(importPath, GnoRealmPkgsPrefixBefore) - - if !astutil.RewriteImport(fset, f, importPath, target) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, target)) - } + transp := TranspileImportPath(importPath) + if !astutil.RewriteImport(fset, f, importPath, transp) { + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, transp)) } } } diff --git a/gnovm/stdlibs/bytes/boundary_test.gno b/gnovm/stdlibs/bytes/boundary_test.gno index 9873b1db987..5d77c576ff8 100644 --- a/gnovm/stdlibs/bytes/boundary_test.gno +++ b/gnovm/stdlibs/bytes/boundary_test.gno @@ -1,8 +1,6 @@ // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// -//go:build linux package bytes_test diff --git a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno index 903e2653572..91e5481fc42 100644 --- a/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno +++ b/gnovm/stdlibs/crypto/chacha20/chacha/chacha_ref.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a license that can be // found in the LICENSE file. -//go:build (!amd64 && !386) || gccgo || appengine || nacl - package chacha import "encoding/binary" diff --git a/gnovm/stdlibs/internal/bytealg/compare_generic.gno b/gnovm/stdlibs/internal/bytealg/compare_generic.gno index e1795e47e9a..b56d0a67e02 100644 --- a/gnovm/stdlibs/internal/bytealg/compare_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/compare_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !wasm && !mips64 && !mips64le - package bytealg // import _ "unsafe" // for go:linkname @@ -35,7 +33,6 @@ samebytes: return 0 } -//go:linkname runtime_cmpstring runtime.cmpstring func runtime_cmpstring(a, b string) int { l := len(a) if len(b) < l { diff --git a/gnovm/stdlibs/internal/bytealg/count_generic.gno b/gnovm/stdlibs/internal/bytealg/count_generic.gno index 932a7c584c1..de08418fcaa 100644 --- a/gnovm/stdlibs/internal/bytealg/count_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/count_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm && !arm64 && !ppc64le && !ppc64 && !riscv64 && !s390x - package bytealg func Count(b []byte, c byte) int { diff --git a/gnovm/stdlibs/internal/bytealg/index_generic.gno b/gnovm/stdlibs/internal/bytealg/index_generic.gno index a59e32938e7..d751b1bc940 100644 --- a/gnovm/stdlibs/internal/bytealg/index_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/index_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !amd64 && !arm64 && !s390x && !ppc64le && !ppc64 - package bytealg const MaxBruteForce = 0 diff --git a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno index 0a45f903843..47aee225df9 100644 --- a/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno +++ b/gnovm/stdlibs/internal/bytealg/indexbyte_generic.gno @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !386 && !amd64 && !s390x && !arm && !arm64 && !ppc64 && !ppc64le && !mips && !mipsle && !mips64 && !mips64le && !riscv64 && !wasm - package bytealg func IndexByte(b []byte, c byte) int { diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index 6ee52cfe293..1bdc860adae 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -476,28 +476,29 @@ func (l *LimitedReader) Read(p []byte) (n int, err error) { return } -// NewSectionReader returns a SectionReader that reads from r +// NewSectionReader returns a [SectionReader] that reads from r // starting at offset off and stops with EOF after n bytes. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { var remaining int64 - const maxInt64 = 1<<63 - 1 - if off <= maxInt64-n { + const maxint64 = 1<<63 - 1 + if off <= maxint64-n { remaining = n + off } else { // Overflow, with no way to return error. - // Assume we can read up to an offset of `1<<63 - 1` bytes in this case. - remaining = maxInt64 + // Assume we can read up to an offset of 1<<63 - 1. + remaining = maxint64 } - return &SectionReader{r, off, off, off + n} + return &SectionReader{r, off, off, remaining, n} } // SectionReader implements Read, Seek, and ReadAt on a section -// of an underlying ReaderAt. +// of an underlying [ReaderAt]. type SectionReader struct { - r ReaderAt - base int64 + r ReaderAt // constant after creation + base int64 // constant after creation off int64 - limit int64 + limit int64 // constant after creation + n int64 // constant after creation } func (s *SectionReader) Read(p []byte) (n int, err error) { @@ -512,10 +513,8 @@ func (s *SectionReader) Read(p []byte) (n int, err error) { return } -var ( - errWhence = errors.New("Seek: invalid whence") - errOffset = errors.New("Seek: invalid offset") -) +var errWhence = errors.New("Seek: invalid whence") +var errOffset = errors.New("Seek: invalid offset") func (s *SectionReader) Seek(offset int64, whence int) (int64, error) { switch whence { @@ -536,7 +535,7 @@ func (s *SectionReader) Seek(offset int64, whence int) (int64, error) { } func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { - if off < 0 || off >= s.limit-s.base { + if off < 0 || off >= s.Size() { return 0, EOF } off += s.base @@ -554,6 +553,14 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { // Size returns the size of the section in bytes. func (s *SectionReader) Size() int64 { return s.limit - s.base } +// Outer returns the underlying [ReaderAt] and offsets for the section. +// +// The returned values are the same that were passed to [NewSectionReader] +// when the [SectionReader] was created. +func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64) { + return s.r, s.base, s.n +} + // An OffsetWriter maps writers at offset base to offset base + off in the underlying writer. type OffsetWriter struct { w WriterAt From 072c9a824569cf3e47fee3bceea78715da67493c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 21:09:00 +0100 Subject: [PATCH 04/67] begin removing support for linked identifiers --- gnovm/stdlibs/std/banker.gno | 68 +++++++++++---- gnovm/stdlibs/std/banker.go | 138 ++++++++++++------------------ gnovm/stdlibs/std/context.gno | 4 - gnovm/stdlibs/std/context.go | 2 +- gnovm/stdlibs/std/native.gno | 73 ++++++++++++---- gnovm/stdlibs/std/native.go | 154 +++++++++++----------------------- 6 files changed, 211 insertions(+), 228 deletions(-) delete mode 100644 gnovm/stdlibs/std/context.gno diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 44e611b780f..9eea149062e 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -22,10 +22,11 @@ type Banker interface { RemoveCoin(addr Address, denom string, amount int64) } -// Also available natively in stdlibs/context.go +// BankerType represents the "permission level" requested for a banker, +// retrievable through [GetBanker]. type BankerType uint8 -// Also available natively in stdlibs/context.go +// Available types of banker. const ( // Can only read state. BankerTypeReadonly BankerType = iota @@ -35,36 +36,69 @@ const ( BankerTypeRealmSend // Can issue and remove realm coins. BankerTypeRealmIssue + + maxBanker ) //---------------------------------------- // adapter for native banker -type bankAdapter struct { - nativeBanker Banker +// GetBanker returns a new Banker, with its capabilities matching the given +// [BankerType]. +func GetBanker(bt BankerType) Banker { + if bt >= maxBanker { + panic("invalid banker type") + } + return banker{bt} +} + +// These are native bindings to the banker's functions. +func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) +func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) +func bankerTotalCoin(bt uint8, denom string) int64 +func bankerIssueCoin(bt uint8, addr string, denom string, amount string) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) + +type banker struct { + bt uint8 } -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) +func (b banker) GetCoins(addr Address) (dst Coins) { + denoms, amounts := bankerGetCoins(b.bt, string(addr)) + dst = make(Coins, len(denoms)) + for i := range dst { + dst[i] = Coin{denoms[i], amounts[i]} } return dst } -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) +func (b banker) SendCoins(from, to Address, amt Coins) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot send coins") + } + denoms := make([]string, len(amt)) + amounts := make([]int64, len(amt)) + for i, coin := range amt { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) } -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) +func (b banker) TotalCoin(denom string) int64 { + return bankerTotalCoin(b.bt, denom) } -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) +func (b banker) IssueCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot issue coins") + } + bankerIssueCoin(b.bt, string(addr), denom, amount) } -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) +func (b banker) RemoveCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot remove coins") + } + bankerRemoveCoin(b.bt, string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 7653f2a519f..695b4a26aa5 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -3,16 +3,17 @@ package std import ( "fmt" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) -// This has the same interface as stdlibs/std.Banker. -// The native implementation of Banker (wrapped by any -// wrappers for limiting functionality as necessary) -// becomes available in Gno that implements -// stdlibs/std.Banker. -type Banker interface { +// BankerInterface is the interface through which Gno is capable of accessing +// the blockchain's banker. +// +// The name is what it is to avoid a collision with Gno's Banker, when +// transpiling. +type BankerInterface interface { GetCoins(addr crypto.Bech32Address) (dst std.Coins) SendCoins(from, to crypto.Bech32Address, amt std.Coins) TotalCoin(denom string) int64 @@ -20,98 +21,63 @@ type Banker interface { RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) } -// Used in std.GetBanker(options). -// Also available as Gno in stdlibs/std/banker.gno -type BankerType uint8 - -// Also available as Gno in stdlibs/std/banker.gno const ( // Can only read state. - BankerTypeReadonly BankerType = iota + btReadonly uint8 = iota // Can only send from tx send. - BankerTypeOrigSend + btOrigSend // Can send from all realm coins. - BankerTypeRealmSend + btRealmSend // Can issue and remove realm coins. - BankerTypeRealmIssue + btRealmIssue ) -//---------------------------------------- -// ReadonlyBanker - -type ReadonlyBanker struct { - banker Banker -} - -func NewReadonlyBanker(banker Banker) ReadonlyBanker { - return ReadonlyBanker{banker} -} - -func (rb ReadonlyBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rb.banker.GetCoins(addr) -} - -func (rb ReadonlyBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - panic("ReadonlyBanker cannot send coins") -} - -func (rb ReadonlyBanker) TotalCoin(denom string) int64 { - return rb.banker.TotalCoin(denom) -} - -func (rb ReadonlyBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot issue coins") -} - -func (rb ReadonlyBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot remove coins") -} - -//---------------------------------------- -// OrigSendBanker - -type OrigSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address - origSend std.Coins - origSendSpent *std.Coins -} - -func NewOrigSendBanker(banker Banker, pkgAddr crypto.Bech32Address, origSend std.Coins, origSendSpent *std.Coins) OrigSendBanker { - if origSendSpent == nil { - panic("origSendSpent cannot be nil") - } - return OrigSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - origSend: origSend, - origSendSpent: origSendSpent, +func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) + denoms = make([]string, len(coins)) + amounts = make([]string, len(coins)) + for i, coin := range coins { + denoms[i] = coin.Denom + amounts[i] = coin.Amounts } -} - -func (osb OrigSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return osb.banker.GetCoins(addr) -} - -func (osb OrigSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) - } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + return denoms, amounts +} + +// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) +// - Make all the X_banker functions work as they should +// - Remove the identifier mapping logic from genstd. +// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. +// - Make precompilation understand that bodyless function -> native function. +// - Add whether the *gno.Machine parameter is present to the native function data. +// When you get back on track for the transpiler: +// - Make StaticCheck work +// - Add a way to recursively precompile dependencies +// - Work until gno transpile --gobuild can create fully buildable go code! + +func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { + // bt != BankerTypeReadonly (checked in gno) + if bt == btOrigSend { + if from != osb.pkgAddr { + panic(fmt.Sprintf( + "OrigSendBanker can only send from the realm package address %q, but got %q", + osb.pkgAddr, from)) + } + spent := (*osb.origSendSpent).Add(amt) + if !osb.origSend.IsAllGTE(spent) { + panic(fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, osb.origSend, *osb.origSendSpent)) + } + osb.banker.SendCoins(from, to, amt) + *osb.origSendSpent = spent } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent } -func (osb OrigSendBanker) TotalCoin(denom string) int64 { - return osb.banker.TotalCoin(denom) +func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { + return m.Context.(ExecContext).Banker.TotalCoin(denom) } +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { panic("OrigSendBanker cannot issue coins") diff --git a/gnovm/stdlibs/std/context.gno b/gnovm/stdlibs/std/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/std/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index c50e2e5e1b9..72ca7445aef 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -16,5 +16,5 @@ type ExecContext struct { OrigPkgAddr crypto.Bech32Address OrigSend std.Coins OrigSendSpent *std.Coins // mutable - Banker Banker + Banker BankerInterface } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 2f7da810bcb..b922376bc71 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,18 +1,59 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func CurrentRealmPath() string // injected -func GetChainID() string // injected -func GetHeight() int64 // injected -func GetOrigSend() Coins // injected -func GetOrigCaller() Address // injected -func CurrentRealm() Realm // injected -func PrevRealm() Realm // injected -func GetOrigPkgAddr() Address // injected -func GetCallerAt(n int) Address // injected -func GetBanker(bt BankerType) Banker // injected -func DerivePkgAddr(pkgPath string) Address // injected - -func EncodeBech32(prefix string, bz [20]byte) Address // injected -func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected + +func GetOrigSend() Coins { + den, amt := origSend() + coins := make(Coins, len(den)) + for i := range coins { + coins[i] = Coin{Denom: den[i], Amount: amt[i]} + } + return coins +} + +func GetOrigCaller() Address { + return Address(origCaller()) +} + +func CurrentRealm() Realm { + addr, path := getRealm(0) + return Realm{addr, path} +} + +func PrevRealm() Realm { + addr, path := getRealm(1) + return Realm{addr, path} +} + +func GetOrigPkgAddr() Address { + return Address(origPkgAddr()) +} + +func GetCallerAt(n int) Address { + return Address(callerAt(n)) +} +func DerivePkgAddr(pkgPath string) Address { + return Address(derivePkgAddr(pkgPath)) +} + +func EncodeBech32(prefix string, bz [20]byte) Address { + return Address(encodeBech32(prefix, bz)) +} + +func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { + return decodeBech32(string(addr)) +} + +// Variations which don't use named types. +func origSend() (denoms []string, amounts []int64) +func origCaller() string +func origPkgAddr() string +func callerAt(n int) string +func getRealm(height int) (address string, pkgPath string) +func derivePkgAddr(pkgPath string) string +func encodeBech32(prefix string, bz [20]byte) string +func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 044badaa308..344db126fb8 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,12 +1,9 @@ package std import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -34,82 +31,26 @@ func GetHeight(m *gno.Machine) int64 { return m.Context.(ExecContext).Height } -func GetOrigSend(m *gno.Machine) std.Coins { - return m.Context.(ExecContext).OrigSend -} - -func GetOrigCaller(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigCaller -} - -func CurrentRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage != nil && fr.LastPackage.IsRealm() { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = fr.LastPackage.PkgPath - break - } - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, +func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := m.Context.(ExecContext).OrigSend + denoms = make([]string, len(os)) + amounts = make([]int64, len(os)) + for i, coin := range os { + denoms[i] = coin.Denom + amounts[i] = coin.Amount } + return denoms, amounts } -func PrevRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { - // Ignore non-realm frame - continue - } - pkgPath := fr.LastPackage.PkgPath - // The first realm we encounter will be the one calling - // this function; to get the calling realm determine the first frame - // where fr.LastPackage changes. - if lastPkgPath == "" { - lastPkgPath = pkgPath - } else if lastPkgPath == pkgPath { - continue - } else { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = pkgPath - break - } - } - - // Empty the pkgPath if we return a user - if ctx.OrigCaller == lastCaller { - lastPkgPath = "" - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - } +func X_origCaller(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigCaller) } -func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigPkgAddr +func X_origPkgAddr(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigPkgAddr) } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" @@ -124,52 +65,57 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { if n == m.NumFrames() { // This makes it consistent with GetOrigCaller. ctx := m.Context.(ExecContext) - return ctx.OrigCaller + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { - ctx := m.Context.(ExecContext) - banker := ctx.Banker - switch bankerType { - case BankerTypeReadonly: - banker = NewReadonlyBanker(banker) - case BankerTypeOrigSend: - banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) - case BankerTypeRealmSend: - banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) - case BankerTypeRealmIssue: - banker = banker - default: - panic("should not happen") // defensive +func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { + var ( + ctx = m.Context.(ExecContext) + currentCaller crypto.Bech32Address + // Keeps track of the number of times currentCaller + // has changed. + changes int + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // current* values. + caller := fr.LastPackage.GetPkgAddr().Bech32() + pkgPath := fr.LastPackage.PkgPath + if caller != currentCaller { + if changes == height { + return string(caller), pkgPath + } + currentCaller = caller + changes++ + } } - m.Alloc.AllocateStruct() // defensive; native space not allocated. - m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - // make gno bankAdapter{rv} - btv := gno.Go2GnoNativeValue(m.Alloc, reflect.ValueOf(banker)) - bsv := m.Alloc.NewStructWithFields(btv) - bankAdapterType := m.Store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) - res0 := gno.TypedValue{T: bankAdapterType, V: bsv} + // Fallback case: return OrigCaller. + return string(ctx.OrigCaller), "" +} - return res0 +func X_derivePkgAddr(pkgPath string) string { + return string(gno.DerivePkgAddr(pkgPath).Bech32()) } -func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { +func X_encodeBech32(prefix string, bytes [20]byte) string { b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) if err != nil { panic(err) // should not happen } - return crypto.Bech32Address(b32) -} - -func DerivePkgAddr(pkgPath string) crypto.Bech32Address { - return gno.DerivePkgAddr(pkgPath).Bech32() + return b32 } -func DecodeBech32(addr crypto.Bech32Address) (prefix string, bytes [20]byte, ok bool) { - prefix, bz, err := bech32.Decode(string(addr)) +func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { + prefix, bz, err := bech32.Decode(addr) if err != nil || len(bz) != 20 { return "", [20]byte{}, false } From 37c9e6690b64703670a9049191a7504cd6027fab Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 21:15:29 +0100 Subject: [PATCH 05/67] fixup --- .github/workflows/examples.yml | 2 +- docs/getting-started/local-setup.md | 2 +- gnovm/cmd/gno/test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index e5178c3e4c2..53d6a58abdd 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -32,7 +32,7 @@ jobs: with: go-version: ${{ matrix.goversion }} - run: go install -v ./gnovm/cmd/gno - - run: go run ./gnovm/cmd/gno precompile --verbose --gobuild ./examples + - run: go run ./gnovm/cmd/gno transpile --verbose --gobuild ./examples test: strategy: fail-fast: false diff --git a/docs/getting-started/local-setup.md b/docs/getting-started/local-setup.md index cbef4522477..753b6688b1a 100644 --- a/docs/getting-started/local-setup.md +++ b/docs/getting-started/local-setup.md @@ -28,7 +28,7 @@ git clone https://github.com/gnolang/gno.git ## 2. Installing the `gno` development toolkit Next, we are going to build and install the `gno` development toolkit. -`gno` provides ample functionality to the user, among which is running, precompiling, testing and building `.gno` files. +`gno` provides ample functionality to the user, among which is running, transpiling, testing and building `.gno` files. To install the toolkit, navigate to the `gnovm` folder from the repository root, and run the `build` make directive: diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index a02c1abf192..ca64227aada 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -19,8 +19,8 @@ import ( "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/gnovm/tests" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/errors" From 34133bdef022eca398b8d9457047c9302bbde9ab Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:56:05 +0100 Subject: [PATCH 06/67] convert native bindings to never use linked types --- gnovm/stdlibs/native.go | 270 ++++++++++++++++++------ gnovm/stdlibs/std/banker.gno | 48 +++-- gnovm/stdlibs/std/banker.go | 118 ++++------- gnovm/stdlibs/std/coins.gno | 12 +- gnovm/stdlibs/std/frame.go | 20 -- gnovm/stdlibs/std/native.gno | 4 +- gnovm/stdlibs/std/native.go | 33 ++- gnovm/stdlibs/stdlibs.go | 1 - gnovm/tests/files/zrealm_natbind0.gno | 57 ++--- gnovm/tests/stdlibs/native.go | 64 +++--- gnovm/tests/stdlibs/std/std.gno | 38 +++- gnovm/tests/stdlibs/std/std.go | 40 ++-- misc/genstd/exprstring.go | 290 -------------------------- misc/genstd/mapping.go | 98 +-------- 14 files changed, 431 insertions(+), 662 deletions(-) delete mode 100644 gnovm/stdlibs/std/frame.go delete mode 100644 misc/genstd/exprstring.go diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 4125c56ffe1..0cb7d9a7859 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -13,7 +13,6 @@ import ( libs_strconv "github.com/gnolang/gno/gnovm/stdlibs/strconv" libs_testing "github.com/gnolang/gno/gnovm/stdlibs/testing" libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" ) type nativeFunc struct { @@ -162,27 +161,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "AssertOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{}, + "bankerGetCoins", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, + }, func(m *gno.Machine) { - libs_std.AssertOriginCall( - m, + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := libs_std.X_bankerGetCoins( + m, + p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, + "bankerSendCoins", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("[]string")}, + {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.IsOriginCall( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []string + rp3 = reflect.ValueOf(&p3).Elem() + p4 []int64 + rp4 = reflect.ValueOf(&p4).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 4, "")).TV, rp4) + + libs_std.X_bankerSendCoins( m, + p0, p1, p2, p3, p4) + }, + }, + { + "std", + "bankerTotalCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_std.X_bankerTotalCoin( + m, + p0, p1) + m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, @@ -192,32 +270,90 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealmPath", - []gno.FieldTypeExpr{}, + "bankerIssueCoin", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.CurrentRealmPath( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerIssueCoin( m, + p0, p1, p2, p3) + }, + }, + { + "std", + "bankerRemoveCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() ) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerRemoveCoin( + m, + p0, p1, p2, p3) }, }, { "std", - "GetChainID", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + libs_std.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r0"), Type: gno.X("bool")}, }, func(m *gno.Machine) { - r0 := libs_std.GetChainID( + r0 := libs_std.IsOriginCall( m, ) @@ -230,13 +366,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetHeight", + "CurrentRealmPath", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("int64")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetHeight( + r0 := libs_std.CurrentRealmPath( m, ) @@ -249,13 +385,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigSend", + "GetChainID", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Coins")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigSend( + r0 := libs_std.GetChainID( m, ) @@ -268,13 +404,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigCaller", + "GetHeight", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigCaller( + r0 := libs_std.GetHeight( m, ) @@ -287,13 +423,14 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealm", + "origSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, func(m *gno.Machine) { - r0 := libs_std.CurrentRealm( + r0, r1 := libs_std.X_origSend( m, ) @@ -302,17 +439,22 @@ var nativeFuncs = [...]nativeFunc{ m.Store, reflect.ValueOf(&r0).Elem(), )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "PrevRealm", + "origCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.PrevRealm( + r0 := libs_std.X_origCaller( m, ) @@ -325,13 +467,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigPkgAddr", + "origPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigPkgAddr( + r0 := libs_std.X_origPkgAddr( m, ) @@ -344,12 +486,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -360,7 +502,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetCallerAt( + r0 := libs_std.X_callerAt( m, p0) @@ -373,37 +515,47 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetBanker", + "getRealm", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("BankerType")}, + {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Banker")}, + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 libs_std.BankerType + p0 int rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetBanker( + r0, r1 := libs_std.X_getRealm( m, p0) - m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "DerivePkgAddr", + "derivePkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -414,7 +566,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.DerivePkgAddr(p0) + r0 := libs_std.X_derivePkgAddr(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -425,13 +577,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "EncodeBech32", + "encodeBech32", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, {Name: gno.N("p1"), Type: gno.X("[20]byte")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -445,7 +597,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - r0 := libs_std.EncodeBech32(p0, p1) + r0 := libs_std.X_encodeBech32(p0, p1) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -456,9 +608,9 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "DecodeBech32", + "decodeBech32", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, @@ -468,13 +620,13 @@ var nativeFuncs = [...]nativeFunc{ func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0, r1, r2 := libs_std.DecodeBech32(p0) + r0, r1, r2 := libs_std.X_decodeBech32(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 9eea149062e..958fea369a3 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -1,5 +1,7 @@ package std +import "strconv" + // Realm functions can call std.GetBanker(options) to get // a banker instance. Banker objects cannot be persisted, // but can be passed onto other functions to be transacted @@ -40,6 +42,21 @@ const ( maxBanker ) +func (b BankerType) String() string { + switch b { + case BankerTypeReadonly: + return "BankerTypeReadonly" + case BankerTypeOrigSend: + return "BankerTypeOrigSend" + case BankerTypeRealmSend: + return "BankerTypeRealmSend" + case BankerTypeRealmIssue: + return "BankerTypeRealmIssue" + default: + panic("invalid BankerType: " + strconv.Itoa(int(b))) + } +} + //---------------------------------------- // adapter for native banker @@ -56,15 +73,15 @@ func GetBanker(bt BankerType) Banker { func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) func bankerTotalCoin(bt uint8, denom string) int64 -func bankerIssueCoin(bt uint8, addr string, denom string, amount string) -func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) +func bankerIssueCoin(bt uint8, addr string, denom string, amount int64) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount int64) type banker struct { - bt uint8 + bt BankerType } func (b banker) GetCoins(addr Address) (dst Coins) { - denoms, amounts := bankerGetCoins(b.bt, string(addr)) + denoms, amounts := bankerGetCoins(uint8(b.bt), string(addr)) dst = make(Coins, len(denoms)) for i := range dst { dst[i] = Coin{denoms[i], amounts[i]} @@ -76,29 +93,24 @@ func (b banker) SendCoins(from, to Address, amt Coins) { if b.bt == BankerTypeReadonly { panic("BankerTypeReadonly cannot send coins") } - denoms := make([]string, len(amt)) - amounts := make([]int64, len(amt)) - for i, coin := range amt { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) + denoms, amounts := amt.expandNative() + bankerSendCoins(uint8(b.bt), string(from), string(to), denoms, amounts) } func (b banker) TotalCoin(denom string) int64 { - return bankerTotalCoin(b.bt, denom) + return bankerTotalCoin(uint8(b.bt), denom) } func (b banker) IssueCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot issue coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot issue coins") } - bankerIssueCoin(b.bt, string(addr), denom, amount) + bankerIssueCoin(uint8(b.bt), string(addr), denom, amount) } func (b banker) RemoveCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot remove coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot remove coins") } - bankerRemoveCoin(b.bt, string(addr), denom, amount) + bankerRemoveCoin(uint8(b.bt), string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 695b4a26aa5..243d457b9be 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -33,96 +33,58 @@ const ( ) func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { - coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) - denoms = make([]string, len(coins)) - amounts = make([]string, len(coins)) - for i, coin := range coins { - denoms[i] = coin.Denom - amounts[i] = coin.Amounts - } - return denoms, amounts + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) + return ExpandCoins(coins) } -// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) -// - Make all the X_banker functions work as they should -// - Remove the identifier mapping logic from genstd. -// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. -// - Make precompilation understand that bodyless function -> native function. -// - Add whether the *gno.Machine parameter is present to the native function data. -// When you get back on track for the transpiler: -// - Make StaticCheck work -// - Add a way to recursively precompile dependencies -// - Work until gno transpile --gobuild can create fully buildable go code! - -func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { +func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []string, amounts []int64) { // bt != BankerTypeReadonly (checked in gno) - if bt == btOrigSend { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) + + ctx := m.Context.(ExecContext) + amt := CompactCoins(denoms, amounts) + from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) + + if bt == btOrigSend || bt == btRealmSend { + if from != ctx.OrigPkgAddr { + m.Panic(typedString( + fmt.Sprintf( + "can only send from the realm package address %q, but got %q", + ctx.OrigPkgAddr, from), + )) + return } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + } + + switch bt { + case btOrigSend: + // indirection allows us to "commit" in a second phase + spent := (*ctx.OrigSendSpent).Add(amt) + if !ctx.OrigSend.IsAllGTE(spent) { + m.Panic(typedString( + fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, ctx.OrigSend, *ctx.OrigSendSpent), + )) } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent + ctx.Banker.SendCoins(from, to, amt) + *ctx.OrigSendSpent = spent + case btRealmSend, btRealmIssue: + ctx.Banker.SendCoins(from, to, amt) + default: + panic(fmt.Sprintf("invalid banker type %d in bankerSendCoins", bt)) } } func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { return m.Context.(ExecContext).Banker.TotalCoin(denom) } -func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) -func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) - -func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot issue coins") -} - -func (osb OrigSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot remove coins") -} - -//---------------------------------------- -// RealmSendBanker - -type RealmSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address -} - -func NewRealmSendBanker(banker Banker, pkgAddr crypto.Bech32Address) RealmSendBanker { - return RealmSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - } -} - -func (rsb RealmSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rsb.banker.GetCoins(addr) -} - -func (rsb RealmSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != rsb.pkgAddr { - panic(fmt.Sprintf( - "RealmSendBanker can only send from the realm package address %q, but got %q", - rsb.pkgAddr, from)) - } - rsb.banker.SendCoins(from, to, amt) -} - -func (rsb RealmSendBanker) TotalCoin(denom string) int64 { - return rsb.banker.TotalCoin(denom) -} -func (rsb RealmSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot issue coins") +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } -func (rsb RealmSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot remove coins") +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index aaede81309d..dd138d30822 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -4,6 +4,7 @@ import "strconv" // NOTE: this is selectly copied over from pkgs/std/coin.go // TODO: import all functionality(?). +// TODO: implement Coin/Coins constructors. // Coin hold some amount of one currency. // A negative amount is invalid. @@ -62,4 +63,13 @@ func (a Coins) Add(b Coins) Coins { return c } -// TODO implement Coin/Coins constructors. +// expand for usage within natively bound functions. +func (cz Coins) expandNative() (denoms []string, amounts []int64) { + denoms = make([]string, len(cz)) + amounts = make([]int64, len(cz)) + for i, coin := range cz { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} diff --git a/gnovm/stdlibs/std/frame.go b/gnovm/stdlibs/std/frame.go deleted file mode 100644 index 2948813ad0f..00000000000 --- a/gnovm/stdlibs/std/frame.go +++ /dev/null @@ -1,20 +0,0 @@ -package std - -import "github.com/gnolang/gno/tm2/pkg/crypto" - -type Realm struct { - addr crypto.Bech32Address - pkgPath string -} - -func (r Realm) Addr() crypto.Bech32Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index b922376bc71..c079e0dd42b 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -21,12 +21,12 @@ func GetOrigCaller() Address { func CurrentRealm() Realm { addr, path := getRealm(0) - return Realm{addr, path} + return Realm{Address(addr), path} } func PrevRealm() Realm { addr, path := getRealm(1) - return Realm{addr, path} + return Realm{Address(addr), path} } func GetOrigPkgAddr() Address { diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 344db126fb8..0965ef74277 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -4,6 +4,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -33,13 +34,7 @@ func GetHeight(m *gno.Machine) int64 { func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { os := m.Context.(ExecContext).OrigSend - denoms = make([]string, len(os)) - amounts = make([]int64, len(os)) - for i, coin := range os { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - return denoms, amounts + return ExpandCoins(os) } func X_origCaller(m *gno.Machine) string { @@ -55,6 +50,8 @@ func X_callerAt(m *gno.Machine, n int) string { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -122,8 +119,26 @@ func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { return prefix, [20]byte(bz), true } -func typedString(s gno.StringValue) gno.TypedValue { +func typedString(s string) gno.TypedValue { tv := gno.TypedValue{T: gno.StringType} - tv.SetString(s) + tv.SetString(gno.StringValue(s)) return tv } + +func ExpandCoins(c std.Coins) (denoms []string, amounts []int64) { + denoms = make([]string, len(c)) + amounts = make([]int64, len(c)) + for i, coin := range c { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} + +func CompactCoins(denoms []string, amounts []int64) std.Coins { + coins := make(std.Coins, len(denoms)) + for i := range coins { + coins[i] = std.Coin{Denom: denoms[i], Amount: amounts[i]} + } + return coins +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 22054613c03..1e215b098e2 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -17,7 +17,6 @@ func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") - store.AddGo2GnoMapping(reflect.TypeOf(libsstd.Realm{}), "std", "Realm") } func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 60e0d448202..0d614f5e8a1 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -8,20 +8,23 @@ import ( var node interface{} func init() { - node = std.GetOrigCaller + node = std.GetHeight } func main() { - f := node.(func() std.Address) + // NOTE: this test uses GetHeight and CurrentRealmPath, which are "pure" + // natively bound functions (ie. not indirections through a wrapper fn, + // to convert the types to builtin go/gno identifiers). + f := node.(func() int64) println(f()) - node = std.DerivePkgAddr - g := node.(func(path string) std.Address) - println(g("x")) + node = std.CurrentRealmPath + g := node.(func() string) + println(g()) } // Output: -// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// g19kt9e22k34ny5jf5plrjdltmws0jc0qqd2cwky +// 123 +// gno.land/r/test // Realm: // switchrealm["gno.land/r/test"] @@ -120,25 +123,15 @@ func main() { // { // "T": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] @@ -148,12 +141,12 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:5" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:6" // }, // "FileName": "native.gno", // "IsMethod": false, -// "Name": "DerivePkgAddr", -// "NativeName": "DerivePkgAddr", +// "Name": "CurrentRealmPath", +// "NativeName": "CurrentRealmPath", // "NativePkg": "std", // "PkgPath": "std", // "Source": { @@ -161,32 +154,22 @@ func main() { // "BlockNode": null, // "Location": { // "File": "native.gno", -// "Line": "15", +// "Line": "5", // "Nonce": "0", // "PkgPath": "std" // } // }, // "Type": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 373be0ad23b..6964a003025 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -9,8 +9,6 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" - tm2_std "github.com/gnolang/gno/tm2/pkg/std" ) type nativeFunc struct { @@ -105,12 +103,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -121,7 +119,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := testlibs_std.GetCallerAt( + r0 := testlibs_std.X_callerAt( m, p0) @@ -134,94 +132,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "TestSetOrigCaller", + "testSetOrigCaller", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigCaller( + testlibs_std.X_testSetOrigCaller( m, p0) }, }, { "std", - "TestSetOrigPkgAddr", + "testSetOrigPkgAddr", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigPkgAddr( + testlibs_std.X_testSetOrigPkgAddr( m, p0) }, }, { "std", - "TestSetOrigSend", + "testSetOrigSend", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Coins")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("[]string")}, + {Name: gno.N("p1"), Type: gno.X("[]int64")}, + {Name: gno.N("p2"), Type: gno.X("[]string")}, + {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_std.Coins + p0 []string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []int64 rp1 = reflect.ValueOf(&p1).Elem() + p2 []string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []int64 + rp3 = reflect.ValueOf(&p3).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - testlibs_std.TestSetOrigSend( + testlibs_std.X_testSetOrigSend( m, - p0, p1) + p0, p1, p2, p3) }, }, { "std", - "TestIssueCoins", + "testIssueCoins", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[]string")}, + {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []string rp1 = reflect.ValueOf(&p1).Elem() + p2 []int64 + rp2 = reflect.ValueOf(&p2).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) - testlibs_std.TestIssueCoins( + testlibs_std.X_testIssueCoins( m, - p0, p1) + p0, p1, p2) }, }, { diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 380549be694..2b142634740 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,12 +1,30 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func TestCurrentRealm() string // injected -func TestSkipHeights(count int64) // injected -func ClearStoreCache() // injected -func GetCallerAt(n int) Address // injected -func TestSetOrigCaller(addr Address) // injected -func TestSetOrigPkgAddr(addr Address) // injected -func TestSetOrigSend(sent, spent Coins) // injected -func TestIssueCoins(addr Address, coins Coins) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected + +func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } +func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOrigSend(sent, spent Coins) { + sentDenom, sentAmt := sent.expandNative() + spentDenom, spentAmt := spent.expandNative() + testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) +} +func TestIssueCoins(addr Address, coins Coins) { + denom, amt := coins.expandNative() + testIssueCoins(string(addr), denom, amt) +} + +// GetCallerAt calls callerAt, which we overwrite +func callerAt(n int) string + +// native bindings +func testSetOrigCaller(s string) +func testSetOrigPkgAddr(s string) +func testSetOrigSend( + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64) +func testIssueCoins(addr string, denom []string, amt []int64) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 27ad079b4a6..0e15c8d0241 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -6,10 +6,8 @@ import ( "testing" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" - tm2std "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -68,11 +66,13 @@ func ClearStoreCache(m *gno.Machine) { } } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -82,35 +82,39 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(stdlibs.ExecContext) - return ctx.OrigCaller + ctx := m.Context.(std.ExecContext) + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { +func X_testSetOrigCaller(m *gno.Machine, addr string) { ctx := m.Context.(std.ExecContext) - ctx.OrigCaller = addr + ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigPkgAddr(m *gno.Machine, addr crypto.Bech32Address) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigPkgAddr = addr +func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { + ctx := m.Context.(std.ExecContext) + ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigSend(m *gno.Machine, sent, spent tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigSend = sent +func X_testSetOrigSend(m *gno.Machine, + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64, +) { + ctx := m.Context.(std.ExecContext) + ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent m.Context = ctx } -func TestIssueCoins(m *gno.Machine, addr crypto.Bech32Address, coins tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) +func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { + ctx := m.Context.(std.ExecContext) banker := ctx.Banker - for _, coin := range coins { - banker.IssueCoin(addr, coin.Denom, coin.Amount) + for i := range denom { + banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) } } diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go deleted file mode 100644 index c95c05c584e..00000000000 --- a/misc/genstd/exprstring.go +++ /dev/null @@ -1,290 +0,0 @@ -// Forked from go/types (go 1.20.3) to implement support for *linkedIdent. -// It cannot be easily split from the original as WriteExpr is highly recursive. - -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements printing of expressions. - -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/types" -) - -const ( - printerModeGoQualified = iota - printerModeGnoType -) - -type exprPrinter struct { - mode int -} - -// ExprString returns the (possibly shortened) string representation for x. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// ExprString is identical to [types.ExprString] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) ExprString(x ast.Expr) string { - var buf bytes.Buffer - ep.WriteExpr(&buf, x) - return buf.String() -} - -// WriteExpr writes the (possibly shortened) string representation for x to buf. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// WriteExpr is identical to [types.WriteExpr] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) WriteExpr(buf *bytes.Buffer, x ast.Expr) { - // The AST preserves source-level parentheses so there is - // no need to introduce them here to correct for different - // operator precedences. (This assumes that the AST was - // generated by a Go parser.) - - switch x := x.(type) { - default: - // fallback to go original -- for all non-recursive ast.Expr types - types.WriteExpr(buf, x) - - case *linkedIdent: - switch ep.mode { - case printerModeGoQualified: - n := pkgNameFromPath(x.lt.goPackage) - buf.WriteString(n) - buf.WriteByte('.') - buf.WriteString(x.lt.goName) - case printerModeGnoType: - buf.WriteString(x.lt.gnoName) - default: - panic(fmt.Errorf("invalid mode %d", ep.mode)) - } - - case *ast.Ellipsis: - buf.WriteString("...") - if x.Elt != nil { - ep.WriteExpr(buf, x.Elt) - } - - case *ast.FuncLit: - buf.WriteByte('(') - ep.WriteExpr(buf, x.Type) - buf.WriteString(" literal)") // shortened - - case *ast.CompositeLit: - ep.WriteExpr(buf, x.Type) - buf.WriteByte('{') - if len(x.Elts) > 0 { - buf.WriteString("…") - } - buf.WriteByte('}') - - case *ast.ParenExpr: - buf.WriteByte('(') - ep.WriteExpr(buf, x.X) - buf.WriteByte(')') - - case *ast.SelectorExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('.') - buf.WriteString(x.Sel.Name) - - case *ast.IndexExpr, *ast.IndexListExpr: - ix := tpUnpackIndexExpr(x) - ep.WriteExpr(buf, ix.X) - buf.WriteByte('[') - ep.writeExprList(buf, ix.Indices) - buf.WriteByte(']') - - case *ast.SliceExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('[') - if x.Low != nil { - ep.WriteExpr(buf, x.Low) - } - buf.WriteByte(':') - if x.High != nil { - ep.WriteExpr(buf, x.High) - } - if x.Slice3 { - buf.WriteByte(':') - if x.Max != nil { - ep.WriteExpr(buf, x.Max) - } - } - buf.WriteByte(']') - - case *ast.TypeAssertExpr: - ep.WriteExpr(buf, x.X) - buf.WriteString(".(") - ep.WriteExpr(buf, x.Type) - buf.WriteByte(')') - - case *ast.CallExpr: - ep.WriteExpr(buf, x.Fun) - buf.WriteByte('(') - ep.writeExprList(buf, x.Args) - if x.Ellipsis.IsValid() { - buf.WriteString("...") - } - buf.WriteByte(')') - - case *ast.StarExpr: - buf.WriteByte('*') - ep.WriteExpr(buf, x.X) - - case *ast.UnaryExpr: - buf.WriteString(x.Op.String()) - ep.WriteExpr(buf, x.X) - - case *ast.BinaryExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte(' ') - buf.WriteString(x.Op.String()) - buf.WriteByte(' ') - ep.WriteExpr(buf, x.Y) - - case *ast.ArrayType: - buf.WriteByte('[') - if x.Len != nil { - ep.WriteExpr(buf, x.Len) - } - buf.WriteByte(']') - ep.WriteExpr(buf, x.Elt) - - case *ast.StructType: - buf.WriteString("struct{") - ep.writeFieldList(buf, x.Fields.List, "; ", false) - buf.WriteByte('}') - - case *ast.FuncType: - buf.WriteString("func") - ep.writeSigExpr(buf, x) - - case *ast.InterfaceType: - buf.WriteString("interface{") - ep.writeFieldList(buf, x.Methods.List, "; ", true) - buf.WriteByte('}') - - case *ast.MapType: - buf.WriteString("map[") - ep.WriteExpr(buf, x.Key) - buf.WriteByte(']') - ep.WriteExpr(buf, x.Value) - - case *ast.ChanType: - var s string - switch x.Dir { - case ast.SEND: - s = "chan<- " - case ast.RECV: - s = "<-chan " - default: - s = "chan " - } - buf.WriteString(s) - ep.WriteExpr(buf, x.Value) - } -} - -func (ep *exprPrinter) writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { - buf.WriteByte('(') - ep.writeFieldList(buf, sig.Params.List, ", ", false) - buf.WriteByte(')') - - res := sig.Results - n := res.NumFields() - if n == 0 { - // no result - return - } - - buf.WriteByte(' ') - if n == 1 && len(res.List[0].Names) == 0 { - // single unnamed result - ep.WriteExpr(buf, res.List[0].Type) - return - } - - // multiple or named result(s) - buf.WriteByte('(') - ep.writeFieldList(buf, res.List, ", ", false) - buf.WriteByte(')') -} - -func (ep *exprPrinter) writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { - for i, f := range list { - if i > 0 { - buf.WriteString(sep) - } - - // field list names - ep.writeIdentList(buf, f.Names) - - // types of interface methods consist of signatures only - if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { - ep.writeSigExpr(buf, sig) - continue - } - - // named fields are separated with a blank from the field type - if len(f.Names) > 0 { - buf.WriteByte(' ') - } - - ep.WriteExpr(buf, f.Type) - - // ignore tag - } -} - -func (ep *exprPrinter) writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(x.Name) - } -} - -func (ep *exprPrinter) writeExprList(buf *bytes.Buffer, list []ast.Expr) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - ep.WriteExpr(buf, x) - } -} - -// The following are copied from go/internal/typeparams. -// We cannot use the original directly as it comes from an "internal" package. - -// tpIndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. -// -// Orig holds the original ast.Expr from which this IndexExpr was derived. -type tpIndexExpr struct { - Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. - *ast.IndexListExpr -} - -func tpUnpackIndexExpr(n ast.Node) *tpIndexExpr { - switch e := n.(type) { - case *ast.IndexExpr: - return &tpIndexExpr{e, &ast.IndexListExpr{ - X: e.X, - Lbrack: e.Lbrack, - Indices: []ast.Expr{e.Index}, - Rbrack: e.Rbrack, - }} - case *ast.IndexListExpr: - return &tpIndexExpr{e, e} - } - return nil -} diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 0e4034a1ab9..d87484ab865 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "go/ast" + "go/types" "path" "strconv" ) @@ -24,8 +25,6 @@ type mapping struct { } type mappingType struct { - // type of ast.Expr is from the normal ast.Expr types - // + *linkedIdent. Type ast.Expr // IsTypedValue is set to true if the parameter or result in go is of type @@ -35,21 +34,11 @@ type mappingType struct { } func (mt mappingType) GoQualifiedName() string { - return (&exprPrinter{ - mode: printerModeGoQualified, - }).ExprString(mt.Type) + return types.ExprString(mt.Type) } func (mt mappingType) GnoType() string { - return (&exprPrinter{ - mode: printerModeGnoType, - }).ExprString(mt.Type) -} - -type linkedIdent struct { - ast.BadExpr // Unused, but it makes *linkedIdent implement ast.Expr - - lt linkedType + return types.ExprString(mt.Type) } func linkFunctions(pkgs []*pkgData) []mapping { @@ -208,50 +197,17 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) // mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is // (recursively) newly allocated. func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { - resolveGoNamed := func(lt *linkedType) bool { - switch goe := goe.(type) { - case *ast.SelectorExpr: - // selector - resolve pkg ident to path - lt.goPackage = resolveSelectorImport(m.goImports, goe) - lt.goName = goe.Sel.Name - case *ast.Ident: - // local name -- use import path of go pkg - lt.goPackage = m.GoImportPath - lt.goName = goe.Name - default: - return false - } - return true - } - switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type - case *ast.SelectorExpr: - lt := linkedType{ - gnoPackage: resolveSelectorImport(m.gnoImports, gnoe), - gnoName: gnoe.Sel.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} case *ast.Ident: // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { return &ast.Ident{Name: gnoe.Name} } - - lt := linkedType{ - gnoPackage: m.GnoImportPath, - gnoName: gnoe.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} + panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: @@ -283,7 +239,8 @@ func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { *ast.FuncType, *ast.InterfaceType, *ast.MapType, - *ast.Ellipsis: + *ast.Ellipsis, + *ast.SelectorExpr: // TODO panic("not implemented") default: @@ -426,46 +383,3 @@ func isBuiltin(name string) bool { } return false } - -type linkedType struct { - gnoPackage string - gnoName string - goPackage string - goName string -} - -var linkedTypes = [...]linkedType{ - { - "std", "Address", - "github.com/gnolang/gno/tm2/pkg/crypto", "Bech32Address", - }, - { - "std", "Coin", - "github.com/gnolang/gno/tm2/pkg/std", "Coin", - }, - { - "std", "Coins", - "github.com/gnolang/gno/tm2/pkg/std", "Coins", - }, - { - "std", "Realm", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Realm", - }, - { - "std", "BankerType", - "github.com/gnolang/gno/gnovm/stdlibs/std", "BankerType", - }, - { - "std", "Banker", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Banker", - }, -} - -func linkedTypeExists(lt linkedType) bool { - for _, ltx := range linkedTypes { - if lt == ltx { - return true - } - } - return false -} From c0535a8245be737c924ab8cef8f71562abda152a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:59:53 +0100 Subject: [PATCH 07/67] remove InjectNativeMappings --- gnovm/stdlibs/stdlibs.go | 10 ---------- gnovm/tests/imports.go | 2 -- 2 files changed, 12 deletions(-) diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 1e215b098e2..7939c0396d1 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -3,22 +3,12 @@ package stdlibs //go:generate go run github.com/gnolang/gno/misc/genstd import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" libsstd "github.com/gnolang/gno/gnovm/stdlibs/std" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) type ExecContext = libsstd.ExecContext -func InjectNativeMappings(store gno.Store) { - store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") -} - func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 0db5651fbcc..d0afee79910 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -400,8 +400,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri store.SetNativeStore(teststdlibs.NativeStore) store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) - // native mappings - stdlibs.InjectNativeMappings(store) return } From 231cf9eb7e1bcfedff2902fe7b77181a1a1b20fd Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 26 Feb 2024 18:58:45 +0100 Subject: [PATCH 08/67] correctly handle native functions in transpiler --- gno.land/pkg/sdk/vm/builtins.go | 1 - gnovm/pkg/transpiler/transpiler.go | 130 +++++++++++++++++++++++++---- gnovm/stdlibs/native.go | 44 ++++++++-- gnovm/stdlibs/stdlibs.go | 26 +++++- gnovm/stdlibs/time/time.gno | 3 - gnovm/tests/stdlibs/native.go | 22 +++-- misc/genstd/template.tmpl | 12 +-- 7 files changed, 201 insertions(+), 37 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index ef2ac93f617..63062847e01 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -41,7 +41,6 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { } store.SetPackageGetter(getPackage) store.SetNativeStore(stdlibs.NativeStore) - stdlibs.InjectNativeMappings(store) } // ---------------------------------------- diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 2872b94ccc6..59a2134114e 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -12,6 +12,7 @@ import ( "go/token" "os" "os/exec" + "path" "path/filepath" "regexp" "sort" @@ -22,6 +23,8 @@ import ( "golang.org/x/tools/go/ast/astutil" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/std" ) @@ -37,7 +40,7 @@ func TranspileImportPath(s string) string { // IsStdlib determines whether s is a pkgpath for a standard library. func IsStdlib(s string) bool { - // NOTE(morgan): this is likely to chagne in the future as we add support for + // NOTE(morgan): this is likely to change in the future as we add support for // IBC/ICS and we allow import paths to other chains. It might be good to // follow the same rule as Go, which is: does the first element of the // import path contain a dot? @@ -54,7 +57,9 @@ func packageDirLocation(s string) string { } } -type result struct { +// Result is returned by Transpile, returning the file's imports and output +// out the transpilation. +type Result struct { Imports []*ast.ImportSpec Translated string } @@ -106,7 +111,10 @@ func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { return nil } -func Transpile(source string, tags string, filename string) (*result, error) { +// Transpile performs transpilation on the given source code. tags can be used +// to specify build tags; and filename helps generate useful error messages and +// discriminate between test and normal source files. +func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) if err != nil { @@ -114,17 +122,30 @@ func Transpile(source string, tags string, filename string) (*result, error) { } isTestFile := strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") - rootDir := gnoenv.RootDir() + ctx := &transpileCtx{ + rootDir: gnoenv.RootDir(), + } + stdlibPrefix := filepath.Join(ctx.rootDir, "gnovm", "stdlibs") if isTestFile { // XXX(morgan): this disables checking that a package exists (in examples or stdlibs) // when transpiling a test file. After all Gno functions, including those in // tests/imports.go are converted to native bindings, support should // be added for transpiling stdlibs only available in tests/stdlibs, and // enable as such "package checking" also on test files. - rootDir = "" + ctx.rootDir = "" + } + if strings.HasPrefix(filename, stdlibPrefix) { + // this is a standard library. Mark it in the options so the native + // bindings resolve correctly. + path := strings.TrimPrefix(filename, stdlibPrefix) + path = filepath.Dir(path) + path = strings.Replace(path, string(filepath.Separator), "/", -1) + path = strings.TrimLeft(path, "/") + + ctx.stdlibPath = path } - transformed, err := transpileAST(fset, f, rootDir) + transformed, err := ctx.transformFile(fset, f) if err != nil { return nil, fmt.Errorf("transpileAST: %w", err) } @@ -140,7 +161,7 @@ func Transpile(source string, tags string, filename string) (*result, error) { return nil, fmt.Errorf("format.Node: %w", err) } - res := &result{ + res := &Result{ Imports: f.Imports, Translated: out.String(), } @@ -251,23 +272,35 @@ func parseGoBuildErrors(out string) error { return errList.Err() } -// If rootDir is given, we will check that the directory of the import path exists. -func transpileAST(fset *token.FileSet, f *ast.File, rootDir string) (ast.Node, error) { +type transpileCtx struct { + // If rootDir is given, we will check that the directory of the import path + // exists (using rootDir/packageDirLocation()). + rootDir string + // This should be set if we're working with a file from a standard library. + // This allows us to easily check if a function has a native binding, and as + // such modify its call expressions appropriately. + stdlibPath string + + stdlibImports map[string]string // symbol -> import path +} + +func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (ast.Node, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) + ctx.stdlibImports = make(map[string]string) - // rewrite imports + // rewrite imports to point to stdlibs/ or examples/ for _, paragraph := range imports { for _, importSpec := range paragraph { importPath, err := strconv.Unquote(importSpec.Path.Value) if err != nil { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("can't unquote import path %s: %w", importSpec.Path.Value, err)) + errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("can't unquote import path %s: %v", importSpec.Path.Value, err)) continue } - if rootDir != "" { - dirPath := filepath.Join(rootDir, packageDirLocation(importPath)) + if ctx.rootDir != "" { + dirPath := filepath.Join(ctx.rootDir, packageDirLocation(importPath)) if _, err := os.Stat(dirPath); err != nil { if !os.IsNotExist(err) { return nil, err @@ -277,6 +310,16 @@ func transpileAST(fset *token.FileSet, f *ast.File, rootDir string) (ast.Node, e } } + // Create mapping + if IsStdlib(importPath) { + if importSpec.Name != nil { + ctx.stdlibImports[importSpec.Name.Name] = importPath + } else { + // XXX: imperfect, see comment on transformCallExpr + ctx.stdlibImports[path.Base(importPath)] = importPath + } + } + transp := TranspileImportPath(importPath) if !astutil.RewriteImport(fset, f, importPath, transp) { errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, transp)) @@ -288,14 +331,71 @@ func transpileAST(fset *token.FileSet, f *ast.File, rootDir string) (ast.Node, e node := astutil.Apply(f, // pre func(c *astutil.Cursor) bool { - // do things here + node := c.Node() + // is function declaration without body? + // -> delete (native binding) + if fd, ok := node.(*ast.FuncDecl); ok && fd.Body == nil { + c.Delete() + return false // don't attempt to traverse children + } + + // is function call to a native function? + // -> rename if unexported, apply `nil,` for the first arg if necessary + if ce, ok := node.(*ast.CallExpr); ok { + return ctx.transformCallExpr(c, ce) + } + return true }, + // post func(c *astutil.Cursor) bool { - // and here return true }, ) return node, errs.Err() } + +func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) bool { + switch fe := ce.Fun.(type) { + case *ast.SelectorExpr: + // XXX: This is not correct in 100% of cases. If I shadow the `std` symbol, and + // its replacement is a type with the method AssertOriginCall, this system + // will incorrectly add a `nil` as the first argument. + // A full fix requires understanding scope; the Go standard library recommends + // using go/types, which for proper functioning requires an importer + // which can work with Gno. This is deferred for a future PR. + id, ok := fe.X.(*ast.Ident) + if !ok { + break + } + ip, ok := ctx.stdlibImports[id.Name] + if !ok { + break + } + if stdlibs.HasMachineParam(ip, gnolang.Name(fe.Sel.Name)) { + // Because it's an import, the symbol is always exported, so no need for the + // X_ prefix we add below. + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + + case *ast.Ident: + // Is this a native binding? + // Note: this is only useful within packages like `std` and `math`. + // The logic here is not robust to be generic. It does not account for locally + // defined scope. However, because native bindings have a narrowly defined and + // controlled scope (standard libraries) this will work for our usecase. + if ctx.stdlibPath != "" && + stdlibs.HasNativeBinding(ctx.stdlibPath, gnolang.Name(fe.Name)) { + if stdlibs.HasMachineParam(ctx.stdlibPath, gnolang.Name(fe.Name)) { + ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) + } + if !fe.IsExported() { + // Prefix unexported names with X_, per native binding convention + // (to export the symbol within Go). + fe.Name = "X_" + fe.Name + } + } + } + return true +} diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 0cb7d9a7859..7e2dd6fe088 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -16,11 +16,12 @@ import ( ) type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } var nativeFuncs = [...]nativeFunc{ @@ -33,6 +34,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[32]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -60,6 +62,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -87,6 +90,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float32")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -114,6 +118,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("uint64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -141,6 +146,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("float64")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -170,6 +176,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -209,6 +216,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -245,6 +253,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -278,6 +287,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -311,6 +321,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -339,6 +350,7 @@ var nativeFuncs = [...]nativeFunc{ "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { libs_std.AssertOriginCall( m, @@ -352,6 +364,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := libs_std.IsOriginCall( m, @@ -371,6 +384,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.CurrentRealmPath( m, @@ -390,6 +404,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetChainID( m, @@ -409,6 +424,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0 := libs_std.GetHeight( m, @@ -429,6 +445,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("[]string")}, {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, + true, func(m *gno.Machine) { r0, r1 := libs_std.X_origSend( m, @@ -453,6 +470,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origCaller( m, @@ -472,6 +490,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := libs_std.X_origPkgAddr( m, @@ -493,6 +512,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -523,6 +543,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("string")}, {Name: gno.N("r1"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -557,6 +578,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -585,6 +607,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -617,6 +640,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("[20]byte")}, {Name: gno.N("r2"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -654,6 +678,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -683,6 +708,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("[]byte")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -717,6 +743,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r0"), Type: gno.X("int")}, {Name: gno.N("r1"), Type: gno.X("error")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -749,6 +776,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -777,6 +805,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -808,6 +837,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -838,6 +868,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -865,6 +896,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -890,6 +922,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := libs_testing.X_unixNano() @@ -909,6 +942,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r1"), Type: gno.X("int32")}, {Name: gno.N("r2"), Type: gno.X("int64")}, }, + true, func(m *gno.Machine) { r0, r1, r2 := libs_time.X_now( m, diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 7939c0396d1..7ce9cfff909 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -9,11 +9,31 @@ import ( type ExecContext = libsstd.ExecContext -func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { +func findNative(pkgPath string, name gno.Name) nativeFunc { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { - return nf.f + return nf } } - return nil + return nativeFunc{} +} + +// NativeStore is used by the GnoVM to determine if the given function, +// specified by its pkgPath and name, has a native implementation; and if so +// retrieve it. +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + return findNative(pkgPath, name).f +} + +// HasNativeBinding determines if the function specified by the given pkgPath +// and name is a native binding. +func HasNativeBinding(pkgPath string, name gno.Name) bool { + return findNative(pkgPath, name).f != nil +} + +// HasMachineParam determines if the function specified by the given pkgPath +// and name contains a machine parameter; ie., its native implementation is +// prefixed with the parameter `m *gno.Machine`. +func HasMachineParam(pkgPath string, name gno.Name) bool { + return findNative(pkgPath, name).hasMachine } diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index ceed70452f6..c2b53e9a921 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -80,7 +80,6 @@ package time import ( "errors" - // XXX _ "unsafe" // for go:linkname ) // A Time represents an instant in time with nanosecond precision. @@ -1072,8 +1071,6 @@ func daysSinceEpoch(year int) uint64 { func now() (sec int64, nsec int32, mono int64) // injected // runtimeNano returns the current value of the runtime clock in nanoseconds. -// -//go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 { _, _, mono := now() return mono diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 6964a003025..7a846757b8e 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -12,11 +12,12 @@ import ( ) type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } var nativeFuncs = [...]nativeFunc{ @@ -25,6 +26,7 @@ var nativeFuncs = [...]nativeFunc{ "AssertOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.AssertOriginCall( m, @@ -38,6 +40,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + true, func(m *gno.Machine) { r0 := testlibs_std.IsOriginCall( m, @@ -57,6 +60,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { r0 := testlibs_std.TestCurrentRealm( m, @@ -76,6 +80,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -95,6 +100,7 @@ var nativeFuncs = [...]nativeFunc{ "ClearStoreCache", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { testlibs_std.ClearStoreCache( m, @@ -110,6 +116,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -137,6 +144,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -158,6 +166,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -182,6 +191,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -214,6 +224,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -241,6 +252,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, + false, func(m *gno.Machine) { r0 := testlibs_testing.X_unixNano() diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index f2cad0a851b..dd638fa4142 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -13,11 +13,12 @@ import ( ) type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + hasMachine bool + f func(m *gno.Machine) } var nativeFuncs = [...]nativeFunc{ @@ -36,6 +37,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, {{- end }} }, + {{ if $m.MachineParam }}true{{ else }}false{{ end }}, func(m *gno.Machine) { {{ if $m.Params -}} b := m.LastBlock() From 17e20057df78d09a448915aff205dee9b9386d96 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 26 Feb 2024 21:21:44 +0100 Subject: [PATCH 09/67] fixes to transpiler --- gnovm/cmd/gno/transpile.go | 43 +++++--- gnovm/pkg/gnolang/nodes.go | 8 +- gnovm/pkg/transpiler/transpiler.go | 171 ++++++++++++++++++++++++----- 3 files changed, 176 insertions(+), 46 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 0f0967b9dba..6ef79c237f5 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) @@ -18,7 +19,7 @@ type importPath string type transpileCfg struct { verbose bool - skipFmt bool + rootDir string skipImports bool gobuild bool goBinary string @@ -79,11 +80,11 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "verbose output when running", ) - fs.BoolVar( - &c.skipFmt, - "skip-fmt", - false, - "do not check syntax of generated .go files", + fs.StringVar( + &c.rootDir, + "root-dir", + "", + "clone location of github.com/gnolang/gno (gno tries to guess it)", ) fs.BoolVar( @@ -127,6 +128,11 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return flag.ErrHelp } + // guess cfg.RootDir + if cfg.rootDir == "" { + cfg.rootDir = gnoenv.RootDir() + } + // transpile .gno files. paths, err := gnoFilesFromArgs(args) if err != nil { @@ -180,7 +186,17 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error { } opts.markAsTranspiled(pkgPath) - files, err := filepath.Glob(filepath.Join(string(pkgPath), "*.gno")) + // resolve dir + dir := filepath.Join(opts.cfg.rootDir, string(pkgPath)) + if _, err := os.Stat(dir); err != nil { + return err + } + + // XXX(morgan): Currently avoiding test files as they contain imports like "fmt". + // The transpiler doesn't currently support "test stdlibs", and even if it + // did all packages like "fmt" would have to exist as standard libraries to work. + // Easier to skip for now. + files, err := listNonTestFiles(dir) if err != nil { log.Fatal(err) } @@ -238,19 +254,14 @@ func transpileFile(srcPath string, opts *transpileOptions) error { return fmt.Errorf("write .go file: %w", err) } - // check .go fmt, if `SkipFmt` sets to false. - if !flags.skipFmt { - err = transpiler.StaticCheck([]byte(transpileRes.Translated)) - if err != nil { - return fmt.Errorf("check .go file: %w", err) - } - } - // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports { importPaths := getPathsFromImportSpec(transpileRes.Imports) + // NOTE: importPaths are relative to root dir for _, path := range importPaths { - transpilePkg(path, opts) + if err := transpilePkg(path, opts); err != nil { + return err + } } } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 8f2c5054a8a..7290acbbcb7 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1114,12 +1114,18 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { } allowedFileExtensions := []string{ ".gno", + // Allows transpilation to work on stdlibs with native fns. + ".go", + } + rejectedFileExtensions := []string{ + ".gen.go", } list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || - (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) { + (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) || + endsWith(file.Name(), rejectedFileExtensions) { continue } list = append(list, filepath.Join(dir, file.Name())) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 59a2134114e..986e460e473 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -10,6 +10,8 @@ import ( "go/parser" goscanner "go/scanner" "go/token" + "go/types" + "log" "os" "os/exec" "path" @@ -35,7 +37,7 @@ const ImportPrefix = "github.com/gnolang/gno" // TranspileImportPath takes an import path s, and converts it into the full // import path relative to the Gno repository. func TranspileImportPath(s string) string { - return ImportPrefix + "/" + packageDirLocation(s) + return ImportPrefix + "/" + PackageDirLocation(s) } // IsStdlib determines whether s is a pkgpath for a standard library. @@ -47,8 +49,10 @@ func IsStdlib(s string) bool { return !strings.HasPrefix(s, "gno.land/") } -// packageDirLocation provides the supposed directory of the package, relative to the root dir. -func packageDirLocation(s string) string { +// PackageDirLocation provides the supposed directory of the package, relative to the root dir. +// +// TODO(morgan): move out, this should go in a "resolver" package. +func PackageDirLocation(s string) string { switch { case !IsStdlib(s): return "examples/" + s @@ -62,6 +66,7 @@ func packageDirLocation(s string) string { type Result struct { Imports []*ast.ImportSpec Translated string + File *ast.File } // TODO: func TranspileFile: supports caching. @@ -84,31 +89,121 @@ func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) return } +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// gnolang.Store, separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *std.MemPackage +} + +// DefaultGetter is used by [TranspileAndCheckMempkg] when a nil getter is passed. +// It resolves paths on-the-fly from the root directory, using gnolang.ReadMemPackage. +// If rootDir == "", then it will be set to the value of gnoenv.RootDir. +func DefaultGetter(rootDir string) MemPackageGetter { + if rootDir == "" { + rootDir = gnoenv.RootDir() + } + return defaultGetter{rootDir} +} + +type defaultGetter struct { + rootDir string +} + +func (dg defaultGetter) GetMemPackage(path string) *std.MemPackage { + dir := filepath.Join(dg.rootDir, PackageDirLocation(path)) + if _, err := os.Stat(dir); os.IsNotExist(err) { + return nil + } + defer func() { + // TODO(morgan): use variant that doesn't panic. *rolls eyes* + err := recover() + if err != nil { + log.Printf("import %q: %v", path, err) + } + }() + return gnolang.ReadMemPackage(dir, path) +} + // TranspileAndCheckMempkg converts each of the files in mempkg to Go, and -// performs basic static checking through the [StaticCheck] function on each -// of these. -func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { +// performs static checking using Go's type checker. +func TranspileAndCheckMempkg(mempkg *std.MemPackage, getter MemPackageGetter) error { + if getter == nil { + getter = DefaultGetter("") + } + imp := &transpImporter{ + getter: getter, + cache: map[string]interface{}{}, + cfg: &types.Config{}, + } + + imp.cfg.Importer = imp + _, err := imp.transpileParseMemPkg(mempkg) + if err != nil { + return err + } + + return nil +} + +type transpImporter struct { + getter MemPackageGetter + cache map[string]any // *types.Package or error + cfg *types.Config +} + +func (g *transpImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *transpImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + switch ret := pkg.(type) { + case *types.Package: + return ret, nil + case error: + return nil, ret + default: + panic(fmt.Sprintf("invalid type in transpImporter.cache %T", ret)) + } + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + g.cache[path] = (*types.Package)(nil) + return nil, nil + } + result, err := g.transpileParseMemPkg(mpkg) + if err != nil { + g.cache[path] = err + return nil, err + } + g.cache[path] = result + return result, nil +} + +func (g *transpImporter) transpileParseMemPkg(mpkg *std.MemPackage) (*types.Package, error) { + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) var errs error - for _, mfile := range mempkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { + for _, file := range mpkg.Files { + // include go files to have native bindings checked. + if !strings.HasSuffix(file.Name, ".gno") && !strings.HasSuffix(file.Name, ".go") { continue // skip spurious file. } - res, err := Transpile(mfile.Body, "gno,tmp", mfile.Name) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - err = StaticCheck([]byte(res.Translated)) + // TODO: because this is in-memory, could avoid header. + res, err := transpileWithFset(fset, file.Body, "gno", file.Name) if err != nil { - errs = multierr.Append(errs, err) - continue + err = multierr.Append(errs, err) } + files = append(files, res.File) } - if errs != nil { - return fmt.Errorf("transpile package: %w", errs) + return nil, errs } - return nil + + // TODO g.cfg.Error + return g.cfg.Check(mpkg.Path, fset, files, nil) } // Transpile performs transpilation on the given source code. tags can be used @@ -116,7 +211,11 @@ func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { // discriminate between test and normal source files. func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) + return transpileWithFset(fset, source, tags, filename) +} + +func transpileWithFset(fset *token.FileSet, source, tags, filename string) (*Result, error) { + f, err := parser.ParseFile(fset, filename, source, parser.SkipObjectResolution) if err != nil { return nil, fmt.Errorf("parse: %w", err) } @@ -164,6 +263,7 @@ func Transpile(source, tags, filename string) (*Result, error) { res := &Result{ Imports: f.Imports, Translated: out.String(), + File: transformed, } return res, nil } @@ -214,18 +314,20 @@ func TranspileBuildPackage(fileOrPkg, goBinary string) error { case strings.HasSuffix(goMatch, "_test.go"): // skip case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip default: + if !filepath.IsAbs(pkgDir) { + // Makes clear to go compiler that this is a relative path, + // rather than a path to a package/module. + // can't use filepath.Join as it cleans its results. + goMatch = "." + string(filepath.Separator) + goMatch + } files = append(files, goMatch) } } } sort.Strings(files) - args := append([]string{"build", "-v", "-tags=gno"}, files...) + args := append([]string{"build", "-tags=gno"}, files...) cmd := exec.Command(goBinary, args...) - rootDir := gnoenv.RootDir() - if err == nil { - cmd.Dir = rootDir - } out, err := cmd.CombinedOutput() if _, ok := err.(*exec.ExitError); ok { // exit error @@ -234,7 +336,10 @@ func TranspileBuildPackage(fileOrPkg, goBinary string) error { return err } -var errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) +var ( + errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) + commentRe = regexp.MustCompile(`(?m)^#.*$`) +) // parseGoBuildErrors returns a scanner.ErrorList filled with all errors found // in out, which is supposed to be the output of the `go build` command. @@ -269,6 +374,14 @@ func parseGoBuildErrors(out string) error { Column: column, }, msg) } + + replaced := errorRe.ReplaceAllLiteralString(out, "") + replaced = commentRe.ReplaceAllString(replaced, "") + replaced = strings.TrimSpace(replaced) + if replaced != "" { + errList.Add(token.Position{}, "Additional go build errors:\n"+replaced) + } + return errList.Err() } @@ -284,7 +397,7 @@ type transpileCtx struct { stdlibImports map[string]string // symbol -> import path } -func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (ast.Node, error) { +func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.File, error) { var errs goscanner.ErrorList imports := astutil.Imports(fset, f) @@ -300,7 +413,7 @@ func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (ast.No } if ctx.rootDir != "" { - dirPath := filepath.Join(ctx.rootDir, packageDirLocation(importPath)) + dirPath := filepath.Join(ctx.rootDir, PackageDirLocation(importPath)) if _, err := os.Stat(dirPath); err != nil { if !os.IsNotExist(err) { return nil, err @@ -353,7 +466,7 @@ func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (ast.No return true }, ) - return node, errs.Err() + return node.(*ast.File), errs.Err() } func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) bool { From 3d5ffef2bcc5c848cfc87d8e82fc0c745d273b99 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 26 Feb 2024 21:33:48 +0100 Subject: [PATCH 10/67] remove stdshim, remove StaticCheck --- gnovm/Makefile | 2 +- gnovm/pkg/transpiler/transpiler.go | 11 ---- gnovm/stdlibs/stdshim/addr_set.gno | 47 ---------------- gnovm/stdlibs/stdshim/banker.gno | 70 ------------------------ gnovm/stdlibs/stdshim/coins.gno | 65 ---------------------- gnovm/stdlibs/stdshim/context.gno | 4 -- gnovm/stdlibs/stdshim/crypto.gno | 16 ------ gnovm/stdlibs/stdshim/frame.gno | 18 ------- gnovm/stdlibs/stdshim/stdshim.gno | 86 ------------------------------ 9 files changed, 1 insertion(+), 318 deletions(-) delete mode 100644 gnovm/stdlibs/stdshim/addr_set.gno delete mode 100644 gnovm/stdlibs/stdshim/banker.gno delete mode 100644 gnovm/stdlibs/stdshim/coins.gno delete mode 100644 gnovm/stdlibs/stdshim/context.gno delete mode 100644 gnovm/stdlibs/stdshim/crypto.gno delete mode 100644 gnovm/stdlibs/stdshim/frame.gno delete mode 100644 gnovm/stdlibs/stdshim/stdshim.gno diff --git a/gnovm/Makefile b/gnovm/Makefile index 599ca58cd39..bbe9761ef12 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -58,7 +58,7 @@ _test.pkg: _test.gnolang: _test.gnolang.native _test.gnolang.stdlibs _test.gnolang.realm _test.gnolang.pkg0 _test.gnolang.pkg1 _test.gnolang.pkg2 _test.gnolang.other _test.gnolang.other:; go test tests/*.go -run "(TestFileStr|TestSelectors)" $(GOTEST_FLAGS) _test.gnolang.realm:; go test tests/*.go -run "TestFiles/^zrealm" $(GOTEST_FLAGS) -_test.gnolang.pkg0:; go test tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|stdshim|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) +_test.gnolang.pkg0:; go test tests/*.go -run "TestPackages/(bufio|crypto|encoding|errors|internal|io|math|sort|std|strconv|strings|testing|unicode)" $(GOTEST_FLAGS) _test.gnolang.pkg1:; go test tests/*.go -run "TestPackages/regexp" $(GOTEST_FLAGS) _test.gnolang.pkg2:; go test tests/*.go -run "TestPackages/bytes" $(GOTEST_FLAGS) _test.gnolang.native:; go test tests/*.go -test.short -run "TestFilesNative/" $(GOTEST_FLAGS) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 986e460e473..6b92efbf895 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -268,17 +268,6 @@ func transpileWithFset(fset *token.FileSet, source, tags, filename string) (*Res return res, nil } -// StaticCheck runs checks similar to gofmt on the given file. This will perform -// a basic round of static analysis, without checking the imports. -func StaticCheck(source []byte) error { - // TODO(morgan): we ignore the output of format.Source because we're only - // interested in errors that may occur in the static analysis of format.Source. - // If all we care is errors, could we use something else that avoids the "formatting" - // part and only does a non-typed Go static check on the source? - _, err := format.Source(source) - return err -} - // TranspileBuildPackage tries to run `go build` against the transpiled .go files. // // This method is the most efficient to detect errors but requires that diff --git a/gnovm/stdlibs/stdshim/addr_set.gno b/gnovm/stdlibs/stdshim/addr_set.gno deleted file mode 100644 index 3f46f48b8b5..00000000000 --- a/gnovm/stdlibs/stdshim/addr_set.gno +++ /dev/null @@ -1,47 +0,0 @@ -package std - -import "errors" - -//---------------------------------------- -// AddressSet - -type AddressSet interface { - Size() int - AddAddress(Address) error - HasAddress(Address) bool -} - -//---------------------------------------- -// AddressList implements AddressSet. -// TODO implement AddressTree with avl. - -type AddressList []Address - -func NewAddressList() *AddressList { - return &AddressList{} -} - -func (alist *AddressList) Size() int { - return len(*alist) -} - -func (alist *AddressList) AddAddress(newAddr Address) error { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return errors.New("address already exists") - } - } - *alist = append(*alist, newAddr) - return nil -} - -func (alist *AddressList) HasAddress(newAddr Address) bool { - // TODO optimize with binary algorithm - for _, addr := range *alist { - if addr == newAddr { - return true - } - } - return false -} diff --git a/gnovm/stdlibs/stdshim/banker.gno b/gnovm/stdlibs/stdshim/banker.gno deleted file mode 100644 index 44e611b780f..00000000000 --- a/gnovm/stdlibs/stdshim/banker.gno +++ /dev/null @@ -1,70 +0,0 @@ -package std - -// Realm functions can call std.GetBanker(options) to get -// a banker instance. Banker objects cannot be persisted, -// but can be passed onto other functions to be transacted -// on. A banker instance can be passed onto other realm -// functions; this allows other realms to spend coins on -// behalf of the first realm. -// -// Banker panics on errors instead of returning errors. -// This also helps simplify the interface and prevent -// hidden bugs (e.g. ignoring errors) -// -// NOTE: this Gno interface is satisfied by a native go -// type, and those can't return non-primitive objects -// (without confusion). -type Banker interface { - GetCoins(addr Address) (dst Coins) - SendCoins(from, to Address, amt Coins) - TotalCoin(denom string) int64 - IssueCoin(addr Address, denom string, amount int64) - RemoveCoin(addr Address, denom string, amount int64) -} - -// Also available natively in stdlibs/context.go -type BankerType uint8 - -// Also available natively in stdlibs/context.go -const ( - // Can only read state. - BankerTypeReadonly BankerType = iota - // Can only send from tx send. - BankerTypeOrigSend - // Can send from all realm coins. - BankerTypeRealmSend - // Can issue and remove realm coins. - BankerTypeRealmIssue -) - -//---------------------------------------- -// adapter for native banker - -type bankAdapter struct { - nativeBanker Banker -} - -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) - } - return dst -} - -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) -} - -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) -} - -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) -} - -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) -} diff --git a/gnovm/stdlibs/stdshim/coins.gno b/gnovm/stdlibs/stdshim/coins.gno deleted file mode 100644 index aaede81309d..00000000000 --- a/gnovm/stdlibs/stdshim/coins.gno +++ /dev/null @@ -1,65 +0,0 @@ -package std - -import "strconv" - -// NOTE: this is selectly copied over from pkgs/std/coin.go -// TODO: import all functionality(?). - -// Coin hold some amount of one currency. -// A negative amount is invalid. -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -func (c Coin) String() string { - return strconv.Itoa(int(c.Amount)) + c.Denom -} - -func (c Coin) IsGTE(other Coin) bool { - if c.Denom != other.Denom { - panic("invalid coin denominations: " + c.Denom) - } - return c.Amount >= other.Amount -} - -// Coins is a set of Coin, one per currency -type Coins []Coin - -func (cz Coins) String() string { - res := "" - for i, c := range cz { - if i > 0 { - res += "," - } - res += c.String() - } - return res -} - -func (cz Coins) AmountOf(denom string) int64 { - for _, c := range cz { - if c.Denom == denom { - return c.Amount - } - } - return 0 -} - -func (a Coins) Add(b Coins) Coins { - c := Coins{} - for _, ac := range a { - bc := b.AmountOf(ac.Denom) - ac.Amount += bc - c = append(c, ac) - } - for _, bc := range b { - cc := c.AmountOf(bc.Denom) - if cc == 0 { - c = append(c, bc) - } - } - return c -} - -// TODO implement Coin/Coins constructors. diff --git a/gnovm/stdlibs/stdshim/context.gno b/gnovm/stdlibs/stdshim/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/stdshim/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/stdshim/crypto.gno b/gnovm/stdlibs/stdshim/crypto.gno deleted file mode 100644 index 8d005dccf5c..00000000000 --- a/gnovm/stdlibs/stdshim/crypto.gno +++ /dev/null @@ -1,16 +0,0 @@ -package std - -type Address string // NOTE: bech32 - -func (a Address) String() string { - return string(a) -} - -// IsValid checks if the address is of specific length. Doesn't check prefix or checksum for the address -func (a Address) IsValid() bool { - return len(a) == RawAddressSize*2 // hex length -} - -const RawAddressSize = 20 - -type RawAddress [RawAddressSize]byte diff --git a/gnovm/stdlibs/stdshim/frame.gno b/gnovm/stdlibs/stdshim/frame.gno deleted file mode 100644 index bc3a000f5a0..00000000000 --- a/gnovm/stdlibs/stdshim/frame.gno +++ /dev/null @@ -1,18 +0,0 @@ -package std - -type Realm struct { - addr Address - pkgPath string -} - -func (r Realm) Addr() Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/stdshim/stdshim.gno b/gnovm/stdlibs/stdshim/stdshim.gno deleted file mode 100644 index 62e97088209..00000000000 --- a/gnovm/stdlibs/stdshim/stdshim.gno +++ /dev/null @@ -1,86 +0,0 @@ -package std - -const shimWarn = "stdshim cannot be used to run code" - -func AssertOriginCall() { - panic(shimWarn) -} - -func IsOriginCall() (isOrigin bool) { - panic(shimWarn) - return false -} - -func Hash(bz []byte) (hash [20]byte) { - panic(shimWarn) - return -} - -func CurrentRealmPath() string { - panic(shimWarn) - return "" -} - -func GetChainID() string { - panic(shimWarn) - return "" -} - -func GetHeight() int64 { - panic(shimWarn) - return -1 -} - -func GetOrigSend() Coins { - panic(shimWarn) - return Coins{} -} - -func CurrentRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func PrevRealm() Realm { - panic(shimWarn) - return Realm{ - addr: Address(""), - pkgPath: "", - } -} - -func GetOrigCaller() Address { - panic(shimWarn) - return Address("") -} - -func GetOrigPkgAddr() Address { - panic(shimWarn) - return Address("") -} - -func GetCallerAt(n int) Address { - panic(shimWarn) - return Address("") -} - -func GetBanker(bankerType BankerType) Banker { - panic(shimWarn) - return nil -} - -func EncodeBech32(prefix string, bytes [20]byte) (addr Address) { - panic(shimWarn) - return "" -} - -func DecodeBech32(addr Address) (prefix string, bytes [20]byte, ok bool) { - panic(shimWarn) -} - -func DerivePkgAddr(pkgPath string) (addr Address) { - panic(shimWarn) -} From e2785984a40789f81d98e47fee3d0b87cab16aa6 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 26 Feb 2024 21:46:54 +0100 Subject: [PATCH 11/67] make code compile --- gno.land/pkg/gnoclient/client_txs.go | 2 +- gno.land/pkg/keyscli/addpkg.go | 2 +- gno.land/pkg/keyscli/run.go | 2 +- gnovm/pkg/gnolang/gonative.go | 13 ------------- gnovm/pkg/gnolang/store.go | 1 - 5 files changed, 3 insertions(+), 17 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index ece7507d569..780727ded1e 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -209,7 +209,7 @@ func (c *Client) Run(cfg RunCfg) (*ctypes.ResultBroadcastTxCommit, error) { caller := c.Signer.Info().GetAddress() // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) + err = transpiler.TranspileAndCheckMempkg(memPkg, nil) if err != nil { return nil, errors.Wrap(err, "transpile and check") } diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 1f89b2c2b82..3c9d4fc9b16 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -103,7 +103,7 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { } // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) + err = transpiler.TranspileAndCheckMempkg(memPkg, nil) if err != nil { panic(err) } diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 6fcee7434eb..3a9fac53a2d 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -110,7 +110,7 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) + err = transpiler.TranspileAndCheckMempkg(memPkg, nil) if err != nil { panic(err) } diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index f3739dad0f3..e634fbcf8f5 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -88,19 +88,6 @@ func (ds *defaultStore) SetStrictGo2GnoMapping(strict bool) { ds.go2gnoStrict = strict } -// Implements Store. -func (ds *defaultStore) AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) { - rtPkgPath := rt.PkgPath() - if rtPkgPath == "" { - panic(fmt.Sprintf("type has no associated package path: %v", rt)) - } - rtName := rt.Name() - if rtName == "" { - panic(fmt.Sprintf("type has no name: %v", rt)) - } - ds.go2gnoMap[rtPkgPath+"."+rtName] = pkgPath + "." + name -} - // Implements Store. // See go2GnoValue2(). Like go2GnoType() but also converts any // top-level complex types (or pointers to them). The result gets diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index be4a854e7ce..5b1bfe960ea 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -40,7 +40,6 @@ type Store interface { SetBlockNode(BlockNode) // UNSTABLE SetStrictGo2GnoMapping(bool) - AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) Go2GnoType(rt reflect.Type) Type GetAllocator() *Allocator NumMemPackages() int64 From e47ff208999d2cc5a7adae45fa2cee72aab23ae3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 12:39:39 +0100 Subject: [PATCH 12/67] move type checker to its own function --- gno.land/pkg/gnoclient/client_txs.go | 6 -- gno.land/pkg/keyscli/addpkg.go | 7 -- gno.land/pkg/keyscli/run.go | 6 -- gno.land/pkg/sdk/vm/builtins.go | 4 +- gno.land/pkg/sdk/vm/keeper.go | 14 +++ gnovm/pkg/gnolang/go2gno.go | 102 +++++++++++++++++++++- gnovm/pkg/gnolang/store.go | 77 ++++++++++++----- gnovm/pkg/transpiler/transpiler.go | 125 --------------------------- gnovm/tests/files/import6.gno | 2 +- gnovm/tests/imports.go | 10 +-- 10 files changed, 180 insertions(+), 173 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 7a62babc13f..f2fa5ea6fb4 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,6 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -138,11 +137,6 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo caller := c.Signer.Info().GetAddress() - // Transpile and validate Gno syntax - if err = transpiler.TranspileAndCheckMempkg(msg.Package, nil); err != nil { - return nil, err - } - msg.Package.Name = "main" msg.Package.Path = "" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 3c9d4fc9b16..1812bf3fd86 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,7 +7,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -102,12 +101,6 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg, nil) - if err != nil { - panic(err) - } - // parse gas wanted & fee. gaswanted := cfg.RootCfg.GasWanted gasfee, err := std.ParseCoin(cfg.RootCfg.GasFee) diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 3a9fac53a2d..a2872ad432e 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -109,11 +108,6 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg, nil) - if err != nil { - panic(err) - } memPkg.Name = "main" // Set to empty; this will be automatically set by the VM keeper. memPkg.Path = "" diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 63062847e01..368ada6ff82 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: store, + Store: newStore, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 54f424ee058..c06c3b3761c 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -160,6 +161,11 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } + // Validate Gno syntax and type check. + if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return err + } + // Pay deposit from creator. pkgAddr := gno.DerivePkgAddr(pkgPath) @@ -317,6 +323,11 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { return "", ErrInvalidPkgPath(err.Error()) } + // Validate Gno syntax and type check. + if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return "", err + } + // Send send-coins to pkg from caller. err = vm.bank.SendCoins(ctx, caller, pkgAddr, send) if err != nil { @@ -565,6 +576,9 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err return memFile.Body, nil } else { memPkg := store.GetMemPackage(dirpath) + if memPkg == nil { + return "", fmt.Errorf("package %q is not available", dirpath) // TODO: XSS protection + } for i, memfile := range memPkg.Files { if i > 0 { res += "\n" diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index ee82cb39555..c726e291abc 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -35,12 +35,16 @@ import ( "go/ast" "go/parser" "go/token" + "go/types" "os" "reflect" "strconv" + "strings" "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" + "go.uber.org/multierr" ) func MustReadFile(path string) *FileNode { @@ -102,7 +106,10 @@ func MustParseExpr(expr string) Expr { func ParseFile(filename string, body string) (fn *FileNode, err error) { // Use go parser to parse the body. fs := token.NewFileSet() - f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) + // TODO(morgan): would be nice to add parser.SkipObjectResolution as we don't + // seem to be using its features, but this breaks when testing (specifically redeclaration tests). + const parseOpts = parser.ParseComments | parser.DeclarationErrors + f, err := parser.ParseFile(fs, filename, body, parseOpts) if err != nil { return nil, err } @@ -469,6 +476,99 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } } +//---------------------------------------- +// type checking (using go/types) + +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// [Store], separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *std.MemPackage +} + +// TypeCheckMemPackage performs type validation and checking on the given +// mempkg. To retrieve dependencies, it uses getter. +// +// The syntax checking is performed entirely using Go's go/types package. +func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { + imp := &gnoImporter{ + getter: getter, + cache: map[string]any{}, + cfg: &types.Config{}, + } + imp.cfg.Importer = imp + + _, err := imp.parseCheckMemPackage(mempkg) + if err != nil { + return err + } + + return nil +} + +type gnoImporter struct { + getter MemPackageGetter + cache map[string]any // *types.Package or error + cfg *types.Config +} + +func (g *gnoImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + switch ret := pkg.(type) { + case *types.Package: + return ret, nil + case error: + return nil, ret + default: + panic(fmt.Sprintf("invalid type in gnoImporter.cache %T", ret)) + } + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + g.cache[path] = (*types.Package)(nil) + return nil, nil + } + result, err := g.parseCheckMemPackage(mpkg) + if err != nil { + g.cache[path] = err + return nil, err + } + g.cache[path] = result + return result, nil +} + +func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) + var errs error + for _, file := range mpkg.Files { + if !strings.HasSuffix(file.Name, ".gno") || + endsWith(file.Name, []string{"_test.gno", "_filetest.gno"}) { + continue // skip spurious file. + } + + const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution + f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) + if err != nil { + err = multierr.Append(errs, err) + continue + } + + files = append(files, f) + } + if errs != nil { + return nil, errs + } + + // TODO g.cfg.Error + return g.cfg.Check(mpkg.Path, fset, files, nil) +} + //---------------------------------------- // utility methods diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 5b1bfe960ea..306253e7f31 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + "slices" "strconv" "strings" @@ -11,8 +12,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) -// return nil if package doesn't exist. -type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) +// PackageGetter specifies how the store may retrieve packages which are not +// already in its cache. PackageGetter should return nil when the requested +// package does not exist. store should be used to run the machine, or otherwise +// call any methods which may call store.GetPackage, to avoid import cycles. +type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -79,8 +83,7 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. - current map[string]struct{} // for detecting import cycles. + opslog []StoreOp // for debugging and testing. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -95,7 +98,6 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore iavlStore: iavlStore, go2gnoMap: make(map[string]string), go2gnoStrict: true, - current: make(map[string]struct{}), } InitStoreCaches(ds) return ds @@ -109,16 +111,29 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } +type importerStore struct { + *defaultStore + importChain []string +} + +func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + if !isImport { + // if not an import, match behaviour to the defaultStore + return is.defaultStore.GetPackage(pkgPath, isImport) + } + // it is an import -- detect cyclic imports + if slices.Contains(is.importChain, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) + } + return is.getPackage(pkgPath, is) +} + // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - // detect circular imports - if isImport { - if _, exists := ds.current[pkgPath]; exists { - panic(fmt.Sprintf("import cycle detected: %q", pkgPath)) - } - ds.current[pkgPath] = struct{}{} - defer delete(ds.current, pkgPath) - } + return ds.getPackage(pkgPath, importerStore{}) +} + +func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -162,7 +177,12 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if pn, pv := ds.pkgGetter(pkgPath); pv != nil { + if impStore.defaultStore == nil { + // pre-allocate 16 strings to likely avoid further slice allocations. + impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} + } + impStore.importChain = append(impStore.importChain, pkgPath) + if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -534,20 +554,41 @@ func (ds *defaultStore) AddMemPackage(memPkg *std.MemPackage) { ds.iavlStore.Set(pathkey, bz) } +// GetMemPackage retrieves the MemPackage at the given path. +// It returns nil if the package could not be found. func (ds *defaultStore) GetMemPackage(path string) *std.MemPackage { + return ds.getMemPackage(path, false) +} + +func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage { pathkey := []byte(backendPackagePathKey(path)) bz := ds.iavlStore.Get(pathkey) if bz == nil { - panic(fmt.Sprintf( - "missing package at path %s", string(pathkey))) + // If this is the first try, attempt using GetPackage to retrieve the + // package, first. GetPackage can leverage pkgGetter, which in most + // implementations works by running Machine.RunMemPackage with save = true, + // which would add the package to the store after running. + // Some packages may never be persisted, thus why we only attempt this twice. + if !isRetry { + if pv := ds.GetPackage(path, false); pv != nil { + return ds.getMemPackage(path, true) + } + } + return nil } var memPkg *std.MemPackage amino.MustUnmarshal(bz, &memPkg) return memPkg } +// GetMemFile retrieves the MemFile with the given name, contained in the +// MemPackage at the given path. It returns nil if the file or the package +// do not exist. func (ds *defaultStore) GetMemFile(path string, name string) *std.MemFile { memPkg := ds.GetMemPackage(path) + if memPkg == nil { + return nil + } memFile := memPkg.GetFile(name) return memFile } @@ -587,9 +628,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. - if len(ds.current) > 0 { - ds.current = make(map[string]struct{}) - } ds.SetCachePackage(Uverse()) } @@ -610,7 +648,6 @@ func (ds *defaultStore) Fork() Store { go2gnoMap: ds.go2gnoMap, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. - current: make(map[string]struct{}), } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 6b92efbf895..8a1493c2563 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -10,8 +10,6 @@ import ( "go/parser" goscanner "go/scanner" "go/token" - "go/types" - "log" "os" "os/exec" "path" @@ -21,13 +19,11 @@ import ( "strconv" "strings" - "go.uber.org/multierr" "golang.org/x/tools/go/ast/astutil" "github.com/gnolang/gno/gnovm/pkg/gnoenv" "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" - "github.com/gnolang/gno/tm2/pkg/std" ) // ImportPrefix is the import path to the root of the gno repository, which should @@ -89,132 +85,11 @@ func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) return } -// MemPackageGetter implements the GetMemPackage() method. It is a subset of -// gnolang.Store, separated for ease of testing. -type MemPackageGetter interface { - GetMemPackage(path string) *std.MemPackage -} - -// DefaultGetter is used by [TranspileAndCheckMempkg] when a nil getter is passed. -// It resolves paths on-the-fly from the root directory, using gnolang.ReadMemPackage. -// If rootDir == "", then it will be set to the value of gnoenv.RootDir. -func DefaultGetter(rootDir string) MemPackageGetter { - if rootDir == "" { - rootDir = gnoenv.RootDir() - } - return defaultGetter{rootDir} -} - -type defaultGetter struct { - rootDir string -} - -func (dg defaultGetter) GetMemPackage(path string) *std.MemPackage { - dir := filepath.Join(dg.rootDir, PackageDirLocation(path)) - if _, err := os.Stat(dir); os.IsNotExist(err) { - return nil - } - defer func() { - // TODO(morgan): use variant that doesn't panic. *rolls eyes* - err := recover() - if err != nil { - log.Printf("import %q: %v", path, err) - } - }() - return gnolang.ReadMemPackage(dir, path) -} - -// TranspileAndCheckMempkg converts each of the files in mempkg to Go, and -// performs static checking using Go's type checker. -func TranspileAndCheckMempkg(mempkg *std.MemPackage, getter MemPackageGetter) error { - if getter == nil { - getter = DefaultGetter("") - } - imp := &transpImporter{ - getter: getter, - cache: map[string]interface{}{}, - cfg: &types.Config{}, - } - - imp.cfg.Importer = imp - _, err := imp.transpileParseMemPkg(mempkg) - if err != nil { - return err - } - - return nil -} - -type transpImporter struct { - getter MemPackageGetter - cache map[string]any // *types.Package or error - cfg *types.Config -} - -func (g *transpImporter) Import(path string) (*types.Package, error) { - return g.ImportFrom(path, "", 0) -} - -// ImportFrom returns the imported package for the given import -// path when imported by a package file located in dir. -func (g *transpImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { - if pkg, ok := g.cache[path]; ok { - switch ret := pkg.(type) { - case *types.Package: - return ret, nil - case error: - return nil, ret - default: - panic(fmt.Sprintf("invalid type in transpImporter.cache %T", ret)) - } - } - mpkg := g.getter.GetMemPackage(path) - if mpkg == nil { - g.cache[path] = (*types.Package)(nil) - return nil, nil - } - result, err := g.transpileParseMemPkg(mpkg) - if err != nil { - g.cache[path] = err - return nil, err - } - g.cache[path] = result - return result, nil -} - -func (g *transpImporter) transpileParseMemPkg(mpkg *std.MemPackage) (*types.Package, error) { - fset := token.NewFileSet() - files := make([]*ast.File, 0, len(mpkg.Files)) - var errs error - for _, file := range mpkg.Files { - // include go files to have native bindings checked. - if !strings.HasSuffix(file.Name, ".gno") && !strings.HasSuffix(file.Name, ".go") { - continue // skip spurious file. - } - // TODO: because this is in-memory, could avoid header. - res, err := transpileWithFset(fset, file.Body, "gno", file.Name) - if err != nil { - err = multierr.Append(errs, err) - } - files = append(files, res.File) - } - if errs != nil { - return nil, errs - } - - // TODO g.cfg.Error - return g.cfg.Check(mpkg.Path, fset, files, nil) -} - // Transpile performs transpilation on the given source code. tags can be used // to specify build tags; and filename helps generate useful error messages and // discriminate between test and normal source files. func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() - return transpileWithFset(fset, source, tags, filename) -} - -func transpileWithFset(fset *token.FileSet, source, tags, filename string) (*Result, error) { f, err := parser.ParseFile(fset, filename, source, parser.SkipObjectResolution) if err != nil { return nil, fmt.Errorf("parse: %w", err) diff --git a/gnovm/tests/files/import6.gno b/gnovm/tests/files/import6.gno index 8c974ea1893..da5dbfbd3b2 100644 --- a/gnovm/tests/files/import6.gno +++ b/gnovm/tests/files/import6.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" +// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" (through [github.com/gnolang/gno/_test/c1 github.com/gnolang/gno/_test/c2]) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d0afee79910..a1615bb81a2 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -62,7 +62,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -81,7 +81,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() @@ -93,7 +93,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -367,7 +367,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -384,7 +384,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) pn, pv = m2.RunMemPackage(memPkg, true) return From 21566090120d07598ccb7c84f35dbf85fb6f03bc Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 14:38:37 +0100 Subject: [PATCH 13/67] improve build --- examples/gno.land/r/demo/art/gnoface/gno.mod | 2 + examples/gno.land/r/x/manfred_outfmt/gno.mod | 2 + gnovm/cmd/gno/test.go | 4 +- gnovm/cmd/gno/transpile.go | 121 ++++++++++++------- gnovm/cmd/gno/util.go | 56 ++++----- gnovm/pkg/transpiler/transpiler.go | 4 +- gnovm/stdlibs/io/io.gno | 6 +- gnovm/stdlibs/std/native.gno | 1 + 8 files changed, 114 insertions(+), 82 deletions(-) diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index bc17ee9df3b..6276629cba2 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,5 @@ +// Draft + module gno.land/r/demo/art/gnoface require ( diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index e6f705c46b9..9804aecc7f1 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -1,3 +1,5 @@ +// Draft + module gno.land/r/x/manfred_outfmt require ( diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 57ed1bdc6be..10d16fc8d90 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -216,7 +216,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { transpileOpts := newTranspileOptions(&transpileCfg{ output: tempdirRoot, }) - err := transpilePkg(importPath(pkg.Dir), transpileOpts) + err := transpilePkg(pkg.Dir, transpileOpts) if err != nil { io.ErrPrintln(err) io.ErrPrintln("FAIL") @@ -230,7 +230,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error { if verbose { io.ErrPrintfln("=== BUILD %s", pkg.Dir) } - tempDir, err := ResolvePath(tempdirRoot, importPath(pkg.Dir)) + tempDir, err := ResolvePath(tempdirRoot, pkg.Dir) if err != nil { return errors.New("cannot resolve build dir") } diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 6ef79c237f5..b388a635a48 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -5,25 +5,27 @@ import ( "errors" "flag" "fmt" + "go/ast" "go/scanner" "log" "os" "path/filepath" + "slices" + "strconv" + "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/commands" ) -type importPath string - type transpileCfg struct { verbose bool rootDir string skipImports bool gobuild bool goBinary string - gofmtBinary string output string } @@ -31,7 +33,9 @@ type transpileOptions struct { cfg *transpileCfg // transpiled is the set of packages already // transpiled from .gno to .go. - transpiled map[importPath]struct{} + transpiled map[string]struct{} + // skipped packages (gno mod marks them as draft) + skipped []string } var defaultTranspileCfg = &transpileCfg{ @@ -40,19 +44,22 @@ var defaultTranspileCfg = &transpileCfg{ } func newTranspileOptions(cfg *transpileCfg) *transpileOptions { - return &transpileOptions{cfg, map[importPath]struct{}{}} + return &transpileOptions{ + cfg: cfg, + transpiled: map[string]struct{}{}, + } } func (p *transpileOptions) getFlags() *transpileCfg { return p.cfg } -func (p *transpileOptions) isTranspiled(pkg importPath) bool { +func (p *transpileOptions) isTranspiled(pkg string) bool { _, transpiled := p.transpiled[pkg] return transpiled } -func (p *transpileOptions) markAsTranspiled(pkg importPath) { +func (p *transpileOptions) markAsTranspiled(pkg string) { p.transpiled[pkg] = struct{}{} } @@ -108,13 +115,6 @@ func (c *transpileCfg) RegisterFlags(fs *flag.FlagSet) { "go binary to use for building", ) - fs.StringVar( - &c.gofmtBinary, - "go-fmt-binary", - "gofmt", - "gofmt binary to use for syntax checking", - ) - fs.StringVar( &c.output, "output", @@ -133,32 +133,43 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { cfg.rootDir = gnoenv.RootDir() } - // transpile .gno files. - paths, err := gnoFilesFromArgs(args) + // transpile .gno packages and files. + paths, err := gnoPackagesFromArgs(args) if err != nil { return fmt.Errorf("list paths: %w", err) } opts := newTranspileOptions(cfg) var errlist scanner.ErrorList - for _, filepath := range paths { - if err := transpileFile(filepath, opts); err != nil { + for _, path := range paths { + st, err := os.Stat(path) + if err != nil { + return err + } + if st.IsDir() { + err = transpilePkg(path, opts) + } else { + if opts.cfg.verbose { + fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(path)) + } + + err = transpileFile(path, opts) + } + if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { // Not an scanner.ErrorList: return immediately. - return fmt.Errorf("%s: transpile: %w", filepath, err) + return fmt.Errorf("%s: transpile: %w", path, err) } errlist = append(errlist, fileErrlist...) } } if errlist.Len() == 0 && cfg.gobuild { - paths, err := gnoPackagesFromArgs(args) - if err != nil { - return fmt.Errorf("list packages: %w", err) - } - for _, pkgPath := range paths { + if slices.Contains(opts.skipped, pkgPath) { + continue + } err := goBuildFileOrPkg(pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList @@ -180,27 +191,39 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return nil } -func transpilePkg(pkgPath importPath, opts *transpileOptions) error { - if opts.isTranspiled(pkgPath) { +// transpilePkg transpiles all non-test files at the given location. +// Additionally, it checks the gno.mod in said location, and skips it if it is +// a draft module +func transpilePkg(dirPath string, opts *transpileOptions) error { + if opts.isTranspiled(dirPath) { return nil } - opts.markAsTranspiled(pkgPath) + opts.markAsTranspiled(dirPath) - // resolve dir - dir := filepath.Join(opts.cfg.rootDir, string(pkgPath)) - if _, err := os.Stat(dir); err != nil { + gmod, err := gnomod.ParseAt(dirPath) + if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) { return err } + if err == nil && gmod.Draft { + if opts.cfg.verbose { + fmt.Fprintf(os.Stderr, "%s (skipped, gno.mod marks module as draft)\n", filepath.Clean(dirPath)) + } + opts.skipped = append(opts.skipped, dirPath) + return nil + } // XXX(morgan): Currently avoiding test files as they contain imports like "fmt". // The transpiler doesn't currently support "test stdlibs", and even if it // did all packages like "fmt" would have to exist as standard libraries to work. // Easier to skip for now. - files, err := listNonTestFiles(dir) + files, err := listNonTestFiles(dirPath) if err != nil { log.Fatal(err) } + if opts.cfg.verbose { + fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(dirPath)) + } for _, file := range files { if err = transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) @@ -212,14 +235,6 @@ func transpilePkg(pkgPath importPath, opts *transpileOptions) error { func transpileFile(srcPath string, opts *transpileOptions) error { flags := opts.getFlags() - gofmt := flags.gofmtBinary - if gofmt == "" { - gofmt = "gofmt" - } - - if flags.verbose { - fmt.Fprintf(os.Stderr, "%s\n", srcPath) - } // parse .gno. source, err := os.ReadFile(srcPath) @@ -239,7 +254,7 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // resolve target path var targetPath string if flags.output != "." { - path, err := ResolvePath(flags.output, importPath(filepath.Dir(srcPath))) + path, err := ResolvePath(flags.output, filepath.Dir(srcPath)) if err != nil { return fmt.Errorf("resolve output path: %w", err) } @@ -255,10 +270,10 @@ func transpileFile(srcPath string, opts *transpileOptions) error { } // transpile imported packages, if `SkipImports` sets to false - if !flags.skipImports { - importPaths := getPathsFromImportSpec(transpileRes.Imports) - // NOTE: importPaths are relative to root dir - for _, path := range importPaths { + if !flags.skipImports && + !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { + dirPaths := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) + for _, path := range dirPaths { if err := transpilePkg(path, opts); err != nil { return err } @@ -273,8 +288,24 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { goBinary := cfg.goBinary if verbose { - fmt.Fprintf(os.Stderr, "%s\n", fileOrPkg) + fmt.Fprintf(os.Stderr, "%s [build]\n", fileOrPkg) } return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) } + +// getPathsFromImportSpec returns the directory paths where the code for each +// importSpec is stored (assuming they start with [transpiler.ImportPrefix]). +func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string) { + for _, i := range importSpec { + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + continue + } + if strings.HasPrefix(path, transpiler.ImportPrefix) { + res := strings.TrimPrefix(path, transpiler.ImportPrefix) + dirs = append(dirs, rootDir+strings.Replace(res, "/", string(filepath.Separator), -1)) + } + } + return +} diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 30d8f808d04..6d650b3c0aa 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "go/ast" "io" "io/fs" "os" @@ -10,9 +9,6 @@ import ( "regexp" "strings" "time" - - "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/transpiler" ) func isGnoFile(f fs.DirEntry) bool { @@ -198,36 +194,36 @@ func makeTestGoMod(path string, packageName string, goversion string) error { return os.WriteFile(path, []byte(content), 0o644) } -// getPathsFromImportSpec derive and returns ImportPaths -// without ImportPrefix from *ast.ImportSpec -func getPathsFromImportSpec(importSpec []*ast.ImportSpec) (importPaths []importPath) { - for _, i := range importSpec { - path := i.Path.Value[1 : len(i.Path.Value)-1] // trim leading and trailing `"` - if strings.HasPrefix(path, transpiler.ImportPrefix) { - res := strings.TrimPrefix(path, transpiler.ImportPrefix) - importPaths = append(importPaths, importPath("."+res)) - } - } - return -} - -// ResolvePath joins the output dir with relative pkg path -// e.g -// Output Dir: Temp/gno-transpile -// Pkg Path: ../example/gno.land/p/pkg -// Returns -> Temp/gno-transpile/example/gno.land/p/pkg -func ResolvePath(output string, path importPath) (string, error) { - absOutput, err := filepath.Abs(output) - if err != nil { - return "", err +// ResolvePath determines the path where to place output files. +// dstPath is the desired output path by the gno program. output is the output +// directory provided by the user. +// +// If dstPath is absolute, it will be simply joined together with output. +// If dstPath is local, the output will be joined together with the relative +// path to reach dstPath. +// If dstPath is relative non-local path (ie. contains ../), the dstPath will +// be made absolute and joined with output +// +// Working directory: /home/gno +// ResolvePath("transpile-result", "./examples/test/test1.gno.gen.go") +// -> transpile-result/examples/test/test1.gen.go +// ResolvePath("/transpile-result", "./examples/test/test1.gno.gen.go") +// -> /transpile-result/examples/test/test1.gen.go +// ResolvePath("/transpile-result", "/home/gno/examples/test/test1.gno.gen.go") +// -> /transpile-result/home/gno/examples/test/test1.gen.go +// ResolvePath("result", "../jae/hello") +// -> result/home/jae/hello +func ResolvePath(output string, dstPath string) (string, error) { + if filepath.IsAbs(dstPath) || + filepath.IsLocal(dstPath) { + return filepath.Join(output, dstPath), nil } - absPkgPath, err := filepath.Abs(string(path)) + // Make dstPath absolute and join it with output. + absDst, err := filepath.Abs(dstPath) if err != nil { return "", err } - pkgPath := strings.TrimPrefix(absPkgPath, gnoenv.RootDir()) - - return filepath.Join(absOutput, pkgPath), nil + return filepath.Join(output, absDst), nil } // WriteDirFile write file to the path and also create diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 8a1493c2563..b0662bfe83c 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -298,9 +298,7 @@ func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.F } transp := TranspileImportPath(importPath) - if !astutil.RewriteImport(fset, f, importPath, transp) { - errs.Add(fset.Position(importSpec.Pos()), fmt.Sprintf("failed to replace the %q package with %q", importPath, transp)) - } + importSpec.Path.Value = strconv.Quote(transp) } } diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index 1bdc860adae..bdbab135140 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -513,8 +513,10 @@ func (s *SectionReader) Read(p []byte) (n int, err error) { return } -var errWhence = errors.New("Seek: invalid whence") -var errOffset = errors.New("Seek: invalid offset") +var ( + errWhence = errors.New("Seek: invalid whence") + errOffset = errors.New("Seek: invalid offset") +) func (s *SectionReader) Seek(offset int64, whence int) (int64, error) { switch whence { diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index c079e0dd42b..8043df49882 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -36,6 +36,7 @@ func GetOrigPkgAddr() Address { func GetCallerAt(n int) Address { return Address(callerAt(n)) } + func DerivePkgAddr(pkgPath string) Address { return Address(derivePkgAddr(pkgPath)) } From d7787889af3721289529e42371216a3f0798d136 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 14:44:10 +0100 Subject: [PATCH 14/67] lint fix --- gno.land/pkg/sdk/vm/keeper.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index c06c3b3761c..23fb3998131 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,7 +9,6 @@ import ( "regexp" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -162,7 +161,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { } // Validate Gno syntax and type check. - if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { return err } @@ -324,7 +323,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { return "", err } From 1a61b6b67b19cb65cce7cfe476cb00fd5c8ee3d9 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 23:01:54 +0100 Subject: [PATCH 15/67] fix errors in genstd --- misc/genstd/mapping.go | 88 +++++++++++++++++-------------------- misc/genstd/mapping_test.go | 86 +++++++++++++++--------------------- 2 files changed, 76 insertions(+), 98 deletions(-) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index d87484ab865..69aad8af3a6 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "go/ast" "go/types" @@ -110,8 +109,7 @@ func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) if m.isTypedValue(goe) { *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) } else { - merged := m.mergeTypes(gnoe, goe) - *dst = append(*dst, mappingType{Type: merged}) + *dst = append(*dst, mappingType{Type: gnoe}) } return nil }) @@ -183,57 +181,56 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) return nil } -// mergeTypes merges gnoe and goe into a single ast.Expr. -// -// gnoe and goe are expected to have the same underlying structure, but they -// may differ in their type identifiers (possibly qualified, ie pkg.T). -// if they differ, mergeTypes returns nil. -// -// When two type identifiers are found, they are checked against the list of -// linkedTypes to determine if they refer to a linkedType. If they are not, -// mergeTypes returns nil. If they are, the *ast.Ident/*ast.SelectorExpr is -// replaced with a *linkedIdent. -// -// mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is -// (recursively) newly allocated. -func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { +type typeMismatchError struct { + gnoe, goe ast.Expr +} + +func (te *typeMismatchError) Error() string { + return fmt.Sprintf("typesEqual: gno type %q does not match go type %q", + types.ExprString(te.gnoe), types.ExprString(te.goe)) +} + +// typesEqual ensures that the given gnoe and goe, expected to represent +// expressions to identify types, are equal. +func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { + // If a type assertion fails, like in the below + // goe, ok := ..., then goe will be set to a zero value, and might + // lead to nil pointer dereferences. Setting up the mismatch error + // here avoids that. + mismatch := typeMismatchError{gnoe, goe} + switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type case *ast.Ident: - // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) - if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { - return &ast.Ident{Name: gnoe.Name} + switch { + case !ok || gnoe.Name != goi.Name: + return &mismatch + case !isBuiltin(gnoe.Name): + return fmt.Errorf("typesEqual: usage of non-builtin type %q", gnoe.Name) + default: + return nil } - panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) - - // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: goe, ok := goe.(*ast.StarExpr) if !ok { - return nil + return &mismatch } - x := m.mergeTypes(gnoe.X, goe.X) - if x == nil { - return nil + if err := m.typesEqual(gnoe.X, goe.X); err != nil { + return err } - return &ast.StarExpr{X: x} + return nil case *ast.ArrayType: goe, ok := goe.(*ast.ArrayType) if !ok || !basicLitsEqual(gnoe.Len, goe.Len) { - return nil - } - elt := m.mergeTypes(gnoe.Elt, goe.Elt) - if elt == nil { - return nil + return &mismatch } - var l ast.Expr - if gnoe.Len != nil { - l = &ast.BasicLit{Value: gnoe.Len.(*ast.BasicLit).Value} + if err := m.typesEqual(gnoe.Elt, goe.Elt); err != nil { + return err } - return &ast.ArrayType{Len: l, Elt: elt} + return nil case *ast.StructType, *ast.FuncType, @@ -304,11 +301,13 @@ func basicLitsEqual(x1, x2 ast.Expr) bool { return l1.Value == l2.Value } -// Signatures match when they accept the same elementary types, or a linked -// type mapping (see [linkedTypes]). +// Signatures match when they accept the same, unnamed types. +// +// If the first parameter to the Go function is *[gnolang.Machine], it is +// ignored when matching to the Gno function. // -// Additionally, if the first parameter to the Go function is -// *[gnolang.Machine], it is ignored when matching to the Gno function. +// If a Go parameter is [gnolang.TypedValue], it always matches any +// corresponding parameter in Gno. func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { panic("type parameters not supported") @@ -329,8 +328,6 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) } -var errNoMatch = errors.New("no match") - func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if gnofl == nil || gofl == nil { return gnofl == nil && gofl == nil @@ -343,10 +340,7 @@ func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if m.isTypedValue(goe) { return nil } - if m.mergeTypes(gnoe, goe) == nil { - return errNoMatch - } - return nil + return m.typesEqual(gnoe, goe) }) return err == nil } diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go index b0cfa1bd4a7..292402a06ac 100644 --- a/misc/genstd/mapping_test.go +++ b/misc/genstd/mapping_test.go @@ -173,9 +173,9 @@ func Test_linkFunctions_noMatchSig(t *testing.T) { linkFunctions(pkgs) } -// mergeTypes - separate tests. +// typesEqual - separate tests. -var mergeTypesMapping = &mapping{ +var typesEqualMapping = &mapping{ GnoImportPath: "std", GnoFunc: "Fn", GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/std", @@ -191,7 +191,6 @@ var mergeTypesMapping = &mapping{ }, gnoImports: []*ast.ImportSpec{ { - // cheating a bit -- but we currently only have linked types in `std`. Path: &ast.BasicLit{Value: `"std"`}, }, { @@ -200,75 +199,60 @@ var mergeTypesMapping = &mapping{ }, } -func Test_mergeTypes(t *testing.T) { +func Test_typesEqual(t *testing.T) { tt := []struct { - gnoe, goe string - result ast.Expr + gnoe, goe string + errContains string }{ - {"int", "int", &ast.Ident{Name: "int"}}, - {"*[11][]rune", "*[11][]rune", &ast.StarExpr{ - X: &ast.ArrayType{Len: &ast.BasicLit{Value: "11"}, Elt: &ast.ArrayType{ - Elt: &ast.Ident{Name: "rune"}, - }}, - }}, - - {"Address", "crypto.Bech32Address", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Address", - goPackage: "github.com/gnolang/gno/tm2/pkg/crypto", - goName: "Bech32Address", - }}}, - {"std.Realm", "Realm", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Realm", - goPackage: "github.com/gnolang/gno/gnovm/stdlibs/std", - goName: "Realm", - }}}, + {"int", "int", ""}, + {"*[11][]rune", "*[11][ ]rune", ""}, + + {"madeup", "madeup", "non-builtin type"}, + + {"int", "string", "does not match"}, + {"*int", "int", "does not match"}, + {"string", "*string", "does not match"}, + {"*string", "*int", "does not match"}, + + {"[]int", "[1]int", "does not match"}, + {"[1]int", "[]int", "does not match"}, + {"[2]int", "[2]string", "does not match"}, + // valid, but unsupported (only BasicLits) + {"[(11)]int", "[(11)]string", "does not match"}, + + // even though mathematically equal, for simplicity we don't implement + // "true" basic lit equivalence + {"[8]int", "[0x8]int", "does not match"}, } - for _, tv := range tt { - t.Run(tv.gnoe, func(t *testing.T) { + for idx, tv := range tt { + t.Run(fmt.Sprintf("%02d_%s", idx, tv.gnoe), func(t *testing.T) { gnoe, err := parser.ParseExpr(tv.gnoe) require.NoError(t, err) goe, err := parser.ParseExpr(tv.goe) require.NoError(t, err) - result := mergeTypesMapping.mergeTypes(gnoe, goe) - assert.Equal(t, result, tv.result) + err = typesEqualMapping.typesEqual(gnoe, goe) + if tv.errContains == "" { + assert.NoError(t, err) + } else { + _ = assert.Error(t, err) && + assert.Contains(t, err.Error(), tv.errContains) + } }) } } -func Test_mergeTypes_invalid(t *testing.T) { +func Test_typesEqual_panic(t *testing.T) { tt := []struct { gnoe, goe string panic string }{ - {"int", "string", ""}, - - {"*int", "int", ""}, - {"string", "*string", ""}, - {"*string", "*int", ""}, - - {"[]int", "[1]int", ""}, - {"[1]int", "[]int", ""}, - {"[2]int", "[2]string", ""}, - // valid, but unsupported (only BasicLits) - {"[(11)]int", "[(11)]string", ""}, - - {"Address", "string", ""}, - {"math.X", "X", ""}, - {"map[string]string", "map[string]string", "not implemented"}, {"func(s string)", "func(s string)", "not implemented"}, {"interface{}", "interface{}", "not implemented"}, {"struct{}", "struct{}", "not implemented"}, - {"1 + 2", "1 + 2", "invalid expression"}, - - // even though semantically equal, for simplicity we don't implement - // "true" basic lit equivalence - {"[8]int", "[0x8]int", ""}, } for _, tv := range tt { @@ -287,7 +271,7 @@ func Test_mergeTypes_invalid(t *testing.T) { } }() - result := mergeTypesMapping.mergeTypes(gnoe, goe) + result := typesEqualMapping.typesEqual(gnoe, goe) assert.Nil(t, result) }) } From 93a99a367070476fbf55d1c353b2742c5c51b541 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 00:01:37 +0100 Subject: [PATCH 16/67] fixup --- gnovm/cmd/gno/transpile.go | 3 ++- gnovm/pkg/transpiler/transpiler.go | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index b388a635a48..2a173c396cf 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -304,7 +304,8 @@ func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs } if strings.HasPrefix(path, transpiler.ImportPrefix) { res := strings.TrimPrefix(path, transpiler.ImportPrefix) - dirs = append(dirs, rootDir+strings.Replace(res, "/", string(filepath.Separator), -1)) + + dirs = append(dirs, rootDir+filepath.FromSlash(res)) } } return diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index b0662bfe83c..ce3b4e85680 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -90,7 +90,10 @@ func TranspiledFilenameAndTags(gnoFilePath string) (targetFilename, tags string) // discriminate between test and normal source files. func Transpile(source, tags, filename string) (*Result, error) { fset := token.NewFileSet() - f, err := parser.ParseFile(fset, filename, source, parser.SkipObjectResolution) + f, err := parser.ParseFile(fset, filename, source, + // SkipObjectResolution -- unused here. + // ParseComments -- so that they show up when re-building the AST. + parser.SkipObjectResolution|parser.ParseComments) if err != nil { return nil, fmt.Errorf("parse: %w", err) } @@ -112,8 +115,7 @@ func Transpile(source, tags, filename string) (*Result, error) { // this is a standard library. Mark it in the options so the native // bindings resolve correctly. path := strings.TrimPrefix(filename, stdlibPrefix) - path = filepath.Dir(path) - path = strings.Replace(path, string(filepath.Separator), "/", -1) + path = filepath.ToSlash(filepath.Dir(path)) path = strings.TrimLeft(path, "/") ctx.stdlibPath = path From b4e0dd00ec1a4efbe0e48373ead8ebec7b305eaa Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 00:24:16 +0100 Subject: [PATCH 17/67] address linter --- gnovm/cmd/gno/util.go | 30 ------------------------------ gnovm/pkg/gnolang/gonative_test.go | 7 ++++--- gnovm/stdlibs/std/banker.go | 2 +- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 6d650b3c0aa..c958768a6cb 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -21,36 +21,6 @@ func isFileExist(path string) bool { return err == nil } -func gnoFilesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) - if err != nil { - return nil, fmt.Errorf("invalid file or package path: %w", err) - } - if !info.IsDir() { - curpath := arg - paths = append(paths, curpath) - } else { - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - - if !isGnoFile(f) { - return nil // skip - } - paths = append(paths, curpath) - return nil - }) - if err != nil { - return nil, err - } - } - } - return paths, nil -} - func gnoPackagesFromArgs(args []string) ([]string, error) { paths := []string{} for _, arg := range args { diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index e348ffe9c22..8cf3a937c2c 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string, newStore Store) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) @@ -90,10 +90,11 @@ func main() { n := MustParseFile("main.go", c) m.RunFiles(n) m.RunMain() + // weird `+` is used to place a space, without having editors strip it away. assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } @@ -132,7 +133,7 @@ func main() { assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 243d457b9be..2533071ccd8 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -23,7 +23,7 @@ type BankerInterface interface { const ( // Can only read state. - btReadonly uint8 = iota + btReadonly uint8 = iota //nolint // Can only send from tx send. btOrigSend // Can send from all realm coins. From a889deab8e04a7ad927f6c2969f9d06adf681783 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 22 Feb 2024 21:09:00 +0100 Subject: [PATCH 18/67] begin removing support for linked identifiers --- gnovm/stdlibs/std/banker.gno | 68 +++++++++++---- gnovm/stdlibs/std/banker.go | 138 ++++++++++++------------------ gnovm/stdlibs/std/context.gno | 4 - gnovm/stdlibs/std/context.go | 2 +- gnovm/stdlibs/std/native.gno | 73 ++++++++++++---- gnovm/stdlibs/std/native.go | 154 +++++++++++----------------------- 6 files changed, 211 insertions(+), 228 deletions(-) delete mode 100644 gnovm/stdlibs/std/context.gno diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 44e611b780f..9eea149062e 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -22,10 +22,11 @@ type Banker interface { RemoveCoin(addr Address, denom string, amount int64) } -// Also available natively in stdlibs/context.go +// BankerType represents the "permission level" requested for a banker, +// retrievable through [GetBanker]. type BankerType uint8 -// Also available natively in stdlibs/context.go +// Available types of banker. const ( // Can only read state. BankerTypeReadonly BankerType = iota @@ -35,36 +36,69 @@ const ( BankerTypeRealmSend // Can issue and remove realm coins. BankerTypeRealmIssue + + maxBanker ) //---------------------------------------- // adapter for native banker -type bankAdapter struct { - nativeBanker Banker +// GetBanker returns a new Banker, with its capabilities matching the given +// [BankerType]. +func GetBanker(bt BankerType) Banker { + if bt >= maxBanker { + panic("invalid banker type") + } + return banker{bt} +} + +// These are native bindings to the banker's functions. +func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) +func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) +func bankerTotalCoin(bt uint8, denom string) int64 +func bankerIssueCoin(bt uint8, addr string, denom string, amount string) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) + +type banker struct { + bt uint8 } -func (ba bankAdapter) GetCoins(addr Address) (dst Coins) { - // convert native -> gno - coins := ba.nativeBanker.GetCoins(addr) - for _, coin := range coins { - dst = append(dst, (Coin)(coin)) +func (b banker) GetCoins(addr Address) (dst Coins) { + denoms, amounts := bankerGetCoins(b.bt, string(addr)) + dst = make(Coins, len(denoms)) + for i := range dst { + dst[i] = Coin{denoms[i], amounts[i]} } return dst } -func (ba bankAdapter) SendCoins(from, to Address, amt Coins) { - ba.nativeBanker.SendCoins(from, to, amt) +func (b banker) SendCoins(from, to Address, amt Coins) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot send coins") + } + denoms := make([]string, len(amt)) + amounts := make([]int64, len(amt)) + for i, coin := range amt { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) } -func (ba bankAdapter) TotalCoin(denom string) int64 { - return ba.nativeBanker.TotalCoin(denom) +func (b banker) TotalCoin(denom string) int64 { + return bankerTotalCoin(b.bt, denom) } -func (ba bankAdapter) IssueCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.IssueCoin(addr, denom, amount) +func (b banker) IssueCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot issue coins") + } + bankerIssueCoin(b.bt, string(addr), denom, amount) } -func (ba bankAdapter) RemoveCoin(addr Address, denom string, amount int64) { - ba.nativeBanker.RemoveCoin(addr, denom, amount) +func (b banker) RemoveCoin(addr Address, denom string, amount int64) { + if b.bt == BankerTypeReadonly { + panic("BankerTypeReadonly cannot remove coins") + } + bankerRemoveCoin(b.bt, string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 7653f2a519f..695b4a26aa5 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -3,16 +3,17 @@ package std import ( "fmt" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) -// This has the same interface as stdlibs/std.Banker. -// The native implementation of Banker (wrapped by any -// wrappers for limiting functionality as necessary) -// becomes available in Gno that implements -// stdlibs/std.Banker. -type Banker interface { +// BankerInterface is the interface through which Gno is capable of accessing +// the blockchain's banker. +// +// The name is what it is to avoid a collision with Gno's Banker, when +// transpiling. +type BankerInterface interface { GetCoins(addr crypto.Bech32Address) (dst std.Coins) SendCoins(from, to crypto.Bech32Address, amt std.Coins) TotalCoin(denom string) int64 @@ -20,98 +21,63 @@ type Banker interface { RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) } -// Used in std.GetBanker(options). -// Also available as Gno in stdlibs/std/banker.gno -type BankerType uint8 - -// Also available as Gno in stdlibs/std/banker.gno const ( // Can only read state. - BankerTypeReadonly BankerType = iota + btReadonly uint8 = iota // Can only send from tx send. - BankerTypeOrigSend + btOrigSend // Can send from all realm coins. - BankerTypeRealmSend + btRealmSend // Can issue and remove realm coins. - BankerTypeRealmIssue + btRealmIssue ) -//---------------------------------------- -// ReadonlyBanker - -type ReadonlyBanker struct { - banker Banker -} - -func NewReadonlyBanker(banker Banker) ReadonlyBanker { - return ReadonlyBanker{banker} -} - -func (rb ReadonlyBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rb.banker.GetCoins(addr) -} - -func (rb ReadonlyBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - panic("ReadonlyBanker cannot send coins") -} - -func (rb ReadonlyBanker) TotalCoin(denom string) int64 { - return rb.banker.TotalCoin(denom) -} - -func (rb ReadonlyBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot issue coins") -} - -func (rb ReadonlyBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("ReadonlyBanker cannot remove coins") -} - -//---------------------------------------- -// OrigSendBanker - -type OrigSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address - origSend std.Coins - origSendSpent *std.Coins -} - -func NewOrigSendBanker(banker Banker, pkgAddr crypto.Bech32Address, origSend std.Coins, origSendSpent *std.Coins) OrigSendBanker { - if origSendSpent == nil { - panic("origSendSpent cannot be nil") - } - return OrigSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - origSend: origSend, - origSendSpent: origSendSpent, +func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) + denoms = make([]string, len(coins)) + amounts = make([]string, len(coins)) + for i, coin := range coins { + denoms[i] = coin.Denom + amounts[i] = coin.Amounts } -} - -func (osb OrigSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return osb.banker.GetCoins(addr) -} - -func (osb OrigSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) - } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + return denoms, amounts +} + +// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) +// - Make all the X_banker functions work as they should +// - Remove the identifier mapping logic from genstd. +// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. +// - Make precompilation understand that bodyless function -> native function. +// - Add whether the *gno.Machine parameter is present to the native function data. +// When you get back on track for the transpiler: +// - Make StaticCheck work +// - Add a way to recursively precompile dependencies +// - Work until gno transpile --gobuild can create fully buildable go code! + +func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { + // bt != BankerTypeReadonly (checked in gno) + if bt == btOrigSend { + if from != osb.pkgAddr { + panic(fmt.Sprintf( + "OrigSendBanker can only send from the realm package address %q, but got %q", + osb.pkgAddr, from)) + } + spent := (*osb.origSendSpent).Add(amt) + if !osb.origSend.IsAllGTE(spent) { + panic(fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, osb.origSend, *osb.origSendSpent)) + } + osb.banker.SendCoins(from, to, amt) + *osb.origSendSpent = spent } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent } -func (osb OrigSendBanker) TotalCoin(denom string) int64 { - return osb.banker.TotalCoin(denom) +func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { + return m.Context.(ExecContext).Banker.TotalCoin(denom) } +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { panic("OrigSendBanker cannot issue coins") diff --git a/gnovm/stdlibs/std/context.gno b/gnovm/stdlibs/std/context.gno deleted file mode 100644 index 878c963b22b..00000000000 --- a/gnovm/stdlibs/std/context.gno +++ /dev/null @@ -1,4 +0,0 @@ -package std - -// ExecContext is not exposed, -// use native injections std.GetChainID(), std.GetHeight() etc instead. diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go index c50e2e5e1b9..72ca7445aef 100644 --- a/gnovm/stdlibs/std/context.go +++ b/gnovm/stdlibs/std/context.go @@ -16,5 +16,5 @@ type ExecContext struct { OrigPkgAddr crypto.Bech32Address OrigSend std.Coins OrigSendSpent *std.Coins // mutable - Banker Banker + Banker BankerInterface } diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 2f7da810bcb..b922376bc71 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,18 +1,59 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func CurrentRealmPath() string // injected -func GetChainID() string // injected -func GetHeight() int64 // injected -func GetOrigSend() Coins // injected -func GetOrigCaller() Address // injected -func CurrentRealm() Realm // injected -func PrevRealm() Realm // injected -func GetOrigPkgAddr() Address // injected -func GetCallerAt(n int) Address // injected -func GetBanker(bt BankerType) Banker // injected -func DerivePkgAddr(pkgPath string) Address // injected - -func EncodeBech32(prefix string, bz [20]byte) Address // injected -func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected + +func GetOrigSend() Coins { + den, amt := origSend() + coins := make(Coins, len(den)) + for i := range coins { + coins[i] = Coin{Denom: den[i], Amount: amt[i]} + } + return coins +} + +func GetOrigCaller() Address { + return Address(origCaller()) +} + +func CurrentRealm() Realm { + addr, path := getRealm(0) + return Realm{addr, path} +} + +func PrevRealm() Realm { + addr, path := getRealm(1) + return Realm{addr, path} +} + +func GetOrigPkgAddr() Address { + return Address(origPkgAddr()) +} + +func GetCallerAt(n int) Address { + return Address(callerAt(n)) +} +func DerivePkgAddr(pkgPath string) Address { + return Address(derivePkgAddr(pkgPath)) +} + +func EncodeBech32(prefix string, bz [20]byte) Address { + return Address(encodeBech32(prefix, bz)) +} + +func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) { + return decodeBech32(string(addr)) +} + +// Variations which don't use named types. +func origSend() (denoms []string, amounts []int64) +func origCaller() string +func origPkgAddr() string +func callerAt(n int) string +func getRealm(height int) (address string, pkgPath string) +func derivePkgAddr(pkgPath string) string +func encodeBech32(prefix string, bz [20]byte) string +func decodeBech32(addr string) (prefix string, bz [20]byte, ok bool) diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 044badaa308..344db126fb8 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,12 +1,9 @@ package std import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -34,82 +31,26 @@ func GetHeight(m *gno.Machine) int64 { return m.Context.(ExecContext).Height } -func GetOrigSend(m *gno.Machine) std.Coins { - return m.Context.(ExecContext).OrigSend -} - -func GetOrigCaller(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigCaller -} - -func CurrentRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage != nil && fr.LastPackage.IsRealm() { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = fr.LastPackage.PkgPath - break - } - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, +func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { + os := m.Context.(ExecContext).OrigSend + denoms = make([]string, len(os)) + amounts = make([]int64, len(os)) + for i, coin := range os { + denoms[i] = coin.Denom + amounts[i] = coin.Amount } + return denoms, amounts } -func PrevRealm(m *gno.Machine) Realm { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { - // Ignore non-realm frame - continue - } - pkgPath := fr.LastPackage.PkgPath - // The first realm we encounter will be the one calling - // this function; to get the calling realm determine the first frame - // where fr.LastPackage changes. - if lastPkgPath == "" { - lastPkgPath = pkgPath - } else if lastPkgPath == pkgPath { - continue - } else { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = pkgPath - break - } - } - - // Empty the pkgPath if we return a user - if ctx.OrigCaller == lastCaller { - lastPkgPath = "" - } - - return Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - } +func X_origCaller(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigCaller) } -func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { - return m.Context.(ExecContext).OrigPkgAddr +func X_origPkgAddr(m *gno.Machine) string { + return string(m.Context.(ExecContext).OrigPkgAddr) } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" @@ -124,52 +65,57 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { if n == m.NumFrames() { // This makes it consistent with GetOrigCaller. ctx := m.Context.(ExecContext) - return ctx.OrigCaller + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { - ctx := m.Context.(ExecContext) - banker := ctx.Banker - switch bankerType { - case BankerTypeReadonly: - banker = NewReadonlyBanker(banker) - case BankerTypeOrigSend: - banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) - case BankerTypeRealmSend: - banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) - case BankerTypeRealmIssue: - banker = banker - default: - panic("should not happen") // defensive +func X_getRealm(m *gno.Machine, height int) (address string, pkgPath string) { + var ( + ctx = m.Context.(ExecContext) + currentCaller crypto.Bech32Address + // Keeps track of the number of times currentCaller + // has changed. + changes int + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { + continue + } + + // LastPackage is a realm. Get caller and pkgPath, and compare against + // current* values. + caller := fr.LastPackage.GetPkgAddr().Bech32() + pkgPath := fr.LastPackage.PkgPath + if caller != currentCaller { + if changes == height { + return string(caller), pkgPath + } + currentCaller = caller + changes++ + } } - m.Alloc.AllocateStruct() // defensive; native space not allocated. - m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - // make gno bankAdapter{rv} - btv := gno.Go2GnoNativeValue(m.Alloc, reflect.ValueOf(banker)) - bsv := m.Alloc.NewStructWithFields(btv) - bankAdapterType := m.Store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) - res0 := gno.TypedValue{T: bankAdapterType, V: bsv} + // Fallback case: return OrigCaller. + return string(ctx.OrigCaller), "" +} - return res0 +func X_derivePkgAddr(pkgPath string) string { + return string(gno.DerivePkgAddr(pkgPath).Bech32()) } -func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { +func X_encodeBech32(prefix string, bytes [20]byte) string { b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) if err != nil { panic(err) // should not happen } - return crypto.Bech32Address(b32) -} - -func DerivePkgAddr(pkgPath string) crypto.Bech32Address { - return gno.DerivePkgAddr(pkgPath).Bech32() + return b32 } -func DecodeBech32(addr crypto.Bech32Address) (prefix string, bytes [20]byte, ok bool) { - prefix, bz, err := bech32.Decode(string(addr)) +func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { + prefix, bz, err := bech32.Decode(addr) if err != nil || len(bz) != 20 { return "", [20]byte{}, false } From aae89db370d6a74865fdc4b6eaeb575f99a2ce3c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:56:05 +0100 Subject: [PATCH 19/67] convert native bindings to never use linked types --- gnovm/stdlibs/native.go | 270 ++++++++++++++++++------ gnovm/stdlibs/std/banker.gno | 48 +++-- gnovm/stdlibs/std/banker.go | 118 ++++------- gnovm/stdlibs/std/coins.gno | 12 +- gnovm/stdlibs/std/frame.go | 20 -- gnovm/stdlibs/std/native.gno | 4 +- gnovm/stdlibs/std/native.go | 33 ++- gnovm/stdlibs/stdlibs.go | 1 - gnovm/tests/files/zrealm_natbind0.gno | 57 ++--- gnovm/tests/stdlibs/native.go | 64 +++--- gnovm/tests/stdlibs/std/std.gno | 38 +++- gnovm/tests/stdlibs/std/std.go | 40 ++-- misc/genstd/exprstring.go | 290 -------------------------- misc/genstd/mapping.go | 98 +-------- 14 files changed, 431 insertions(+), 662 deletions(-) delete mode 100644 gnovm/stdlibs/std/frame.go delete mode 100644 misc/genstd/exprstring.go diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 4125c56ffe1..0cb7d9a7859 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -13,7 +13,6 @@ import ( libs_strconv "github.com/gnolang/gno/gnovm/stdlibs/strconv" libs_testing "github.com/gnolang/gno/gnovm/stdlibs/testing" libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" ) type nativeFunc struct { @@ -162,27 +161,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "AssertOriginCall", - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{}, + "bankerGetCoins", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, + }, func(m *gno.Machine) { - libs_std.AssertOriginCall( - m, + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0, r1 := libs_std.X_bankerGetCoins( + m, + p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "IsOriginCall", - []gno.FieldTypeExpr{}, + "bankerSendCoins", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("bool")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("[]string")}, + {Name: gno.N("p4"), Type: gno.X("[]int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.IsOriginCall( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []string + rp3 = reflect.ValueOf(&p3).Elem() + p4 []int64 + rp4 = reflect.ValueOf(&p4).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 4, "")).TV, rp4) + + libs_std.X_bankerSendCoins( m, + p0, p1, p2, p3, p4) + }, + }, + { + "std", + "bankerTotalCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() ) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_std.X_bankerTotalCoin( + m, + p0, p1) + m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, @@ -192,32 +270,90 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealmPath", - []gno.FieldTypeExpr{}, + "bankerIssueCoin", []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, }, + []gno.FieldTypeExpr{}, func(m *gno.Machine) { - r0 := libs_std.CurrentRealmPath( + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerIssueCoin( m, + p0, p1, p2, p3) + }, + }, + { + "std", + "bankerRemoveCoin", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint8")}, + {Name: gno.N("p1"), Type: gno.X("string")}, + {Name: gno.N("p2"), Type: gno.X("string")}, + {Name: gno.N("p3"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint8 + rp0 = reflect.ValueOf(&p0).Elem() + p1 string + rp1 = reflect.ValueOf(&p1).Elem() + p2 string + rp2 = reflect.ValueOf(&p2).Elem() + p3 int64 + rp3 = reflect.ValueOf(&p3).Elem() ) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) + + libs_std.X_bankerRemoveCoin( + m, + p0, p1, p2, p3) }, }, { "std", - "GetChainID", + "AssertOriginCall", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + libs_std.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r0"), Type: gno.X("bool")}, }, func(m *gno.Machine) { - r0 := libs_std.GetChainID( + r0 := libs_std.IsOriginCall( m, ) @@ -230,13 +366,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetHeight", + "CurrentRealmPath", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("int64")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetHeight( + r0 := libs_std.CurrentRealmPath( m, ) @@ -249,13 +385,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigSend", + "GetChainID", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Coins")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigSend( + r0 := libs_std.GetChainID( m, ) @@ -268,13 +404,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigCaller", + "GetHeight", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigCaller( + r0 := libs_std.GetHeight( m, ) @@ -287,13 +423,14 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "CurrentRealm", + "origSend", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("[]string")}, + {Name: gno.N("r1"), Type: gno.X("[]int64")}, }, func(m *gno.Machine) { - r0 := libs_std.CurrentRealm( + r0, r1 := libs_std.X_origSend( m, ) @@ -302,17 +439,22 @@ var nativeFuncs = [...]nativeFunc{ m.Store, reflect.ValueOf(&r0).Elem(), )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "PrevRealm", + "origCaller", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Realm")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.PrevRealm( + r0 := libs_std.X_origCaller( m, ) @@ -325,13 +467,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetOrigPkgAddr", + "origPkgAddr", []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := libs_std.GetOrigPkgAddr( + r0 := libs_std.X_origPkgAddr( m, ) @@ -344,12 +486,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -360,7 +502,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetCallerAt( + r0 := libs_std.X_callerAt( m, p0) @@ -373,37 +515,47 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetBanker", + "getRealm", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("BankerType")}, + {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Banker")}, + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 libs_std.BankerType + p0 int rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.GetBanker( + r0, r1 := libs_std.X_getRealm( m, p0) - m.PushValue(r0) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) }, }, { "std", - "DerivePkgAddr", + "derivePkgAddr", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -414,7 +566,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := libs_std.DerivePkgAddr(p0) + r0 := libs_std.X_derivePkgAddr(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -425,13 +577,13 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "EncodeBech32", + "encodeBech32", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, {Name: gno.N("p1"), Type: gno.X("[20]byte")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -445,7 +597,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - r0 := libs_std.EncodeBech32(p0, p1) + r0 := libs_std.X_encodeBech32(p0, p1) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -456,9 +608,9 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "DecodeBech32", + "decodeBech32", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, @@ -468,13 +620,13 @@ var nativeFuncs = [...]nativeFunc{ func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0, r1, r2 := libs_std.DecodeBech32(p0) + r0, r1, r2 := libs_std.X_decodeBech32(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, diff --git a/gnovm/stdlibs/std/banker.gno b/gnovm/stdlibs/std/banker.gno index 9eea149062e..958fea369a3 100644 --- a/gnovm/stdlibs/std/banker.gno +++ b/gnovm/stdlibs/std/banker.gno @@ -1,5 +1,7 @@ package std +import "strconv" + // Realm functions can call std.GetBanker(options) to get // a banker instance. Banker objects cannot be persisted, // but can be passed onto other functions to be transacted @@ -40,6 +42,21 @@ const ( maxBanker ) +func (b BankerType) String() string { + switch b { + case BankerTypeReadonly: + return "BankerTypeReadonly" + case BankerTypeOrigSend: + return "BankerTypeOrigSend" + case BankerTypeRealmSend: + return "BankerTypeRealmSend" + case BankerTypeRealmIssue: + return "BankerTypeRealmIssue" + default: + panic("invalid BankerType: " + strconv.Itoa(int(b))) + } +} + //---------------------------------------- // adapter for native banker @@ -56,15 +73,15 @@ func GetBanker(bt BankerType) Banker { func bankerGetCoins(bt uint8, addr string) (denoms []string, amounts []int64) func bankerSendCoins(bt uint8, from, to string, denoms []string, amounts []int64) func bankerTotalCoin(bt uint8, denom string) int64 -func bankerIssueCoin(bt uint8, addr string, denom string, amount string) -func bankerRemoveCoin(bt uint8, addr string, denom string, amount string) +func bankerIssueCoin(bt uint8, addr string, denom string, amount int64) +func bankerRemoveCoin(bt uint8, addr string, denom string, amount int64) type banker struct { - bt uint8 + bt BankerType } func (b banker) GetCoins(addr Address) (dst Coins) { - denoms, amounts := bankerGetCoins(b.bt, string(addr)) + denoms, amounts := bankerGetCoins(uint8(b.bt), string(addr)) dst = make(Coins, len(denoms)) for i := range dst { dst[i] = Coin{denoms[i], amounts[i]} @@ -76,29 +93,24 @@ func (b banker) SendCoins(from, to Address, amt Coins) { if b.bt == BankerTypeReadonly { panic("BankerTypeReadonly cannot send coins") } - denoms := make([]string, len(amt)) - amounts := make([]int64, len(amt)) - for i, coin := range amt { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - bankerSendCoins(b.bt, string(from), string(to), denoms, amounts) + denoms, amounts := amt.expandNative() + bankerSendCoins(uint8(b.bt), string(from), string(to), denoms, amounts) } func (b banker) TotalCoin(denom string) int64 { - return bankerTotalCoin(b.bt, denom) + return bankerTotalCoin(uint8(b.bt), denom) } func (b banker) IssueCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot issue coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot issue coins") } - bankerIssueCoin(b.bt, string(addr), denom, amount) + bankerIssueCoin(uint8(b.bt), string(addr), denom, amount) } func (b banker) RemoveCoin(addr Address, denom string, amount int64) { - if b.bt == BankerTypeReadonly { - panic("BankerTypeReadonly cannot remove coins") + if b.bt != BankerTypeRealmIssue { + panic(b.bt.String() + " cannot remove coins") } - bankerRemoveCoin(b.bt, string(addr), denom, amount) + bankerRemoveCoin(uint8(b.bt), string(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 695b4a26aa5..243d457b9be 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -33,96 +33,58 @@ const ( ) func X_bankerGetCoins(m *gno.Machine, bt uint8, addr string) (denoms []string, amounts []int64) { - coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address()) - denoms = make([]string, len(coins)) - amounts = make([]string, len(coins)) - for i, coin := range coins { - denoms[i] = coin.Denom - amounts[i] = coin.Amounts - } - return denoms, amounts + coins := m.Context.(ExecContext).Banker.GetCoins(crypto.Bech32Address(addr)) + return ExpandCoins(coins) } -// TODO: LEAVING IT HERE FOR NOW (22/02/24 21:02) -// - Make all the X_banker functions work as they should -// - Remove the identifier mapping logic from genstd. -// - Possibly move all std types to std/types. This avoids any clashes with gno precompilation. -// - Make precompilation understand that bodyless function -> native function. -// - Add whether the *gno.Machine parameter is present to the native function data. -// When you get back on track for the transpiler: -// - Make StaticCheck work -// - Add a way to recursively precompile dependencies -// - Work until gno transpile --gobuild can create fully buildable go code! - -func X_bankerSendCoins(m *gno.Machine, bt uint8, from, to string, denoms []string, amounts []int64) { +func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []string, amounts []int64) { // bt != BankerTypeReadonly (checked in gno) - if bt == btOrigSend { - if from != osb.pkgAddr { - panic(fmt.Sprintf( - "OrigSendBanker can only send from the realm package address %q, but got %q", - osb.pkgAddr, from)) + + ctx := m.Context.(ExecContext) + amt := CompactCoins(denoms, amounts) + from, to := crypto.Bech32Address(fromS), crypto.Bech32Address(toS) + + if bt == btOrigSend || bt == btRealmSend { + if from != ctx.OrigPkgAddr { + m.Panic(typedString( + fmt.Sprintf( + "can only send from the realm package address %q, but got %q", + ctx.OrigPkgAddr, from), + )) + return } - spent := (*osb.origSendSpent).Add(amt) - if !osb.origSend.IsAllGTE(spent) { - panic(fmt.Sprintf( - `cannot send "%v", limit "%v" exceeded with "%v" already spent`, - amt, osb.origSend, *osb.origSendSpent)) + } + + switch bt { + case btOrigSend: + // indirection allows us to "commit" in a second phase + spent := (*ctx.OrigSendSpent).Add(amt) + if !ctx.OrigSend.IsAllGTE(spent) { + m.Panic(typedString( + fmt.Sprintf( + `cannot send "%v", limit "%v" exceeded with "%v" already spent`, + amt, ctx.OrigSend, *ctx.OrigSendSpent), + )) } - osb.banker.SendCoins(from, to, amt) - *osb.origSendSpent = spent + ctx.Banker.SendCoins(from, to, amt) + *ctx.OrigSendSpent = spent + case btRealmSend, btRealmIssue: + ctx.Banker.SendCoins(from, to, amt) + default: + panic(fmt.Sprintf("invalid banker type %d in bankerSendCoins", bt)) } } func X_bankerTotalCoin(m *gno.Machine, bt uint8, denom string) int64 { return m.Context.(ExecContext).Banker.TotalCoin(denom) } -func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) -func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount string) - -func (osb OrigSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot issue coins") -} - -func (osb OrigSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("OrigSendBanker cannot remove coins") -} - -//---------------------------------------- -// RealmSendBanker - -type RealmSendBanker struct { - banker Banker - pkgAddr crypto.Bech32Address -} - -func NewRealmSendBanker(banker Banker, pkgAddr crypto.Bech32Address) RealmSendBanker { - return RealmSendBanker{ - banker: banker, - pkgAddr: pkgAddr, - } -} - -func (rsb RealmSendBanker) GetCoins(addr crypto.Bech32Address) (dst std.Coins) { - return rsb.banker.GetCoins(addr) -} - -func (rsb RealmSendBanker) SendCoins(from, to crypto.Bech32Address, amt std.Coins) { - if from != rsb.pkgAddr { - panic(fmt.Sprintf( - "RealmSendBanker can only send from the realm package address %q, but got %q", - rsb.pkgAddr, from)) - } - rsb.banker.SendCoins(from, to, amt) -} - -func (rsb RealmSendBanker) TotalCoin(denom string) int64 { - return rsb.banker.TotalCoin(denom) -} -func (rsb RealmSendBanker) IssueCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot issue coins") +func X_bankerIssueCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } -func (rsb RealmSendBanker) RemoveCoin(addr crypto.Bech32Address, denom string, amount int64) { - panic("RealmSendBanker cannot remove coins") +func X_bankerRemoveCoin(m *gno.Machine, bt uint8, addr string, denom string, amount int64) { + // gno checks for bt == RealmIssue + m.Context.(ExecContext).Banker.IssueCoin(crypto.Bech32Address(addr), denom, amount) } diff --git a/gnovm/stdlibs/std/coins.gno b/gnovm/stdlibs/std/coins.gno index aaede81309d..dd138d30822 100644 --- a/gnovm/stdlibs/std/coins.gno +++ b/gnovm/stdlibs/std/coins.gno @@ -4,6 +4,7 @@ import "strconv" // NOTE: this is selectly copied over from pkgs/std/coin.go // TODO: import all functionality(?). +// TODO: implement Coin/Coins constructors. // Coin hold some amount of one currency. // A negative amount is invalid. @@ -62,4 +63,13 @@ func (a Coins) Add(b Coins) Coins { return c } -// TODO implement Coin/Coins constructors. +// expand for usage within natively bound functions. +func (cz Coins) expandNative() (denoms []string, amounts []int64) { + denoms = make([]string, len(cz)) + amounts = make([]int64, len(cz)) + for i, coin := range cz { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} diff --git a/gnovm/stdlibs/std/frame.go b/gnovm/stdlibs/std/frame.go deleted file mode 100644 index 2948813ad0f..00000000000 --- a/gnovm/stdlibs/std/frame.go +++ /dev/null @@ -1,20 +0,0 @@ -package std - -import "github.com/gnolang/gno/tm2/pkg/crypto" - -type Realm struct { - addr crypto.Bech32Address - pkgPath string -} - -func (r Realm) Addr() crypto.Bech32Address { - return r.addr -} - -func (r Realm) PkgPath() string { - return r.pkgPath -} - -func (r Realm) IsUser() bool { - return r.pkgPath == "" -} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index b922376bc71..c079e0dd42b 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -21,12 +21,12 @@ func GetOrigCaller() Address { func CurrentRealm() Realm { addr, path := getRealm(0) - return Realm{addr, path} + return Realm{Address(addr), path} } func PrevRealm() Realm { addr, path := getRealm(1) - return Realm{addr, path} + return Realm{Address(addr), path} } func GetOrigPkgAddr() Address { diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 344db126fb8..0965ef74277 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -4,6 +4,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -33,13 +34,7 @@ func GetHeight(m *gno.Machine) int64 { func X_origSend(m *gno.Machine) (denoms []string, amounts []int64) { os := m.Context.(ExecContext).OrigSend - denoms = make([]string, len(os)) - amounts = make([]int64, len(os)) - for i, coin := range os { - denoms[i] = coin.Denom - amounts[i] = coin.Amount - } - return denoms, amounts + return ExpandCoins(os) } func X_origCaller(m *gno.Machine) string { @@ -55,6 +50,8 @@ func X_callerAt(m *gno.Machine, n int) string { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames() { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -122,8 +119,26 @@ func X_decodeBech32(addr string) (prefix string, bytes [20]byte, ok bool) { return prefix, [20]byte(bz), true } -func typedString(s gno.StringValue) gno.TypedValue { +func typedString(s string) gno.TypedValue { tv := gno.TypedValue{T: gno.StringType} - tv.SetString(s) + tv.SetString(gno.StringValue(s)) return tv } + +func ExpandCoins(c std.Coins) (denoms []string, amounts []int64) { + denoms = make([]string, len(c)) + amounts = make([]int64, len(c)) + for i, coin := range c { + denoms[i] = coin.Denom + amounts[i] = coin.Amount + } + return denoms, amounts +} + +func CompactCoins(denoms []string, amounts []int64) std.Coins { + coins := make(std.Coins, len(denoms)) + for i := range coins { + coins[i] = std.Coin{Denom: denoms[i], Amount: amounts[i]} + } + return coins +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 22054613c03..1e215b098e2 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -17,7 +17,6 @@ func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") - store.AddGo2GnoMapping(reflect.TypeOf(libsstd.Realm{}), "std", "Realm") } func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno index 60e0d448202..0d614f5e8a1 100644 --- a/gnovm/tests/files/zrealm_natbind0.gno +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -8,20 +8,23 @@ import ( var node interface{} func init() { - node = std.GetOrigCaller + node = std.GetHeight } func main() { - f := node.(func() std.Address) + // NOTE: this test uses GetHeight and CurrentRealmPath, which are "pure" + // natively bound functions (ie. not indirections through a wrapper fn, + // to convert the types to builtin go/gno identifiers). + f := node.(func() int64) println(f()) - node = std.DerivePkgAddr - g := node.(func(path string) std.Address) - println(g("x")) + node = std.CurrentRealmPath + g := node.(func() string) + println(g()) } // Output: -// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// g19kt9e22k34ny5jf5plrjdltmws0jc0qqd2cwky +// 123 +// gno.land/r/test // Realm: // switchrealm["gno.land/r/test"] @@ -120,25 +123,15 @@ func main() { // { // "T": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] @@ -148,12 +141,12 @@ func main() { // "Closure": { // "@type": "/gno.RefValue", // "Escaped": true, -// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:5" +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:6" // }, // "FileName": "native.gno", // "IsMethod": false, -// "Name": "DerivePkgAddr", -// "NativeName": "DerivePkgAddr", +// "Name": "CurrentRealmPath", +// "NativeName": "CurrentRealmPath", // "NativePkg": "std", // "PkgPath": "std", // "Source": { @@ -161,32 +154,22 @@ func main() { // "BlockNode": null, // "Location": { // "File": "native.gno", -// "Line": "15", +// "Line": "5", // "Nonce": "0", // "PkgPath": "std" // } // }, // "Type": { // "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "pkgPath", -// "Tag": "", -// "Type": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// } -// } -// ], +// "Params": [], // "Results": [ // { // "Embedded": false, // "Name": "", // "Tag": "", // "Type": { -// "@type": "/gno.RefType", -// "ID": "std.Address" +// "@type": "/gno.PrimitiveType", +// "value": "16" // } // } // ] diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 373be0ad23b..6964a003025 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -9,8 +9,6 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" - tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" - tm2_std "github.com/gnolang/gno/tm2/pkg/std" ) type nativeFunc struct { @@ -105,12 +103,12 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "GetCallerAt", + "callerAt", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("Address")}, + {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -121,7 +119,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := testlibs_std.GetCallerAt( + r0 := testlibs_std.X_callerAt( m, p0) @@ -134,94 +132,106 @@ var nativeFuncs = [...]nativeFunc{ }, { "std", - "TestSetOrigCaller", + "testSetOrigCaller", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigCaller( + testlibs_std.X_testSetOrigCaller( m, p0) }, }, { "std", - "TestSetOrigPkgAddr", + "testSetOrigPkgAddr", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p0"), Type: gno.X("string")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - testlibs_std.TestSetOrigPkgAddr( + testlibs_std.X_testSetOrigPkgAddr( m, p0) }, }, { "std", - "TestSetOrigSend", + "testSetOrigSend", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Coins")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("[]string")}, + {Name: gno.N("p1"), Type: gno.X("[]int64")}, + {Name: gno.N("p2"), Type: gno.X("[]string")}, + {Name: gno.N("p3"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_std.Coins + p0 []string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []int64 rp1 = reflect.ValueOf(&p1).Elem() + p2 []string + rp2 = reflect.ValueOf(&p2).Elem() + p3 []int64 + rp3 = reflect.ValueOf(&p3).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 3, "")).TV, rp3) - testlibs_std.TestSetOrigSend( + testlibs_std.X_testSetOrigSend( m, - p0, p1) + p0, p1, p2, p3) }, }, { "std", - "TestIssueCoins", + "testIssueCoins", []gno.FieldTypeExpr{ - {Name: gno.N("p0"), Type: gno.X("Address")}, - {Name: gno.N("p1"), Type: gno.X("Coins")}, + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[]string")}, + {Name: gno.N("p2"), Type: gno.X("[]int64")}, }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { b := m.LastBlock() var ( - p0 tm2_crypto.Bech32Address + p0 string rp0 = reflect.ValueOf(&p0).Elem() - p1 tm2_std.Coins + p1 []string rp1 = reflect.ValueOf(&p1).Elem() + p2 []int64 + rp2 = reflect.ValueOf(&p2).Elem() ) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) - testlibs_std.TestIssueCoins( + testlibs_std.X_testIssueCoins( m, - p0, p1) + p0, p1, p2) }, }, { diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 380549be694..2b142634740 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,12 +1,30 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func TestCurrentRealm() string // injected -func TestSkipHeights(count int64) // injected -func ClearStoreCache() // injected -func GetCallerAt(n int) Address // injected -func TestSetOrigCaller(addr Address) // injected -func TestSetOrigPkgAddr(addr Address) // injected -func TestSetOrigSend(sent, spent Coins) // injected -func TestIssueCoins(addr Address, coins Coins) // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected + +func TestSetOrigCaller(addr Address) { testSetOrigCaller(string(addr)) } +func TestSetOrigPkgAddr(addr Address) { testSetOrigPkgAddr(string(addr)) } +func TestSetOrigSend(sent, spent Coins) { + sentDenom, sentAmt := sent.expandNative() + spentDenom, spentAmt := spent.expandNative() + testSetOrigSend(sentDenom, sentAmt, spentDenom, spentAmt) +} +func TestIssueCoins(addr Address, coins Coins) { + denom, amt := coins.expandNative() + testIssueCoins(string(addr), denom, amt) +} + +// GetCallerAt calls callerAt, which we overwrite +func callerAt(n int) string + +// native bindings +func testSetOrigCaller(s string) +func testSetOrigPkgAddr(s string) +func testSetOrigSend( + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64) +func testIssueCoins(addr string, denom []string, amt []int64) diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 27ad079b4a6..0e15c8d0241 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -6,10 +6,8 @@ import ( "testing" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" - tm2std "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -68,11 +66,13 @@ func ClearStoreCache(m *gno.Machine) { } } -func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { +func X_callerAt(m *gno.Machine, n int) string { if n <= 0 { m.Panic(typedString("GetCallerAt requires positive arg")) return "" } + // Add 1 to n to account for the GetCallerAt (gno fn) frame. + n++ if n > m.NumFrames()-1 { // NOTE: the last frame's LastPackage // is set to the original non-frame @@ -82,35 +82,39 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { } if n == m.NumFrames()-1 { // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(stdlibs.ExecContext) - return ctx.OrigCaller + ctx := m.Context.(std.ExecContext) + return string(ctx.OrigCaller) } - return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() + return string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) } -func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { +func X_testSetOrigCaller(m *gno.Machine, addr string) { ctx := m.Context.(std.ExecContext) - ctx.OrigCaller = addr + ctx.OrigCaller = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigPkgAddr(m *gno.Machine, addr crypto.Bech32Address) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigPkgAddr = addr +func X_testSetOrigPkgAddr(m *gno.Machine, addr string) { + ctx := m.Context.(std.ExecContext) + ctx.OrigPkgAddr = crypto.Bech32Address(addr) m.Context = ctx } -func TestSetOrigSend(m *gno.Machine, sent, spent tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigSend = sent +func X_testSetOrigSend(m *gno.Machine, + sentDenom []string, sentAmt []int64, + spentDenom []string, spentAmt []int64, +) { + ctx := m.Context.(std.ExecContext) + ctx.OrigSend = std.CompactCoins(sentDenom, sentAmt) + spent := std.CompactCoins(spentDenom, spentAmt) ctx.OrigSendSpent = &spent m.Context = ctx } -func TestIssueCoins(m *gno.Machine, addr crypto.Bech32Address, coins tm2std.Coins) { - ctx := m.Context.(stdlibs.ExecContext) +func X_testIssueCoins(m *gno.Machine, addr string, denom []string, amt []int64) { + ctx := m.Context.(std.ExecContext) banker := ctx.Banker - for _, coin := range coins { - banker.IssueCoin(addr, coin.Denom, coin.Amount) + for i := range denom { + banker.IssueCoin(crypto.Bech32Address(addr), denom[i], amt[i]) } } diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go deleted file mode 100644 index c95c05c584e..00000000000 --- a/misc/genstd/exprstring.go +++ /dev/null @@ -1,290 +0,0 @@ -// Forked from go/types (go 1.20.3) to implement support for *linkedIdent. -// It cannot be easily split from the original as WriteExpr is highly recursive. - -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file implements printing of expressions. - -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/types" -) - -const ( - printerModeGoQualified = iota - printerModeGnoType -) - -type exprPrinter struct { - mode int -} - -// ExprString returns the (possibly shortened) string representation for x. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// ExprString is identical to [types.ExprString] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) ExprString(x ast.Expr) string { - var buf bytes.Buffer - ep.WriteExpr(&buf, x) - return buf.String() -} - -// WriteExpr writes the (possibly shortened) string representation for x to buf. -// Shortened representations are suitable for user interfaces but may not -// necessarily follow Go syntax. -// -// WriteExpr is identical to [types.WriteExpr] with the difference that it -// supports *linkedIdent. -func (ep *exprPrinter) WriteExpr(buf *bytes.Buffer, x ast.Expr) { - // The AST preserves source-level parentheses so there is - // no need to introduce them here to correct for different - // operator precedences. (This assumes that the AST was - // generated by a Go parser.) - - switch x := x.(type) { - default: - // fallback to go original -- for all non-recursive ast.Expr types - types.WriteExpr(buf, x) - - case *linkedIdent: - switch ep.mode { - case printerModeGoQualified: - n := pkgNameFromPath(x.lt.goPackage) - buf.WriteString(n) - buf.WriteByte('.') - buf.WriteString(x.lt.goName) - case printerModeGnoType: - buf.WriteString(x.lt.gnoName) - default: - panic(fmt.Errorf("invalid mode %d", ep.mode)) - } - - case *ast.Ellipsis: - buf.WriteString("...") - if x.Elt != nil { - ep.WriteExpr(buf, x.Elt) - } - - case *ast.FuncLit: - buf.WriteByte('(') - ep.WriteExpr(buf, x.Type) - buf.WriteString(" literal)") // shortened - - case *ast.CompositeLit: - ep.WriteExpr(buf, x.Type) - buf.WriteByte('{') - if len(x.Elts) > 0 { - buf.WriteString("…") - } - buf.WriteByte('}') - - case *ast.ParenExpr: - buf.WriteByte('(') - ep.WriteExpr(buf, x.X) - buf.WriteByte(')') - - case *ast.SelectorExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('.') - buf.WriteString(x.Sel.Name) - - case *ast.IndexExpr, *ast.IndexListExpr: - ix := tpUnpackIndexExpr(x) - ep.WriteExpr(buf, ix.X) - buf.WriteByte('[') - ep.writeExprList(buf, ix.Indices) - buf.WriteByte(']') - - case *ast.SliceExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte('[') - if x.Low != nil { - ep.WriteExpr(buf, x.Low) - } - buf.WriteByte(':') - if x.High != nil { - ep.WriteExpr(buf, x.High) - } - if x.Slice3 { - buf.WriteByte(':') - if x.Max != nil { - ep.WriteExpr(buf, x.Max) - } - } - buf.WriteByte(']') - - case *ast.TypeAssertExpr: - ep.WriteExpr(buf, x.X) - buf.WriteString(".(") - ep.WriteExpr(buf, x.Type) - buf.WriteByte(')') - - case *ast.CallExpr: - ep.WriteExpr(buf, x.Fun) - buf.WriteByte('(') - ep.writeExprList(buf, x.Args) - if x.Ellipsis.IsValid() { - buf.WriteString("...") - } - buf.WriteByte(')') - - case *ast.StarExpr: - buf.WriteByte('*') - ep.WriteExpr(buf, x.X) - - case *ast.UnaryExpr: - buf.WriteString(x.Op.String()) - ep.WriteExpr(buf, x.X) - - case *ast.BinaryExpr: - ep.WriteExpr(buf, x.X) - buf.WriteByte(' ') - buf.WriteString(x.Op.String()) - buf.WriteByte(' ') - ep.WriteExpr(buf, x.Y) - - case *ast.ArrayType: - buf.WriteByte('[') - if x.Len != nil { - ep.WriteExpr(buf, x.Len) - } - buf.WriteByte(']') - ep.WriteExpr(buf, x.Elt) - - case *ast.StructType: - buf.WriteString("struct{") - ep.writeFieldList(buf, x.Fields.List, "; ", false) - buf.WriteByte('}') - - case *ast.FuncType: - buf.WriteString("func") - ep.writeSigExpr(buf, x) - - case *ast.InterfaceType: - buf.WriteString("interface{") - ep.writeFieldList(buf, x.Methods.List, "; ", true) - buf.WriteByte('}') - - case *ast.MapType: - buf.WriteString("map[") - ep.WriteExpr(buf, x.Key) - buf.WriteByte(']') - ep.WriteExpr(buf, x.Value) - - case *ast.ChanType: - var s string - switch x.Dir { - case ast.SEND: - s = "chan<- " - case ast.RECV: - s = "<-chan " - default: - s = "chan " - } - buf.WriteString(s) - ep.WriteExpr(buf, x.Value) - } -} - -func (ep *exprPrinter) writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { - buf.WriteByte('(') - ep.writeFieldList(buf, sig.Params.List, ", ", false) - buf.WriteByte(')') - - res := sig.Results - n := res.NumFields() - if n == 0 { - // no result - return - } - - buf.WriteByte(' ') - if n == 1 && len(res.List[0].Names) == 0 { - // single unnamed result - ep.WriteExpr(buf, res.List[0].Type) - return - } - - // multiple or named result(s) - buf.WriteByte('(') - ep.writeFieldList(buf, res.List, ", ", false) - buf.WriteByte(')') -} - -func (ep *exprPrinter) writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { - for i, f := range list { - if i > 0 { - buf.WriteString(sep) - } - - // field list names - ep.writeIdentList(buf, f.Names) - - // types of interface methods consist of signatures only - if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { - ep.writeSigExpr(buf, sig) - continue - } - - // named fields are separated with a blank from the field type - if len(f.Names) > 0 { - buf.WriteByte(' ') - } - - ep.WriteExpr(buf, f.Type) - - // ignore tag - } -} - -func (ep *exprPrinter) writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - buf.WriteString(x.Name) - } -} - -func (ep *exprPrinter) writeExprList(buf *bytes.Buffer, list []ast.Expr) { - for i, x := range list { - if i > 0 { - buf.WriteString(", ") - } - ep.WriteExpr(buf, x) - } -} - -// The following are copied from go/internal/typeparams. -// We cannot use the original directly as it comes from an "internal" package. - -// tpIndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. -// -// Orig holds the original ast.Expr from which this IndexExpr was derived. -type tpIndexExpr struct { - Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. - *ast.IndexListExpr -} - -func tpUnpackIndexExpr(n ast.Node) *tpIndexExpr { - switch e := n.(type) { - case *ast.IndexExpr: - return &tpIndexExpr{e, &ast.IndexListExpr{ - X: e.X, - Lbrack: e.Lbrack, - Indices: []ast.Expr{e.Index}, - Rbrack: e.Rbrack, - }} - case *ast.IndexListExpr: - return &tpIndexExpr{e, e} - } - return nil -} diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 0e4034a1ab9..d87484ab865 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "go/ast" + "go/types" "path" "strconv" ) @@ -24,8 +25,6 @@ type mapping struct { } type mappingType struct { - // type of ast.Expr is from the normal ast.Expr types - // + *linkedIdent. Type ast.Expr // IsTypedValue is set to true if the parameter or result in go is of type @@ -35,21 +34,11 @@ type mappingType struct { } func (mt mappingType) GoQualifiedName() string { - return (&exprPrinter{ - mode: printerModeGoQualified, - }).ExprString(mt.Type) + return types.ExprString(mt.Type) } func (mt mappingType) GnoType() string { - return (&exprPrinter{ - mode: printerModeGnoType, - }).ExprString(mt.Type) -} - -type linkedIdent struct { - ast.BadExpr // Unused, but it makes *linkedIdent implement ast.Expr - - lt linkedType + return types.ExprString(mt.Type) } func linkFunctions(pkgs []*pkgData) []mapping { @@ -208,50 +197,17 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) // mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is // (recursively) newly allocated. func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { - resolveGoNamed := func(lt *linkedType) bool { - switch goe := goe.(type) { - case *ast.SelectorExpr: - // selector - resolve pkg ident to path - lt.goPackage = resolveSelectorImport(m.goImports, goe) - lt.goName = goe.Sel.Name - case *ast.Ident: - // local name -- use import path of go pkg - lt.goPackage = m.GoImportPath - lt.goName = goe.Name - default: - return false - } - return true - } - switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type - case *ast.SelectorExpr: - lt := linkedType{ - gnoPackage: resolveSelectorImport(m.gnoImports, gnoe), - gnoName: gnoe.Sel.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} case *ast.Ident: // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { return &ast.Ident{Name: gnoe.Name} } - - lt := linkedType{ - gnoPackage: m.GnoImportPath, - gnoName: gnoe.Name, - } - if !resolveGoNamed(<) || !linkedTypeExists(lt) { - return nil - } - return &linkedIdent{lt: lt} + panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: @@ -283,7 +239,8 @@ func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { *ast.FuncType, *ast.InterfaceType, *ast.MapType, - *ast.Ellipsis: + *ast.Ellipsis, + *ast.SelectorExpr: // TODO panic("not implemented") default: @@ -426,46 +383,3 @@ func isBuiltin(name string) bool { } return false } - -type linkedType struct { - gnoPackage string - gnoName string - goPackage string - goName string -} - -var linkedTypes = [...]linkedType{ - { - "std", "Address", - "github.com/gnolang/gno/tm2/pkg/crypto", "Bech32Address", - }, - { - "std", "Coin", - "github.com/gnolang/gno/tm2/pkg/std", "Coin", - }, - { - "std", "Coins", - "github.com/gnolang/gno/tm2/pkg/std", "Coins", - }, - { - "std", "Realm", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Realm", - }, - { - "std", "BankerType", - "github.com/gnolang/gno/gnovm/stdlibs/std", "BankerType", - }, - { - "std", "Banker", - "github.com/gnolang/gno/gnovm/stdlibs/std", "Banker", - }, -} - -func linkedTypeExists(lt linkedType) bool { - for _, ltx := range linkedTypes { - if lt == ltx { - return true - } - } - return false -} From fd2722e35e15ae9cd55c522fbe5f1dd1b808fccf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Feb 2024 10:59:53 +0100 Subject: [PATCH 20/67] remove InjectNativeMappings --- gnovm/stdlibs/stdlibs.go | 10 ---------- gnovm/tests/imports.go | 2 -- 2 files changed, 12 deletions(-) diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 1e215b098e2..7939c0396d1 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -3,22 +3,12 @@ package stdlibs //go:generate go run github.com/gnolang/gno/misc/genstd import ( - "reflect" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" libsstd "github.com/gnolang/gno/gnovm/stdlibs/std" - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/std" ) type ExecContext = libsstd.ExecContext -func InjectNativeMappings(store gno.Store) { - store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") - store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") -} - func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 0db5651fbcc..d0afee79910 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -400,8 +400,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri store.SetNativeStore(teststdlibs.NativeStore) store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) - // native mappings - stdlibs.InjectNativeMappings(store) return } From 88562255736ffdc45b6eb2059687ad9a8cda67de Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 27 Feb 2024 23:01:54 +0100 Subject: [PATCH 21/67] fix errors in genstd --- misc/genstd/mapping.go | 88 +++++++++++++++++-------------------- misc/genstd/mapping_test.go | 86 +++++++++++++++--------------------- 2 files changed, 76 insertions(+), 98 deletions(-) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index d87484ab865..69aad8af3a6 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "go/ast" "go/types" @@ -110,8 +109,7 @@ func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) if m.isTypedValue(goe) { *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) } else { - merged := m.mergeTypes(gnoe, goe) - *dst = append(*dst, mappingType{Type: merged}) + *dst = append(*dst, mappingType{Type: gnoe}) } return nil }) @@ -183,57 +181,56 @@ func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) return nil } -// mergeTypes merges gnoe and goe into a single ast.Expr. -// -// gnoe and goe are expected to have the same underlying structure, but they -// may differ in their type identifiers (possibly qualified, ie pkg.T). -// if they differ, mergeTypes returns nil. -// -// When two type identifiers are found, they are checked against the list of -// linkedTypes to determine if they refer to a linkedType. If they are not, -// mergeTypes returns nil. If they are, the *ast.Ident/*ast.SelectorExpr is -// replaced with a *linkedIdent. -// -// mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is -// (recursively) newly allocated. -func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { +type typeMismatchError struct { + gnoe, goe ast.Expr +} + +func (te *typeMismatchError) Error() string { + return fmt.Sprintf("typesEqual: gno type %q does not match go type %q", + types.ExprString(te.gnoe), types.ExprString(te.goe)) +} + +// typesEqual ensures that the given gnoe and goe, expected to represent +// expressions to identify types, are equal. +func (m *mapping) typesEqual(gnoe, goe ast.Expr) error { + // If a type assertion fails, like in the below + // goe, ok := ..., then goe will be set to a zero value, and might + // lead to nil pointer dereferences. Setting up the mismatch error + // here avoids that. + mismatch := typeMismatchError{gnoe, goe} + switch gnoe := gnoe.(type) { // We're working with a subset of all expressions: // https://go.dev/ref/spec#Type case *ast.Ident: - // easy case - built-in identifiers goi, ok := goe.(*ast.Ident) - if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { - return &ast.Ident{Name: gnoe.Name} + switch { + case !ok || gnoe.Name != goi.Name: + return &mismatch + case !isBuiltin(gnoe.Name): + return fmt.Errorf("typesEqual: usage of non-builtin type %q", gnoe.Name) + default: + return nil } - panic(fmt.Sprintf("usage of non-builtin type %q in mergeTypes", gnoe.Name)) - - // easier cases -- check for equality of structure and underlying types case *ast.StarExpr: goe, ok := goe.(*ast.StarExpr) if !ok { - return nil + return &mismatch } - x := m.mergeTypes(gnoe.X, goe.X) - if x == nil { - return nil + if err := m.typesEqual(gnoe.X, goe.X); err != nil { + return err } - return &ast.StarExpr{X: x} + return nil case *ast.ArrayType: goe, ok := goe.(*ast.ArrayType) if !ok || !basicLitsEqual(gnoe.Len, goe.Len) { - return nil - } - elt := m.mergeTypes(gnoe.Elt, goe.Elt) - if elt == nil { - return nil + return &mismatch } - var l ast.Expr - if gnoe.Len != nil { - l = &ast.BasicLit{Value: gnoe.Len.(*ast.BasicLit).Value} + if err := m.typesEqual(gnoe.Elt, goe.Elt); err != nil { + return err } - return &ast.ArrayType{Len: l, Elt: elt} + return nil case *ast.StructType, *ast.FuncType, @@ -304,11 +301,13 @@ func basicLitsEqual(x1, x2 ast.Expr) bool { return l1.Value == l2.Value } -// Signatures match when they accept the same elementary types, or a linked -// type mapping (see [linkedTypes]). +// Signatures match when they accept the same, unnamed types. +// +// If the first parameter to the Go function is *[gnolang.Machine], it is +// ignored when matching to the Gno function. // -// Additionally, if the first parameter to the Go function is -// *[gnolang.Machine], it is ignored when matching to the Gno function. +// If a Go parameter is [gnolang.TypedValue], it always matches any +// corresponding parameter in Gno. func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { panic("type parameters not supported") @@ -329,8 +328,6 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) } -var errNoMatch = errors.New("no match") - func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if gnofl == nil || gofl == nil { return gnofl == nil && gofl == nil @@ -343,10 +340,7 @@ func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if m.isTypedValue(goe) { return nil } - if m.mergeTypes(gnoe, goe) == nil { - return errNoMatch - } - return nil + return m.typesEqual(gnoe, goe) }) return err == nil } diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go index b0cfa1bd4a7..292402a06ac 100644 --- a/misc/genstd/mapping_test.go +++ b/misc/genstd/mapping_test.go @@ -173,9 +173,9 @@ func Test_linkFunctions_noMatchSig(t *testing.T) { linkFunctions(pkgs) } -// mergeTypes - separate tests. +// typesEqual - separate tests. -var mergeTypesMapping = &mapping{ +var typesEqualMapping = &mapping{ GnoImportPath: "std", GnoFunc: "Fn", GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/std", @@ -191,7 +191,6 @@ var mergeTypesMapping = &mapping{ }, gnoImports: []*ast.ImportSpec{ { - // cheating a bit -- but we currently only have linked types in `std`. Path: &ast.BasicLit{Value: `"std"`}, }, { @@ -200,75 +199,60 @@ var mergeTypesMapping = &mapping{ }, } -func Test_mergeTypes(t *testing.T) { +func Test_typesEqual(t *testing.T) { tt := []struct { - gnoe, goe string - result ast.Expr + gnoe, goe string + errContains string }{ - {"int", "int", &ast.Ident{Name: "int"}}, - {"*[11][]rune", "*[11][]rune", &ast.StarExpr{ - X: &ast.ArrayType{Len: &ast.BasicLit{Value: "11"}, Elt: &ast.ArrayType{ - Elt: &ast.Ident{Name: "rune"}, - }}, - }}, - - {"Address", "crypto.Bech32Address", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Address", - goPackage: "github.com/gnolang/gno/tm2/pkg/crypto", - goName: "Bech32Address", - }}}, - {"std.Realm", "Realm", &linkedIdent{lt: linkedType{ - gnoPackage: "std", - gnoName: "Realm", - goPackage: "github.com/gnolang/gno/gnovm/stdlibs/std", - goName: "Realm", - }}}, + {"int", "int", ""}, + {"*[11][]rune", "*[11][ ]rune", ""}, + + {"madeup", "madeup", "non-builtin type"}, + + {"int", "string", "does not match"}, + {"*int", "int", "does not match"}, + {"string", "*string", "does not match"}, + {"*string", "*int", "does not match"}, + + {"[]int", "[1]int", "does not match"}, + {"[1]int", "[]int", "does not match"}, + {"[2]int", "[2]string", "does not match"}, + // valid, but unsupported (only BasicLits) + {"[(11)]int", "[(11)]string", "does not match"}, + + // even though mathematically equal, for simplicity we don't implement + // "true" basic lit equivalence + {"[8]int", "[0x8]int", "does not match"}, } - for _, tv := range tt { - t.Run(tv.gnoe, func(t *testing.T) { + for idx, tv := range tt { + t.Run(fmt.Sprintf("%02d_%s", idx, tv.gnoe), func(t *testing.T) { gnoe, err := parser.ParseExpr(tv.gnoe) require.NoError(t, err) goe, err := parser.ParseExpr(tv.goe) require.NoError(t, err) - result := mergeTypesMapping.mergeTypes(gnoe, goe) - assert.Equal(t, result, tv.result) + err = typesEqualMapping.typesEqual(gnoe, goe) + if tv.errContains == "" { + assert.NoError(t, err) + } else { + _ = assert.Error(t, err) && + assert.Contains(t, err.Error(), tv.errContains) + } }) } } -func Test_mergeTypes_invalid(t *testing.T) { +func Test_typesEqual_panic(t *testing.T) { tt := []struct { gnoe, goe string panic string }{ - {"int", "string", ""}, - - {"*int", "int", ""}, - {"string", "*string", ""}, - {"*string", "*int", ""}, - - {"[]int", "[1]int", ""}, - {"[1]int", "[]int", ""}, - {"[2]int", "[2]string", ""}, - // valid, but unsupported (only BasicLits) - {"[(11)]int", "[(11)]string", ""}, - - {"Address", "string", ""}, - {"math.X", "X", ""}, - {"map[string]string", "map[string]string", "not implemented"}, {"func(s string)", "func(s string)", "not implemented"}, {"interface{}", "interface{}", "not implemented"}, {"struct{}", "struct{}", "not implemented"}, - {"1 + 2", "1 + 2", "invalid expression"}, - - // even though semantically equal, for simplicity we don't implement - // "true" basic lit equivalence - {"[8]int", "[0x8]int", ""}, } for _, tv := range tt { @@ -287,7 +271,7 @@ func Test_mergeTypes_invalid(t *testing.T) { } }() - result := mergeTypesMapping.mergeTypes(gnoe, goe) + result := typesEqualMapping.typesEqual(gnoe, goe) assert.Nil(t, result) }) } From d6955d0316f322bb8494372dda83f7af4244f6fc Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 14:43:13 +0100 Subject: [PATCH 22/67] remove AddGo2GnoMapping, nolint on btReadonly --- gno.land/pkg/sdk/vm/builtins.go | 1 - gnovm/pkg/gnolang/gonative.go | 13 ------------- gnovm/pkg/gnolang/store.go | 1 - gnovm/stdlibs/std/banker.go | 2 +- 4 files changed, 1 insertion(+), 16 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index ef2ac93f617..63062847e01 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -41,7 +41,6 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { } store.SetPackageGetter(getPackage) store.SetNativeStore(stdlibs.NativeStore) - stdlibs.InjectNativeMappings(store) } // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index f3739dad0f3..e634fbcf8f5 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -88,19 +88,6 @@ func (ds *defaultStore) SetStrictGo2GnoMapping(strict bool) { ds.go2gnoStrict = strict } -// Implements Store. -func (ds *defaultStore) AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) { - rtPkgPath := rt.PkgPath() - if rtPkgPath == "" { - panic(fmt.Sprintf("type has no associated package path: %v", rt)) - } - rtName := rt.Name() - if rtName == "" { - panic(fmt.Sprintf("type has no name: %v", rt)) - } - ds.go2gnoMap[rtPkgPath+"."+rtName] = pkgPath + "." + name -} - // Implements Store. // See go2GnoValue2(). Like go2GnoType() but also converts any // top-level complex types (or pointers to them). The result gets diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index be4a854e7ce..5b1bfe960ea 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -40,7 +40,6 @@ type Store interface { SetBlockNode(BlockNode) // UNSTABLE SetStrictGo2GnoMapping(bool) - AddGo2GnoMapping(rt reflect.Type, pkgPath string, name string) Go2GnoType(rt reflect.Type) Type GetAllocator() *Allocator NumMemPackages() int64 diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 243d457b9be..2533071ccd8 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -23,7 +23,7 @@ type BankerInterface interface { const ( // Can only read state. - btReadonly uint8 = iota + btReadonly uint8 = iota //nolint // Can only send from tx send. btOrigSend // Can send from all realm coins. From b3c7adb0a4033435f3b1ee8e30a3bbb7b7db34dc Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 15:30:50 +0100 Subject: [PATCH 23/67] fmt + bugfix --- gnovm/stdlibs/std/banker.go | 1 + gnovm/stdlibs/std/native.gno | 1 + 2 files changed, 2 insertions(+) diff --git a/gnovm/stdlibs/std/banker.go b/gnovm/stdlibs/std/banker.go index 2533071ccd8..d60efc40819 100644 --- a/gnovm/stdlibs/std/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -65,6 +65,7 @@ func X_bankerSendCoins(m *gno.Machine, bt uint8, fromS, toS string, denoms []str `cannot send "%v", limit "%v" exceeded with "%v" already spent`, amt, ctx.OrigSend, *ctx.OrigSendSpent), )) + return } ctx.Banker.SendCoins(from, to, amt) *ctx.OrigSendSpent = spent diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index c079e0dd42b..8043df49882 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -36,6 +36,7 @@ func GetOrigPkgAddr() Address { func GetCallerAt(n int) Address { return Address(callerAt(n)) } + func DerivePkgAddr(pkgPath string) Address { return Address(derivePkgAddr(pkgPath)) } From 250e29213360f8e02d65d6a7c0f7f82c1940a37a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 15:44:58 +0100 Subject: [PATCH 24/67] feat(gno.land): add go type checking to keeper --- gno.land/pkg/gnoclient/client_txs.go | 6 -- gno.land/pkg/keyscli/addpkg.go | 7 -- gno.land/pkg/keyscli/run.go | 7 +- gno.land/pkg/sdk/vm/builtins.go | 4 +- gno.land/pkg/sdk/vm/keeper.go | 14 ++++ gnovm/pkg/gnolang/go2gno.go | 102 ++++++++++++++++++++++++++- gnovm/pkg/gnolang/gonative_test.go | 7 +- gnovm/pkg/gnolang/store.go | 77 ++++++++++++++------ gnovm/pkg/transpiler/transpiler.go | 41 ----------- gnovm/stdlibs/io/io.gno | 31 +++++--- gnovm/tests/files/import6.gno | 2 +- gnovm/tests/imports.go | 10 +-- 12 files changed, 205 insertions(+), 103 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index 2a83eef1b79..f2fa5ea6fb4 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -2,7 +2,6 @@ package gnoclient import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -138,11 +137,6 @@ func (c *Client) Run(cfg BaseTxCfg, msgs ...MsgRun) (*ctypes.ResultBroadcastTxCo caller := c.Signer.Info().GetAddress() - // Transpile and validate Gno syntax - if err = transpiler.TranspileAndCheckMempkg(msg.Package); err != nil { - return nil, err - } - msg.Package.Name = "main" msg.Package.Path = "" diff --git a/gno.land/pkg/keyscli/addpkg.go b/gno.land/pkg/keyscli/addpkg.go index 1f89b2c2b82..1812bf3fd86 100644 --- a/gno.land/pkg/keyscli/addpkg.go +++ b/gno.land/pkg/keyscli/addpkg.go @@ -7,7 +7,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -102,12 +101,6 @@ func execMakeAddPkg(cfg *MakeAddPkgCfg, args []string, io commands.IO) error { panic(fmt.Sprintf("found an empty package %q", cfg.PkgPath)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) - if err != nil { - panic(err) - } - // parse gas wanted & fee. gaswanted := cfg.RootCfg.GasWanted gasfee, err := std.ParseCoin(cfg.RootCfg.GasFee) diff --git a/gno.land/pkg/keyscli/run.go b/gno.land/pkg/keyscli/run.go index 6fcee7434eb..66f4bc3c671 100644 --- a/gno.land/pkg/keyscli/run.go +++ b/gno.land/pkg/keyscli/run.go @@ -9,7 +9,6 @@ import ( "github.com/gnolang/gno/gno.land/pkg/sdk/vm" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/gnovm/pkg/transpiler" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/commands" "github.com/gnolang/gno/tm2/pkg/crypto/keys" @@ -109,11 +108,7 @@ func execMakeRun(cfg *MakeRunCfg, args []string, cmdio commands.IO) error { if memPkg.IsEmpty() { panic(fmt.Sprintf("found an empty package %q", memPkg.Path)) } - // transpile and validate syntax - err = transpiler.TranspileAndCheckMempkg(memPkg) - if err != nil { - panic(err) - } + memPkg.Name = "main" // Set to empty; this will be automatically set by the VM keeper. memPkg.Path = "" diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 63062847e01..368ada6ff82 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: store, + Store: newStore, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 54f424ee058..c06c3b3761c 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -160,6 +161,11 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { return ErrInvalidPkgPath("reserved package name: " + pkgPath) } + // Validate Gno syntax and type check. + if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return err + } + // Pay deposit from creator. pkgAddr := gno.DerivePkgAddr(pkgPath) @@ -317,6 +323,11 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { return "", ErrInvalidPkgPath(err.Error()) } + // Validate Gno syntax and type check. + if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + return "", err + } + // Send send-coins to pkg from caller. err = vm.bank.SendCoins(ctx, caller, pkgAddr, send) if err != nil { @@ -565,6 +576,9 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err return memFile.Body, nil } else { memPkg := store.GetMemPackage(dirpath) + if memPkg == nil { + return "", fmt.Errorf("package %q is not available", dirpath) // TODO: XSS protection + } for i, memfile := range memPkg.Files { if i > 0 { res += "\n" diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index ee82cb39555..c726e291abc 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -35,12 +35,16 @@ import ( "go/ast" "go/parser" "go/token" + "go/types" "os" "reflect" "strconv" + "strings" "github.com/davecgh/go-spew/spew" "github.com/gnolang/gno/tm2/pkg/errors" + "github.com/gnolang/gno/tm2/pkg/std" + "go.uber.org/multierr" ) func MustReadFile(path string) *FileNode { @@ -102,7 +106,10 @@ func MustParseExpr(expr string) Expr { func ParseFile(filename string, body string) (fn *FileNode, err error) { // Use go parser to parse the body. fs := token.NewFileSet() - f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) + // TODO(morgan): would be nice to add parser.SkipObjectResolution as we don't + // seem to be using its features, but this breaks when testing (specifically redeclaration tests). + const parseOpts = parser.ParseComments | parser.DeclarationErrors + f, err := parser.ParseFile(fs, filename, body, parseOpts) if err != nil { return nil, err } @@ -469,6 +476,99 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } } +//---------------------------------------- +// type checking (using go/types) + +// MemPackageGetter implements the GetMemPackage() method. It is a subset of +// [Store], separated for ease of testing. +type MemPackageGetter interface { + GetMemPackage(path string) *std.MemPackage +} + +// TypeCheckMemPackage performs type validation and checking on the given +// mempkg. To retrieve dependencies, it uses getter. +// +// The syntax checking is performed entirely using Go's go/types package. +func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { + imp := &gnoImporter{ + getter: getter, + cache: map[string]any{}, + cfg: &types.Config{}, + } + imp.cfg.Importer = imp + + _, err := imp.parseCheckMemPackage(mempkg) + if err != nil { + return err + } + + return nil +} + +type gnoImporter struct { + getter MemPackageGetter + cache map[string]any // *types.Package or error + cfg *types.Config +} + +func (g *gnoImporter) Import(path string) (*types.Package, error) { + return g.ImportFrom(path, "", 0) +} + +// ImportFrom returns the imported package for the given import +// path when imported by a package file located in dir. +func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { + if pkg, ok := g.cache[path]; ok { + switch ret := pkg.(type) { + case *types.Package: + return ret, nil + case error: + return nil, ret + default: + panic(fmt.Sprintf("invalid type in gnoImporter.cache %T", ret)) + } + } + mpkg := g.getter.GetMemPackage(path) + if mpkg == nil { + g.cache[path] = (*types.Package)(nil) + return nil, nil + } + result, err := g.parseCheckMemPackage(mpkg) + if err != nil { + g.cache[path] = err + return nil, err + } + g.cache[path] = result + return result, nil +} + +func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { + fset := token.NewFileSet() + files := make([]*ast.File, 0, len(mpkg.Files)) + var errs error + for _, file := range mpkg.Files { + if !strings.HasSuffix(file.Name, ".gno") || + endsWith(file.Name, []string{"_test.gno", "_filetest.gno"}) { + continue // skip spurious file. + } + + const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution + f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) + if err != nil { + err = multierr.Append(errs, err) + continue + } + + files = append(files, f) + } + if errs != nil { + return nil, errs + } + + // TODO g.cfg.Error + return g.cfg.Check(mpkg.Path, fset, files, nil) +} + //---------------------------------------- // utility methods diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index e348ffe9c22..7b9167e681b 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) @@ -90,10 +90,11 @@ func main() { n := MustParseFile("main.go", c) m.RunFiles(n) m.RunMain() + // weird `+` is used to place a space, without having editors strip it away. assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } @@ -132,7 +133,7 @@ func main() { assert.Equal(t, string(out.Bytes()), `A: 1 B: 0 C: 0 -D: +D: `+` `) } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 5b1bfe960ea..306253e7f31 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -3,6 +3,7 @@ package gnolang import ( "fmt" "reflect" + "slices" "strconv" "strings" @@ -11,8 +12,11 @@ import ( "github.com/gnolang/gno/tm2/pkg/store" ) -// return nil if package doesn't exist. -type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) +// PackageGetter specifies how the store may retrieve packages which are not +// already in its cache. PackageGetter should return nil when the requested +// package does not exist. store should be used to run the machine, or otherwise +// call any methods which may call store.GetPackage, to avoid import cycles. +type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -79,8 +83,7 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. - current map[string]struct{} // for detecting import cycles. + opslog []StoreOp // for debugging and testing. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -95,7 +98,6 @@ func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore iavlStore: iavlStore, go2gnoMap: make(map[string]string), go2gnoStrict: true, - current: make(map[string]struct{}), } InitStoreCaches(ds) return ds @@ -109,16 +111,29 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } +type importerStore struct { + *defaultStore + importChain []string +} + +func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + if !isImport { + // if not an import, match behaviour to the defaultStore + return is.defaultStore.GetPackage(pkgPath, isImport) + } + // it is an import -- detect cyclic imports + if slices.Contains(is.importChain, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) + } + return is.getPackage(pkgPath, is) +} + // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - // detect circular imports - if isImport { - if _, exists := ds.current[pkgPath]; exists { - panic(fmt.Sprintf("import cycle detected: %q", pkgPath)) - } - ds.current[pkgPath] = struct{}{} - defer delete(ds.current, pkgPath) - } + return ds.getPackage(pkgPath, importerStore{}) +} + +func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -162,7 +177,12 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if pn, pv := ds.pkgGetter(pkgPath); pv != nil { + if impStore.defaultStore == nil { + // pre-allocate 16 strings to likely avoid further slice allocations. + impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} + } + impStore.importChain = append(impStore.importChain, pkgPath) + if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -534,20 +554,41 @@ func (ds *defaultStore) AddMemPackage(memPkg *std.MemPackage) { ds.iavlStore.Set(pathkey, bz) } +// GetMemPackage retrieves the MemPackage at the given path. +// It returns nil if the package could not be found. func (ds *defaultStore) GetMemPackage(path string) *std.MemPackage { + return ds.getMemPackage(path, false) +} + +func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage { pathkey := []byte(backendPackagePathKey(path)) bz := ds.iavlStore.Get(pathkey) if bz == nil { - panic(fmt.Sprintf( - "missing package at path %s", string(pathkey))) + // If this is the first try, attempt using GetPackage to retrieve the + // package, first. GetPackage can leverage pkgGetter, which in most + // implementations works by running Machine.RunMemPackage with save = true, + // which would add the package to the store after running. + // Some packages may never be persisted, thus why we only attempt this twice. + if !isRetry { + if pv := ds.GetPackage(path, false); pv != nil { + return ds.getMemPackage(path, true) + } + } + return nil } var memPkg *std.MemPackage amino.MustUnmarshal(bz, &memPkg) return memPkg } +// GetMemFile retrieves the MemFile with the given name, contained in the +// MemPackage at the given path. It returns nil if the file or the package +// do not exist. func (ds *defaultStore) GetMemFile(path string, name string) *std.MemFile { memPkg := ds.GetMemPackage(path) + if memPkg == nil { + return nil + } memFile := memPkg.GetFile(name) return memFile } @@ -587,9 +628,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. - if len(ds.current) > 0 { - ds.current = make(map[string]struct{}) - } ds.SetCachePackage(Uverse()) } @@ -610,7 +648,6 @@ func (ds *defaultStore) Fork() Store { go2gnoMap: ds.go2gnoMap, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. - current: make(map[string]struct{}), } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index a5e1e068ba5..77d05e392e2 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -16,10 +16,7 @@ import ( "strconv" "strings" - "go.uber.org/multierr" "golang.org/x/tools/go/ast/astutil" - - "github.com/gnolang/gno/tm2/pkg/std" ) const ( @@ -122,44 +119,6 @@ func GetTranspileFilenameAndTags(gnoFilePath string) (targetFilename, tags strin return } -func TranspileAndCheckMempkg(mempkg *std.MemPackage) error { - gofmt := "gofmt" - - tmpDir, err := os.MkdirTemp("", mempkg.Name) - if err != nil { - return err - } - defer os.RemoveAll(tmpDir) //nolint: errcheck - - var errs error - for _, mfile := range mempkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // skip spurious file. - } - res, err := Transpile(mfile.Body, "gno,tmp", mfile.Name) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - tmpFile := filepath.Join(tmpDir, mfile.Name) - err = os.WriteFile(tmpFile, []byte(res.Translated), 0o644) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - err = TranspileVerifyFile(tmpFile, gofmt) - if err != nil { - errs = multierr.Append(errs, err) - continue - } - } - - if errs != nil { - return fmt.Errorf("transpile package: %w", errs) - } - return nil -} - func Transpile(source string, tags string, filename string) (*transpileResult, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, source, parser.ParseComments) diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index 6ee52cfe293..bdbab135140 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -476,28 +476,29 @@ func (l *LimitedReader) Read(p []byte) (n int, err error) { return } -// NewSectionReader returns a SectionReader that reads from r +// NewSectionReader returns a [SectionReader] that reads from r // starting at offset off and stops with EOF after n bytes. func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { var remaining int64 - const maxInt64 = 1<<63 - 1 - if off <= maxInt64-n { + const maxint64 = 1<<63 - 1 + if off <= maxint64-n { remaining = n + off } else { // Overflow, with no way to return error. - // Assume we can read up to an offset of `1<<63 - 1` bytes in this case. - remaining = maxInt64 + // Assume we can read up to an offset of 1<<63 - 1. + remaining = maxint64 } - return &SectionReader{r, off, off, off + n} + return &SectionReader{r, off, off, remaining, n} } // SectionReader implements Read, Seek, and ReadAt on a section -// of an underlying ReaderAt. +// of an underlying [ReaderAt]. type SectionReader struct { - r ReaderAt - base int64 + r ReaderAt // constant after creation + base int64 // constant after creation off int64 - limit int64 + limit int64 // constant after creation + n int64 // constant after creation } func (s *SectionReader) Read(p []byte) (n int, err error) { @@ -536,7 +537,7 @@ func (s *SectionReader) Seek(offset int64, whence int) (int64, error) { } func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { - if off < 0 || off >= s.limit-s.base { + if off < 0 || off >= s.Size() { return 0, EOF } off += s.base @@ -554,6 +555,14 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) { // Size returns the size of the section in bytes. func (s *SectionReader) Size() int64 { return s.limit - s.base } +// Outer returns the underlying [ReaderAt] and offsets for the section. +// +// The returned values are the same that were passed to [NewSectionReader] +// when the [SectionReader] was created. +func (s *SectionReader) Outer() (r ReaderAt, off int64, n int64) { + return s.r, s.base, s.n +} + // An OffsetWriter maps writers at offset base to offset base + off in the underlying writer. type OffsetWriter struct { w WriterAt diff --git a/gnovm/tests/files/import6.gno b/gnovm/tests/files/import6.gno index 8c974ea1893..da5dbfbd3b2 100644 --- a/gnovm/tests/files/import6.gno +++ b/gnovm/tests/files/import6.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" +// github.com/gnolang/gno/_test/c2/c2.gno:1: import cycle detected: "github.com/gnolang/gno/_test/c1" (through [github.com/gnolang/gno/_test/c1 github.com/gnolang/gno/_test/c2]) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index d0afee79910..a1615bb81a2 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -62,7 +62,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -81,7 +81,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() @@ -93,7 +93,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -367,7 +367,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -384,7 +384,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, }) pn, pv = m2.RunMemPackage(memPkg, true) return From 0d8596d50766c507111f2f2d078fa22173e278f3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 28 Feb 2024 16:12:45 +0100 Subject: [PATCH 25/67] lint + test fixes --- gno.land/pkg/gnoclient/integration_test.go | 3 --- gno.land/pkg/sdk/vm/keeper.go | 5 ++--- gno.land/pkg/sdk/vm/keeper_test.go | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/gno.land/pkg/gnoclient/integration_test.go b/gno.land/pkg/gnoclient/integration_test.go index 554cba8ecf2..fa2e78cb397 100644 --- a/gno.land/pkg/gnoclient/integration_test.go +++ b/gno.land/pkg/gnoclient/integration_test.go @@ -239,7 +239,6 @@ func TestRunSingle_Integration(t *testing.T) { fileBody := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/tests" ) @@ -297,7 +296,6 @@ func TestRunMultiple_Integration(t *testing.T) { fileBody1 := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/tests" ) @@ -311,7 +309,6 @@ func main() { fileBody2 := `package main import ( - "std" "gno.land/p/demo/ufmt" "gno.land/r/demo/deep/very/deep" ) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index c06c3b3761c..23fb3998131 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -9,7 +9,6 @@ import ( "regexp" "strings" - "github.com/gnolang/gno/gnovm/pkg/gnolang" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "github.com/gnolang/gno/tm2/pkg/errors" @@ -162,7 +161,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { } // Validate Gno syntax and type check. - if err := gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { return err } @@ -324,7 +323,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gnolang.TypeCheckMemPackage(memPkg, store); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { return "", err } diff --git a/gno.land/pkg/sdk/vm/keeper_test.go b/gno.land/pkg/sdk/vm/keeper_test.go index bc6bc285704..8e46114d857 100644 --- a/gno.land/pkg/sdk/vm/keeper_test.go +++ b/gno.land/pkg/sdk/vm/keeper_test.go @@ -31,8 +31,6 @@ func TestVMKeeperAddPackage(t *testing.T) { Name: "test.gno", Body: `package test -import "std" - func Echo() string { return "hello world" }`, @@ -413,8 +411,6 @@ func TestNumberOfArgsError(t *testing.T) { Name: "test.gno", Body: `package test -import "std" - func Echo(msg string) string { return "echo:"+msg }`, From 9779ff30a61a6d066c7912ee17e68e5c59a7f376 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 12:51:06 +0100 Subject: [PATCH 26/67] use custom type instead of any in importer --- gnovm/pkg/gnolang/go2gno.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index c726e291abc..0bc9fa9a407 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -492,7 +492,7 @@ type MemPackageGetter interface { func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { imp := &gnoImporter{ getter: getter, - cache: map[string]any{}, + cache: map[string]gnoImporterResult{}, cfg: &types.Config{}, } imp.cfg.Importer = imp @@ -505,9 +505,14 @@ func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error return nil } +type gnoImporterResult struct { + pkg *types.Package + err error +} + type gnoImporter struct { getter MemPackageGetter - cache map[string]any // *types.Package or error + cache map[string]gnoImporterResult cfg *types.Config } @@ -519,27 +524,16 @@ func (g *gnoImporter) Import(path string) (*types.Package, error) { // path when imported by a package file located in dir. func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { if pkg, ok := g.cache[path]; ok { - switch ret := pkg.(type) { - case *types.Package: - return ret, nil - case error: - return nil, ret - default: - panic(fmt.Sprintf("invalid type in gnoImporter.cache %T", ret)) - } + return pkg.pkg, pkg.err } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - g.cache[path] = (*types.Package)(nil) + g.cache[path] = gnoImporterResult{} return nil, nil } result, err := g.parseCheckMemPackage(mpkg) - if err != nil { - g.cache[path] = err - return nil, err - } - g.cache[path] = result - return result, nil + g.cache[path] = gnoImporterResult{pkg: result, err: err} + return result, err } func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package, error) { From 7b239dfc856ddefec0da2709db6caf05aca38999 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 13:20:02 +0100 Subject: [PATCH 27/67] support multiple errors in type checker --- gnovm/pkg/gnolang/go2gno.go | 19 ++++++++++++------- gnovm/pkg/gnolang/go2gno_test.go | 27 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 0bc9fa9a407..60ea2389ef6 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -490,19 +490,25 @@ type MemPackageGetter interface { // // The syntax checking is performed entirely using Go's go/types package. func TypeCheckMemPackage(mempkg *std.MemPackage, getter MemPackageGetter) error { + var errs error imp := &gnoImporter{ getter: getter, cache: map[string]gnoImporterResult{}, - cfg: &types.Config{}, + cfg: &types.Config{ + Error: func(err error) { + errs = multierr.Append(errs, err) + }, + }, } imp.cfg.Importer = imp _, err := imp.parseCheckMemPackage(mempkg) - if err != nil { - return err + // prefer to return errs instead of err: + // err will generally contain only the first error encountered. + if errs != nil { + return errs } - - return nil + return err } type gnoImporterResult struct { @@ -549,7 +555,7 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) if err != nil { - err = multierr.Append(errs, err) + errs = multierr.Append(errs, err) continue } @@ -559,7 +565,6 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *std.MemPackage) (*types.Package return nil, errs } - // TODO g.cfg.Error return g.cfg.Check(mpkg.Path, fset, files, nil) } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index d5b94c618b0..bb7dcd22559 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -4,7 +4,9 @@ import ( "fmt" "testing" - "github.com/jaekwon/testify/assert" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/stretchr/testify/assert" + "go.uber.org/multierr" ) func TestParseForLoop(t *testing.T) { @@ -25,3 +27,26 @@ func main(){ fmt.Printf("AST:\n%#v\n\n", n) fmt.Printf("AST.String():\n%s\n", n.String()) } + +func TestTypeCheckMemPackage_MultiError(t *testing.T) { + const src = `package main +func main() { + _, _ = 11 + return 88, 88 +}` + err := TypeCheckMemPackage(&std.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/x", + Files: []*std.MemFile{ + { + Name: "main.gno", + Body: src, + }, + }, + }, nil) + errs := multierr.Errors(err) + if assert.Len(t, errs, 2, "should contain two errors") { + assert.ErrorContains(t, errs[0], "assignment mismatch") + assert.ErrorContains(t, errs[1], "too many return values") + } +} From 1b323a2ac62319ea597ac894ce5bc3789f799e95 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 13:46:35 +0100 Subject: [PATCH 28/67] correctly return error in vm keeper --- gno.land/pkg/sdk/vm/errors.go | 26 +++++++++++++++++++++++++- gno.land/pkg/sdk/vm/keeper.go | 4 ++-- gno.land/pkg/sdk/vm/package.go | 1 + 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go index 64778f8b467..132d98b7ecd 100644 --- a/gno.land/pkg/sdk/vm/errors.go +++ b/gno.land/pkg/sdk/vm/errors.go @@ -1,6 +1,11 @@ package vm -import "github.com/gnolang/gno/tm2/pkg/errors" +import ( + "strings" + + "github.com/gnolang/gno/tm2/pkg/errors" + "go.uber.org/multierr" +) // for convenience: type abciError struct{} @@ -13,11 +18,21 @@ type ( InvalidPkgPathError struct{ abciError } InvalidStmtError struct{ abciError } InvalidExprError struct{ abciError } + TypeCheckError struct { + abciError + Errors []string + } ) func (e InvalidPkgPathError) Error() string { return "invalid package path" } func (e InvalidStmtError) Error() string { return "invalid statement" } func (e InvalidExprError) Error() string { return "invalid expression" } +func (e TypeCheckError) Error() string { + var bld strings.Builder + bld.WriteString("invalid gno package; type check errors:\n") + bld.WriteString(strings.Join(e.Errors, "\n")) + return bld.String() +} func ErrInvalidPkgPath(msg string) error { return errors.Wrap(InvalidPkgPathError{}, msg) @@ -30,3 +45,12 @@ func ErrInvalidStmt(msg string) error { func ErrInvalidExpr(msg string) error { return errors.Wrap(InvalidExprError{}, msg) } + +func ErrTypeCheck(err error) error { + var tce TypeCheckError + errs := multierr.Errors(err) + for _, err := range errs { + tce.Errors = append(tce.Errors, err.Error()) + } + return errors.NewWithData(tce).Stacktrace() +} diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index 23fb3998131..12a1795b74b 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -162,7 +162,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) error { // Validate Gno syntax and type check. if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { - return err + return ErrTypeCheck(err) } // Pay deposit from creator. @@ -324,7 +324,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { // Validate Gno syntax and type check. if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { - return "", err + return "", ErrTypeCheck(err) } // Send send-coins to pkg from caller. diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go index 01fad3284e3..b2e7fbecfc4 100644 --- a/gno.land/pkg/sdk/vm/package.go +++ b/gno.land/pkg/sdk/vm/package.go @@ -20,4 +20,5 @@ var Package = amino.RegisterPackage(amino.NewPackage( InvalidPkgPathError{}, "InvalidPkgPathError", InvalidStmtError{}, "InvalidStmtError", InvalidExprError{}, "InvalidExprError", + TypeCheckError{}, "TypeCheckError", )) From df1679c771020d15882635385b10b4c718ced382 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 14:44:52 +0100 Subject: [PATCH 29/67] add tests to type checker --- gnovm/pkg/gnolang/go2gno.go | 10 +- gnovm/pkg/gnolang/go2gno_test.go | 267 ++++++++++++++++++++++++++++--- 2 files changed, 257 insertions(+), 20 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 60ea2389ef6..0b473f82da1 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -522,10 +522,15 @@ type gnoImporter struct { cfg *types.Config } +// Unused, but satisfies the Importer interface. func (g *gnoImporter) Import(path string) (*types.Package, error) { return g.ImportFrom(path, "", 0) } +type errImportNotFound string + +func (e errImportNotFound) Error() string { return "import not found: " + string(e) } + // ImportFrom returns the imported package for the given import // path when imported by a package file located in dir. func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { @@ -534,8 +539,9 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - g.cache[path] = gnoImporterResult{} - return nil, nil + err := errImportNotFound(path) + g.cache[path] = gnoImporterResult{err: err} + return nil, err } result, err := g.parseCheckMemPackage(mpkg) g.cache[path] = gnoImporterResult{pkg: result, err: err} diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index bb7dcd22559..403291b3f84 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -6,6 +6,7 @@ import ( "github.com/gnolang/gno/tm2/pkg/std" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.uber.org/multierr" ) @@ -28,25 +29,255 @@ func main(){ fmt.Printf("AST.String():\n%s\n", n.String()) } -func TestTypeCheckMemPackage_MultiError(t *testing.T) { - const src = `package main -func main() { - _, _ = 11 - return 88, 88 -}` - err := TypeCheckMemPackage(&std.MemPackage{ - Name: "main", - Path: "gno.land/p/demo/x", - Files: []*std.MemFile{ - { - Name: "main.gno", - Body: src, +func newMemPackage( + pkgName, pkgPath string, + namesAndFiles ...string, +) *std.MemPackage { + if len(namesAndFiles)%2 != 0 { + panic("namesAndFiles must be pairs") + } + files := make([]*std.MemFile, 0, len(namesAndFiles)/2) + for i := 0; i < len(namesAndFiles); i += 2 { + files = append(files, &std.MemFile{ + Name: namesAndFiles[i], + Body: namesAndFiles[i+1], + }) + } + return &std.MemPackage{ + Name: pkgName, + Path: pkgPath, + Files: files, + } +} + +type mockPackageGetter []*std.MemPackage + +func (mi mockPackageGetter) GetMemPackage(path string) *std.MemPackage { + for _, pkg := range mi { + if pkg.Path == path { + return pkg + } + } + return nil +} + +type mockPackageGetterCounts struct { + mockPackageGetter + counts map[string]int +} + +func (mpg mockPackageGetterCounts) GetMemPackage(path string) *std.MemPackage { + mpg.counts[path]++ + return mpg.mockPackageGetter.GetMemPackage(path) +} + +func TestTypeCheckMemPackage(t *testing.T) { + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and + // each error in order must contain the associated string. + errContains := func(s0 string, ss ...string) func(*testing.T, error) { + return func(t *testing.T, err error) { + t.Helper() + errs := multierr.Errors(err) + if len(errs) == 0 { + t.Errorf("expected an error, got nil") + return + } + want := len(ss) + 1 + if len(errs) != want { + t.Errorf("expected %d errors, got %d", want, len(errs)) + return + } + assert.ErrorContains(t, errs[0], s0) + for idx, err := range errs[1:] { + assert.ErrorContains(t, err, ss[idx]) + } + } + } + + type testCase struct { + name string + pkg *std.MemPackage + getter MemPackageGetter + check func(*testing.T, error) + } + tt := []testCase{ + { + "Simple", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + ), + nil, + nil, + }, + { + "WrongReturn", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + ), + nil, + errContains("cannot use 11"), + }, + { + "ParseError", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello! + func B() int { return 11 }`, + ), + nil, + errContains("found '!'"), + }, + { + "MultiError", + newMemPackage( + "main", "gno.land/p/demo/main", + + "hello.gno", + `package main + func main() { + _, _ = 11 + return 88, 88 + }`, + ), + nil, + errContains("assignment mismatch", "too many return values"), + }, + { + "TestsIgnored", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + func B() int { return 11 }`, + "hello_test.gno", + `This is not valid Gno code, but it doesn't matter because test + files are not checked.`, + ), + nil, + nil, + }, + { + "ImportFailed", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{}, + errContains("import not found: std"), + }, + { + "ImportSucceeded", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{ + newMemPackage( + "std", "std", + + "std.gno", + `package std + type Address string`, + ), }, + nil, }, - }, nil) - errs := multierr.Errors(err) - if assert.Len(t, errs, 2, "should contain two errors") { - assert.ErrorContains(t, errs[0], "assignment mismatch") - assert.ErrorContains(t, errs[1], "too many return values") + { + "ImportBadIdent", + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import "std" + func Hello() std.Address { return "hello" }`, + ), + mockPackageGetter{ + newMemPackage( + "a_completely_dfferent_identifier", "std", + + "std.gno", + `package a_completely_different_identifier + type Address string`, + ), + }, + errContains("undefined: std", "a_completely_different_identifier and not used"), + }, + } + + cacheMpg := mockPackageGetterCounts{ + mockPackageGetter{ + newMemPackage( + "bye", "bye", + + "bye.gno", + `package bye + import "std" + func Bye() std.Address { return "bye" }`, + ), + newMemPackage( + "std", "std", + + "std.gno", + `package std + type Address string`, + ), + }, + make(map[string]int), + } + + tt = append(tt, testCase{ + "ImportWithCache", + // This test will make use of the importer's internal cache for package `std`. + newMemPackage( + "hello", "gno.land/p/demo/hello", + + "hello.gno", + `package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + ), + cacheMpg, + func(t *testing.T, err error) { + require.NoError(t, err) + assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) + }, + }) + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := TypeCheckMemPackage(tc.pkg, tc.getter) + if tc.check == nil { + assert.NoError(t, err) + } else { + tc.check(t, err) + } + }) } } From ee0448eb8d7c5a25b89d7f7c4a77b558299672aa Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 14:58:25 +0100 Subject: [PATCH 30/67] fixup --- gnovm/pkg/gnolang/go2gno.go | 6 +++--- gnovm/pkg/gnolang/go2gno_test.go | 1 + gnovm/pkg/gnolang/store.go | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 0b473f82da1..6b0cf4a6cda 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -527,9 +527,9 @@ func (g *gnoImporter) Import(path string) (*types.Package, error) { return g.ImportFrom(path, "", 0) } -type errImportNotFound string +type importNotFoundError string -func (e errImportNotFound) Error() string { return "import not found: " + string(e) } +func (e importNotFoundError) Error() string { return "import not found: " + string(e) } // ImportFrom returns the imported package for the given import // path when imported by a package file located in dir. @@ -539,7 +539,7 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac } mpkg := g.getter.GetMemPackage(path) if mpkg == nil { - err := errImportNotFound(path) + err := importNotFoundError(path) g.cache[path] = gnoImporterResult{err: err} return nil, err } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 403291b3f84..35d00dce8f3 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -265,6 +265,7 @@ func TestTypeCheckMemPackage(t *testing.T) { ), cacheMpg, func(t *testing.T, err error) { + t.Helper() require.NoError(t, err) assert.Equal(t, map[string]int{"std": 1, "bye": 1}, cacheMpg.counts) }, diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 306253e7f31..491a2770be0 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -178,7 +178,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { if impStore.defaultStore == nil { - // pre-allocate 16 strings to likely avoid further slice allocations. + // pre-allocate 16 entries to likely avoid further slice allocations. impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} } impStore.importChain = append(impStore.importChain, pkgPath) From a1698000292fb02fce0ad365fa3a1f90100aedc1 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sun, 3 Mar 2024 15:02:26 +0100 Subject: [PATCH 31/67] add integration test for gno.land --- gno.land/cmd/gnoland/testdata/addpkg.txtar | 2 +- .../cmd/gnoland/testdata/addpkg_invalid.txtar | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar diff --git a/gno.land/cmd/gnoland/testdata/addpkg.txtar b/gno.land/cmd/gnoland/testdata/addpkg.txtar index e7437552b50..89da1378e0c 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg.txtar @@ -3,7 +3,7 @@ ## start a new node gnoland start -## add bar.gno package located in $WORK directory as gno.land/r/foobar/bar +## add bar package located in $WORK directory as gno.land/r/foobar/bar gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 ## execute Render diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar new file mode 100644 index 00000000000..e0395fb479d --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -0,0 +1,21 @@ +# test for add package; ensuring type checker catches invalid code. + +# start a new node +gnoland start + +# add bar package located in $WORK directory as gno.land/r/foobar/bar +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +# compare render +! stdout .+ +stderr 'as string value in return statement' +stderr '"std" imported and not used' + +-- bar.gno -- +package bar + +import "std" + +func Render(path string) string { + return 89 +} From 0fa34fae17fed35a226e3a447c8c98a15fc599cf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 27 Mar 2024 19:29:04 +0100 Subject: [PATCH 32/67] remove newMemPackage --- gnovm/pkg/gnolang/go2gno_test.go | 304 ++++++++++++++++++------------- 1 file changed, 175 insertions(+), 129 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 35d00dce8f3..0cfe7610bd8 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -29,27 +29,6 @@ func main(){ fmt.Printf("AST.String():\n%s\n", n.String()) } -func newMemPackage( - pkgName, pkgPath string, - namesAndFiles ...string, -) *std.MemPackage { - if len(namesAndFiles)%2 != 0 { - panic("namesAndFiles must be pairs") - } - files := make([]*std.MemFile, 0, len(namesAndFiles)/2) - for i := 0; i < len(namesAndFiles); i += 2 { - files = append(files, &std.MemFile{ - Name: namesAndFiles[i], - Body: namesAndFiles[i+1], - }) - } - return &std.MemPackage{ - Name: pkgName, - Path: pkgPath, - Files: files, - } -} - type mockPackageGetter []*std.MemPackage func (mi mockPackageGetter) GetMemPackage(path string) *std.MemPackage { @@ -103,126 +82,178 @@ func TestTypeCheckMemPackage(t *testing.T) { tt := []testCase{ { "Simple", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - type S struct{} - func A() S { return S{} } - func B() S { return A() }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return A() }`, + }, + }, + }, nil, nil, }, { "WrongReturn", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - type S struct{} - func A() S { return S{} } - func B() S { return 11 }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + type S struct{} + func A() S { return S{} } + func B() S { return 11 }`, + }, + }, + }, nil, errContains("cannot use 11"), }, { "ParseError", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello! - func B() int { return 11 }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello! + func B() int { return 11 }`, + }, + }, + }, nil, errContains("found '!'"), }, { "MultiError", - newMemPackage( - "main", "gno.land/p/demo/main", - - "hello.gno", - `package main - func main() { - _, _ = 11 - return 88, 88 - }`, - ), + &std.MemPackage{ + Name: "main", + Path: "gno.land/p/demo/main", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package main + func main() { + _, _ = 11 + return 88, 88 + }`, + }, + }, + }, nil, errContains("assignment mismatch", "too many return values"), }, { "TestsIgnored", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - func B() int { return 11 }`, - "hello_test.gno", - `This is not valid Gno code, but it doesn't matter because test + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + func B() int { return 11 }`, + }, + { + Name: "hello_test.gno", + Body: `This is not valid Gno code, but it doesn't matter because test files are not checked.`, - ), + }, + }, + }, nil, nil, }, { "ImportFailed", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{}, errContains("import not found: std"), }, { "ImportSucceeded", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{ - newMemPackage( - "std", "std", - - "std.gno", - `package std - type Address string`, - ), + &std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package std + type Address string`, + }, + }, + }, }, nil, }, { "ImportBadIdent", - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import "std" - func Hello() std.Address { return "hello" }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import "std" + func Hello() std.Address { return "hello" }`, + }, + }, + }, mockPackageGetter{ - newMemPackage( - "a_completely_dfferent_identifier", "std", - - "std.gno", - `package a_completely_different_identifier - type Address string`, - ), + &std.MemPackage{ + Name: "a_completely_different_identifier", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package a_completely_different_identifier + type Address string`, + }, + }, + }, }, errContains("undefined: std", "a_completely_different_identifier and not used"), }, @@ -230,21 +261,31 @@ func TestTypeCheckMemPackage(t *testing.T) { cacheMpg := mockPackageGetterCounts{ mockPackageGetter{ - newMemPackage( - "bye", "bye", - - "bye.gno", - `package bye - import "std" - func Bye() std.Address { return "bye" }`, - ), - newMemPackage( - "std", "std", - - "std.gno", - `package std - type Address string`, - ), + &std.MemPackage{ + Name: "bye", + Path: "bye", + Files: []*std.MemFile{ + { + Name: "bye.gno", + Body: ` + package bye + import "std" + func Bye() std.Address { return "bye" }`, + }, + }, + }, + &std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + { + Name: "std.gno", + Body: ` + package std + type Address string`, + }, + }, + }, }, make(map[string]int), } @@ -252,17 +293,22 @@ func TestTypeCheckMemPackage(t *testing.T) { tt = append(tt, testCase{ "ImportWithCache", // This test will make use of the importer's internal cache for package `std`. - newMemPackage( - "hello", "gno.land/p/demo/hello", - - "hello.gno", - `package hello - import ( - "std" - "bye" - ) - func Hello() std.Address { return bye.Bye() }`, - ), + &std.MemPackage{ + Name: "hello", + Path: "gno.land/p/demo/hello", + Files: []*std.MemFile{ + { + Name: "hello.gno", + Body: ` + package hello + import ( + "std" + "bye" + ) + func Hello() std.Address { return bye.Bye() }`, + }, + }, + }, cacheMpg, func(t *testing.T, err error) { t.Helper() From 89d9887af1a3a181590928b074a5484c568c82b3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 27 Mar 2024 20:14:28 +0100 Subject: [PATCH 33/67] remove unused var --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index c42cca490b6..d6ca62609e6 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -24,7 +24,7 @@ stdout '10000 uint64' # unwrap 500 wugnot gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -# XXX without patching anything it will panic +# XXX without patching anything it will panic # panic msg: insufficient coins error # XXX with pathcing only wugnot.gnot it will panic # panic msg: RealmSendBanker can only send from the realm package address "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3", but got "g1pf6dv9fjk3rn0m4jjcne306ga4he3mzmupfjl6" @@ -72,7 +72,6 @@ func ProxyUnwrap(wugnotAmount uint64) { if wugnotAmount == 0 { return } - userOldWugnot := wugnot.BalanceOf(std.GetOrigCaller()) // SEND WUGNOT: USER -> PROXY_WUGNOT wugnot.TransferFrom(std.GetOrigCaller(), std.CurrentRealm().Addr(), wugnotAmount) @@ -83,4 +82,4 @@ func ProxyUnwrap(wugnotAmount uint64) { // SEND GNOT: PROXY_WUGNOT -> USER banker := std.GetBanker(std.BankerTypeRealmSend) banker.SendCoins(std.CurrentRealm().Addr(), std.GetOrigCaller(), std.Coins{{"ugnot", int64(wugnotAmount)}}) -} \ No newline at end of file +} From 338454f02b32a68ea608ee9d957d7334e02f1f4e Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 18:11:54 +0200 Subject: [PATCH 34/67] remove other usage in gnoclient --- gno.land/pkg/gnoclient/client_txs.go | 5 ----- gnovm/stdlibs/io/io.gno | 1 - 2 files changed, 6 deletions(-) diff --git a/gno.land/pkg/gnoclient/client_txs.go b/gno.land/pkg/gnoclient/client_txs.go index ea523317e33..e306d737ede 100644 --- a/gno.land/pkg/gnoclient/client_txs.go +++ b/gno.land/pkg/gnoclient/client_txs.go @@ -257,11 +257,6 @@ func (c *Client) AddPackage(cfg BaseTxCfg, msgs ...MsgAddPackage) (*ctypes.Resul caller := c.Signer.Info().GetAddress() - // Transpile and validate Gno syntax - if err = transpiler.TranspileAndCheckMempkg(msg.Package); err != nil { - return nil, err - } - // Unwrap syntax sugar to vm.MsgCall slice vmMsgs = append(vmMsgs, std.Msg(vm.MsgAddPackage{ Creator: caller, diff --git a/gnovm/stdlibs/io/io.gno b/gnovm/stdlibs/io/io.gno index a4ab3b3f8fd..bdbab135140 100644 --- a/gnovm/stdlibs/io/io.gno +++ b/gnovm/stdlibs/io/io.gno @@ -489,7 +489,6 @@ func NewSectionReader(r ReaderAt, off int64, n int64) *SectionReader { remaining = maxint64 } return &SectionReader{r, off, off, remaining, n} - } // SectionReader implements Read, Seek, and ReadAt on a section From 6b4ed9e500894c15c74f14459c07d964ae1010b9 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 18:17:35 +0200 Subject: [PATCH 35/67] remove unused import in test --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 2 -- 1 file changed, 2 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index d6ca62609e6..15747414fa9 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -46,8 +46,6 @@ import ( "std" "gno.land/r/demo/wugnot" - - "gno.land/p/demo/ufmt" ) func ProxyWrap() { From 0a40b4671f87015906fb04ea7b7639cc9393750a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 18:46:45 +0200 Subject: [PATCH 36/67] fix some tests --- .../cmd/gnoland/testdata/addpkg_invalid.txtar | 2 +- .../gno_transpile/05_skip_fmt_flag.txtar | 36 ------------------- .../09_gno_files_whitelist_error.txtar | 4 +-- 3 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar index e0395fb479d..84aab3c7ab9 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -4,7 +4,7 @@ gnoland start # add bar package located in $WORK directory as gno.land/r/foobar/bar -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 3_000_000 -broadcast -chainid=tendermint_test test1 # compare render ! stdout .+ diff --git a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar deleted file mode 100644 index c07c670f721..00000000000 --- a/gnovm/cmd/gno/testdata/gno_transpile/05_skip_fmt_flag.txtar +++ /dev/null @@ -1,36 +0,0 @@ -# Run gno transpile with -skip-fmt flag -# NOTE(tb): this flag doesn't actually prevent the code format, because -# `gnolang.Transpile()` calls `format.Node()`. - -gno transpile -skip-fmt . - -! stdout .+ -! stderr .+ - -cmp main.gno.gen.go main.gno.gen.go.golden -cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden - --- main.gno -- -package main - -func main(){} - --- sub/sub.gno -- -package sub - --- main.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line main.gno:1:1 -package main - -func main() {} --- sub/sub.gno.gen.go.golden -- -// Code generated by github.com/gnolang/gno. DO NOT EDIT. - -//go:build gno - -//line sub.gno:1:1 -package sub diff --git a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar index 79d4d6a4a2c..4aafc55ef26 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar @@ -3,8 +3,8 @@ ! gno transpile . ! stdout .+ -stderr '^main.gno:5:2: import "xxx" is not in the whitelist$' -stderr '^sub/sub.gno:3:8: import "xxx" is not in the whitelist$' +stderr '^main.gno:5:2: import "xxx" does not exist$' +stderr '^sub/sub.gno:3:8: import "xxx" does not exist$' stderr '^2 transpile error\(s\)$' # no *.gen.go files are created From 4c7596e9b3957f4f3190796eaec787b7ba6376a9 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 15 Apr 2024 22:25:46 +0200 Subject: [PATCH 37/67] add tests for transpiler changes --- gnovm/cmd/gno/transpile.go | 103 +++++++++ gnovm/cmd/gno/transpile_test.go | 61 ++++++ gnovm/pkg/gnolang/realm_test.go | 30 +++ gnovm/pkg/transpiler/transpiler.go | 106 +--------- gnovm/pkg/transpiler/transpiler_test.go | 270 +++++++++++++++++------- 5 files changed, 390 insertions(+), 180 deletions(-) create mode 100644 gnovm/pkg/gnolang/realm_test.go diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index a0ee25ac1af..6672ef73c45 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -7,10 +7,14 @@ import ( "fmt" "go/ast" "go/scanner" + "go/token" "log" "os" + "os/exec" "path/filepath" + "regexp" "slices" + "sort" "strconv" "strings" @@ -310,3 +314,102 @@ func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs } return } + +// buildTranspiledPackage tries to run `go build` against the transpiled .go files. +// +// This method is the most efficient to detect errors but requires that +// all the import are valid and available. +func buildTranspiledPackage(fileOrPkg, goBinary string) error { + // TODO: use cmd/compile instead of exec? + // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? + // TODO: temporarily create an in-memory go.mod or disable go modules for gno? + // TODO: ignore .go files that were not generated from gno? + // TODO: automatically transpile if not yet done. + + files := []string{} + + info, err := os.Stat(fileOrPkg) + if err != nil { + return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) + } + if !info.IsDir() { + file := fileOrPkg + files = append(files, file) + } else { + pkgDir := fileOrPkg + goGlob := filepath.Join(pkgDir, "*.go") + goMatches, err := filepath.Glob(goGlob) + if err != nil { + return fmt.Errorf("glob %s: %w", goGlob, err) + } + for _, goMatch := range goMatches { + switch { + case strings.HasPrefix(goMatch, "."): // skip + case strings.HasSuffix(goMatch, "_filetest.go"): // skip + case strings.HasSuffix(goMatch, "_filetest.gno.gen.go"): // skip + case strings.HasSuffix(goMatch, "_test.go"): // skip + case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip + default: + if !filepath.IsAbs(pkgDir) { + // Makes clear to go compiler that this is a relative path, + // rather than a path to a package/module. + // can't use filepath.Join as it cleans its results. + goMatch = "." + string(filepath.Separator) + goMatch + } + files = append(files, goMatch) + } + } + } + + sort.Strings(files) + args := append([]string{"build", "-tags=gno"}, files...) + cmd := exec.Command(goBinary, args...) + out, err := cmd.CombinedOutput() + if _, ok := err.(*exec.ExitError); ok { + // exit error + return parseGoBuildErrors(string(out)) + } + return err +} + +var ( + errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) + commentRe = regexp.MustCompile(`(?m)^#.*$`) +) + +// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found +// in out, which is supposed to be the output of the `go build` command. +// +// TODO(tb): update when `go build -json` is released to replace regexp usage. +// See https://github.com/golang/go/issues/62067 +func parseGoBuildErrors(out string) error { + var errList goscanner.ErrorList + matches := errorRe.FindAllStringSubmatch(out, -1) + for _, match := range matches { + filename := match[1] + line, err := strconv.Atoi(match[2]) + if err != nil { + return fmt.Errorf("parse line go build error %s: %w", match, err) + } + + column, err := strconv.Atoi(match[3]) + if err != nil { + return fmt.Errorf("parse column go build error %s: %w", match, err) + } + msg := match[4] + errList.Add(token.Position{ + Filename: filename, + Line: line, + Column: column, + }, msg) + } + + replaced := errorRe.ReplaceAllLiteralString(out, "") + replaced = commentRe.ReplaceAllString(replaced, "") + replaced = strings.TrimSpace(replaced) + if replaced != "" { + errList.Add(token.Position{}, "Additional go build errors:\n"+replaced) + } + + return errList.Err() +} diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 2770026a01a..291aa080165 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -1,9 +1,11 @@ package main import ( + "go/token" "testing" "github.com/rogpeppe/go-internal/testscript" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gnolang/gno/gnovm/pkg/integration" @@ -24,3 +26,62 @@ func Test_ScriptsTranspile(t *testing.T) { testscript.Run(t, p) } + +func Test_parseGoBuildErrors(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + output string + expectedError error + }{ + { + name: "empty output", + output: "", + expectedError: nil, + }, + { + name: "random output", + output: "xxx", + expectedError: goscanner.ErrorList{ + &goscanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + { + name: "some errors", + output: `xxx +main.gno:6:2: nasty error +pkg/file.gno:60:20: ugly error`, + expectedError: goscanner.ErrorList{ + &goscanner.Error{ + Pos: token.Position{ + Filename: "main.gno", + Line: 6, + Column: 2, + }, + Msg: "nasty error", + }, + &goscanner.Error{ + Pos: token.Position{ + Filename: "pkg/file.gno", + Line: 60, + Column: 20, + }, + Msg: "ugly error", + }, + &goscanner.Error{ + Msg: "Additional go build errors:\nxxx", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := parseGoBuildErrors(tt.output) + + assert.Equal(t, tt.expectedError, err) + }) + } +} diff --git a/gnovm/pkg/gnolang/realm_test.go b/gnovm/pkg/gnolang/realm_test.go new file mode 100644 index 00000000000..8ad83e57f23 --- /dev/null +++ b/gnovm/pkg/gnolang/realm_test.go @@ -0,0 +1,30 @@ +package gnolang + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsRealmPath(t *testing.T) { + tt := []struct { + input string + result bool + }{ + {"gno.land/r/demo/users", true}, + {"gno.land/r/hello", true}, + {"gno.land/p/demo/users", false}, + {"gno.land/p/hello", false}, + {"gno.land/x", false}, + {"std", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsRealmPath(tc.input), + "unexpected IsRealmPath(%q) result", tc.input, + ) + } +} diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 5e4515a1dbc..24665f0df27 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -11,11 +11,8 @@ import ( goscanner "go/scanner" "go/token" "os" - "os/exec" "path" "path/filepath" - "regexp" - "sort" "strconv" "strings" @@ -39,8 +36,8 @@ func TranspileImportPath(s string) string { func IsStdlib(s string) bool { // NOTE(morgan): this is likely to change in the future as we add support for // IBC/ICS and we allow import paths to other chains. It might be good to - // follow the same rule as Go, which is: does the first element of the - // import path contain a dot? + // (eventually) follow the same rule as Go, which is: does the first + // element of the import path contain a dot? return !strings.HasPrefix(s, "gno.land/") } @@ -150,105 +147,6 @@ func Transpile(source, tags, filename string) (*Result, error) { return res, nil } -// TranspileBuildPackage tries to run `go build` against the transpiled .go files. -// -// This method is the most efficient to detect errors but requires that -// all the import are valid and available. -func TranspileBuildPackage(fileOrPkg, goBinary string) error { - // TODO: use cmd/compile instead of exec? - // TODO: find the nearest go.mod file, chdir in the same folder, rim prefix? - // TODO: temporarily create an in-memory go.mod or disable go modules for gno? - // TODO: ignore .go files that were not generated from gno? - // TODO: automatically transpile if not yet done. - - files := []string{} - - info, err := os.Stat(fileOrPkg) - if err != nil { - return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) - } - if !info.IsDir() { - file := fileOrPkg - files = append(files, file) - } else { - pkgDir := fileOrPkg - goGlob := filepath.Join(pkgDir, "*.go") - goMatches, err := filepath.Glob(goGlob) - if err != nil { - return fmt.Errorf("glob %s: %w", goGlob, err) - } - for _, goMatch := range goMatches { - switch { - case strings.HasPrefix(goMatch, "."): // skip - case strings.HasSuffix(goMatch, "_filetest.go"): // skip - case strings.HasSuffix(goMatch, "_filetest.gno.gen.go"): // skip - case strings.HasSuffix(goMatch, "_test.go"): // skip - case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip - default: - if !filepath.IsAbs(pkgDir) { - // Makes clear to go compiler that this is a relative path, - // rather than a path to a package/module. - // can't use filepath.Join as it cleans its results. - goMatch = "." + string(filepath.Separator) + goMatch - } - files = append(files, goMatch) - } - } - } - - sort.Strings(files) - args := append([]string{"build", "-tags=gno"}, files...) - cmd := exec.Command(goBinary, args...) - out, err := cmd.CombinedOutput() - if _, ok := err.(*exec.ExitError); ok { - // exit error - return parseGoBuildErrors(string(out)) - } - return err -} - -var ( - errorRe = regexp.MustCompile(`(?m)^(\S+):(\d+):(\d+): (.+)$`) - commentRe = regexp.MustCompile(`(?m)^#.*$`) -) - -// parseGoBuildErrors returns a scanner.ErrorList filled with all errors found -// in out, which is supposed to be the output of the `go build` command. -// -// TODO(tb): update when `go build -json` is released to replace regexp usage. -// See https://github.com/golang/go/issues/62067 -func parseGoBuildErrors(out string) error { - var errList goscanner.ErrorList - matches := errorRe.FindAllStringSubmatch(out, -1) - for _, match := range matches { - filename := match[1] - line, err := strconv.Atoi(match[2]) - if err != nil { - return fmt.Errorf("parse line go build error %s: %w", match, err) - } - - column, err := strconv.Atoi(match[3]) - if err != nil { - return fmt.Errorf("parse column go build error %s: %w", match, err) - } - msg := match[4] - errList.Add(token.Position{ - Filename: filename, - Line: line, - Column: column, - }, msg) - } - - replaced := errorRe.ReplaceAllLiteralString(out, "") - replaced = commentRe.ReplaceAllString(replaced, "") - replaced = strings.TrimSpace(replaced) - if replaced != "" { - errList.Add(token.Position{}, "Additional go build errors:\n"+replaced) - } - - return errList.Err() -} - type transpileCtx struct { // If rootDir is given, we will check that the directory of the import path // exists (using rootDir/packageDirLocation()). diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 454d03a7da1..656347d4858 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -2,21 +2,93 @@ package transpiler import ( "go/ast" - goscanner "go/scanner" - "go/token" + "path/filepath" "strings" "testing" - "github.com/jaekwon/testify/assert" - "github.com/jaekwon/testify/require" + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestIsStdlib(t *testing.T) { + t.Parallel() + + tt := []struct { + s string + result bool + }{ + {"std", true}, + {"math", true}, + {"very/long/path/with_underscores", true}, + {"gno.land/r/demo/users", false}, + {"gno.land/hello", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsStdlib(tc.s), + "IsStdlib(%q)", tc.s, + ) + } +} + +func TestTranspiledFilenameAndTags(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + changed string + tags string + }{ + { + "hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "a/b/hello.gno", + "hello.gno.gen.go", + "gno", + }, + { + "hey_test.gno", + ".hey_test.gno.gen_test.go", + "gno && test", + }, + { + "hey_filetest.gno", + ".hey_filetest.gno.gen.go", + "gno && filetest", + }, + { + "badname.go", + "badname.go.gno.gen.go", + "gno", + }, + { + "badname_test.go", + "badname_test.go.gno.gen.go", + "gno", + }, + } + + for _, tc := range tt { + newName, tags := TranspiledFilenameAndTags(tc.name) + assert.Equal(t, tc.changed, newName, "name for %q", tc.name) + assert.Equal(t, tc.tags, tags, "tags for %q", tc.name) + } +} + func TestTranspile(t *testing.T) { t.Parallel() cases := []struct { name string tags string + filename string source string expectedOutput string expectedImports []*ast.ImportSpec @@ -75,7 +147,7 @@ func hello() string { //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = std.Foo @@ -87,9 +159,8 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 26, }, }, }, @@ -98,7 +169,7 @@ func hello() string { source: ` package foo -import "gno.land/r/users" +import "gno.land/r/demo/users" func foo() { _ = users.Register} `, @@ -108,7 +179,7 @@ func foo() { _ = users.Register} //line foo.gno:1:1 package foo -import "github.com/gnolang/gno/examples/gno.land/r/users" +import "github.com/gnolang/gno/examples/gno.land/r/demo/users" func foo() { _ = users.Register } `, @@ -117,9 +188,8 @@ func foo() { _ = users.Register } Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"github.com/gnolang/gno/examples/gno.land/r/users"`, + Value: `"github.com/gnolang/gno/examples/gno.land/r/demo/users"`, }, - EndPos: 39, }, }, }, @@ -130,7 +200,7 @@ package foo import "gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedOutput: ` // Code generated by github.com/gnolang/gno. DO NOT EDIT. @@ -140,7 +210,7 @@ package foo import "github.com/gnolang/gno/examples/gno.land/p/demo/avl" -func foo() { _ = avl.Tree } +func foo() { _ = avl.NewTree("hey", 1) } `, expectedImports: []*ast.ImportSpec{ { @@ -149,7 +219,6 @@ func foo() { _ = avl.Tree } Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/avl"`, }, - EndPos: 42, }, }, }, @@ -171,7 +240,7 @@ func hello() string { //line foo.gno:1:1 package foo -import bar "github.com/gnolang/gno/gnovm/stdlibs/stdshim" +import bar "github.com/gnolang/gno/gnovm/stdlibs/std" func hello() string { _ = bar.Foo @@ -187,14 +256,13 @@ func hello() string { Path: &ast.BasicLit{ ValuePos: 25, Kind: 9, - Value: `"github.com/gnolang/gno/gnovm/stdlibs/stdshim"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, }, - EndPos: 30, }, }, }, { - name: "blacklisted-package", + name: "unknown-package", source: ` package foo @@ -202,7 +270,7 @@ import "reflect" func foo() { _ = reflect.ValueOf } `, - expectedError: `transpileAST: foo.gno:3:8: import "reflect" is not in the whitelist`, + expectedError: `transpileAST: foo.gno:3:8: import "reflect" does not exist`, }, { name: "syntax-error", @@ -226,6 +294,27 @@ import "gno.land/p/demo/unknownxyz" //line foo.gno:1:1 package foo +import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" +`, + expectedError: `transpileAST: foo.gno:3:8: import "gno.land/p/demo/unknownxyz" does not exist`, + }, + { + // Test files should allow unknown imports while + // we still have "native" packages. + + name: "unknown-realm-test", + filename: "foo_test.gno", + source: ` +package foo + +import "gno.land/p/demo/unknownxyz" +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line foo_test.gno:1:1 +package foo + import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" `, expectedImports: []*ast.ImportSpec{ @@ -235,12 +324,11 @@ import "github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz" Kind: 9, Value: `"github.com/gnolang/gno/examples/gno.land/p/demo/unknownxyz"`, }, - EndPos: 49, }, }, }, { - name: "whitelisted-package", + name: "imported-package", source: ` package foo @@ -254,7 +342,7 @@ func foo() { _ = regexp.MatchString } //line foo.gno:1:1 package foo -import "regexp" +import "github.com/gnolang/gno/gnovm/stdlibs/regexp" func foo() { _ = regexp.MatchString } `, @@ -263,11 +351,87 @@ func foo() { _ = regexp.MatchString } Path: &ast.BasicLit{ ValuePos: 21, Kind: 9, - Value: `"regexp"`, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/regexp"`, }, }, }, }, + { + name: "natbind-func", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/math/math.gno"), + source: ` +package math + +import "std" + +func Float32bits(i float32) uint32 + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall() +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall() +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line math.gno:1:1 +package math + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func testfunc() { + println(Float32bits(3.14159)) + std.AssertOriginCall(nil) +} + +func otherFunc() { + std := 1 + // This is (incorrectly) changed for now. + std.AssertOriginCall(nil) +} +`, + expectedImports: []*ast.ImportSpec{ + { + Path: &ast.BasicLit{ + ValuePos: 22, + Kind: 9, + Value: `"github.com/gnolang/gno/gnovm/stdlibs/std"`, + }, + }, + }, + }, + { + name: "natbind-std", + filename: filepath.Join(gnoenv.RootDir(), "gnovm/stdlibs/std/std.gno"), + source: ` +package std + +func AssertOriginCall() +func origCaller() string + +func testfunc() { + AssertOriginCall() + println(origCaller()) +} +`, + expectedOutput: ` +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//line std.gno:1:1 +package std + +func testfunc() { + AssertOriginCall(nil) + println(X_origCaller(nil)) +} +`, + }, } for _, c := range cases { c := c // scopelint @@ -276,7 +440,11 @@ func foo() { _ = regexp.MatchString } // "\n" is added for better test case readability, now trim it source := strings.TrimPrefix(c.source, "\n") - res, err := Transpile(source, c.tags, "foo.gno") + filename := c.filename + if filename == "" { + filename = "foo.gno" + } + res, err := Transpile(source, c.tags, filename) if c.expectedError != "" { require.EqualError(t, err, c.expectedError) @@ -284,58 +452,8 @@ func foo() { _ = regexp.MatchString } } require.NoError(t, err) expectedOutput := strings.TrimPrefix(c.expectedOutput, "\n") - assert.Equal(t, res.Translated, expectedOutput, "wrong output") - assert.Equal(t, res.Imports, c.expectedImports, "wrong imports") - }) - } -} - -func TestParseGoBuildErrors(t *testing.T) { - tests := []struct { - name string - output string - expectedError error - }{ - { - name: "empty output", - output: "", - expectedError: nil, - }, - { - name: "random output", - output: "xxx", - expectedError: nil, - }, - { - name: "some errors", - output: `xxx -main.gno:6:2: nasty error -pkg/file.gno:60:20: ugly error`, - expectedError: goscanner.ErrorList{ - &goscanner.Error{ - Pos: token.Position{ - Filename: "main.gno", - Line: 6, - Column: 2, - }, - Msg: "nasty error", - }, - &goscanner.Error{ - Pos: token.Position{ - Filename: "pkg/file.gno", - Line: 60, - Column: 20, - }, - Msg: "ugly error", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := parseGoBuildErrors(tt.output) - - assert.Equal(t, err, tt.expectedError) + assert.Equal(t, expectedOutput, res.Translated, "wrong output") + assert.Equal(t, c.expectedImports, res.Imports, "wrong imports") }) } } From e89a7c1ed9b9caae95445a4a22cccb7ccbcdef6d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 16 Apr 2024 17:00:04 +0200 Subject: [PATCH 38/67] create makefile rule to simplify coverage in cmd/gno --- gnovm/Makefile | 21 +++++++++++++++++++++ gnovm/cmd/gno/transpile.go | 4 ++-- gnovm/cmd/gno/transpile_test.go | 13 +++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/gnovm/Makefile b/gnovm/Makefile index e3dad8f369a..b67345a361a 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -27,6 +27,9 @@ GNOROOT_DIR ?= $(abspath $(lastword $(MAKEFILE_LIST))/../../) # We can't use '-trimpath' yet as amino use absolute path from call stack # to find some directory: see #1236 GOBUILD_FLAGS ?= -ldflags "-X github.com/gnolang/gno/gnovm/pkg/gnoenv._GNOROOT=$(GNOROOT_DIR)" +# file where to place cover profile; used for coverage commands which are +# more complex than adding -coverprofile, like test.cmd.coverage. +GOTEST_COVER_PROFILE ?= cmd-profile.out ######################################## # Dev tools @@ -66,6 +69,24 @@ test: _test.cmd _test.pkg _test.gnolang _test.cmd: go test ./cmd/... $(GOTEST_FLAGS) +# Run tests on ./cmd/, saving the result of the coverage in +# GOTEST_COVER_PROFILE. +.PHONY: test.cmd.coverage +test.cmd.coverage: + $(eval export TXTARCOVERDIR := $(shell mktemp -d --tmpdir gnovm-make.XXXXXXX)) + go test ./cmd/... -covermode atomic -test.gocoverdir='$(TXTARCOVERDIR)' $(GOTEST_FLAGS) + @echo "coverage results:" + go tool covdata percent -i="$(TXTARCOVERDIR)" + go tool covdata textfmt -v 1 -i="$(TXTARCOVERDIR)" -o '$(GOTEST_COVER_PROFILE)' + rm -rf "$(TXTARCOVERDIR)" + +# Run test.cmd.coverage, then view the result in the HTML browser render +# and delete the original file. +.PHONY: test.cmd.coverage_view +test.cmd.coverage_view: test.cmd.coverage + go tool cover -html='$(GOTEST_COVER_PROFILE)' + rm '$(GOTEST_COVER_PROFILE)' + .PHONY: _test.pkg _test.pkg: go test ./pkg/... $(GOTEST_FLAGS) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 6672ef73c45..d3448c40dec 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -295,7 +295,7 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { fmt.Fprintf(os.Stderr, "%s [build]\n", fileOrPkg) } - return transpiler.TranspileBuildPackage(fileOrPkg, goBinary) + return buildTranspiledPackage(fileOrPkg, goBinary) } // getPathsFromImportSpec returns the directory paths where the code for each @@ -383,7 +383,7 @@ var ( // TODO(tb): update when `go build -json` is released to replace regexp usage. // See https://github.com/golang/go/issues/62067 func parseGoBuildErrors(out string) error { - var errList goscanner.ErrorList + var errList scanner.ErrorList matches := errorRe.FindAllStringSubmatch(out, -1) for _, match := range matches { filename := match[1] diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 291aa080165..8425aeb773d 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -1,6 +1,7 @@ package main import ( + "go/scanner" "go/token" "testing" @@ -43,8 +44,8 @@ func Test_parseGoBuildErrors(t *testing.T) { { name: "random output", output: "xxx", - expectedError: goscanner.ErrorList{ - &goscanner.Error{ + expectedError: scanner.ErrorList{ + &scanner.Error{ Msg: "Additional go build errors:\nxxx", }, }, @@ -54,8 +55,8 @@ func Test_parseGoBuildErrors(t *testing.T) { output: `xxx main.gno:6:2: nasty error pkg/file.gno:60:20: ugly error`, - expectedError: goscanner.ErrorList{ - &goscanner.Error{ + expectedError: scanner.ErrorList{ + &scanner.Error{ Pos: token.Position{ Filename: "main.gno", Line: 6, @@ -63,7 +64,7 @@ pkg/file.gno:60:20: ugly error`, }, Msg: "nasty error", }, - &goscanner.Error{ + &scanner.Error{ Pos: token.Position{ Filename: "pkg/file.gno", Line: 60, @@ -71,7 +72,7 @@ pkg/file.gno:60:20: ugly error`, }, Msg: "ugly error", }, - &goscanner.Error{ + &scanner.Error{ Msg: "Additional go build errors:\nxxx", }, }, From 86abc8cec2145b1f104cc4f8e3e652cf08f3d779 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 16 Apr 2024 17:05:58 +0200 Subject: [PATCH 39/67] dont use sequential numbering --- ...ag_with_build_error.txtar => gobuild_flag_build_error.txtar} | 0 ...ag_with_parse_error.txtar => gobuild_flag_parse_error.txtar} | 0 ...{09_gno_files_whitelist_error.txtar => invalid_import.txtar} | 2 +- .../testdata/gno_transpile/{01_no_args.txtar => no_args.txtar} | 0 .../{03_gno_files_parse_error.txtar => parse_error.txtar} | 0 .../gno_transpile/{02_empty_dir.txtar => valid_empty_dir.txtar} | 0 .../{04_valid_gno_files.txtar => valid_gno_files.txctar} | 0 .../{06_build_flag.txtar => valid_gobuild_flag.txtar} | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename gnovm/cmd/gno/testdata/gno_transpile/{07_build_flag_with_build_error.txtar => gobuild_flag_build_error.txtar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{08_build_flag_with_parse_error.txtar => gobuild_flag_parse_error.txtar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{09_gno_files_whitelist_error.txtar => invalid_import.txtar} (86%) rename gnovm/cmd/gno/testdata/gno_transpile/{01_no_args.txtar => no_args.txtar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{03_gno_files_parse_error.txtar => parse_error.txtar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{02_empty_dir.txtar => valid_empty_dir.txtar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{04_valid_gno_files.txtar => valid_gno_files.txctar} (100%) rename gnovm/cmd/gno/testdata/gno_transpile/{06_build_flag.txtar => valid_gobuild_flag.txtar} (100%) diff --git a/gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/07_build_flag_with_build_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/08_build_flag_with_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar similarity index 86% rename from gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar index 4aafc55ef26..0c51012feb7 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/09_gno_files_whitelist_error.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar @@ -1,4 +1,4 @@ -# Run gno transpile with gno files with whitelist errors +# Run gno transpile with gno files with an invalid import path ! gno transpile . diff --git a/gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar b/gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/01_no_args.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar b/gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/03_gno_files_parse_error.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/02_empty_dir.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gno_files.txctar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/04_valid_gno_files.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_gno_files.txctar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/06_build_flag.txtar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar From ead58773a982505c7f4c160a24bd25834549dcb1 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 16 Apr 2024 21:39:46 +0200 Subject: [PATCH 40/67] fix tests, add more tests, cry in go build --- .../gno_transpile/valid_gobuild_file.txtar | 31 ++++++ .../gno_transpile/valid_gobuild_flag.txtar | 26 ++++- .../gno_transpile/valid_transpile_file.txtar | 65 +++++++++++++ ...s.txctar => valid_transpile_package.txtar} | 0 .../gno_transpile/valid_transpile_tree.txtar | 94 +++++++++++++++++++ gnovm/cmd/gno/transpile.go | 54 +++++------ gnovm/cmd/gno/util.go | 8 +- gnovm/pkg/integration/gno.go | 2 + 8 files changed, 243 insertions(+), 37 deletions(-) create mode 100644 gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar create mode 100644 gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar rename gnovm/cmd/gno/testdata/gno_transpile/{valid_gno_files.txctar => valid_transpile_package.txtar} (100%) create mode 100644 gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar new file mode 100644 index 00000000000..40bb1ecb98a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar @@ -0,0 +1,31 @@ +# Run gno transpile with -gobuild flag on an individual file + +gno transpile -gobuild -v main.gno + +! stdout .+ +cmp stderr stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden + +-- stderr.golden -- +main.gno +main.gno [build] +-- main.gno -- +package main + +func main() { + var x = 1 + _=x +} +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + var x = 1 + _ = x +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar index 110d04959c0..fde49b03a23 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar @@ -1,13 +1,20 @@ # Run gno transpile with -gobuild flag -gno transpile -gobuild . +gno transpile -gobuild -v . ! stdout .+ -! stderr .+ +cmp stderr stderr.golden +# The test file will be excluded from transpilation unless we pass it explicitly. cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden +-- stderr.golden -- +. +sub +. [build] +sub [build] -- main.gno -- package main @@ -15,8 +22,6 @@ func main() { var x = 1 _=x } --- sub/sub.gno -- -package sub -- main.gno.gen.go.golden -- // Code generated by github.com/gnolang/gno. DO NOT EDIT. @@ -29,6 +34,19 @@ func main() { var x = 1 _ = x } +-- main_test.gno -- +package main + +import ( + "testing" + "badimport" +) + +func TestMain(t *testing.T) { + badimport.DoesNotExist() +} +-- sub/sub.gno -- +package sub -- sub/sub.gno.gen.go.golden -- // Code generated by github.com/gnolang/gno. DO NOT EDIT. diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar new file mode 100644 index 00000000000..86cc6f12f7a --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar @@ -0,0 +1,65 @@ +# Run gno transpile with an individual file. + +# Running transpile on the current directory should only precompile +# main.gno. +gno transpile -v . + +! stdout .+ +stderr ^\.$ + +exists main.gno.gen.go +! exists .hello_test.gno.gen_test.go +rm main.gno.gen.go + +# Running it using individual filenames should precompile hello_test.gno, as well. +gno transpile -v main.gno hello_test.gno + +! stdout .+ +cmp stderr transpile-files-stderr.golden + +cmp main.gno.gen.go main.gno.gen.go.golden +cmp .hello_test.gno.gen_test.go .hello_test.gno.gen_test.go.golden + +-- transpile-files-stderr.golden -- +main.gno +hello_test.gno +-- main.gno -- +package main + +func main() { + println("hello") +} + +-- hello_test.gno -- +package main + +import "std" + +func hello() { + std.AssertOriginCall() +} + +-- main.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line main.gno:1:1 +package main + +func main() { + println("hello") +} +-- .hello_test.gno.gen_test.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno && test + +//line hello_test.gno:1:1 +package main + +import "github.com/gnolang/gno/gnovm/stdlibs/std" + +func hello() { + std.AssertOriginCall(nil) +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gno_files.txctar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_gno_files.txctar rename to gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar new file mode 100644 index 00000000000..a765ab5093b --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar @@ -0,0 +1,94 @@ +# Run gno transpile with dependencies +env GNOROOT=$WORK + +gno transpile -v ./examples + +! stdout .+ +cmpenv stderr stderr.golden + +! exists examples/gno.land/r/question/question.gno.gen.go +cmp examples/gno.land/r/answer/answer.gno.gen.go examples/gno.land/r/answer/answer.gno.gen.go.golden +cmp examples/gno.land/r/answer/anti_answer.gno.gen.go examples/gno.land/r/answer/anti_answer.gno.gen.go.golden +cmp gnovm/stdlibs/math/math.gno.gen.go gnovm/stdlibs/math/math.gno.gen.go.golden + +-- stderr.golden -- +examples/gno.land/r/answer +$WORK/gnovm/stdlibs/math +examples/gno.land/r/question (skipped, gno.mod marks module as draft) +-- examples/gno.land/r/question/gno.mod -- +// Draft + +module gno.land/r/question + +-- examples/gno.land/r/question/question.gno -- +package question + +func Question() string { + return "What is the answer to Life, The Universe and Everything?" + invalid syntax +} + +-- examples/gno.land/r/answer/answer.gno -- +package answer + +import "math" + +func Answer() int { + return math.Sqrt(1764) +} + +-- examples/gno.land/r/answer/answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func Answer() int { + return math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno -- +package answer + +import "math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/anti_answer.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line anti_answer.gno:1:1 +package answer + +import "github.com/gnolang/gno/gnovm/stdlibs/math" + +func AntiAnswer() int { + return -math.Sqrt(1764) +} +-- examples/gno.land/r/answer/gno.mod -- +module gno.land/r/answer + +-- gnovm/stdlibs/math/math.gno -- +package math + +func Sqrt(i int) int { + return 42 +} + +-- gnovm/stdlibs/math/math.gno.gen.go.golden -- +// Code generated by github.com/gnolang/gno. DO NOT EDIT. + +//go:build gno + +//line math.gno:1:1 +package math + +func Sqrt(i int) int { + return 42 +} diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index d3448c40dec..3c6d7d696f5 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -14,7 +14,6 @@ import ( "path/filepath" "regexp" "slices" - "sort" "strconv" "strings" @@ -292,7 +291,7 @@ func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { goBinary := cfg.goBinary if verbose { - fmt.Fprintf(os.Stderr, "%s [build]\n", fileOrPkg) + fmt.Fprintf(os.Stderr, "%s [build]\n", filepath.Clean(fileOrPkg)) } return buildTranspiledPackage(fileOrPkg, goBinary) @@ -324,45 +323,38 @@ func buildTranspiledPackage(fileOrPkg, goBinary string) error { // TODO: find the nearest go.mod file, chdir in the same folder, trim prefix? // TODO: temporarily create an in-memory go.mod or disable go modules for gno? // TODO: ignore .go files that were not generated from gno? - // TODO: automatically transpile if not yet done. - - files := []string{} info, err := os.Stat(fileOrPkg) if err != nil { return fmt.Errorf("invalid file or package path %s: %w", fileOrPkg, err) } + var ( + target string + chdir string + ) if !info.IsDir() { - file := fileOrPkg - files = append(files, file) + dstFilename, _ := transpiler.TranspiledFilenameAndTags(fileOrPkg) + // Makes clear to go compiler that this is a relative path, + // rather than a path to a package/module. + // can't use filepath.Join as it cleans its results. + target = filepath.Dir(fileOrPkg) + string(filepath.Separator) + dstFilename } else { - pkgDir := fileOrPkg - goGlob := filepath.Join(pkgDir, "*.go") - goMatches, err := filepath.Glob(goGlob) - if err != nil { - return fmt.Errorf("glob %s: %w", goGlob, err) - } - for _, goMatch := range goMatches { - switch { - case strings.HasPrefix(goMatch, "."): // skip - case strings.HasSuffix(goMatch, "_filetest.go"): // skip - case strings.HasSuffix(goMatch, "_filetest.gno.gen.go"): // skip - case strings.HasSuffix(goMatch, "_test.go"): // skip - case strings.HasSuffix(goMatch, "_test.gno.gen.go"): // skip - default: - if !filepath.IsAbs(pkgDir) { - // Makes clear to go compiler that this is a relative path, - // rather than a path to a package/module. - // can't use filepath.Join as it cleans its results. - goMatch = "." + string(filepath.Separator) + goMatch - } - files = append(files, goMatch) - } + target = fileOrPkg + if filepath.IsAbs(target) { + // Go does not allow building packages using absolute paths. + // To circumvent this, we use the -C flag to chdir into the right + // directory, then run `go build .` + chdir = target + target = "." } } - sort.Strings(files) - args := append([]string{"build", "-tags=gno"}, files...) + // pre-alloc max 5 args + args := append(make([]string, 0, 5), "build") + if chdir != "" { + args = append(args, "-C", chdir) + } + args = append(args, "-tags=gno", target) cmd := exec.Command(goBinary, args...) out, err := cmd.CombinedOutput() if _, ok := err.(*exec.ExitError); ok { diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index c958768a6cb..72628d49e86 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -29,7 +29,11 @@ func gnoPackagesFromArgs(args []string) ([]string, error) { return nil, fmt.Errorf("invalid file or package path: %w", err) } if !info.IsDir() { - paths = append(paths, arg) + if filepath.IsAbs(arg) { + paths = append(paths, arg) + } else { + paths = append(paths, "."+string(filepath.Separator)+arg) + } } else { // if the passed arg is a dir, then we'll recursively walk the dir // and look for directories containing at least one .gno file. @@ -57,7 +61,7 @@ func gnoPackagesFromArgs(args []string) ([]string, error) { // cannot use path.Join or filepath.Join, because we need // to ensure that ./ is the prefix to pass to go build. // if not absolute. - pkg = "./" + parentDir + pkg = "." + string(filepath.Separator) + parentDir } paths = append(paths, pkg) diff --git a/gnovm/pkg/integration/gno.go b/gnovm/pkg/integration/gno.go index ee0216fa9e8..a389b6a9b24 100644 --- a/gnovm/pkg/integration/gno.go +++ b/gnovm/pkg/integration/gno.go @@ -68,6 +68,8 @@ func SetupGno(p *testscript.Params, buildDir string) error { return fmt.Errorf("unable to create temporary home directory: %w", err) } env.Setenv("HOME", home) + // Avoids go command printing errors relating to lack of go.mod. + env.Setenv("GO111MODULE", "off") // Cleanup home folder env.Defer(func() { os.RemoveAll(home) }) From 261b57630bf841ed1c7abe9d7894c2088d554d70 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 16 Apr 2024 22:07:14 +0200 Subject: [PATCH 41/67] very confused by this gnomod code --- gnovm/cmd/gno/transpile_test.go | 2 ++ gnovm/pkg/gnomod/file.go | 3 +-- gnovm/pkg/gnomod/gnomod.go | 25 ++++++++----------------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 8425aeb773d..a64cadbb639 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -80,6 +80,8 @@ pkg/file.gno:60:20: ugly error`, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := parseGoBuildErrors(tt.output) assert.Equal(t, tt.expectedError, err) diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index 744ef5faf7f..ce88e5d5287 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -184,8 +184,7 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { continue } - if strings.HasPrefix(path, transpiler.ImportPrefix) { - path = strings.TrimPrefix(path, transpiler.ImportPrefix+"/examples/") + if !transpiler.IsStdlib(path) { modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 3f8636ea2f6..36f2eaf11fa 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -3,6 +3,8 @@ package gnomod import ( "errors" "fmt" + "go/parser" + gotoken "go/token" "os" "path/filepath" "strings" @@ -63,23 +65,12 @@ func writePackage(remote, basePath, pkgPath string) (requirements []string, err } else { // Is File // Transpile and write generated go file - if strings.HasSuffix(fileName, ".gno") { - filePath := filepath.Join(basePath, pkgPath) - targetFilename, _ := transpiler.TranspiledFilenameAndTags(filePath) - transpileRes, err := transpiler.Transpile(string(res.Data), "", fileName) - if err != nil { - return nil, fmt.Errorf("transpile: %w", err) - } - - for _, i := range transpileRes.Imports { - requirements = append(requirements, i.Path.Value) - } - - targetFileNameWithPath := filepath.Join(basePath, dirPath, targetFilename) - err = os.WriteFile(targetFileNameWithPath, []byte(transpileRes.Translated), 0o644) - if err != nil { - return nil, fmt.Errorf("writefile %q: %w", targetFileNameWithPath, err) - } + file, err := parser.ParseFile(gotoken.NewFileSet(), fileName, res.Data, parser.ImportsOnly) + if err != nil { + return nil, fmt.Errorf("parse gno file: %w", err) + } + for _, i := range file.Imports { + requirements = append(requirements, i.Path.Value) } // Write file From e2e2e3a6ef29121e5822a51f7ac2a9592124fefa Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 12:29:32 +0200 Subject: [PATCH 42/67] add tests for -output --- .../gno_transpile/valid_gobuild_flag.txtar | 16 +++++++ .../gno_transpile/valid_output_flag.txtar | 41 +++++++++++++++++ .../gno_transpile/valid_output_gobuild.txtar | 44 ++++++++++++++++++ gnovm/cmd/gno/transpile.go | 21 +++++---- gnovm/cmd/gno/transpile_test.go | 13 +++++- gnovm/cmd/gno/util.go | 23 +++------- gnovm/cmd/gno/util_test.go | 45 +++++++++++++++++++ 7 files changed, 178 insertions(+), 25 deletions(-) create mode 100644 gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar create mode 100644 gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar index fde49b03a23..2eacfb9de60 100644 --- a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar @@ -6,6 +6,17 @@ gno transpile -gobuild -v . cmp stderr stderr.golden # The test file will be excluded from transpilation unless we pass it explicitly. +cmp main.gno.gen.go main.gno.gen.go.golden +! exists .main_test.gno.gen_test.go +cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden +rm mai.gno.gen.gosub/sub.gno.gen.go + +# Re-try, but use an absolute path. +gno transpile -gobuild -v $WORK + +! stdout .+ +cmpenv stderr stderr2.golden + cmp main.gno.gen.go main.gno.gen.go.golden ! exists .main_test.gno.gen_test.go cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden @@ -15,6 +26,11 @@ cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden sub . [build] sub [build] +-- stderr2.golden -- +$WORK +$WORK/sub +$WORK [build] +$WORK/sub [build] -- main.gno -- package main diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar new file mode 100644 index 00000000000..b1a63890f46 --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar @@ -0,0 +1,41 @@ +# Run gno transpile with valid gno files, using the -output flag. + +gno transpile -v -output directory/hello/ . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +-- stderr2.golden -- +$WORK +-- stderr3.golden -- +.. +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar new file mode 100644 index 00000000000..3540e865f3e --- /dev/null +++ b/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar @@ -0,0 +1,44 @@ +# Run gno transpile with valid gno files, using the -output and -gobuild flags together. + +gno transpile -v -output directory/hello/ -gobuild . +! stdout .+ +cmp stderr stderr1.golden + +exists directory/hello/main.gno.gen.go +! exists main.gno.gen.go +rm directory + +# Try running using the absolute path to the directory. +gno transpile -v -output directory/hello -gobuild $WORK +! stdout .+ +cmpenv stderr stderr2.golden + +exists directory/hello$WORK/main.gno.gen.go +! exists directory/hello/main.gno.gen.go +rm directory + +# Try running in subdirectory, using a "relative non-local path." (ie. has "../") +mkdir subdir +cd subdir +gno transpile -v -output hello -gobuild .. +! stdout .+ +cmpenv stderr ../stderr3.golden + +exists hello$WORK/main.gno.gen.go +! exists main.gno.gen.go + +-- stderr1.golden -- +. +directory/hello [build] +-- stderr2.golden -- +$WORK +directory/hello$WORK [build] +-- stderr3.golden -- +.. +hello$WORK [build] +-- main.gno -- +package main + +func main() { + println("hello") +} diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 3c6d7d696f5..c090c30894d 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -173,6 +173,12 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { if slices.Contains(opts.skipped, pkgPath) { continue } + if cfg.output != "." { + pkgPath, err = ResolvePath(cfg.output, pkgPath) + if err != nil { + return fmt.Errorf("resolve output path: %w", err) + } + } err := goBuildFileOrPkg(pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList @@ -339,14 +345,13 @@ func buildTranspiledPackage(fileOrPkg, goBinary string) error { // can't use filepath.Join as it cleans its results. target = filepath.Dir(fileOrPkg) + string(filepath.Separator) + dstFilename } else { - target = fileOrPkg - if filepath.IsAbs(target) { - // Go does not allow building packages using absolute paths. - // To circumvent this, we use the -C flag to chdir into the right - // directory, then run `go build .` - chdir = target - target = "." - } + // Go does not allow building packages using absolute paths, and requires + // relative paths to always be prefixed with `./` (because the argument + // go expects are import paths, not directories). + // To circumvent this, we use the -C flag to chdir into the right + // directory, then run `go build .` + chdir = fileOrPkg + target = "." } // pre-alloc max 5 args diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index a64cadbb639..0132c9c6171 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -3,6 +3,7 @@ package main import ( "go/scanner" "go/token" + "strconv" "testing" "github.com/rogpeppe/go-internal/testscript" @@ -77,6 +78,16 @@ pkg/file.gno:60:20: ugly error`, }, }, }, + { + name: "line parse error", + output: `main.gno:9000000000000000000000000000000000000000000000000000:11: error`, + expectedError: strconv.ErrRange, + }, + { + name: "column parse error", + output: `main.gno:1:9000000000000000000000000000000000000000000000000000: error`, + expectedError: strconv.ErrRange, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -84,7 +95,7 @@ pkg/file.gno:60:20: ugly error`, err := parseGoBuildErrors(tt.output) - assert.Equal(t, tt.expectedError, err) + assert.ErrorIs(t, err, tt.expectedError) }) } } diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 72628d49e86..e3231f132ff 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -169,25 +169,16 @@ func makeTestGoMod(path string, packageName string, goversion string) error { } // ResolvePath determines the path where to place output files. -// dstPath is the desired output path by the gno program. output is the output -// directory provided by the user. +// output is the output directory provided by the user. +// dstPath is the desired output path by the gno program. // -// If dstPath is absolute, it will be simply joined together with output. -// If dstPath is local, the output will be joined together with the relative -// path to reach dstPath. // If dstPath is relative non-local path (ie. contains ../), the dstPath will -// be made absolute and joined with output +// be made absolute and joined with output. // -// Working directory: /home/gno -// ResolvePath("transpile-result", "./examples/test/test1.gno.gen.go") -// -> transpile-result/examples/test/test1.gen.go -// ResolvePath("/transpile-result", "./examples/test/test1.gno.gen.go") -// -> /transpile-result/examples/test/test1.gen.go -// ResolvePath("/transpile-result", "/home/gno/examples/test/test1.gno.gen.go") -// -> /transpile-result/home/gno/examples/test/test1.gen.go -// ResolvePath("result", "../jae/hello") -// -> result/home/jae/hello -func ResolvePath(output string, dstPath string) (string, error) { +// Otherwise, the result is simply filepath.Join(output, dstPath). +// +// See related test for examples. +func ResolvePath(output, dstPath string) (string, error) { if filepath.IsAbs(dstPath) || filepath.IsLocal(dstPath) { return filepath.Join(output, dstPath), nil diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go index 9e9659bfe4f..ae2f3bc5123 100644 --- a/gnovm/cmd/gno/util_test.go +++ b/gnovm/cmd/gno/util_test.go @@ -295,3 +295,48 @@ func createGnoPackages(t *testing.T, tmpDir string) { } } } + +func TestResolvePath(t *testing.T) { + if os.PathSeparator != '/' { + t.Skip("ResolvePath test is only written of UNIX-like filesystems") + } + wd, err := os.Getwd() + require.NoError(t, err) + tt := []struct { + output string + dstPath string + result string + }{ + { + "transpile-result", + "./examples/test/test1.gno.gen.go", + "transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "./examples/test/test1.gno.gen.go", + "/transpile-result/examples/test/test1.gno.gen.go", + }, + { + "/transpile-result", + "/home/gno/examples/test/test1.gno.gen.go", + "/transpile-result/home/gno/examples/test/test1.gno.gen.go", + }, + { + "result", + "../hello", + filepath.Join("result", filepath.Join(wd, "../hello")), + }, + } + + for _, tc := range tt { + res, err := ResolvePath(tc.output, tc.dstPath) + // ResolvePath should error only in case we can't get the abs path; + // so never in normal conditions. + require.NoError(t, err) + assert.Equal(t, + tc.result, res, + "unexpected result of ResolvePath(%q, %q)", tc.output, tc.dstPath, + ) + } +} From dd370ae77e697fc5a5ed36b22630430406e77376 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 12:39:18 +0200 Subject: [PATCH 43/67] move IsStdlib to gnolang --- gnovm/pkg/gnolang/helpers.go | 22 +++++++++++++++++ .../{realm_test.go => helpers_test.go} | 24 +++++++++++++++++++ gnovm/pkg/gnolang/nodes.go | 12 ++++++---- gnovm/pkg/gnolang/realm.go | 10 -------- gnovm/pkg/gnomod/file.go | 4 ++-- gnovm/pkg/gnomod/gnomod.go | 4 ++-- gnovm/pkg/transpiler/transpiler.go | 21 +++++----------- gnovm/pkg/transpiler/transpiler_test.go | 24 ------------------- 8 files changed, 64 insertions(+), 57 deletions(-) rename gnovm/pkg/gnolang/{realm_test.go => helpers_test.go} (57%) diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index b163b6a52a7..478edb80d7f 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -7,6 +7,28 @@ import ( "strings" ) +// ---------------------------------------- +// Functions centralizing definitions + +// RealmPathPrefix is the prefix used to identify pkgpaths which are meant to +// be realms and as such to have their state persisted. This is used by [IsRealmPath]. +const RealmPathPrefix = "gno.land/r/" + +// IsRealmPath determines whether the given pkgpath is for a realm, and as such +// should persist the global state. +func IsRealmPath(pkgPath string) bool { + return strings.HasPrefix(pkgPath, RealmPathPrefix) +} + +// IsStdlib determines whether s is a pkgpath for a standard library. +func IsStdlib(s string) bool { + // NOTE(morgan): this is likely to change in the future as we add support for + // IBC/ICS and we allow import paths to other chains. It might be good to + // (eventually) follow the same rule as Go, which is: does the first + // element of the import path contain a dot? + return !strings.HasPrefix(s, "gno.land/") +} + // ---------------------------------------- // AST Construction (Expr) // These are copied over from go-amino-x, but produces Gno ASTs. diff --git a/gnovm/pkg/gnolang/realm_test.go b/gnovm/pkg/gnolang/helpers_test.go similarity index 57% rename from gnovm/pkg/gnolang/realm_test.go rename to gnovm/pkg/gnolang/helpers_test.go index 8ad83e57f23..a1c959b4cee 100644 --- a/gnovm/pkg/gnolang/realm_test.go +++ b/gnovm/pkg/gnolang/helpers_test.go @@ -28,3 +28,27 @@ func TestIsRealmPath(t *testing.T) { ) } } + +func TestIsStdlib(t *testing.T) { + t.Parallel() + + tt := []struct { + s string + result bool + }{ + {"std", true}, + {"math", true}, + {"very/long/path/with_underscores", true}, + {"gno.land/r/demo/users", false}, + {"gno.land/hello", false}, + } + + for _, tc := range tt { + assert.Equal( + t, + tc.result, + IsStdlib(tc.s), + "IsStdlib(%q)", tc.s, + ) + } +} diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 97447cd12ae..56a299d64bb 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1131,12 +1131,16 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { } allowedFileExtensions := []string{ ".gno", - // Allows transpilation to work on stdlibs with native fns. - ".go", } - rejectedFileExtensions := []string{ - ".gen.go", + // exceptions to allowedFileExtensions + var rejectedFileExtensions []string + + if IsStdlib(pkgPath) { + // Allows transpilation to work on stdlibs with native fns. + allowedFileExtensions = append(allowedFileExtensions, ".go") + rejectedFileExtensions = []string{".gen.go"} } + list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 24b47fd17c6..0036f9a54bf 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1513,16 +1513,6 @@ func isUnsaved(oo Object) bool { return oo.GetIsNewReal() || oo.GetIsDirty() } -// RealmPathPrefix is the prefix used to identify pkgpaths which are meant to -// be realms and as such to have their state persisted. This is used by [IsRealmPath]. -const RealmPathPrefix = "gno.land/r/" - -// IsRealmPath determines whether the given pkgpath is for a realm, and as such -// should persist the global state. -func IsRealmPath(pkgPath string) bool { - return strings.HasPrefix(pkgPath, RealmPathPrefix) -} - func prettyJSON(jstr []byte) []byte { var c interface{} err := json.Unmarshal(jstr, &c) diff --git a/gnovm/pkg/gnomod/file.go b/gnovm/pkg/gnomod/file.go index ce88e5d5287..b6ee95acac8 100644 --- a/gnovm/pkg/gnomod/file.go +++ b/gnovm/pkg/gnomod/file.go @@ -17,7 +17,7 @@ import ( "path/filepath" "strings" - "github.com/gnolang/gno/gnovm/pkg/transpiler" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "golang.org/x/mod/modfile" "golang.org/x/mod/module" ) @@ -184,7 +184,7 @@ func (f *File) FetchDeps(path string, remote string, verbose bool) error { continue } - if !transpiler.IsStdlib(path) { + if !gno.IsStdlib(path) { modFile.AddNewRequire(path, "v0.0.0-latest", true) } } diff --git a/gnovm/pkg/gnomod/gnomod.go b/gnovm/pkg/gnomod/gnomod.go index 36f2eaf11fa..0effa532107 100644 --- a/gnovm/pkg/gnomod/gnomod.go +++ b/gnovm/pkg/gnomod/gnomod.go @@ -91,7 +91,7 @@ func GnoToGoMod(f File) (*File, error) { gnoModPath := GetGnoModPath() - if !transpiler.IsStdlib(f.Module.Mod.Path) { + if !gnolang.IsStdlib(f.Module.Mod.Path) { f.AddModuleStmt(transpiler.TranspileImportPath(f.Module.Mod.Path)) } @@ -103,7 +103,7 @@ func GnoToGoMod(f File) (*File, error) { } } path := f.Require[i].Mod.Path - if !transpiler.IsStdlib(path) { + if !gnolang.IsStdlib(path) { // Add dependency with a modified import path f.AddRequire(transpiler.TranspileImportPath(path), f.Require[i].Mod.Version) } diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 24665f0df27..239175e8d94 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -17,7 +17,7 @@ import ( "strings" "github.com/gnolang/gno/gnovm/pkg/gnoenv" - "github.com/gnolang/gno/gnovm/pkg/gnolang" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" "golang.org/x/tools/go/ast/astutil" ) @@ -32,21 +32,12 @@ func TranspileImportPath(s string) string { return ImportPrefix + "/" + PackageDirLocation(s) } -// IsStdlib determines whether s is a pkgpath for a standard library. -func IsStdlib(s string) bool { - // NOTE(morgan): this is likely to change in the future as we add support for - // IBC/ICS and we allow import paths to other chains. It might be good to - // (eventually) follow the same rule as Go, which is: does the first - // element of the import path contain a dot? - return !strings.HasPrefix(s, "gno.land/") -} - // PackageDirLocation provides the supposed directory of the package, relative to the root dir. // // TODO(morgan): move out, this should go in a "resolver" package. func PackageDirLocation(s string) string { switch { - case !IsStdlib(s): + case !gno.IsStdlib(s): return "examples/" + s default: return "gnovm/stdlibs/" + s @@ -186,7 +177,7 @@ func (ctx *transpileCtx) transformFile(fset *token.FileSet, f *ast.File) (*ast.F } // Create mapping - if IsStdlib(importPath) { + if gno.IsStdlib(importPath) { if importSpec.Name != nil { ctx.stdlibImports[importSpec.Name.Name] = importPath } else { @@ -246,7 +237,7 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) if !ok { break } - if stdlibs.HasMachineParam(ip, gnolang.Name(fe.Sel.Name)) { + if stdlibs.HasMachineParam(ip, gno.Name(fe.Sel.Name)) { // Because it's an import, the symbol is always exported, so no need for the // X_ prefix we add below. ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) @@ -259,8 +250,8 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) // defined scope. However, because native bindings have a narrowly defined and // controlled scope (standard libraries) this will work for our usecase. if ctx.stdlibPath != "" && - stdlibs.HasNativeBinding(ctx.stdlibPath, gnolang.Name(fe.Name)) { - if stdlibs.HasMachineParam(ctx.stdlibPath, gnolang.Name(fe.Name)) { + stdlibs.HasNativeBinding(ctx.stdlibPath, gno.Name(fe.Name)) { + if stdlibs.HasMachineParam(ctx.stdlibPath, gno.Name(fe.Name)) { ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) } if !fe.IsExported() { diff --git a/gnovm/pkg/transpiler/transpiler_test.go b/gnovm/pkg/transpiler/transpiler_test.go index 656347d4858..2a0707f7f79 100644 --- a/gnovm/pkg/transpiler/transpiler_test.go +++ b/gnovm/pkg/transpiler/transpiler_test.go @@ -11,30 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestIsStdlib(t *testing.T) { - t.Parallel() - - tt := []struct { - s string - result bool - }{ - {"std", true}, - {"math", true}, - {"very/long/path/with_underscores", true}, - {"gno.land/r/demo/users", false}, - {"gno.land/hello", false}, - } - - for _, tc := range tt { - assert.Equal( - t, - tc.result, - IsStdlib(tc.s), - "IsStdlib(%q)", tc.s, - ) - } -} - func TestTranspiledFilenameAndTags(t *testing.T) { t.Parallel() From 360dac20d79f48861dd6f62c95664644ec4e368b Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 12:45:19 +0200 Subject: [PATCH 44/67] bump limits for issue-1786 test --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index b6162dfe392..47a1aef25ff 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -5,24 +5,24 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error @@ -31,7 +31,7 @@ gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '9500 uint64' From 9ccb5ee54beb3cf3b25907b785b6ba592a33f210 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 12:45:19 +0200 Subject: [PATCH 45/67] bump limits for issue-1786 test --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index b6162dfe392..47a1aef25ff 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -5,24 +5,24 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error @@ -31,7 +31,7 @@ gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '9500 uint64' From e20ebbe0baf93f0f279f2d575bd29e1230c0b1c4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 17 Apr 2024 13:00:53 +0200 Subject: [PATCH 46/67] bump up to 4M --- gno.land/cmd/gnoland/testdata/issue-1786.txtar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/issue-1786.txtar b/gno.land/cmd/gnoland/testdata/issue-1786.txtar index 47a1aef25ff..44ea17674c9 100644 --- a/gno.land/cmd/gnoland/testdata/issue-1786.txtar +++ b/gno.land/cmd/gnoland/testdata/issue-1786.txtar @@ -5,24 +5,24 @@ loadpkg gno.land/r/demo/wugnot gnoland start # add contract -gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/demo/proxywugnot -gas-fee 1000000ugnot -gas-wanted 4000000 -broadcast -chainid=tendermint_test test1 stdout OK! # approve wugnot to `proxywugnot ≈ g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3` -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func Approve -args "g1fndyg0we60rdfchyy5dwxzkfmhl5u34j932rg3" -args 10000 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # send 10000ugnot to `proxywugnot` to wrap it -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot --send "10000ugnot" -func ProxyWrap -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '10000 uint64' # unwrap 500 wugnot -gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args 500 -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 # XXX without patching anything it will panic # panic msg: insufficient coins error @@ -31,7 +31,7 @@ gnokey maketx call -pkgpath gno.land/r/demo/proxywugnot -func ProxyUnwrap -args # check user's wugnot balance -gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 3000000 -broadcast -chainid=tendermint_test test1 +gnokey maketx call -pkgpath gno.land/r/demo/wugnot -func BalanceOf -args "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 stdout OK! stdout '9500 uint64' From 66fb953a31eb4da2464f97fcc1f4d2e802213ebf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 6 May 2024 21:00:40 +0200 Subject: [PATCH 47/67] changes from code review --- gno.land/pkg/sdk/vm/keeper.go | 4 ++-- gnovm/pkg/gnolang/go2gno_test.go | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gno.land/pkg/sdk/vm/keeper.go b/gno.land/pkg/sdk/vm/keeper.go index e3737de7216..e7757235020 100644 --- a/gno.land/pkg/sdk/vm/keeper.go +++ b/gno.land/pkg/sdk/vm/keeper.go @@ -161,7 +161,7 @@ func (vm *VMKeeper) AddPackage(ctx sdk.Context, msg MsgAddPackage) (err error) { } // Validate Gno syntax and type check. - if err := gno.TypeCheckMemPackage(memPkg, store); err != nil { + if err := gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { return ErrTypeCheck(err) } @@ -342,7 +342,7 @@ func (vm *VMKeeper) Run(ctx sdk.Context, msg MsgRun) (res string, err error) { } // Validate Gno syntax and type check. - if err = gno.TypeCheckMemPackage(memPkg, store); err != nil { + if err = gno.TypeCheckMemPackage(memPkg, gnostore); err != nil { return "", ErrTypeCheck(err) } diff --git a/gnovm/pkg/gnolang/go2gno_test.go b/gnovm/pkg/gnolang/go2gno_test.go index 0cfe7610bd8..995ca3e8c0e 100644 --- a/gnovm/pkg/gnolang/go2gno_test.go +++ b/gnovm/pkg/gnolang/go2gno_test.go @@ -51,6 +51,8 @@ func (mpg mockPackageGetterCounts) GetMemPackage(path string) *std.MemPackage { } func TestTypeCheckMemPackage(t *testing.T) { + t.Parallel() + // if len(ss) > 0, then multierr.Errors must decompose it in errors, and // each error in order must contain the associated string. errContains := func(s0 string, ss ...string) func(*testing.T, error) { @@ -318,7 +320,10 @@ func TestTypeCheckMemPackage(t *testing.T) { }) for _, tc := range tt { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := TypeCheckMemPackage(tc.pkg, tc.getter) if tc.check == nil { assert.NoError(t, err) From 38cba04b121c40072eed25ece4e11062fbea9f3a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 10:10:19 +0200 Subject: [PATCH 48/67] fix gas numbers --- gno.land/pkg/sdk/vm/gas_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gno.land/pkg/sdk/vm/gas_test.go b/gno.land/pkg/sdk/vm/gas_test.go index 75d13aa6c5d..9f4ae1a6678 100644 --- a/gno.land/pkg/sdk/vm/gas_test.go +++ b/gno.land/pkg/sdk/vm/gas_test.go @@ -86,7 +86,7 @@ func TestAddPkgDeliverTxFailed(t *testing.T) { gasDeliver := gctx.GasMeter().GasConsumed() assert.False(t, res.IsOK()) - assert.Equal(t, int64(17989), gasDeliver) + assert.Equal(t, int64(2231), gasDeliver) } // Not enough gas for a failed transaction. @@ -98,7 +98,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { ctx = ctx.WithMode(sdk.RunTxModeDeliver) simulate = false - tx.Fee.GasWanted = 17988 + tx.Fee.GasWanted = 2230 gctx := auth.SetGasMeter(simulate, ctx, tx.Fee.GasWanted) var res sdk.Result @@ -116,7 +116,7 @@ func TestAddPkgDeliverTxFailedNoGas(t *testing.T) { assert.True(t, abort) assert.False(t, res.IsOK()) gasCheck := gctx.GasMeter().GasConsumed() - assert.Equal(t, int64(17989), gasCheck) + assert.Equal(t, int64(2231), gasCheck) } else { t.Errorf("should panic") } From bee0f302f0da38264c3a1e886106ad9134ab1804 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 16:31:09 +0200 Subject: [PATCH 49/67] add transaction simulation in gnokey --- .../cmd/gnoland/testdata/addpkg_invalid.txtar | 2 +- .../gnoland/testdata/gnokey_simulate.txtar | 93 +++++++++++++++++++ tm2/pkg/crypto/keys/client/broadcast.go | 19 +++- tm2/pkg/crypto/keys/client/maketx.go | 41 +++++++- tm2/pkg/crypto/keys/client/query.go | 2 +- 5 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar diff --git a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar index e0395fb479d..5cfd48bf2ea 100644 --- a/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar +++ b/gno.land/cmd/gnoland/testdata/addpkg_invalid.txtar @@ -6,7 +6,7 @@ gnoland start # add bar package located in $WORK directory as gno.land/r/foobar/bar ! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/foobar/bar -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -# compare render +# check error message ! stdout .+ stderr 'as string value in return statement' stderr '"std" imported and not used' diff --git a/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar new file mode 100644 index 00000000000..dab238a6122 --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/gnokey_simulate.txtar @@ -0,0 +1,93 @@ +# test for gnokey maketx -simulate options, and how they return any errors + +loadpkg gno.land/r/hello $WORK/hello + +# start a new node +gnoland start + +# Initial state: assert that sequence == 0. +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' + +# attempt adding the "test" package. +# the package has a syntax error; simulation should catch this ahead of time and prevent the tx. +# -simulate test +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' +# -simulate only +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "0"' +# -simulate skip +! gnokey maketx addpkg -pkgdir $WORK/test -pkgpath gno.land/r/test -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "1"' + +# attempt calling hello.SetName correctly. +# -simulate test and skip should do it successfully, -simulate only should not. +# -simulate test +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args John -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "2"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, John!' +# -simulate only +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args Paul -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "2"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, John!' +# -simulate skip +gnokey maketx call -pkgpath gno.land/r/hello -func SetName -args George -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' + +# attempt calling hello.Grumpy (always panics). +# all calls should fail, however -test skip should increase the account sequence. +# none should change the name (ie. panic rollbacks). +# -simulate test +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate test test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' +# -simulate only +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate only test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "3"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' +# -simulate skip +! gnokey maketx call -pkgpath gno.land/r/hello -func Grumpy -gas-wanted 2000000 -gas-fee 1000000ugnot -broadcast -chainid tendermint_test -simulate skip test1 +gnokey query auth/accounts/$USER_ADDR_test1 +stdout '"sequence": "4"' +gnokey query vm/qeval --data "gno.land/r/hello\nHello()" +stdout 'Hello, George!' + +-- test/test.gno -- +package test + +func Render(path string) string { + return 89 +} + +-- hello/hello.gno -- +package hello + +var name = "Ringo" + +func SetName(newName string) { + name = newName +} + +func Hello() string { + return "Hello, " + name + "!" +} + +func Grumpy() string { + name = "SCOUNDREL" + panic("YOU MAY NOT GREET ME, " + name) +} diff --git a/tm2/pkg/crypto/keys/client/broadcast.go b/tm2/pkg/crypto/keys/client/broadcast.go index 423714b2141..c088c63d19f 100644 --- a/tm2/pkg/crypto/keys/client/broadcast.go +++ b/tm2/pkg/crypto/keys/client/broadcast.go @@ -21,6 +21,10 @@ type BroadcastCfg struct { // internal tx *std.Tx + // Set by SignAndBroadcastHandler, similar to DryRun. + // If true, simulation is attempted but not printed; + // the result is only returned in case of an error. + testSimulate bool } func NewBroadcastCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command { @@ -81,6 +85,8 @@ func execBroadcast(cfg *BroadcastCfg, args []string, io commands.IO) error { io.Println("OK!") io.Println("GAS WANTED:", res.DeliverTx.GasWanted) io.Println("GAS USED: ", res.DeliverTx.GasUsed) + io.Println("HEIGHT: ", res.Height) + io.Println("EVENTS: ", string(res.DeliverTx.EncodeEvents())) } return nil } @@ -91,7 +97,7 @@ func BroadcastHandler(cfg *BroadcastCfg) (*ctypes.ResultBroadcastTxCommit, error } remote := cfg.RootCfg.Remote - if remote == "" || remote == "y" { + if remote == "" { return nil, errors.New("missing remote url") } @@ -105,8 +111,15 @@ func BroadcastHandler(cfg *BroadcastCfg) (*ctypes.ResultBroadcastTxCommit, error return nil, err } - if cfg.DryRun { - return SimulateTx(cli, bz) + // Both for DryRun and testSimulate, we perform simulation. + // However, DryRun always returns here, while in case of success + // testSimulate continues onto broadcasting the transaction. + if cfg.DryRun || cfg.testSimulate { + res, err := SimulateTx(cli, bz) + hasError := err != nil || res.CheckTx.IsErr() || res.DeliverTx.IsErr() + if cfg.DryRun || hasError { + return res, err + } } bres, err := cli.BroadcastTxCommit(bz) diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 603be59396c..8410804665d 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -20,7 +20,25 @@ type MakeTxCfg struct { Memo string Broadcast bool - ChainID string + // Valid options are SimulateTest, SimulateSkip or SimulateOnly. + Simulate string + ChainID string +} + +// These are the valid options for MakeTxConfig.Simulate. +const ( + SimulateTest = "test" + SimulateSkip = "skip" + SimulateOnly = "only" +) + +func (m *MakeTxCfg) Validate() error { + switch m.Simulate { + case SimulateTest, SimulateSkip, SimulateOnly: + default: + return fmt.Errorf("invalid simulate option: %q", m.Simulate) + } + return nil } func NewMakeTxCmd(rootCfg *BaseCfg, io commands.IO) *commands.Command { @@ -71,14 +89,24 @@ func (c *MakeTxCfg) RegisterFlags(fs *flag.FlagSet) { &c.Broadcast, "broadcast", false, - "sign and broadcast", + "sign, simulate and broadcast", + ) + + fs.StringVar( + &c.Simulate, + "simulate", + "test", + `select how to simulate the transaction (only useful with --broadcast); valid options are + - test: attempts simulating the transaction, and if successful performs broadcasting (default) + - skip: avoids performing transaction simulation + - only: avoids broadcasting transaction (ie. dry run)`, ) fs.StringVar( &c.ChainID, "chainid", "dev", - "chainid to sign for (only useful if --broadcast)", + "chainid to sign for (only useful with --broadcast)", ) } @@ -139,6 +167,9 @@ func SignAndBroadcastHandler( bopts := &BroadcastCfg{ RootCfg: baseopts, tx: &tx, + + DryRun: cfg.Simulate == SimulateOnly, + testSimulate: cfg.Simulate == SimulateTest, } return BroadcastHandler(bopts) @@ -150,6 +181,10 @@ func ExecSignAndBroadcast( tx std.Tx, io commands.IO, ) error { + if err := cfg.Validate(); err != nil { + return err + } + baseopts := cfg.RootCfg // query account diff --git a/tm2/pkg/crypto/keys/client/query.go b/tm2/pkg/crypto/keys/client/query.go index e44bb796b9d..f4b65adebc0 100644 --- a/tm2/pkg/crypto/keys/client/query.go +++ b/tm2/pkg/crypto/keys/client/query.go @@ -91,7 +91,7 @@ func execQuery(cfg *QueryCfg, args []string, io commands.IO) error { func QueryHandler(cfg *QueryCfg) (*ctypes.ResultABCIQuery, error) { remote := cfg.RootCfg.Remote - if remote == "" || remote == "y" { + if remote == "" { return nil, errors.New("missing remote url") } From 3a82a98e85a9649706c8d60f3fe0284e36f7e576 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 17:10:24 +0200 Subject: [PATCH 50/67] fixup lint --- tm2/pkg/crypto/keys/client/maketx.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tm2/pkg/crypto/keys/client/maketx.go b/tm2/pkg/crypto/keys/client/maketx.go index 8410804665d..2afccf9141c 100644 --- a/tm2/pkg/crypto/keys/client/maketx.go +++ b/tm2/pkg/crypto/keys/client/maketx.go @@ -32,11 +32,11 @@ const ( SimulateOnly = "only" ) -func (m *MakeTxCfg) Validate() error { - switch m.Simulate { +func (c *MakeTxCfg) Validate() error { + switch c.Simulate { case SimulateTest, SimulateSkip, SimulateOnly: default: - return fmt.Errorf("invalid simulate option: %q", m.Simulate) + return fmt.Errorf("invalid simulate option: %q", c.Simulate) } return nil } From 39ae304e2fa930aa98523e430f00d4a16ed561f8 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 18:01:13 +0200 Subject: [PATCH 51/67] update docs --- docs/gno-tooling/cli/gnokey.md | 49 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/gno-tooling/cli/gnokey.md b/docs/gno-tooling/cli/gnokey.md index 8abd0722229..8479e9c112d 100644 --- a/docs/gno-tooling/cli/gnokey.md +++ b/docs/gno-tooling/cli/gnokey.md @@ -171,13 +171,14 @@ gnokey maketx addpkg \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|--------------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | Defines the chainid to sign for (should only be used with `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx AddPackage Options** @@ -208,13 +209,14 @@ gnokey maketx call \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|------------------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx Call Options** @@ -246,13 +248,14 @@ gnokey maketx send \ #### **SignBroadcast Options** -| Name | Type | Description | -|--------------|---------|-------------------------------------------------------| -| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | -| `gas-fee` | String | The gas fee to pay for the transaction. | -| `memo` | String | Any descriptive text. | -| `broadcast` | Boolean | Broadcasts the transaction. | -| `chainid` | String | The chainid to sign for (implies `--broadcast`) | +| Name | Type | Description | +|--------------|---------|----------------------------------------------------------------------------------------| +| `gas-wanted` | Int64 | The maximum amount of gas to use for the transaction. | +| `gas-fee` | String | The gas fee to pay for the transaction. | +| `memo` | String | Any descriptive text. | +| `broadcast` | Boolean | Broadcasts the transaction. | +| `chainid` | String | The chainid to sign for (should only be used with `--broadcast`) | +| `simulate` | String | One of `test` (default), `skip` or `only` (should only be used with `--broadcast`)[^1] | #### **makeTx Send Options** @@ -302,3 +305,7 @@ Broadcast a signed document with the following command. ```bash gnokey broadcast {signed transaction file document} ``` + +[^1]: `only` simulates the transaction as a "dry run" (ie. without committing to + the chain), `test` performs simulation and, if successful, commits the + transaction, `skip` skips simulation entirely and commits directly. From 5aa3ce5e53636630ce93fd833d2e4a0d8cc65e11 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 7 May 2024 18:23:55 +0200 Subject: [PATCH 52/67] genstd --- gnovm/stdlibs/native.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 4b94d0731c5..a42c09a6832 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -37,6 +37,7 @@ var nativeFuncs = [...]nativeFunc{ []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, + false, func(m *gno.Machine) { b := m.LastBlock() var ( @@ -389,6 +390,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("p1"), Type: gno.X("[]string")}, }, []gno.FieldTypeExpr{}, + true, func(m *gno.Machine) { b := m.LastBlock() var ( From dbde296e6e1d1a90ee9052a190b95c47df9cfc05 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 9 May 2024 21:23:42 +0200 Subject: [PATCH 53/67] partial revert of store changes --- gno.land/pkg/sdk/vm/builtins.go | 4 +-- gnovm/pkg/gnolang/store.go | 48 ++++++++++++--------------------- gnovm/tests/imports.go | 10 +++---- 3 files changed, 24 insertions(+), 38 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 368ada6ff82..63062847e01 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: newStore, + Store: store, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index fd46df41aaa..38a5f3a50ff 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -14,9 +14,8 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested -// package does not exist. store should be used to run the machine, or otherwise -// call any methods which may call store.GetPackage, to avoid import cycles. -type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) +// package does not exist. +type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -82,7 +81,8 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. + opslog []StoreOp // for debugging and testing. + current []string // for detecting import cycles. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,29 +109,18 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } -type importerStore struct { - *defaultStore - importChain []string -} - -func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - if !isImport { - // if not an import, match behaviour to the defaultStore - return is.defaultStore.GetPackage(pkgPath, isImport) - } - // it is an import -- detect cyclic imports - if slices.Contains(is.importChain, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) - } - return is.getPackage(pkgPath, is) -} - // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - return ds.getPackage(pkgPath, importerStore{}) -} - -func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { + // detect circular imports + if isImport { + if slices.Contains(ds.current, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) + } + ds.current = append(ds.current, pkgPath) + defer func() { + ds.current = ds.current[:len(ds.current)-1] + }() + } // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -175,12 +164,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if impStore.defaultStore == nil { - // pre-allocate 16 entries to likely avoid further slice allocations. - impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} - } - impStore.importChain = append(impStore.importChain, pkgPath) - if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { + if pn, pv := ds.pkgGetter(pkgPath); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -626,6 +610,7 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. + ds.current = nil ds.SetCachePackage(Uverse()) } @@ -645,6 +630,7 @@ func (ds *defaultStore) Fork() Store { nativeStore: ds.nativeStore, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. + current: nil, } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 1a41a1d2f1d..389c6717780 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -62,7 +62,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -81,7 +81,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) // pv := pkg.NewPackage() @@ -93,7 +93,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -377,7 +377,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) if pn != nil { return } @@ -394,7 +394,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: newStore, + Store: store, }) pn, pv = m2.RunMemPackage(memPkg, true) return From f843acb5870cd6f84b9dfcc647c167a5f06a55ed Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 9 May 2024 21:29:17 +0200 Subject: [PATCH 54/67] fixup --- gnovm/pkg/gnolang/gonative_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index 42729b43699..33e687cd40a 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) From 2558b09dfae5e88e1a9ef5c54086915f0d71e07a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:29:57 +0200 Subject: [PATCH 55/67] Revert "fixup" This reverts commit f843acb5870cd6f84b9dfcc647c167a5f06a55ed. --- gnovm/pkg/gnolang/gonative_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/gonative_test.go b/gnovm/pkg/gnolang/gonative_test.go index 33e687cd40a..42729b43699 100644 --- a/gnovm/pkg/gnolang/gonative_test.go +++ b/gnovm/pkg/gnolang/gonative_test.go @@ -15,7 +15,7 @@ import ( // and the odd index items are corresponding package values. func gonativeTestStore(args ...interface{}) Store { store := NewStore(nil, nil, nil) - store.SetPackageGetter(func(pkgPath string) (*PackageNode, *PackageValue) { + store.SetPackageGetter(func(pkgPath string, _ Store) (*PackageNode, *PackageValue) { for i := 0; i < len(args)/2; i++ { pn := args[i*2].(*PackageNode) pv := args[i*2+1].(*PackageValue) From cc9d45e37a2219a2fa09a14f3ba48ce93807d42d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:30:56 +0200 Subject: [PATCH 56/67] Revert "partial revert of store changes" This reverts commit dbde296e6e1d1a90ee9052a190b95c47df9cfc05. --- gno.land/pkg/sdk/vm/builtins.go | 4 +-- gnovm/pkg/gnolang/store.go | 48 +++++++++++++++++++++------------ gnovm/tests/imports.go | 10 +++---- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/gno.land/pkg/sdk/vm/builtins.go b/gno.land/pkg/sdk/vm/builtins.go index 63062847e01..368ada6ff82 100644 --- a/gno.land/pkg/sdk/vm/builtins.go +++ b/gno.land/pkg/sdk/vm/builtins.go @@ -16,7 +16,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { // NOTE: native functions/methods added here must be quick operations, // or account for gas before operation. // TODO: define criteria for inclusion, and solve gas calculations. - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { // otherwise, built-in package value. // first, load from filepath. stdlibPath := filepath.Join(vm.stdlibsDir, pkgPath) @@ -34,7 +34,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { PkgPath: "gno.land/r/stdlibs/" + pkgPath, // PkgPath: pkgPath, Output: os.Stdout, - Store: store, + Store: newStore, }) defer m2.Release() return m2.RunMemPackage(memPkg, true) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 38a5f3a50ff..fd46df41aaa 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -14,8 +14,9 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested -// package does not exist. -type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) +// package does not exist. store should be used to run the machine, or otherwise +// call any methods which may call store.GetPackage, to avoid import cycles. +type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) @@ -81,8 +82,7 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. - current []string // for detecting import cycles. + opslog []StoreOp // for debugging and testing. } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,18 +109,29 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } +type importerStore struct { + *defaultStore + importChain []string +} + +func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { + if !isImport { + // if not an import, match behaviour to the defaultStore + return is.defaultStore.GetPackage(pkgPath, isImport) + } + // it is an import -- detect cyclic imports + if slices.Contains(is.importChain, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) + } + return is.getPackage(pkgPath, is) +} + // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - // detect circular imports - if isImport { - if slices.Contains(ds.current, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) - } - ds.current = append(ds.current, pkgPath) - defer func() { - ds.current = ds.current[:len(ds.current)-1] - }() - } + return ds.getPackage(pkgPath, importerStore{}) +} + +func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -164,7 +175,12 @@ func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if pn, pv := ds.pkgGetter(pkgPath); pv != nil { + if impStore.defaultStore == nil { + // pre-allocate 16 entries to likely avoid further slice allocations. + impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} + } + impStore.importChain = append(impStore.importChain, pkgPath) + if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") @@ -610,7 +626,6 @@ func (ds *defaultStore) ClearObjectCache() { ds.alloc.Reset() ds.cacheObjects = make(map[ObjectID]Object) // new cache. ds.opslog = nil // new ops log. - ds.current = nil ds.SetCachePackage(Uverse()) } @@ -630,7 +645,6 @@ func (ds *defaultStore) Fork() Store { nativeStore: ds.nativeStore, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. - current: nil, } ds2.SetCachePackage(Uverse()) return ds2 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 43d0969c007..d5541fb0554 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -63,7 +63,7 @@ const ( // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { - getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } @@ -84,7 +84,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, Context: ctx, }) // pkg := gno.NewPackageNode(gno.Name(memPkg.Name), memPkg.Path, nil) @@ -97,7 +97,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -381,7 +381,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + pn, pv = loadStdlib(rootDir, pkgPath, newStore, stdout) if pn != nil { return } @@ -400,7 +400,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri m2 := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", Output: stdout, - Store: store, + Store: newStore, Context: ctx, }) pn, pv = m2.RunMemPackage(memPkg, true) From 09f081c276528c25c478b16d8c5861179bf2ed4c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:35:24 +0200 Subject: [PATCH 57/67] fixup --- gnovm/pkg/gnolang/store.go | 40 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index fd46df41aaa..21a07a60276 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -82,7 +82,8 @@ type defaultStore struct { go2gnoStrict bool // if true, native->gno type conversion must be registered. // transient - opslog []StoreOp // for debugging and testing. + opslog []StoreOp // for debugging and testing. + current []string } func NewStore(alloc *Allocator, baseStore, iavlStore store.Store) *defaultStore { @@ -109,29 +110,17 @@ func (ds *defaultStore) SetPackageGetter(pg PackageGetter) { ds.pkgGetter = pg } -type importerStore struct { - *defaultStore - importChain []string -} - -func (is importerStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - if !isImport { - // if not an import, match behaviour to the defaultStore - return is.defaultStore.GetPackage(pkgPath, isImport) - } - // it is an import -- detect cyclic imports - if slices.Contains(is.importChain, pkgPath) { - panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, is.importChain)) - } - return is.getPackage(pkgPath, is) -} - // Gets package from cache, or loads it from baseStore, or gets it from package getter. func (ds *defaultStore) GetPackage(pkgPath string, isImport bool) *PackageValue { - return ds.getPackage(pkgPath, importerStore{}) -} - -func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *PackageValue { + if isImport { + if slices.Contains(ds.current, pkgPath) { + panic(fmt.Sprintf("import cycle detected: %q (through %v)", pkgPath, ds.current)) + } + ds.current = append(ds.current, pkgPath) + defer func() { + ds.current = ds.current[:len(ds.current)-1] + }() + } // first, check cache. oid := ObjectIDFromPkgPath(pkgPath) if oo, exists := ds.cacheObjects[oid]; exists { @@ -175,12 +164,7 @@ func (ds *defaultStore) getPackage(pkgPath string, impStore importerStore) *Pack } // otherwise, fetch from pkgGetter. if ds.pkgGetter != nil { - if impStore.defaultStore == nil { - // pre-allocate 16 entries to likely avoid further slice allocations. - impStore = importerStore{defaultStore: ds, importChain: make([]string, 0, 16)} - } - impStore.importChain = append(impStore.importChain, pkgPath) - if pn, pv := ds.pkgGetter(pkgPath, impStore); pv != nil { + if pn, pv := ds.pkgGetter(pkgPath, ds); pv != nil { // e.g. tests/imports_tests loads example/gno.land/r/... realms. // if pv.IsRealm() { // panic("realm packages cannot be gotten from pkgGetter") From 38719cdc7f694099ee45774be759a5917eb6cf0a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 10 May 2024 17:38:31 +0200 Subject: [PATCH 58/67] update docs --- gnovm/pkg/gnolang/store.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 21a07a60276..7fc54c12b0f 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -15,7 +15,9 @@ import ( // PackageGetter specifies how the store may retrieve packages which are not // already in its cache. PackageGetter should return nil when the requested // package does not exist. store should be used to run the machine, or otherwise -// call any methods which may call store.GetPackage, to avoid import cycles. +// call any methods which may call store.GetPackage; avoid using any "global" +// store as the one passed to the PackageGetter may be a fork of that (ie. +// the original is not meant to be written to). type PackageGetter func(pkgPath string, store Store) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) From 0305e3347646ae5a55e8dd878513f730ee198750 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 18 Jun 2024 15:01:13 +0200 Subject: [PATCH 59/67] Update gnovm/cmd/gno/transpile.go Co-authored-by: Antonio Navarro Perez --- gnovm/cmd/gno/transpile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 7dc8091a641..c85c623907d 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -229,7 +229,7 @@ func transpilePkg(dirPath string, opts *transpileOptions) error { fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(dirPath)) } for _, file := range files { - if err = transpileFile(file, opts); err != nil { + if err := transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } From 12139003bb11477520c8b7363277c894879ab47d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 18 Jun 2024 15:16:18 +0200 Subject: [PATCH 60/67] code review changes --- gnovm/cmd/gno/transpile.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 7dc8091a641..dc4fdf02380 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -8,7 +8,6 @@ import ( "go/ast" "go/scanner" "go/token" - "log" "os" "os/exec" "path/filepath" @@ -222,14 +221,14 @@ func transpilePkg(dirPath string, opts *transpileOptions) error { // Easier to skip for now. files, err := listNonTestFiles(dirPath) if err != nil { - log.Fatal(err) + return err } if opts.cfg.verbose { fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(dirPath)) } for _, file := range files { - if err = transpileFile(file, opts); err != nil { + if err := transpileFile(file, opts); err != nil { return fmt.Errorf("%s: %w", file, err) } } @@ -357,10 +356,11 @@ func buildTranspiledPackage(fileOrPkg, goBinary string) error { args = append(args, "-tags=gno", target) cmd := exec.Command(goBinary, args...) out, err := cmd.CombinedOutput() - if _, ok := err.(*exec.ExitError); ok { - // exit error + if errors.As(err, new(*exec.ExitError)) { + // there was a non-zero exit code; parse the go build errors return parseGoBuildErrors(string(out)) } + // other kinds of errors; return return err } From 79fd65f7603b1565d65c815f0c08edc3e0b9ab93 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 18 Jun 2024 17:31:25 +0200 Subject: [PATCH 61/67] Update gnovm/cmd/gno/transpile.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miloš Živković --- gnovm/cmd/gno/transpile.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index dc4fdf02380..f6593d106a3 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -168,8 +168,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { continue } if cfg.output != "." { - pkgPath, err = ResolvePath(cfg.output, pkgPath) - if err != nil { + if pkgPath, err = ResolvePath(cfg.output, pkgPath); err != nil { return fmt.Errorf("resolve output path: %w", err) } } From f8b0e9b9818c83d925b865f1e3f478070f75add9 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 18 Jun 2024 18:47:51 +0200 Subject: [PATCH 62/67] different system for FindNative --- gnovm/pkg/transpiler/transpiler.go | 9 +++++---- gnovm/stdlibs/native.go | 13 +++++++++++-- gnovm/stdlibs/stdlibs.go | 29 +++++++++++------------------ gnovm/tests/stdlibs/native.go | 13 +++++++++++-- misc/genstd/template.tmpl | 13 +++++++++++-- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/gnovm/pkg/transpiler/transpiler.go b/gnovm/pkg/transpiler/transpiler.go index 239175e8d94..bd4bb1b1bc9 100644 --- a/gnovm/pkg/transpiler/transpiler.go +++ b/gnovm/pkg/transpiler/transpiler.go @@ -237,7 +237,8 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) if !ok { break } - if stdlibs.HasMachineParam(ip, gno.Name(fe.Sel.Name)) { + nat := stdlibs.FindNative(ip, gno.Name(fe.Sel.Name)) + if nat != nil && nat.HasMachineParam() { // Because it's an import, the symbol is always exported, so no need for the // X_ prefix we add below. ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) @@ -249,9 +250,9 @@ func (ctx *transpileCtx) transformCallExpr(_ *astutil.Cursor, ce *ast.CallExpr) // The logic here is not robust to be generic. It does not account for locally // defined scope. However, because native bindings have a narrowly defined and // controlled scope (standard libraries) this will work for our usecase. - if ctx.stdlibPath != "" && - stdlibs.HasNativeBinding(ctx.stdlibPath, gno.Name(fe.Name)) { - if stdlibs.HasMachineParam(ctx.stdlibPath, gno.Name(fe.Name)) { + nat := stdlibs.FindNative(ctx.stdlibPath, gno.Name(fe.Name)) + if ctx.stdlibPath != "" && nat != nil { + if nat.HasMachineParam() { ce.Args = append([]ast.Expr{ast.NewIdent("nil")}, ce.Args...) } if !fe.IsExported() { diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 1c180ca72dd..3dd432c90c0 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -16,7 +16,9 @@ import ( libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" ) -type nativeFunc struct { +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { gnoPkg string gnoFunc gno.Name params []gno.FieldTypeExpr @@ -25,7 +27,14 @@ type nativeFunc struct { f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "crypto/ed25519", "verify", diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index e7ae651c86c..c9b16815ab5 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -13,31 +13,24 @@ func GetContext(m *gno.Machine) ExecContext { return libsstd.GetContext(m) } -func findNative(pkgPath string, name gno.Name) nativeFunc { - for _, nf := range nativeFuncs { +// FindNative returns the NativeFunc associated with the given pkgPath+name +// combination. If there is none, FindNative returns nil. +func FindNative(pkgPath string, name gno.Name) *NativeFunc { + for i, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { - return nf + return &nativeFuncs[i] } } - return nativeFunc{} + return nil } // NativeStore is used by the GnoVM to determine if the given function, // specified by its pkgPath and name, has a native implementation; and if so // retrieve it. func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { - return findNative(pkgPath, name).f -} - -// HasNativeBinding determines if the function specified by the given pkgPath -// and name is a native binding. -func HasNativeBinding(pkgPath string, name gno.Name) bool { - return findNative(pkgPath, name).f != nil -} - -// HasMachineParam determines if the function specified by the given pkgPath -// and name contains a machine parameter; ie., its native implementation is -// prefixed with the parameter `m *gno.Machine`. -func HasMachineParam(pkgPath string, name gno.Name) bool { - return findNative(pkgPath, name).hasMachine + nt := FindNative(pkgPath, name) + if nt == nil { + return nil + } + return nt.f } diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index a0ccb28d057..d2964a7958c 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -11,7 +11,9 @@ import ( testlibs_testing "github.com/gnolang/gno/gnovm/tests/stdlibs/testing" ) -type nativeFunc struct { +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { gnoPkg string gnoFunc gno.Name params []gno.FieldTypeExpr @@ -20,7 +22,14 @@ type nativeFunc struct { f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ { "std", "AssertOriginCall", diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index dd638fa4142..bfbe252a2d5 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -12,7 +12,9 @@ import ( {{- end }} ) -type nativeFunc struct { +// NativeFunc represents a function in the standard library which has a native +// (go-based) implementation, commonly referred to as a "native binding". +type NativeFunc struct { gnoPkg string gnoFunc gno.Name params []gno.FieldTypeExpr @@ -21,7 +23,14 @@ type nativeFunc struct { f func(m *gno.Machine) } -var nativeFuncs = [...]nativeFunc{ +// HasMachineParam returns whether the given native binding has a machine parameter. +// This means that the Go version of this function expects a *gno.Machine +// as its first parameter. +func (n *NativeFunc) HasMachineParam() bool { + return n.hasMachine +} + +var nativeFuncs = [...]NativeFunc{ {{- range $i, $m := .Mappings }} { {{ printf "%q" $m.GnoImportPath }}, From dd29804e0fc2099079600229dcc563539efebbc6 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 18 Jun 2024 19:01:51 +0200 Subject: [PATCH 63/67] parallel --- gnovm/cmd/gno/transpile_test.go | 1 - gnovm/cmd/gno/util_test.go | 2 ++ gnovm/pkg/gnolang/helpers_test.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 0132c9c6171..90da1f73f32 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -94,7 +94,6 @@ pkg/file.gno:60:20: ugly error`, t.Parallel() err := parseGoBuildErrors(tt.output) - assert.ErrorIs(t, err, tt.expectedError) }) } diff --git a/gnovm/cmd/gno/util_test.go b/gnovm/cmd/gno/util_test.go index ae2f3bc5123..a92c924e272 100644 --- a/gnovm/cmd/gno/util_test.go +++ b/gnovm/cmd/gno/util_test.go @@ -297,6 +297,8 @@ func createGnoPackages(t *testing.T, tmpDir string) { } func TestResolvePath(t *testing.T) { + t.Parallel() + if os.PathSeparator != '/' { t.Skip("ResolvePath test is only written of UNIX-like filesystems") } diff --git a/gnovm/pkg/gnolang/helpers_test.go b/gnovm/pkg/gnolang/helpers_test.go index a1c959b4cee..af8fa64ac79 100644 --- a/gnovm/pkg/gnolang/helpers_test.go +++ b/gnovm/pkg/gnolang/helpers_test.go @@ -7,6 +7,7 @@ import ( ) func TestIsRealmPath(t *testing.T) { + t.Parallel() tt := []struct { input string result bool From e3dff754ef1f69f30f2e847676d8afedc58ed7a0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 19 Jun 2024 14:13:25 +0200 Subject: [PATCH 64/67] commands.IO --- gnovm/cmd/gno/transpile.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index f6593d106a3..81de9b32432 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -33,6 +33,8 @@ type transpileCfg struct { type transpileOptions struct { cfg *transpileCfg + // CLI output + io commands.IO // transpiled is the set of packages already // transpiled from .gno to .go. transpiled map[string]struct{} @@ -147,7 +149,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { err = transpilePkg(path, opts) } else { if opts.cfg.verbose { - fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(path)) + io.ErrPrintln(filepath.Clean(path)) } err = transpileFile(path, opts) @@ -172,7 +174,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return fmt.Errorf("resolve output path: %w", err) } } - err := goBuildFileOrPkg(pkgPath, cfg) + err := goBuildFileOrPkg(io, pkgPath, cfg) if err != nil { var fileErrlist scanner.ErrorList if !errors.As(err, &fileErrlist) { @@ -208,7 +210,7 @@ func transpilePkg(dirPath string, opts *transpileOptions) error { } if err == nil && gmod.Draft { if opts.cfg.verbose { - fmt.Fprintf(os.Stderr, "%s (skipped, gno.mod marks module as draft)\n", filepath.Clean(dirPath)) + opts.io.ErrPrintfln("%s (skipped, gno.mod marks module as draft)", filepath.Clean(dirPath)) } opts.skipped = append(opts.skipped, dirPath) return nil @@ -224,7 +226,7 @@ func transpilePkg(dirPath string, opts *transpileOptions) error { } if opts.cfg.verbose { - fmt.Fprintf(os.Stderr, "%s\n", filepath.Clean(dirPath)) + opts.io.ErrPrintln(filepath.Clean(dirPath)) } for _, file := range files { if err := transpileFile(file, opts); err != nil { @@ -285,12 +287,12 @@ func transpileFile(srcPath string, opts *transpileOptions) error { return nil } -func goBuildFileOrPkg(fileOrPkg string, cfg *transpileCfg) error { +func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error { verbose := cfg.verbose goBinary := cfg.goBinary if verbose { - fmt.Fprintf(os.Stderr, "%s [build]\n", filepath.Clean(fileOrPkg)) + io.ErrPrintfln("%s [build]", filepath.Clean(fileOrPkg)) } return buildTranspiledPackage(fileOrPkg, goBinary) From d1b1ca2465cbd919c21069c8ba94cf7a7c75e4c4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 19 Jun 2024 14:20:24 +0200 Subject: [PATCH 65/67] return error --- gnovm/cmd/gno/transpile.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index 81de9b32432..ea5ba9c39ab 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -276,7 +276,10 @@ func transpileFile(srcPath string, opts *transpileOptions) error { // transpile imported packages, if `SkipImports` sets to false if !flags.skipImports && !strings.HasSuffix(srcPath, "_filetest.gno") && !strings.HasSuffix(srcPath, "_test.gno") { - dirPaths := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) + dirPaths, err := getPathsFromImportSpec(opts.cfg.rootDir, transpileRes.Imports) + if err != nil { + return err + } for _, path := range dirPaths { if err := transpilePkg(path, opts); err != nil { return err @@ -300,11 +303,11 @@ func goBuildFileOrPkg(io commands.IO, fileOrPkg string, cfg *transpileCfg) error // getPathsFromImportSpec returns the directory paths where the code for each // importSpec is stored (assuming they start with [transpiler.ImportPrefix]). -func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string) { +func getPathsFromImportSpec(rootDir string, importSpec []*ast.ImportSpec) (dirs []string, err error) { for _, i := range importSpec { path, err := strconv.Unquote(i.Path.Value) if err != nil { - continue + return nil, err } if strings.HasPrefix(path, transpiler.ImportPrefix) { res := strings.TrimPrefix(path, transpiler.ImportPrefix) From 4c8eca0c6a76051277819a0acedb67067f89a5e0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 19 Jun 2024 15:05:13 +0200 Subject: [PATCH 66/67] add milos refactor --- gnovm/cmd/gno/transpile.go | 5 +- gnovm/cmd/gno/util.go | 99 +++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/transpile.go index ea5ba9c39ab..2e12ee6f4b3 100644 --- a/gnovm/cmd/gno/transpile.go +++ b/gnovm/cmd/gno/transpile.go @@ -42,9 +42,10 @@ type transpileOptions struct { skipped []string } -func newTranspileOptions(cfg *transpileCfg) *transpileOptions { +func newTranspileOptions(cfg *transpileCfg, io commands.IO) *transpileOptions { return &transpileOptions{ cfg: cfg, + io: io, transpiled: map[string]struct{}{}, } } @@ -138,7 +139,7 @@ func execTranspile(cfg *transpileCfg, args []string, io commands.IO) error { return fmt.Errorf("list paths: %w", err) } - opts := newTranspileOptions(cfg) + opts := newTranspileOptions(cfg, io) var errlist scanner.ErrorList for _, path := range paths { st, err := os.Stat(path) diff --git a/gnovm/cmd/gno/util.go b/gnovm/cmd/gno/util.go index 2d71c4fbf0e..480161c2b7e 100644 --- a/gnovm/cmd/gno/util.go +++ b/gnovm/cmd/gno/util.go @@ -22,59 +22,70 @@ func isFileExist(path string) bool { } func gnoPackagesFromArgs(args []string) ([]string, error) { - paths := []string{} - for _, arg := range args { - info, err := os.Stat(arg) + var paths []string + + for _, argPath := range args { + info, err := os.Stat(argPath) if err != nil { return nil, fmt.Errorf("invalid file or package path: %w", err) } + if !info.IsDir() { - if filepath.IsAbs(arg) { - paths = append(paths, arg) - } else { - paths = append(paths, "."+string(filepath.Separator)+arg) - } - } else { - // if the passed arg is a dir, then we'll recursively walk the dir - // and look for directories containing at least one .gno file. - - visited := map[string]bool{} // used to run the builder only once per folder. - err = filepath.WalkDir(arg, func(curpath string, f fs.DirEntry, err error) error { - if err != nil { - return fmt.Errorf("%s: walk dir: %w", arg, err) - } - if f.IsDir() { - return nil // skip - } - if !isGnoFile(f) { - return nil // skip - } - - parentDir := filepath.Dir(curpath) - if _, found := visited[parentDir]; found { - return nil - } - visited[parentDir] = true - - pkg := parentDir - if !filepath.IsAbs(parentDir) { - // cannot use path.Join or filepath.Join, because we need - // to ensure that ./ is the prefix to pass to go build. - // if not absolute. - pkg = "." + string(filepath.Separator) + parentDir - } - - paths = append(paths, pkg) - return nil - }) - if err != nil { - return nil, err - } + paths = append(paths, ensurePathPrefix(argPath)) + + continue + } + + // Gather package paths from the directory + err = walkDirForGnoFiles(argPath, func(path string) { + paths = append(paths, ensurePathPrefix(path)) + }) + if err != nil { + return nil, fmt.Errorf("unable to walk dir: %w", err) } } + return paths, nil } +func ensurePathPrefix(path string) string { + if filepath.IsAbs(path) { + return path + } + + // cannot use path.Join or filepath.Join, because we need + // to ensure that ./ is the prefix to pass to go build. + // if not absolute. + return "." + string(filepath.Separator) + path +} + +func walkDirForGnoFiles(root string, addPath func(path string)) error { + visited := make(map[string]struct{}) + + walkFn := func(currPath string, f fs.DirEntry, err error) error { + if err != nil { + return fmt.Errorf("%s: walk dir: %w", root, err) + } + + if f.IsDir() || !isGnoFile(f) { + return nil + } + + parentDir := filepath.Dir(currPath) + if _, found := visited[parentDir]; found { + return nil + } + + visited[parentDir] = struct{}{} + + addPath(parentDir) + + return nil + } + + return filepath.WalkDir(root, walkFn) +} + // targetsFromPatterns returns a list of target paths that match the patterns. // Each pattern can represent a file or a directory, and if the pattern // includes "/...", the "..." is treated as a wildcard, matching any string. From afc957b263df3d482cd15a42b4b971b09a368483 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 19 Jun 2024 15:24:39 +0200 Subject: [PATCH 67/67] better error support --- gnovm/cmd/gno/transpile_test.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 90da1f73f32..827c09e23f1 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -33,9 +33,10 @@ func Test_parseGoBuildErrors(t *testing.T) { t.Parallel() tests := []struct { - name string - output string - expectedError error + name string + output string + expectedError error + expectedErrorIs error }{ { name: "empty output", @@ -79,22 +80,27 @@ pkg/file.gno:60:20: ugly error`, }, }, { - name: "line parse error", - output: `main.gno:9000000000000000000000000000000000000000000000000000:11: error`, - expectedError: strconv.ErrRange, + name: "line parse error", + output: `main.gno:9000000000000000000000000000000000000000000000000000:11: error`, + expectedErrorIs: strconv.ErrRange, }, { - name: "column parse error", - output: `main.gno:1:9000000000000000000000000000000000000000000000000000: error`, - expectedError: strconv.ErrRange, + name: "column parse error", + output: `main.gno:1:9000000000000000000000000000000000000000000000000000: error`, + expectedErrorIs: strconv.ErrRange, }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := parseGoBuildErrors(tt.output) - assert.ErrorIs(t, err, tt.expectedError) + if eis := tt.expectedErrorIs; eis != nil { + assert.ErrorIs(t, err, eis) + } else { + assert.Equal(t, tt.expectedError, err) + } }) } }