From e5cfd92deb348030b7cfe7846ac2302084443029 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 31 Jul 2013 09:33:46 -0700 Subject: [PATCH] go.tools/go/types: record comma-ok results as tuples - added corresponding api tests - support tuple comparison with IsIdentical This CL will require some adjustments to SSA. R=adonovan CC=golang-dev https://golang.org/cl/12024046 --- go/types/api_test.go | 87 +++++++++++++++++++++++++++++++++++++++++ go/types/assignments.go | 16 ++++++-- go/types/check.go | 12 ++++++ go/types/errors.go | 5 ++- go/types/predicates.go | 17 +++++--- go/types/types_test.go | 2 +- 6 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 go/types/api_test.go diff --git a/go/types/api_test.go b/go/types/api_test.go new file mode 100644 index 00000000..8faa4028 --- /dev/null +++ b/go/types/api_test.go @@ -0,0 +1,87 @@ +// Copyright 2013 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. + +// TODO(gri) This file needs to be expanded significantly. + +package types + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "testing" +) + +func pkgFor(path string, source string, info *Info) (*Package, error) { + fset = token.NewFileSet() + f, err := parser.ParseFile(fset, path, source, 0) + if err != nil { + return nil, err + } + + var conf Config + pkg, err := conf.Check(path, fset, []*ast.File{f}, info) + if err != nil { + return nil, err + } + + return pkg, nil +} + +func TestCommaOkTypes(t *testing.T) { + var tests = []struct { + src string + expr string // comma-ok expression string + typ Type // type of first comma-ok value + }{ + {`package p; var x interface{}; var _, _ = x.(int)`, + `x.(int)`, + Typ[Int], + }, + {`package p; var m map[string]complex128; var _, _ = m["foo"]`, + `m["foo"]`, + Typ[Complex128], + }, + {`package p; var c chan string; var _, _ = <-c`, + `<-c`, + Typ[String], + }, + } + + for i, test := range tests { + path := fmt.Sprintf("CommaOk%d", i) + + // type-check + info := Info{Types: make(map[ast.Expr]Type)} + _, err := pkgFor(path, test.src, &info) + if err != nil { + t.Error(err) + continue + } + + // look for comma-ok expression type + var typ Type + for e, t := range info.Types { + if exprString(e) == test.expr { + typ = t + break + } + } + if typ == nil { + t.Errorf("%s: no type found for %s", path, test.expr) + continue + } + + // check that type is correct + got, _ := typ.(*Tuple) + want := NewTuple( + NewVar(token.NoPos, nil, "", test.typ), + NewVar(token.NoPos, nil, "", Typ[UntypedBool]), + ) + if got == nil || !identicalTuples(got, want) { + t.Errorf("%s: got %s; want %s", path, got, want) + } + } +} diff --git a/go/types/assignments.go b/go/types/assignments.go index b76b2da4..224e8b3b 100644 --- a/go/types/assignments.go +++ b/go/types/assignments.go @@ -159,7 +159,8 @@ func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) { // Start with rhs so we have expression types // for declarations with implicit types. var x operand - check.expr(&x, rhs[0]) + rhs := rhs[0] + check.expr(&x, rhs) if x.mode == invalid { invalidateVars(lhs) return @@ -171,7 +172,7 @@ func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) { if l == r { for i, lhs := range lhs { x.mode = value - x.expr = rhs[0] + x.expr = rhs x.typ = t.At(i).typ check.initVar(lhs, &x) } @@ -181,10 +182,13 @@ func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, allowCommaOk bool) { if allowCommaOk && x.mode == valueok && l == 2 { // comma-ok expression + check.recordCommaOkType(rhs, x.typ) + x.mode = value check.initVar(lhs[0], &x) x.mode = value + x.expr = rhs x.typ = Typ[UntypedBool] check.initVar(lhs[1], &x) return @@ -219,7 +223,8 @@ func (check *checker) assignVars(lhs, rhs []ast.Expr) { if r == 1 { // l > 1 var x operand - check.expr(&x, rhs[0]) + rhs := rhs[0] + check.expr(&x, rhs) if x.mode == invalid { return } @@ -230,7 +235,7 @@ func (check *checker) assignVars(lhs, rhs []ast.Expr) { if l == r { for i, lhs := range lhs { x.mode = value - x.expr = rhs[0] + x.expr = rhs x.typ = t.At(i).typ check.assignVar(lhs, &x) } @@ -240,10 +245,13 @@ func (check *checker) assignVars(lhs, rhs []ast.Expr) { if x.mode == valueok && l == 2 { // comma-ok expression + check.recordCommaOkType(rhs, x.typ) + x.mode = value check.assignVar(lhs[0], &x) x.mode = value + x.expr = rhs x.typ = Typ[UntypedBool] check.assignVar(lhs[1], &x) return diff --git a/go/types/check.go b/go/types/check.go index ce08c342..529b95bc 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -82,6 +82,18 @@ func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) } } +func (check *checker) recordCommaOkType(x ast.Expr, typ Type) { + assert(x != nil && typ != nil) + if m := check.Types; m != nil { + assert(m[x] != nil) // should have been recorded already + pos := x.Pos() + m[x] = NewTuple( + NewVar(pos, check.pkg, "", typ), + NewVar(pos, check.pkg, "", Typ[UntypedBool]), + ) + } +} + func (check *checker) recordObject(id *ast.Ident, obj Object) { assert(id != nil) if m := check.Objects; m != nil { diff --git a/go/types/errors.go b/go/types/errors.go index 3f7f97c2..fe51597b 100644 --- a/go/types/errors.go +++ b/go/types/errors.go @@ -133,7 +133,10 @@ func writeExpr(buf *bytes.Buffer, expr ast.Expr) { case *ast.TypeAssertExpr: writeExpr(buf, x.X) - buf.WriteString(".(...)") + buf.WriteString(".(") + // TODO(gri) expand writeExpr so that types are not handled by default case + writeExpr(buf, x.Type) + buf.WriteByte(')') case *ast.CallExpr: writeExpr(buf, x.Fun) diff --git a/go/types/predicates.go b/go/types/predicates.go index 84286acb..778bf88f 100644 --- a/go/types/predicates.go +++ b/go/types/predicates.go @@ -146,6 +146,13 @@ func IsIdentical(x, y Type) bool { return IsIdentical(x.base, y.base) } + case *Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*Tuple); ok { + return identicalTuples(x, y) + } + case *Signature: // Two function types are identical if they have the same number of parameters // and result values, corresponding parameter and result types are identical, @@ -153,8 +160,8 @@ func IsIdentical(x, y Type) bool { // names are not required to match. if y, ok := y.(*Signature); ok { return x.isVariadic == y.isVariadic && - identicalTypes(x.params, y.params) && - identicalTypes(x.results, y.results) + identicalTuples(x.params, y.params) && + identicalTuples(x.results, y.results) } case *Interface: @@ -189,9 +196,9 @@ func IsIdentical(x, y Type) bool { return false } -// identicalTypes returns true if both lists a and b have the -// same length and corresponding objects have identical types. -func identicalTypes(a, b *Tuple) bool { +// identicalTuples returns true if both tuples a and b have the +// same length and corresponding elements have identical types. +func identicalTuples(a, b *Tuple) bool { if a.Len() != b.Len() { return false } diff --git a/go/types/types_test.go b/go/types/types_test.go index a3dd631f..548b4006 100644 --- a/go/types/types_test.go +++ b/go/types/types_test.go @@ -148,7 +148,7 @@ var testExprs = []testEntry{ {"func(a, b int) []int {}(1, 2)[x]", "(func literal)(1, 2)[x]"}, {"[]int{1, 2, 3}", "(composite literal)"}, {"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"}, - {"i.([]string)", "i.(...)"}, + {"i.([]string)", "i.()"}, } func TestExprs(t *testing.T) {