From 69921f4a7ec081c5b37dae13e3372003e4efd49f Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 16 Mar 2022 10:31:57 -0700 Subject: [PATCH] libgo: update to final Go 1.18 release Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/393377 --- gcc/go/gofrontend/MERGE | 2 +- libgo/MERGE | 2 +- libgo/VERSION | 2 +- libgo/go/cmd/go/alldocs.go | 24 +- libgo/go/cmd/go/internal/modfetch/coderepo.go | 53 +++- libgo/go/cmd/go/internal/modfetch/coderepo_test.go | 48 ++++ libgo/go/cmd/go/internal/modload/init.go | 5 + libgo/go/cmd/go/internal/modload/modfile.go | 2 +- libgo/go/cmd/go/internal/run/run.go | 5 +- libgo/go/cmd/go/internal/vcs/vcs.go | 2 +- libgo/go/cmd/go/internal/workcmd/edit.go | 5 +- libgo/go/cmd/go/internal/workcmd/init.go | 2 + libgo/go/cmd/go/internal/workcmd/sync.go | 3 + libgo/go/cmd/go/internal/workcmd/use.go | 10 +- libgo/go/cmd/go/internal/workcmd/work.go | 8 +- .../cmd/go/testdata/script/run_work_versioned.txt | 16 ++ .../script/test_fuzz_minimize_dirty_cov.txt | 84 ++++++ .../script/test_fuzz_minimize_interesting.txt | 11 - .../go/testdata/script/version_buildvcs_git.txt | 2 +- libgo/go/cmd/go/testdata/script/work.txt | 4 +- libgo/go/cmd/go/testdata/script/work_edit.txt | 8 +- libgo/go/cmd/go/testdata/script/work_init_path.txt | 17 ++ libgo/go/cmd/go/testdata/script/work_use.txt | 10 +- .../go/cmd/go/testdata/script/work_use_deleted.txt | 6 +- libgo/go/cmd/go/testdata/script/work_use_dot.txt | 4 +- .../go/cmd/go/testdata/script/work_use_noargs.txt | 11 + libgo/go/encoding/xml/marshal.go | 2 +- libgo/go/encoding/xml/marshal_test.go | 36 +++ libgo/go/go/internal/gcimporter/iimport.go | 23 +- libgo/go/go/printer/nodes.go | 24 +- libgo/go/go/printer/testdata/generics.golden | 26 ++ libgo/go/go/printer/testdata/generics.input | 25 ++ libgo/go/go/types/api.go | 28 +- libgo/go/go/types/api_test.go | 261 ++++++++++--------- libgo/go/go/types/assignments.go | 13 +- libgo/go/go/types/call.go | 15 +- libgo/go/go/types/check.go | 58 +++-- libgo/go/go/types/conversions.go | 5 +- libgo/go/go/types/decl.go | 2 - libgo/go/go/types/errorcodes.go | 5 - libgo/go/go/types/eval.go | 4 +- libgo/go/go/types/expr.go | 9 +- libgo/go/go/types/index.go | 1 + libgo/go/go/types/infer.go | 128 +++++++-- libgo/go/go/types/instantiate.go | 10 +- libgo/go/go/types/interface.go | 28 +- libgo/go/go/types/lookup.go | 3 +- libgo/go/go/types/named.go | 44 +++- libgo/go/go/types/predicates.go | 11 +- libgo/go/go/types/signature.go | 144 +++++----- libgo/go/go/types/stmt.go | 17 +- libgo/go/go/types/subst.go | 5 +- libgo/go/go/types/termlist.go | 9 - libgo/go/go/types/termlist_test.go | 29 --- libgo/go/go/types/testdata/examples/inference.go2 | 18 +- libgo/go/go/types/testdata/examples/methods.go2 | 2 +- .../go/go/types/testdata/fixedbugs/issue41124.go2 | 10 +- .../go/go/types/testdata/fixedbugs/issue45548.go2 | 2 +- .../go/go/types/testdata/fixedbugs/issue51229.go2 | 164 ++++++++++++ .../go/go/types/testdata/fixedbugs/issue51232.go2 | 30 +++ .../go/go/types/testdata/fixedbugs/issue51233.go2 | 27 ++ .../go/go/types/testdata/fixedbugs/issue51257.go2 | 46 ++++ .../go/go/types/testdata/fixedbugs/issue51335.go2 | 16 ++ .../go/go/types/testdata/fixedbugs/issue51339.go2 | 18 ++ libgo/go/go/types/testdata/fixedbugs/issue51360.go | 13 + .../go/go/types/testdata/fixedbugs/issue51376.go2 | 24 ++ .../go/go/types/testdata/fixedbugs/issue51386.go2 | 17 ++ libgo/go/go/types/testdata/fixedbugs/issue51437.go | 17 ++ .../go/go/types/testdata/fixedbugs/issue51472.go2 | 54 ++++ libgo/go/go/types/testdata/fixedbugs/issue51509.go | 7 + .../go/go/types/testdata/fixedbugs/issue51578.go2 | 17 ++ .../go/go/types/testdata/fixedbugs/issue51593.go2 | 13 + libgo/go/go/types/type.go | 4 +- libgo/go/go/types/typeparam.go | 31 +-- libgo/go/go/types/typeset.go | 90 ++++--- libgo/go/go/types/typeset_test.go | 10 +- libgo/go/go/types/typexpr.go | 32 ++- libgo/go/go/types/unify.go | 19 +- libgo/go/go/types/union.go | 18 +- libgo/go/go/types/universe.go | 2 +- libgo/go/go/types/validtype.go | 2 +- libgo/go/internal/fuzz/coverage.go | 11 + libgo/go/internal/fuzz/encoding.go | 195 +++++++++++--- libgo/go/internal/fuzz/encoding_test.go | 290 ++++++++++++++++++--- libgo/go/internal/fuzz/worker.go | 16 +- libgo/go/net/net.go | 12 + libgo/go/runtime/mfinal_test.go | 9 + libgo/go/runtime/testdata/testprogcgo/aprof.go | 2 +- libgo/go/strings/builder.go | 7 +- libgo/go/syscall/syscall_unix_test.go | 27 -- libgo/merge.sh | 2 +- libgo/misc/cgo/testsanitizers/asan_test.go | 2 +- 92 files changed, 1983 insertions(+), 604 deletions(-) create mode 100644 libgo/go/cmd/go/testdata/script/run_work_versioned.txt create mode 100644 libgo/go/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt create mode 100644 libgo/go/cmd/go/testdata/script/work_init_path.txt create mode 100644 libgo/go/cmd/go/testdata/script/work_use_noargs.txt create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51229.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51232.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51233.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51257.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51335.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51339.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51360.go create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51376.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51386.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51437.go create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51472.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51509.go create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51578.go2 create mode 100644 libgo/go/go/types/testdata/fixedbugs/issue51593.go2 diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index d9b1269..afaccb0 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -5042f7efbdb2d64537dfef53a19e96ee5ec4db2d +7f33baa09a8172bb2c5f1ca0435d9efe3e194c9b The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/libgo/MERGE b/libgo/MERGE index 11da9a2..85536ad 100644 --- a/libgo/MERGE +++ b/libgo/MERGE @@ -1,4 +1,4 @@ -cb5a598d7f2ebd276686403d141a97c026d33458 +4aa1efed4853ea067d665a952eee77c52faac774 The first line of this file holds the git revision number of the last merge done from the master library sources. diff --git a/libgo/VERSION b/libgo/VERSION index 6a12199..39560f0 100644 --- a/libgo/VERSION +++ b/libgo/VERSION @@ -1 +1 @@ -go1.18rc1 +go1.18 diff --git a/libgo/go/cmd/go/alldocs.go b/libgo/go/cmd/go/alldocs.go index 63e7900..420529b 100644 --- a/libgo/go/cmd/go/alldocs.go +++ b/libgo/go/cmd/go/alldocs.go @@ -1356,7 +1356,7 @@ // // Workspace maintenance // -// Go workspace provides access to operations on workspaces. +// Work provides access to operations on workspaces. // // Note that support for workspaces is built into many other commands, not // just 'go work'. @@ -1364,6 +1364,12 @@ // See 'go help modules' for information about Go's module system of which // workspaces are a part. // +// See https://go.dev/ref/mod#workspaces for an in-depth reference on +// workspaces. +// +// See https://go.dev/doc/tutorial/workspaces for an introductory +// tutorial on workspaces. +// // A workspace is specified by a go.work file that specifies a set of // module directories with the "use" directive. These modules are used as // root modules by the go command for builds and related operations. A @@ -1485,9 +1491,8 @@ // Version string // } // -// See the workspaces design proposal at -// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for -// more information. +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. // // // Initialize workspace file @@ -1507,6 +1512,9 @@ // Each argument path is added to a use directive in the go.work file. The // current go version will also be listed in the go.work file. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Sync workspace build list to modules // @@ -1530,12 +1538,15 @@ // build list's version of each module is always the same or higher than // that in each workspace module. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Add modules to workspace file // // Usage: // -// go work use [-r] [moddirs] +// go work use [-r] moddirs // // Use provides a command-line interface for adding // directories, optionally recursively, to a go.work file. @@ -1549,6 +1560,9 @@ // were specified as arguments: namely, use directives will be added for // directories that exist, and removed for directories that do not exist. // +// See the workspaces reference at https://go.dev/ref/mod#workspaces +// for more information. +// // // Compile and run Go program // diff --git a/libgo/go/cmd/go/internal/modfetch/coderepo.go b/libgo/go/cmd/go/internal/modfetch/coderepo.go index 2206c7c..dfaf16d 100644 --- a/libgo/go/cmd/go/internal/modfetch/coderepo.go +++ b/libgo/go/cmd/go/internal/modfetch/coderepo.go @@ -305,17 +305,46 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // // (If the version is +incompatible, then the go.mod file must not exist: // +incompatible is not an ongoing opt-out from semantic import versioning.) - var canUseIncompatible func() bool - canUseIncompatible = func() bool { - var ok bool - if r.codeDir == "" && r.pathMajor == "" { + incompatibleOk := map[string]bool{} + canUseIncompatible := func(v string) bool { + if r.codeDir != "" || r.pathMajor != "" { + // A non-empty codeDir indicates a module within a subdirectory, + // which necessarily has a go.mod file indicating the module boundary. + // A non-empty pathMajor indicates a module path with a major-version + // suffix, which must match. + return false + } + + ok, seen := incompatibleOk[""] + if !seen { _, errGoMod := r.code.ReadFile(info.Name, "go.mod", codehost.MaxGoMod) - if errGoMod != nil { - ok = true + ok = (errGoMod != nil) + incompatibleOk[""] = ok + } + if !ok { + // A go.mod file exists at the repo root. + return false + } + + // Per https://go.dev/issue/51324, previous versions of the 'go' command + // didn't always check for go.mod files in subdirectories, so if the user + // requests a +incompatible version explicitly, we should continue to allow + // it. Otherwise, if vN/go.mod exists, expect that release tags for that + // major version are intended for the vN module. + if v != "" && !strings.HasSuffix(statVers, "+incompatible") { + major := semver.Major(v) + ok, seen = incompatibleOk[major] + if !seen { + _, errGoModSub := r.code.ReadFile(info.Name, path.Join(major, "go.mod"), codehost.MaxGoMod) + ok = (errGoModSub != nil) + incompatibleOk[major] = ok + } + if !ok { + return false } } - canUseIncompatible = func() bool { return ok } - return ok + + return true } // checkCanonical verifies that the canonical version v is compatible with the @@ -367,7 +396,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e base := strings.TrimSuffix(v, "+incompatible") var errIncompatible error if !module.MatchPathMajor(base, r.pathMajor) { - if canUseIncompatible() { + if canUseIncompatible(base) { v = base + "+incompatible" } else { if r.pathMajor != "" { @@ -495,7 +524,7 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // Save the highest non-retracted canonical tag for the revision. // If we don't find a better match, we'll use it as the canonical version. if tagIsCanonical && semver.Compare(highestCanonical, v) < 0 && !isRetracted(v) { - if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible() { + if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible(v) { highestCanonical = v } } @@ -513,12 +542,12 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e // retracted versions. allowedMajor := func(major string) func(v string) bool { return func(v string) bool { - return (major == "" || semver.Major(v) == major) && !isRetracted(v) + return ((major == "" && canUseIncompatible(v)) || semver.Major(v) == major) && !isRetracted(v) } } if pseudoBase == "" { var tag string - if r.pseudoMajor != "" || canUseIncompatible() { + if r.pseudoMajor != "" || canUseIncompatible("") { tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor(r.pseudoMajor)) } else { // Allow either v1 or v0, but not incompatible higher versions. diff --git a/libgo/go/cmd/go/internal/modfetch/coderepo_test.go b/libgo/go/cmd/go/internal/modfetch/coderepo_test.go index d98ea87..bb9268a 100644 --- a/libgo/go/cmd/go/internal/modfetch/coderepo_test.go +++ b/libgo/go/cmd/go/internal/modfetch/coderepo_test.go @@ -458,6 +458,54 @@ var codeRepoTests = []codeRepoTest{ rev: "v3.0.0-devel", err: `resolves to version v0.1.1-0.20220203155313-d59622f6e4d7 (v3.0.0-devel is not a tag)`, }, + + // If v2/go.mod exists, then we should prefer to match the "v2" + // pseudo-versions to the nested module, and resolve the module in the parent + // directory to only compatible versions. + // + // However (https://go.dev/issue/51324), previous versions of the 'go' command + // didn't always do so, so if the user explicitly requests a +incompatible + // version (as would be present in an existing go.mod file), we should + // continue to allow it. + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "80beb17a1603", + version: "v0.0.0-20220222205507-80beb17a1603", + name: "80beb17a16036f17a5aedd1bb5bd6d407b3c6dc5", + short: "80beb17a1603", + time: time.Date(2022, 2, 22, 20, 55, 7, 0, time.UTC), + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.0", + err: `module contains a go.mod file, so module path must match major version ("vcs-test.golang.org/git/v2sub.git/v2")`, + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.1-0.20220222205507-80beb17a1603", + err: `module contains a go.mod file, so module path must match major version ("vcs-test.golang.org/git/v2sub.git/v2")`, + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.0+incompatible", + version: "v2.0.0+incompatible", + name: "5fcd3eaeeb391d399f562fd45a50dac9fc34ae8b", + short: "5fcd3eaeeb39", + time: time.Date(2022, 2, 22, 20, 53, 33, 0, time.UTC), + }, + { + vcs: "git", + path: "vcs-test.golang.org/git/v2sub.git", + rev: "v2.0.1-0.20220222205507-80beb17a1603+incompatible", + version: "v2.0.1-0.20220222205507-80beb17a1603+incompatible", + name: "80beb17a16036f17a5aedd1bb5bd6d407b3c6dc5", + short: "80beb17a1603", + time: time.Date(2022, 2, 22, 20, 55, 7, 0, time.UTC), + }, } func TestCodeRepo(t *testing.T) { diff --git a/libgo/go/cmd/go/internal/modload/init.go b/libgo/go/cmd/go/internal/modload/init.go index a070666..f960edd 100644 --- a/libgo/go/cmd/go/internal/modload/init.go +++ b/libgo/go/cmd/go/internal/modload/init.go @@ -288,6 +288,11 @@ func BinDir() string { // operate in workspace mode. It should not be called by other commands, // for example 'go mod tidy', that don't operate in workspace mode. func InitWorkfile() { + if RootMode == NoRoot { + workFilePath = "" + return + } + switch gowork := cfg.Getenv("GOWORK"); gowork { case "off": workFilePath = "" diff --git a/libgo/go/cmd/go/internal/modload/modfile.go b/libgo/go/cmd/go/internal/modload/modfile.go index 627cf1d..75c278a 100644 --- a/libgo/go/cmd/go/internal/modload/modfile.go +++ b/libgo/go/cmd/go/internal/modload/modfile.go @@ -802,7 +802,7 @@ var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersi // an absolute path or a relative path starting with a '.' or '..' // path component. func ToDirectoryPath(path string) string { - if modfile.IsDirectoryPath(path) { + if path == "." || modfile.IsDirectoryPath(path) { return path } // The path is not a relative path or an absolute path, so make it relative diff --git a/libgo/go/cmd/go/internal/run/run.go b/libgo/go/cmd/go/internal/run/run.go index 00a3e4b..312b49e 100644 --- a/libgo/go/cmd/go/internal/run/run.go +++ b/libgo/go/cmd/go/internal/run/run.go @@ -73,8 +73,6 @@ func printStderr(args ...any) (int, error) { } func runRun(ctx context.Context, cmd *base.Command, args []string) { - modload.InitWorkfile() - if shouldUseOutsideModuleMode(args) { // Set global module flags for 'go run cmd@version'. // This must be done before modload.Init, but we need to call work.BuildInit @@ -84,7 +82,10 @@ func runRun(ctx context.Context, cmd *base.Command, args []string) { modload.RootMode = modload.NoRoot modload.AllowMissingModuleImports() modload.Init() + } else { + modload.InitWorkfile() } + work.BuildInit() var b work.Builder b.Init() diff --git a/libgo/go/cmd/go/internal/vcs/vcs.go b/libgo/go/cmd/go/internal/vcs/vcs.go index fd521b2..2acabf7 100644 --- a/libgo/go/cmd/go/internal/vcs/vcs.go +++ b/libgo/go/cmd/go/internal/vcs/vcs.go @@ -312,7 +312,7 @@ func gitStatus(vcsGit *Cmd, rootDir string) (Status, error) { // uncommitted files and skip tagging revision / committime. var rev string var commitTime time.Time - out, err = vcsGit.runOutputVerboseOnly(rootDir, "show -s --no-show-signature --format=%H:%ct") + out, err = vcsGit.runOutputVerboseOnly(rootDir, "-c log.showsignature=false show -s --format=%H:%ct") if err != nil && !uncommitted { return Status{}, err } else if err == nil { diff --git a/libgo/go/cmd/go/internal/workcmd/edit.go b/libgo/go/cmd/go/internal/workcmd/edit.go index 05f4f3d..1478c19 100644 --- a/libgo/go/cmd/go/internal/workcmd/edit.go +++ b/libgo/go/cmd/go/internal/workcmd/edit.go @@ -84,9 +84,8 @@ writing it back to go.mod. The JSON output corresponds to these Go types: Version string } -See the workspaces design proposal at -https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for -more information. +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, } diff --git a/libgo/go/cmd/go/internal/workcmd/init.go b/libgo/go/cmd/go/internal/workcmd/init.go index 63bee6e..c2513ba 100644 --- a/libgo/go/cmd/go/internal/workcmd/init.go +++ b/libgo/go/cmd/go/internal/workcmd/init.go @@ -27,6 +27,8 @@ modules will be created. Each argument path is added to a use directive in the go.work file. The current go version will also be listed in the go.work file. +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, Run: runInit, } diff --git a/libgo/go/cmd/go/internal/workcmd/sync.go b/libgo/go/cmd/go/internal/workcmd/sync.go index b0f61c5..7712eb6 100644 --- a/libgo/go/cmd/go/internal/workcmd/sync.go +++ b/libgo/go/cmd/go/internal/workcmd/sync.go @@ -33,6 +33,9 @@ if the dependency module's version is not already the same as the build list's version. Note that Minimal Version Selection guarantees that the build list's version of each module is always the same or higher than that in each workspace module. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, Run: runSync, } diff --git a/libgo/go/cmd/go/internal/workcmd/use.go b/libgo/go/cmd/go/internal/workcmd/use.go index 1ee2d4e..e20041f 100644 --- a/libgo/go/cmd/go/internal/workcmd/use.go +++ b/libgo/go/cmd/go/internal/workcmd/use.go @@ -20,7 +20,7 @@ import ( ) var cmdUse = &base.Command{ - UsageLine: "go work use [-r] [moddirs]", + UsageLine: "go work use [-r] moddirs", Short: "add modules to workspace file", Long: `Use provides a command-line interface for adding directories, optionally recursively, to a go.work file. @@ -33,6 +33,9 @@ The -r flag searches recursively for modules in the argument directories, and the use command operates as if each of the directories were specified as arguments: namely, use directives will be added for directories that exist, and removed for directories that do not exist. + +See the workspaces reference at https://go.dev/ref/mod#workspaces +for more information. `, } @@ -101,6 +104,9 @@ func runUse(ctx context.Context, cmd *base.Command, args []string) { keepDirs[absDir] = dir } + if len(args) == 0 { + base.Fatalf("go: 'go work use' requires one or more directory arguments") + } for _, useDir := range args { if !*useR { lookDir(useDir) @@ -186,5 +192,5 @@ func pathRel(workDir, dir string) (abs, canonical string) { // Normalize relative paths to use slashes, so that checked-in go.work // files with relative paths within the repo are platform-independent. - return abs, filepath.ToSlash(rel) + return abs, modload.ToDirectoryPath(rel) } diff --git a/libgo/go/cmd/go/internal/workcmd/work.go b/libgo/go/cmd/go/internal/workcmd/work.go index d3cc250..39c81e8 100644 --- a/libgo/go/cmd/go/internal/workcmd/work.go +++ b/libgo/go/cmd/go/internal/workcmd/work.go @@ -12,7 +12,7 @@ import ( var CmdWork = &base.Command{ UsageLine: "go work", Short: "workspace maintenance", - Long: `Go workspace provides access to operations on workspaces. + Long: `Work provides access to operations on workspaces. Note that support for workspaces is built into many other commands, not just 'go work'. @@ -20,6 +20,12 @@ just 'go work'. See 'go help modules' for information about Go's module system of which workspaces are a part. +See https://go.dev/ref/mod#workspaces for an in-depth reference on +workspaces. + +See https://go.dev/doc/tutorial/workspaces for an introductory +tutorial on workspaces. + A workspace is specified by a go.work file that specifies a set of module directories with the "use" directive. These modules are used as root modules by the go command for builds and related operations. A diff --git a/libgo/go/cmd/go/testdata/script/run_work_versioned.txt b/libgo/go/cmd/go/testdata/script/run_work_versioned.txt new file mode 100644 index 0000000..eb0f22d --- /dev/null +++ b/libgo/go/cmd/go/testdata/script/run_work_versioned.txt @@ -0,0 +1,16 @@ +[short] skip +go run example.com/printversion@v0.1.0 +stdout '^main is example.com/printversion v0.1.0$' + +-- go.work -- +go 1.18 + +use ( + . +) +-- go.mod -- +module example + +go 1.18 + +require example.com/printversion v1.0.0 diff --git a/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt b/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt new file mode 100644 index 0000000..571bf75 --- /dev/null +++ b/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_dirty_cov.txt @@ -0,0 +1,84 @@ +# Test that minimization doesn't use dirty coverage snapshots when it +# is unable to actually minimize the input. We do this by checking that +# a expected value appears in the cache. If a dirty coverage map is used +# (i.e. the coverage map generated during the last minimization step, +# rather than the map provided with the initial input) then this value +# is unlikely to appear in the cache, since the map generated during +# the last minimization step should not increase the coverage. + +[short] skip +[!fuzz-instrumented] skip + +env GOCACHE=$WORK/gocache +go test -fuzz=FuzzCovMin -fuzztime=25s -test.fuzzcachedir=$GOCACHE/fuzz +go run check_file/main.go $GOCACHE/fuzz/FuzzCovMin abcd + +-- go.mod -- +module test + +-- covmin_test.go -- +package covmin + +import "testing" + +func FuzzCovMin(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + if len(data) >= 4 && data[0] == 'a' && data[1] == 'b' && data[2] == 'c' && data[3] == 'd' { + return + } + }) +} + +-- check_file/main.go -- +package main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" +) + +func checkFile(name, expected string) (bool, error) { + data, err := os.ReadFile(name) + if err != nil { + return false, err + } + for _, line := range bytes.Split(data, []byte("\n")) { + m := valRe.FindSubmatch(line) + if m == nil { + continue + } + fmt.Println(strconv.Unquote(string(m[1]))) + if s, err := strconv.Unquote(string(m[1])); err != nil { + return false, err + } else if s == expected { + return true, nil + } + } + return false, nil +} + +var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) + +func main() { + dir, expected := os.Args[1], os.Args[2] + ents, err := os.ReadDir(dir) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + for _, ent := range ents { + name := filepath.Join(dir, ent.Name()) + if good, err := checkFile(name, expected); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } else if good { + os.Exit(0) + } + } + fmt.Fprintln(os.Stderr, "input over minimized") + os.Exit(1) +} diff --git a/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt b/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt index 5d0de17..a09e85b 100644 --- a/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt +++ b/libgo/go/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt @@ -127,19 +127,8 @@ func FuzzMinCache(f *testing.F) { if bytes.Equal(buf, seed) { return } - if n := sum(buf); n < 0 { - t.Error("sum cannot be negative") - } }) } - -func sum(buf []byte) int { - n := 0 - for _, b := range buf { - n += int(b) - } - return n -} -- check_testdata/check_testdata.go -- //go:build ignore // +build ignore diff --git a/libgo/go/cmd/go/testdata/script/version_buildvcs_git.txt b/libgo/go/cmd/go/testdata/script/version_buildvcs_git.txt index 86d1de0..4470687 100644 --- a/libgo/go/cmd/go/testdata/script/version_buildvcs_git.txt +++ b/libgo/go/cmd/go/testdata/script/version_buildvcs_git.txt @@ -111,7 +111,7 @@ rm $GOBIN/d$GOEXE go list -x ./... stdout -count=3 '^example.com' stderr -count=1 '^git status' -stderr -count=1 '^git show' +stderr -count=1 '^git -c log.showsignature=false show' -- $WORK/fakebin/git -- #!/bin/sh diff --git a/libgo/go/cmd/go/testdata/script/work.txt b/libgo/go/cmd/go/testdata/script/work.txt index a10bf5a..fa1558f 100644 --- a/libgo/go/cmd/go/testdata/script/work.txt +++ b/libgo/go/cmd/go/testdata/script/work.txt @@ -4,7 +4,7 @@ go env GOWORK ! stdout . go work init ./a ./b -cmp go.work go.work.want +cmpenv go.work go.work.want go env GOWORK stdout '^'$WORK'(\\|/)gopath(\\|/)src(\\|/)go.work$' @@ -69,7 +69,7 @@ use ( ../src/a ) -- go.work.want -- -go 1.18 +go $goversion use ( ./a diff --git a/libgo/go/cmd/go/testdata/script/work_edit.txt b/libgo/go/cmd/go/testdata/script/work_edit.txt index 71959ca..278afb7 100644 --- a/libgo/go/cmd/go/testdata/script/work_edit.txt +++ b/libgo/go/cmd/go/testdata/script/work_edit.txt @@ -1,10 +1,10 @@ # Test editing go.work files. go work init m -cmp go.work go.work.want_initial +cmpenv go.work go.work.want_initial go work edit -use n -cmp go.work go.work.want_use_n +cmpenv go.work go.work.want_use_n go work edit -go 1.18 cmp go.work go.work.want_go_118 @@ -39,11 +39,11 @@ module m go 1.18 -- go.work.want_initial -- -go 1.18 +go $goversion use ./m -- go.work.want_use_n -- -go 1.18 +go $goversion use ( ./m diff --git a/libgo/go/cmd/go/testdata/script/work_init_path.txt b/libgo/go/cmd/go/testdata/script/work_init_path.txt new file mode 100644 index 0000000..e397788 --- /dev/null +++ b/libgo/go/cmd/go/testdata/script/work_init_path.txt @@ -0,0 +1,17 @@ +# Regression test for https://go.dev/issue/51448. +# 'go work init . foo/bar' should produce a go.work file +# with the same paths as 'go work init; go work use -r .'. + +go work init . foo/bar +mv go.work go.work.init + +go work init +go work use -r . +cmp go.work go.work.init + +-- go.mod -- +module example +go 1.18 +-- foo/bar/go.mod -- +module example +go 1.18 diff --git a/libgo/go/cmd/go/testdata/script/work_use.txt b/libgo/go/cmd/go/testdata/script/work_use.txt index f5ea89c..12c8cec 100644 --- a/libgo/go/cmd/go/testdata/script/work_use.txt +++ b/libgo/go/cmd/go/testdata/script/work_use.txt @@ -14,16 +14,16 @@ use ( go 1.18 use ( - foo - foo/bar/baz + ./foo + ./foo/bar/baz ) -- go.want_work_other -- go 1.18 use ( - foo - foo/bar/baz - other + ./foo + ./foo/bar/baz + ./other ) -- foo/go.mod -- module foo diff --git a/libgo/go/cmd/go/testdata/script/work_use_deleted.txt b/libgo/go/cmd/go/testdata/script/work_use_deleted.txt index 660eb56..b379cbc 100644 --- a/libgo/go/cmd/go/testdata/script/work_use_deleted.txt +++ b/libgo/go/cmd/go/testdata/script/work_use_deleted.txt @@ -6,13 +6,13 @@ go 1.18 use ( . - sub - sub/dir/deleted + ./sub + ./sub/dir/deleted ) -- go.work.want -- go 1.18 -use sub/dir +use ./sub/dir -- sub/README.txt -- A go.mod file has been deleted from this directory. In addition, the entire subdirectory sub/dir/deleted diff --git a/libgo/go/cmd/go/testdata/script/work_use_dot.txt b/libgo/go/cmd/go/testdata/script/work_use_dot.txt index ccd83d6..8f21042 100644 --- a/libgo/go/cmd/go/testdata/script/work_use_dot.txt +++ b/libgo/go/cmd/go/testdata/script/work_use_dot.txt @@ -31,7 +31,7 @@ grep '^use ["]?'$PWD'["]?$' ../../go.work # resulting workspace would contain a duplicate module. cp ../../go.work.orig ../../go.work ! go work use $PWD . -stderr '^go: already added "bar/baz" as "'$PWD'"$' +stderr '^go: already added "\./bar/baz" as "'$PWD'"$' cmp ../../go.work ../../go.work.orig @@ -43,7 +43,7 @@ go 1.18 -- go.work.rel -- go 1.18 -use bar/baz +use ./bar/baz -- bar/baz/go.mod -- module example/bar/baz go 1.18 diff --git a/libgo/go/cmd/go/testdata/script/work_use_noargs.txt b/libgo/go/cmd/go/testdata/script/work_use_noargs.txt new file mode 100644 index 0000000..ca05434 --- /dev/null +++ b/libgo/go/cmd/go/testdata/script/work_use_noargs.txt @@ -0,0 +1,11 @@ +# For now, 'go work use' requires arguments. +# (Eventually, we may may it implicitly behave like 'go work use .'. + +! go work use +stderr '^go: ''go work use'' requires one or more directory arguments' + +! go work use -r +stderr '^go: ''go work use'' requires one or more directory arguments' + +-- go.work -- +go 1.18 diff --git a/libgo/go/encoding/xml/marshal.go b/libgo/go/encoding/xml/marshal.go index 6859be0..7792ac7 100644 --- a/libgo/go/encoding/xml/marshal.go +++ b/libgo/go/encoding/xml/marshal.go @@ -512,7 +512,7 @@ func (p *printer) marshalValue(val reflect.Value, finfo *fieldInfo, startTemplat } fv := finfo.value(val, dontInitNilPointers) - if finfo.flags&fOmitEmpty != 0 && isEmptyValue(fv) { + if finfo.flags&fOmitEmpty != 0 && (!fv.IsValid() || isEmptyValue(fv)) { continue } diff --git a/libgo/go/encoding/xml/marshal_test.go b/libgo/go/encoding/xml/marshal_test.go index 115a93f..0b6965d 100644 --- a/libgo/go/encoding/xml/marshal_test.go +++ b/libgo/go/encoding/xml/marshal_test.go @@ -2499,3 +2499,39 @@ func TestInvalidXMLName(t *testing.T) { t.Errorf("error %q does not contain %q", err, want) } } + +// Issue 50164. Crash on zero value XML attribute. +type LayerOne struct { + XMLName Name `xml:"l1"` + + Value *float64 `xml:"value,omitempty"` + *LayerTwo `xml:",omitempty"` +} + +type LayerTwo struct { + ValueTwo *int `xml:"value_two,attr,omitempty"` +} + +func TestMarshalZeroValue(t *testing.T) { + proofXml := `1.2345` + var l1 LayerOne + err := Unmarshal([]byte(proofXml), &l1) + if err != nil { + t.Fatalf("unmarshal XML error: %v", err) + } + want := float64(1.2345) + got := *l1.Value + if got != want { + t.Fatalf("unexpected unmarshal result, want %f but got %f", want, got) + } + + // Marshal again (or Encode again) + // In issue 50164, here `Marshal(l1)` will panic because of the zero value of xml attribute ValueTwo `value_two`. + anotherXML, err := Marshal(l1) + if err != nil { + t.Fatalf("marshal XML error: %v", err) + } + if string(anotherXML) != proofXml { + t.Fatalf("unexpected unmarshal result, want %q but got %q", proofXml, anotherXML) + } +} diff --git a/libgo/go/go/internal/gcimporter/iimport.go b/libgo/go/go/internal/gcimporter/iimport.go index 8ec4c54..bff1c09 100644 --- a/libgo/go/go/internal/gcimporter/iimport.go +++ b/libgo/go/go/internal/gcimporter/iimport.go @@ -181,6 +181,15 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataRea p.doDecl(localpkg, name) } + // SetConstraint can't be called if the constraint type is not yet complete. + // When type params are created in the 'P' case of (*importReader).obj(), + // the associated constraint type may not be complete due to recursion. + // Therefore, we defer calling SetConstraint there, and call it here instead + // after all types are complete. + for _, d := range p.later { + d.t.SetConstraint(d.constraint) + } + for _, typ := range p.interfaceList { typ.Complete() } @@ -195,6 +204,11 @@ func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataRea return localpkg, nil } +type setConstraintArgs struct { + t *types.TypeParam + constraint types.Type +} + type iimporter struct { exportVersion int64 ipath string @@ -211,6 +225,9 @@ type iimporter struct { fake fakeFileSet interfaceList []*types.Interface + + // Arguments for calls to SetConstraint that are deferred due to recursive types + later []setConstraintArgs } func (p *iimporter) doDecl(pkg *types.Package, name string) { @@ -391,7 +408,11 @@ func (r *importReader) obj(name string) { } iface.MarkImplicit() } - t.SetConstraint(constraint) + // The constraint type may not be complete, if we + // are in the middle of a type recursion involving type + // constraints. So, we defer SetConstraint until we have + // completely set up all types in ImportData. + r.p.later = append(r.p.later, setConstraintArgs{t: t, constraint: constraint}) case 'V': typ := r.typ() diff --git a/libgo/go/go/printer/nodes.go b/libgo/go/go/printer/nodes.go index f2170db..9a09d58 100644 --- a/libgo/go/go/printer/nodes.go +++ b/libgo/go/go/printer/nodes.go @@ -319,9 +319,17 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp } } -func (p *printer) parameters(fields *ast.FieldList, isTypeParam bool) { +type paramMode int + +const ( + funcParam paramMode = iota + funcTParam + typeTParam +) + +func (p *printer) parameters(fields *ast.FieldList, mode paramMode) { openTok, closeTok := token.LPAREN, token.RPAREN - if isTypeParam { + if mode != funcParam { openTok, closeTok = token.LBRACK, token.RBRACK } p.print(fields.Opening, openTok) @@ -373,7 +381,7 @@ func (p *printer) parameters(fields *ast.FieldList, isTypeParam bool) { if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing { p.print(token.COMMA) p.linebreak(closing, 0, ignore, true) - } else if isTypeParam && fields.NumFields() == 1 { + } else if mode == typeTParam && fields.NumFields() == 1 { // Otherwise, if we are in a type parameter list that could be confused // with the constant array length expression [P*C], print a comma so that // parsing is unambiguous. @@ -411,10 +419,10 @@ func isTypeLit(x ast.Expr) bool { func (p *printer) signature(sig *ast.FuncType) { if sig.TypeParams != nil { - p.parameters(sig.TypeParams, true) + p.parameters(sig.TypeParams, funcTParam) } if sig.Params != nil { - p.parameters(sig.Params, false) + p.parameters(sig.Params, funcParam) } else { p.print(token.LPAREN, token.RPAREN) } @@ -428,7 +436,7 @@ func (p *printer) signature(sig *ast.FuncType) { p.expr(stripParensAlways(res.List[0].Type)) return } - p.parameters(res, false) + p.parameters(res, funcParam) } } @@ -1639,7 +1647,7 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { p.setComment(s.Doc) p.expr(s.Name) if s.TypeParams != nil { - p.parameters(s.TypeParams, true) + p.parameters(s.TypeParams, typeTParam) } if n == 1 { p.print(blank) @@ -1829,7 +1837,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl) { // FUNC is emitted). startCol := p.out.Column - len("func ") if d.Recv != nil { - p.parameters(d.Recv, false) // method: print receiver + p.parameters(d.Recv, funcParam) // method: print receiver p.print(blank) } p.expr(d.Name) diff --git a/libgo/go/go/printer/testdata/generics.golden b/libgo/go/go/printer/testdata/generics.golden index 4fac2c9..c3a7df8 100644 --- a/libgo/go/go/printer/testdata/generics.golden +++ b/libgo/go/go/printer/testdata/generics.golden @@ -64,3 +64,29 @@ type _ [P*T - T]struct{} type _[ P *T, ] struct{} + +// equivalent test cases for potentially ambiguous type parameter lists, except +// for function declarations there is no ambiguity (issue #51548) +func _[P *T]() {} +func _[P *T, _ any]() {} +func _[P *T]() {} +func _[P *T, _ any]() {} +func _[P T]() {} +func _[P T, _ any]() {} + +func _[P *struct{}]() {} +func _[P *struct{}]() {} +func _[P []int]() {} + +func _[P T]() {} +func _[P T]() {} +func _[P **T]() {} +func _[P *T]() {} +func _[P *T]() {} +func _[P **T]() {} +func _[P *T]() {} + +func _[ + P *T, +]() { +} diff --git a/libgo/go/go/printer/testdata/generics.input b/libgo/go/go/printer/testdata/generics.input index fde9d32..66e1554 100644 --- a/libgo/go/go/printer/testdata/generics.input +++ b/libgo/go/go/printer/testdata/generics.input @@ -61,3 +61,28 @@ type _ [P * T - T]struct{} type _[ P *T, ] struct{} + +// equivalent test cases for potentially ambiguous type parameter lists, except +// for function declarations there is no ambiguity (issue #51548) +func _[P *T,]() {} +func _[P *T, _ any]() {} +func _[P (*T),]() {} +func _[P (*T), _ any]() {} +func _[P (T),]() {} +func _[P (T), _ any]() {} + +func _[P *struct{}] () {} +func _[P (*struct{})] () {} +func _[P ([]int)] () {} + +func _ [P(T)]() {} +func _ [P((T))]() {} +func _ [P * *T]() {} +func _ [P * T]() {} +func _ [P(*T)]() {} +func _ [P(**T)]() {} +func _ [P * T]() {} + +func _[ + P *T, +]() {} diff --git a/libgo/go/go/types/api.go b/libgo/go/go/types/api.go index e5ae240..248db18 100644 --- a/libgo/go/go/types/api.go +++ b/libgo/go/go/types/api.go @@ -201,12 +201,12 @@ type Info struct { // qualified identifiers are collected in the Uses map. Types map[ast.Expr]TypeAndValue - // Instances maps identifiers denoting parameterized types or functions to - // their type arguments and instantiated type. + // Instances maps identifiers denoting generic types or functions to their + // type arguments and instantiated type. // // For example, Instances will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and - // resulting instantiated *Named type. Given a parameterized function + // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. @@ -419,8 +419,11 @@ func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, i } // AssertableTo reports whether a value of type V can be asserted to have type T. -// The behavior of AssertableTo is undefined if V is a generalized interface; i.e., -// an interface that may only be used as a type constraint in Go code. +// +// The behavior of AssertableTo is undefined in two cases: +// - if V is a generalized interface; i.e., an interface that may only be used +// as a type constraint in Go code +// - if T is an uninstantiated generic type func AssertableTo(V *Interface, T Type) bool { // Checker.newAssertableTo suppresses errors for invalid types, so we need special // handling here. @@ -430,20 +433,31 @@ func AssertableTo(V *Interface, T Type) bool { return (*Checker)(nil).newAssertableTo(V, T) == nil } -// AssignableTo reports whether a value of type V is assignable to a variable of type T. +// AssignableTo reports whether a value of type V is assignable to a variable +// of type T. +// +// The behavior of AssignableTo is undefined if V or T is an uninstantiated +// generic type. func AssignableTo(V, T Type) bool { x := operand{mode: value, typ: V} ok, _ := x.assignableTo(nil, T, nil) // check not needed for non-constant x return ok } -// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +// ConvertibleTo reports whether a value of type V is convertible to a value of +// type T. +// +// The behavior of ConvertibleTo is undefined if V or T is an uninstantiated +// generic type. func ConvertibleTo(V, T Type) bool { x := operand{mode: value, typ: V} return x.convertibleTo(nil, T, nil) // check not needed for non-constant x } // Implements reports whether type V implements interface T. +// +// The behavior of Implements is undefined if V is an uninstantiated generic +// type. func Implements(V Type, T *Interface) bool { if T.Empty() { // All types (even Typ[Invalid]) implement the empty interface. diff --git a/libgo/go/go/types/api_test.go b/libgo/go/go/types/api_test.go index b67af8c..5003ce2 100644 --- a/libgo/go/go/types/api_test.go +++ b/libgo/go/go/types/api_test.go @@ -16,6 +16,7 @@ import ( "internal/testenv" "reflect" "regexp" + "sort" "strings" "testing" @@ -435,129 +436,146 @@ func TestTypesInfo(t *testing.T) { } func TestInstanceInfo(t *testing.T) { - var tests = []struct { - src string + const lib = `package lib + +func F[P any](P) {} + +type T[P any] []P +` + + type testInst struct { name string targs []string typ string + } + + var tests = []struct { + src string + instances []testInst // recorded instances in source order }{ {`package p0; func f[T any](T) {}; func _() { f(42) }`, - `f`, - []string{`int`}, - `func(int)`, + []testInst{{`f`, []string{`int`}, `func(int)`}}, }, {`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`, - `f`, - []string{`rune`}, - `func(rune) rune`, + []testInst{{`f`, []string{`rune`}, `func(rune) rune`}}, }, {`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`, - `f`, - []string{`complex128`}, - `func(...complex128) complex128`, + []testInst{{`f`, []string{`complex128`}, `func(...complex128) complex128`}}, }, {`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`, - `f`, - []string{`float64`, `string`, `byte`}, - `func(float64, *string, []byte)`, + []testInst{{`f`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}}, }, {`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`, - `f`, - []string{`float64`, `byte`}, - `func(float64, *byte, ...[]byte)`, + []testInst{{`f`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}}, }, - {`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`, - `f`, - []string{`string`, `*string`}, - `func(x string)`, + {`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func(x string)`}}, }, - {`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `*int`}, - `func(x []int)`, + {`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`, + []testInst{{`f`, []string{`int`, `*int`}, `func(x []int)`}}, }, - {`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`}, - `func(x []int)`, + {`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`f`, []string{`int`, `chan<- int`}, `func(x []int)`}, + }, }, - {`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func(x []int)`, + {`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func(x []int)`}, + }, }, - {`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`, - `f`, - []string{`string`, `*string`}, - `func() string`, + {`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, }, - {`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, - `f`, - []string{`string`, `*string`}, - `func() string`, + {`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`, + []testInst{{`f`, []string{`string`, `*string`}, `func() string`}}, }, - {`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`}, - `func() []int`, + {`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, }, - {`package t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`, - `f`, - []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, - `func() []int`, + {`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`, + []testInst{ + {`C`, []string{`T`}, `interface{chan<- T}`}, + {`C`, []string{`[]*P`}, `interface{chan<- []*P}`}, + {`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`}, + }, }, {`package i0; import "lib"; func _() { lib.F(42) }`, - `F`, - []string{`int`}, - `func(int)`, + []testInst{{`F`, []string{`int`}, `func(int)`}}, }, + + {`package duplfunc0; func f[T any](T) {}; func _() { f(42); f("foo"); f[int](3) }`, + []testInst{ + {`f`, []string{`int`}, `func(int)`}, + {`f`, []string{`string`}, `func(string)`}, + {`f`, []string{`int`}, `func(int)`}, + }, + }, + {`package duplfunc1; import "lib"; func _() { lib.F(42); lib.F("foo"); lib.F(3) }`, + []testInst{ + {`F`, []string{`int`}, `func(int)`}, + {`F`, []string{`string`}, `func(string)`}, + {`F`, []string{`int`}, `func(int)`}, + }, + }, + {`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`, - `T`, - []string{`int`}, - `struct{x int}`, + []testInst{{`T`, []string{`int`}, `struct{x int}`}}, }, {`package type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`, - `T`, - []string{`[]int`, `int`}, - `struct{x []int; y int}`, + []testInst{{`T`, []string{`[]int`, `int`}, `struct{x []int; y int}`}}, }, {`package type4; import "lib"; var _ lib.T[int]`, - `T`, - []string{`int`}, - `[]int`, + []testInst{{`T`, []string{`int`}, `[]int`}}, + }, + + {`package dupltype0; type T[P interface{~int}] struct{ x P }; var x T[int]; var y T[int]`, + []testInst{ + {`T`, []string{`int`}, `struct{x int}`}, + {`T`, []string{`int`}, `struct{x int}`}, + }, + }, + {`package dupltype1; type T[P ~int] struct{ x P }; func (r *T[Q]) add(z T[Q]) { r.x += z.x }`, + []testInst{ + {`T`, []string{`Q`}, `struct{x Q}`}, + {`T`, []string{`Q`}, `struct{x Q}`}, + }, + }, + {`package dupltype1; import "lib"; var x lib.T[int]; var y lib.T[int]; var z lib.T[string]`, + []testInst{ + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`int`}, `[]int`}, + {`T`, []string{`string`}, `[]string`}, + }, }, } for _, test := range tests { - const lib = `package lib - -func F[P any](P) {} - -type T[P any] []P -` - imports := make(testImporter) conf := Config{Importer: imports} - instances := make(map[*ast.Ident]Instance) - uses := make(map[*ast.Ident]Object) + instMap := make(map[*ast.Ident]Instance) + useMap := make(map[*ast.Ident]Object) makePkg := func(src string) *Package { f, err := parser.ParseFile(fset, "p.go", src, 0) if err != nil { t.Fatal(err) } - pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instances, Uses: uses}) + pkg, err := conf.Check("", fset, []*ast.File{f}, &Info{Instances: instMap, Uses: useMap}) if err != nil { t.Fatal(err) } @@ -567,58 +585,69 @@ type T[P any] []P makePkg(lib) pkg := makePkg(test.src) - // look for instance information - var targs []Type - var typ Type - for ident, inst := range instances { - if ExprString(ident) == test.name { - for i := 0; i < inst.TypeArgs.Len(); i++ { - targs = append(targs, inst.TypeArgs.At(i)) + t.Run(pkg.Name(), func(t *testing.T) { + // Sort instances in source order for stability. + instances := sortedInstances(instMap) + if got, want := len(instances), len(test.instances); got != want { + t.Fatalf("got %d instances, want %d", got, want) + } + + // Pairwise compare with the expected instances. + for ii, inst := range instances { + var targs []Type + for i := 0; i < inst.Inst.TypeArgs.Len(); i++ { + targs = append(targs, inst.Inst.TypeArgs.At(i)) + } + typ := inst.Inst.Type + + testInst := test.instances[ii] + if got := inst.Ident.Name; got != testInst.name { + t.Fatalf("got name %s, want %s", got, testInst.name) + } + if len(targs) != len(testInst.targs) { + t.Fatalf("got %d type arguments; want %d", len(targs), len(testInst.targs)) + } + for i, targ := range targs { + if got := targ.String(); got != testInst.targs[i] { + t.Errorf("type argument %d: got %s; want %s", i, got, testInst.targs[i]) + } + } + if got := typ.Underlying().String(); got != testInst.typ { + t.Errorf("package %s: got %s; want %s", pkg.Name(), got, testInst.typ) } - typ = inst.Type - // Check that we can find the corresponding parameterized type. - ptype := uses[ident].Type() + // Verify the invariant that re-instantiating the corresponding generic + // type with TypeArgs results in an identical instance. + ptype := useMap[inst.Ident].Type() lister, _ := ptype.(interface{ TypeParams() *TypeParamList }) if lister == nil || lister.TypeParams().Len() == 0 { - t.Errorf("package %s: info.Types[%v] = %v, want parameterized type", pkg.Name(), ident, ptype) - continue + t.Fatalf("info.Types[%v] = %v, want parameterized type", inst.Ident, ptype) } - - // Verify the invariant that re-instantiating the generic type with - // TypeArgs results in an equivalent type. inst2, err := Instantiate(nil, ptype, targs, true) if err != nil { t.Errorf("Instantiate(%v, %v) failed: %v", ptype, targs, err) } - if !Identical(inst.Type, inst2) { - t.Errorf("%v and %v are not identical", inst.Type, inst2) + if !Identical(inst.Inst.Type, inst2) { + t.Errorf("%v and %v are not identical", inst.Inst.Type, inst2) } - break } - } - if targs == nil { - t.Errorf("package %s: no instance information found for %s", pkg.Name(), test.name) - continue - } + }) + } +} - // check that type arguments are correct - if len(targs) != len(test.targs) { - t.Errorf("package %s: got %d type arguments; want %d", pkg.Name(), len(targs), len(test.targs)) - continue - } - for i, targ := range targs { - if got := targ.String(); got != test.targs[i] { - t.Errorf("package %s, %d. type argument: got %s; want %s", pkg.Name(), i, got, test.targs[i]) - continue - } - } +type recordedInstance struct { + Ident *ast.Ident + Inst Instance +} - // check that the types match - if got := typ.Underlying().String(); got != test.typ { - t.Errorf("package %s: got %s; want %s", pkg.Name(), got, test.typ) - } +func sortedInstances(m map[*ast.Ident]Instance) (instances []recordedInstance) { + for id, inst := range m { + instances = append(instances, recordedInstance{id, inst}) } + sort.Slice(instances, func(i, j int) bool { + return instances[i].Ident.Pos() < instances[j].Ident.Pos() + }) + return instances } func TestDefsInfo(t *testing.T) { @@ -1690,7 +1719,7 @@ func F(){ var F = /*F=func:12*/ F /*F=var:17*/ ; _ = F var a []int - for i, x := range /*i=undef*/ /*x=var:16*/ a /*i=var:20*/ /*x=var:20*/ { _ = i; _ = x } + for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } var i interface{} switch y := i.(type) { /*y=undef*/ diff --git a/libgo/go/go/types/assignments.go b/libgo/go/go/types/assignments.go index f75b8b6..f5e22c2 100644 --- a/libgo/go/go/types/assignments.go +++ b/libgo/go/go/types/assignments.go @@ -290,15 +290,14 @@ func (check *Checker) typesSummary(list []Type, variadic bool) string { return "(" + strings.Join(res, ", ") + ")" } -func (check *Checker) assignError(rhs []ast.Expr, nvars, nvals int) { - measure := func(x int, unit string) string { - s := fmt.Sprintf("%d %s", x, unit) - if x != 1 { - s += "s" - } - return s +func measure(x int, unit string) string { + if x != 1 { + unit += "s" } + return fmt.Sprintf("%d %s", x, unit) +} +func (check *Checker) assignError(rhs []ast.Expr, nvars, nvals int) { vars := measure(nvars, "variable") vals := measure(nvals, "value") rhs0 := rhs[0] diff --git a/libgo/go/go/types/call.go b/libgo/go/go/types/call.go index 3dab284..5d1f60d 100644 --- a/libgo/go/go/types/call.go +++ b/libgo/go/go/types/call.go @@ -429,7 +429,7 @@ var cgoPrefixes = [...]string{ "_Cmacro_", // function to evaluate the expanded expression } -func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { +func (check *Checker) selector(x *operand, e *ast.SelectorExpr, def *Named) { // these must be declared before the "goto Error" statements var ( obj Object @@ -527,7 +527,18 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { } check.exprOrType(x, e.X, false) - if x.mode == invalid { + switch x.mode { + case typexpr: + // don't crash for "type T T.x" (was issue #51509) + if def != nil && x.typ == def { + check.cycleError([]Object{def.obj}) + goto Error + } + case builtin: + // types2 uses the position of '.' for the error + check.errorf(e.Sel, _UncalledBuiltin, "cannot select on %s", x) + goto Error + case invalid: goto Error } diff --git a/libgo/go/go/types/check.go b/libgo/go/go/types/check.go index 6e1da04..2313637 100644 --- a/libgo/go/go/types/check.go +++ b/libgo/go/go/types/check.go @@ -133,7 +133,7 @@ type Checker struct { untyped map[ast.Expr]exprInfo // map of expressions without final type delayed []action // stack of delayed action segments; segments are processed in FIFO order objPath []Object // path of object dependencies during type inference (for cycle reporting) - defTypes []*Named // defined types created during type checking, for final validation. + cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking // environment within which the current object is type-checked (valid only // for the duration of type-checking a specific object) @@ -212,6 +212,16 @@ func (check *Checker) pop() Object { return obj } +type cleaner interface { + cleanup() +} + +// needsCleanup records objects/types that implement the cleanup method +// which will be called at the end of type-checking. +func (check *Checker) needsCleanup(c cleaner) { + check.cleaners = append(check.cleaners, c) +} + // NewChecker returns a new Checker instance for a given package. // Package files may be added incrementally via checker.Files. func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker { @@ -255,6 +265,8 @@ func (check *Checker) initFiles(files []*ast.File) { check.methods = nil check.untyped = nil check.delayed = nil + check.objPath = nil + check.cleaners = nil // determine package name and collect valid files pkg := check.pkg @@ -304,22 +316,37 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) { defer check.handleBailout(&err) + print := func(msg string) { + if trace { + fmt.Println() + fmt.Println(msg) + } + } + + print("== initFiles ==") check.initFiles(files) + print("== collectObjects ==") check.collectObjects() + print("== packageObjects ==") check.packageObjects() + print("== processDelayed ==") check.processDelayed(0) // incl. all functions - check.expandDefTypes() + print("== cleanup ==") + check.cleanup() + print("== initOrder ==") check.initOrder() if !check.conf.DisableUnusedImportCheck { + print("== unusedImports ==") check.unusedImports() } + print("== recordUntyped ==") check.recordUntyped() if check.firstErr == nil { @@ -337,7 +364,6 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) { check.recvTParamMap = nil check.brokenAliases = nil check.unionTypeSets = nil - check.defTypes = nil check.ctxt = nil // TODO(rFindley) There's more memory we should release at this point. @@ -365,27 +391,13 @@ func (check *Checker) processDelayed(top int) { check.delayed = check.delayed[:top] } -func (check *Checker) expandDefTypes() { - // Ensure that every defined type created in the course of type-checking has - // either non-*Named underlying, or is unresolved. - // - // This guarantees that we don't leak any types whose underlying is *Named, - // because any unresolved instances will lazily compute their underlying by - // substituting in the underlying of their origin. The origin must have - // either been imported or type-checked and expanded here, and in either case - // its underlying will be fully expanded. - for i := 0; i < len(check.defTypes); i++ { - n := check.defTypes[i] - switch n.underlying.(type) { - case nil: - if n.resolver == nil { - panic("nil underlying") - } - case *Named: - n.under() // n.under may add entries to check.defTypes - } - n.check = nil +// cleanup runs cleanup for all collected cleaners. +func (check *Checker) cleanup() { + // Don't use a range clause since Named.cleanup may add more cleaners. + for i := 0; i < len(check.cleaners); i++ { + check.cleaners[i].cleanup() } + check.cleaners = nil } func (check *Checker) record(x *operand) { diff --git a/libgo/go/go/types/conversions.go b/libgo/go/go/types/conversions.go index 8474135..c5a69cd 100644 --- a/libgo/go/go/types/conversions.go +++ b/libgo/go/go/types/conversions.go @@ -48,11 +48,14 @@ func (check *Checker) conversion(x *operand, T Type) { // have specific types, constant x cannot be // converted. ok = T.(*TypeParam).underIs(func(u Type) bool { - // t is nil if there are no specific type terms + // u is nil if there are no specific type terms if u == nil { cause = check.sprintf("%s does not contain specific types", T) return false } + if isString(x.typ) && isBytesOrRunes(u) { + return true + } if !constConvertibleTo(u, nil) { cause = check.sprintf("cannot convert %s to %s (in %s)", x, u, T) return false diff --git a/libgo/go/go/types/decl.go b/libgo/go/go/types/decl.go index cd6f709a..93a37d7 100644 --- a/libgo/go/go/types/decl.go +++ b/libgo/go/go/types/decl.go @@ -624,7 +624,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list *ast.FieldList }() index := 0 - var bounds []Type for _, f := range list.List { var bound Type // NOTE: we may be able to assert that f.Type != nil here, but this is not @@ -642,7 +641,6 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list *ast.FieldList } else { bound = Typ[Invalid] } - bounds = append(bounds, bound) for i := range f.Names { tparams[index+i].bound = bound } diff --git a/libgo/go/go/types/errorcodes.go b/libgo/go/go/types/errorcodes.go index a7514b3..64cf24c 100644 --- a/libgo/go/go/types/errorcodes.go +++ b/libgo/go/go/types/errorcodes.go @@ -1339,11 +1339,6 @@ const ( // func _() { // f() // } - // - // Example: - // type N[P, Q any] struct{} - // - // var _ N[int] _CannotInferTypeArgs // _InvalidTypeArg occurs when a type argument does not satisfy its diff --git a/libgo/go/go/types/eval.go b/libgo/go/go/types/eval.go index c8bb005..5700cbf 100644 --- a/libgo/go/go/types/eval.go +++ b/libgo/go/go/types/eval.go @@ -37,8 +37,8 @@ func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ Type // CheckExpr type checks the expression expr as if it had appeared at position // pos of package pkg. Type information about the expression is recorded in -// info. The expression may be an uninstantiated parameterized function or -// type. +// info. The expression may be an identifier denoting an uninstantiated generic +// function or type. // // If pkg == nil, the Universe scope is used and the provided // position pos is ignored. If pkg != nil, and pos is invalid, diff --git a/libgo/go/go/types/expr.go b/libgo/go/go/types/expr.go index 8747838..e24bd60 100644 --- a/libgo/go/go/types/expr.go +++ b/libgo/go/go/types/expr.go @@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string { } // see if we can extract a more specific error var cause string - comparable(typ, nil, func(format string, args ...interface{}) { + comparable(typ, true, nil, func(format string, args ...interface{}) { cause = check.sprintf(format, args...) }) return cause @@ -1339,6 +1339,10 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // no composite literal type present - use hint (element type of enclosing type) typ = hint base, _ = deref(coreType(typ)) // *T implies &T{} + if base == nil { + check.errorf(e, _InvalidLit, "invalid composite literal element type %s: no core type", typ) + goto Error + } default: // TODO(gri) provide better error messages depending on context @@ -1529,7 +1533,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { return kind case *ast.SelectorExpr: - check.selector(x, e) + check.selector(x, e, nil) case *ast.IndexExpr, *ast.IndexListExpr: ix := typeparams.UnpackIndexExpr(e) @@ -1584,6 +1588,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { case invalid: goto Error case typexpr: + check.validVarType(e.X, x.typ) x.typ = &Pointer{base: x.typ} default: var base Type diff --git a/libgo/go/go/types/index.go b/libgo/go/go/types/index.go index eac6017..33075ed 100644 --- a/libgo/go/go/types/index.go +++ b/libgo/go/go/types/index.go @@ -183,6 +183,7 @@ func (check *Checker) indexExpr(x *operand, e *typeparams.IndexExpr) (isFuncInst } if !valid { + // types2 uses the position of '[' for the error check.invalidOp(x, _NonIndexableOperand, "cannot index %s", x) x.mode = invalid return false diff --git a/libgo/go/go/types/infer.go b/libgo/go/go/types/infer.go index 8f22144..6bed55c 100644 --- a/libgo/go/go/types/infer.go +++ b/libgo/go/go/types/infer.go @@ -487,21 +487,88 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type } } - // If a constraint has a core type, unify the corresponding type parameter with it. - for _, tpar := range tparams { - if ctype := adjCoreType(tpar); ctype != nil { - if !u.unify(tpar, ctype) { - // TODO(gri) improve error message by providing the type arguments - // which we know already - check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, ctype) - return nil, 0 + // Repeatedly apply constraint type inference as long as + // there are still unknown type arguments and progress is + // being made. + // + // This is an O(n^2) algorithm where n is the number of + // type parameters: if there is progress (and iteration + // continues), at least one type argument is inferred + // per iteration and we have a doubly nested loop. + // In practice this is not a problem because the number + // of type parameters tends to be very small (< 5 or so). + // (It should be possible for unification to efficiently + // signal newly inferred type arguments; then the loops + // here could handle the respective type parameters only, + // but that will come at a cost of extra complexity which + // may not be worth it.) + for n := u.x.unknowns(); n > 0; { + nn := n + + for i, tpar := range tparams { + // If there is a core term (i.e., a core type with tilde information) + // unify the type parameter with the core type. + if core, single := coreTerm(tpar); core != nil { + // A type parameter can be unified with its core type in two cases. + tx := u.x.at(i) + switch { + case tx != nil: + // The corresponding type argument tx is known. + // In this case, if the core type has a tilde, the type argument's underlying + // type must match the core type, otherwise the type argument and the core type + // must match. + // If tx is an external type parameter, don't consider its underlying type + // (which is an interface). Core type unification will attempt to unify against + // core.typ. + // Note also that even with inexact unification we cannot leave away the under + // call here because it's possible that both tx and core.typ are named types, + // with under(tx) being a (named) basic type matching core.typ. Such cases do + // not match with inexact unification. + if core.tilde && !isTypeParam(tx) { + tx = under(tx) + } + if !u.unify(tx, core.typ) { + // TODO(gri) improve error message by providing the type arguments + // which we know already + // Don't use term.String() as it always qualifies types, even if they + // are in the current package. + tilde := "" + if core.tilde { + tilde = "~" + } + check.errorf(posn, _InvalidTypeArg, "%s does not match %s%s", tpar, tilde, core.typ) + return nil, 0 + } + + case single && !core.tilde: + // The corresponding type argument tx is unknown and there's a single + // specific type and no tilde. + // In this case the type argument must be that single type; set it. + u.x.set(i, core.typ) + + default: + // Unification is not possible and no progress was made. + continue + } + + // The number of known type arguments may have changed. + nn = u.x.unknowns() + if nn == 0 { + break // all type arguments are known + } } } + + assert(nn <= n) + if nn == n { + break // no progress + } + n = nn } // u.x.types() now contains the incoming type arguments plus any additional type - // arguments which were inferred from core types. The newly inferred non- - // nil entries may still contain references to other type parameters. + // arguments which were inferred from core terms. The newly inferred non-nil + // entries may still contain references to other type parameters. // For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int // was given, unification produced the type list [int, []C, *A]. We eliminate the // remaining type parameters by substituting the type parameters in this type list @@ -590,17 +657,40 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type return } -func adjCoreType(tpar *TypeParam) Type { - // If the type parameter embeds a single, possibly named - // type, use that one instead of the core type (which is - // always the underlying type of that single type). - if single := tpar.singleType(); single != nil { +// If the type parameter has a single specific type S, coreTerm returns (S, true). +// Otherwise, if tpar has a core type T, it returns a term corresponding to that +// core type and false. In that case, if any term of tpar has a tilde, the core +// term has a tilde. In all other cases coreTerm returns (nil, false). +func coreTerm(tpar *TypeParam) (*term, bool) { + n := 0 + var single *term // valid if n == 1 + var tilde bool + tpar.is(func(t *term) bool { + if t == nil { + assert(n == 0) + return false // no terms + } + n++ + single = t + if t.tilde { + tilde = true + } + return true + }) + if n == 1 { if debug { - assert(under(single) == coreType(tpar)) + assert(debug && under(single.typ) == coreType(tpar)) } - return single + return single, true + } + if typ := coreType(tpar); typ != nil { + // A core type is always an underlying type. + // If any term of tpar has a tilde, we don't + // have a precise core type and we must return + // a tilde as well. + return &term{tilde, typ}, false } - return coreType(tpar) + return nil, false } type cycleFinder struct { @@ -648,8 +738,6 @@ func (w *cycleFinder) typ(typ Type) { // in signatures where they are handled explicitly. case *Signature: - // There are no "method types" so we should never see a recv. - assert(t.recv == nil) if t.params != nil { w.varList(t.params.vars) } diff --git a/libgo/go/go/types/instantiate.go b/libgo/go/go/types/instantiate.go index 4aeaeb7..a481746 100644 --- a/libgo/go/go/types/instantiate.go +++ b/libgo/go/go/types/instantiate.go @@ -15,10 +15,10 @@ import ( // Instantiate instantiates the type orig with the given type arguments targs. // orig must be a *Named or a *Signature type. If there is no error, the -// resulting Type is a new, instantiated (not parameterized) type of the same -// kind (either a *Named or a *Signature). Methods attached to a *Named type -// are also instantiated, and associated with a new *Func that has the same -// position as the original method, but nil function scope. +// resulting Type is an instantiated type of the same kind (either a *Named or +// a *Signature). Methods attached to a *Named type are also instantiated, and +// associated with a new *Func that has the same position as the original +// method, but nil function scope. // // If ctxt is non-nil, it may be used to de-duplicate the instance against // previous instances with the same identity. As a special case, generic @@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error { // If T is comparable, V must be comparable. // Remember as a pending error and report only if we don't have a more specific error. var pending error - if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) { + if Ti.IsComparable() && !comparable(V, false, nil, nil) { pending = errorf("%s does not implement comparable", V) } diff --git a/libgo/go/go/types/interface.go b/libgo/go/go/types/interface.go index b9d4660..3db3580 100644 --- a/libgo/go/go/types/interface.go +++ b/libgo/go/go/types/interface.go @@ -56,7 +56,7 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { } // set method receivers if necessary - typ := new(Interface) + typ := (*Checker)(nil).newInterface() for _, m := range methods { if sig := m.typ.(*Signature); sig.recv == nil { sig.recv = NewVar(m.pos, m.pkg, "", typ) @@ -73,6 +73,15 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { return typ } +// check may be nil +func (check *Checker) newInterface() *Interface { + typ := &Interface{check: check} + if check != nil { + check.needsCleanup(typ) + } + return typ +} + // MarkImplicit marks the interface t as implicit, meaning this interface // corresponds to a constraint literal such as ~T or A|B without explicit // interface embedding. MarkImplicit should be called before any concurrent use @@ -141,6 +150,11 @@ func (t *Interface) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *Interface) cleanup() { + t.check = nil + t.embedPos = nil +} + func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) { addEmbedded := func(pos token.Pos, typ Type) { ityp.embeddeds = append(ityp.embeddeds, typ) @@ -210,16 +224,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d sortMethods(ityp.methods) // (don't sort embeddeds: they must correspond to *embedPos entries) - // Compute type set with a non-nil *Checker as soon as possible - // to report any errors. Subsequent uses of type sets will use - // this computed type set and won't need to pass in a *Checker. - // - // Pin the checker to the interface type in the interim, in case the type set - // must be used before delayed funcs are processed (see issue #48234). - // TODO(rfindley): clean up use of *Checker with computeInterfaceTypeSet - ityp.check = check + // Compute type set as soon as possible to report any errors. + // Subsequent uses of type sets will use this computed type + // set and won't need to pass in a *Checker. check.later(func() { computeInterfaceTypeSet(check, iface.Pos(), ityp) - ityp.check = nil }).describef(iface, "compute type set for %s", ityp) } diff --git a/libgo/go/go/types/lookup.go b/libgo/go/go/types/lookup.go index 501c230..335fada 100644 --- a/libgo/go/go/types/lookup.go +++ b/libgo/go/go/types/lookup.go @@ -70,7 +70,8 @@ func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o // see if there is a matching field (but not a method, those need to be declared // explicitly in the constraint). If the constraint is a named pointer type (see // above), we are ok here because only fields are accepted as results. - if obj == nil && isTypeParam(T) { + const enableTParamFieldLookup = false // see issue #51576 + if enableTParamFieldLookup && obj == nil && isTypeParam(T) { if t := coreType(T); t != nil { obj, index, indirect = lookupFieldOrMethod(t, addressable, pkg, name, false) if _, ok := obj.(*Var); !ok { diff --git a/libgo/go/go/types/named.go b/libgo/go/go/types/named.go index 5e84c39..876f7e8 100644 --- a/libgo/go/go/types/named.go +++ b/libgo/go/go/types/named.go @@ -72,18 +72,38 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar } // Ensure that typ is always expanded and sanity-checked. if check != nil { - check.defTypes = append(check.defTypes, typ) + check.needsCleanup(typ) } return typ } +func (t *Named) cleanup() { + // Ensure that every defined type created in the course of type-checking has + // either non-*Named underlying, or is unresolved. + // + // This guarantees that we don't leak any types whose underlying is *Named, + // because any unresolved instances will lazily compute their underlying by + // substituting in the underlying of their origin. The origin must have + // either been imported or type-checked and expanded here, and in either case + // its underlying will be fully expanded. + switch t.underlying.(type) { + case nil: + if t.resolver == nil { + panic("nil underlying") + } + case *Named: + t.under() // t.under may add entries to check.cleaners + } + t.check = nil +} + // Obj returns the type name for the declaration defining the named type t. For -// instantiated types, this is the type name of the base type. +// instantiated types, this is same as the type name of the origin type. func (t *Named) Obj() *TypeName { return t.orig.obj // for non-instances this is the same as t.obj } -// Origin returns the parameterized type from which the named type t is +// Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. func (t *Named) Origin() *Named { return t.orig } @@ -91,7 +111,7 @@ func (t *Named) Origin() *Named { return t.orig } // between parameterized instantiated and non-instantiated types. // TypeParams returns the type parameters of the named type t, or nil. -// The result is non-nil for an (originally) parameterized type even if it is instantiated. +// The result is non-nil for an (originally) generic type even if it is instantiated. func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. @@ -104,7 +124,11 @@ func (t *Named) SetTypeParams(tparams []*TypeParam) { // TypeArgs returns the type arguments used to instantiate the named type t. func (t *Named) TypeArgs() *TypeList { return t.targs } -// NumMethods returns the number of explicit methods whose receiver is named type t. +// NumMethods returns the number of explicit methods defined for t. +// +// For an ordinary or instantiated type t, the receiver base type of these +// methods will be the named type t. For an uninstantiated generic type t, each +// method receiver will be instantiated with its receiver type parameters. func (t *Named) NumMethods() int { return t.resolve(nil).methods.Len() } // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). @@ -362,11 +386,11 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam // that it wasn't substituted. In this case we need to create a new // *Interface before modifying receivers. if iface == n.orig.underlying { - iface = &Interface{ - embeddeds: iface.embeddeds, - complete: iface.complete, - implicit: iface.implicit, // should be false but be conservative - } + old := iface + iface = check.newInterface() + iface.embeddeds = old.embeddeds + iface.complete = old.complete + iface.implicit = old.implicit // should be false but be conservative underlying = iface } iface.methods = methods diff --git a/libgo/go/go/types/predicates.go b/libgo/go/go/types/predicates.go index 14e99bf..0360f27 100644 --- a/libgo/go/go/types/predicates.go +++ b/libgo/go/go/types/predicates.go @@ -104,11 +104,12 @@ func isGeneric(t Type) bool { // Comparable reports whether values of type T are comparable. func Comparable(T Type) bool { - return comparable(T, nil, nil) + return comparable(T, true, nil, nil) } +// If dynamic is set, non-type parameter interfaces are always comparable. // If reportf != nil, it may be used to report why T is not comparable. -func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool { +func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool { if seen[T] { return true } @@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) return true case *Struct: for _, f := range t.fields { - if !comparable(f.typ, seen, nil) { + if !comparable(f.typ, dynamic, seen, nil) { if reportf != nil { reportf("struct containing %s cannot be compared", f.typ) } @@ -135,7 +136,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Array: - if !comparable(t.elem, seen, nil) { + if !comparable(t.elem, dynamic, seen, nil) { if reportf != nil { reportf("%s cannot be compared", t) } @@ -143,7 +144,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{}) } return true case *Interface: - return !isTypeParam(T) || t.typeSet().IsComparable(seen) + return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen) } return false } diff --git a/libgo/go/go/types/signature.go b/libgo/go/go/types/signature.go index 8f89e93..a340ac7 100644 --- a/libgo/go/go/types/signature.go +++ b/libgo/go/go/types/signature.go @@ -112,7 +112,8 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast // - the receiver specification acts as local declaration for its type parameters, which may be blank _, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true) if len(rparams) > 0 { - sig.rparams = bindTParams(check.declareTypeParams(nil, rparams)) + tparams := check.declareTypeParams(nil, rparams) + sig.rparams = bindTParams(tparams) // Blank identifiers don't get declared, so naive type-checking of the // receiver type expression would fail in Checker.collectParams below, // when Checker.ident cannot resolve the _ to a type. @@ -122,11 +123,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast // lookup in the scope. for i, p := range rparams { if p.Name == "_" { - tpar := sig.rparams.At(i) if check.recvTParamMap == nil { check.recvTParamMap = make(map[*ast.Ident]*TypeParam) } - check.recvTParamMap[p] = tpar + check.recvTParamMap[p] = tparams[i] } } // determine receiver type to get its type parameters @@ -142,22 +142,23 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast } } // provide type parameter bounds - // - only do this if we have the right number (otherwise an error is reported elsewhere) - if sig.RecvTypeParams().Len() == len(recvTParams) { - // We have a list of *TypeNames but we need a list of Types. - list := make([]Type, sig.RecvTypeParams().Len()) - for i, t := range sig.RecvTypeParams().list() { - list[i] = t - check.mono.recordCanon(t, recvTParams[i]) - } - smap := makeSubstMap(recvTParams, list) - for i, tpar := range sig.RecvTypeParams().list() { - bound := recvTParams[i].bound - // bound is (possibly) parameterized in the context of the - // receiver type declaration. Substitute parameters for the - // current context. - tpar.bound = check.subst(tpar.obj.pos, bound, smap, nil) + if len(tparams) == len(recvTParams) { + smap := makeRenameMap(recvTParams, tparams) + for i, tpar := range tparams { + recvTPar := recvTParams[i] + check.mono.recordCanon(tpar, recvTPar) + // recvTPar.bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the current + // context. + tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil) } + } else if len(tparams) < len(recvTParams) { + // Reporting an error here is a stop-gap measure to avoid crashes in the + // compiler when a type parameter/argument cannot be inferred later. It + // may lead to follow-on errors (see issues #51339, #51343). + // TODO(gri) find a better solution + got := measure(len(tparams), "type parameter") + check.errorf(recvPar, _BadRecv, "got %s, but receiver base type declares %d", got, len(recvTParams)) } } } @@ -192,66 +193,77 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast switch len(recvList) { case 0: // error reported by resolver - recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below + recv = NewParam(token.NoPos, nil, "", Typ[Invalid]) // ignore recv below default: // more than one receiver - check.error(recvList[len(recvList)-1], _BadRecv, "method must have exactly one receiver") + check.error(recvList[len(recvList)-1], _InvalidRecv, "method must have exactly one receiver") fallthrough // continue with first receiver case 1: recv = recvList[0] } + sig.recv = recv - // TODO(gri) We should delay rtyp expansion to when we actually need the - // receiver; thus all checks here should be delayed to later. - rtyp, _ := deref(recv.typ) + // Delay validation of receiver type as it may cause premature expansion + // of types the receiver type is dependent on (see issues #51232, #51233). + check.later(func() { + rtyp, _ := deref(recv.typ) - // spec: "The receiver type must be of the form T or *T where T is a type name." - // (ignore invalid types - error was reported before) - if rtyp != Typ[Invalid] { - var err string - switch T := rtyp.(type) { - case *Named: - T.resolve(check.bestContext(nil)) - // The receiver type may be an instantiated type referred to - // by an alias (which cannot have receiver parameters for now). - if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { - check.errorf(atPos(recv.pos), _InvalidRecv, "cannot define methods on instantiated type %s", recv.typ) - break - } - // spec: "The type denoted by T is called the receiver base type; it must not - // be a pointer or interface type and it must be declared in the same package - // as the method." - if T.obj.pkg != check.pkg { - err = "type not defined in this package" - } else { - // The underlying type of a receiver base type can be a type parameter; - // e.g. for methods with a generic receiver T[P] with type T[P any] P. - underIs(T, func(u Type) bool { - switch u := u.(type) { - case *Basic: - // unsafe.Pointer is treated like a regular pointer - if u.kind == UnsafePointer { - err = "unsafe.Pointer" + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if rtyp != Typ[Invalid] { + var err string + switch T := rtyp.(type) { + case *Named: + T.resolve(check.bestContext(nil)) + // The receiver type may be an instantiated type referred to + // by an alias (which cannot have receiver parameters for now). + if T.TypeArgs() != nil && sig.RecvTypeParams() == nil { + check.errorf(recv, _InvalidRecv, "cannot define methods on instantiated type %s", recv.typ) + break + } + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + if compilerErrorMessages { + check.errorf(recv, _InvalidRecv, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + } else { + // The underlying type of a receiver base type can be a type parameter; + // e.g. for methods with a generic receiver T[P] with type T[P any] P. + // TODO(gri) Such declarations are currently disallowed. + // Revisit the need for underIs. + underIs(T, func(u Type) bool { + switch u := u.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + return false + } + case *Pointer, *Interface: + err = "pointer or interface type" return false } - case *Pointer, *Interface: - err = "pointer or interface type" - return false - } - return true - }) + return true + }) + } + case *Basic: + err = "basic or unnamed type" + if compilerErrorMessages { + check.errorf(recv, _InvalidRecv, "cannot define new methods on non-local type %s", recv.typ) + err = "" + } + default: + check.errorf(recv, _InvalidRecv, "invalid receiver type %s", recv.typ) + } + if err != "" { + check.errorf(recv, _InvalidRecv, "invalid receiver type %s (%s)", recv.typ, err) } - case *Basic: - err = "basic or unnamed type" - default: - check.errorf(recv, _InvalidRecv, "invalid receiver type %s", recv.typ) - } - if err != "" { - check.errorf(recv, _InvalidRecv, "invalid receiver type %s (%s)", recv.typ, err) - // ok to continue } - } - sig.recv = recv + }).describef(recv, "validate receiver %s", recv) } sig.params = NewTuple(params...) diff --git a/libgo/go/go/types/stmt.go b/libgo/go/go/types/stmt.go index a5aee48..9ebfbb6 100644 --- a/libgo/go/go/types/stmt.go +++ b/libgo/go/go/types/stmt.go @@ -821,8 +821,6 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { case *ast.RangeStmt: inner |= breakOk | continueOk - check.openScope(s, "for") - defer check.closeScope() // check expression to iterate over var x operand @@ -857,6 +855,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { } } + // Open the for-statement block scope now, after the range clause. + // Iteration variables declared with := need to go in this scope (was issue #51437). + check.openScope(s, "range") + defer check.closeScope() + // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) @@ -865,9 +868,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { rhs := [2]Type{key, val} // key, val may be nil if s.Tok == token.DEFINE { - // short variable declaration; variable scope starts after the range clause - // (the for loop opens a new scope, so variables on the lhs never redeclare - // previously declared variables) + // short variable declaration var vars []*Var for i, lhs := range lhs { if lhs == nil { @@ -904,12 +905,8 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // declare variables if len(vars) > 0 { - scopePos := s.X.End() + scopePos := s.Body.Pos() for _, obj := range vars { - // spec: "The scope of a constant or variable identifier declared inside - // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl - // for short variable declarations) and ends at the end of the innermost - // containing block." check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) } } else { diff --git a/libgo/go/go/types/subst.go b/libgo/go/go/types/subst.go index 53247a3..4b4a0f4 100644 --- a/libgo/go/go/types/subst.go +++ b/libgo/go/go/types/subst.go @@ -160,7 +160,10 @@ func (subst *subster) typ(typ Type) Type { methods, mcopied := subst.funcList(t.methods) embeddeds, ecopied := subst.typeList(t.embeddeds) if mcopied || ecopied { - iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete} + iface := subst.check.newInterface() + iface.embeddeds = embeddeds + iface.implicit = t.implicit + iface.complete = t.complete // If we've changed the interface type, we may need to replace its // receiver if the receiver type is the original interface. Receivers of // *Named type are replaced during named type expansion. diff --git a/libgo/go/go/types/termlist.go b/libgo/go/go/types/termlist.go index c4ab0e0..94e49ca 100644 --- a/libgo/go/go/types/termlist.go +++ b/libgo/go/go/types/termlist.go @@ -92,15 +92,6 @@ func (xl termlist) norm() termlist { return rl } -// If the type set represented by xl is specified by a single (non-𝓤) term, -// singleType returns that type. Otherwise it returns nil. -func (xl termlist) singleType() Type { - if nl := xl.norm(); len(nl) == 1 { - return nl[0].typ // if nl.isAll() then typ is nil, which is ok - } - return nil -} - // union returns the union xl ∪ yl. func (xl termlist) union(yl termlist) termlist { return append(xl, yl...).norm() diff --git a/libgo/go/go/types/termlist_test.go b/libgo/go/go/types/termlist_test.go index dddca7a..f0d58ac 100644 --- a/libgo/go/go/types/termlist_test.go +++ b/libgo/go/go/types/termlist_test.go @@ -106,35 +106,6 @@ func TestTermlistNorm(t *testing.T) { } } -func TestTermlistSingleType(t *testing.T) { - // helper to deal with nil types - tstring := func(typ Type) string { - if typ == nil { - return "nil" - } - return typ.String() - } - - for test, want := range map[string]string{ - "∅": "nil", - "𝓤": "nil", - "int": "int", - "myInt": "myInt", - "~int": "int", - "~int ∪ string": "nil", - "~int ∪ myInt": "int", - "∅ ∪ int": "int", - "∅ ∪ ~int": "int", - "∅ ∪ ~int ∪ string": "nil", - } { - xl := maketl(test) - got := tstring(xl.singleType()) - if got != want { - t.Errorf("(%v).singleType() == %v; want %v", test, got, want) - } - } -} - func TestTermlistUnion(t *testing.T) { for _, test := range []struct { xl, yl, want string diff --git a/libgo/go/go/types/testdata/examples/inference.go2 b/libgo/go/go/types/testdata/examples/inference.go2 index 70d393b..e59a544 100644 --- a/libgo/go/go/types/testdata/examples/inference.go2 +++ b/libgo/go/go/types/testdata/examples/inference.go2 @@ -78,7 +78,7 @@ func _() { related1(si, "foo" /* ERROR cannot use "foo" */ ) } -func related2[Elem any, Slice interface{~[]Elem}](e Elem, s Slice) {} +func related2[Elem any, Slice interface{[]Elem}](e Elem, s Slice) {} func _() { // related2 can be called with explicit instantiation. @@ -109,16 +109,8 @@ func _() { related3[int, []int]() related3[byte, List[byte]]() - // Alternatively, the 2nd type argument can be inferred - // from the first one through constraint type inference. - related3[int]() - - // The inferred type is the core type of the Slice - // type parameter. - var _ []int = related3[int]() - - // It is not the defined parameterized type List. - type anotherList []float32 - var _ anotherList = related3[float32]() // valid - var _ anotherList = related3 /* ERROR cannot use .* \(value of type List\[float32\]\) as anotherList */ [float32, List[float32]]() + // The 2nd type argument cannot be inferred from the first + // one because there's two possible choices: []Elem and + // List[Elem]. + related3 /* ERROR cannot infer Slice */ [int]() } diff --git a/libgo/go/go/types/testdata/examples/methods.go2 b/libgo/go/go/types/testdata/examples/methods.go2 index 1d76d55..a46f789 100644 --- a/libgo/go/go/types/testdata/examples/methods.go2 +++ b/libgo/go/go/types/testdata/examples/methods.go2 @@ -35,7 +35,7 @@ func (t T1[[ /* ERROR must be an identifier */ ]int]) m2() {} // style. In m3 below, int is the name of the local receiver type parameter // and it shadows the predeclared identifier int which then cannot be used // anymore as expected. -// This is no different from locally redelaring a predeclared identifier +// This is no different from locally re-declaring a predeclared identifier // and usually should be avoided. There are some notable exceptions; e.g., // sometimes it makes sense to use the identifier "copy" which happens to // also be the name of a predeclared built-in function. diff --git a/libgo/go/go/types/testdata/fixedbugs/issue41124.go2 b/libgo/go/go/types/testdata/fixedbugs/issue41124.go2 index 7f55ba8..4550dd7 100644 --- a/libgo/go/go/types/testdata/fixedbugs/issue41124.go2 +++ b/libgo/go/go/types/testdata/fixedbugs/issue41124.go2 @@ -47,7 +47,7 @@ type _ struct{ } type _ struct{ - I3 // ERROR interface is .* comparable + I3 // ERROR interface contains type constraints } // General composite types. @@ -59,19 +59,19 @@ type ( _ []I1 // ERROR interface is .* comparable _ []I2 // ERROR interface contains type constraints - _ *I3 // ERROR interface is .* comparable + _ *I3 // ERROR interface contains type constraints _ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints - _ chan I3 // ERROR interface is .* comparable + _ chan I3 // ERROR interface contains type constraints _ func(I1 /* ERROR interface is .* comparable */ ) _ func() I2 // ERROR interface contains type constraints ) // Other cases. -var _ = [...]I3 /* ERROR interface is .* comparable */ {} +var _ = [...]I3 /* ERROR interface contains type constraints */ {} func _(x interface{}) { - _ = x.(I3 /* ERROR interface is .* comparable */ ) + _ = x.(I3 /* ERROR interface contains type constraints */ ) } type T1[_ any] struct{} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue45548.go2 b/libgo/go/go/types/testdata/fixedbugs/issue45548.go2 index b8ba0ad..01c9672 100644 --- a/libgo/go/go/types/testdata/fixedbugs/issue45548.go2 +++ b/libgo/go/go/types/testdata/fixedbugs/issue45548.go2 @@ -4,7 +4,7 @@ package p -func f[F interface{~*Q}, G interface{~*R}, Q, R any](q Q, r R) {} +func f[F interface{*Q}, G interface{*R}, Q, R any](q Q, r R) {} func _() { f[*float64, *int](1, 2) diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51229.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51229.go2 new file mode 100644 index 0000000..808b647 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51229.go2 @@ -0,0 +1,164 @@ +// Copyright 2022 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. + +package p + +// Constraint type inference should be independent of the +// ordering of the type parameter declarations. Try all +// permutations in the test case below. +// Permutations produced by https://go.dev/play/p/PHcZNGJTEBZ. + +func f00[S1 ~[]E1, S2 ~[]E2, E1 ~byte, E2 ~byte](S1, S2) {} +func f01[S2 ~[]E2, S1 ~[]E1, E1 ~byte, E2 ~byte](S1, S2) {} +func f02[E1 ~byte, S1 ~[]E1, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f03[S1 ~[]E1, E1 ~byte, S2 ~[]E2, E2 ~byte](S1, S2) {} +func f04[S2 ~[]E2, E1 ~byte, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f05[E1 ~byte, S2 ~[]E2, S1 ~[]E1, E2 ~byte](S1, S2) {} +func f06[E2 ~byte, S2 ~[]E2, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f07[S2 ~[]E2, E2 ~byte, S1 ~[]E1, E1 ~byte](S1, S2) {} +func f08[S1 ~[]E1, E2 ~byte, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f09[E2 ~byte, S1 ~[]E1, S2 ~[]E2, E1 ~byte](S1, S2) {} +func f10[S2 ~[]E2, S1 ~[]E1, E2 ~byte, E1 ~byte](S1, S2) {} +func f11[S1 ~[]E1, S2 ~[]E2, E2 ~byte, E1 ~byte](S1, S2) {} +func f12[S1 ~[]E1, E1 ~byte, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f13[E1 ~byte, S1 ~[]E1, E2 ~byte, S2 ~[]E2](S1, S2) {} +func f14[E2 ~byte, S1 ~[]E1, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f15[S1 ~[]E1, E2 ~byte, E1 ~byte, S2 ~[]E2](S1, S2) {} +func f16[E1 ~byte, E2 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f17[E2 ~byte, E1 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {} +func f18[E2 ~byte, E1 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f19[E1 ~byte, E2 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {} +func f20[S2 ~[]E2, E2 ~byte, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f21[E2 ~byte, S2 ~[]E2, E1 ~byte, S1 ~[]E1](S1, S2) {} +func f22[E1 ~byte, S2 ~[]E2, E2 ~byte, S1 ~[]E1](S1, S2) {} +func f23[S2 ~[]E2, E1 ~byte, E2 ~byte, S1 ~[]E1](S1, S2) {} + +type myByte byte + +func _(a []byte, b []myByte) { + f00(a, b) + f01(a, b) + f02(a, b) + f03(a, b) + f04(a, b) + f05(a, b) + f06(a, b) + f07(a, b) + f08(a, b) + f09(a, b) + f10(a, b) + f11(a, b) + f12(a, b) + f13(a, b) + f14(a, b) + f15(a, b) + f16(a, b) + f17(a, b) + f18(a, b) + f19(a, b) + f20(a, b) + f21(a, b) + f22(a, b) + f23(a, b) +} + +// Constraint type inference may have to iterate. +// Again, the order of the type parameters shouldn't matter. + +func g0[S ~[]E, M ~map[string]S, E any](m M) {} +func g1[M ~map[string]S, S ~[]E, E any](m M) {} +func g2[E any, S ~[]E, M ~map[string]S](m M) {} +func g3[S ~[]E, E any, M ~map[string]S](m M) {} +func g4[M ~map[string]S, E any, S ~[]E](m M) {} +func g5[E any, M ~map[string]S, S ~[]E](m M) {} + +func _(m map[string][]byte) { + g0(m) + g1(m) + g2(m) + g3(m) + g4(m) + g5(m) +} + +// Worst-case scenario. +// There are 10 unknown type parameters. In each iteration of +// constraint type inference we infer one more, from right to left. +// Each iteration looks repeatedly at all 11 type parameters, +// requiring a total of 10*11 = 110 iterations with the current +// implementation. Pathological case. + +func h[K any, J ~*K, I ~*J, H ~*I, G ~*H, F ~*G, E ~*F, D ~*E, C ~*D, B ~*C, A ~*B](x A) {} + +func _(x **********int) { + h(x) +} + +// Examples with channel constraints and tilde. + +func ch1[P chan<- int]() (_ P) { return } // core(P) == chan<- int (single type, no tilde) +func ch2[P ~chan int]() { return } // core(P) == ~chan<- int (tilde) +func ch3[P chan E, E any](E) { return } // core(P) == chan<- E (single type, no tilde) +func ch4[P chan E | ~chan<- E, E any](E) { return } // core(P) == ~chan<- E (tilde) +func ch5[P chan int | chan<- int]() { return } // core(P) == chan<- int (not a single type) + +func _() { + // P can be inferred as there's a single specific type and no tilde. + var _ chan int = ch1 /* ERROR cannot use ch1.*value of type chan<- int */ () + var _ chan<- int = ch1() + + // P cannot be inferred as there's a tilde. + ch2 /* ERROR cannot infer P */ () + type myChan chan int + ch2[myChan]() + + // P can be inferred as there's a single specific type and no tilde. + var e int + ch3(e) + + // P cannot be inferred as there's more than one specific type and a tilde. + ch4 /* ERROR cannot infer P */ (e) + _ = ch4[chan int] + + // P cannot be inferred as there's more than one specific type. + ch5 /* ERROR cannot infer P */ () + ch5[chan<- int]() +} + +// test case from issue + +func equal[M1 ~map[K1]V1, M2 ~map[K2]V2, K1, K2 ~uint32, V1, V2 ~string](m1 M1, m2 M2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 { + return false + } + } + return true +} + +func equalFixed[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool { + if len(m1) != len(m2) { + return false + } + for k, v1 := range m1 { + if v2, ok := m2[K2(k)]; !ok || v1 != V1(v2) { + return false + } + } + return true +} + +type ( + someNumericID uint32 + someStringID string +) + +func _() { + foo := map[uint32]string{10: "bar"} + bar := map[someNumericID]someStringID{10: "bar"} + equal(foo, bar) +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51232.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51232.go2 new file mode 100644 index 0000000..3fa6a05 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51232.go2 @@ -0,0 +1,30 @@ +// Copyright 2022 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. + +package p + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn func() Fn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} + +func NewConcrete[RCT RC[RG], RG any](Rc RCT) F /* ERROR got 1 arguments */ [RCT] { + // TODO(rfindley): eliminate the duplicate error below. + return & /* ERROR cannot use .* as F\[RCT\] */ concreteF /* ERROR got 1 arguments */ [RCT]{ + makeFn: nil, + } +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51233.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51233.go2 new file mode 100644 index 0000000..9c15028 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51233.go2 @@ -0,0 +1,27 @@ +// Copyright 2022 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. + +package p + +// As of issue #51527, type-type inference has been disabled. + +type RC[RG any] interface { + ~[]RG +} + +type Fn[RCT RC[RG], RG any] func(RCT) + +type FFn[RCT RC[RG], RG any] func() Fn /* ERROR got 1 arguments */ [RCT] + +type F[RCT RC[RG], RG any] interface { + Fn() Fn /* ERROR got 1 arguments */ [RCT] +} + +type concreteF[RCT RC[RG], RG any] struct { + makeFn FFn /* ERROR got 1 arguments */ [RCT] +} + +func (c *concreteF[RCT, RG]) Fn() Fn /* ERROR got 1 arguments */ [RCT] { + return c.makeFn() +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51257.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51257.go2 new file mode 100644 index 0000000..8a3eb32 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51257.go2 @@ -0,0 +1,46 @@ +// Copyright 2022 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. + +package p + +func f[_ comparable]() {} + +type S1 struct{ x int } +type S2 struct{ x any } +type S3 struct{ x [10]interface{ m() } } + +func _[P1 comparable, P2 S2]() { + _ = f[S1] + _ = f[S2 /* ERROR S2 does not implement comparable */ ] + _ = f[S3 /* ERROR S3 does not implement comparable */ ] + + type L1 struct { x P1 } + type L2 struct { x P2 } + _ = f[L1] + _ = f[L2 /* ERROR L2 does not implement comparable */ ] +} + + +// example from issue + +type Set[T comparable] map[T]struct{} + +func NewSetFromSlice[T comparable](items []T) *Set[T] { + s := Set[T]{} + + for _, item := range items { + s[item] = struct{}{} + } + + return &s +} + +type T struct{ x any } + +func main() { + NewSetFromSlice /* ERROR T does not implement comparable */ ([]T{ + {"foo"}, + {5}, + }) +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51335.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51335.go2 new file mode 100644 index 0000000..0b5a1af --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51335.go2 @@ -0,0 +1,16 @@ +// Copyright 2022 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. + +package p + +type S1 struct{} +type S2 struct{} + +func _[P *S1|*S2]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} + +func _[P *S1|S1]() { + _= []P{{ /* ERROR invalid composite literal element type P: no core type */ }} +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51339.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51339.go2 new file mode 100644 index 0000000..38f8610 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51339.go2 @@ -0,0 +1,18 @@ +// Copyright 2022 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 is tested when running "go test -run Manual" +// without source arguments. Use for one-off debugging. + +package p + +type T[P any, B *P] struct{} + +func (T /* ERROR cannot use generic type */ ) m0() {} + +// TODO(rfindley): eliminate the duplicate errors here. +func (/* ERROR got 1 type parameter, but receiver base type declares 2 */ T /* ERROR got 1 arguments but 2 type parameters */ [_]) m1() {} +func (T[_, _]) m2() {} +// TODO(gri) this error is unfortunate (issue #51343) +func (T /* ERROR got 3 arguments but 2 type parameters */ [_, _, _]) m3() {} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51360.go b/libgo/go/go/types/testdata/fixedbugs/issue51360.go new file mode 100644 index 0000000..fe3de04 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51360.go @@ -0,0 +1,13 @@ +// Copyright 2022 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. + +package p + +func _() { + len.Println /* ERROR cannot select on len */ + len.Println /* ERROR cannot select on len */ () + _ = len.Println /* ERROR cannot select on len */ + _ = len /* ERROR cannot index len */ [0] + _ = *len /* ERROR cannot indirect len */ +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51376.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51376.go2 new file mode 100644 index 0000000..d51607b --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51376.go2 @@ -0,0 +1,24 @@ +// Copyright 2022 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. + +package p + +type Map map[string]int + +func f[M ~map[K]V, K comparable, V any](M) {} +func g[M map[K]V, K comparable, V any](M) {} + +func _[M1 ~map[K]V, M2 map[K]V, K comparable, V any]() { + var m1 M1 + f(m1) + g /* ERROR M1 does not implement map\[K\]V */ (m1) // M1 has tilde + + var m2 M2 + f(m2) + g(m2) // M1 does not have tilde + + var m3 Map + f(m3) + g /* ERROR Map does not implement map\[string\]int */ (m3) // M in g does not have tilde +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51386.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51386.go2 new file mode 100644 index 0000000..ef62239 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51386.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 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. + +package p + +type myString string + +func _[P ~string | ~[]byte | ~[]rune]() { + _ = P("") + const s myString = "" + _ = P(s) +} + +func _[P myString]() { + _ = P("") +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51437.go b/libgo/go/go/types/testdata/fixedbugs/issue51437.go new file mode 100644 index 0000000..3762615 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51437.go @@ -0,0 +1,17 @@ +// Copyright 2022 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. + +package p + +type T struct{} + +func (T) m() []int { return nil } + +func f(x T) { + for _, x := range func() []int { + return x.m() // x declared in parameter list of f + }() { + _ = x // x declared by range clause + } +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51472.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51472.go2 new file mode 100644 index 0000000..3126770 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51472.go2 @@ -0,0 +1,54 @@ +// Copyright 2022 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. + +package p + +func _[T comparable](x T) { + _ = x == x +} + +func _[T interface{interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; interface{comparable}}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~int}](x T) { + _ = x == x +} + +func _[T interface{comparable; ~[]byte}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{comparable; ~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// TODO(gri) The error message here should be better. See issue #51525. +func _[T interface{~int; ~string}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +func _[T interface{comparable; interface{~int}; interface{int|float64}}](x T) { + _ = x == x +} + +func _[T interface{interface{comparable; ~int}; interface{~float64; comparable; m()}}](x T) { + _ = x /* ERROR cannot compare */ == x +} + +// test case from issue + +func f[T interface{comparable; []byte|string}](x T) { + _ = x == x +} + +func _(s []byte) { + f /* ERROR \[\]byte does not implement interface{comparable; \[\]byte\|string} */ (s) + _ = f[[ /* ERROR does not implement */ ]byte] +} diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51509.go b/libgo/go/go/types/testdata/fixedbugs/issue51509.go new file mode 100644 index 0000000..5ae4717 --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51509.go @@ -0,0 +1,7 @@ +// Copyright 2022 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. + +package p + +type T /* ERROR illegal cycle */ T.x diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51578.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51578.go2 new file mode 100644 index 0000000..5c204ba --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51578.go2 @@ -0,0 +1,17 @@ +// Copyright 2022 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. + +package p + +var _ = (*interface /* ERROR interface contains type constraints */ {int})(nil) + +// abbreviated test case from issue + +type TypeSet interface{ int | string } + +func _() { + f((*TypeSet /* ERROR interface contains type constraints */)(nil)) +} + +func f(any) {} \ No newline at end of file diff --git a/libgo/go/go/types/testdata/fixedbugs/issue51593.go2 b/libgo/go/go/types/testdata/fixedbugs/issue51593.go2 new file mode 100644 index 0000000..e06c39f --- /dev/null +++ b/libgo/go/go/types/testdata/fixedbugs/issue51593.go2 @@ -0,0 +1,13 @@ +// Copyright 2022 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. + +package p + +func f[P interface{ m(R) }, R any]() {} + +type T = interface { m(int) } + +func _() { + _ = f /* ERROR cannot infer R */ [T] // don't crash in type inference +} diff --git a/libgo/go/go/types/type.go b/libgo/go/go/types/type.go index 323365a..1306375 100644 --- a/libgo/go/go/types/type.go +++ b/libgo/go/go/types/type.go @@ -7,9 +7,7 @@ package types // A Type represents a type of Go. // All types implement the Type interface. type Type interface { - // Underlying returns the underlying type of a type - // w/o following forwarding chains. Only used by - // client packages. + // Underlying returns the underlying type of a type. Underlying() Type // String returns a string representation of a type. diff --git a/libgo/go/go/types/typeparam.go b/libgo/go/go/types/typeparam.go index 71e6861..40d96ac 100644 --- a/libgo/go/go/types/typeparam.go +++ b/libgo/go/go/types/typeparam.go @@ -30,11 +30,13 @@ type TypeParam struct { // or Signature type by calling SetTypeParams. Setting a type parameter on more // than one type will result in a panic. // -// The constraint argument can be nil, and set later via SetConstraint. +// The constraint argument can be nil, and set later via SetConstraint. If the +// constraint is non-nil, it must be fully defined. func NewTypeParam(obj *TypeName, constraint Type) *TypeParam { return (*Checker)(nil).newTypeParam(obj, constraint) } +// check may be nil func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // Always increment lastID, even if it is not used. id := nextID() @@ -49,9 +51,7 @@ func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam { // iface may mutate typ.bound, so we must ensure that iface() is called // at least once before the resulting TypeParam escapes. if check != nil { - check.later(func() { - typ.iface() - }) + check.needsCleanup(typ) } else if constraint != nil { typ.iface() } @@ -74,8 +74,10 @@ func (t *TypeParam) Constraint() Type { // SetConstraint sets the type constraint for t. // -// SetConstraint should not be called concurrently, but once SetConstraint -// returns the receiver t is safe for concurrent use. +// It must be called by users of NewTypeParam after the bound's underlying is +// fully defined, and before using the type parameter in any way other than to +// form other types. Once SetConstraint returns the receiver, t is safe for +// concurrent use. func (t *TypeParam) SetConstraint(bound Type) { if bound == nil { panic("nil constraint") @@ -95,9 +97,12 @@ func (t *TypeParam) String() string { return TypeString(t, nil) } // ---------------------------------------------------------------------------- // Implementation +func (t *TypeParam) cleanup() { + t.iface() + t.check = nil +} + // iface returns the constraint interface of t. -// TODO(gri) If we make tparamIsIface the default, this should be renamed to under -// (similar to Named.under). func (t *TypeParam) iface() *Interface { bound := t.bound @@ -138,16 +143,6 @@ func (t *TypeParam) iface() *Interface { return ityp } -// singleType returns the single type of the type parameter constraint; or nil. -func (t *TypeParam) singleType() Type { - return t.iface().typeSet().singleType() -} - -// hasTerms reports whether the type parameter constraint has specific type terms. -func (t *TypeParam) hasTerms() bool { - return t.iface().typeSet().hasTerms() -} - // is calls f with the specific type terms of t's constraint and reports whether // all calls to f returned true. If there are no specific terms, is // returns the result of f(nil). diff --git a/libgo/go/go/types/typeset.go b/libgo/go/go/types/typeset.go index e1f7301..6603383 100644 --- a/libgo/go/go/types/typeset.go +++ b/libgo/go/go/types/typeset.go @@ -15,18 +15,25 @@ import ( // API // A _TypeSet represents the type set of an interface. +// Because of existing language restrictions, methods can be "factored out" +// from the terms. The actual type set is the intersection of the type set +// implied by the methods and the type set described by the terms and the +// comparable bit. To test whether a type is included in a type set +// ("implements" relation), the type must implement all methods _and_ be +// an element of the type set described by the terms and the comparable bit. +// If the term list describes the set of all types and comparable is true, +// only comparable types are meant; in all other cases comparable is false. type _TypeSet struct { - comparable bool // if set, the interface is or embeds comparable - // TODO(gri) consider using a set for the methods for faster lookup - methods []*Func // all methods of the interface; sorted by unique ID - terms termlist // type terms of the type set + methods []*Func // all methods of the interface; sorted by unique ID + terms termlist // type terms of the type set + comparable bool // invariant: !comparable || terms.isAll() } // IsEmpty reports whether type set s is the empty set. func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() } // IsAll reports whether type set s is the set of all types (corresponding to the empty interface). -func (s *_TypeSet) IsAll() bool { return !s.comparable && len(s.methods) == 0 && s.terms.isAll() } +func (s *_TypeSet) IsAll() bool { return s.IsMethodSet() && len(s.methods) == 0 } // IsMethodSet reports whether the interface t is fully described by its method set. func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() } @@ -37,17 +44,10 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool { return s.comparable } return s.is(func(t *term) bool { - return t != nil && comparable(t.typ, seen, nil) + return t != nil && comparable(t.typ, false, seen, nil) }) } -// TODO(gri) IsTypeSet is not a great name for this predicate. Find a better one. - -// IsTypeSet reports whether the type set s is represented by a finite set of underlying types. -func (s *_TypeSet) IsTypeSet() bool { - return !s.comparable && len(s.methods) == 0 -} - // NumMethods returns the number of methods available. func (s *_TypeSet) NumMethods() int { return len(s.methods) } @@ -101,9 +101,6 @@ func (s *_TypeSet) String() string { // hasTerms reports whether the type set has specific type terms. func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll() } -// singleType returns the single type in s if there is exactly one; otherwise the result is nil. -func (s *_TypeSet) singleType() Type { return s.terms.singleType() } - // subsetOf reports whether s1 ⊆ s2. func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) } @@ -220,12 +217,12 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T var todo []*Func var seen objset - var methods []*Func + var allMethods []*Func mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages addMethod := func(pos token.Pos, m *Func, explicit bool) { switch other := seen.insert(m); { case other == nil: - methods = append(methods, m) + allMethods = append(allMethods, m) mpos[m] = pos case explicit: if check == nil { @@ -260,7 +257,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } // collect embedded elements - var allTerms = allTermlist + allTerms := allTermlist + allComparable := false for i, typ := range ityp.embeddeds { // The embedding position is nil for imported interfaces // and also for interface copies after substitution (but @@ -269,6 +267,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T if ityp.embedPos != nil { pos = (*ityp.embedPos)[i] } + var comparable bool var terms termlist switch u := under(typ).(type) { case *Interface: @@ -280,9 +279,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T check.errorf(atPos(pos), _UnsupportedFeature, "embedding constraint interface %s requires go1.18 or later", typ) continue } - if tset.comparable { - ityp.tset.comparable = true - } + comparable = tset.comparable for _, m := range tset.methods { addMethod(pos, m, false) // use embedding position pos rather than m.pos } @@ -296,6 +293,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T if tset == &invalidTypeSet { continue // ignore invalid unions } + assert(!tset.comparable) + assert(len(tset.methods) == 0) terms = tset.terms default: if u == Typ[Invalid] { @@ -307,11 +306,11 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } terms = termlist{{false, typ}} } - // The type set of an interface is the intersection - // of the type sets of all its elements. - // Intersection cannot produce longer termlists and - // thus cannot overflow. - allTerms = allTerms.intersect(terms) + + // The type set of an interface is the intersection of the type sets of all its elements. + // Due to language restrictions, only embedded interfaces can add methods, they are handled + // separately. Here we only need to intersect the term lists and comparable bits. + allTerms, allComparable = intersectTermLists(allTerms, allComparable, terms, comparable) } ityp.embedPos = nil // not needed anymore (errors have been reported) @@ -324,15 +323,46 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } } - if methods != nil { - sort.Sort(byUniqueMethodName(methods)) - ityp.tset.methods = methods + ityp.tset.comparable = allComparable + if len(allMethods) != 0 { + sortMethods(allMethods) + ityp.tset.methods = allMethods } ityp.tset.terms = allTerms return ityp.tset } +// TODO(gri) The intersectTermLists function belongs to the termlist implementation. +// The comparable type set may also be best represented as a term (using +// a special type). + +// intersectTermLists computes the intersection of two term lists and respective comparable bits. +// xcomp, ycomp are valid only if xterms.isAll() and yterms.isAll() respectively. +func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool) (termlist, bool) { + terms := xterms.intersect(yterms) + // If one of xterms or yterms is marked as comparable, + // the result must only include comparable types. + comp := xcomp || ycomp + if comp && !terms.isAll() { + // only keep comparable terms + i := 0 + for _, t := range terms { + assert(t.typ != nil) + if Comparable(t.typ) { + terms[i] = t + i++ + } + } + terms = terms[:i] + if !terms.isAll() { + comp = false + } + } + assert(!comp || terms.isAll()) // comparable invariant + return terms, comp +} + func sortMethods(list []*Func) { sort.Sort(byUniqueMethodName(list)) } diff --git a/libgo/go/go/types/typeset_test.go b/libgo/go/go/types/typeset_test.go index 1c0eece..2bbe611 100644 --- a/libgo/go/go/types/typeset_test.go +++ b/libgo/go/go/types/typeset_test.go @@ -26,9 +26,9 @@ func TestTypeSetString(t *testing.T) { "{int; string}": "∅", "{comparable}": "{comparable}", - "{comparable; int}": "{comparable; int}", - "{~int; comparable}": "{comparable; ~int}", - "{int|string; comparable}": "{comparable; int ∪ string}", + "{comparable; int}": "{int}", + "{~int; comparable}": "{~int}", + "{int|string; comparable}": "{int ∪ string}", "{comparable; int; string}": "∅", "{m()}": "{func (p.T).m()}", @@ -38,8 +38,8 @@ func TestTypeSetString(t *testing.T) { "{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}", "{comparable; error}": "{comparable; func (error).Error() string}", - "{m(); comparable; int|float32|string}": "{comparable; func (p.T).m(); int ∪ float32 ∪ string}", - "{m1(); int; m2(); comparable }": "{comparable; func (p.T).m1(); func (p.T).m2(); int}", + "{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}", + "{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}", "{E}; type E interface{}": "𝓤", "{E}; type E interface{int;string}": "∅", diff --git a/libgo/go/go/types/typexpr.go b/libgo/go/go/types/typexpr.go index db6a904..5bb2d8f 100644 --- a/libgo/go/go/types/typexpr.go +++ b/libgo/go/go/types/typexpr.go @@ -144,10 +144,16 @@ func (check *Checker) typ(e ast.Expr) Type { // constraint interface. func (check *Checker) varType(e ast.Expr) Type { typ := check.definedType(e, nil) + check.validVarType(e, typ) + return typ +} +// validVarType reports an error if typ is a constraint interface. +// The expression e is used for error reporting, if any. +func (check *Checker) validVarType(e ast.Expr, typ Type) { // If we have a type parameter there's nothing to do. if isTypeParam(typ) { - return typ + return } // We don't want to call under() or complete interfaces while we are in @@ -165,8 +171,6 @@ func (check *Checker) varType(e ast.Expr) Type { } } }) - - return typ } // definedType is like typ but also accepts a type name def. @@ -254,7 +258,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { case *ast.SelectorExpr: var x operand - check.selector(&x, e) + check.selector(&x, e, def) switch x.mode { case typexpr: @@ -323,7 +327,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { return typ case *ast.InterfaceType: - typ := new(Interface) + typ := check.newInterface() def.setUnderlying(typ) if def != nil { typ.obj = def.obj @@ -415,10 +419,14 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // evaluate arguments targs := check.typeList(ix.Indices) if targs == nil { - def.setUnderlying(Typ[Invalid]) // avoid later errors due to lazy instantiation + def.setUnderlying(Typ[Invalid]) // avoid errors later due to lazy instantiation return Typ[Invalid] } + // enableTypeTypeInference controls whether to infer missing type arguments + // using constraint type inference. See issue #51527. + const enableTypeTypeInference = false + // create the instance ctxt := check.bestContext(nil) h := ctxt.instanceHash(orig, targs) @@ -438,19 +446,18 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re def.setUnderlying(inst) inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) { - tparams := orig.TypeParams().list() + tparams := n.orig.TypeParams().list() - inferred := targs - if len(targs) < len(tparams) { + targs := n.targs.list() + if enableTypeTypeInference && len(targs) < len(tparams) { // If inference fails, len(inferred) will be 0, and inst.underlying will // be set to Typ[Invalid] in expandNamed. - inferred = check.infer(ix.Orig, tparams, targs, nil, nil) + inferred := check.infer(ix.Orig, tparams, targs, nil, nil) if len(inferred) > len(targs) { - inst.targs = newTypeList(inferred) + n.targs = newTypeList(inferred) } } - check.recordInstance(ix.Orig, inferred, inst) return expandNamed(ctxt, n, pos) } @@ -463,6 +470,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // Since check is non-nil, we can still mutate inst. Unpinning the resolver // frees some memory. inst.resolver = nil + check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) if check.validateTArgLen(pos, inst.tparams.Len(), inst.targs.Len()) { if i, err := check.verify(pos, inst.tparams.list(), inst.targs.list()); err != nil { diff --git a/libgo/go/go/types/unify.go b/libgo/go/go/types/unify.go index ac904d6..7b9aeee 100644 --- a/libgo/go/go/types/unify.go +++ b/libgo/go/go/types/unify.go @@ -247,6 +247,17 @@ func (d *tparamsList) set(i int, typ Type) { } } +// unknowns returns the number of type parameters for which no type has been set yet. +func (d *tparamsList) unknowns() int { + n := 0 + for _, ti := range d.indices { + if ti <= 0 { + n++ + } + } + return n +} + // types returns the list of inferred types (via unification) for the type parameters // described by d, and an index. If all types were inferred, the returned index is < 0. // Otherwise, it is the index of the first type parameter which couldn't be inferred; @@ -349,12 +360,16 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { if enableCoreTypeUnification && !u.exact { if isTypeParam(x) && !hasName(y) { // When considering the type parameter for unification - // we look at the adjusted core type (adjCoreType). + // we look at the adjusted core term (adjusted core type + // with tilde information). // If the adjusted core type is a named type N; the // corresponding core type is under(N). Since !u.exact // and y doesn't have a name, unification will end up // comparing under(N) to y, so we can just use the core - // type instead. Optimization. + // type instead. And we can ignore the tilde because we + // already look at the underlying types on both sides + // and we have known types on both sides. + // Optimization. if cx := coreType(x); cx != nil { if traceInference { u.tracef("core %s ≡ %s", x, y) diff --git a/libgo/go/go/types/union.go b/libgo/go/go/types/union.go index 9c59279..8397d65 100644 --- a/libgo/go/go/types/union.go +++ b/libgo/go/go/types/union.go @@ -103,25 +103,27 @@ func parseUnion(check *Checker, uexpr ast.Expr) Type { if !Identical(u, t.typ) { check.errorf(tlist[i], _InvalidUnion, "invalid use of ~ (underlying type of %s is %s)", t.typ, u) - continue // don't report another error for t + continue } } // Stand-alone embedded interfaces are ok and are handled by the single-type case // in the beginning. Embedded interfaces with tilde are excluded above. If we reach - // here, we must have at least two terms in the union. - if f != nil && !f.typeSet().IsTypeSet() { + // here, we must have at least two terms in the syntactic term list (but not necessarily + // in the term list of the union's type set). + if f != nil { + tset := f.typeSet() switch { - case f.typeSet().NumMethods() != 0: + case tset.NumMethods() != 0: check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s contains methods)", t, t) + continue case t.typ == universeComparable.Type(): check.error(tlist[i], _InvalidUnion, "cannot use comparable in union") - case f.typeSet().comparable: + continue + case tset.comparable: check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s embeds comparable)", t, t) - default: - panic("not a type set but no methods and not comparable") + continue } - continue // don't report another error for t } // Report overlapping (non-disjoint) terms such as diff --git a/libgo/go/go/types/universe.go b/libgo/go/go/types/universe.go index 3421634..303ada4 100644 --- a/libgo/go/go/types/universe.go +++ b/libgo/go/go/types/universe.go @@ -112,7 +112,7 @@ func defPredeclaredTypes() { typ := NewNamed(obj, nil, nil) // interface{} // marked as comparable - ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{true, nil, allTermlist}} + ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{nil, allTermlist, true}} typ.SetUnderlying(ityp) def(obj) diff --git a/libgo/go/go/types/validtype.go b/libgo/go/go/types/validtype.go index c4ec2f2..7d7029b 100644 --- a/libgo/go/go/types/validtype.go +++ b/libgo/go/go/types/validtype.go @@ -79,7 +79,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn // would have reported a type cycle and couldn't have been // imported in the first place. assert(t.obj.pkg == check.pkg) - t.underlying = Typ[Invalid] // t is in the current package (no race possibilty) + t.underlying = Typ[Invalid] // t is in the current package (no race possibility) // Find the starting point of the cycle and report it. for i, tn := range path { if tn == t.obj { diff --git a/libgo/go/internal/fuzz/coverage.go b/libgo/go/internal/fuzz/coverage.go index 3dee73b..88f98a1 100644 --- a/libgo/go/internal/fuzz/coverage.go +++ b/libgo/go/internal/fuzz/coverage.go @@ -66,6 +66,17 @@ func countNewCoverageBits(base, snapshot []byte) int { return n } +// isCoverageSubset returns true if all the base coverage bits are set in +// snapshot +func isCoverageSubset(base, snapshot []byte) bool { + for i, v := range base { + if v&snapshot[i] != v { + return false + } + } + return true +} + // hasCoverageBit returns true if snapshot has at least one bit set that is // also set in base. func hasCoverageBit(base, snapshot []byte) bool { diff --git a/libgo/go/internal/fuzz/encoding.go b/libgo/go/internal/fuzz/encoding.go index 2bfa02b..c95d9e0 100644 --- a/libgo/go/internal/fuzz/encoding.go +++ b/libgo/go/internal/fuzz/encoding.go @@ -10,7 +10,9 @@ import ( "go/ast" "go/parser" "go/token" + "math" "strconv" + "unicode/utf8" ) // encVersion1 will be the first line of a file with version 1 encoding. @@ -27,13 +29,64 @@ func marshalCorpusFile(vals ...any) []byte { // instead of changing to byte and rune respectively. for _, val := range vals { switch t := val.(type) { - case int, int8, int16, int64, uint, uint16, uint32, uint64, float32, float64, bool: + case int, int8, int16, int64, uint, uint16, uint32, uint64, bool: fmt.Fprintf(b, "%T(%v)\n", t, t) + case float32: + if math.IsNaN(float64(t)) && math.Float32bits(t) != math.Float32bits(float32(math.NaN())) { + // We encode unusual NaNs as hex values, because that is how users are + // likely to encounter them in literature about floating-point encoding. + // This allows us to reproduce fuzz failures that depend on the specific + // NaN representation (for float32 there are about 2^24 possibilities!), + // not just the fact that the value is *a* NaN. + // + // Note that the specific value of float32(math.NaN()) can vary based on + // whether the architecture represents signaling NaNs using a low bit + // (as is common) or a high bit (as commonly implemented on MIPS + // hardware before around 2012). We believe that the increase in clarity + // from identifying "NaN" with math.NaN() is worth the slight ambiguity + // from a platform-dependent value. + fmt.Fprintf(b, "math.Float32frombits(0x%x)\n", math.Float32bits(t)) + } else { + // We encode all other values — including the NaN value that is + // bitwise-identical to float32(math.Nan()) — using the default + // formatting, which is equivalent to strconv.FormatFloat with format + // 'g' and can be parsed by strconv.ParseFloat. + // + // For an ordinary floating-point number this format includes + // sufficiently many digits to reconstruct the exact value. For positive + // or negative infinity it is the string "+Inf" or "-Inf". For positive + // or negative zero it is "0" or "-0". For NaN, it is the string "NaN". + fmt.Fprintf(b, "%T(%v)\n", t, t) + } + case float64: + if math.IsNaN(t) && math.Float64bits(t) != math.Float64bits(math.NaN()) { + fmt.Fprintf(b, "math.Float64frombits(0x%x)\n", math.Float64bits(t)) + } else { + fmt.Fprintf(b, "%T(%v)\n", t, t) + } case string: fmt.Fprintf(b, "string(%q)\n", t) case rune: // int32 - fmt.Fprintf(b, "rune(%q)\n", t) + // Although rune and int32 are represented by the same type, only a subset + // of valid int32 values can be expressed as rune literals. Notably, + // negative numbers, surrogate halves, and values above unicode.MaxRune + // have no quoted representation. + // + // fmt with "%q" (and the corresponding functions in the strconv package) + // would quote out-of-range values to the Unicode replacement character + // instead of the original value (see https://go.dev/issue/51526), so + // they must be treated as int32 instead. + // + // We arbitrarily draw the line at UTF-8 validity, which biases toward the + // "rune" interpretation. (However, we accept either format as input.) + if utf8.ValidRune(t) { + fmt.Fprintf(b, "rune(%q)\n", t) + } else { + fmt.Fprintf(b, "int32(%v)\n", t) + } case byte: // uint8 + // For bytes, we arbitrarily prefer the character interpretation. + // (Every byte has a valid character encoding.) fmt.Fprintf(b, "byte(%q)\n", t) case []byte: // []uint8 fmt.Fprintf(b, "[]byte(%q)\n", t) @@ -105,44 +158,78 @@ func parseCorpusValue(line []byte) (any, error) { return []byte(s), nil } - idType, ok := call.Fun.(*ast.Ident) - if !ok { - return nil, fmt.Errorf("expected []byte or primitive type") - } - if idType.Name == "bool" { - id, ok := arg.(*ast.Ident) + var idType *ast.Ident + if selector, ok := call.Fun.(*ast.SelectorExpr); ok { + xIdent, ok := selector.X.(*ast.Ident) + if !ok || xIdent.Name != "math" { + return nil, fmt.Errorf("invalid selector type") + } + switch selector.Sel.Name { + case "Float64frombits": + idType = &ast.Ident{Name: "float64-bits"} + case "Float32frombits": + idType = &ast.Ident{Name: "float32-bits"} + default: + return nil, fmt.Errorf("invalid selector type") + } + } else { + idType, ok = call.Fun.(*ast.Ident) if !ok { - return nil, fmt.Errorf("malformed bool") + return nil, fmt.Errorf("expected []byte or primitive type") } - if id.Name == "true" { - return true, nil - } else if id.Name == "false" { - return false, nil - } else { - return nil, fmt.Errorf("true or false required for type bool") + if idType.Name == "bool" { + id, ok := arg.(*ast.Ident) + if !ok { + return nil, fmt.Errorf("malformed bool") + } + if id.Name == "true" { + return true, nil + } else if id.Name == "false" { + return false, nil + } else { + return nil, fmt.Errorf("true or false required for type bool") + } } } + var ( val string kind token.Token ) if op, ok := arg.(*ast.UnaryExpr); ok { - // Special case for negative numbers. - lit, ok := op.X.(*ast.BasicLit) - if !ok || (lit.Kind != token.INT && lit.Kind != token.FLOAT) { + switch lit := op.X.(type) { + case *ast.BasicLit: + if op.Op != token.SUB { + return nil, fmt.Errorf("unsupported operation on int/float: %v", op.Op) + } + // Special case for negative numbers. + val = op.Op.String() + lit.Value // e.g. "-" + "124" + kind = lit.Kind + case *ast.Ident: + if lit.Name != "Inf" { + return nil, fmt.Errorf("expected operation on int or float type") + } + if op.Op == token.SUB { + val = "-Inf" + } else { + val = "+Inf" + } + kind = token.FLOAT + default: return nil, fmt.Errorf("expected operation on int or float type") } - if op.Op != token.SUB { - return nil, fmt.Errorf("unsupported operation on int: %v", op.Op) - } - val = op.Op.String() + lit.Value // e.g. "-" + "124" - kind = lit.Kind } else { - lit, ok := arg.(*ast.BasicLit) - if !ok { + switch lit := arg.(type) { + case *ast.BasicLit: + val, kind = lit.Value, lit.Kind + case *ast.Ident: + if lit.Name != "NaN" { + return nil, fmt.Errorf("literal value required for primitive type") + } + val, kind = "NaN", token.FLOAT + default: return nil, fmt.Errorf("literal value required for primitive type") } - val, kind = lit.Value, lit.Kind } switch typ := idType.Name; typ { @@ -152,6 +239,14 @@ func parseCorpusValue(line []byte) (any, error) { } return strconv.Unquote(val) case "byte", "rune": + if kind == token.INT { + switch typ { + case "rune": + return parseInt(val, typ) + case "byte": + return parseUint(val, typ) + } + } if kind != token.CHAR { return nil, fmt.Errorf("character literal required for byte/rune types") } @@ -191,6 +286,24 @@ func parseCorpusValue(line []byte) (any, error) { return nil, fmt.Errorf("float or integer literal required for float64 type") } return strconv.ParseFloat(val, 64) + case "float32-bits": + if kind != token.INT { + return nil, fmt.Errorf("integer literal required for math.Float32frombits type") + } + bits, err := parseUint(val, "uint32") + if err != nil { + return nil, err + } + return math.Float32frombits(bits.(uint32)), nil + case "float64-bits": + if kind != token.FLOAT && kind != token.INT { + return nil, fmt.Errorf("integer literal required for math.Float64frombits type") + } + bits, err := parseUint(val, "uint64") + if err != nil { + return nil, err + } + return math.Float64frombits(bits.(uint64)), nil default: return nil, fmt.Errorf("expected []byte or primitive type") } @@ -200,18 +313,24 @@ func parseCorpusValue(line []byte) (any, error) { func parseInt(val, typ string) (any, error) { switch typ { case "int": - return strconv.Atoi(val) + // The int type may be either 32 or 64 bits. If 32, the fuzz tests in the + // corpus may include 64-bit values produced by fuzzing runs on 64-bit + // architectures. When running those tests, we implicitly wrap the values to + // fit in a regular int. (The test case is still “interesting”, even if the + // specific values of its inputs are platform-dependent.) + i, err := strconv.ParseInt(val, 0, 64) + return int(i), err case "int8": - i, err := strconv.ParseInt(val, 10, 8) + i, err := strconv.ParseInt(val, 0, 8) return int8(i), err case "int16": - i, err := strconv.ParseInt(val, 10, 16) + i, err := strconv.ParseInt(val, 0, 16) return int16(i), err - case "int32": - i, err := strconv.ParseInt(val, 10, 32) + case "int32", "rune": + i, err := strconv.ParseInt(val, 0, 32) return int32(i), err case "int64": - return strconv.ParseInt(val, 10, 64) + return strconv.ParseInt(val, 0, 64) default: panic("unreachable") } @@ -221,19 +340,19 @@ func parseInt(val, typ string) (any, error) { func parseUint(val, typ string) (any, error) { switch typ { case "uint": - i, err := strconv.ParseUint(val, 10, 0) + i, err := strconv.ParseUint(val, 0, 64) return uint(i), err - case "uint8": - i, err := strconv.ParseUint(val, 10, 8) + case "uint8", "byte": + i, err := strconv.ParseUint(val, 0, 8) return uint8(i), err case "uint16": - i, err := strconv.ParseUint(val, 10, 16) + i, err := strconv.ParseUint(val, 0, 16) return uint16(i), err case "uint32": - i, err := strconv.ParseUint(val, 10, 32) + i, err := strconv.ParseUint(val, 0, 32) return uint32(i), err case "uint64": - return strconv.ParseUint(val, 10, 64) + return strconv.ParseUint(val, 0, 64) default: panic("unreachable") } diff --git a/libgo/go/internal/fuzz/encoding_test.go b/libgo/go/internal/fuzz/encoding_test.go index b429d42..8e3800e 100644 --- a/libgo/go/internal/fuzz/encoding_test.go +++ b/libgo/go/internal/fuzz/encoding_test.go @@ -5,85 +5,104 @@ package fuzz import ( + "math" "strconv" - "strings" "testing" + "unicode" ) func TestUnmarshalMarshal(t *testing.T) { var tests = []struct { - in string - ok bool + desc string + in string + reject bool + want string // if different from in }{ { - in: "int(1234)", - ok: false, // missing version + desc: "missing version", + in: "int(1234)", + reject: true, }, { + desc: "malformed string", in: `go test fuzz v1 string("a"bcad")`, - ok: false, // malformed + reject: true, }, { + desc: "empty value", in: `go test fuzz v1 int()`, - ok: false, // empty value + reject: true, }, { + desc: "negative uint", in: `go test fuzz v1 uint(-32)`, - ok: false, // invalid negative uint + reject: true, }, { + desc: "int8 too large", in: `go test fuzz v1 int8(1234456)`, - ok: false, // int8 too large + reject: true, }, { + desc: "multiplication in int value", in: `go test fuzz v1 int(20*5)`, - ok: false, // expression in int value + reject: true, }, { + desc: "double negation", in: `go test fuzz v1 int(--5)`, - ok: false, // expression in int value + reject: true, }, { + desc: "malformed bool", in: `go test fuzz v1 bool(0)`, - ok: false, // malformed bool + reject: true, }, { + desc: "malformed byte", in: `go test fuzz v1 byte('aa)`, - ok: false, // malformed byte + reject: true, }, { + desc: "byte out of range", in: `go test fuzz v1 byte('☃')`, - ok: false, // byte out of range + reject: true, }, { + desc: "extra newline", in: `go test fuzz v1 -string("has final newline") +string("has extra newline") `, - ok: true, // has final newline + want: `go test fuzz v1 +string("has extra newline")`, }, { + desc: "trailing spaces", in: `go test fuzz v1 string("extra") []byte("spacing") `, - ok: true, // extra spaces in the final newline + want: `go test fuzz v1 +string("extra") +[]byte("spacing")`, }, { + desc: "float types", in: `go test fuzz v1 float64(0) float32(0)`, - ok: true, // will be an integer literal since there is no decimal }, { + desc: "various types", in: `go test fuzz v1 int(-23) int8(-2) @@ -101,19 +120,112 @@ bool(true) string("hello\\xbd\\xb2=\\xbc ⌘") float64(-12.5) float32(2.5)`, - ok: true, + }, + { + desc: "float edge cases", + // The two IEEE 754 bit patterns used for the math.Float{64,32}frombits + // encodings are non-math.NAN quiet-NaN values. Since they are not equal + // to math.NaN(), they should be re-encoded to their bit patterns. They + // are, respectively: + // * math.Float64bits(math.NaN())+1 + // * math.Float32bits(float32(math.NaN()))+1 + in: `go test fuzz v1 +float32(-0) +float64(-0) +float32(+Inf) +float32(-Inf) +float32(NaN) +float64(+Inf) +float64(-Inf) +float64(NaN) +math.Float64frombits(0x7ff8000000000002) +math.Float32frombits(0x7fc00001)`, + }, + { + desc: "int variations", + // Although we arbitrarily choose default integer bases (0 or 16), we may + // want to change those arbitrary choices in the future and should not + // break the parser. Verify that integers in the opposite bases still + // parse correctly. + in: `go test fuzz v1 +int(0x0) +int32(0x41) +int64(0xfffffffff) +uint32(0xcafef00d) +uint64(0xffffffffffffffff) +uint8(0b0000000) +byte(0x0) +byte('\000') +byte('\u0000') +byte('\'') +math.Float64frombits(9221120237041090562) +math.Float32frombits(2143289345)`, + want: `go test fuzz v1 +int(0) +rune('A') +int64(68719476735) +uint32(3405705229) +uint64(18446744073709551615) +byte('\x00') +byte('\x00') +byte('\x00') +byte('\x00') +byte('\'') +math.Float64frombits(0x7ff8000000000002) +math.Float32frombits(0x7fc00001)`, + }, + { + desc: "rune validation", + in: `go test fuzz v1 +rune(0) +rune(0x41) +rune(-1) +rune(0xfffd) +rune(0xd800) +rune(0x10ffff) +rune(0x110000) +`, + want: `go test fuzz v1 +rune('\x00') +rune('A') +int32(-1) +rune('�') +int32(55296) +rune('\U0010ffff') +int32(1114112)`, + }, + { + desc: "int overflow", + in: `go test fuzz v1 +int(0x7fffffffffffffff) +uint(0xffffffffffffffff)`, + want: func() string { + switch strconv.IntSize { + case 32: + return `go test fuzz v1 +int(-1) +uint(4294967295)` + case 64: + return `go test fuzz v1 +int(9223372036854775807) +uint(18446744073709551615)` + default: + panic("unreachable") + } + }(), }, } for _, test := range tests { - t.Run(test.in, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { vals, err := unmarshalCorpusFile([]byte(test.in)) - if test.ok && err != nil { - t.Fatalf("unmarshal unexpected error: %v", err) - } else if !test.ok && err == nil { - t.Fatalf("unmarshal unexpected success") + if test.reject { + if err == nil { + t.Fatalf("unmarshal unexpected success") + } + return } - if !test.ok { - return // skip the rest of the test + if err != nil { + t.Fatalf("unmarshal unexpected error: %v", err) } newB := marshalCorpusFile(vals...) if err != nil { @@ -122,9 +234,15 @@ float32(2.5)`, if newB[len(newB)-1] != '\n' { t.Error("didn't write final newline to corpus file") } - before, after := strings.TrimSpace(test.in), strings.TrimSpace(string(newB)) - if before != after { - t.Errorf("values changed after unmarshal then marshal\nbefore: %q\nafter: %q", before, after) + + want := test.want + if want == "" { + want = test.in + } + want += "\n" + got := string(newB) + if got != want { + t.Errorf("unexpected marshaled value\ngot:\n%s\nwant:\n%s", got, want) } }) } @@ -170,3 +288,117 @@ func BenchmarkUnmarshalCorpusFile(b *testing.B) { }) } } + +func TestByteRoundTrip(t *testing.T) { + for x := 0; x < 256; x++ { + b1 := byte(x) + buf := marshalCorpusFile(b1) + vs, err := unmarshalCorpusFile(buf) + if err != nil { + t.Fatal(err) + } + b2 := vs[0].(byte) + if b2 != b1 { + t.Fatalf("unmarshaled %v, want %v:\n%s", b2, b1, buf) + } + } +} + +func TestInt8RoundTrip(t *testing.T) { + for x := -128; x < 128; x++ { + i1 := int8(x) + buf := marshalCorpusFile(i1) + vs, err := unmarshalCorpusFile(buf) + if err != nil { + t.Fatal(err) + } + i2 := vs[0].(int8) + if i2 != i1 { + t.Fatalf("unmarshaled %v, want %v:\n%s", i2, i1, buf) + } + } +} + +func FuzzFloat64RoundTrip(f *testing.F) { + f.Add(math.Float64bits(0)) + f.Add(math.Float64bits(math.Copysign(0, -1))) + f.Add(math.Float64bits(math.MaxFloat64)) + f.Add(math.Float64bits(math.SmallestNonzeroFloat64)) + f.Add(math.Float64bits(math.NaN())) + f.Add(uint64(0x7FF0000000000001)) // signaling NaN + f.Add(math.Float64bits(math.Inf(1))) + f.Add(math.Float64bits(math.Inf(-1))) + + f.Fuzz(func(t *testing.T, u1 uint64) { + x1 := math.Float64frombits(u1) + + b := marshalCorpusFile(x1) + t.Logf("marshaled math.Float64frombits(0x%x):\n%s", u1, b) + + xs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(xs) != 1 { + t.Fatalf("unmarshaled %d values", len(xs)) + } + x2 := xs[0].(float64) + u2 := math.Float64bits(x2) + if u2 != u1 { + t.Errorf("unmarshaled %v (bits 0x%x)", x2, u2) + } + }) +} + +func FuzzRuneRoundTrip(f *testing.F) { + f.Add(rune(-1)) + f.Add(rune(0xd800)) + f.Add(rune(0xdfff)) + f.Add(rune(unicode.ReplacementChar)) + f.Add(rune(unicode.MaxASCII)) + f.Add(rune(unicode.MaxLatin1)) + f.Add(rune(unicode.MaxRune)) + f.Add(rune(unicode.MaxRune + 1)) + f.Add(rune(-0x80000000)) + f.Add(rune(0x7fffffff)) + + f.Fuzz(func(t *testing.T, r1 rune) { + b := marshalCorpusFile(r1) + t.Logf("marshaled rune(0x%x):\n%s", r1, b) + + rs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(rs) != 1 { + t.Fatalf("unmarshaled %d values", len(rs)) + } + r2 := rs[0].(rune) + if r2 != r1 { + t.Errorf("unmarshaled rune(0x%x)", r2) + } + }) +} + +func FuzzStringRoundTrip(f *testing.F) { + f.Add("") + f.Add("\x00") + f.Add(string([]rune{unicode.ReplacementChar})) + + f.Fuzz(func(t *testing.T, s1 string) { + b := marshalCorpusFile(s1) + t.Logf("marshaled %q:\n%s", s1, b) + + rs, err := unmarshalCorpusFile(b) + if err != nil { + t.Fatal(err) + } + if len(rs) != 1 { + t.Fatalf("unmarshaled %d values", len(rs)) + } + s2 := rs[0].(string) + if s2 != s1 { + t.Errorf("unmarshaled %q", s2) + } + }) +} diff --git a/libgo/go/internal/fuzz/worker.go b/libgo/go/internal/fuzz/worker.go index e984ba7..6e4c4e2 100644 --- a/libgo/go/internal/fuzz/worker.go +++ b/libgo/go/internal/fuzz/worker.go @@ -800,6 +800,7 @@ func (ws *workerServer) minimize(ctx context.Context, args minimizeArgs) (resp m if err != nil { panic(err) } + inpHash := sha256.Sum256(mem.valueCopy()) if args.Timeout != 0 { var cancel func() ctx, cancel = context.WithTimeout(ctx, args.Timeout) @@ -811,12 +812,22 @@ func (ws *workerServer) minimize(ctx context.Context, args minimizeArgs) (resp m success, err := ws.minimizeInput(ctx, vals, mem, args) if success { writeToMem(vals, mem) + outHash := sha256.Sum256(mem.valueCopy()) mem.header().rawInMem = false resp.WroteToMem = true if err != nil { resp.Err = err.Error() } else { - resp.CoverageData = coverageSnapshot + // If the values didn't change during minimization then coverageSnapshot is likely + // a dirty snapshot which represents the very last step of minimization, not the + // coverage for the initial input. In that case just return the coverage we were + // given initially, since it more accurately represents the coverage map for the + // input we are returning. + if outHash != inpHash { + resp.CoverageData = coverageSnapshot + } else { + resp.CoverageData = args.KeepCoverage + } } } return resp @@ -883,7 +894,8 @@ func (ws *workerServer) minimizeInput(ctx context.Context, vals []any, mem *shar } return true } - if keepCoverage != nil && hasCoverageBit(keepCoverage, coverageSnapshot) { + // Minimization should preserve coverage bits. + if keepCoverage != nil && isCoverageSubset(keepCoverage, coverageSnapshot) { return true } vals[args.Index] = prev diff --git a/libgo/go/net/net.go b/libgo/go/net/net.go index 77e54a9..d91e743 100644 --- a/libgo/go/net/net.go +++ b/libgo/go/net/net.go @@ -703,6 +703,12 @@ var ( _ io.Reader = (*Buffers)(nil) ) +// WriteTo writes contents of the buffers to w. +// +// WriteTo implements io.WriterTo for Buffers. +// +// WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v), +// but does not modify v[i][j] for any i, j. func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { if wv, ok := w.(buffersWriter); ok { return wv.writeBuffers(v) @@ -719,6 +725,12 @@ func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { return n, nil } +// Read from the buffers. +// +// Read implements io.Reader for Buffers. +// +// Read modifies the slice v as well as v[i] for 0 <= i < len(v), +// but does not modify v[i][j] for any i, j. func (v *Buffers) Read(p []byte) (n int, err error) { for len(p) > 0 && len(*v) > 0 { n0 := copy(p, (*v)[0]) diff --git a/libgo/go/runtime/mfinal_test.go b/libgo/go/runtime/mfinal_test.go index 81c924f..2eb60b9 100644 --- a/libgo/go/runtime/mfinal_test.go +++ b/libgo/go/runtime/mfinal_test.go @@ -45,6 +45,15 @@ func TestFinalizerType(t *testing.T) { {func(x *int) any { return Tintptr(x) }, func(v *int) { finalize(v) }}, {func(x *int) any { return (*Tint)(x) }, func(v *Tint) { finalize((*int)(v)) }}, {func(x *int) any { return (*Tint)(x) }, func(v Tinter) { finalize((*int)(v.(*Tint))) }}, + // Test case for argument spill slot. + // If the spill slot was not counted for the frame size, it will (incorrectly) choose + // call32 as the result has (exactly) 32 bytes. When the argument actually spills, + // it clobbers the caller's frame (likely the return PC). + {func(x *int) any { return x }, func(v any) [4]int64 { + print() // force spill + finalize(v.(*int)) + return [4]int64{} + }}, } for i, tt := range finalizerTests { diff --git a/libgo/go/runtime/testdata/testprogcgo/aprof.go b/libgo/go/runtime/testdata/testprogcgo/aprof.go index c70d633..1687014 100644 --- a/libgo/go/runtime/testdata/testprogcgo/aprof.go +++ b/libgo/go/runtime/testdata/testprogcgo/aprof.go @@ -10,7 +10,7 @@ package main // This is a regression test for issue 14599, where profiling fails when the // function is the first C function. Exported functions are the first C // functions, so we use an exported function. Exported functions are created in -// lexigraphical order of source files, so this file is named aprof.go to +// lexicographical order of source files, so this file is named aprof.go to // ensure its function is first. // extern void CallGoNop(); diff --git a/libgo/go/strings/builder.go b/libgo/go/strings/builder.go index 547e52e..ba4df61 100644 --- a/libgo/go/strings/builder.go +++ b/libgo/go/strings/builder.go @@ -17,10 +17,9 @@ type Builder struct { buf []byte } -// noescape hides a pointer from escape analysis. noescape is -// the identity function but escape analysis doesn't think the -// output depends on the input. noescape is inlined and currently -// compiles down to zero instructions. +// noescape hides a pointer from escape analysis. It is the identity function +// but escape analysis doesn't think the output depends on the input. +// noescape is inlined and currently compiles down to zero instructions. // USE CAREFULLY! // This was copied from the runtime; see issues 23382 and 7921. //go:nosplit diff --git a/libgo/go/syscall/syscall_unix_test.go b/libgo/go/syscall/syscall_unix_test.go index 8bfbc93..9fed7c5 100644 --- a/libgo/go/syscall/syscall_unix_test.go +++ b/libgo/go/syscall/syscall_unix_test.go @@ -328,33 +328,6 @@ func TestUnixRightsRoundtrip(t *testing.T) { } } -func TestRlimit(t *testing.T) { - var rlimit, zero syscall.Rlimit - if err := syscall.Getrlimit(syscall.RLIMIT_CPU, &rlimit); err != nil { - t.Fatalf("Getrlimit: save failed: %v", err) - } - if zero == rlimit { - t.Fatalf("Getrlimit: save failed: got zero value %#v", rlimit) - } - set := rlimit - set.Cur = set.Max - 1 - if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &set); err != nil { - t.Fatalf("Setrlimit: set failed: %#v %v", set, err) - } - var get syscall.Rlimit - if err := syscall.Getrlimit(syscall.RLIMIT_CPU, &get); err != nil { - t.Fatalf("Getrlimit: get failed: %v", err) - } - set = rlimit - set.Cur = set.Max - 1 - if set != get { - t.Fatalf("Rlimit: change failed: wanted %#v got %#v", set, get) - } - if err := syscall.Setrlimit(syscall.RLIMIT_CPU, &rlimit); err != nil { - t.Fatalf("Setrlimit: restore failed: %#v %v", rlimit, err) - } -} - func TestSeekFailure(t *testing.T) { _, err := syscall.Seek(-1, 0, io.SeekStart) if err == nil { diff --git a/libgo/merge.sh b/libgo/merge.sh index 2ad0fe9..cd2510b 100755 --- a/libgo/merge.sh +++ b/libgo/merge.sh @@ -198,7 +198,7 @@ done libgofile=${libgotd}/$f merge ${name} ${oldfile} ${newfile} ${libgofile} done - (cd ${newtd} & git ls-files .) | while read f; do + (cd ${newtd} && git ls-files .) | while read f; do if test "`basename -- $f`" = ".gitignore"; then continue fi diff --git a/libgo/misc/cgo/testsanitizers/asan_test.go b/libgo/misc/cgo/testsanitizers/asan_test.go index 1b70bce..22dcf23 100644 --- a/libgo/misc/cgo/testsanitizers/asan_test.go +++ b/libgo/misc/cgo/testsanitizers/asan_test.go @@ -63,7 +63,7 @@ func TestASAN(t *testing.T) { // sanitizer library needs a // symbolizer program and can't find it. const noSymbolizer = "external symbolizer" - // Check if -asan option can correctly print where the error occured. + // Check if -asan option can correctly print where the error occurred. if tc.errorLocation != "" && !strings.Contains(out, tc.errorLocation) && !strings.Contains(out, noSymbolizer) && -- 2.7.4