diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index 6d5399ca932..a3e7f5310e1 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -6,17 +6,19 @@ import ( "flag" "fmt" "go/scanner" + "go/types" "io" "os" "path/filepath" "regexp" "strings" + "github.com/gnolang/gno/gnovm" "github.com/gnolang/gno/gnovm/pkg/gnoenv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/pkg/gnomod" "github.com/gnolang/gno/gnovm/pkg/test" "github.com/gnolang/gno/tm2/pkg/commands" - osm "github.com/gnolang/gno/tm2/pkg/os" "go.uber.org/multierr" ) @@ -50,6 +52,31 @@ func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)") } +type lintCode int + +const ( + lintUnknown lintCode = iota + lintGnoMod + lintGnoError + lintParserError + lintTypeCheckError + + // TODO: add new linter codes here. +) + +type lintIssue struct { + Code lintCode + Msg string + Confidence float64 // 1 is 100% + Location string // file:line, or equivalent + // TODO: consider writing fix suggestions +} + +func (i lintIssue) String() string { + // TODO: consider crafting a doc URL based on Code. + return fmt.Sprintf("%s: %s (code=%d)", i.Location, i.Msg, i.Code) +} + func execLint(cfg *lintCfg, args []string, io commands.IO) error { if len(args) < 1 { return flag.ErrHelp @@ -72,37 +99,55 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { for _, pkgPath := range pkgPaths { if verbose { - fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath) + io.ErrPrintln(pkgPath) + } + + info, err := os.Stat(pkgPath) + if err == nil && !info.IsDir() { + pkgPath = filepath.Dir(pkgPath) } // Check if 'gno.mod' exists - gnoModPath := filepath.Join(pkgPath, "gno.mod") - if !osm.FileExists(gnoModPath) { - hasError = true + gmFile, err := gnomod.ParseAt(pkgPath) + if err != nil { issue := lintIssue{ - Code: lintNoGnoMod, + Code: lintGnoMod, Confidence: 1, Location: pkgPath, - Msg: "missing 'gno.mod' file", + Msg: err.Error(), } - fmt.Fprint(io.Err(), issue.String()+"\n") + io.ErrPrintln(issue) + hasError = true } - // Handle runtime errors - hasError = catchRuntimeError(pkgPath, io.Err(), func() { - stdout, stdin, stderr := io.Out(), io.In(), io.Err() - _, testStore := test.Store( - rootDir, false, - stdin, stdout, stderr, - ) - - targetPath := pkgPath - info, err := os.Stat(pkgPath) - if err == nil && !info.IsDir() { - targetPath = filepath.Dir(pkgPath) + stdout, stdin, stderr := io.Out(), io.In(), io.Err() + _, testStore := test.Store( + rootDir, false, + stdin, stdout, stderr, + ) + + memPkg, err := gno.ReadMemPackage(pkgPath, pkgPath) + if err != nil { + io.ErrPrintln(issueFromError(pkgPath, err).String()) + hasError = true + continue + } + + // Run type checking + if gmFile == nil || !gmFile.Draft { + foundErr, err := lintTypeCheck(io, memPkg, testStore) + if err != nil { + io.ErrPrintln(err) + hasError = true + } else if foundErr { + hasError = true } + } else if verbose { + io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath) + } - memPkg := gno.MustReadMemPackage(targetPath, targetPath) + // Handle runtime errors + hasRuntimeErr := catchRuntimeError(pkgPath, io.Err(), func() { tm := test.Machine(testStore, stdout, memPkg.Path) defer tm.Release() @@ -110,28 +155,13 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { tm.RunMemPackage(memPkg, true) // Check test files - testfiles := &gno.FileSet{} - for _, mfile := range memPkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // Skip non-GNO files - } + testFiles := lintTestFiles(memPkg) - n, _ := gno.ParseFile(mfile.Name, mfile.Body) - if n == nil { - continue // Skip empty files - } - - // XXX: package ending with `_test` is not supported yet - if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") { - // Keep only test files - testfiles.AddFiles(n) - } - } - - tm.RunFiles(testfiles.Files...) - }) || hasError - - // TODO: Add more checkers + tm.RunFiles(testFiles.Files...) + }) + if hasRuntimeErr { + hasError = true + } } if hasError { @@ -141,6 +171,66 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error { return nil } +func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) { + tcErr := gno.TypeCheckMemPackageTest(memPkg, testStore) + if tcErr == nil { + return false, nil + } + + errs := multierr.Errors(tcErr) + for _, err := range errs { + switch err := err.(type) { + case types.Error: + io.ErrPrintln(lintIssue{ + Code: lintTypeCheckError, + Msg: err.Msg, + Confidence: 1, + Location: err.Fset.Position(err.Pos).String(), + }) + case scanner.ErrorList: + for _, scErr := range err { + io.ErrPrintln(lintIssue{ + Code: lintParserError, + Msg: scErr.Msg, + Confidence: 1, + Location: scErr.Pos.String(), + }) + } + case scanner.Error: + io.ErrPrintln(lintIssue{ + Code: lintParserError, + Msg: err.Msg, + Confidence: 1, + Location: err.Pos.String(), + }) + default: + return false, fmt.Errorf("unexpected error type: %T", err) + } + } + return true, nil +} + +func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet { + testfiles := &gno.FileSet{} + for _, mfile := range memPkg.Files { + if !strings.HasSuffix(mfile.Name, ".gno") { + continue // Skip non-GNO files + } + + n, _ := gno.ParseFile(mfile.Name, mfile.Body) + if n == nil { + continue // Skip empty files + } + + // XXX: package ending with `_test` is not supported yet + if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") { + // Keep only test files + testfiles.AddFiles(n) + } + } + return testfiles +} + func guessSourcePath(pkg, source string) string { if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() { pkg = filepath.Dir(pkg) @@ -174,21 +264,21 @@ func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (ha switch verr := r.(type) { case *gno.PreprocessError: err := verr.Unwrap() - fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n") + fmt.Fprintln(stderr, issueFromError(pkgPath, err).String()) case error: errors := multierr.Errors(verr) for _, err := range errors { errList, ok := err.(scanner.ErrorList) if ok { for _, errorInList := range errList { - fmt.Fprint(stderr, issueFromError(pkgPath, errorInList).String()+"\n") + fmt.Fprintln(stderr, issueFromError(pkgPath, errorInList).String()) } } else { - fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n") + fmt.Fprintln(stderr, issueFromError(pkgPath, err).String()) } } case string: - fmt.Fprint(stderr, issueFromError(pkgPath, errors.New(verr)).String()+"\n") + fmt.Fprintln(stderr, issueFromError(pkgPath, errors.New(verr)).String()) default: panic(r) } @@ -198,29 +288,6 @@ func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (ha return } -type lintCode int - -const ( - lintUnknown lintCode = 0 - lintNoGnoMod lintCode = iota - lintGnoError - - // TODO: add new linter codes here. -) - -type lintIssue struct { - Code lintCode - Msg string - Confidence float64 // 1 is 100% - Location string // file:line, or equivalent - // TODO: consider writing fix suggestions -} - -func (i lintIssue) String() string { - // TODO: consider crafting a doc URL based on Code. - return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code) -} - func issueFromError(pkgPath string, err error) lintIssue { var issue lintIssue issue.Confidence = 1 diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go index 031c252bc79..4589fc55f92 100644 --- a/gnovm/cmd/gno/lint_test.go +++ b/gnovm/cmd/gno/lint_test.go @@ -1,6 +1,9 @@ package main -import "testing" +import ( + "strings" + "testing" +) func TestLintApp(t *testing.T) { tc := []testMainCase{ @@ -9,7 +12,7 @@ func TestLintApp(t *testing.T) { errShouldBe: "flag: help requested", }, { args: []string{"lint", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", + stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"}, @@ -17,33 +20,43 @@ func TestLintApp(t *testing.T) { errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"}, - stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2).", + stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)", errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"}, - stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6", + stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6", errShouldBe: "exit code: 1", }, { - args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, - stderrShouldContain: "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n", - errShouldBe: "exit code: 1", - }, { - args: []string{"lint", "../../tests/integ/run_main/"}, - stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).", - errShouldBe: "exit code: 1", + args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"}, + stderrShouldContain: func() string { + lines := []string{ + "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", + "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)", + "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)", + "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)", + } + return strings.Join(lines, "\n") + "\n" + }(), + errShouldBe: "exit code: 1", }, { args: []string{"lint", "../../tests/integ/minimalist_gnomod/"}, // TODO: raise an error because there is a gno.mod, but no .gno files }, { args: []string{"lint", "../../tests/integ/invalid_module_name/"}, // TODO: raise an error because gno.mod is invalid + }, { + args: []string{"lint", "../../tests/integ/invalid_gno_file/"}, + stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)", + errShouldBe: "exit code: 1", + }, { + args: []string{"lint", "../../tests/integ/typecheck_missing_return/"}, + stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)", + errShouldBe: "exit code: 1", }, // TODO: 'gno mod' is valid? - // TODO: is gno source valid? // TODO: are dependencies valid? // TODO: is gno source using unsafe/discouraged features? - // 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/run_test.go b/gnovm/cmd/gno/run_test.go index 74f99f7490c..aa7780c149e 100644 --- a/gnovm/cmd/gno/run_test.go +++ b/gnovm/cmd/gno/run_test.go @@ -1,6 +1,9 @@ package main -import "testing" +import ( + "strings" + "testing" +) func TestRunApp(t *testing.T) { tc := []testMainCase{ @@ -84,9 +87,17 @@ func TestRunApp(t *testing.T) { stdoutShouldContain: "Context worked", }, { - args: []string{"run", "../../tests/integ/several-files-multiple-errors/"}, - stderrShouldContain: "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n", - errShouldBe: "exit code: 1", + args: []string{"run", "../../tests/integ/several-files-multiple-errors/"}, + stderrShouldContain: func() string { + lines := []string{ + "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)", + "../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2)", + "../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2)", + "../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2)", + } + return strings.Join(lines, "\n") + "\n" + }(), + errShouldBe: "exit code: 1", }, // TODO: a test file // TODO: args diff --git a/gnovm/cmd/gno/testdata/gno_fmt/empty.txtar b/gnovm/cmd/gno/testdata/fmt/empty.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/empty.txtar rename to gnovm/cmd/gno/testdata/fmt/empty.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar b/gnovm/cmd/gno/testdata/fmt/import_cleaning.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/import_cleaning.txtar rename to gnovm/cmd/gno/testdata/fmt/import_cleaning.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/include.txtar b/gnovm/cmd/gno/testdata/fmt/include.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/include.txtar rename to gnovm/cmd/gno/testdata/fmt/include.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar b/gnovm/cmd/gno/testdata/fmt/multi_import.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/multi_import.txtar rename to gnovm/cmd/gno/testdata/fmt/multi_import.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar b/gnovm/cmd/gno/testdata/fmt/noimport_format.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/noimport_format.txtar rename to gnovm/cmd/gno/testdata/fmt/noimport_format.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar b/gnovm/cmd/gno/testdata/fmt/parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/parse_error.txtar rename to gnovm/cmd/gno/testdata/fmt/parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar b/gnovm/cmd/gno/testdata/fmt/shadow_import.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_fmt/shadow_import.txtar rename to gnovm/cmd/gno/testdata/fmt/shadow_import.txtar diff --git a/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar b/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar deleted file mode 100644 index 9482eeb1f4f..00000000000 --- a/gnovm/cmd/gno/testdata/gno_lint/file_error_txtar +++ /dev/null @@ -1,20 +0,0 @@ -# gno lint: test file error - -! gno lint ./i_have_error_test.gno - -cmp stdout stdout.golden -cmp stderr stderr.golden - --- i_have_error_test.gno -- -package main - -import "fmt" - -func TestIHaveSomeError() { - i := undefined_variable - fmt.Println("Hello", 42) -} - --- stdout.golden -- --- stderr.golden -- -i_have_error_test.gno:6: name undefined_variable not declared (code=2). diff --git a/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar similarity index 54% rename from gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar rename to gnovm/cmd/gno/testdata/lint/bad_import.txtar index 52141dff09b..b5edbdd0223 100644 --- a/gnovm/cmd/gno/testdata/gno_lint/bad_import.txtar +++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar @@ -11,9 +11,13 @@ package main import "python" func main() { - fmt.Println("Hello", 42) + println("Hello", 42) } +-- gno.mod -- +module gno.land/p/test + -- stdout.golden -- -- stderr.golden -- -bad_file.gno:3:8: unknown import path python (code=2). +bad_file.gno:3:8: could not import python (import not found: python) (code=4) +bad_file.gno:3:8: unknown import path python (code=2) diff --git a/gnovm/cmd/gno/testdata/gno_lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar similarity index 88% rename from gnovm/cmd/gno/testdata/gno_lint/file_error.txtar rename to gnovm/cmd/gno/testdata/lint/file_error.txtar index 5aa3a3282d5..4fa50c6da81 100644 --- a/gnovm/cmd/gno/testdata/gno_lint/file_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar @@ -15,6 +15,9 @@ func TestIHaveSomeError() { fmt.Println("Hello", 42) } +-- gno.mod -- +module gno.land/p/test + -- stdout.golden -- -- stderr.golden -- -i_have_error_test.gno:6:7: name undefined_variable not declared (code=2). +i_have_error_test.gno:6:7: name undefined_variable not declared (code=2) diff --git a/gnovm/cmd/gno/testdata/gno_lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar similarity index 68% rename from gnovm/cmd/gno/testdata/gno_lint/no_error.txtar rename to gnovm/cmd/gno/testdata/lint/no_error.txtar index 95356b1ba2b..5dd3b164952 100644 --- a/gnovm/cmd/gno/testdata/gno_lint/no_error.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar @@ -1,6 +1,6 @@ # testing simple gno lint command with any error -gno lint ./good_file.gno +gno lint ./good_file.gno cmp stdout stdout.golden cmp stdout stderr.golden @@ -8,11 +8,12 @@ cmp stdout stderr.golden -- good_file.gno -- package main -import "fmt" - func main() { - fmt.Println("Hello", 42) + println("Hello", 42) } +-- gno.mod -- +module gno.land/p/demo/test + -- stdout.golden -- -- stderr.golden -- diff --git a/gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar similarity index 60% rename from gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar rename to gnovm/cmd/gno/testdata/lint/no_gnomod.txtar index 52daa6f0e9b..b5a046a7095 100644 --- a/gnovm/cmd/gno/testdata/gno_lint/no_gnomod.txtar +++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar @@ -8,12 +8,10 @@ cmp stderr stderr.golden -- good_file.gno -- package main -import "fmt" - func main() { - fmt.Println("Hello", 42) + println("Hello", 42) } -- stdout.golden -- -- stderr.golden -- -./.: missing 'gno.mod' file (code=1). +./.: parsing gno.mod at ./.: gno.mod file not found in current or any parent directory (code=1) diff --git a/gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar similarity index 55% rename from gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar rename to gnovm/cmd/gno/testdata/lint/not_declared.txtar index b63c5c447e1..ac56b27e0df 100644 --- a/gnovm/cmd/gno/testdata/gno_lint/not_declared.txtar +++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar @@ -8,13 +8,15 @@ cmp stderr stderr.golden -- bad_file.gno -- package main -import "fmt" - func main() { - hello.Foo() - fmt.Println("Hello", 42) + hello.Foo() + println("Hello", 42) } +-- gno.mod -- +module gno.land/p/demo/hello + -- stdout.golden -- -- stderr.golden -- -bad_file.gno:6:3: name hello not declared (code=2). +bad_file.gno:4:2: undefined: hello (code=4) +bad_file.gno:4:2: name hello not declared (code=2) diff --git a/gnovm/cmd/gno/testdata/gno_test/dir_not_exist.txtar b/gnovm/cmd/gno/testdata/test/dir_not_exist.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/dir_not_exist.txtar rename to gnovm/cmd/gno/testdata/test/dir_not_exist.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar b/gnovm/cmd/gno/testdata/test/empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/empty_dir.txtar rename to gnovm/cmd/gno/testdata/test/empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar b/gnovm/cmd/gno/testdata/test/empty_gno1.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/empty_gno1.txtar rename to gnovm/cmd/gno/testdata/test/empty_gno1.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar b/gnovm/cmd/gno/testdata/test/empty_gno2.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/empty_gno2.txtar rename to gnovm/cmd/gno/testdata/test/empty_gno2.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar b/gnovm/cmd/gno/testdata/test/empty_gno3.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/empty_gno3.txtar rename to gnovm/cmd/gno/testdata/test/empty_gno3.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/error_correct.txtar b/gnovm/cmd/gno/testdata/test/error_correct.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/error_correct.txtar rename to gnovm/cmd/gno/testdata/test/error_correct.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/error_incorrect.txtar b/gnovm/cmd/gno/testdata/test/error_incorrect.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/error_incorrect.txtar rename to gnovm/cmd/gno/testdata/test/error_incorrect.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/error_sync.txtar b/gnovm/cmd/gno/testdata/test/error_sync.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/error_sync.txtar rename to gnovm/cmd/gno/testdata/test/error_sync.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar b/gnovm/cmd/gno/testdata/test/failing_filetest.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/failing_filetest.txtar rename to gnovm/cmd/gno/testdata/test/failing_filetest.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/failing_test.txtar b/gnovm/cmd/gno/testdata/test/failing_test.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/failing_test.txtar rename to gnovm/cmd/gno/testdata/test/failing_test.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar b/gnovm/cmd/gno/testdata/test/filetest_events.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/filetest_events.txtar rename to gnovm/cmd/gno/testdata/test/filetest_events.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_print-runtime-metrics.txtar b/gnovm/cmd/gno/testdata/test/flag_print-runtime-metrics.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/flag_print-runtime-metrics.txtar rename to gnovm/cmd/gno/testdata/test/flag_print-runtime-metrics.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_run.txtar b/gnovm/cmd/gno/testdata/test/flag_run.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/flag_run.txtar rename to gnovm/cmd/gno/testdata/test/flag_run.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/flag_timeout.txtar b/gnovm/cmd/gno/testdata/test/flag_timeout.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/flag_timeout.txtar rename to gnovm/cmd/gno/testdata/test/flag_timeout.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar b/gnovm/cmd/gno/testdata/test/fmt_write_import.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/fmt_write_import.txtar rename to gnovm/cmd/gno/testdata/test/fmt_write_import.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/minim1.txtar b/gnovm/cmd/gno/testdata/test/minim1.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/minim1.txtar rename to gnovm/cmd/gno/testdata/test/minim1.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/minim2.txtar b/gnovm/cmd/gno/testdata/test/minim2.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/minim2.txtar rename to gnovm/cmd/gno/testdata/test/minim2.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/minim3.txtar b/gnovm/cmd/gno/testdata/test/minim3.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/minim3.txtar rename to gnovm/cmd/gno/testdata/test/minim3.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/multitest_events.txtar b/gnovm/cmd/gno/testdata/test/multitest_events.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/multitest_events.txtar rename to gnovm/cmd/gno/testdata/test/multitest_events.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/no_args.txtar b/gnovm/cmd/gno/testdata/test/no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/no_args.txtar rename to gnovm/cmd/gno/testdata/test/no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/output_correct.txtar b/gnovm/cmd/gno/testdata/test/output_correct.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/output_correct.txtar rename to gnovm/cmd/gno/testdata/test/output_correct.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/output_incorrect.txtar b/gnovm/cmd/gno/testdata/test/output_incorrect.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/output_incorrect.txtar rename to gnovm/cmd/gno/testdata/test/output_incorrect.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/output_sync.txtar b/gnovm/cmd/gno/testdata/test/output_sync.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/output_sync.txtar rename to gnovm/cmd/gno/testdata/test/output_sync.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/panic.txtar b/gnovm/cmd/gno/testdata/test/panic.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/panic.txtar rename to gnovm/cmd/gno/testdata/test/panic.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/pkg_underscore_test.txtar b/gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/pkg_underscore_test.txtar rename to gnovm/cmd/gno/testdata/test/pkg_underscore_test.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar b/gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/realm_boundmethod.txtar rename to gnovm/cmd/gno/testdata/test/realm_boundmethod.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/test/realm_correct.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar rename to gnovm/cmd/gno/testdata/test/realm_correct.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/test/realm_incorrect.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar rename to gnovm/cmd/gno/testdata/test/realm_incorrect.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/test/realm_sync.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar rename to gnovm/cmd/gno/testdata/test/realm_sync.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/recover.txtar b/gnovm/cmd/gno/testdata/test/recover.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/recover.txtar rename to gnovm/cmd/gno/testdata/test/recover.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/skip.txtar b/gnovm/cmd/gno/testdata/test/skip.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/skip.txtar rename to gnovm/cmd/gno/testdata/test/skip.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/unknown_package.txtar b/gnovm/cmd/gno/testdata/test/unknown_package.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/unknown_package.txtar rename to gnovm/cmd/gno/testdata/test/unknown_package.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar b/gnovm/cmd/gno/testdata/test/valid_filetest.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/valid_filetest.txtar rename to gnovm/cmd/gno/testdata/test/valid_filetest.txtar diff --git a/gnovm/cmd/gno/testdata/gno_test/valid_test.txtar b/gnovm/cmd/gno/testdata/test/valid_test.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_test/valid_test.txtar rename to gnovm/cmd/gno/testdata/test/valid_test.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_build_error.txtar rename to gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/gobuild_flag_parse_error.txtar rename to gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/invalid_import.txtar rename to gnovm/cmd/gno/testdata/transpile/invalid_import.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/no_args.txtar rename to gnovm/cmd/gno/testdata/transpile/no_args.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/parse_error.txtar rename to gnovm/cmd/gno/testdata/transpile/parse_error.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_empty_dir.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_file.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_gobuild_flag.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_output_flag.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_output_gobuild.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_file.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_package.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar diff --git a/gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar similarity index 100% rename from gnovm/cmd/gno/testdata/gno_transpile/valid_transpile_tree.txtar rename to gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar diff --git a/gnovm/cmd/gno/testdata_test.go b/gnovm/cmd/gno/testdata_test.go index 15bc8d96e26..6b1bbd1d459 100644 --- a/gnovm/cmd/gno/testdata_test.go +++ b/gnovm/cmd/gno/testdata_test.go @@ -24,7 +24,6 @@ func Test_Scripts(t *testing.T) { } name := dir.Name() - t.Logf("testing: %s", name) t.Run(name, func(t *testing.T) { updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS")) p := testscript.Params{ diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/transpile_test.go index 827c09e23f1..5a03ddc7657 100644 --- a/gnovm/cmd/gno/transpile_test.go +++ b/gnovm/cmd/gno/transpile_test.go @@ -6,29 +6,9 @@ import ( "strconv" "testing" - "github.com/rogpeppe/go-internal/testscript" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/gnolang/gno/gnovm/pkg/integration" ) -func Test_ScriptsTranspile(t *testing.T) { - p := testscript.Params{ - Dir: "testdata/gno_transpile", - } - - if coverdir, ok := integration.ResolveCoverageDir(); ok { - err := integration.SetupTestscriptsCoverage(&p, coverdir) - require.NoError(t, err) - } - - err := integration.SetupGno(&p, t.TempDir()) - require.NoError(t, err) - - testscript.Run(t, p) -} - func Test_parseGoBuildErrors(t *testing.T) { t.Parallel() diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 338efa20fcc..82d5c69b08b 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -39,7 +39,9 @@ import ( "go/token" "go/types" "os" + "path" "reflect" + "slices" "strconv" "strings" @@ -499,6 +501,18 @@ type MemPackageGetter interface { // If format is true, the code will be automatically updated with the // formatted source code. func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, format bool) error { + return typeCheckMemPackage(mempkg, getter, false, format) +} + +// TypeCheckMemPackageTest performs the same type checks as [TypeCheckMemPackage], +// but allows re-declarations. +// +// Note: like TypeCheckMemPackage, this function ignores tests and filetests. +func TypeCheckMemPackageTest(mempkg *gnovm.MemPackage, getter MemPackageGetter) error { + return typeCheckMemPackage(mempkg, getter, true, false) +} + +func typeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, testing, format bool) error { var errs error imp := &gnoImporter{ getter: getter, @@ -508,6 +522,7 @@ func TypeCheckMemPackage(mempkg *gnovm.MemPackage, getter MemPackageGetter, form errs = multierr.Append(errs, err) }, }, + allowRedefinitions: testing, } imp.cfg.Importer = imp @@ -529,6 +544,9 @@ type gnoImporter struct { getter MemPackageGetter cache map[string]gnoImporterResult cfg *types.Config + + // allow symbol redefinitions? (test standard libraries) + allowRedefinitions bool } // Unused, but satisfies the Importer interface. @@ -559,22 +577,39 @@ func (g *gnoImporter) ImportFrom(path, _ string, _ types.ImportMode) (*types.Pac } func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*types.Package, error) { + // This map is used to allow for function re-definitions, which are allowed + // in Gno (testing context) but not in Go. + // This map links each function identifier with a closure to remove its + // associated declaration. + var delFunc map[string]func() + if g.allowRedefinitions { + delFunc = make(map[string]func()) + } + fset := token.NewFileSet() files := make([]*ast.File, 0, len(mpkg.Files)) var errs error for _, file := range mpkg.Files { + // Ignore non-gno files. + // TODO: support filetest type checking. (should probably handle as each its + // own separate pkg, which should also be typechecked) if !strings.HasSuffix(file.Name, ".gno") || - endsWith(file.Name, []string{"_test.gno", "_filetest.gno"}) { - continue // skip spurious file. + strings.HasSuffix(file.Name, "_test.gno") || + strings.HasSuffix(file.Name, "_filetest.gno") { + continue } const parseOpts = parser.ParseComments | parser.DeclarationErrors | parser.SkipObjectResolution - f, err := parser.ParseFile(fset, file.Name, file.Body, parseOpts) + f, err := parser.ParseFile(fset, path.Join(mpkg.Path, file.Name), file.Body, parseOpts) if err != nil { errs = multierr.Append(errs, err) continue } + if delFunc != nil { + deleteOldIdents(delFunc, f) + } + // enforce formatting if fmt { var buf bytes.Buffer @@ -595,6 +630,24 @@ func (g *gnoImporter) parseCheckMemPackage(mpkg *gnovm.MemPackage, fmt bool) (*t return g.cfg.Check(mpkg.Path, fset, files, nil) } +func deleteOldIdents(idents map[string]func(), f *ast.File) { + for _, decl := range f.Decls { + fd, ok := decl.(*ast.FuncDecl) + if !ok || fd.Recv != nil { // ignore methods + continue + } + if del := idents[fd.Name.Name]; del != nil { + del() + } + decl := decl + idents[fd.Name.Name] = func() { + // NOTE: cannot use the index as a file may contain multiple decls to be removed, + // so removing one would make all "later" indexes wrong. + f.Decls = slices.DeleteFunc(f.Decls, func(d ast.Decl) bool { return decl == d }) + } + } +} + //---------------------------------------- // utility methods diff --git a/gnovm/tests/integ/typecheck_missing_return/gno.mod b/gnovm/tests/integ/typecheck_missing_return/gno.mod new file mode 100644 index 00000000000..3eaaa374994 --- /dev/null +++ b/gnovm/tests/integ/typecheck_missing_return/gno.mod @@ -0,0 +1 @@ +module gno.land/p/integ/valid diff --git a/gnovm/tests/integ/typecheck_missing_return/main.gno b/gnovm/tests/integ/typecheck_missing_return/main.gno new file mode 100644 index 00000000000..5d6e547097c --- /dev/null +++ b/gnovm/tests/integ/typecheck_missing_return/main.gno @@ -0,0 +1,5 @@ +package valid + +func Hello() int { + // no return +}