| // Copyright 2024 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 methodsets |
| |
| // This is an internal test of [fingerprint] and [unify]. |
| // |
| // TODO(adonovan): avoid internal tests. |
| // Break fingerprint.go off into its own package? |
| |
| import ( |
| "go/types" |
| "testing" |
| |
| "golang.org/x/tools/go/packages" |
| "golang.org/x/tools/go/types/typeutil" |
| "golang.org/x/tools/internal/testenv" |
| "golang.org/x/tools/internal/testfiles" |
| "golang.org/x/tools/txtar" |
| ) |
| |
| // Test_fingerprint runs the fingerprint encoder, decoder, and printer |
| // on the types of all package-level symbols in gopls, and ensures |
| // that parse+print is lossless. |
| func Test_fingerprint(t *testing.T) { |
| if testing.Short() { |
| t.Skip("skipping slow test") |
| } |
| |
| cfg := &packages.Config{Mode: packages.NeedTypes} |
| pkgs, err := packages.Load(cfg, "std", "golang.org/x/tools/gopls/...") |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Record the fingerprint of each logical type (equivalence |
| // class of types.Types) and assert that they are all equal. |
| // (Non-tricky types only.) |
| var fingerprints typeutil.Map |
| |
| type eqclass struct { |
| class map[types.Type]bool |
| fp string |
| } |
| |
| for _, pkg := range pkgs { |
| switch pkg.Types.Path() { |
| case "unsafe", "builtin": |
| continue |
| } |
| scope := pkg.Types.Scope() |
| for _, name := range scope.Names() { |
| obj := scope.Lookup(name) |
| typ := obj.Type() |
| |
| if basic, ok := typ.(*types.Basic); ok && |
| basic.Info()&types.IsUntyped != 0 { |
| continue // untyped constant |
| } |
| |
| fp, tricky := fingerprint(typ) // check Type encoder doesn't panic |
| |
| // All equivalent (non-tricky) types have the same fingerprint. |
| if !tricky { |
| if prevfp, ok := fingerprints.At(typ).(string); !ok { |
| fingerprints.Set(typ, fp) |
| } else if fp != prevfp { |
| t.Errorf("inconsistent fingerprints for type %v:\n- old: %s\n- new: %s", |
| typ, fp, prevfp) |
| } |
| } |
| |
| tree := parseFingerprint(fp) // check parser doesn't panic |
| fp2 := sexprString(tree) // check formatter doesn't pannic |
| |
| // A parse+print round-trip should be lossless. |
| if fp != fp2 { |
| t.Errorf("%s: %v: parse+print changed fingerprint:\n"+ |
| "was: %s\ngot: %s\ntype: %v", |
| pkg.Fset.Position(obj.Pos()), obj, fp, fp2, typ) |
| } |
| } |
| } |
| } |
| |
| // Test_unify exercises the matching algorithm for generic types. |
| func Test_unify(t *testing.T) { |
| if testenv.Go1Point() < 24 { |
| testenv.NeedsGoExperiment(t, "aliastypeparams") // testenv.Go1Point() >= 24 implies aliastypeparams=1 |
| } |
| |
| const src = ` |
| -- go.mod -- |
| module example.com |
| go 1.24 |
| |
| -- a/a.go -- |
| package a |
| |
| type Int = int |
| type String = string |
| |
| // Eq.Equal matches casefold.Equal. |
| type Eq[T any] interface { Equal(T, T) bool } |
| type casefold struct{} |
| func (casefold) Equal(x, y string) bool |
| |
| // A matches AString. |
| type A[T any] = struct { x T } |
| type AString = struct { x string } |
| |
| // B matches anything! |
| type B[T any] = T |
| |
| func C1[T any](int, T, ...string) T { panic(0) } |
| func C2[U any](int, int, ...U) bool { panic(0) } |
| func C3(int, bool, ...string) rune |
| func C4(int, bool, ...string) |
| func C5(int, float64, bool, string) bool |
| |
| func DAny[T any](Named[T]) { panic(0) } |
| func DString(Named[string]) |
| func DInt(Named[int]) |
| |
| type Named[T any] struct { x T } |
| |
| func E1(byte) rune |
| func E2(uint8) int32 |
| func E3(int8) uint32 |
| ` |
| pkg := testfiles.LoadPackages(t, txtar.Parse([]byte(src)), "./a")[0] |
| scope := pkg.Types.Scope() |
| for _, test := range []struct { |
| a, b string |
| method string // optional field or method |
| want bool |
| }{ |
| {"Eq", "casefold", "Equal", true}, |
| {"A", "AString", "", true}, |
| {"A", "Eq", "", false}, // completely unrelated |
| {"B", "String", "", true}, |
| {"B", "Int", "", true}, |
| {"B", "A", "", true}, |
| {"C1", "C2", "", true}, // matches despite inconsistent substitution |
| {"C1", "C3", "", true}, |
| {"C1", "C4", "", false}, |
| {"C1", "C5", "", false}, |
| {"C2", "C3", "", false}, // intransitive (C1≡C2 ^ C1≡C3) |
| {"C2", "C4", "", false}, |
| {"C3", "C4", "", false}, |
| {"DAny", "DString", "", true}, |
| {"DAny", "DInt", "", true}, |
| {"DString", "DInt", "", false}, // different instantiations of Named |
| {"E1", "E2", "", true}, // byte and rune are just aliases |
| {"E2", "E3", "", false}, |
| } { |
| lookup := func(name string) types.Type { |
| obj := scope.Lookup(name) |
| if obj == nil { |
| t.Fatalf("Lookup %s failed", name) |
| } |
| if test.method != "" { |
| obj, _, _ = types.LookupFieldOrMethod(obj.Type(), true, pkg.Types, test.method) |
| if obj == nil { |
| t.Fatalf("Lookup %s.%s failed", name, test.method) |
| } |
| } |
| return obj.Type() |
| } |
| |
| a := lookup(test.a) |
| b := lookup(test.b) |
| |
| afp, _ := fingerprint(a) |
| bfp, _ := fingerprint(b) |
| |
| atree := parseFingerprint(afp) |
| btree := parseFingerprint(bfp) |
| |
| got := unify(atree, btree) |
| if got != test.want { |
| t.Errorf("a=%s b=%s method=%s: unify returned %t for these inputs:\n- %s\n- %s", |
| test.a, test.b, test.method, |
| got, sexprString(atree), sexprString(btree)) |
| } |
| } |
| } |