1
0
mirror of https://github.com/golang/go synced 2024-11-19 00:54:42 -07:00

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
This commit is contained in:
Robert Griesemer 2013-10-11 10:04:10 -07:00
parent 7b481db506
commit 0c45220917
8 changed files with 355 additions and 28 deletions

View File

@ -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.

View File

@ -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, ...)
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 _
}
}
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}

194
go/types/builtins_test.go Normal file
View File

@ -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
}
}
}

View File

@ -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 {

View File

@ -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()
}
// If explicit test files are specified, only check those.
if files := *testFiles; files != "" {

View File

@ -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)
}
}

View File

@ -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 */ <<s, 0)
var _ int = complex /* ERROR cannot initialize */ (1 /* ERROR integer */ <<s, 0)
// floating-point argument types must be identical
type F32 float32
type F64 float64
var x32 F32
var x64 F64
c64 = complex(x32, x32)
_ = complex(x32 /* ERROR mismatched types */ , f32)
_ = complex(f32 /* ERROR mismatched types */ , x32)
c128 = complex(x64, x64)
_ = c128
_ = complex(x64 /* ERROR mismatched types */ , f64)
_ = complex(f64 /* ERROR mismatched types */ , x64)
var t []float32
_ = complex(t... /* ERROR invalid use of \.\.\. */ )
}
@ -217,6 +231,9 @@ func copy1() {
var t [][]int
copy(t, t)
copy(t /* ERROR copy expects slice arguments */ , nil)
copy(nil /* ERROR copy expects slice arguments */ , t)
copy(nil /* ERROR copy expects slice arguments */ , nil)
copy(t... /* ERROR invalid use of \.\.\. */ )
}
@ -277,6 +294,14 @@ func imag1() {
imag /* ERROR not used */ (c64)
_, _ = f32, f64
// complex type may not be predeclared
type C64 complex64
type C128 complex128
var x64 C64
var x128 C128
f32 = imag(x64)
f64 = imag(x128)
var s []complex64
_ = imag(s... /* ERROR invalid use of \.\.\. */ )
}
@ -504,6 +529,14 @@ func real1() {
f64 = real /* ERROR cannot assign */ (c64)
real /* ERROR not used */ (c64)
// complex type may not be predeclared
type C64 complex64
type C128 complex128
var x64 C64
var x128 C128
f32 = imag(x64)
f64 = imag(x128)
var s []complex64
_ = real(s... /* ERROR invalid use of \.\.\. */ )
_, _ = f32, f64

View File

@ -164,6 +164,9 @@ func defPredeclaredFuncs() {
}
func defPredeclaredTestFuncs() {
if Universe.Lookup("assert") != nil {
return // already defined
}
def(newBuiltin(_Assert))
def(newBuiltin(_Trace))
}