diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cad086593..c7fcb855f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,8 +7,8 @@ on: pull_request: env: - GO_VERSION: 1.19 - GOLANGCI_LINT_VERSION: v1.47.1 + GO_VERSION: '1.20' + GOLANGCI_LINT_VERSION: v1.53.3 jobs: diff --git a/.golangci.toml b/.golangci.toml deleted file mode 100644 index af3316c0f..000000000 --- a/.golangci.toml +++ /dev/null @@ -1,92 +0,0 @@ -[run] - deadline = "5m" - skip-files = [] - -[linters-settings] - - [linters-settings.govet] - check-shadowing = false - - [linters-settings.gocyclo] - min-complexity = 12.0 - - [linters-settings.maligned] - suggest-new = true - - [linters-settings.goconst] - min-len = 3.0 - min-occurrences = 3.0 - - [linters-settings.misspell] - locale = "US" - -[linters] - enable-all = true - disable = [ - "golint", # deprecated - "scopelint", # deprecated - "interfacer", # deprecated - "maligned", # deprecated - "exhaustivestruct", # deprecated - "lll", - "gas", - "dupl", - "prealloc", - "gocyclo", - "cyclop", - "gochecknoinits", - "gochecknoglobals", - "wsl", - "nlreturn", - "godox", - "funlen", - "gocognit", - "stylecheck", - "gomnd", - "testpackage", - "paralleltest", - "tparallel", - "goerr113", - "wrapcheck", - "nestif", - "exhaustive", - "exhaustruct", - "forbidigo", - "ifshort", - "forcetypeassert", - "varnamelen", - "nosnakecase", - "nonamedreturns", - "nilnil", - "maintidx", - "errorlint", # TODO: must be reactivate before fixes - ] - -[issues] - exclude-use-default = false - max-per-linter = 0 - max-same-issues = 0 - exclude = [] - - [[issues.exclude-rules]] - path = ".+_test\\.go" - linters = ["goconst"] - [[issues.exclude-rules]] - path = ".+_test\\.go" - text = "var-declaration:" - - [[issues.exclude-rules]] - path = "interp/interp.go" - text = "`in` can be `io.Reader`" - [[issues.exclude-rules]] - path = "interp/interp.go" - text = "`out` can be `io.Writer`" - [[issues.exclude-rules]] - path = "interp/interp.go" - text = "`Panic` should conform to the `XxxError` format" - [[issues.exclude-rules]] - path = "interp/interp_eval_test.go" - linters = ["thelper"] - [[issues.exclude-rules]] - path = "interp/debugger.go" - linters = ["containedctx"] diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..f01213a80 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,165 @@ +run: + timeout: 10m + skip-files: [] + +linters-settings: + govet: + check-shadowing: false + gocyclo: + min-complexity: 12 + maligned: + suggest-new: true + goconst: + min-len: 3 + min-occurrences: 3 + funlen: + lines: -1 + statements: 50 + misspell: + locale: US + depguard: + rules: + main: + files: + - $all + allow: + - $gostd + - github.com/traefik/yaegi + tagalign: + align: false + order: + - xml + - json + - yaml + - yml + - toml + - mapstructure + - url + godox: + keywords: + - FIXME + gocritic: + enabled-tags: + - diagnostic + - style + - performance + disabled-checks: + - paramTypeCombine # already handle by gofumpt.extra-rules + - whyNoLint # already handle by nonolint + - unnamedResult + - hugeParam + - sloppyReassign + - rangeValCopy + - octalLiteral + - ptrToRefParam + - appendAssign + - ruleguard + - httpNoBody + - exposedSyncMutex + - importShadow # TODO should be fixed + - commentedOutCode # TODO should be fixed + revive: + rules: + - name: struct-tag + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + disabled: true + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + disabled: true + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + disabled: true + - name: unreachable-code + - name: redefines-builtin-id + +linters: + enable-all: true + disable: + - deadcode # deprecated + - exhaustivestruct # deprecated + - golint # deprecated + - ifshort # deprecated + - interfacer # deprecated + - maligned # deprecated + - nosnakecase # deprecated + - scopelint # deprecated + - structcheck # deprecated + - varcheck # deprecated + - cyclop # duplicate of gocyclo + - sqlclosecheck # not relevant (SQL) + - rowserrcheck # not relevant (SQL) + - execinquery # not relevant (SQL) + - lll + - gas + - dupl + - prealloc + - gocyclo + - cyclop + - gochecknoinits + - gochecknoglobals + - wsl + - nlreturn + - godox + - funlen + - gocognit + - stylecheck + - gomnd + - testpackage + - paralleltest + - tparallel + - goerr113 + - wrapcheck + - nestif + - exhaustive + - exhaustruct + - forbidigo + - ifshort + - forcetypeassert + - varnamelen + - nosnakecase + - nonamedreturns + - nilnil + - maintidx + - dupword # false positives + - errorlint # TODO: must be reactivate after fixes + +issues: + exclude-use-default: false + max-per-linter: 0 + max-same-issues: 0 + exclude: [] + exclude-rules: + - path: .+_test\.go + linters: + - goconst + - path: .+_test\.go + text: 'var-declaration:' + - path: interp/interp.go + text: '`in` can be `io.Reader`' + - path: interp/interp.go + text: '`out` can be `io.Writer`' + - path: interp/interp.go + text: '`Panic` should conform to the `XxxError` format' + - path: interp/interp_eval_test.go + linters: + - thelper + - path: interp/debugger.go + linters: + - containedctx diff --git a/README.md b/README.md index 70cf69dc8..54eac0e06 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ It powers executable Go scripts and plugins, in embedded interpreters or interac * Works everywhere Go works * All Go & runtime resources accessible from script (with control) * Security: `unsafe` and `syscall` packages neither used nor exported by default -* Support Go 1.18 and Go 1.19 (the latest 2 major releases) +* Support the latest 2 major releases of Go (Go 1.19 and Go 1.20) ## Install @@ -30,7 +30,7 @@ import "github.com/traefik/yaegi/interp" ### Command-line executable ```bash -go get -u github.com/traefik/yaegi/cmd/yaegi +go install github.com/traefik/yaegi/cmd/yaegi@latest ``` Note that you can use [rlwrap](https://github.com/hanslub42/rlwrap) (install with your favorite package manager), diff --git a/_test/issue-1571.go b/_test/issue-1571.go new file mode 100644 index 000000000..aa295ad6d --- /dev/null +++ b/_test/issue-1571.go @@ -0,0 +1,20 @@ +package main + +type A struct { + *B[string] +} + +type B[T any] struct { + data T +} + +func main() { + _ = &A{ + B: &B[string]{}, + } + + println("PASS") +} + +// Output: +// PASS diff --git a/example/pkg/pkg_test.go b/example/pkg/pkg_test.go index 80e3c0229..9ce6bbc2a 100644 --- a/example/pkg/pkg_test.go +++ b/example/pkg/pkg_test.go @@ -128,7 +128,7 @@ func TestPackages(t *testing.T) { if test.topImport != "" { topImport = test.topImport } - if _, err = i.Eval(fmt.Sprintf(`import "%s"`, topImport)); err != nil { + if _, err = i.Eval(fmt.Sprintf(`import %q`, topImport)); err != nil { t.Fatal(err) } value, err := i.Eval(`pkg.NewSample()`) diff --git a/extract/extract.go b/extract/extract.go index 1bb324d4c..f83740b6f 100644 --- a/extract/extract.go +++ b/extract/extract.go @@ -130,6 +130,15 @@ func matchList(name string, list []string) (match bool, err error) { return } +// Extractor creates a package with all the symbols from a dependency package. +type Extractor struct { + Dest string // The name of the created package. + License string // License text to be included in the created package, optional. + Exclude []string // Comma separated list of regexp matching symbols to exclude. + Include []string // Comma separated list of regexp matching symbols to include. + Tag []string // Comma separated of build tags to be added to the created package. +} + func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, error) { prefix := "_" + importPath + "_" prefix = strings.NewReplacer("/", "_", "-", "_", ".", "_", "~", "_").Replace(prefix) @@ -283,11 +292,11 @@ func (e *Extractor) genContent(importPath string, p *types.Package) ([]byte, err } for _, t := range e.Tag { - if len(t) != 0 { + if t != "" { buildTags += "," + t } } - if len(buildTags) != 0 && buildTags[0] == ',' { + if buildTags != "" && buildTags[0] == ',' { buildTags = buildTags[1:] } @@ -340,7 +349,7 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string { str = f.Text('g', int(f.Prec())) case constant.Complex: // TODO: not sure how to parse this case - fallthrough + fallthrough //nolint:gocritic // Empty Fallthrough is expected. default: return name } @@ -351,15 +360,6 @@ func fixConst(name string, val constant.Value, imports map[string]bool) string { return fmt.Sprintf("constant.MakeFromLiteral(%q, token.%s, 0)", str, tok) } -// Extractor creates a package with all the symbols from a dependency package. -type Extractor struct { - Dest string // The name of the created package. - License string // License text to be included in the created package, optional. - Exclude []string // Comma separated list of regexp matching symbols to exclude. - Include []string // Comma separated list of regexp matching symbols to include. - Tag []string // Comma separated of build tags to be added to the created package. -} - // importPath checks whether pkgIdent is an existing directory relative to // e.WorkingDir. If yes, it returns the actual import path of the Go package // located in the directory. If it is definitely a relative path, but it does not diff --git a/go.mod b/go.mod index d4a2c9c0f..14e6f6889 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/traefik/yaegi -go 1.18 +go 1.19 diff --git a/interp/interp.go b/interp/interp.go index 766c39242..4b9ff5e55 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -184,14 +184,14 @@ type opt struct { noRun bool // compile, but do not run fastChan bool // disable cancellable chan operations specialStdio bool // allows os.Stdin, os.Stdout, os.Stderr to not be file descriptors - unrestricted bool // allow use of non sandboxed symbols + unrestricted bool // allow use of non-sandboxed symbols } // Interpreter contains global resources and state. type Interpreter struct { - // id is an atomic counter counter used for run cancellation, + // id is an atomic counter used for run cancellation, // only accessed via runid/stop - // Located at start of struct to ensure proper alignment on 32 bit + // Located at start of struct to ensure proper alignment on 32-bit // architectures. id uint64 diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index e4d913af7..3fa8c9b9e 100644 --- a/interp/interp_consistent_test.go +++ b/interp/interp_consistent_test.go @@ -1,6 +1,7 @@ package interp_test import ( + "bytes" "go/build" "io" "os" @@ -176,7 +177,7 @@ func TestInterpConsistencyBuild(t *testing.T) { t.Fatal(err) } - if string(outInterp) != string(outRun) { + if !bytes.Equal(outInterp, outRun) { t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outRun)) } }) @@ -288,8 +289,8 @@ func TestInterpErrorConsistency(t *testing.T) { for _, test := range testCases { t.Run(test.fileName, func(t *testing.T) { - if len(test.expectedInterp) == 0 && len(test.expectedExec) == 0 { - t.Fatal("at least expectedInterp must be define") + if test.expectedInterp == "" && test.expectedExec == "" { + t.Fatal("at least expectedInterp must be defined") } filePath := filepath.Join("..", "_test", test.fileName) @@ -315,7 +316,7 @@ func TestInterpErrorConsistency(t *testing.T) { t.Fatal("An error is expected but got none.") } - if len(test.expectedExec) == 0 && !strings.Contains(string(outRun), test.expectedInterp) { + if test.expectedExec == "" && !strings.Contains(string(outRun), test.expectedInterp) { t.Errorf("got %q, want: %q", string(outRun), test.expectedInterp) } else if !strings.Contains(string(outRun), test.expectedExec) { t.Errorf("got %q, want: %q", string(outRun), test.expectedExec) diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index c4ce59904..59bd7dd2d 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -1252,10 +1252,9 @@ func TestConcurrentEvals2(t *testing.T) { if hello1 { if l == "hello world2" { break - } else { - done <- fmt.Errorf("unexpected output: %v", l) - return } + done <- fmt.Errorf("unexpected output: %v", l) + return } if l == "hello world1" { hello1 = true @@ -1335,7 +1334,7 @@ func TestConcurrentEvals3(t *testing.T) { }() for _, v := range input { - in := strings.NewReader(fmt.Sprintf("println(\"%s\")\n", v)) + in := strings.NewReader(fmt.Sprintf("println(%q)\n", v)) if _, err := io.Copy(poutin, in); err != nil { t.Fatal(err) } @@ -1880,25 +1879,25 @@ func TestIssue1383(t *testing.T) { } ` - interp := interp.New(interp.Options{}) - err := interp.Use(stdlib.Symbols) + i := interp.New(interp.Options{}) + err := i.Use(stdlib.Symbols) if err != nil { t.Fatal(err) } - _, err = interp.Eval(`import "fmt"`) + _, err = i.Eval(`import "fmt"`) if err != nil { t.Fatal(err) } - ast, err := parser.ParseFile(interp.FileSet(), "_.go", src, parser.DeclarationErrors) + ast, err := parser.ParseFile(i.FileSet(), "_.go", src, parser.DeclarationErrors) if err != nil { t.Fatal(err) } - prog, err := interp.CompileAST(ast) + prog, err := i.CompileAST(ast) if err != nil { t.Fatal(err) } - _, err = interp.Execute(prog) + _, err = i.Execute(prog) if err != nil { t.Fatal(err) } diff --git a/interp/interp_file_test.go b/interp/interp_file_test.go index d9f1e208f..b2bdde0a2 100644 --- a/interp/interp_file_test.go +++ b/interp/interp_file_test.go @@ -98,7 +98,7 @@ func wantedFromComment(p string) (res string, goPath string, err bool) { if err != nil { panic(err) } - goPath = filepath.Join(wd, "../_test", strings.TrimPrefix(parts[0], "GOPATH:")) + goPath = filepath.Join(wd, "..", "_test", strings.TrimPrefix(parts[0], "GOPATH:")) } if strings.HasPrefix(text, "Output:\n") { return strings.TrimPrefix(text, "Output:\n"), goPath, false diff --git a/interp/run.go b/interp/run.go index 74d8c1b60..b4e9803f7 100644 --- a/interp/run.go +++ b/interp/run.go @@ -1958,7 +1958,7 @@ func getMethodByName(n *node) { } if val, ok = val0.Field(i).Interface().(valueInterface); ok { break - // TODO: should we keep track of all the the vals that are indeed valueInterface, + // TODO: should we keep track of all the vals that are indeed valueInterface, // so that later on we can call MethodByName on all of them until one matches? } } diff --git a/interp/src.go b/interp/src.go index 762449c19..bf167c376 100644 --- a/interp/src.go +++ b/interp/src.go @@ -209,7 +209,7 @@ func (interp *Interpreter) pkgDir(goPath string, root, importPath string) (strin return dir, root, nil // found! } - if len(root) == 0 { + if root == "" { if interp.context.GOPATH == "" { return "", "", fmt.Errorf("unable to find source related to: %q. Either the GOPATH environment variable, or the Interpreter.Options.GoPath needs to be set", importPath) } diff --git a/interp/type.go b/interp/type.go index 6c493b31b..ddfbbea09 100644 --- a/interp/type.go +++ b/interp/type.go @@ -484,7 +484,7 @@ func nodeType2(interp *Interpreter, sc *scope, n *node, seen []*node) (t *itype, length = int(vInt(sym.rval)) default: // Size is defined by a numeric constant expression. - ok := false + var ok bool if _, err := interp.cfg(c0, sc, sc.pkgID, sc.pkgName); err != nil { if strings.Contains(err.Error(), " undefined: ") { incomplete = true @@ -1230,6 +1230,8 @@ func fieldName(n *node) string { return fieldName(n.child[1]) case starExpr: return fieldName(n.child[0]) + case indexExpr: + return fieldName(n.child[0]) case identExpr: return n.ident default: