From c212b356b8db7799c816565c2d7b70124f88e7a7 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 7 May 2014 18:35:43 -0400 Subject: [PATCH] go.tools/go/types: add API test of Selection mechanism. Also: fixed a crash in (*Selection).String() for FieldVal of a non-function type. LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/93830043 --- go/types/api_test.go | 167 ++++++++++++++++++++++++++++++++++++++++++ go/types/selection.go | 14 +++- 2 files changed, 180 insertions(+), 1 deletion(-) diff --git a/go/types/api_test.go b/go/types/api_test.go index edd70f6668..b32c725a5a 100644 --- a/go/types/api_test.go +++ b/go/types/api_test.go @@ -469,3 +469,170 @@ func TestFiles(t *testing.T) { } } } + +func TestSelection(t *testing.T) { + selections := make(map[*ast.SelectorExpr]*Selection) + + fset := token.NewFileSet() + conf := Config{ + Packages: make(map[string]*Package), + Import: func(imports map[string]*Package, path string) (*Package, error) { + return imports[path], nil + }, + } + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path+".go", src, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, fset, []*ast.File{f}, &Info{Selections: selections}) + if err != nil { + t.Fatal(err) + } + conf.Packages[path] = pkg + } + + libSrc := ` +package lib +type T float64 +const C T = 3 +var V T +func F() {} +func (T) M() {} +` + mainSrc := ` +package main +import "lib" + +type A struct { + *B + C +} + +type B struct { + b int +} + +func (B) f(int) + +type C struct { + c int +} + +func (C) g() +func (*C) h() + +func main() { + // qualified identifiers + var _ lib.T + _ = lib.C + _ = lib.F + _ = lib.V + _ = lib.T.M + + // fields + _ = A{}.B + _ = new(A).B + + _ = A{}.C + _ = new(A).C + + _ = A{}.b + _ = new(A).b + + _ = A{}.c + _ = new(A).c + + // methods + _ = A{}.f + _ = new(A).f + _ = A{}.g + _ = new(A).g + _ = new(A).h + + _ = B{}.f + _ = new(B).f + + _ = C{}.g + _ = new(C).g + _ = new(C).h + + // method expressions + _ = A.f + _ = (*A).f + _ = B.f + _ = (*B).f +}` + + // TODO(adonovan): assert all map entries are used at least once. + wantOut := map[string][2]string{ + "lib.T": {"qualified ident type lib.T float64", ".[]"}, + "lib.C": {"qualified ident const lib.C lib.T", ".[]"}, + "lib.F": {"qualified ident func lib.F()", ".[]"}, + "lib.V": {"qualified ident var lib.V lib.T", ".[]"}, + "lib.T.M": {"method expr (lib.T) M(lib.T)", ".[0]"}, + + "A{}.B": {"field (main.A) B *main.B", ".[0]"}, + "new(A).B": {"field (*main.A) B *main.B", "->[0]"}, + "A{}.C": {"field (main.A) C main.C", ".[1]"}, + "new(A).C": {"field (*main.A) C main.C", "->[1]"}, + "A{}.b": {"field (main.A) b int", "->[0 0]"}, + "new(A).b": {"field (*main.A) b int", "->[0 0]"}, + "A{}.c": {"field (main.A) c int", ".[1 0]"}, + "new(A).c": {"field (*main.A) c int", "->[1 0]"}, + + "A{}.f": {"method (main.A) f(int)", "->[0 0]"}, + "new(A).f": {"method (*main.A) f(int)", "->[0 0]"}, + "A{}.g": {"method (main.A) g()", ".[1 0]"}, + "new(A).g": {"method (*main.A) g()", "->[1 0]"}, + "new(A).h": {"method (*main.A) h()", "->[1 1]"}, + "B{}.f": {"method (main.B) f(int)", ".[0]"}, + "new(B).f": {"method (*main.B) f(int)", "->[0]"}, + "C{}.g": {"method (main.C) g()", ".[0]"}, + "new(C).g": {"method (*main.C) g()", "->[0]"}, + "new(C).h": {"method (*main.C) h()", "->[1]"}, + + "A.f": {"method expr (main.A) f(main.A, int)", "->[0 0]"}, + "(*A).f": {"method expr (*main.A) f(*main.A, int)", "->[0 0]"}, + "B.f": {"method expr (main.B) f(main.B, int)", ".[0]"}, + "(*B).f": {"method expr (*main.B) f(*main.B, int)", "->[0]"}, + } + + makePkg("lib", libSrc) + makePkg("main", mainSrc) + + for e, sel := range selections { + sel.String() // assertion: must not panic + + start := fset.Position(e.Pos()).Offset + end := fset.Position(e.End()).Offset + syntax := mainSrc[start:end] // (all SelectorExprs are in main, not lib) + + direct := "." + if sel.Indirect() { + direct = "->" + } + got := [2]string{ + sel.String(), + fmt.Sprintf("%s%v", direct, sel.Index()), + } + want := wantOut[syntax] + if want != got { + t.Errorf("%s: got %q; want %q", syntax, got, want) + } + + // We must explicitly assert properties of the + // Signature's receiver since it doesn't participate + // in Identical() or String(). + sig, _ := sel.Type().(*Signature) + if sel.Kind() == MethodVal { + got := sig.Recv().Type() + want := sel.Recv() + if !Identical(got, want) { + t.Errorf("%s: Recv() = %s, want %s", got, want) + } + } else if sig != nil && sig.Recv() != nil { + t.Error("%s: signature has receiver %s", sig, sig.Recv().Type()) + } + } +} diff --git a/go/types/selection.go b/go/types/selection.go index d1dd1c81d9..6a873d5a11 100644 --- a/go/types/selection.go +++ b/go/types/selection.go @@ -123,6 +123,12 @@ func (s *Selection) String() string { return SelectionString(nil, s) } // Type names are printed package-qualified // only if they do not belong to this package. // +// Examples: +// "field (T) f int" +// "method (T) f(X) Y" +// "method expr (T) f(X) Y" +// "qualified ident var math.Pi float64" +// func SelectionString(this *Package, s *Selection) string { var k string switch s.kind { @@ -142,6 +148,12 @@ func SelectionString(this *Package, s *Selection) string { buf.WriteByte('(') WriteType(&buf, this, s.Recv()) fmt.Fprintf(&buf, ") %s", s.obj.Name()) - WriteSignature(&buf, this, s.Type().(*Signature)) + if T := s.Type(); s.kind == FieldVal { + // TODO(adonovan): use "T.f" not "(T) f". + buf.WriteByte(' ') + WriteType(&buf, this, T) + } else { + WriteSignature(&buf, this, T.(*Signature)) + } return buf.String() }