diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f452563..ee63dc7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,6 +105,32 @@ the hash of the operation's inputs, and the last component is the *content ID*, the hash of the operation's output. For more, read [the docs in buildid.go](https://github.com/golang/go/blob/master/src/cmd/go/internal/work/buildid.go) +### Bundled x/tools packages + +`bundled_typeutil.go` and `bundled_typeparams.go` are modified copies of x/tools packages. +We need to modify [typeutil.Hasher](https://pkg.go.dev/golang.org/x/tools/go/types/typeutil#Hasher) +so that it doesn't include struct field tags in the hash algorithm, +given that struct field tags do not affect type identity. + +The typeutil package imports the internal `typeparams` package, so we must bundle that too, +and replace the import with references to the bundled typeparams file. + +Our patches are marked via `// NOTE(garble)` and are done by hand. +We also remove the `//go:generate` lines added by `bundle` +because re-running the tool will undo the manual edits. + +Finally, we also remove any bundled API we don't need, that is, +anything which is not reachable from the bundled `typeutil_Hasher` type. +We also ensure that `staticcheck` does not report any unused code. + +To update these files to a newer x/tools version to gain upstream fixes: + +* `go get golang.org/x/tools@latest` +* `go tool bundle -o bundled_typeutil.go golang.org/x/tools/go/types/typeutil` +* `go tool bundle -o bundled_typeparams.go golang.org/x/tools/internal/typeparams` +* re-apply the changes mentioned above +* inspect the diff to ensure the changes look reasonable and we haven't lost any of our edits + ### Benchmarking A build benchmark is available, to be able to measure the cost of builing a diff --git a/bundled_typeparams.go b/bundled_typeparams.go index 32f29c6..928998e 100644 --- a/bundled_typeparams.go +++ b/bundled_typeparams.go @@ -1,16 +1,20 @@ -// Originally bundled from golang.org/x/tools/internal/typeparams@v0.29.0, -// as it is used by x/tools/go/types/typeutil and is an internal package. +// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md. + +//lint:file-ignore ST1012 NOTE(garble): err var names get a new prefix package main import ( - "bytes" "errors" "fmt" "go/types" + "os" + "strings" ) -var errEmptyTypeSet = errors.New("empty type set") +const typeparams_debug = false + +var typeparams_ErrEmptyTypeSet = errors.New("empty type set") // InterfaceTermSet computes the normalized terms for a constraint interface, // returning an error if the term set cannot be computed or is empty. In the @@ -38,7 +42,7 @@ func typeparams_computeTermSet(typ types.Type) ([]*types.Term, error) { return nil, err } if tset.terms.isEmpty() { - return nil, errEmptyTypeSet + return nil, typeparams_ErrEmptyTypeSet } if tset.terms.isAll() { return nil, nil @@ -60,11 +64,26 @@ type typeparams_termSet struct { terms typeparams_termlist } +func typeparams_indentf(depth int, format string, args ...any) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typeparams_termSet, depth int) (res *typeparams_termSet, err error) { if t == nil { panic("nil type") } + if typeparams_debug { + typeparams_indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + typeparams_indentf(depth, "=> %s", err) + } else { + typeparams_indentf(depth, "=> %s", res.terms.String()) + } + }() + } + const maxTermCount = 100 if tset, ok := seen[t]; ok { if !tset.complete { @@ -85,8 +104,7 @@ func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typepa // The term set of an interface is the intersection of the term sets of its // embedded types. tset.terms = typeparams_allTermlist - for i := range u.NumEmbeddeds() { - embedded := u.EmbeddedType(i) + for embedded := range u.EmbeddedTypes() { if _, ok := embedded.Underlying().(*types.TypeParam); ok { return nil, fmt.Errorf("invalid embedded type %T", embedded) } @@ -99,8 +117,7 @@ func typeparams_computeTermSetInternal(t types.Type, seen map[types.Type]*typepa case *types.Union: // The term set of a union is the union of term sets of its terms. tset.terms = nil - for i := range u.Len() { - t := u.Term(i) + for t := range u.Terms() { var terms typeparams_termlist switch t.Type().Underlying().(type) { case *types.Interface: @@ -156,15 +173,18 @@ type typeparams_termlist []*typeparams_term // It is in normal form. var typeparams_allTermlist = typeparams_termlist{new(typeparams_term)} +// termSep is the separator used between individual terms. +const typeparams_termSep = " | " + // String prints the termlist exactly (without normalization). func (xl typeparams_termlist) String() string { if len(xl) == 0 { return "∅" } - var buf bytes.Buffer + var buf strings.Builder for i, x := range xl { if i > 0 { - buf.WriteString(" | ") + buf.WriteString(typeparams_termSep) } buf.WriteString(x.String()) } @@ -342,6 +362,9 @@ func (x *typeparams_term) intersect(y *typeparams_term) *typeparams_term { // disjoint reports whether x ∩ y == ∅. // x.typ and y.typ must not be nil. func (x *typeparams_term) disjoint(y *typeparams_term) bool { + if typeparams_debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } ux := x.typ if y.tilde { ux = typeparams_under(ux) diff --git a/bundled_typeutil.go b/bundled_typeutil.go index 8e0ee51..ac03dbd 100644 --- a/bundled_typeutil.go +++ b/bundled_typeutil.go @@ -1,12 +1,11 @@ -// Originally bundled from golang.org/x/tools/go/types/typeutil@v0.29.0. -// Edited to just keep the hasher API in place, removing the use of internal/typeparams, -// and removed the inclusion of struct field tags in the hasher. +// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md. package main import ( "fmt" "go/types" + _ "unsafe" ) // -- Hasher -- @@ -37,7 +36,7 @@ type typeutil_hasher struct{ inGenericSig bool } // hashString computes the Fowler–Noll–Vo hash of s. func typeutil_hashString(s string) uint32 { var h uint32 - for i := range len(s) { + for i := 0; i < len(s); i++ { h ^= uint32(s[i]) h *= 16777619 } @@ -67,7 +66,7 @@ func (h typeutil_hasher) hash(t types.Type) uint32 { if f.Anonymous() { hash += 8861 } - // NOTE: we must not hash struct field tags, as they do not affect type identity. + // NOTE(garble): we must not hash struct field tags, as they do not affect type identity. // hash += typeutil_hashString(t.Tag(i)) hash += typeutil_hashString(f.Name()) // (ignore f.Pkg) hash += h.hash(f.Type()) @@ -84,10 +83,13 @@ func (h typeutil_hasher) hash(t types.Type) uint32 { } tparams := t.TypeParams() - for i := range tparams.Len() { - h.inGenericSig = true - tparam := tparams.At(i) - hash += 7 * h.hash(tparam.Constraint()) + if n := tparams.Len(); n > 0 { + h.inGenericSig = true // affects constraints, params, and results + + for i := range n { + tparam := tparams.At(i) + hash += 7 * h.hash(tparam.Constraint()) + } } return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results()) @@ -129,8 +131,7 @@ func (h typeutil_hasher) hash(t types.Type) uint32 { case *types.Named: hash := h.hashTypeName(t.Obj()) targs := t.TypeArgs() - for i := range targs.Len() { - targ := targs.At(i) + for targ := range targs.Types() { hash += 2 * h.hash(targ) } return hash @@ -200,21 +201,12 @@ func (h typeutil_hasher) hashTypeParam(t *types.TypeParam) uint32 { // hashTypeName hashes the pointer of tname. func (typeutil_hasher) hashTypeName(tname *types.TypeName) uint32 { - // NOTE: we must not hash any pointers, as garble is a toolexec tool - // so by nature it uses multiple processes. + // NOTE(garble): we must not hash any pointers, + // as garble is a toolexec tool so by nature it uses multiple processes. return typeutil_hashString(tname.Name()) // Since types.Identical uses == to compare TypeNames, // the Hash function uses maphash.Comparable. - // TODO(adonovan): or will, when it becomes available in go1.24. - // In the meantime we use the pointer's numeric value. - // - // hash := maphash.Comparable(theSeed, tname) - // - // (Another approach would be to hash the name and package - // path, and whether or not it is a package-level typename. It - // is rare for a package to define multiple local types with - // the same name.) - // hash := uintptr(unsafe.Pointer(tname)) + // hash := maphash.Comparable(typeutil_theSeed, tname) // return uint32(hash ^ (hash >> 32)) }