diff --git a/go/types/assignments.go b/go/types/assignments.go index c51fb360..9a9a6123 100644 --- a/go/types/assignments.go +++ b/go/types/assignments.go @@ -13,7 +13,7 @@ import ( "code.google.com/p/go.tools/go/exact" ) -// assignment reports whether x can be assigned to a variable of type 'T', +// assignment reports whether x can be assigned to a variable of type T, // if necessary by attempting to convert untyped values to the appropriate // type. If x.mode == invalid upon return, then assignment has already // issued an error message and the caller doesn't have to report another. @@ -148,8 +148,6 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type { if ident != nil && ident.Name == "_" { check.recordObject(ident, nil) // If the lhs is untyped, determine the default type. - // The spec is unclear about this, but gc appears to - // do this. // TODO(gri) This is still not correct (_ = 1<<1e3) typ := x.typ if isUntyped(typ) { @@ -188,8 +186,8 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type { return nil } - // spec: Each left-hand side operand must be addressable, a map index - // expression, or the blank identifier. Operands may be parenthesized. + // spec: "Each left-hand side operand must be addressable, a map index + // expression, or the blank identifier. Operands may be parenthesized." switch z.mode { case invalid: return nil @@ -214,137 +212,57 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type { // return expressions, and returnPos is the position of the return statement. func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { l := len(lhs) - r := len(rhs) - assert(l > 0) - - // If the lhs and rhs have corresponding expressions, - // treat each matching pair as an individual pair. - if l == r { - var x operand - for i, e := range rhs { - check.expr(&x, e) - check.initVar(lhs[i], &x) + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) + if l != r { + invalidateVars(lhs) + if returnPos.IsValid() { + check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) + return } + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) return } - // Otherwise, the rhs must be a single expression (possibly - // a function call returning multiple values, or a comma-ok - // expression). - if r == 1 { - // l > 1 - // Start with rhs so we have expression types - // for declarations with implicit types. - var x operand - rhs := rhs[0] - check.expr(&x, rhs) - if x.mode == invalid { - invalidateVars(lhs) - return + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.initVar(lhs[i], &x) } - - if t, ok := x.typ.(*Tuple); ok { - // function result - r = t.Len() - if l == r { - for i, lhs := range lhs { - x.mode = value - x.expr = rhs - x.typ = t.At(i).typ - check.initVar(lhs, &x) - } - return - } - } - - if !returnPos.IsValid() && (x.mode == mapindex || x.mode == commaok) && l == 2 { - // comma-ok expression (not permitted with return statements) - x.mode = value - t1 := check.initVar(lhs[0], &x) - - x.mode = value - x.expr = rhs - x.typ = Typ[UntypedBool] - t2 := check.initVar(lhs[1], &x) - - if t1 != nil && t2 != nil { - check.recordCommaOkTypes(rhs, t1, t2) - } - return - } - } - - invalidateVars(lhs) - - // lhs variables may be function result parameters (return statement); - // use rhs position for properly located error messages - if returnPos.IsValid() { - check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) + check.recordCommaOkTypes(rhs[0], a) return } - check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + + for i, lhs := range lhs { + get(&x, i) + check.initVar(lhs, &x) + } } func (check *checker) assignVars(lhs, rhs []ast.Expr) { l := len(lhs) - r := len(rhs) - assert(l > 0) - - // If the lhs and rhs have corresponding expressions, - // treat each matching pair as an individual pair. - if l == r { - var x operand - for i, e := range rhs { - check.expr(&x, e) - check.assignVar(lhs[i], &x) - } + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2) + if l != r { + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) return } - // Otherwise, the rhs must be a single expression (possibly - // a function call returning multiple values, or a comma-ok - // expression). - if r == 1 { - // l > 1 - var x operand - rhs := rhs[0] - check.expr(&x, rhs) - if x.mode == invalid { - return - } - - if t, ok := x.typ.(*Tuple); ok { - // function result - r = t.Len() - if l == r { - for i, lhs := range lhs { - x.mode = value - x.expr = rhs - x.typ = t.At(i).typ - check.assignVar(lhs, &x) - } - return - } - } - - if (x.mode == mapindex || x.mode == commaok) && l == 2 { - // comma-ok expression - x.mode = value - t1 := check.assignVar(lhs[0], &x) - - x.mode = value - x.expr = rhs - x.typ = Typ[UntypedBool] - t2 := check.assignVar(lhs[1], &x) - - if t1 != nil && t2 != nil { - check.recordCommaOkTypes(rhs, t1, t2) - } - return + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.assignVar(lhs[i], &x) } + check.recordCommaOkTypes(rhs[0], a) + return } - check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + for i, lhs := range lhs { + get(&x, i) + check.assignVar(lhs, &x) + } } func (check *checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) { diff --git a/go/types/builtins.go b/go/types/builtins.go index 312ef39a..cacc2c94 100644 --- a/go/types/builtins.go +++ b/go/types/builtins.go @@ -33,7 +33,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b switch id { default: // make argument getter - arg, nargs = unpack(func(x *operand, i int) { check.expr(x, call.Args[i]) }, nargs, false) + arg, nargs, _ = unpack(func(x *operand, i int) { check.expr(x, call.Args[i]) }, nargs, false) // evaluate first argument, if present if nargs > 0 { arg(x, 0) diff --git a/go/types/call.go b/go/types/call.go index d8b0d5c3..43109786 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -60,7 +60,7 @@ func (check *checker) call(x *operand, e *ast.CallExpr) exprKind { return statement } - arg, n := unpack(func(x *operand, i int) { check.expr(x, e.Args[i]) }, len(e.Args), false) + arg, n, _ := unpack(func(x *operand, i int) { check.expr(x, e.Args[i]) }, len(e.Args), false) check.arguments(x, e, sig, arg, n) // determine result @@ -90,8 +90,6 @@ func (check *checker) use(list []ast.Expr) { } } -// TODO(gri) use unpack for assignment checking as well. - // A getter sets x as the i'th operand, where 0 <= i < n and n is the total // number of operands (context-specific, and maintained elsewhere). A getter // type-checks the i'th operand; the details of the actual check are getter- @@ -101,17 +99,20 @@ type getter func(x *operand, i int) // unpack takes a getter get and a number of operands n. If n == 1 and the // first operand is a function call, or a comma,ok expression and allowCommaOk // is set, the result is a new getter and operand count providing access to the -// function results, or comma,ok values, respectively. In all other cases, the -// incoming getter and operand count are returned unchanged. In other words, -// if there's exactly one operand that - after type-checking by calling get - -// stands for multiple operands, the resulting getter provides access to those -// operands instead. +// function results, or comma,ok values, respectively. The third result value +// reports if it is indeed the comma,ok case. In all other cases, the incoming +// getter and operand count are returned unchanged, and the third result value +// is false. +// +// In other words, if there's exactly one operand that - after type-checking by +// calling get - stands for multiple operands, the resulting getter provides access +// to those operands instead. // // Note that unpack may call get(..., 0); but if the result getter is called // at most once for a given operand index i (including i == 0), that operand // is guaranteed to cause only one call of the incoming getter with that i. // -func unpack(get getter, n int, allowCommaOk bool) (getter, int) { +func unpack(get getter, n int, allowCommaOk bool) (getter, int, bool) { if n == 1 { // possibly result of an n-valued function call or comma,ok value var x0 operand @@ -121,9 +122,8 @@ func unpack(get getter, n int, allowCommaOk bool) (getter, int) { if i != 0 { unreachable() } - // i == 0 x.mode = invalid - }, 1 + }, 1, false } if t, ok := x0.typ.(*Tuple); ok { @@ -132,26 +132,18 @@ func unpack(get getter, n int, allowCommaOk bool) (getter, int) { x.mode = value x.expr = x0.expr x.typ = t.At(i).typ - }, t.Len() + }, t.Len(), false } if x0.mode == mapindex || x0.mode == commaok { // comma-ok value if allowCommaOk { + a := [2]Type{x0.typ, Typ[UntypedBool]} return func(x *operand, i int) { - switch i { - case 0: - x.mode = value - x.expr = x0.expr - x.typ = x0.typ - case 1: - x.mode = value - x.expr = x0.expr - x.typ = Typ[UntypedBool] - default: - unreachable() - } - }, 2 + x.mode = value + x.expr = x0.expr + x.typ = a[i] + }, 2, true } x0.mode = value } @@ -162,16 +154,16 @@ func unpack(get getter, n int, allowCommaOk bool) (getter, int) { unreachable() } *x = x0 - }, 1 + }, 1, false } // zero or multiple values - return get, n + return get, n, false } // arguments checks argument passing for the call with the given signature. // The arg function provides the operand for the i'th argument. -func (check *checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg func(*operand, int), n int) { +func (check *checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg getter, n int) { passSlice := false if call.Ellipsis.IsValid() { // last argument is of the form x... diff --git a/go/types/check.go b/go/types/check.go index 86d239c0..3c14f3e3 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -121,15 +121,19 @@ func (check *checker) recordBuiltinType(f ast.Expr, sig *Signature) { } } -func (check *checker) recordCommaOkTypes(x ast.Expr, t1, t2 Type) { - assert(x != nil && isTyped(t1) && isTyped(t2) && isBoolean(t2)) +func (check *checker) recordCommaOkTypes(x ast.Expr, a [2]Type) { + assert(x != nil) + if a[0] == nil || a[1] == nil { + return + } + assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1])) if m := check.Types; m != nil { for { assert(m[x] != nil) // should have been recorded already pos := x.Pos() m[x] = NewTuple( - NewVar(pos, check.pkg, "", t1), - NewVar(pos, check.pkg, "", t2), + NewVar(pos, check.pkg, "", a[0]), + NewVar(pos, check.pkg, "", a[1]), ) // if x is a parenthesized expression (p.X), update p.X p, _ := x.(*ast.ParenExpr) diff --git a/go/types/self_test.go b/go/types/self_test.go index 6a54e9a6..e7342de7 100644 --- a/go/types/self_test.go +++ b/go/types/self_test.go @@ -5,6 +5,7 @@ package types_test import ( + "flag" "fmt" "go/ast" "go/parser" @@ -17,6 +18,8 @@ import ( . "code.google.com/p/go.tools/go/types" ) +var benchmark = flag.Bool("b", false, "run benchmarks") + func TestSelf(t *testing.T) { fset := token.NewFileSet() files, err := pkgFiles(fset, ".") @@ -27,9 +30,8 @@ func TestSelf(t *testing.T) { _, err = Check("go/types", fset, files) if err != nil { // Importing go.tools/go/exact doensn't work in the - // build dashboard environment at the moment. Don't - // report an error for now so that the build remains - // green. + // build dashboard environment. Don't report an error + // for now so that the build remains green. // TODO(gri) fix this t.Log(err) // replace w/ t.Fatal eventually return @@ -37,8 +39,8 @@ func TestSelf(t *testing.T) { } func TestBenchmark(t *testing.T) { - if testing.Short() { - return // skip benchmark in short mode + if !*benchmark { + return } // We're not using testing's benchmarking mechanism directly @@ -46,13 +48,13 @@ func TestBenchmark(t *testing.T) { for _, p := range []string{"types", "exact", "gcimporter"} { path := filepath.Join("..", p) - benchmark(t, path, false) - benchmark(t, path, true) + runbench(t, path, false) + runbench(t, path, true) fmt.Println() } } -func benchmark(t *testing.T, path string, ignoreFuncBodies bool) { +func runbench(t *testing.T, path string, ignoreFuncBodies bool) { fset := token.NewFileSet() files, err := pkgFiles(fset, path) if err != nil { diff --git a/go/types/testdata/stmt0.src b/go/types/testdata/stmt0.src index 9ba0b8e1..12bb2d9b 100644 --- a/go/types/testdata/stmt0.src +++ b/go/types/testdata/stmt0.src @@ -21,7 +21,7 @@ func assignments0() (int, int) { a = f0 /* ERROR "used as value" */ () a = f1() - a = f2 /* ERROR "used as single value" */ () + a = f2 /* ERROR "assignment count mismatch" */ () a, b = f2() a, b, c = f2 /* ERROR "assignment count mismatch" */ () a, b, c = f3()