aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorMatthew Dempsky <mdempsky@google.com>2022-06-16 13:08:10 -0700
committerMatthew Dempsky <mdempsky@google.com>2022-06-16 13:08:10 -0700
commit1f4e8afafee02b69231ac31bfff7c7211bb41336 (patch)
tree03ec911ee9636245f5022e6a8e986d2b946c16fd /src/cmd
parent8a9485c023543ba688b6b316223e243fdf36b074 (diff)
parent635b1244aa7671bcd665613680f527452cac7555 (diff)
downloadgo-1f4e8afafee02b69231ac31bfff7c7211bb41336.tar.xz
[dev.unified] all: merge master (635b124) into dev.unified
Merge List: + 2022-06-16 635b1244aa cmd/go: pass GOEXPERIMENT through to subtests + 2022-06-16 ef808ae1d4 expvar: don't crash if map value set to nil + 2022-06-16 32510eea74 go/parser: remove unused method checkBinaryExpr + 2022-06-16 74f1fa6ecb cmd/go: parallelize matchPackages work in each module + 2022-06-16 1d9d99b7ce cmd/link: consider alignment in carrier symbol size calculation + 2022-06-16 bcce8ef498 spec: adjust incorrect sentence in section on rune literals + 2022-06-16 ecc268aa26 test: add test that gofrontend fails + 2022-06-15 b6c1606889 internal/goarch, internal/goos: update generators for syslist.go + 2022-06-15 91baf5cecc reflect: fix reference comment to runtime/map.go + 2022-06-15 0e3d0c9581 syscall: clarify Pdeathsig documentation on Linux + 2022-06-15 74bf90c779 go/types, types2: add test case for issue for coverage + 2022-06-15 0cd0c12f57 doc/go1.19: use matching closing tag in unix build constraint heading + 2022-06-15 97bfc77f38 syscall, runtime/internal/syscall: always zero the higher bits of return value on linux/loong64 + 2022-06-15 937fa5000a net/netip: add missing ) in ParsePrefix errors + 2022-06-15 c2c76c6f19 cmd/link: set alignment for carrier symbols + 2022-06-15 36147dd1e8 cmd/go/internal/modindex: disable indexing for modules outside GOROOT and the module cache + 2022-06-15 2a78e8afc0 test: add tests for string/[]byte/[]rune conversions + 2022-06-15 f9c0264107 net: avoid infinite recursion in Windows Resolver.lookupTXT + 2022-06-14 0dffda1383 spec: clarify "slice of bytes" and "slice of runes" through examples + 2022-06-14 c22a6c3b90 reflect: when StructOf overflows computing size/offset, panic + 2022-06-14 e1e66a03a6 cmd/compile,runtime,reflect: move embedded bit from offset to name + 2022-06-14 cb9bf93078 cmd/go: quote package directory when calling glob + 2022-06-14 cad477c922 cpu: fix typos in test case + 2022-06-13 c29be2d41c runtime: add HACKING section on nosplit functions + 2022-06-13 c5be77b687 doc/go1.19: minor edits + 2022-06-13 56bc3098f4 sync: improve linearity test robustness + 2022-06-13 1fe2810f9c sync: move lock linearity test and treat it like a performance test + 2022-06-13 6130461149 internal/testmath: add two-sample Welch's t-test for performance tests + 2022-06-13 24b9039149 doc/go1.19: prefer relative links to other parts of the Go website + 2022-06-13 fbc75dff2f cmd/cgo: remove -fsanitize=hwaddress hardware tags + 2022-06-13 5ee939b819 spec: clarify behavior of map size hint for make built-in + 2022-06-13 4703546a29 spec: add missing optional type arguments after TypeName in syntax + 2022-06-13 2c52465cb3 net: avoid darwin_arm64 bug in TestDialParallelSpuriousConnection + 2022-06-13 9228d7d7d5 doc/go1.19: add a release note for module indexing + 2022-06-13 7eeec1f6e4 cmd/compile: fix missing dict pass for type assertions + 2022-06-13 d27128b065 doc/go1.19: fix crypto tags + 2022-06-10 55590f3a2b net/http: doc: update RFC reference for appropriate HTTP codes + 2022-06-10 ff3db8d12d doc: fix typos in Go memory model + 2022-06-10 fb75c2da91 cmd/dist, cmd/internal/metadata: don't install metadata binary + 2022-06-10 386245b68e runtime: fix stack split at bad time when fuzzing + 2022-06-09 2cfbef4380 cmd/cgo: recognize clang 14 DWARF type names + 2022-06-09 c7ccabf3fe runtime/cgo: retry _beginthread on EACCES + 2022-06-09 91019cc13d runtime/cgo: merge bodies of cgo_sys_thread_start on windows + 2022-06-09 840e99ed74 api: promote next to go1.19 + 2022-06-09 1a2ca95ad2 go/types, types2: only set instance context if packages match + 2022-06-08 b51d44c6dd cmd/go/testdata/script: fix skip on list_replace_absolute_windows + 2022-06-08 80f86f706d api/next: minor reformat + 2022-06-08 13f6be2833 runtime: use pidleget for faketime jump + 2022-06-08 1292176bc9 cmd/go: clean paths before using them form index functions + 2022-06-08 1858ea5d85 syscall: remove unused setgroups on linux/loong64 + 2022-06-08 bdde41e3ba runtime: skip TestGdbBacktrace on gdb bug + 2022-06-08 432158b69a net: fix testHookDialTCP race + 2022-06-08 899f0a29c7 cmd/go: enable module index by default + 2022-06-08 f862280e30 cmd/go: properly call PackageModuleRoot to get modroot for index + 2022-06-08 d65166024f cmd/go: set Root and target fields for packages in GOPATH + 2022-06-08 4afb0b9e53 doc/go1.19: delete remaining TODOs + 2022-06-08 3426b7201d runtime: gofmt + 2022-06-08 f330a3a987 doc/go1.19: complete most remaining TODOs + 2022-06-08 2882786bf4 runtime: remove unused pipe and setNonblock on linux/loong64 + 2022-06-08 decdd87bea doc/go1.19: mention riscv64 supported regabi + 2022-06-07 b72a6a7b86 os: document that Chdir affects fs.FS returned by DirFS with a relative path + 2022-06-07 30b929b1ef syscall: remove unused accept on linux/loong64 + 2022-06-07 a7551fe245 net: use synthetic network in TestDialParallel + 2022-06-07 19d71acd97 doc/go1.19: document that the assembler requires -p + 2022-06-07 d151134851 doc/go1.19: document linker CL that switches DWARF compressed section format + 2022-06-07 3507805bcd go/types, types2: better error message for invalid use of constraint type + 2022-06-07 269bf7e855 go/types, types2: better error message if type is not in type set + 2022-06-07 d4fb93be87 go/types, types2: use | rather than ∪ when printing term lists + 2022-06-07 346698eea7 doc/go1.19: add release notes for net/http and net/url + 2022-06-07 7a82c6859f doc/go1.19: adjust runtime release notes + 2022-06-07 f3e051a184 runtime: document GOMEMLIMIT in environment variables section + 2022-06-07 ef2567c7dd doc/go1.19: document loong64 port + 2022-06-07 69bb7c6ef5 sync/atomic: clarify that 8-byte alignment of variables is due to escape + 2022-06-07 81033fbd8e doc/go1.19: some platforms are still on TSAN v2 + 2022-06-07 0c3a0543c2 doc/go1.19: compiler section is complete, modulo TODOs + 2022-06-07 835a946137 doc/go1.19: minor edits + 2022-06-07 429a4041eb doc/go1.19: complete TODOs for go/types + 2022-06-07 d2630aa4b2 doc/go1.19: add various crypto release notes + 2022-06-07 77d9252ddf runtime: fix inline assembly trampoline for arm64 + 2022-06-07 38607c5538 cmd/link: specify -Wl,-z params as documented + 2022-06-07 95b68e1e02 doc/go1.19: delete boringcrypto TODO + 2022-06-07 a79623b019 doc/go1.19: add more TODOs from updated relnote + 2022-06-06 acfff42802 doc/go1.19: add release notes for the soft memory limit and idle GC + 2022-06-06 a71ca3dfbd runtime, sync, sync/atomic: document happens-before guarantees + 2022-06-06 3651a6117e go/doc/comment: add heuristics for common badly formatted comments + 2022-06-06 4c08260c51 doc/go_mem: update revision date + 2022-06-06 7271a0a287 doc/go1.19: gc requires -p=importpath + 2022-06-06 c1e2ecbaf9 doc/go1.19: document Resolver.PreferGo + 2022-06-06 11195c60e6 cmd/go: use index to match packages in dependency modules + 2022-06-06 ea5d7cbc26 all: boringcrypto post-merge cleanup + 2022-06-06 6c7b223c2b go/doc/comment: do not turn ``` into “` + 2022-06-06 ce757e94e0 go/doc/comment: add doc comment + 2022-06-06 95547aee8c cmd/compile: cast riscv64 rewrite shifts to unsigned int + 2022-06-06 d43ddc1f3f strconv: fix typo in atof.go + 2022-06-06 2fa45a4fcd cmd/link/internal/loadpe: handle _main reference properly + 2022-06-06 fc97075949 go/types, types2: simplify implementation of validType (fix TODO) + 2022-06-06 07eca49055 go/types, types2: use type nest to detect type cycles (fix validType) + 2022-06-06 770146d5a8 doc/go1.19: add TODOs for changes to go/types + 2022-06-06 1b8ca75eaa runtime: fix breakpoint in ppc64x + 2022-06-06 9ce28b518d text/template/parse: fix data race on lexer initialization + 2022-06-06 47e34ca533 go/types, types2: ensure that named types never expand infinitely + 2022-06-06 02e69cfa96 go/types, types2: store Named instance information separately + 2022-06-06 1323b0e8f0 go/types, types2: eliminate methodList in favor of just using Named.mu + 2022-06-06 846f971daa go/types, types2: remove Named.once in favor of monotonic state + 2022-06-06 66cbf67345 cmd/buildid: reject rewriting legacy buildids + 2022-06-04 47f806ce81 strconv: clarify ParseFloat accepts Go syntax for float literals + 2022-06-04 2730c6af9f runtime: fix typo in libfuzzer_arm64.s + 2022-06-04 a32a592c8c database/sql/driver: fix typo in driver.go + 2022-06-04 0293c51bc5 regexp: avoid copying each instruction executed + 2022-06-04 865911424d doc: update Go memory model + 2022-06-04 fc66cae490 doc/go1.19: remove TODO about LimitedReader + 2022-06-04 f8a53df314 io: revert: add an Err field to LimitedReader + 2022-06-04 21f05284c7 cmd/go: index standard library packages Change-Id: Ia7595c77a555fd2a0e7bb3b6b2cfbb745bd4947b
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/buildid/buildid.go5
-rw-r--r--src/cmd/cgo/gcc.go39
-rw-r--r--src/cmd/compile/internal/noder/stencil.go4
-rw-r--r--src/cmd/compile/internal/reflectdata/reflect.go33
-rw-r--r--src/cmd/compile/internal/ssa/gen/RISCV64.rules6
-rw-r--r--src/cmd/compile/internal/ssa/rewriteRISCV64.go16
-rw-r--r--src/cmd/compile/internal/types2/api_test.go2
-rw-r--r--src/cmd/compile/internal/types2/call.go6
-rw-r--r--src/cmd/compile/internal/types2/check.go3
-rw-r--r--src/cmd/compile/internal/types2/decl.go16
-rw-r--r--src/cmd/compile/internal/types2/infer.go12
-rw-r--r--src/cmd/compile/internal/types2/instantiate.go90
-rw-r--r--src/cmd/compile/internal/types2/methodlist.go79
-rw-r--r--src/cmd/compile/internal/types2/methodlist_test.go40
-rw-r--r--src/cmd/compile/internal/types2/named.go504
-rw-r--r--src/cmd/compile/internal/types2/named_test.go120
-rw-r--r--src/cmd/compile/internal/types2/object.go14
-rw-r--r--src/cmd/compile/internal/types2/predicates.go13
-rw-r--r--src/cmd/compile/internal/types2/signature.go2
-rw-r--r--src/cmd/compile/internal/types2/sizeof_test.go2
-rw-r--r--src/cmd/compile/internal/types2/subst.go60
-rw-r--r--src/cmd/compile/internal/types2/termlist.go2
-rw-r--r--src/cmd/compile/internal/types2/termlist_test.go140
-rw-r--r--src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go16
-rw-r--r--src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go16
-rw-r--r--src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go19
-rw-r--r--src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go62
-rw-r--r--src/cmd/compile/internal/types2/typeset_test.go6
-rw-r--r--src/cmd/compile/internal/types2/typestring.go4
-rw-r--r--src/cmd/compile/internal/types2/typexpr.go53
-rw-r--r--src/cmd/compile/internal/types2/unify.go4
-rw-r--r--src/cmd/compile/internal/types2/validtype.go261
-rw-r--r--src/cmd/dist/test.go2
-rw-r--r--src/cmd/go/internal/clean/clean.go3
-rw-r--r--src/cmd/go/internal/load/pkg.go7
-rw-r--r--src/cmd/go/internal/modfetch/fetch.go5
-rw-r--r--src/cmd/go/internal/modindex/build.go35
-rw-r--r--src/cmd/go/internal/modindex/read.go226
-rw-r--r--src/cmd/go/internal/modindex/scan.go7
-rw-r--r--src/cmd/go/internal/modload/build.go4
-rw-r--r--src/cmd/go/internal/modload/import.go3
-rw-r--r--src/cmd/go/internal/modload/search.go83
-rw-r--r--src/cmd/go/internal/str/path.go18
-rw-r--r--src/cmd/go/script_test.go2
-rw-r--r--src/cmd/go/testdata/script/embed_brackets.txt18
-rw-r--r--src/cmd/go/testdata/script/list_replace_absolute_windows.txt38
-rw-r--r--src/cmd/internal/metadata/main.go6
-rw-r--r--src/cmd/internal/notsha256/example_test.go41
-rw-r--r--src/cmd/link/internal/ld/data.go6
-rw-r--r--src/cmd/link/internal/ld/decodesym.go17
-rw-r--r--src/cmd/link/internal/ld/dwarf.go6
-rw-r--r--src/cmd/link/internal/ld/lib.go4
-rw-r--r--src/cmd/link/internal/ld/symtab.go4
-rw-r--r--src/cmd/link/internal/loadpe/ldpe.go8
54 files changed, 1467 insertions, 725 deletions
diff --git a/src/cmd/buildid/buildid.go b/src/cmd/buildid/buildid.go
index 8e02a7ae10..72ad80dbbb 100644
--- a/src/cmd/buildid/buildid.go
+++ b/src/cmd/buildid/buildid.go
@@ -53,6 +53,11 @@ func main() {
log.Fatal(err)
}
+ // <= go 1.7 doesn't embed the contentID or actionID, so no slash is present
+ if !strings.Contains(id, "/") {
+ log.Fatalf("%s: build ID is a legacy format...binary too old for this tool", file)
+ }
+
newID := id[:strings.LastIndex(id, "/")] + "/" + buildid.HashToString(hash)
if len(newID) != len(id) {
log.Fatalf("%s: build ID length mismatch %q vs %q", file, id, newID)
diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go
index 4d1a5bd8de..4dff5e2b1c 100644
--- a/src/cmd/cgo/gcc.go
+++ b/src/cmd/cgo/gcc.go
@@ -1831,6 +1831,23 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
bo := f.ByteOrder
symtab, err := f.Symbols()
if err == nil {
+ // Check for use of -fsanitize=hwaddress (issue 53285).
+ removeTag := func(v uint64) uint64 { return v }
+ if goarch == "arm64" {
+ for i := range symtab {
+ if symtab[i].Name == "__hwasan_init" {
+ // -fsanitize=hwaddress on ARM
+ // uses the upper byte of a
+ // memory address as a hardware
+ // tag. Remove it so that
+ // we can find the associated
+ // data.
+ removeTag = func(v uint64) uint64 { return v &^ (0xff << (64 - 8)) }
+ break
+ }
+ }
+ }
+
for i := range symtab {
s := &symtab[i]
switch {
@@ -1838,9 +1855,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
// Found it. Now find data section.
if i := int(s.Section); 0 <= i && i < len(f.Sections) {
sect := f.Sections[i]
- if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
+ val := removeTag(s.Value)
+ if sect.Addr <= val && val < sect.Addr+sect.Size {
if sdat, err := sect.Data(); err == nil {
- data := sdat[s.Value-sect.Addr:]
+ data := sdat[val-sect.Addr:]
ints = make([]int64, len(data)/8)
for i := range ints {
ints[i] = int64(bo.Uint64(data[i*8:]))
@@ -1852,9 +1870,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
// Found it. Now find data section.
if i := int(s.Section); 0 <= i && i < len(f.Sections) {
sect := f.Sections[i]
- if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
+ val := removeTag(s.Value)
+ if sect.Addr <= val && val < sect.Addr+sect.Size {
if sdat, err := sect.Data(); err == nil {
- data := sdat[s.Value-sect.Addr:]
+ data := sdat[val-sect.Addr:]
floats = make([]float64, len(data)/8)
for i := range floats {
floats[i] = math.Float64frombits(bo.Uint64(data[i*8:]))
@@ -1867,9 +1886,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
// Found it. Now find data section.
if i := int(s.Section); 0 <= i && i < len(f.Sections) {
sect := f.Sections[i]
- if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
+ val := removeTag(s.Value)
+ if sect.Addr <= val && val < sect.Addr+sect.Size {
if sdat, err := sect.Data(); err == nil {
- data := sdat[s.Value-sect.Addr:]
+ data := sdat[val-sect.Addr:]
strdata[n] = string(data)
}
}
@@ -1880,9 +1900,10 @@ func (p *Package) gccDebug(stdin []byte, nnames int) (d *dwarf.Data, ints []int6
// Found it. Now find data section.
if i := int(s.Section); 0 <= i && i < len(f.Sections) {
sect := f.Sections[i]
- if sect.Addr <= s.Value && s.Value < sect.Addr+sect.Size {
+ val := removeTag(s.Value)
+ if sect.Addr <= val && val < sect.Addr+sect.Size {
if sdat, err := sect.Data(); err == nil {
- data := sdat[s.Value-sect.Addr:]
+ data := sdat[val-sect.Addr:]
strlen := bo.Uint64(data[:8])
if strlen > (1<<(uint(p.IntSize*8)-1) - 1) { // greater than MaxInt?
fatalf("string literal too big")
@@ -2242,6 +2263,8 @@ var dwarfToName = map[string]string{
"long long unsigned int": "ulonglong",
"signed char": "schar",
"unsigned char": "uchar",
+ "unsigned long": "ulong", // Used by Clang 14; issue 53013.
+ "unsigned long long": "ulonglong", // Used by Clang 14; issue 53013.
}
const signedDelta = 64
diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go
index 88e4961666..3f12aa3cbd 100644
--- a/src/cmd/compile/internal/noder/stencil.go
+++ b/src/cmd/compile/internal/noder/stencil.go
@@ -1330,10 +1330,10 @@ func (g *genInst) dictPass(info *instInfo) {
m = convertUsingDictionary(info, info.dictParam, m.Pos(), mce.X, m, m.Type())
}
case ir.ODOTTYPE, ir.ODOTTYPE2:
- if !m.Type().HasShape() {
+ dt := m.(*ir.TypeAssertExpr)
+ if !dt.Type().HasShape() && !dt.X.Type().HasShape() {
break
}
- dt := m.(*ir.TypeAssertExpr)
var rtype, itab ir.Node
if dt.Type().IsInterface() || dt.X.Type().IsEmptyInterface() {
// TODO(mdempsky): Investigate executing this block unconditionally.
diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go
index 3ffb7dcefa..21301ab149 100644
--- a/src/cmd/compile/internal/reflectdata/reflect.go
+++ b/src/cmd/compile/internal/reflectdata/reflect.go
@@ -412,7 +412,7 @@ func dimportpath(p *types.Pkg) {
}
s := base.Ctxt.Lookup("type..importpath." + p.Prefix + ".")
- ot := dnameData(s, 0, p.Path, "", nil, false)
+ ot := dnameData(s, 0, p.Path, "", nil, false, false)
objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA)
s.Set(obj.AttrContentAddressable, true)
p.Pathsym = s
@@ -461,12 +461,12 @@ func dnameField(lsym *obj.LSym, ot int, spkg *types.Pkg, ft *types.Field) int {
if !types.IsExported(ft.Sym.Name) && ft.Sym.Pkg != spkg {
base.Fatalf("package mismatch for %v", ft.Sym)
}
- nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name))
+ nsym := dname(ft.Sym.Name, ft.Note, nil, types.IsExported(ft.Sym.Name), ft.Embedded != 0)
return objw.SymPtr(lsym, ot, nsym, 0)
}
// dnameData writes the contents of a reflect.name into s at offset ot.
-func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported bool) int {
+func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported, embedded bool) int {
if len(name) >= 1<<29 {
base.Fatalf("name too long: %d %s...", len(name), name[:1024])
}
@@ -491,6 +491,9 @@ func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported b
if pkg != nil {
bits |= 1 << 2
}
+ if embedded {
+ bits |= 1 << 3
+ }
b := make([]byte, l)
b[0] = bits
copy(b[1:], nameLen[:nameLenLen])
@@ -513,7 +516,7 @@ func dnameData(s *obj.LSym, ot int, name, tag string, pkg *types.Pkg, exported b
var dnameCount int
// dname creates a reflect.name for a struct field or method.
-func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym {
+func dname(name, tag string, pkg *types.Pkg, exported, embedded bool) *obj.LSym {
// Write out data as "type.." to signal two things to the
// linker, first that when dynamically linking, the symbol
// should be moved to a relro section, and second that the
@@ -538,11 +541,14 @@ func dname(name, tag string, pkg *types.Pkg, exported bool) *obj.LSym {
sname = fmt.Sprintf(`%s"".%d`, sname, dnameCount)
dnameCount++
}
+ if embedded {
+ sname += ".embedded"
+ }
s := base.Ctxt.Lookup(sname)
if len(s.P) > 0 {
return s
}
- ot := dnameData(s, 0, name, tag, pkg, exported)
+ ot := dnameData(s, 0, name, tag, pkg, exported, embedded)
objw.Global(s, int32(ot), obj.DUPOK|obj.RODATA)
s.Set(obj.AttrContentAddressable, true)
return s
@@ -610,7 +616,7 @@ func dextratypeData(lsym *obj.LSym, ot int, t *types.Type) int {
if !exported && a.name.Pkg != typePkg(t) {
pkg = a.name.Pkg
}
- nsym := dname(a.name.Name, "", pkg, exported)
+ nsym := dname(a.name.Name, "", pkg, exported, false)
ot = objw.SymPtrOff(lsym, ot, nsym)
ot = dmethodptrOff(lsym, ot, writeType(a.mtype))
@@ -775,7 +781,7 @@ func dcommontype(lsym *obj.LSym, t *types.Type) int {
}
ot = objw.SymPtr(lsym, ot, gcsym, 0) // gcdata
- nsym := dname(p, "", nil, exported)
+ nsym := dname(p, "", nil, exported, false)
ot = objw.SymPtrOff(lsym, ot, nsym) // str
// ptrToThis
if sptr == nil {
@@ -1074,7 +1080,7 @@ func writeType(t *types.Type) *obj.LSym {
if !exported && a.name.Pkg != tpkg {
pkg = a.name.Pkg
}
- nsym := dname(a.name.Name, "", pkg, exported)
+ nsym := dname(a.name.Name, "", pkg, exported, false)
ot = objw.SymPtrOff(lsym, ot, nsym)
ot = objw.SymPtrOff(lsym, ot, writeType(a.type_))
@@ -1180,14 +1186,7 @@ func writeType(t *types.Type) *obj.LSym {
// ../../../../runtime/type.go:/structField
ot = dnameField(lsym, ot, spkg, f)
ot = objw.SymPtr(lsym, ot, writeType(f.Type), 0)
- offsetAnon := uint64(f.Offset) << 1
- if offsetAnon>>1 != uint64(f.Offset) {
- base.Fatalf("%v: bad field offset for %s", t, f.Sym.Name)
- }
- if f.Embedded != 0 {
- offsetAnon |= 1
- }
- ot = objw.Uintptr(lsym, ot, offsetAnon)
+ ot = objw.Uintptr(lsym, ot, uint64(f.Offset))
}
}
@@ -1356,7 +1355,7 @@ func WriteTabs() {
// name nameOff
// typ typeOff // pointer to symbol
// }
- nsym := dname(p.Sym().Name, "", nil, true)
+ nsym := dname(p.Sym().Name, "", nil, true, false)
t := p.Type()
if p.Class != ir.PFUNC {
t = types.NewPtr(t)
diff --git a/src/cmd/compile/internal/ssa/gen/RISCV64.rules b/src/cmd/compile/internal/ssa/gen/RISCV64.rules
index 7aea622c5e..dd20be2aeb 100644
--- a/src/cmd/compile/internal/ssa/gen/RISCV64.rules
+++ b/src/cmd/compile/internal/ssa/gen/RISCV64.rules
@@ -735,9 +735,9 @@
(NEGW (MOVDconst [x])) => (MOVDconst [int64(int32(-x))])
// Shift of a constant.
-(SLLI [x] (MOVDconst [y])) && is32Bit(y << x) => (MOVDconst [y << x])
-(SRLI [x] (MOVDconst [y])) => (MOVDconst [int64(uint64(y) >> x)])
-(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> x])
+(SLLI [x] (MOVDconst [y])) && is32Bit(y << uint32(x)) => (MOVDconst [y << uint32(x)])
+(SRLI [x] (MOVDconst [y])) => (MOVDconst [int64(uint64(y) >> uint32(x))])
+(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> uint32(x)])
// SLTI/SLTIU with constants.
(SLTI [x] (MOVDconst [y])) => (MOVDconst [b2i(int64(y) < int64(x))])
diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
index 6828d97ff8..2677e99dc0 100644
--- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go
+++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
@@ -4843,19 +4843,19 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool {
func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
v_0 := v.Args[0]
// match: (SLLI [x] (MOVDconst [y]))
- // cond: is32Bit(y << x)
- // result: (MOVDconst [y << x])
+ // cond: is32Bit(y << uint32(x))
+ // result: (MOVDconst [y << uint32(x)])
for {
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVDconst {
break
}
y := auxIntToInt64(v_0.AuxInt)
- if !(is32Bit(y << x)) {
+ if !(is32Bit(y << uint32(x))) {
break
}
v.reset(OpRISCV64MOVDconst)
- v.AuxInt = int64ToAuxInt(y << x)
+ v.AuxInt = int64ToAuxInt(y << uint32(x))
return true
}
return false
@@ -4913,7 +4913,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
v_0 := v.Args[0]
// match: (SRAI [x] (MOVDconst [y]))
- // result: (MOVDconst [int64(y) >> x])
+ // result: (MOVDconst [int64(y) >> uint32(x)])
for {
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVDconst {
@@ -4921,7 +4921,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
}
y := auxIntToInt64(v_0.AuxInt)
v.reset(OpRISCV64MOVDconst)
- v.AuxInt = int64ToAuxInt(int64(y) >> x)
+ v.AuxInt = int64ToAuxInt(int64(y) >> uint32(x))
return true
}
return false
@@ -4947,7 +4947,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
v_0 := v.Args[0]
// match: (SRLI [x] (MOVDconst [y]))
- // result: (MOVDconst [int64(uint64(y) >> x)])
+ // result: (MOVDconst [int64(uint64(y) >> uint32(x))])
for {
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVDconst {
@@ -4955,7 +4955,7 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
}
y := auxIntToInt64(v_0.AuxInt)
v.reset(OpRISCV64MOVDconst)
- v.AuxInt = int64ToAuxInt(int64(uint64(y) >> x))
+ v.AuxInt = int64ToAuxInt(int64(uint64(y) >> uint32(x)))
return true
}
return false
diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go
index e6de955a6e..f5526bb25a 100644
--- a/src/cmd/compile/internal/types2/api_test.go
+++ b/src/cmd/compile/internal/types2/api_test.go
@@ -38,7 +38,7 @@ func pkgFor(path, source string, info *Info) (*Package, error) {
return conf.Check(f.PkgName.Value, []*syntax.File{f}, info)
}
-func mustTypecheck(t *testing.T, path, source string, info *Info) string {
+func mustTypecheck(t testing.TB, path, source string, info *Info) string {
pkg, err := pkgFor(path, source, info)
if err != nil {
name := path
diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go
index 3ade147dfe..b1ea6917fb 100644
--- a/src/cmd/compile/internal/types2/call.go
+++ b/src/cmd/compile/internal/types2/call.go
@@ -72,13 +72,13 @@ func (check *Checker) instantiateSignature(pos syntax.Pos, typ *Signature, targs
}()
}
- inst := check.instance(pos, typ, targs, check.bestContext(nil)).(*Signature)
+ inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature)
assert(len(xlist) <= len(targs))
// verify instantiation lazily (was issue #50450)
check.later(func() {
tparams := typ.TypeParams().list()
- if i, err := check.verify(pos, tparams, targs); err != nil {
+ if i, err := check.verify(pos, tparams, targs, check.context()); err != nil {
// best position for error reporting
pos := pos
if i < len(xlist) {
@@ -395,7 +395,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
// need to compute it from the adjusted list; otherwise we can
// simply use the result signature's parameter list.
if adjusted {
- sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil).(*Tuple)
+ sigParams = check.subst(call.Pos(), sigParams, makeSubstMap(sig.TypeParams().list(), targs), nil, check.context()).(*Tuple)
} else {
sigParams = rsig.params
}
diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go
index 5cf8454aa4..ff8ae3bc7e 100644
--- a/src/cmd/compile/internal/types2/check.go
+++ b/src/cmd/compile/internal/types2/check.go
@@ -98,7 +98,7 @@ type Checker struct {
nextID uint64 // unique Id for type parameters (first valid Id is 1)
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
- infoMap map[*Named]typeInfo // maps named types to their associated type info (for cycle detection)
+ valids instanceLookup // valid *Named (incl. instantiated) types per the validType check
// pkgPathMap maps package names to the set of distinct import paths we've
// seen for that name, anywhere in the import graph. It is used for
@@ -241,7 +241,6 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
version: version,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
- infoMap: make(map[*Named]typeInfo),
}
}
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index b6f81aa8a5..0bc5f9f3e1 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -508,7 +508,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
}
// type definition or generic type declaration
- named := check.newNamed(obj, nil, nil, nil)
+ named := check.newNamed(obj, nil, nil)
def.setUnderlying(named)
if tdecl.TParamList != nil {
@@ -522,8 +522,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
assert(rhs != nil)
named.fromRHS = rhs
- // If the underlying was not set while type-checking the right-hand side, it
- // is invalid and an error should have been reported elsewhere.
+ // If the underlying type was not set while type-checking the right-hand
+ // side, it is invalid and an error should have been reported elsewhere.
if named.underlying == nil {
named.underlying = Typ[Invalid]
}
@@ -635,7 +635,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
// and field names must be distinct."
base, _ := obj.typ.(*Named) // shouldn't fail but be conservative
if base != nil {
- assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type
+ assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type
// See issue #52529: we must delay the expansion of underlying here, as
// base may not be fully set-up.
@@ -646,8 +646,8 @@ func (check *Checker) collectMethods(obj *TypeName) {
// Checker.Files may be called multiple times; additional package files
// may add methods to already type-checked types. Add pre-existing methods
// so that we can detect redeclarations.
- for i := 0; i < base.methods.Len(); i++ {
- m := base.methods.At(i, nil)
+ for i := 0; i < base.NumMethods(); i++ {
+ m := base.Method(i)
assert(m.name != "_")
assert(mset.insert(m) == nil)
}
@@ -679,8 +679,8 @@ func (check *Checker) collectMethods(obj *TypeName) {
func (check *Checker) checkFieldUniqueness(base *Named) {
if t, _ := base.under().(*Struct); t != nil {
var mset objset
- for i := 0; i < base.methods.Len(); i++ {
- m := base.methods.At(i, nil)
+ for i := 0; i < base.NumMethods(); i++ {
+ m := base.Method(i)
assert(m.name != "_")
assert(mset.insert(m) == nil)
}
diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go
index 9e77d67a7d..b0c6a4fcea 100644
--- a/src/cmd/compile/internal/types2/infer.go
+++ b/src/cmd/compile/internal/types2/infer.go
@@ -110,11 +110,11 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
renameMap := makeRenameMap(tparams, tparams2)
for i, tparam := range tparams {
- tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil)
+ tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil, check.context())
}
tparams = tparams2
- params = check.subst(pos, params, renameMap, nil).(*Tuple)
+ params = check.subst(pos, params, renameMap, nil, check.context()).(*Tuple)
}
}
@@ -188,7 +188,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// but that doesn't impact the isParameterized check for now).
if params.Len() > 0 {
smap := makeSubstMap(tparams, targs)
- params = check.subst(nopos, params, smap, nil).(*Tuple)
+ params = check.subst(nopos, params, smap, nil, check.context()).(*Tuple)
}
// Unify parameter and argument types for generic parameters with typed arguments
@@ -224,7 +224,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
}
}
smap := makeSubstMap(tparams, targs)
- inferred := check.subst(arg.Pos(), tpar, smap, nil)
+ inferred := check.subst(arg.Pos(), tpar, smap, nil, check.context())
if inferred != tpar {
check.errorf(arg, "%s %s of %s does not match inferred type %s for %s", kind, targ, arg.expr, inferred, tpar)
} else {
@@ -434,7 +434,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
return w.isParameterized(t.elem)
case *Named:
- return w.isParameterizedTypeList(t.targs.list())
+ return w.isParameterizedTypeList(t.TypeArgs().list())
case *TypeParam:
// t must be one of w.tparams
@@ -626,7 +626,7 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type)
n := 0
for _, index := range dirty {
t0 := types[index]
- if t1 := check.subst(nopos, t0, smap, nil); t1 != t0 {
+ if t1 := check.subst(nopos, t0, smap, nil, check.context()); t1 != t0 {
types[index] = t1
dirty[n] = index
n++
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go
index bb90ab3736..5833f8db7e 100644
--- a/src/cmd/compile/internal/types2/instantiate.go
+++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -40,6 +40,9 @@ import (
// count is incorrect; for *Named types, a panic may occur later inside the
// *Named API.
func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) {
+ if ctxt == nil {
+ ctxt = NewContext()
+ }
if validate {
var tparams []*TypeParam
switch t := orig.(type) {
@@ -51,40 +54,72 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
if len(targs) != len(tparams) {
return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams))
}
- if i, err := (*Checker)(nil).verify(nopos, tparams, targs); err != nil {
+ if i, err := (*Checker)(nil).verify(nopos, tparams, targs, ctxt); err != nil {
return nil, &ArgumentError{i, err}
}
}
- inst := (*Checker)(nil).instance(nopos, orig, targs, ctxt)
+ inst := (*Checker)(nil).instance(nopos, orig, targs, nil, ctxt)
return inst, nil
}
-// instance creates a type or function instance using the given original type
-// typ and arguments targs. For Named types the resulting instance will be
-// unexpanded. check may be nil.
-func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Context) (res Type) {
- var h string
+// instance instantiates the given original (generic) function or type with the
+// provided type arguments and returns the resulting instance. If an identical
+// instance exists already in the given contexts, it returns that instance,
+// otherwise it creates a new one.
+//
+// If expanding is non-nil, it is the Named instance type currently being
+// expanded. If ctxt is non-nil, it is the context associated with the current
+// type-checking pass or call to Instantiate. At least one of expanding or ctxt
+// must be non-nil.
+//
+// For Named types the resulting instance may be unexpanded.
+func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, expanding *Named, ctxt *Context) (res Type) {
+ // The order of the contexts below matters: we always prefer instances in the
+ // expanding instance context in order to preserve reference cycles.
+ //
+ // Invariant: if expanding != nil, the returned instance will be the instance
+ // recorded in expanding.inst.ctxt.
+ var ctxts []*Context
+ if expanding != nil {
+ ctxts = append(ctxts, expanding.inst.ctxt)
+ }
if ctxt != nil {
- h = ctxt.instanceHash(orig, targs)
- // typ may already have been instantiated with identical type arguments. In
- // that case, re-use the existing instance.
- if inst := ctxt.lookup(h, orig, targs); inst != nil {
- return inst
+ ctxts = append(ctxts, ctxt)
+ }
+ assert(len(ctxts) > 0)
+
+ // Compute all hashes; hashes may differ across contexts due to different
+ // unique IDs for Named types within the hasher.
+ hashes := make([]string, len(ctxts))
+ for i, ctxt := range ctxts {
+ hashes[i] = ctxt.instanceHash(orig, targs)
+ }
+
+ // If local is non-nil, updateContexts return the type recorded in
+ // local.
+ updateContexts := func(res Type) Type {
+ for i := len(ctxts) - 1; i >= 0; i-- {
+ res = ctxts[i].update(hashes[i], orig, targs, res)
+ }
+ return res
+ }
+
+ // typ may already have been instantiated with identical type arguments. In
+ // that case, re-use the existing instance.
+ for i, ctxt := range ctxts {
+ if inst := ctxt.lookup(hashes[i], orig, targs); inst != nil {
+ return updateContexts(inst)
}
}
switch orig := orig.(type) {
case *Named:
- tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil)
- named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved
- named.targs = newTypeList(targs)
- named.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) {
- return expandNamed(ctxt, n, pos)
- }
- res = named
+ res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily
case *Signature:
+ assert(expanding == nil) // function instances cannot be reached from Named types
+
tparams := orig.TypeParams()
if !check.validateTArgLen(pos, tparams.Len(), len(targs)) {
return Typ[Invalid]
@@ -92,7 +127,7 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co
if tparams.Len() == 0 {
return orig // nothing to do (minor optimization)
}
- sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), ctxt).(*Signature)
+ sig := check.subst(pos, orig, makeSubstMap(tparams.list(), targs), nil, ctxt).(*Signature)
// If the signature doesn't use its type parameters, subst
// will not make a copy. In that case, make a copy now (so
// we can set tparams to nil w/o causing side-effects).
@@ -110,13 +145,8 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, orig))
}
- if ctxt != nil {
- // It's possible that we've lost a race to add named to the context.
- // In this case, use whichever instance is recorded in the context.
- res = ctxt.update(h, orig, targs, res)
- }
-
- return res
+ // Update all contexts; it's possible that we've lost a race.
+ return updateContexts(res)
}
// validateTArgLen verifies that the length of targs and tparams matches,
@@ -134,7 +164,7 @@ func (check *Checker) validateTArgLen(pos syntax.Pos, ntparams, ntargs int) bool
return true
}
-func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type) (int, error) {
+func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) {
smap := makeSubstMap(tparams, targs)
for i, tpar := range tparams {
// Ensure that we have a (possibly implicit) interface as type bound (issue #51048).
@@ -143,7 +173,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type)
// as the instantiated type; before we can use it for bounds checking we
// need to instantiate it with the type arguments with which we instantiated
// the parameterized type.
- bound := check.subst(pos, tpar.bound, smap, nil)
+ bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
if err := check.implements(targs[i], bound); err != nil {
return i, err
}
@@ -248,7 +278,7 @@ func (check *Checker) implements(V, T Type) error {
if alt != nil {
return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
} else {
- return errorf("%s does not implement %s", V, T)
+ return errorf("%s does not implement %s (%s missing in %s)", V, T, V, Ti.typeSet().terms)
}
}
diff --git a/src/cmd/compile/internal/types2/methodlist.go b/src/cmd/compile/internal/types2/methodlist.go
deleted file mode 100644
index cd6c06c5fb..0000000000
--- a/src/cmd/compile/internal/types2/methodlist.go
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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 types2
-
-import "sync"
-
-// methodList holds a list of methods that may be lazily resolved by a provided
-// resolution method.
-type methodList struct {
- methods []*Func
-
- // guards synchronizes the instantiation of lazy methods. For lazy method
- // lists, guards is non-nil and of the length passed to newLazyMethodList.
- // For non-lazy method lists, guards is nil.
- guards *[]sync.Once
-}
-
-// newMethodList creates a non-lazy method list holding the given methods.
-func newMethodList(methods []*Func) *methodList {
- return &methodList{methods: methods}
-}
-
-// newLazyMethodList creates a lazy method list of the given length. Methods
-// may be resolved lazily for a given index by providing a resolver function.
-func newLazyMethodList(length int) *methodList {
- guards := make([]sync.Once, length)
- return &methodList{
- methods: make([]*Func, length),
- guards: &guards,
- }
-}
-
-// isLazy reports whether the receiver is a lazy method list.
-func (l *methodList) isLazy() bool {
- return l != nil && l.guards != nil
-}
-
-// Add appends a method to the method list if not not already present. Add
-// panics if the receiver is lazy.
-func (l *methodList) Add(m *Func) {
- assert(!l.isLazy())
- if i, _ := lookupMethod(l.methods, m.pkg, m.name, false); i < 0 {
- l.methods = append(l.methods, m)
- }
-}
-
-// Lookup looks up the method identified by pkg and name in the receiver.
-// Lookup panics if the receiver is lazy. If foldCase is true, method names
-// are considered equal if they are equal with case folding.
-func (l *methodList) Lookup(pkg *Package, name string, foldCase bool) (int, *Func) {
- assert(!l.isLazy())
- if l == nil {
- return -1, nil
- }
- return lookupMethod(l.methods, pkg, name, foldCase)
-}
-
-// Len returns the length of the method list.
-func (l *methodList) Len() int {
- if l == nil {
- return 0
- }
- return len(l.methods)
-}
-
-// At returns the i'th method of the method list. At panics if i is out of
-// bounds, or if the receiver is lazy and resolve is nil.
-func (l *methodList) At(i int, resolve func() *Func) *Func {
- if !l.isLazy() {
- return l.methods[i]
- }
- assert(resolve != nil)
- (*l.guards)[i].Do(func() {
- l.methods[i] = resolve()
- })
- return l.methods[i]
-}
diff --git a/src/cmd/compile/internal/types2/methodlist_test.go b/src/cmd/compile/internal/types2/methodlist_test.go
deleted file mode 100644
index 7a183ac7f9..0000000000
--- a/src/cmd/compile/internal/types2/methodlist_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 types2
-
-import (
- "testing"
-)
-
-func TestLazyMethodList(t *testing.T) {
- l := newLazyMethodList(2)
-
- if got := l.Len(); got != 2 {
- t.Fatalf("Len() = %d, want 2", got)
- }
-
- f0 := NewFunc(nopos, nil, "f0", nil)
- f1 := NewFunc(nopos, nil, "f1", nil)
-
- // Verify that methodList.At is idempotent, by calling it repeatedly with a
- // resolve func that returns different pointer values (f0 or f1).
- steps := []struct {
- index int
- resolve *Func // the *Func returned by the resolver
- want *Func // the actual *Func returned by methodList.At
- }{
- {0, f0, f0},
- {0, f1, f0},
- {1, f1, f1},
- {1, f0, f1},
- }
-
- for i, step := range steps {
- got := l.At(step.index, func() *Func { return step.resolve })
- if got != step.want {
- t.Errorf("step %d: At(%d, ...) = %s, want %s", i, step.index, got.Name(), step.want.Name())
- }
- }
-}
diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go
index 849398a6f4..2cf6d3871f 100644
--- a/src/cmd/compile/internal/types2/named.go
+++ b/src/cmd/compile/internal/types2/named.go
@@ -7,30 +7,136 @@ package types2
import (
"cmd/compile/internal/syntax"
"sync"
+ "sync/atomic"
)
+// Type-checking Named types is subtle, because they may be recursively
+// defined, and because their full details may be spread across multiple
+// declarations (via methods). For this reason they are type-checked lazily,
+// to avoid information being accessed before it is complete.
+//
+// Conceptually, it is helpful to think of named types as having two distinct
+// sets of information:
+// - "LHS" information, defining their identity: Obj() and TypeArgs()
+// - "RHS" information, defining their details: TypeParams(), Underlying(),
+// and methods.
+//
+// In this taxonomy, LHS information is available immediately, but RHS
+// information is lazy. Specifically, a named type N may be constructed in any
+// of the following ways:
+// 1. type-checked from the source
+// 2. loaded eagerly from export data
+// 3. loaded lazily from export data (when using unified IR)
+// 4. instantiated from a generic type
+//
+// In cases 1, 3, and 4, it is possible that the underlying type or methods of
+// N may not be immediately available.
+// - During type-checking, we allocate N before type-checking its underlying
+// type or methods, so that we may resolve recursive references.
+// - When loading from export data, we may load its methods and underlying
+// type lazily using a provided load function.
+// - After instantiating, we lazily expand the underlying type and methods
+// (note that instances may be created while still in the process of
+// type-checking the original type declaration).
+//
+// In cases 3 and 4 this lazy construction may also occur concurrently, due to
+// concurrent use of the type checker API (after type checking or importing has
+// finished). It is critical that we keep track of state, so that Named types
+// are constructed exactly once and so that we do not access their details too
+// soon.
+//
+// We achieve this by tracking state with an atomic state variable, and
+// guarding potentially concurrent calculations with a mutex. At any point in
+// time this state variable determines which data on N may be accessed. As
+// state monotonically progresses, any data available at state M may be
+// accessed without acquiring the mutex at state N, provided N >= M.
+//
+// GLOSSARY: Here are a few terms used in this file to describe Named types:
+// - We say that a Named type is "instantiated" if it has been constructed by
+// instantiating a generic named type with type arguments.
+// - We say that a Named type is "declared" if it corresponds to a type
+// declaration in the source. Instantiated named types correspond to a type
+// instantiation in the source, not a declaration. But their Origin type is
+// a declared type.
+// - We say that a Named type is "resolved" if its RHS information has been
+// loaded or fully type-checked. For Named types constructed from export
+// data, this may involve invoking a loader function to extract information
+// from export data. For instantiated named types this involves reading
+// information from their origin.
+// - We say that a Named type is "expanded" if it is an instantiated type and
+// type parameters in its underlying type and methods have been substituted
+// with the type arguments from the instantiation. A type may be partially
+// expanded if some but not all of these details have been substituted.
+// Similarly, we refer to these individual details (underlying type or
+// method) as being "expanded".
+// - When all information is known for a named type, we say it is "complete".
+//
+// Some invariants to keep in mind: each declared Named type has a single
+// corresponding object, and that object's type is the (possibly generic) Named
+// type. Declared Named types are identical if and only if their pointers are
+// identical. On the other hand, multiple instantiated Named types may be
+// identical even though their pointers are not identical. One has to use
+// Identical to compare them. For instantiated named types, their obj is a
+// synthetic placeholder that records their position of the corresponding
+// instantiation in the source (if they were constructed during type checking).
+//
+// To prevent infinite expansion of named instances that are created outside of
+// type-checking, instances share a Context with other instances created during
+// their expansion. Via the pidgeonhole principle, this guarantees that in the
+// presence of a cycle of named types, expansion will eventually find an
+// existing instance in the Context and short-circuit the expansion.
+//
+// Once an instance is complete, we can nil out this shared Context to unpin
+// memory, though this Context may still be held by other incomplete instances
+// in its "lineage".
+
// A Named represents a named (defined) type.
type Named struct {
- check *Checker
- obj *TypeName // corresponding declared object for declared types; placeholder for instantiated types
- orig *Named // original, uninstantiated type
- fromRHS Type // type (on RHS of declaration) this *Named type is derived from (for cycle reporting)
+ check *Checker // non-nil during type-checking; nil otherwise
+ obj *TypeName // corresponding declared object for declared types; see above for instantiated types
+
+ // fromRHS holds the type (on RHS of declaration) this *Named type is derived
+ // from (for cycle reporting). Only used by validType, and therefore does not
+ // require synchronization.
+ fromRHS Type
+
+ // information for instantiated types; nil otherwise
+ inst *instance
+
+ mu sync.Mutex // guards all fields below
+ state_ uint32 // the current state of this type; must only be accessed atomically
underlying Type // possibly a *Named during setup; never a *Named once set up completely
tparams *TypeParamList // type parameters, or nil
- targs *TypeList // type arguments (after instantiation), or nil
- // methods declared for this type (not the method set of this type).
+ // methods declared for this type (not the method set of this type)
// Signatures are type-checked lazily.
// For non-instantiated types, this is a fully populated list of methods. For
- // instantiated types, this is a 'lazy' list, and methods are instantiated
- // when they are first accessed.
- methods *methodList
+ // instantiated types, methods are individually expanded when they are first
+ // accessed.
+ methods []*Func
- // resolver may be provided to lazily resolve type parameters, underlying, and methods.
- resolver func(*Context, *Named) (tparams *TypeParamList, underlying Type, methods *methodList)
- once sync.Once // ensures that tparams, underlying, and methods are resolved before accessing
+ // loader may be provided to lazily load type parameters, underlying type, and methods.
+ loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func)
}
+// instance holds information that is only necessary for instantiated named
+// types.
+type instance struct {
+ orig *Named // original, uninstantiated type
+ targs *TypeList // type arguments
+ expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods)
+ ctxt *Context // local Context; set to nil after full expansion
+}
+
+// namedState represents the possible states that a named type may assume.
+type namedState uint32
+
+const (
+ unresolved namedState = iota // tparams, underlying type and methods might be unavailable
+ resolved // resolve has run; methods might be incomplete (for instances)
+ complete // all data is known
+)
+
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
// If the given type name obj doesn't have a type yet, its type is set to the returned named type.
// The underlying type must not be a *Named.
@@ -38,39 +144,122 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
if _, ok := underlying.(*Named); ok {
panic("underlying type must not be *Named")
}
- return (*Checker)(nil).newNamed(obj, nil, underlying, newMethodList(methods))
+ return (*Checker)(nil).newNamed(obj, underlying, methods)
}
-func (t *Named) resolve(ctxt *Context) *Named {
- if t.resolver == nil {
- return t
+// resolve resolves the type parameters, methods, and underlying type of n.
+// This information may be loaded from a provided loader function, or computed
+// from an origin type (in the case of instances).
+//
+// After resolution, the type parameters, methods, and underlying type of n are
+// accessible; but if n is an instantiated type, its methods may still be
+// unexpanded.
+func (n *Named) resolve() *Named {
+ if n.state() >= resolved { // avoid locking below
+ return n
+ }
+
+ // TODO(rfindley): if n.check is non-nil we can avoid locking here, since
+ // type-checking is not concurrent. Evaluate if this is worth doing.
+ n.mu.Lock()
+ defer n.mu.Unlock()
+
+ if n.state() >= resolved {
+ return n
}
- t.once.Do(func() {
- // TODO(mdempsky): Since we're passing t to the resolver anyway
- // (necessary because types2 expects the receiver type for methods
- // on defined interface types to be the Named rather than the
- // underlying Interface), maybe it should just handle calling
- // SetTypeParams, SetUnderlying, and AddMethod instead? Those
- // methods would need to support reentrant calls though. It would
- // also make the API more future-proof towards further extensions
- // (like SetTypeParams).
- t.tparams, t.underlying, t.methods = t.resolver(ctxt, t)
- t.fromRHS = t.underlying // for cycle detection
- })
- return t
+ if n.inst != nil {
+ assert(n.underlying == nil) // n is an unresolved instance
+ assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil
+
+ orig := n.inst.orig
+ orig.resolve()
+ underlying := n.expandUnderlying()
+
+ n.tparams = orig.tparams
+ n.underlying = underlying
+ n.fromRHS = orig.fromRHS // for cycle detection
+
+ if len(orig.methods) == 0 {
+ n.setState(complete) // nothing further to do
+ n.inst.ctxt = nil
+ } else {
+ n.setState(resolved)
+ }
+ return n
+ }
+
+ // TODO(mdempsky): Since we're passing n to the loader anyway
+ // (necessary because types2 expects the receiver type for methods
+ // on defined interface types to be the Named rather than the
+ // underlying Interface), maybe it should just handle calling
+ // SetTypeParams, SetUnderlying, and AddMethod instead? Those
+ // methods would need to support reentrant calls though. It would
+ // also make the API more future-proof towards further extensions.
+ if n.loader != nil {
+ assert(n.underlying == nil)
+ assert(n.TypeArgs().Len() == 0) // instances are created by instantiation, in which case n.loader is nil
+
+ tparams, underlying, methods := n.loader(n)
+
+ n.tparams = bindTParams(tparams)
+ n.underlying = underlying
+ n.fromRHS = underlying // for cycle detection
+ n.methods = methods
+ n.loader = nil
+ }
+
+ n.setState(complete)
+ return n
+}
+
+// state atomically accesses the current state of the receiver.
+func (n *Named) state() namedState {
+ return namedState(atomic.LoadUint32(&n.state_))
+}
+
+// setState atomically stores the given state for n.
+// Must only be called while holding n.mu.
+func (n *Named) setState(state namedState) {
+ atomic.StoreUint32(&n.state_, uint32(state))
}
// newNamed is like NewNamed but with a *Checker receiver and additional orig argument.
-func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods *methodList) *Named {
- typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods}
- if typ.orig == nil {
- typ.orig = typ
- }
+func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
+ typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods}
if obj.typ == nil {
obj.typ = typ
}
- // Ensure that typ is always expanded and sanity-checked.
+ // Ensure that typ is always sanity-checked.
+ if check != nil {
+ check.needsCleanup(typ)
+ }
+ return typ
+}
+
+// newNamedInstance creates a new named instance for the given origin and type
+// arguments, recording pos as the position of its synthetic object (for error
+// reporting).
+//
+// If set, expanding is the named type instance currently being expanded, that
+// led to the creation of this instance.
+func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type, expanding *Named) *Named {
+ assert(len(targs) > 0)
+
+ obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil)
+ inst := &instance{orig: orig, targs: newTypeList(targs)}
+
+ // Only pass the expanding context to the new instance if their packages
+ // match. Since type reference cycles are only possible within a single
+ // package, this is sufficient for the purposes of short-circuiting cycles.
+ // Avoiding passing the context in other cases prevents unnecessary coupling
+ // of types across packages.
+ if expanding != nil && expanding.Obj().pkg == obj.pkg {
+ inst.ctxt = expanding.inst.ctxt
+ }
+ typ := &Named{check: check, obj: obj, inst: inst}
+ obj.typ = typ
+ // Ensure that typ is always sanity-checked.
if check != nil {
check.needsCleanup(typ)
}
@@ -78,18 +267,18 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth
}
func (t *Named) cleanup() {
- assert(t.orig.orig == t.orig)
+ assert(t.inst == nil || t.inst.orig.inst == nil)
// Ensure that every defined type created in the course of type-checking has
- // either non-*Named underlying, or is unresolved.
+ // either non-*Named underlying type, or is unexpanded.
//
- // 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.
+ // This guarantees that we don't leak any types whose underlying type is
+ // *Named, because any unexpanded instances will lazily compute their
+ // underlying type by substituting in the underlying type of their origin.
+ // The origin must have either been imported or type-checked and expanded
+ // here, and in either case its underlying type will be fully expanded.
switch t.underlying.(type) {
case nil:
- if t.resolver == nil {
+ if t.TypeArgs().Len() == 0 {
panic("nil underlying")
}
case *Named:
@@ -100,51 +289,91 @@ func (t *Named) cleanup() {
// Obj returns the type name for the declaration defining the named type t. For
// 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
+func (t *Named) Obj() *TypeName {
+ if t.inst == nil {
+ return t.obj
+ }
+ return t.inst.orig.obj
+}
// 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 }
-
-// TODO(gri) Come up with a better representation and API to distinguish
-// between parameterized instantiated and non-instantiated types.
+func (t *Named) Origin() *Named {
+ if t.inst == nil {
+ return t
+ }
+ return t.inst.orig
+}
// TypeParams returns the type parameters of the named type t, or nil.
// 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 }
+func (t *Named) TypeParams() *TypeParamList { return t.resolve().tparams }
// SetTypeParams sets the type parameters of the named type t.
// t must not have type arguments.
func (t *Named) SetTypeParams(tparams []*TypeParam) {
- assert(t.targs.Len() == 0)
- t.resolve(nil).tparams = bindTParams(tparams)
+ assert(t.inst == nil)
+ t.resolve().tparams = bindTParams(tparams)
}
// TypeArgs returns the type arguments used to instantiate the named type t.
-func (t *Named) TypeArgs() *TypeList { return t.targs }
+func (t *Named) TypeArgs() *TypeList {
+ if t.inst == nil {
+ return nil
+ }
+ return t.inst.targs
+}
// 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() }
+func (t *Named) NumMethods() int {
+ return len(t.Origin().resolve().methods)
+}
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
+//
+// For an ordinary or instantiated type t, the receiver base type of this
+// method is the named type t. For an uninstantiated generic type t, each
+// method receiver is instantiated with its receiver type parameters.
func (t *Named) Method(i int) *Func {
- t.resolve(nil)
- return t.methods.At(i, func() *Func {
- return t.instantiateMethod(i)
- })
-}
+ t.resolve()
+
+ if t.state() >= complete {
+ return t.methods[i]
+ }
+
+ assert(t.inst != nil) // only instances should have incomplete methods
+ orig := t.inst.orig
-// instantiateMethod instantiates the i'th method for an instantiated receiver.
-func (t *Named) instantiateMethod(i int) *Func {
- assert(t.TypeArgs().Len() > 0) // t must be an instance
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ if len(t.methods) != len(orig.methods) {
+ assert(len(t.methods) == 0)
+ t.methods = make([]*Func, len(orig.methods))
+ }
+
+ if t.methods[i] == nil {
+ assert(t.inst.ctxt != nil) // we should still have a context remaining from the resolution phase
+ t.methods[i] = t.expandMethod(i)
+ t.inst.expandedMethods++
+
+ // Check if we've created all methods at this point. If we have, mark the
+ // type as fully expanded.
+ if t.inst.expandedMethods == len(orig.methods) {
+ t.setState(complete)
+ t.inst.ctxt = nil // no need for a context anymore
+ }
+ }
+
+ return t.methods[i]
+}
+
+// expandMethod substitutes type arguments in the i'th method for an
+// instantiated receiver.
+func (t *Named) expandMethod(i int) *Func {
// t.orig.methods is not lazy. origm is the method instantiated with its
// receiver type parameters (the "origin" method).
- origm := t.orig.Method(i)
+ origm := t.inst.orig.Method(i)
assert(origm != nil)
check := t.check
@@ -171,10 +400,13 @@ func (t *Named) instantiateMethod(i int) *Func {
// We can only substitute if we have a correspondence between type arguments
// and type parameters. This check is necessary in the presence of invalid
// code.
- if origSig.RecvTypeParams().Len() == t.targs.Len() {
- ctxt := check.bestContext(nil)
- smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list())
- sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature)
+ if origSig.RecvTypeParams().Len() == t.inst.targs.Len() {
+ smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list())
+ var ctxt *Context
+ if check != nil {
+ ctxt = check.context()
+ }
+ sig = check.subst(origm.pos, origSig, smap, t, ctxt).(*Signature)
}
if sig == origSig {
@@ -198,14 +430,14 @@ func (t *Named) instantiateMethod(i int) *Func {
// SetUnderlying sets the underlying type and marks t as complete.
// t must not have type arguments.
func (t *Named) SetUnderlying(underlying Type) {
- assert(t.targs.Len() == 0)
+ assert(t.inst == nil)
if underlying == nil {
panic("underlying type must not be nil")
}
if _, ok := underlying.(*Named); ok {
panic("underlying type must not be *Named")
}
- t.resolve(nil).underlying = underlying
+ t.resolve().underlying = underlying
if t.fromRHS == nil {
t.fromRHS = underlying // for cycle detection
}
@@ -214,19 +446,21 @@ func (t *Named) SetUnderlying(underlying Type) {
// AddMethod adds method m unless it is already in the method list.
// t must not have type arguments.
func (t *Named) AddMethod(m *Func) {
- assert(t.targs.Len() == 0)
- t.resolve(nil)
- if t.methods == nil {
- t.methods = newMethodList(nil)
+ assert(t.inst == nil)
+ t.resolve()
+ if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 {
+ t.methods = append(t.methods, m)
}
- t.methods.Add(m)
}
-func (t *Named) Underlying() Type { return t.resolve(nil).underlying }
+func (t *Named) Underlying() Type { return t.resolve().underlying }
func (t *Named) String() string { return TypeString(t, nil) }
// ----------------------------------------------------------------------------
// Implementation
+//
+// TODO(rfindley): reorganize the loading and expansion methods under this
+// heading.
// under returns the expanded underlying type of n0; possibly by following
// forward chains of named types. If an underlying type is found, resolve
@@ -275,7 +509,7 @@ func (n0 *Named) under() Type {
check := n0.check
n := n0
- seen := make(map[*Named]int) // types that need their underlying resolved
+ seen := make(map[*Named]int) // types that need their underlying type resolved
var path []Object // objects encountered, for cycle reporting
loop:
@@ -323,11 +557,11 @@ func (n *Named) setUnderlying(typ Type) {
}
func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Func) {
- n.resolve(nil)
+ n.resolve()
// If n is an instance, we may not have yet instantiated all of its methods.
// Look up the method index in orig, and only instantiate method at the
// matching index (if any).
- i, _ := n.orig.methods.Lookup(pkg, name, foldCase)
+ i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase)
if i < 0 {
return -1, nil
}
@@ -335,83 +569,83 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu
return i, n.Method(i)
}
-// bestContext returns the best available context. In order of preference:
-// - the given ctxt, if non-nil
-// - check.ctxt, if check is non-nil
-// - a new Context
-func (check *Checker) bestContext(ctxt *Context) *Context {
- if ctxt != nil {
- return ctxt
+// context returns the type-checker context.
+func (check *Checker) context() *Context {
+ if check.ctxt == nil {
+ check.ctxt = NewContext()
}
- if check != nil {
- if check.ctxt == nil {
- check.ctxt = NewContext()
- }
- return check.ctxt
- }
- return NewContext()
+ return check.ctxt
}
-// expandNamed ensures that the underlying type of n is instantiated.
-// The underlying type will be Typ[Invalid] if there was an error.
-func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypeParamList, underlying Type, methods *methodList) {
+// expandUnderlying substitutes type arguments in the underlying type n.orig,
+// returning the result. Returns Typ[Invalid] if there was an error.
+func (n *Named) expandUnderlying() Type {
check := n.check
if check != nil && check.conf.Trace {
- check.trace(instPos, "-- expandNamed %s", n)
+ check.trace(n.obj.pos, "-- Named.expandUnderlying %s", n)
check.indent++
defer func() {
check.indent--
- check.trace(instPos, "=> %s (tparams = %s, under = %s)", n, tparams.list(), underlying)
+ check.trace(n.obj.pos, "=> %s (tparams = %s, under = %s)", n, n.tparams.list(), n.underlying)
}()
}
- n.orig.resolve(ctxt)
- assert(n.orig.underlying != nil)
+ assert(n.inst.orig.underlying != nil)
+ if n.inst.ctxt == nil {
+ n.inst.ctxt = NewContext()
+ }
+
+ orig := n.inst.orig
+ targs := n.inst.targs
- if _, unexpanded := n.orig.underlying.(*Named); unexpanded {
- // We should only get an unexpanded underlying here during type checking
+ if _, unexpanded := orig.underlying.(*Named); unexpanded {
+ // We should only get a Named underlying type here during type checking
// (for example, in recursive type declarations).
assert(check != nil)
}
- // Mismatching arg and tparam length may be checked elsewhere.
- if n.orig.tparams.Len() == n.targs.Len() {
- // We must always have a context, to avoid infinite recursion.
- ctxt = check.bestContext(ctxt)
- h := ctxt.instanceHash(n.orig, n.targs.list())
- // ensure that an instance is recorded for h to avoid infinite recursion.
- ctxt.update(h, n.orig, n.TypeArgs().list(), n)
+ if orig.tparams.Len() != targs.Len() {
+ // Mismatching arg and tparam length may be checked elsewhere.
+ return Typ[Invalid]
+ }
+
+ // Ensure that an instance is recorded before substituting, so that we
+ // resolve n for any recursive references.
+ h := n.inst.ctxt.instanceHash(orig, targs.list())
+ n2 := n.inst.ctxt.update(h, orig, n.TypeArgs().list(), n)
+ assert(n == n2)
- smap := makeSubstMap(n.orig.tparams.list(), n.targs.list())
- underlying = n.check.subst(instPos, n.orig.underlying, smap, ctxt)
- // If the underlying of n is an interface, we need to set the receiver of
- // its methods accurately -- we set the receiver of interface methods on
- // the RHS of a type declaration to the defined type.
- if iface, _ := underlying.(*Interface); iface != nil {
- if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied {
- // If the underlying doesn't actually use type parameters, it's possible
- // that it wasn't substituted. In this case we need to create a new
- // *Interface before modifying receivers.
- if iface == n.orig.underlying {
- 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
+ smap := makeSubstMap(orig.tparams.list(), targs.list())
+ var ctxt *Context
+ if check != nil {
+ ctxt = check.context()
+ }
+ underlying := n.check.subst(n.obj.pos, orig.underlying, smap, n, ctxt)
+ // If the underlying type of n is an interface, we need to set the receiver of
+ // its methods accurately -- we set the receiver of interface methods on
+ // the RHS of a type declaration to the defined type.
+ if iface, _ := underlying.(*Interface); iface != nil {
+ if methods, copied := replaceRecvType(iface.methods, orig, n); copied {
+ // If the underlying type doesn't actually use type parameters, it's
+ // possible that it wasn't substituted. In this case we need to create
+ // a new *Interface before modifying receivers.
+ if iface == orig.underlying {
+ 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
}
- } else {
- underlying = Typ[Invalid]
}
- return n.orig.tparams, underlying, newLazyMethodList(n.orig.methods.Len())
+ return underlying
}
-// safeUnderlying returns the underlying of typ without expanding instances, to
-// avoid infinite recursion.
+// safeUnderlying returns the underlying type of typ without expanding
+// instances, to avoid infinite recursion.
//
// TODO(rfindley): eliminate this function or give it a better name.
func safeUnderlying(typ Type) Type {
diff --git a/src/cmd/compile/internal/types2/named_test.go b/src/cmd/compile/internal/types2/named_test.go
new file mode 100644
index 0000000000..e5e8eddb05
--- /dev/null
+++ b/src/cmd/compile/internal/types2/named_test.go
@@ -0,0 +1,120 @@
+// 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 types2_test
+
+import (
+ "testing"
+
+ "cmd/compile/internal/syntax"
+ . "cmd/compile/internal/types2"
+)
+
+func BenchmarkNamed(b *testing.B) {
+ const src = `
+package p
+
+type T struct {
+ P int
+}
+
+func (T) M(int) {}
+func (T) N() (i int) { return }
+
+type G[P any] struct {
+ F P
+}
+
+func (G[P]) M(P) {}
+func (G[P]) N() (p P) { return }
+
+type Inst = G[int]
+ `
+ pkg, err := pkgFor("p", src, nil)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ var (
+ T = pkg.Scope().Lookup("T").Type()
+ G = pkg.Scope().Lookup("G").Type()
+ SrcInst = pkg.Scope().Lookup("Inst").Type()
+ UserInst = mustInstantiate(b, G, Typ[Int])
+ )
+
+ tests := []struct {
+ name string
+ typ Type
+ }{
+ {"nongeneric", T},
+ {"generic", G},
+ {"src instance", SrcInst},
+ {"user instance", UserInst},
+ }
+
+ b.Run("Underlying", func(b *testing.B) {
+ for _, test := range tests {
+ b.Run(test.name, func(b *testing.B) {
+ // Access underlying once, to trigger any lazy calculation.
+ _ = test.typ.Underlying()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _ = test.typ.Underlying()
+ }
+ })
+ }
+ })
+}
+
+func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type {
+ inst, err := Instantiate(nil, orig, targs, true)
+ if err != nil {
+ tb.Fatal(err)
+ }
+ return inst
+}
+
+// Test that types do not expand infinitely, as in golang/go#52715.
+func TestFiniteTypeExpansion(t *testing.T) {
+ const src = `
+package p
+
+type Tree[T any] struct {
+ *Node[T]
+}
+
+func (*Tree[R]) N(r R) R { return r }
+
+type Node[T any] struct {
+ *Tree[T]
+}
+
+func (Node[Q]) M(Q) {}
+
+type Inst = *Tree[int]
+`
+
+ f, err := parseSrc("foo.go", src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pkg := NewPackage("p", f.PkgName.Value)
+ if err := NewChecker(nil, pkg, nil).Files([]*syntax.File{f}); err != nil {
+ t.Fatal(err)
+ }
+
+ firstFieldType := func(n *Named) *Named {
+ return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named)
+ }
+
+ Inst := pkg.Scope().Lookup("Inst").Type().(*Pointer).Elem().(*Named)
+ Node := firstFieldType(Inst)
+ Tree := firstFieldType(Node)
+ if !Identical(Inst, Tree) {
+ t.Fatalf("Not a cycle: got %v, want %v", Tree, Inst)
+ }
+ if Inst != Tree {
+ t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree)
+ }
+}
diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go
index 75f7ea5b12..df080f071c 100644
--- a/src/cmd/compile/internal/types2/object.go
+++ b/src/cmd/compile/internal/types2/object.go
@@ -279,19 +279,7 @@ func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName
// lazily calls resolve to finish constructing the Named object.
func NewTypeNameLazy(pos syntax.Pos, pkg *Package, name string, load func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName {
obj := NewTypeName(pos, pkg, name, nil)
-
- resolve := func(_ *Context, t *Named) (*TypeParamList, Type, *methodList) {
- tparams, underlying, methods := load(t)
-
- switch underlying.(type) {
- case nil, *Named:
- panic(fmt.Sprintf("invalid underlying type %T", t.underlying))
- }
-
- return bindTParams(tparams), underlying, newMethodList(methods)
- }
-
- NewNamed(obj, nil, nil).resolver = resolve
+ NewNamed(obj, nil, nil).loader = load
return obj
}
diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go
index 6bce26137e..f7b5b16204 100644
--- a/src/cmd/compile/internal/types2/predicates.go
+++ b/src/cmd/compile/internal/types2/predicates.go
@@ -102,7 +102,7 @@ func isTypeParam(t Type) bool {
func isGeneric(t Type) bool {
// A parameterized type is only generic if it doesn't have an instantiation already.
named, _ := t.(*Named)
- return named != nil && named.obj != nil && named.targs == nil && named.TypeParams() != nil
+ return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0
}
// Comparable reports whether values of type T are comparable.
@@ -283,18 +283,19 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
}
smap := makeSubstMap(ytparams, targs)
- var check *Checker // ok to call subst on a nil *Checker
+ var check *Checker // ok to call subst on a nil *Checker
+ ctxt := NewContext() // need a non-nil Context for the substitution below
// Constraints must be pair-wise identical, after substitution.
for i, xtparam := range xtparams {
- ybound := check.subst(nopos, ytparams[i].bound, smap, nil)
+ ybound := check.subst(nopos, ytparams[i].bound, smap, nil, ctxt)
if !identical(xtparam.bound, ybound, cmpTags, p) {
return false
}
}
- yparams = check.subst(nopos, y.params, smap, nil).(*Tuple)
- yresults = check.subst(nopos, y.results, smap, nil).(*Tuple)
+ yparams = check.subst(nopos, y.params, smap, nil, ctxt).(*Tuple)
+ yresults = check.subst(nopos, y.results, smap, nil, ctxt).(*Tuple)
}
return x.variadic == y.variadic &&
@@ -401,7 +402,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
if len(xargs) > 0 {
// Instances are identical if their original type and type arguments
// are identical.
- if !Identical(x.orig, y.orig) {
+ if !Identical(x.Origin(), y.Origin()) {
return false
}
for i, xa := range xargs {
diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go
index 92d3aadf88..1b61b368d2 100644
--- a/src/cmd/compile/internal/types2/signature.go
+++ b/src/cmd/compile/internal/types2/signature.go
@@ -143,7 +143,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
// 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)
+ tpar.bound = check.subst(tpar.obj.pos, recvTPar.bound, smap, nil, check.context())
}
} else if len(tparams) < len(recvTParams) {
// Reporting an error here is a stop-gap measure to avoid crashes in the
diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go
index 7ab7abb317..17876d1f3c 100644
--- a/src/cmd/compile/internal/types2/sizeof_test.go
+++ b/src/cmd/compile/internal/types2/sizeof_test.go
@@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) {
{Interface{}, 40, 80},
{Map{}, 16, 32},
{Chan{}, 12, 24},
- {Named{}, 56, 104},
+ {Named{}, 60, 112},
{TypeParam{}, 28, 48},
{term{}, 12, 24},
diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go
index 6e41ebdf53..d5a48c6995 100644
--- a/src/cmd/compile/internal/types2/subst.go
+++ b/src/cmd/compile/internal/types2/subst.go
@@ -48,8 +48,11 @@ func (m substMap) lookup(tpar *TypeParam) Type {
// incoming type. If a substitution took place, the result type is different
// from the incoming type.
//
-// If the given context is non-nil, it is used in lieu of check.Config.Context.
-func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, ctxt *Context) Type {
+// If expanding is non-nil, it is the instance type currently being expanded.
+// One of expanding or ctxt must be non-nil.
+func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, expanding *Named, ctxt *Context) Type {
+ assert(expanding != nil || ctxt != nil)
+
if smap.empty() {
return typ
}
@@ -64,19 +67,21 @@ func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, ctxt *Conte
// general case
subst := subster{
- pos: pos,
- smap: smap,
- check: check,
- ctxt: check.bestContext(ctxt),
+ pos: pos,
+ smap: smap,
+ check: check,
+ expanding: expanding,
+ ctxt: ctxt,
}
return subst.typ(typ)
}
type subster struct {
- pos syntax.Pos
- smap substMap
- check *Checker // nil if called via Instantiate
- ctxt *Context
+ pos syntax.Pos
+ smap substMap
+ check *Checker // nil if called via Instantiate
+ expanding *Named // if non-nil, the instance that is being expanded
+ ctxt *Context
}
func (subst *subster) typ(typ Type) Type {
@@ -176,7 +181,7 @@ func (subst *subster) typ(typ Type) Type {
// In this case the interface will not be substituted here, because its
// method signatures do not depend on the type parameter P, but we still
// need to create new interface methods to hold the instantiated
- // receiver. This is handled by expandNamed.
+ // receiver. This is handled by Named.expandUnderlying.
iface.methods, _ = replaceRecvType(methods, t, iface)
return iface
}
@@ -207,19 +212,20 @@ func (subst *subster) typ(typ Type) Type {
}
}
- // subst is called by expandNamed, so in this function we need to be
+ // subst is called during expansion, so in this function we need to be
// careful not to call any methods that would cause t to be expanded: doing
// so would result in deadlock.
//
- // So we call t.orig.TypeParams() rather than t.TypeParams() here and
- // below.
- if t.orig.TypeParams().Len() == 0 {
+ // So we call t.Origin().TypeParams() rather than t.TypeParams().
+ orig := t.Origin()
+ n := orig.TypeParams().Len()
+ if n == 0 {
dump(">>> %s is not parameterized", t)
return t // type is not parameterized
}
var newTArgs []Type
- if t.targs.Len() != t.orig.TypeParams().Len() {
+ if t.TypeArgs().Len() != n {
return Typ[Invalid] // error reported elsewhere
}
@@ -228,14 +234,14 @@ func (subst *subster) typ(typ Type) Type {
// For each (existing) type argument targ, determine if it needs
// to be substituted; i.e., if it is or contains a type parameter
// that has a type argument for it.
- for i, targ := range t.targs.list() {
+ for i, targ := range t.TypeArgs().list() {
dump(">>> %d targ = %s", i, targ)
new_targ := subst.typ(targ)
if new_targ != targ {
dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
if newTArgs == nil {
- newTArgs = make([]Type, t.orig.TypeParams().Len())
- copy(newTArgs, t.targs.list())
+ newTArgs = make([]Type, n)
+ copy(newTArgs, t.TypeArgs().list())
}
newTArgs[i] = new_targ
}
@@ -246,25 +252,11 @@ func (subst *subster) typ(typ Type) Type {
return t // nothing to substitute
}
- // before creating a new named type, check if we have this one already
- h := subst.ctxt.instanceHash(t.orig, newTArgs)
- dump(">>> new type hash: %s", h)
- if named := subst.ctxt.lookup(h, t.orig, newTArgs); named != nil {
- dump(">>> found %s", named)
- return named
- }
-
// Create a new instance and populate the context to avoid endless
// recursion. The position used here is irrelevant because validation only
// occurs on t (we don't call validType on named), but we use subst.pos to
// help with debugging.
- return subst.check.instance(subst.pos, t.orig, newTArgs, subst.ctxt)
-
- // Note that if we were to expose substitution more generally (not just in
- // the context of a declaration), we'd have to substitute in
- // named.underlying as well.
- //
- // But this is unnecessary for now.
+ return subst.check.instance(subst.pos, orig, newTArgs, subst.expanding, subst.ctxt)
case *TypeParam:
return subst.smap.lookup(t)
diff --git a/src/cmd/compile/internal/types2/termlist.go b/src/cmd/compile/internal/types2/termlist.go
index a0108c4638..43e43ce87c 100644
--- a/src/cmd/compile/internal/types2/termlist.go
+++ b/src/cmd/compile/internal/types2/termlist.go
@@ -25,7 +25,7 @@ func (xl termlist) String() string {
var buf bytes.Buffer
for i, x := range xl {
if i > 0 {
- buf.WriteString(" ∪ ")
+ buf.WriteString(" | ")
}
buf.WriteString(x.String())
}
diff --git a/src/cmd/compile/internal/types2/termlist_test.go b/src/cmd/compile/internal/types2/termlist_test.go
index d1e3bdf88e..3005d0edea 100644
--- a/src/cmd/compile/internal/types2/termlist_test.go
+++ b/src/cmd/compile/internal/types2/termlist_test.go
@@ -12,7 +12,7 @@ import (
// maketl makes a term list from a string of the term list.
func maketl(s string) termlist {
s = strings.ReplaceAll(s, " ", "")
- names := strings.Split(s, "∪")
+ names := strings.Split(s, "|")
r := make(termlist, len(names))
for i, n := range names {
r[i] = testTerm(n)
@@ -33,10 +33,10 @@ func TestTermlistString(t *testing.T) {
"int",
"~int",
"myInt",
- "∅ ∪ ∅",
- "𝓤 ∪ 𝓤",
- "∅ ∪ 𝓤 ∪ int",
- "∅ ∪ 𝓤 ∪ int ∪ myInt",
+ "∅ | ∅",
+ "𝓤 | 𝓤",
+ "∅ | 𝓤 | int",
+ "∅ | 𝓤 | int | myInt",
} {
if got := maketl(want).String(); got != want {
t.Errorf("(%v).String() == %v", want, got)
@@ -47,12 +47,12 @@ func TestTermlistString(t *testing.T) {
func TestTermlistIsEmpty(t *testing.T) {
for test, want := range map[string]bool{
"∅": true,
- "∅ ∪ ∅": true,
- "∅ ∪ ∅ ∪ 𝓤": false,
- "∅ ∪ ∅ ∪ myInt": false,
+ "∅ | ∅": true,
+ "∅ | ∅ | 𝓤": false,
+ "∅ | ∅ | myInt": false,
"𝓤": false,
- "𝓤 ∪ int": false,
- "𝓤 ∪ myInt ∪ ∅": false,
+ "𝓤 | int": false,
+ "𝓤 | myInt | ∅": false,
} {
xl := maketl(test)
got := xl.isEmpty()
@@ -65,13 +65,13 @@ func TestTermlistIsEmpty(t *testing.T) {
func TestTermlistIsAll(t *testing.T) {
for test, want := range map[string]bool{
"∅": false,
- "∅ ∪ ∅": false,
- "int ∪ ~string": false,
- "~int ∪ myInt": false,
- "∅ ∪ ∅ ∪ 𝓤": true,
+ "∅ | ∅": false,
+ "int | ~string": false,
+ "~int | myInt": false,
+ "∅ | ∅ | 𝓤": true,
"𝓤": true,
- "𝓤 ∪ int": true,
- "myInt ∪ 𝓤": true,
+ "𝓤 | int": true,
+ "myInt | 𝓤": true,
} {
xl := maketl(test)
got := xl.isAll()
@@ -86,17 +86,17 @@ func TestTermlistNorm(t *testing.T) {
xl, want string
}{
{"∅", "∅"},
- {"∅ ∪ ∅", "∅"},
- {"∅ ∪ int", "int"},
- {"∅ ∪ myInt", "myInt"},
- {"𝓤 ∪ int", "𝓤"},
- {"𝓤 ∪ myInt", "𝓤"},
- {"int ∪ myInt", "int ∪ myInt"},
- {"~int ∪ int", "~int"},
- {"~int ∪ myInt", "~int"},
- {"int ∪ ~string ∪ int", "int ∪ ~string"},
- {"~int ∪ string ∪ 𝓤 ∪ ~string ∪ int", "𝓤"},
- {"~int ∪ string ∪ myInt ∪ ~string ∪ int", "~int ∪ ~string"},
+ {"∅ | ∅", "∅"},
+ {"∅ | int", "int"},
+ {"∅ | myInt", "myInt"},
+ {"𝓤 | int", "𝓤"},
+ {"𝓤 | myInt", "𝓤"},
+ {"int | myInt", "int | myInt"},
+ {"~int | int", "~int"},
+ {"~int | myInt", "~int"},
+ {"int | ~string | int", "int | ~string"},
+ {"~int | string | 𝓤 | ~string | int", "𝓤"},
+ {"~int | string | myInt | ~string | int", "~int | ~string"},
} {
xl := maketl(test.xl)
got := maketl(test.xl).norm()
@@ -116,15 +116,15 @@ func TestTermlistUnion(t *testing.T) {
{"∅", "int", "int"},
{"𝓤", "~int", "𝓤"},
{"int", "~int", "~int"},
- {"int", "string", "int ∪ string"},
- {"int", "myInt", "int ∪ myInt"},
+ {"int", "string", "int | string"},
+ {"int", "myInt", "int | myInt"},
{"~int", "myInt", "~int"},
- {"int ∪ string", "~string", "int ∪ ~string"},
- {"~int ∪ string", "~string ∪ int", "~int ∪ ~string"},
- {"~int ∪ string ∪ ∅", "~string ∪ int", "~int ∪ ~string"},
- {"~int ∪ myInt ∪ ∅", "~string ∪ int", "~int ∪ ~string"},
- {"~int ∪ string ∪ 𝓤", "~string ∪ int", "𝓤"},
- {"~int ∪ string ∪ myInt", "~string ∪ int", "~int ∪ ~string"},
+ {"int | string", "~string", "int | ~string"},
+ {"~int | string", "~string | int", "~int | ~string"},
+ {"~int | string | ∅", "~string | int", "~int | ~string"},
+ {"~int | myInt | ∅", "~string | int", "~int | ~string"},
+ {"~int | string | 𝓤", "~string | int", "𝓤"},
+ {"~int | string | myInt", "~string | int", "~int | ~string"},
} {
xl := maketl(test.xl)
yl := maketl(test.yl)
@@ -150,12 +150,12 @@ func TestTermlistIntersect(t *testing.T) {
{"int", "string", "∅"},
{"int", "myInt", "∅"},
{"~int", "myInt", "myInt"},
- {"int ∪ string", "~string", "string"},
- {"~int ∪ string", "~string ∪ int", "int ∪ string"},
- {"~int ∪ string ∪ ∅", "~string ∪ int", "int ∪ string"},
- {"~int ∪ myInt ∪ ∅", "~string ∪ int", "int"},
- {"~int ∪ string ∪ 𝓤", "~string ∪ int", "int ∪ ~string"},
- {"~int ∪ string ∪ myInt", "~string ∪ int", "int ∪ string"},
+ {"int | string", "~string", "string"},
+ {"~int | string", "~string | int", "int | string"},
+ {"~int | string | ∅", "~string | int", "int | string"},
+ {"~int | myInt | ∅", "~string | int", "int"},
+ {"~int | string | 𝓤", "~string | int", "int | ~string"},
+ {"~int | string | myInt", "~string | int", "int | string"},
} {
xl := maketl(test.xl)
yl := maketl(test.yl)
@@ -174,12 +174,12 @@ func TestTermlistEqual(t *testing.T) {
{"∅", "∅", true},
{"∅", "𝓤", false},
{"𝓤", "𝓤", true},
- {"𝓤 ∪ int", "𝓤", true},
- {"𝓤 ∪ int", "string ∪ 𝓤", true},
- {"𝓤 ∪ myInt", "string ∪ 𝓤", true},
- {"int ∪ ~string", "string ∪ int", false},
- {"~int ∪ string", "string ∪ myInt", false},
- {"int ∪ ~string ∪ ∅", "string ∪ int ∪ ~string", true},
+ {"𝓤 | int", "𝓤", true},
+ {"𝓤 | int", "string | 𝓤", true},
+ {"𝓤 | myInt", "string | 𝓤", true},
+ {"int | ~string", "string | int", false},
+ {"~int | string", "string | myInt", false},
+ {"int | ~string | ∅", "string | int | ~string", true},
} {
xl := maketl(test.xl)
yl := maketl(test.yl)
@@ -201,11 +201,11 @@ func TestTermlistIncludes(t *testing.T) {
{"int", "string", false},
{"~int", "string", false},
{"~int", "myInt", true},
- {"int ∪ string", "string", true},
- {"~int ∪ string", "int", true},
- {"~int ∪ string", "myInt", true},
- {"~int ∪ myInt ∪ ∅", "myInt", true},
- {"myInt ∪ ∅ ∪ 𝓤", "int", true},
+ {"int | string", "string", true},
+ {"~int | string", "int", true},
+ {"~int | string", "myInt", true},
+ {"~int | myInt | ∅", "myInt", true},
+ {"myInt | ∅ | 𝓤", "int", true},
} {
xl := maketl(test.xl)
yl := testTerm(test.typ).typ
@@ -236,12 +236,12 @@ func TestTermlistSupersetOf(t *testing.T) {
{"myInt", "~int", false},
{"int", "string", false},
{"~int", "string", false},
- {"int ∪ string", "string", true},
- {"int ∪ string", "~string", false},
- {"~int ∪ string", "int", true},
- {"~int ∪ string", "myInt", true},
- {"~int ∪ string ∪ ∅", "string", true},
- {"~string ∪ ∅ ∪ 𝓤", "myInt", true},
+ {"int | string", "string", true},
+ {"int | string", "~string", false},
+ {"~int | string", "int", true},
+ {"~int | string", "myInt", true},
+ {"~int | string | ∅", "string", true},
+ {"~string | ∅ | 𝓤", "myInt", true},
} {
xl := maketl(test.xl)
y := testTerm(test.typ)
@@ -261,18 +261,18 @@ func TestTermlistSubsetOf(t *testing.T) {
{"∅", "𝓤", true},
{"𝓤", "∅", false},
{"𝓤", "𝓤", true},
- {"int", "int ∪ string", true},
- {"~int", "int ∪ string", false},
- {"~int", "myInt ∪ string", false},
- {"myInt", "~int ∪ string", true},
- {"~int", "string ∪ string ∪ int ∪ ~int", true},
- {"myInt", "string ∪ string ∪ ~int", true},
- {"int ∪ string", "string", false},
- {"int ∪ string", "string ∪ int", true},
- {"int ∪ ~string", "string ∪ int", false},
- {"myInt ∪ ~string", "string ∪ int ∪ 𝓤", true},
- {"int ∪ ~string", "string ∪ int ∪ ∅ ∪ string", false},
- {"int ∪ myInt", "string ∪ ~int ∪ ∅ ∪ string", true},
+ {"int", "int | string", true},
+ {"~int", "int | string", false},
+ {"~int", "myInt | string", false},
+ {"myInt", "~int | string", true},
+ {"~int", "string | string | int | ~int", true},
+ {"myInt", "string | string | ~int", true},
+ {"int | string", "string", false},
+ {"int | string", "string | int", true},
+ {"int | ~string", "string | int", false},
+ {"myInt | ~string", "string | int | 𝓤", true},
+ {"int | ~string", "string | int | ∅ | string", false},
+ {"int | myInt", "string | ~int | ∅ | string", true},
} {
xl := maketl(test.xl)
yl := maketl(test.yl)
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go
new file mode 100644
index 0000000000..7ffd551c2e
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue40350.go
@@ -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 number interface {
+ ~float64 | ~int | ~int32
+ float64 | ~int32
+}
+
+func f[T number]() {}
+
+func _() {
+ _ = f[int /* ERROR int does not implement number \(int missing in float64 | ~int32\)*/]
+}
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go
new file mode 100644
index 0000000000..7122d1c787
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue42881.go
@@ -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 (
+ T1 interface{ comparable }
+ T2 interface{ int }
+)
+
+var (
+ _ comparable // ERROR cannot use type comparable outside a type constraint: interface is \(or embeds\) comparable
+ _ T1 // ERROR cannot use type T1 outside a type constraint: interface is \(or embeds\) comparable
+ _ T2 // ERROR cannot use type T2 outside a type constraint: interface contains type constraints
+)
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go
new file mode 100644
index 0000000000..fe19fdfa68
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue50729.go
@@ -0,0 +1,19 @@
+// 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
+
+// version 1
+var x1 T1[B1]
+
+type T1[_ any] struct{}
+type A1 T1[B1]
+type B1 = T1[A1]
+
+// version 2
+type T2[_ any] struct{}
+type A2 T2[B2]
+type B2 = T2[A2]
+
+var x2 T2[B2]
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go
new file mode 100644
index 0000000000..d1b06a210d
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52698.go
@@ -0,0 +1,62 @@
+// 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
+
+// correctness check: ensure that cycles through generic instantiations are detected
+type T[P any] struct {
+ _ P
+}
+
+type S /* ERROR illegal cycle */ struct {
+ _ T[S]
+}
+
+// simplified test 1
+
+var _ A1[A1[string]]
+
+type A1[P any] struct {
+ _ B1[P]
+}
+
+type B1[P any] struct {
+ _ P
+}
+
+// simplified test 2
+var _ B2[A2]
+
+type A2 struct {
+ _ B2[string]
+}
+
+type B2[P any] struct {
+ _ C2[P]
+}
+
+type C2[P any] struct {
+ _ P
+}
+
+// test case from issue
+type T23 interface {
+ ~struct {
+ Field0 T13[T15]
+ }
+}
+
+type T1[P1 interface {
+}] struct {
+ Field2 P1
+}
+
+type T13[P2 interface {
+}] struct {
+ Field2 T1[P2]
+}
+
+type T15 struct {
+ Field0 T13[string]
+}
diff --git a/src/cmd/compile/internal/types2/typeset_test.go b/src/cmd/compile/internal/types2/typeset_test.go
index 69eaff741f..40ca28e525 100644
--- a/src/cmd/compile/internal/types2/typeset_test.go
+++ b/src/cmd/compile/internal/types2/typeset_test.go
@@ -21,13 +21,13 @@ func TestTypeSetString(t *testing.T) {
"{}": "𝓤",
"{int}": "{int}",
"{~int}": "{~int}",
- "{int|string}": "{int ∪ string}",
+ "{int|string}": "{int | string}",
"{int; string}": "∅",
"{comparable}": "{comparable}",
"{comparable; int}": "{int}",
"{~int; comparable}": "{~int}",
- "{int|string; comparable}": "{int ∪ string}",
+ "{int|string; comparable}": "{int | string}",
"{comparable; int; string}": "∅",
"{m()}": "{func (p.T).m()}",
@@ -37,7 +37,7 @@ 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}": "{func (p.T).m(); int ∪ float32 ∪ string}",
+ "{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{}": "𝓤",
diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go
index e0f36fcec4..c10c2d8973 100644
--- a/src/cmd/compile/internal/types2/typestring.go
+++ b/src/cmd/compile/internal/types2/typestring.go
@@ -284,9 +284,9 @@ func (w *typeWriter) typ(typ Type) {
w.string(strconv.Itoa(w.ctxt.getID(t)))
}
w.typeName(t.obj) // when hashing written for readability of the hash only
- if t.targs != nil {
+ if t.inst != nil {
// instantiated type
- w.typeList(t.targs.list())
+ w.typeList(t.inst.targs.list())
} else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams
// parameterized type
w.tParamList(t.TypeParams().list())
diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go
index 1f8b40dba6..692feb9751 100644
--- a/src/cmd/compile/internal/types2/typexpr.go
+++ b/src/cmd/compile/internal/types2/typexpr.go
@@ -167,9 +167,9 @@ func (check *Checker) validVarType(e syntax.Expr, typ Type) {
tset := computeInterfaceTypeSet(check, pos, t) // TODO(gri) is this the correct position?
if !tset.IsMethodSet() {
if tset.comparable {
- check.softErrorf(pos, "interface is (or embeds) comparable")
+ check.softErrorf(pos, "cannot use type %s outside a type constraint: interface is (or embeds) comparable", typ)
} else {
- check.softErrorf(pos, "interface contains type constraints")
+ check.softErrorf(pos, "cannot use type %s outside a type constraint: interface contains type constraints", typ)
}
}
}
@@ -432,57 +432,19 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *
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)
- // targs may be incomplete, and require inference. In any case we should de-duplicate.
- inst, _ := ctxt.lookup(h, orig, targs).(*Named)
- // If inst is non-nil, we can't just return here. Inst may have been
- // constructed via recursive substitution, in which case we wouldn't do the
- // validation below. Ensure that the validation (and resulting errors) runs
- // for each instantiated type in the source.
- if inst == nil {
- // x may be a selector for an imported type; use its start pos rather than x.Pos().
- tname := NewTypeName(syntax.StartPos(x), orig.obj.pkg, orig.obj.name, nil)
- inst = check.newNamed(tname, orig, nil, nil) // underlying, methods and tparams are set when named is resolved
- inst.targs = newTypeList(targs)
- inst = ctxt.update(h, orig, targs, inst).(*Named)
- }
+ inst := check.instance(x.Pos(), orig, targs, nil, check.context()).(*Named)
def.setUnderlying(inst)
- inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, *methodList) {
- tparams := n.orig.TypeParams().list()
-
- 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(x.Pos(), tparams, targs, nil, nil)
- if len(inferred) > len(targs) {
- n.targs = newTypeList(inferred)
- }
- }
-
- return expandNamed(ctxt, n, x.Pos())
- }
-
// orig.tparams may not be set up, so we need to do expansion later.
check.later(func() {
// This is an instance from the source, not from recursive substitution,
// and so it must be resolved during type-checking so that we can report
// errors.
- inst.resolve(ctxt)
- // Since check is non-nil, we can still mutate inst. Unpinning the resolver
- // frees some memory.
- inst.resolver = nil
check.recordInstance(x, inst.TypeArgs().list(), inst)
- if check.validateTArgLen(x.Pos(), inst.tparams.Len(), inst.targs.Len()) {
- if i, err := check.verify(x.Pos(), inst.tparams.list(), inst.targs.list()); err != nil {
+ if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) {
+ if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), check.context()); err != nil {
// best position for error reporting
pos := x.Pos()
if i < len(xlist) {
@@ -490,10 +452,13 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *
}
check.softErrorf(pos, "%s", err)
} else {
- check.mono.recordInstance(check.pkg, x.Pos(), inst.tparams.list(), inst.targs.list(), xlist)
+ check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), xlist)
}
}
+ // TODO(rfindley): remove this call: we don't need to call validType here,
+ // as cycles can only occur for types used inside a Named type declaration,
+ // and so it suffices to call validType from declared types.
check.validType(inst)
}).describef(x, "resolve instance %s", inst)
diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go
index a7f68a05b1..7063789f3f 100644
--- a/src/cmd/compile/internal/types2/unify.go
+++ b/src/cmd/compile/internal/types2/unify.go
@@ -546,8 +546,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
case *Named:
// TODO(gri) This code differs now from the parallel code in Checker.identical. Investigate.
if y, ok := y.(*Named); ok {
- xargs := x.targs.list()
- yargs := y.targs.list()
+ xargs := x.TypeArgs().list()
+ yargs := y.TypeArgs().list()
if len(xargs) != len(yargs) {
return false
diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go
index d495c6788e..99fdebc978 100644
--- a/src/cmd/compile/internal/types2/validtype.go
+++ b/src/cmd/compile/internal/types2/validtype.go
@@ -5,29 +5,24 @@
package types2
// validType verifies that the given type does not "expand" indefinitely
-// producing a cycle in the type graph. Cycles are detected by marking
-// defined types.
+// producing a cycle in the type graph.
// (Cycles involving alias types, as in "type A = [10]A" are detected
// earlier, via the objDecl cycle detection mechanism.)
func (check *Checker) validType(typ *Named) {
check.validType0(typ, nil, nil)
}
-type typeInfo uint
-
// validType0 checks if the given type is valid. If typ is a type parameter
-// its value is looked up in the provided environment. The environment is
-// nil if typ is not part of (the RHS of) an instantiated type, in that case
-// any type parameter encountered must be from an enclosing function and can
-// be ignored. The path is the list of type names that lead to the current typ.
-func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeInfo {
- const (
- unknown typeInfo = iota
- marked
- valid
- invalid
- )
-
+// its value is looked up in the type argument list of the instantiated
+// (enclosing) type, if it exists. Otherwise the type parameter must be from
+// an enclosing function and can be ignored.
+// The nest list describes the stack (the "nest in memory") of types which
+// contain (or embed in the case of interfaces) other types. For instance, a
+// struct named S which contains a field of named type F contains (the memory
+// of) F in S, leading to the nest S->F. If a type appears in its own nest
+// (say S->F->S) we have an invalid recursive type. The path list is the full
+// path of named types in a cycle, it is only needed for error reporting.
+func (check *Checker) validType0(typ Type, nest, path []*Named) bool {
switch t := typ.(type) {
case nil:
// We should never see a nil type but be conservative and panic
@@ -37,112 +32,204 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn
}
case *Array:
- return check.validType0(t.elem, env, path)
+ return check.validType0(t.elem, nest, path)
case *Struct:
for _, f := range t.fields {
- if check.validType0(f.typ, env, path) == invalid {
- return invalid
+ if !check.validType0(f.typ, nest, path) {
+ return false
}
}
case *Union:
for _, t := range t.terms {
- if check.validType0(t.typ, env, path) == invalid {
- return invalid
+ if !check.validType0(t.typ, nest, path) {
+ return false
}
}
case *Interface:
for _, etyp := range t.embeddeds {
- if check.validType0(etyp, env, path) == invalid {
- return invalid
+ if !check.validType0(etyp, nest, path) {
+ return false
}
}
case *Named:
+ // Exit early if we already know t is valid.
+ // This is purely an optimization but it prevents excessive computation
+ // times in pathological cases such as testdata/fixedbugs/issue6977.go.
+ // (Note: The valids map could also be allocated locally, once for each
+ // validType call.)
+ if check.valids.lookup(t) != nil {
+ break
+ }
+
// Don't report a 2nd error if we already know the type is invalid
// (e.g., if a cycle was detected earlier, via under).
// Note: ensure that t.orig is fully resolved by calling Underlying().
if t.Underlying() == Typ[Invalid] {
- check.infoMap[t] = invalid
- return invalid
+ return false
}
- switch check.infoMap[t] {
- case unknown:
- check.infoMap[t] = marked
- check.infoMap[t] = check.validType0(t.orig.fromRHS, env.push(t), append(path, t.obj))
- case marked:
- // We have seen type t before and thus must have a cycle.
- check.infoMap[t] = invalid
- // t cannot be in an imported package otherwise that package
- // 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 possibility)
- // Find the starting point of the cycle and report it.
- for i, tn := range path {
- if tn == t.obj {
- check.cycleError(path[i:])
- return invalid
+ // If the current type t is also found in nest, (the memory of) t is
+ // embedded in itself, indicating an invalid recursive type.
+ for _, e := range nest {
+ if Identical(e, t) {
+ // t cannot be in an imported package otherwise that package
+ // 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 possibility)
+ // Find the starting point of the cycle and report it.
+ // Because each type in nest must also appear in path (see invariant below),
+ // type t must be in path since it was found in nest. But not every type in path
+ // is in nest. Specifically t may appear in path with an earlier index than the
+ // index of t in nest. Search again.
+ for start, p := range path {
+ if Identical(p, t) {
+ check.cycleError(makeObjList(path[start:]))
+ return false
+ }
}
+ panic("cycle start not found")
}
- panic("cycle start not found")
}
- return check.infoMap[t]
+
+ // No cycle was found. Check the RHS of t.
+ // Every type added to nest is also added to path; thus every type that is in nest
+ // must also be in path (invariant). But not every type in path is in nest, since
+ // nest may be pruned (see below, *TypeParam case).
+ if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) {
+ return false
+ }
+
+ check.valids.add(t) // t is valid
case *TypeParam:
// A type parameter stands for the type (argument) it was instantiated with.
- // Check the corresponding type argument for validity if we have one.
- if env != nil {
- if targ := env.tmap[t]; targ != nil {
- // Type arguments found in targ must be looked
- // up in the enclosing environment env.link.
- return check.validType0(targ, env.link, path)
+ // Check the corresponding type argument for validity if we are in an
+ // instantiated type.
+ if len(nest) > 0 {
+ inst := nest[len(nest)-1] // the type instance
+ // Find the corresponding type argument for the type parameter
+ // and proceed with checking that type argument.
+ for i, tparam := range inst.TypeParams().list() {
+ // The type parameter and type argument lists should
+ // match in length but be careful in case of errors.
+ if t == tparam && i < inst.TypeArgs().Len() {
+ targ := inst.TypeArgs().At(i)
+ // The type argument must be valid in the enclosing
+ // type (where inst was instantiated), hence we must
+ // check targ's validity in the type nest excluding
+ // the current (instantiated) type (see the example
+ // at the end of this file).
+ // For error reporting we keep the full path.
+ return check.validType0(targ, nest[:len(nest)-1], path)
+ }
}
}
}
- return valid
-}
-
-// A tparamEnv provides the environment for looking up the type arguments
-// with which type parameters for a given instance were instantiated.
-// If we don't have an instance, the corresponding tparamEnv is nil.
-type tparamEnv struct {
- tmap substMap
- link *tparamEnv
+ return true
}
-func (env *tparamEnv) push(typ *Named) *tparamEnv {
- // If typ is not an instantiated type there are no typ-specific
- // type parameters to look up and we don't need an environment.
- targs := typ.TypeArgs()
- if targs == nil {
- return nil // no instance => nil environment
+// makeObjList returns the list of type name objects for the given
+// list of named types.
+func makeObjList(tlist []*Named) []Object {
+ olist := make([]Object, len(tlist))
+ for i, t := range tlist {
+ olist[i] = t.obj
}
-
- // Populate tmap: remember the type argument for each type parameter.
- // We cannot use makeSubstMap because the number of type parameters
- // and arguments may not match due to errors in the source (too many
- // or too few type arguments). Populate tmap "manually".
- tparams := typ.TypeParams()
- n, m := targs.Len(), tparams.Len()
- if n > m {
- n = m // too many targs
- }
- tmap := make(substMap, n)
- for i := 0; i < n; i++ {
- tmap[tparams.At(i)] = targs.At(i)
- }
-
- return &tparamEnv{tmap: tmap, link: env}
+ return olist
}
-// TODO(gri) Alternative implementation:
-// We may not need to build a stack of environments to
-// look up the type arguments for type parameters. The
-// same information should be available via the path:
-// We should be able to just walk the path backwards
-// and find the type arguments in the instance objects.
+// Here is an example illustrating why we need to exclude the
+// instantiated type from nest when evaluating the validity of
+// a type parameter. Given the declarations
+//
+// var _ A[A[string]]
+//
+// type A[P any] struct { _ B[P] }
+// type B[P any] struct { _ P }
+//
+// we want to determine if the type A[A[string]] is valid.
+// We start evaluating A[A[string]] outside any type nest:
+//
+// A[A[string]]
+// nest =
+// path =
+//
+// The RHS of A is now evaluated in the A[A[string]] nest:
+//
+// struct{_ B[P₁]}
+// nest = A[A[string]]
+// path = A[A[string]]
+//
+// The struct has a single field of type B[P₁] with which
+// we continue:
+//
+// B[P₁]
+// nest = A[A[string]]
+// path = A[A[string]]
+//
+// struct{_ P₂}
+// nest = A[A[string]]->B[P]
+// path = A[A[string]]->B[P]
+//
+// Eventutally we reach the type parameter P of type B (P₂):
+//
+// P₂
+// nest = A[A[string]]->B[P]
+// path = A[A[string]]->B[P]
+//
+// The type argument for P of B is the type parameter P of A (P₁).
+// It must be evaluated in the type nest that existed when B was
+// instantiated:
+//
+// P₁
+// nest = A[A[string]] <== type nest at B's instantiation time
+// path = A[A[string]]->B[P]
+//
+// If we'd use the current nest it would correspond to the path
+// which will be wrong as we will see shortly. P's type argument
+// is A[string], which again must be evaluated in the type nest
+// that existed when A was instantiated with A[string]. That type
+// nest is empty:
+//
+// A[string]
+// nest = <== type nest at A's instantiation time
+// path = A[A[string]]->B[P]
+//
+// Evaluation then proceeds as before for A[string]:
+//
+// struct{_ B[P₁]}
+// nest = A[string]
+// path = A[A[string]]->B[P]->A[string]
+//
+// Now we reach B[P] again. If we had not adjusted nest, it would
+// correspond to path, and we would find B[P] in nest, indicating
+// a cycle, which would clearly be wrong since there's no cycle in
+// A[string]:
+//
+// B[P₁]
+// nest = A[string]
+// path = A[A[string]]->B[P]->A[string] <== path contains B[P]!
+//
+// But because we use the correct type nest, evaluation proceeds without
+// errors and we get the evaluation sequence:
+//
+// struct{_ P₂}
+// nest = A[string]->B[P]
+// path = A[A[string]]->B[P]->A[string]->B[P]
+// P₂
+// nest = A[string]->B[P]
+// path = A[A[string]]->B[P]->A[string]->B[P]
+// P₁
+// nest = A[string]
+// path = A[A[string]]->B[P]->A[string]->B[P]
+// string
+// nest =
+// path = A[A[string]]->B[P]->A[string]->B[P]
+//
+// At this point we're done and A[A[string]] and is valid.
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go
index 677be336ac..26d7fe0f73 100644
--- a/src/cmd/dist/test.go
+++ b/src/cmd/dist/test.go
@@ -290,7 +290,7 @@ func (t *tester) maybeLogMetadata() error {
//
// TODO(prattmic): If we split dist bootstrap and dist test then this
// could be simplified to directly use internal/sysinfo here.
- return t.dirCmd(filepath.Join(goroot, "src/cmd/internal/metadata"), "go", []string{"run", "."}).Run()
+ return t.dirCmd(filepath.Join(goroot, "src/cmd/internal/metadata"), "go", []string{"run", "main.go"}).Run()
}
// short returns a -short flag value to use with 'go test'
diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go
index 8564411fb6..019d36490f 100644
--- a/src/cmd/go/internal/clean/clean.go
+++ b/src/cmd/go/internal/clean/clean.go
@@ -22,6 +22,7 @@ import (
"cmd/go/internal/lockedfile"
"cmd/go/internal/modfetch"
"cmd/go/internal/modload"
+ "cmd/go/internal/str"
"cmd/go/internal/work"
)
@@ -141,7 +142,7 @@ func runClean(ctx context.Context, cmd *base.Command, args []string) {
// The top cache directory may have been created with special permissions
// and not something that we want to remove. Also, we'd like to preserve
// the access log for future analysis, even if the cache is cleared.
- subdirs, _ := filepath.Glob(filepath.Join(dir, "[0-9a-f][0-9a-f]"))
+ subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
printedErrors := false
if len(subdirs) > 0 {
if cfg.BuildN || cfg.BuildX {
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index 8ceacec326..fe4a82472d 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -14,7 +14,6 @@ import (
"go/build"
"go/scanner"
"go/token"
- "internal/goroot"
"io/fs"
"os"
"os/exec"
@@ -872,7 +871,7 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo
if !cfg.ModulesEnabled {
buildMode = build.ImportComment
}
- if modroot := modload.PackageModRoot(ctx, r.dir); modroot != "" {
+ if modroot := modload.PackageModRoot(ctx, r.path); modroot != "" {
if mi, err := modindex.Get(modroot); err == nil {
data.p, data.err = mi.Import(cfg.BuildContext, mi.RelPath(r.dir), buildMode)
goto Happy
@@ -2064,7 +2063,7 @@ func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[st
}
// Glob to find matches.
- match, err := fsys.Glob(pkgdir + string(filepath.Separator) + filepath.FromSlash(glob))
+ match, err := fsys.Glob(str.QuoteGlob(pkgdir) + string(filepath.Separator) + filepath.FromSlash(glob))
if err != nil {
return nil, nil, err
}
@@ -3072,7 +3071,7 @@ func PackagesAndErrorsOutsideModule(ctx context.Context, opts PackageOpts, args
return nil, fmt.Errorf("%s: argument must be a package path, not a meta-package", arg)
case path.Clean(p) != p:
return nil, fmt.Errorf("%s: argument must be a clean package path", arg)
- case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p):
+ case !strings.Contains(p, "...") && search.IsStandardImportPath(p) && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, p):
return nil, fmt.Errorf("%s: argument must not be a package in the standard library", arg)
default:
patterns[i] = p
diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go
index 21d5f54688..a7c8c2c769 100644
--- a/src/cmd/go/internal/modfetch/fetch.go
+++ b/src/cmd/go/internal/modfetch/fetch.go
@@ -26,6 +26,7 @@ import (
"cmd/go/internal/lockedfile"
"cmd/go/internal/par"
"cmd/go/internal/robustio"
+ "cmd/go/internal/str"
"cmd/go/internal/trace"
"golang.org/x/mod/module"
@@ -102,7 +103,7 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) {
// active.
parentDir := filepath.Dir(dir)
tmpPrefix := filepath.Base(dir) + ".tmp-"
- if old, err := filepath.Glob(filepath.Join(parentDir, tmpPrefix+"*")); err == nil {
+ if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(parentDir), str.QuoteGlob(tmpPrefix)+"*")); err == nil {
for _, path := range old {
RemoveAll(path) // best effort
}
@@ -224,7 +225,7 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e
// This is only safe to do because the lock file ensures that their
// writers are no longer active.
tmpPattern := filepath.Base(zipfile) + "*.tmp"
- if old, err := filepath.Glob(filepath.Join(filepath.Dir(zipfile), tmpPattern)); err == nil {
+ if old, err := filepath.Glob(filepath.Join(str.QuoteGlob(filepath.Dir(zipfile)), tmpPattern)); err == nil {
for _, path := range old {
os.Remove(path) // best effort
}
diff --git a/src/cmd/go/internal/modindex/build.go b/src/cmd/go/internal/modindex/build.go
index 78bd12636d..d6d4ea371a 100644
--- a/src/cmd/go/internal/modindex/build.go
+++ b/src/cmd/go/internal/modindex/build.go
@@ -177,6 +177,37 @@ func hasSubdir(root, dir string) (rel string, ok bool) {
return filepath.ToSlash(dir[len(root):]), true
}
+// gopath returns the list of Go path directories.
+func (ctxt *Context) gopath() []string {
+ var all []string
+ for _, p := range ctxt.splitPathList(ctxt.GOPATH) {
+ if p == "" || p == ctxt.GOROOT {
+ // Empty paths are uninteresting.
+ // If the path is the GOROOT, ignore it.
+ // People sometimes set GOPATH=$GOROOT.
+ // Do not get confused by this common mistake.
+ continue
+ }
+ if strings.HasPrefix(p, "~") {
+ // Path segments starting with ~ on Unix are almost always
+ // users who have incorrectly quoted ~ while setting GOPATH,
+ // preventing it from expanding to $HOME.
+ // The situation is made more confusing by the fact that
+ // bash allows quoted ~ in $PATH (most shells do not).
+ // Do not get confused by this, and do not try to use the path.
+ // It does not exist, and printing errors about it confuses
+ // those users even more, because they think "sure ~ exists!".
+ // The go command diagnoses this situation and prints a
+ // useful error.
+ // On Windows, ~ is used in short names, such as c:\progra~1
+ // for c:\program files.
+ continue
+ }
+ all = append(all, p)
+ }
+ return all
+}
+
var defaultToolTags, defaultReleaseTags []string
// A Package describes the Go package found in a directory.
@@ -887,8 +918,8 @@ func (ctxt *Context) eval(x constraint.Expr, allTags map[string]bool) bool {
// $GOARCH
// boringcrypto
// ctxt.Compiler
-// linux (if GOOS = android)
-// solaris (if GOOS = illumos)
+// linux (if GOOS == android)
+// solaris (if GOOS == illumos)
// tag (if tag is listed in ctxt.BuildTags or ctxt.ReleaseTags)
//
// It records all consulted tags in allTags.
diff --git a/src/cmd/go/internal/modindex/read.go b/src/cmd/go/internal/modindex/read.go
index e180ca5450..ffa091df41 100644
--- a/src/cmd/go/internal/modindex/read.go
+++ b/src/cmd/go/internal/modindex/read.go
@@ -1,38 +1,53 @@
+// 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 modindex
import (
"bytes"
- "cmd/go/internal/base"
- "cmd/go/internal/cache"
- "cmd/go/internal/cfg"
- "cmd/go/internal/fsys"
- "cmd/go/internal/imports"
- "cmd/go/internal/par"
- "cmd/go/internal/str"
"encoding/binary"
"errors"
"fmt"
"go/build"
"go/build/constraint"
"go/token"
+ "internal/goroot"
"internal/unsafeheader"
"io/fs"
"math"
"os"
+ "path"
"path/filepath"
+ "runtime"
"runtime/debug"
"sort"
- "strconv"
"strings"
"sync"
"unsafe"
+
+ "cmd/go/internal/base"
+ "cmd/go/internal/cache"
+ "cmd/go/internal/cfg"
+ "cmd/go/internal/fsys"
+ "cmd/go/internal/imports"
+ "cmd/go/internal/par"
+ "cmd/go/internal/str"
)
// enabled is used to flag off the behavior of the module index on tip.
// It will be removed before the release.
// TODO(matloob): Remove enabled once we have more confidence on the
// module index.
-var enabled, _ = strconv.ParseBool(os.Getenv("GOINDEX"))
+var enabled = func() bool {
+ debug := strings.Split(os.Getenv("GODEBUG"), ",")
+ for _, f := range debug {
+ if f == "goindex=0" {
+ return false
+ }
+ }
+ return true
+}()
// ModuleIndex represents and encoded module index file. It is used to
// do the equivalent of build.Import of packages in the module and answer other
@@ -47,38 +62,56 @@ type ModuleIndex struct {
var fcache par.Cache
func moduleHash(modroot string, ismodcache bool) (cache.ActionID, error) {
- h := cache.NewHash("moduleIndex")
- fmt.Fprintf(h, "module index %s %v", indexVersion, modroot)
- if ismodcache {
- return h.Sum(), nil
+ // We expect modules stored within the module cache to be checksummed and
+ // immutable, and we expect released Go modules to change only infrequently
+ // (when the Go version changes).
+ if !ismodcache || !str.HasFilePathPrefix(modroot, cfg.GOROOT) {
+ return cache.ActionID{}, ErrNotIndexed
}
- // walkdir happens in deterministic order.
- err := fsys.Walk(modroot, func(path string, info fs.FileInfo, err error) error {
- if modroot == path {
- // Check for go.mod in root directory, and return ErrNotIndexed
- // if it doesn't exist. Outside the module cache, it's not a module
- // if it doesn't have a go.mod file.
- }
- if err := moduleWalkErr(modroot, path, info, err); err != nil {
- return err
- }
- if info.IsDir() {
- return nil
- }
- fmt.Fprintf(h, "file %v %v\n", info.Name(), info.ModTime())
- if info.Mode()&fs.ModeSymlink != 0 {
- targ, err := fsys.Stat(path)
- if err != nil {
+ h := cache.NewHash("moduleIndex")
+ fmt.Fprintf(h, "module index %s %s %v\n", runtime.Version(), indexVersion, modroot)
+
+ if strings.HasPrefix(runtime.Version(), "devel ") {
+ // This copy of the standard library is a development version, not a
+ // release. It could be based on a Git commit (like "devel go1.19-2a78e8afc0
+ // Wed Jun 15 00:06:24 2022 +0000") with or without changes on top of that
+ // commit, or it could be completly artificial due to lacking a `git` binary
+ // (like "devel gomote.XXXXX", as synthesized by "gomote push" as of
+ // 2022-06-15). Compute an inexpensive hash of its files using mtimes so
+ // that during development we can continue to exercise the logic for cached
+ // GOROOT indexes.
+ //
+ // mtimes may be granular, imprecise, and loosely updated (see
+ // https://apenwarr.ca/log/20181113), but we don't expect Go contributors to
+ // be mucking around with the import graphs in GOROOT often enough for mtime
+ // collisions to matter essentially ever.
+ //
+ // Note that fsys.Walk walks paths in deterministic order, so this hash
+ // should be completely deterministic if the files are unchanged.
+ err := fsys.Walk(modroot, func(path string, info fs.FileInfo, err error) error {
+ if err := moduleWalkErr(modroot, path, info, err); err != nil {
return err
}
- fmt.Fprintf(h, "target %v %v\n", targ.Name(), targ.ModTime())
+
+ if info.IsDir() {
+ return nil
+ }
+ fmt.Fprintf(h, "file %v %v\n", info.Name(), info.ModTime())
+ if info.Mode()&fs.ModeSymlink != 0 {
+ targ, err := fsys.Stat(path)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(h, "target %v %v\n", targ.Name(), targ.ModTime())
+ }
+ return nil
+ })
+ if err != nil {
+ return cache.ActionID{}, err
}
- return nil
- })
- if err != nil {
- return cache.ActionID{}, err
}
+
return h.Sum(), nil
}
@@ -97,10 +130,7 @@ func Get(modroot string) (*ModuleIndex, error) {
if modroot == "" {
panic("modindex.Get called with empty modroot")
}
- if str.HasFilePathPrefix(modroot, cfg.GOROOT) {
- // TODO(matloob): add a case for stdlib here.
- return nil, ErrNotIndexed
- }
+ modroot = filepath.Clean(modroot)
isModCache := str.HasFilePathPrefix(modroot, cfg.GOMODCACHE)
return openIndex(modroot, isModCache)
}
@@ -121,7 +151,7 @@ func openIndex(modroot string, ismodcache bool) (*ModuleIndex, error) {
data, _, err := cache.Default().GetMmap(id)
if err != nil {
// Couldn't read from modindex. Assume we couldn't read from
- // the index because the module has't been indexed yet.
+ // the index because the module hasn't been indexed yet.
data, err = indexModule(modroot)
if err != nil {
return result{nil, err}
@@ -206,7 +236,7 @@ func (mi *ModuleIndex) Packages() []string {
// RelPath returns the path relative to the module's root.
func (mi *ModuleIndex) RelPath(path string) string {
- return filepath.Clean(str.TrimFilePathPrefix(path, mi.modroot))
+ return str.TrimFilePathPrefix(filepath.Clean(path), mi.modroot) // mi.modroot is already clean
}
// ImportPackage is the equivalent of build.Import given the information in ModuleIndex.
@@ -225,9 +255,6 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im
p.ImportPath = "."
p.Dir = filepath.Join(mi.modroot, rp.dir)
- if rp.error != "" {
- return p, errors.New(rp.error)
- }
var pkgerr error
switch ctxt.Compiler {
@@ -241,6 +268,78 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im
return p, fmt.Errorf("import %q: import of unknown directory", p.Dir)
}
+ // goroot and gopath
+ inTestdata := func(sub string) bool {
+ return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || str.HasPathPrefix(sub, "testdata")
+ }
+ if !inTestdata(relpath) {
+ // In build.go, p.Root should only be set in the non-local-import case, or in
+ // GOROOT or GOPATH. Since module mode only calls Import with path set to "."
+ // and the module index doesn't apply outside modules, the GOROOT case is
+ // the only case where GOROOT needs to be set.
+ // But: p.Root is actually set in the local-import case outside GOROOT, if
+ // the directory is contained in GOPATH/src
+ // TODO(#37015): fix that behavior in go/build and remove the gopath case
+ // below.
+ if ctxt.GOROOT != "" && str.HasFilePathPrefix(p.Dir, cfg.GOROOTsrc) && p.Dir != cfg.GOROOTsrc {
+ p.Root = ctxt.GOROOT
+ p.Goroot = true
+ modprefix := str.TrimFilePathPrefix(mi.modroot, cfg.GOROOTsrc)
+ p.ImportPath = relpath
+ if modprefix != "" {
+ p.ImportPath = filepath.Join(modprefix, p.ImportPath)
+ }
+ }
+ for _, root := range ctxt.gopath() {
+ // TODO(matloob): do we need to reimplement the conflictdir logic?
+
+ // TODO(matloob): ctxt.hasSubdir evaluates symlinks, so it
+ // can be slower than we'd like. Find out if we can drop this
+ // logic before the release.
+ if sub, ok := ctxt.hasSubdir(filepath.Join(root, "src"), p.Dir); ok {
+ p.ImportPath = sub
+ p.Root = root
+ }
+ }
+ }
+ if p.Root != "" {
+ // Set GOROOT-specific fields (sometimes for modules in a GOPATH directory).
+ // The fields set below (SrcRoot, PkgRoot, BinDir, PkgTargetRoot, and PkgObj)
+ // are only set in build.Import if p.Root != "". As noted in the comment
+ // on setting p.Root above, p.Root should only be set in the GOROOT case for the
+ // set of packages we care about, but is also set for modules in a GOPATH src
+ // directory.
+ var pkgtargetroot string
+ var pkga string
+ suffix := ""
+ if ctxt.InstallSuffix != "" {
+ suffix = "_" + ctxt.InstallSuffix
+ }
+ switch ctxt.Compiler {
+ case "gccgo":
+ pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
+ dir, elem := path.Split(p.ImportPath)
+ pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a"
+ case "gc":
+ pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix
+ pkga = pkgtargetroot + "/" + p.ImportPath + ".a"
+ }
+ p.SrcRoot = ctxt.joinPath(p.Root, "src")
+ p.PkgRoot = ctxt.joinPath(p.Root, "pkg")
+ p.BinDir = ctxt.joinPath(p.Root, "bin")
+ if pkga != "" {
+ p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot)
+ p.PkgObj = ctxt.joinPath(p.Root, pkga)
+ }
+ }
+
+ if rp.error != nil {
+ if errors.Is(rp.error, errCannotFindPackage) && ctxt.Compiler == "gccgo" && p.Goroot {
+ return p, nil
+ }
+ return p, rp.error
+ }
+
if mode&build.FindOnly != 0 {
return p, pkgerr
}
@@ -444,8 +543,31 @@ func (mi *ModuleIndex) Import(bctxt build.Context, relpath string, mode build.Im
return p, pkgerr
}
-// IsDirWithGoFiles is the equivalent of fsys.IsDirWithGoFiles using the information in the
-// RawPackage.
+// IsStandardPackage reports whether path is a standard package
+// for the goroot and compiler using the module index if possible,
+// and otherwise falling back to internal/goroot.IsStandardPackage
+func IsStandardPackage(goroot_, compiler, path string) bool {
+ if !enabled || compiler != "gc" {
+ return goroot.IsStandardPackage(goroot_, compiler, path)
+ }
+
+ reldir := filepath.FromSlash(path) // relative dir path in module index for package
+ modroot := filepath.Join(goroot_, "src")
+ if str.HasFilePathPrefix(reldir, "cmd") {
+ reldir = str.TrimFilePathPrefix(reldir, "cmd")
+ modroot = filepath.Join(modroot, "cmd")
+ }
+ mod, err := Get(modroot)
+ if err != nil {
+ return goroot.IsStandardPackage(goroot_, compiler, path)
+ }
+
+ pkgs := mod.Packages()
+ i := sort.SearchStrings(pkgs, reldir)
+ return i != len(pkgs) && pkgs[i] == reldir
+}
+
+// IsDirWithGoFiles is the equivalent of fsys.IsDirWithGoFiles using the information in the index.
func (mi *ModuleIndex) IsDirWithGoFiles(relpath string) (_ bool, err error) {
rp := mi.indexPackage(relpath)
@@ -462,7 +584,7 @@ func (mi *ModuleIndex) IsDirWithGoFiles(relpath string) (_ bool, err error) {
return false, nil
}
-// ScanDir implements imports.ScanDir using the information in the RawPackage.
+// ScanDir implements imports.ScanDir using the information in the index.
func (mi *ModuleIndex) ScanDir(path string, tags map[string]bool) (sortedImports []string, sortedTestImports []string, err error) {
rp := mi.indexPackage(path)
@@ -556,13 +678,15 @@ func shouldBuild(sf *sourceFile, tags map[string]bool) bool {
// index package holds the information needed to access information in the
// index about a package.
type indexPackage struct {
- error string
+ error error
dir string // directory of the package relative to the modroot
// Source files
sourceFiles []*sourceFile
}
+var errCannotFindPackage = errors.New("cannot find package")
+
// indexPackage returns an indexPackage constructed using the information in the ModuleIndex.
func (mi *ModuleIndex) indexPackage(path string) *indexPackage {
defer func() {
@@ -572,13 +696,15 @@ func (mi *ModuleIndex) indexPackage(path string) *indexPackage {
}()
offset, ok := mi.packages[path]
if !ok {
- return &indexPackage{error: fmt.Sprintf("cannot find package %q in:\n\t%s", path, filepath.Join(mi.modroot, path))}
+ return &indexPackage{error: fmt.Errorf("%w %q in:\n\t%s", errCannotFindPackage, path, filepath.Join(mi.modroot, path))}
}
// TODO(matloob): do we want to lock on the module index?
d := mi.od.decoderAt(offset)
rp := new(indexPackage)
- rp.error = d.string()
+ if errstr := d.string(); errstr != "" {
+ rp.error = errors.New(errstr)
+ }
rp.dir = d.string()
numSourceFiles := d.uint32()
rp.sourceFiles = make([]*sourceFile, numSourceFiles)
diff --git a/src/cmd/go/internal/modindex/scan.go b/src/cmd/go/internal/modindex/scan.go
index e40d3e0f53..d1f73dbb53 100644
--- a/src/cmd/go/internal/modindex/scan.go
+++ b/src/cmd/go/internal/modindex/scan.go
@@ -4,6 +4,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/fsys"
"cmd/go/internal/par"
+ "cmd/go/internal/str"
"encoding/json"
"errors"
"fmt"
@@ -54,10 +55,10 @@ func indexModule(modroot string) ([]byte, error) {
if !info.IsDir() {
return nil
}
- rel, err := filepath.Rel(modroot, path)
- if err != nil {
- panic(err)
+ if !str.HasFilePathPrefix(path, modroot) {
+ panic(fmt.Errorf("path %v in walk doesn't have modroot %v as prefix:", path, modroot))
}
+ rel := str.TrimFilePathPrefix(path, modroot)
packages = append(packages, importRaw(modroot, rel))
return nil
})
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 7b1bc732fc..0799fec35c 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -9,7 +9,6 @@ import (
"encoding/hex"
"errors"
"fmt"
- "internal/goroot"
"io/fs"
"os"
"path/filepath"
@@ -18,6 +17,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/modfetch"
+ "cmd/go/internal/modindex"
"cmd/go/internal/modinfo"
"cmd/go/internal/search"
@@ -39,7 +39,7 @@ func findStandardImportPath(path string) string {
panic("findStandardImportPath called with empty path")
}
if search.IsStandardImportPath(path) {
- if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
+ if modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
return filepath.Join(cfg.GOROOT, "src", path)
}
}
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index 22286e5e2d..f7810ca5c6 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"go/build"
- "internal/goroot"
"io/fs"
"os"
pathpkg "path"
@@ -281,7 +280,7 @@ func importFromModules(ctx context.Context, path string, rs *Requirements, mg *M
// Is the package in the standard library?
pathIsStd := search.IsStandardImportPath(path)
- if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
+ if pathIsStd && modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
for _, mainModule := range MainModules.Versions() {
if MainModules.InGorootSrc(mainModule) {
if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index cddb9f8067..4b90392d94 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -6,15 +6,22 @@ package modload
import (
"context"
+ "errors"
"fmt"
"io/fs"
"os"
+ "path"
"path/filepath"
+ "runtime"
+ "sort"
"strings"
+ "sync"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/imports"
+ "cmd/go/internal/modindex"
+ "cmd/go/internal/par"
"cmd/go/internal/search"
"golang.org/x/mod/module"
@@ -40,9 +47,15 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
treeCanMatch = search.TreeCanMatchPattern(m.Pattern())
}
+ var mu sync.Mutex
have := map[string]bool{
"builtin": true, // ignore pseudo-package that exists only for documentation
}
+ addPkg := func(p string) {
+ mu.Lock()
+ m.Pkgs = append(m.Pkgs, p)
+ mu.Unlock()
+ }
if !cfg.BuildContext.CgoEnabled {
have["runtime/cgo"] = true // ignore during walk
}
@@ -53,6 +66,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
pruneGoMod
)
+ q := par.NewQueue(runtime.GOMAXPROCS(0))
+
walkPkgs := func(root, importPathRoot string, prune pruning) {
root = filepath.Clean(root)
err := fsys.Walk(root, func(path string, fi fs.FileInfo, err error) error {
@@ -107,9 +122,11 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
if !have[name] {
have[name] = true
if isMatch(name) {
- if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo {
- m.Pkgs = append(m.Pkgs, name)
- }
+ q.Add(func() {
+ if _, _, err := scanDir(root, path, tags); err != imports.ErrNoGo {
+ addPkg(name)
+ }
+ })
}
}
@@ -123,6 +140,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
}
}
+ // Wait for all in-flight operations to complete before returning.
+ defer func() {
+ <-q.Idle()
+ sort.Strings(m.Pkgs) // sort everything we added for determinism
+ }()
+
if filter == includeStd {
walkPkgs(cfg.GOROOTsrc, "", pruneGoMod)
if treeCanMatch("cmd") {
@@ -165,6 +188,12 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
}
modPrefix = mod.Path
}
+ if mi, err := modindex.Get(root); err == nil {
+ walkFromIndex(mi, modPrefix, isMatch, treeCanMatch, tags, have, addPkg)
+ continue
+ } else if !errors.Is(err, modindex.ErrNotIndexed) {
+ m.AddError(err)
+ }
prune := pruneVendor
if isLocal {
@@ -176,6 +205,54 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
return
}
+// walkFromIndex matches packages in a module using the module index. modroot
+// is the module's root directory on disk, index is the ModuleIndex for the
+// module, and importPathRoot is the module's path prefix.
+func walkFromIndex(index *modindex.ModuleIndex, importPathRoot string, isMatch, treeCanMatch func(string) bool, tags, have map[string]bool, addPkg func(string)) {
+loopPackages:
+ for _, reldir := range index.Packages() {
+ // Avoid .foo, _foo, and testdata subdirectory trees.
+ p := reldir
+ for {
+ elem, rest, found := strings.Cut(p, string(filepath.Separator))
+ if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
+ continue loopPackages
+ }
+ if found && elem == "vendor" {
+ // Ignore this path if it contains the element "vendor" anywhere
+ // except for the last element (packages named vendor are allowed
+ // for historical reasons). Note that found is true when this
+ // isn't the last path element.
+ continue loopPackages
+ }
+ if !found {
+ // Didn't find the separator, so we're considering the last element.
+ break
+ }
+ p = rest
+ }
+
+ // Don't use GOROOT/src.
+ if reldir == "" && importPathRoot == "" {
+ continue
+ }
+
+ name := path.Join(importPathRoot, filepath.ToSlash(reldir))
+ if !treeCanMatch(name) {
+ continue
+ }
+
+ if !have[name] {
+ have[name] = true
+ if isMatch(name) {
+ if _, _, err := index.ScanDir(reldir, tags); err != imports.ErrNoGo {
+ addPkg(name)
+ }
+ }
+ }
+ }
+}
+
// MatchInModule identifies the packages matching the given pattern within the
// given module version, which does not need to be in the build list or module
// requirement graph.
diff --git a/src/cmd/go/internal/str/path.go b/src/cmd/go/internal/str/path.go
index a69e171f8c..c165b91785 100644
--- a/src/cmd/go/internal/str/path.go
+++ b/src/cmd/go/internal/str/path.go
@@ -66,3 +66,21 @@ func TrimFilePathPrefix(s, prefix string) string {
}
return trimmed[1:]
}
+
+// QuoteGlob returns s with all Glob metacharacters quoted.
+// We don't try to handle backslash here, as that can appear in a
+// file path on Windows.
+func QuoteGlob(s string) string {
+ if !strings.ContainsAny(s, `*?[]`) {
+ return s
+ }
+ var sb strings.Builder
+ for _, c := range s {
+ switch c {
+ case '*', '?', '[', ']':
+ sb.WriteByte('\\')
+ }
+ sb.WriteRune(c)
+ }
+ return sb.String()
+}
diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go
index d1fe36ec21..3ad0608725 100644
--- a/src/cmd/go/script_test.go
+++ b/src/cmd/go/script_test.go
@@ -170,7 +170,7 @@ func (ts *testScript) setup() {
"GOCACHE=" + testGOCACHE,
"GODEBUG=" + os.Getenv("GODEBUG"),
"GOEXE=" + cfg.ExeSuffix,
- "GOINDEX=true",
+ "GOEXPERIMENT=" + os.Getenv("GOEXPERIMENT"),
"GOOS=" + runtime.GOOS,
"GOPATH=" + filepath.Join(ts.workdir, "gopath"),
"GOPROXY=" + proxyURL,
diff --git a/src/cmd/go/testdata/script/embed_brackets.txt b/src/cmd/go/testdata/script/embed_brackets.txt
new file mode 100644
index 0000000000..7093a8497e
--- /dev/null
+++ b/src/cmd/go/testdata/script/embed_brackets.txt
@@ -0,0 +1,18 @@
+# issue 53314
+[windows] skip
+cd [pkg]
+go build
+
+-- [pkg]/go.mod --
+module m
+
+go 1.19
+-- [pkg]/x.go --
+package p
+
+import _ "embed"
+
+//go:embed t.txt
+var S string
+
+-- [pkg]//t.txt --
diff --git a/src/cmd/go/testdata/script/list_replace_absolute_windows.txt b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt
new file mode 100644
index 0000000000..b3ff2a7c2d
--- /dev/null
+++ b/src/cmd/go/testdata/script/list_replace_absolute_windows.txt
@@ -0,0 +1,38 @@
+# Test a replacement with an absolute path (so the path isn't
+# cleaned by having filepath.Abs called on it). This checks
+# whether the modindex logic cleans the modroot path before using
+# it.
+
+[!windows] skip
+[short] skip
+
+go run print_go_mod.go # use this program to write a go.mod with an absolute path
+cp stdout go.mod
+
+go list -modfile=go.mod all
+-- print_go_mod.go --
+//go:build ignore
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ work := os.Getenv("WORK")
+fmt.Printf(`module example.com/mod
+
+require b.com v0.0.0
+
+replace b.com => %s\gopath\src/modb
+`, work)
+}
+-- a.go --
+package a
+
+import _ "b.com/b"
+-- modb/go.mod --
+module b.com
+-- modb/b/b.go --
+package b
diff --git a/src/cmd/internal/metadata/main.go b/src/cmd/internal/metadata/main.go
index 157226e890..7478eec1c9 100644
--- a/src/cmd/internal/metadata/main.go
+++ b/src/cmd/internal/metadata/main.go
@@ -5,6 +5,12 @@
// Metadata prints basic system metadata to include in test logs. This is
// separate from cmd/dist so it does not need to build with the bootstrap
// toolchain.
+
+// This program is only used by cmd/dist. Add an "ignore" build tag so it
+// is not installed. cmd/dist does "go run main.go" directly.
+
+//go:build ignore
+
package main
import (
diff --git a/src/cmd/internal/notsha256/example_test.go b/src/cmd/internal/notsha256/example_test.go
deleted file mode 100644
index 06e9c379c9..0000000000
--- a/src/cmd/internal/notsha256/example_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2016 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 notsha256_test
-
-import (
- "crypto/sha256"
- "fmt"
- "io"
- "log"
- "os"
-)
-
-func ExampleSum256() {
- sum := sha256.Sum256([]byte("hello world\n"))
- fmt.Printf("%x", sum)
- // Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
-}
-
-func ExampleNew() {
- h := sha256.New()
- h.Write([]byte("hello world\n"))
- fmt.Printf("%x", h.Sum(nil))
- // Output: a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447
-}
-
-func ExampleNew_file() {
- f, err := os.Open("file.txt")
- if err != nil {
- log.Fatal(err)
- }
- defer f.Close()
-
- h := sha256.New()
- if _, err := io.Copy(h, f); err != nil {
- log.Fatal(err)
- }
-
- fmt.Printf("%x", h.Sum(nil))
-}
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index f12cb78fb8..cb2afeaa9a 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1854,6 +1854,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
}
for _, symn := range sym.ReadOnly {
symnStartValue := state.datsize
+ if len(state.data[symn]) != 0 {
+ symnStartValue = aligndatsize(state, symnStartValue, state.data[symn][0])
+ }
state.assignToSection(sect, symn, sym.SRODATA)
setCarrierSize(symn, state.datsize-symnStartValue)
if ctxt.HeadType == objabi.Haix {
@@ -1935,6 +1938,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
symn := sym.RelROMap[symnro]
symnStartValue := state.datsize
+ if len(state.data[symn]) != 0 {
+ symnStartValue = aligndatsize(state, symnStartValue, state.data[symn][0])
+ }
for _, s := range state.data[symn] {
outer := ldr.OuterSym(s)
diff --git a/src/cmd/link/internal/ld/decodesym.go b/src/cmd/link/internal/ld/decodesym.go
index a6ae202859..b0f4b87563 100644
--- a/src/cmd/link/internal/ld/decodesym.go
+++ b/src/cmd/link/internal/ld/decodesym.go
@@ -132,6 +132,15 @@ func decodetypeName(ldr *loader.Loader, symIdx loader.Sym, relocs *loader.Relocs
return string(data[1+nameLenLen : 1+nameLenLen+int(nameLen)])
}
+func decodetypeNameEmbedded(ldr *loader.Loader, symIdx loader.Sym, relocs *loader.Relocs, off int) bool {
+ r := decodeRelocSym(ldr, symIdx, relocs, int32(off))
+ if r == 0 {
+ return false
+ }
+ data := ldr.Data(r)
+ return data[0]&(1<<3) != 0
+}
+
func decodetypeFuncInType(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs, i int) loader.Sym {
uadd := commonsize(arch) + 4
if arch.PtrSize == 8 {
@@ -204,12 +213,18 @@ func decodetypeStructFieldType(ldr *loader.Loader, arch *sys.Arch, symIdx loader
return decodeRelocSym(ldr, symIdx, &relocs, int32(off+arch.PtrSize))
}
-func decodetypeStructFieldOffsAnon(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) int64 {
+func decodetypeStructFieldOffset(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) int64 {
off := decodetypeStructFieldArrayOff(ldr, arch, symIdx, i)
data := ldr.Data(symIdx)
return int64(decodeInuxi(arch, data[off+2*arch.PtrSize:], arch.PtrSize))
}
+func decodetypeStructFieldEmbedded(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, i int) bool {
+ off := decodetypeStructFieldArrayOff(ldr, arch, symIdx, i)
+ relocs := ldr.Relocs(symIdx)
+ return decodetypeNameEmbedded(ldr, symIdx, &relocs, off)
+}
+
// decodetypeStr returns the contents of an rtype's str field (a nameOff).
func decodetypeStr(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) string {
relocs := ldr.Relocs(symIdx)
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index 6ed9697aec..c42511ea3f 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -682,9 +682,9 @@ func (d *dwctxt) newtype(gotype loader.Sym) *dwarf.DWDie {
}
fld := d.newdie(die, dwarf.DW_ABRV_STRUCTFIELD, f)
d.newrefattr(fld, dwarf.DW_AT_type, d.defgotype(s))
- offsetAnon := decodetypeStructFieldOffsAnon(d.ldr, d.arch, gotype, i)
- newmemberoffsetattr(fld, int32(offsetAnon>>1))
- if offsetAnon&1 != 0 { // is embedded field
+ offset := decodetypeStructFieldOffset(d.ldr, d.arch, gotype, i)
+ newmemberoffsetattr(fld, int32(offset))
+ if decodetypeStructFieldEmbedded(d.ldr, d.arch, gotype, i) {
newattr(fld, dwarf.DW_AT_go_embedded_field, dwarf.DW_CLS_FLAG, 1, 0)
}
}
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index 19678adbd5..9a5d89a6f7 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -1463,12 +1463,12 @@ func (ctxt *Link) hostlink() {
// We force all symbol resolution to be done at program startup
// because lazy PLT resolution can use large amounts of stack at
// times we cannot allow it to do so.
- argv = append(argv, "-Wl,-znow")
+ argv = append(argv, "-Wl,-z,now")
// Do not let the host linker generate COPY relocations. These
// can move symbols out of sections that rely on stable offsets
// from the beginning of the section (like sym.STYPE).
- argv = append(argv, "-Wl,-znocopyreloc")
+ argv = append(argv, "-Wl,-z,nocopyreloc")
if buildcfg.GOOS == "android" {
// Use lld to avoid errors from default linker (issue #38838)
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index cc6a2c0e10..ee963bc366 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -475,16 +475,19 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
s = ldr.CreateSymForUpdate("type.*", 0)
s.SetType(sym.STYPE)
s.SetSize(0)
+ s.SetAlign(int32(ctxt.Arch.PtrSize))
symtype = s.Sym()
s = ldr.CreateSymForUpdate("typerel.*", 0)
s.SetType(sym.STYPERELRO)
s.SetSize(0)
+ s.SetAlign(int32(ctxt.Arch.PtrSize))
symtyperel = s.Sym()
} else {
s = ldr.CreateSymForUpdate("type.*", 0)
s.SetType(sym.STYPE)
s.SetSize(0)
+ s.SetAlign(int32(ctxt.Arch.PtrSize))
symtype = s.Sym()
symtyperel = s.Sym()
}
@@ -496,6 +499,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
s := ldr.CreateSymForUpdate(name, 0)
s.SetType(t)
s.SetSize(0)
+ s.SetAlign(int32(ctxt.Arch.PtrSize))
s.SetLocal(true)
setCarrierSym(t, s.Sym())
return s.Sym()
diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go
index bfe2e837c9..bc66252cfa 100644
--- a/src/cmd/link/internal/loadpe/ldpe.go
+++ b/src/cmd/link/internal/loadpe/ldpe.go
@@ -551,7 +551,13 @@ func (state *peLoaderState) readpesym(pesym *pe.COFFSymbol) (*loader.SymbolBuild
name = strings.TrimPrefix(name, "__imp_") // __imp_Name => Name
}
}
- if state.arch.Family == sys.I386 && name[0] == '_' {
+ // A note on the "_main" exclusion below: the main routine
+ // defined by the Go runtime is named "_main", not "main", so
+ // when reading references to _main from a host object we want
+ // to avoid rewriting "_main" to "main" in this specific
+ // instance. See #issuecomment-1143698749 on #35006 for more
+ // details on this problem.
+ if state.arch.Family == sys.I386 && name[0] == '_' && name != "_main" {
name = name[1:] // _Name => Name
}
}