From 0c4522091728c38bf1b9f0ddb94376d08a2be22a Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 11 Oct 2013 10:04:10 -0700 Subject: [PATCH] go.tools/go/types: record call-site specific types of built-in functions Given a built-in call f(args), Info.Types now maps f to the call-site specific type of f (by looking at the argument types) if the built-in call is not producing a constant (at typecheck time) result. If the result is constant, the recorded type is invalid (a back-end won't need it). R=adonovan CC=golang-dev https://golang.org/cl/14598045 --- go/types/api.go | 5 + go/types/builtins.go | 115 ++++++++++++++++--- go/types/builtins_test.go | 194 +++++++++++++++++++++++++++++++++ go/types/check.go | 18 +++ go/types/check_test.go | 9 +- go/types/issues_test.go | 2 +- go/types/testdata/builtins.src | 37 ++++++- go/types/universe.go | 3 + 8 files changed, 355 insertions(+), 28 deletions(-) create mode 100644 go/types/builtins_test.go diff --git a/go/types/api.go b/go/types/api.go index de387738d5..78b4239746 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -110,6 +110,11 @@ type Config struct { type Info struct { // Types maps expressions to their types. Identifiers on the // lhs of declarations are collected in Objects, not Types. + // + // For an expression denoting a predeclared built-in function + // the recorded signature is call-site specific. If the call + // result is not a constant, the recorded type is an argument- + // specific signature. Otherwise, the recorded type is invalid. Types map[ast.Expr]Type // Values maps constant expressions to their values. diff --git a/go/types/builtins.go b/go/types/builtins.go index cc70e2c28e..a13db14d5e 100644 --- a/go/types/builtins.go +++ b/go/types/builtins.go @@ -87,6 +87,15 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b return } if isString(x.typ) { + if check.Types != nil { + // TODO(gri, adonovan) change this to: + // + // sig := makeSig(S, S, NewSlice(Typ[Byte])) + // sig.isVariadic = true + // + // once ssa has been adjusted + check.recordBuiltinType(call.Fun, makeSig(S, S, x.typ)) + } x.mode = value x.typ = S break @@ -106,18 +115,23 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } arg(x, i) }, nargs) + // ok to continue even if check.arguments reported errors x.mode = value x.typ = S + if check.Types != nil { + check.recordBuiltinType(call.Fun, sig) + } case _Cap, _Len: // cap(x) // len(x) mode := invalid + var typ Type var val exact.Value - switch typ := implicitArrayDeref(x.typ.Underlying()).(type) { + switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) { case *Basic: - if isString(typ) && id == _Len { + if isString(t) && id == _Len { if x.mode == constant { mode = constant val = exact.MakeInt64(int64(len(exact.StringVal(x.val)))) @@ -134,7 +148,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b // function calls; in this case s is not evaluated." if !check.containsCallsOrReceives(x.expr) { mode = constant - val = exact.MakeInt64(typ.len) + val = exact.MakeInt64(t.len) } case *Slice, *Chan: @@ -150,9 +164,13 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b check.invalidArg(x.pos(), "%s for %s", x, bin.name) return } + x.mode = mode x.typ = Typ[Int] x.val = val + if check.Types != nil && mode != constant { + check.recordBuiltinType(call.Fun, makeSig(x.typ, typ)) + } case _Close: // close(c) @@ -165,7 +183,11 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b check.invalidArg(x.pos(), "%s must not be a receive-only channel", x) return } + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, c)) + } case _Complex: // complex(x, y realT) complexT @@ -202,9 +224,9 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b x.mode = value } - realT := x.typ.Underlying().(*Basic) + realT := x.typ complexT := Typ[Invalid] - switch realT.kind { + switch realT.Underlying().(*Basic).kind { case Float32: complexT = Typ[Complex64] case Float64: @@ -223,7 +245,11 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b check.invalidArg(x.pos(), "float32 or float64 arguments expected") return } + x.typ = complexT + if check.Types != nil && x.mode != constant { + check.recordBuiltinType(call.Fun, makeSig(complexT, realT, realT)) + } if x.mode != constant { // The arguments have now their final types, which at run- @@ -270,6 +296,10 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b x.mode = value x.typ = Typ[Int] + if check.Types != nil { + S := NewSlice(dst) + check.recordBuiltinType(call.Fun, makeSig(x.typ, S, S)) + } case _Delete: // delete(m, k) @@ -282,11 +312,16 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b if x.mode == invalid { return } + if !x.isAssignableTo(check.conf, m.key) { check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.key) return } + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key)) + } case _Imag, _Real: // imag(complexT) realT @@ -304,7 +339,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } else { x.mode = value } - k := Invalid + var k BasicKind switch x.typ.Underlying().(*Basic).kind { case Complex64: k = Float32 @@ -315,6 +350,10 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b default: unreachable() } + + if check.Types != nil && x.mode != constant { + check.recordBuiltinType(call.Fun, makeSig(Typ[k], x.typ)) + } x.typ = Typ[k] case _Make: @@ -352,6 +391,10 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } x.mode = variable x.typ = T + if check.Types != nil { + params := [...]Type{T, Typ[Int], Typ[Int]} + check.recordBuiltinType(call.Fun, makeSig(x.typ, params[:1+len(sizes)]...)) + } case _New: // new(T) @@ -360,32 +403,64 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b if T == Typ[Invalid] { return } + x.mode = variable x.typ = &Pointer{base: T} + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) + } - case _Panic, _Print, _Println: - // panic(x interface{}) + case _Panic: + // panic(x) + arg(x, 0) + if x.mode == invalid { + return + } + // TODO(gri) arguments must be assignable to _ + + x.mode = novalue + if check.Types != nil { + // TODO(gri) we need a global empty interface somewhere + check.recordBuiltinType(call.Fun, makeSig(nil, new(Interface))) + } + + case _Print, _Println: // print(x, y, ...) // println(x, y, ...) - for i := 1; i < nargs; i++ { - arg(x, i) - if x.mode == invalid { - return + var params []Type + if nargs > 0 { + params = make([]Type, nargs) + params[0] = x.typ // first argument already evaluated + for i := 1; i < nargs; i++ { + arg(x, i) + if x.mode == invalid { + return + } + params[i] = x.typ + // TODO(gri) arguments must be assignable to _ } - // TODO(gri) arguments must be assignable to _ } + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, params...)) + } case _Recover: // recover() interface{} x.mode = value - x.typ = new(Interface) + x.typ = new(Interface) // TODO(gri) we need a global empty interface somewhere + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ)) + } case _Alignof: - // unsafe.Alignof(x T) uintptr, where x must be a variable + // unsafe.Alignof(x T) uintptr + // TODO(gri) argument must be assignable to _ x.mode = constant x.val = exact.MakeInt64(check.conf.alignof(x.typ)) x.typ = Typ[Uintptr] + // result is constant - no need to record signature case _Offsetof: // unsafe.Offsetof(x T) uintptr, where x must be a selector @@ -424,12 +499,15 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b x.mode = constant x.val = exact.MakeInt64(offs) x.typ = Typ[Uintptr] + // result is constant - no need to record signature case _Sizeof: // unsafe.Sizeof(x T) uintptr + // TODO(gri) argument must be assignable to _ x.mode = constant x.val = exact.MakeInt64(check.conf.sizeof(x.typ)) x.typ = Typ[Uintptr] + // result is constant - no need to record signature case _Assert: // assert(pred) causes a typechecker error if pred is false. @@ -447,6 +525,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b check.errorf(call.Pos(), "%s failed", call) // compile-time assertion failure - safe to continue } + // result is constant - no need to record signature case _Trace: // trace(x, y, z, ...) dumps the positions, expressions, and @@ -466,6 +545,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b check.dump("%s: %s", x1.pos(), x1) x1 = &t // use incoming x only for first argument } + // trace is only available in test mode - no need to record signature default: unreachable() @@ -475,15 +555,16 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b } // makeSig makes a signature for the given argument and result types. -// res may be nil. +// Default types are used for untyped arguments, and res may be nil. func makeSig(res Type, args ...Type) *Signature { list := make([]*Var, len(args)) for i, param := range args { - list[i] = NewVar(token.NoPos, nil, "", param) + list[i] = NewVar(token.NoPos, nil, "", defaultType(param)) } params := NewTuple(list...) var result *Tuple if res != nil { + assert(!isUntyped(res)) result = NewTuple(NewVar(token.NoPos, nil, "", res)) } return &Signature{params: params, results: result} diff --git a/go/types/builtins_test.go b/go/types/builtins_test.go new file mode 100644 index 0000000000..346c25ae74 --- /dev/null +++ b/go/types/builtins_test.go @@ -0,0 +1,194 @@ +// 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. + +package types + +import ( + "fmt" + "go/ast" + "go/parser" + "testing" +) + +var builtinCalls = []struct { + id builtinId + src string + sig string +}{ + {_Append, `var s []int; _ = append(s)`, `func([]int, ...int) []int`}, + {_Append, `var s []int; _ = append(s, 0)`, `func([]int, ...int) []int`}, + {_Append, `var s []int; _ = (append)(s, 0)`, `func([]int, ...int) []int`}, + {_Append, `var s []byte; _ = ((append))(s, 0)`, `func([]byte, ...byte) []byte`}, + {_Append, `var s []byte; _ = append(s, "foo"...)`, `func([]byte, string) []byte`}, + {_Append, `type T []byte; var s T; _ = append(s, "foo"...)`, `func(p.T, string) p.T`}, + + {_Cap, `var s [10]int; _ = cap(s)`, `invalid type`}, // constant + {_Cap, `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant + {_Cap, `var s []int64; _ = cap(s)`, `func([]int64) int`}, + {_Cap, `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`}, + + {_Len, `_ = len("foo")`, `invalid type`}, // constant + {_Len, `var s string; _ = len(s)`, `func(string) int`}, + {_Len, `var s [10]int; _ = len(s)`, `invalid type`}, // constant + {_Len, `var s [10]int; _ = len(&s)`, `invalid type`}, // constant + {_Len, `var s []int64; _ = len(s)`, `func([]int64) int`}, + {_Len, `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`}, + {_Len, `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`}, + + {_Close, `var c chan int; close(c)`, `func(chan int)`}, + {_Close, `var c chan<- chan string; close(c)`, `func(chan<- chan string)`}, + + {_Complex, `_ = complex(1, 0)`, `invalid type`}, // constant + {_Complex, `var re float32; _ = complex(re, 1.0)`, `func(float32, float32) complex64`}, + {_Complex, `var im float64; _ = complex(1, im)`, `func(float64, float64) complex128`}, + {_Complex, `type F32 float32; var re, im F32; _ = complex(re, im)`, `func(p.F32, p.F32) complex64`}, + {_Complex, `type F64 float64; var re, im F64; _ = complex(re, im)`, `func(p.F64, p.F64) complex128`}, + + {_Copy, `var src, dst []byte; copy(dst, src)`, `func([]byte, []byte) int`}, + {_Copy, `type T [][]int; var src, dst T; _ = copy(dst, src)`, `func([][]int, [][]int) int`}, + + {_Delete, `var m map[string]bool; delete(m, "foo")`, `func(map[string]bool, string)`}, + {_Delete, `type (K string; V int); var m map[K]V; delete(m, "foo")`, `func(map[p.K]p.V, p.K)`}, + + {_Imag, `_ = imag(1i)`, `invalid type`}, // constant + {_Imag, `var c complex64; _ = imag(c)`, `func(complex64) float32`}, + {_Imag, `var c complex128; _ = imag(c)`, `func(complex128) float64`}, + {_Imag, `type C64 complex64; var c C64; _ = imag(c)`, `func(p.C64) float32`}, + {_Imag, `type C128 complex128; var c C128; _ = imag(c)`, `func(p.C128) float64`}, + + {_Real, `_ = real(1i)`, `invalid type`}, // constant + {_Real, `var c complex64; _ = real(c)`, `func(complex64) float32`}, + {_Real, `var c complex128; _ = real(c)`, `func(complex128) float64`}, + {_Real, `type C64 complex64; var c C64; _ = real(c)`, `func(p.C64) float32`}, + {_Real, `type C128 complex128; var c C128; _ = real(c)`, `func(p.C128) float64`}, + + {_Make, `_ = make([]int, 10)`, `func([]int, int) []int`}, + {_Make, `type T []byte; _ = make(T, 10, 20)`, `func(p.T, int, int) p.T`}, + + {_New, `_ = new(int)`, `func(int) *int`}, + {_New, `type T struct{}; _ = new(T)`, `func(p.T) *p.T`}, + + {_Panic, `panic(0)`, `func(interface{})`}, + {_Panic, `panic("foo")`, `func(interface{})`}, + + {_Print, `print()`, `func()`}, + {_Print, `print(0)`, `func(int)`}, + {_Print, `print(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {_Println, `println()`, `func()`}, + {_Println, `println(0)`, `func(int)`}, + {_Println, `println(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {_Recover, `recover()`, `func() interface{}`}, + {_Recover, `_ = recover()`, `func() interface{}`}, + + {_Alignof, `_ = unsafe.Alignof(0)`, `invalid type`}, // constant + {_Alignof, `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant + + {_Offsetof, `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant + {_Offsetof, `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant + + {_Sizeof, `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant + {_Sizeof, `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant + + {_Assert, `assert(true)`, `invalid type`}, // constant + {_Assert, `type B bool; const pred B = 1 < 2; assert(pred)`, `invalid type`}, // constant + + // no tests for trace since it produces output as a side-effect +} + +func TestBuiltinSignatures(t *testing.T) { + defPredeclaredTestFuncs() + + seen := map[builtinId]bool{_Trace: true} // no test for _Trace; add it manually + for _, call := range builtinCalls { + testBuiltinSignature(t, call.id, call.src, call.sig) + seen[call.id] = true + } + + // make sure we didn't miss one + for i := range predeclaredFuncs { + if id := builtinId(i); !seen[id] { + t.Errorf("missing test for %s", predeclaredFuncs[id].name) + } + } +} + +func testBuiltinSignature(t *testing.T, id builtinId, src0, want string) { + src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0) + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + var conf Config + objects := make(map[*ast.Ident]Object) + types := make(map[ast.Expr]Type) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Objects: objects, Types: types}) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + // find called function + n := 0 + var fun ast.Expr + for x, _ := range types { + if call, _ := x.(*ast.CallExpr); call != nil { + fun = call.Fun + n++ + } + } + if n != 1 { + t.Errorf("%s: got %d CallExprs; want 1", src0, n) + return + } + + // check recorded types for fun and descendents (may be parenthesized) + for { + // the recorded type for the built-in must match the wanted signature + typ := types[fun] + if typ == nil { + t.Errorf("%s: no type recorded for %s", src0, exprString(fun)) + return + } + if got := typ.String(); got != want { + t.Errorf("%s: got type %s; want %s", src0, got, want) + return + } + + // called function must be a (possibly parenthesized, qualified) + // identifier denoting the expected built-in + switch p := fun.(type) { + case *ast.Ident: + obj := objects[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p) + return + } + if bin.id != id { + t.Errorf("%s: got built-in %s; want %s", src0, bin.name, predeclaredFuncs[id].name) + return + } + return // we're done + + case *ast.ParenExpr: + fun = p.X // unpack + + case *ast.SelectorExpr: + // built-in from package unsafe - ignore details + return // we're done + + default: + t.Errorf("%s: invalid function call", src0) + return + } + } +} diff --git a/go/types/check.go b/go/types/check.go index f0b129d631..48a1ba6b29 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -78,6 +78,24 @@ func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) } } +func (check *checker) recordBuiltinType(f ast.Expr, sig *Signature) { + // f must be a (possibly parenthesized) identifier denoting a built-in + // (built-ins in package unsafe always produce a constant result and + // we don't record their signatures, so we don't see qualified idents + // here): record the signature for f and possible children. + for { + check.recordTypeAndValue(f, sig, nil) + switch p := f.(type) { + case *ast.Ident: + return // we're done + case *ast.ParenExpr: + f = p.X + default: + unreachable() + } + } +} + func (check *checker) recordCommaOkTypes(x ast.Expr, t1, t2 Type) { assert(x != nil && isTyped(t1) && isTyped(t2) && isBoolean(t2)) if m := check.Types; m != nil { diff --git a/go/types/check_test.go b/go/types/check_test.go index cb03a79d57..f65ba49259 100644 --- a/go/types/check_test.go +++ b/go/types/check_test.go @@ -247,16 +247,9 @@ func checkFiles(t *testing.T, testfiles []string) { } } -var testBuiltinsDeclared = false - func TestCheck(t *testing.T) { // Declare builtins for testing. - // Not done in an init func to avoid an init race with - // the construction of the Universe var. - if !testBuiltinsDeclared { - testBuiltinsDeclared = true - defPredeclaredTestFuncs() - } + defPredeclaredTestFuncs() // If explicit test files are specified, only check those. if files := *testFiles; files != "" { diff --git a/go/types/issues_test.go b/go/types/issues_test.go index 840fab1dae..7d8126b95c 100644 --- a/go/types/issues_test.go +++ b/go/types/issues_test.go @@ -133,6 +133,6 @@ func f() int { } if n != 2 { - t.Errorf("got %d call CallExprs; want 2", n) + t.Errorf("got %d CallExprs; want 2", n) } } diff --git a/go/types/testdata/builtins.src b/go/types/testdata/builtins.src index 12898051b0..65fa1d7973 100644 --- a/go/types/testdata/builtins.src +++ b/go/types/testdata/builtins.src @@ -139,6 +139,7 @@ func complex1() { var f32 float32 var f64 float64 var c64 complex64 + var c128 complex128 _ = complex() // ERROR not enough arguments _ = complex(1) // ERROR not enough arguments _ = complex(true /* ERROR invalid argument */ , 0) @@ -157,8 +158,8 @@ func complex1() { _ = complex(f64, 1) _ = complex(f64, 1.0) _ = complex(f64, 'a') - _ = complex(f32 /* ERROR mismatched types */, f64) - _ = complex(f64 /* ERROR mismatched types */, f32) + _ = complex(f32 /* ERROR mismatched types */ , f64) + _ = complex(f64 /* ERROR mismatched types */ , f32) _ = complex(1, 1) _ = complex(1, 1.1) _ = complex(1, 'a') @@ -185,6 +186,19 @@ func complex1() { const _ = complex /* ERROR not constant */ (1 /* ERROR integer */ <