1
0
mirror of https://github.com/golang/go synced 2024-11-18 10:54:40 -07:00

go.tools/go/types: provide TypeAndValue predicates

LGTM=adonovan
R=adonovan, pcc
CC=golang-codereviews
https://golang.org/cl/110880043
This commit is contained in:
Robert Griesemer 2014-07-10 16:59:50 -07:00
parent a16d58355f
commit 961ab3ca8d
6 changed files with 211 additions and 27 deletions

View File

@ -121,24 +121,23 @@ type Config struct {
// in a client of go/types will initialize DefaultImport to gcimporter.Import.
var DefaultImport Importer
type TypeAndValue struct {
Type Type
Value exact.Value
}
// Info holds result type information for a type-checked package.
// Only the information for which a map is provided is collected.
// If the package has type errors, the collected information may
// be incomplete.
type Info struct {
// Types maps expressions to their types, and for constant
// expressions, their values.
// Identifiers are collected in Defs and Uses, not Types.
// expressions, their values. Invalid expressions are omitted.
//
// 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.
// For (possibly parenthesized) identifiers denoting built-in
// functions, the recorded signatures are 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.
//
// Identifiers on the lhs of declarations (i.e., the identifiers
// which are being declared) are collected in the Defs map.
// Identifiers denoting packages are collected in the Uses maps.
Types map[ast.Expr]TypeAndValue
// Defs maps identifiers to the objects they define (including
@ -207,6 +206,69 @@ type Info struct {
InitOrder []*Initializer
}
// TypeAndValue reports the type and value (for constants)
// of the corresponding expression.
type TypeAndValue struct {
mode operandMode
Type Type
Value exact.Value
}
// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report
// "void" values as regular values but with the empty tuple type.
// IsVoid reports whether the corresponding expression
// is a function call without results.
func (tv TypeAndValue) IsVoid() bool {
return tv.mode == novalue
}
// IsType reports whether the corresponding expression specifies a type.
func (tv TypeAndValue) IsType() bool {
return tv.mode == typexpr
}
// IsBuiltin reports whether the corresponding expression denotes
// a (possibly parenthesized) built-in function.
func (tv TypeAndValue) IsBuiltin() bool {
return tv.mode == builtin
}
// IsValue reports whether the corresponding expression is a value.
// Builtins are not considered values. Constant values have a non-
// nil Value.
func (tv TypeAndValue) IsValue() bool {
switch tv.mode {
case constant, variable, mapindex, value, commaok:
return true
}
return false
}
// IsNil reports whether the corresponding expression denotes the
// predeclared value nil.
func (tv TypeAndValue) IsNil() bool {
return tv.mode == value && tv.Type == Typ[UntypedNil]
}
// Addressable reports whether the corresponding expression
// is addressable (http://golang.org/ref/spec#Address_operators).
func (tv TypeAndValue) Addressable() bool {
return tv.mode == variable
}
// Assignable reports whether the corresponding expression
// is assignable to (provided a value of the right type).
func (tv TypeAndValue) Assignable() bool {
return tv.mode == variable || tv.mode == mapindex
}
// HasOk reports whether the corresponding expression may be
// used on the lhs of a comma-ok assignment.
func (tv TypeAndValue) HasOk() bool {
return tv.mode == commaok || tv.mode == mapindex
}
// An Initializer describes a package-level variable, or a list of variables in case
// of a multi-valued initialization expression, and the corresponding initialization
// expression.

View File

@ -5,6 +5,7 @@
package types_test
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
@ -255,6 +256,117 @@ func TestTypesInfo(t *testing.T) {
}
}
func predString(tv TypeAndValue) string {
var buf bytes.Buffer
pred := func(b bool, s string) {
if b {
if buf.Len() > 0 {
buf.WriteString(", ")
}
buf.WriteString(s)
}
}
pred(tv.IsVoid(), "void")
pred(tv.IsType(), "type")
pred(tv.IsBuiltin(), "builtin")
pred(tv.IsValue() && tv.Value != nil, "const")
pred(tv.IsValue() && tv.Value == nil, "value")
pred(tv.IsNil(), "nil")
pred(tv.Addressable(), "addressable")
pred(tv.Assignable(), "assignable")
pred(tv.HasOk(), "hasOk")
if buf.Len() == 0 {
return "invalid"
}
return buf.String()
}
func TestPredicatesInfo(t *testing.T) {
var tests = []struct {
src string
expr string
pred string
}{
// void
{`package n0; func f() { f() }`, `f()`, `void`},
// types
{`package t0; type _ int`, `int`, `type`},
{`package t1; type _ []int`, `[]int`, `type`},
{`package t2; type _ func()`, `func()`, `type`},
// built-ins
{`package b0; var _ = len("")`, `len`, `builtin`},
{`package b1; var _ = (len)("")`, `(len)`, `builtin`},
// constants
{`package c0; var _ = 42`, `42`, `const`},
{`package c1; var _ = "foo" + "bar"`, `"foo" + "bar"`, `const`},
{`package c2; const (i = 1i; _ = i)`, `i`, `const`},
// values
{`package v0; var (a, b int; _ = a + b)`, `a + b`, `value`},
{`package v1; var _ = &[]int{1}`, `([]int literal)`, `value`},
{`package v2; var _ = func(){}`, `(func() literal)`, `value`},
{`package v4; func f() { _ = f }`, `f`, `value`},
{`package v3; var _ *int = nil`, `nil`, `value, nil`},
{`package v3; var _ *int = (nil)`, `(nil)`, `value, nil`},
// addressable (and thus assignable) operands
{`package a0; var (x int; _ = x)`, `x`, `value, addressable, assignable`},
{`package a1; var (p *int; _ = *p)`, `*p`, `value, addressable, assignable`},
{`package a2; var (s []int; _ = s[0])`, `s[0]`, `value, addressable, assignable`},
{`package a3; var (s struct{f int}; _ = s.f)`, `s.f`, `value, addressable, assignable`},
{`package a4; var (a [10]int; _ = a[0])`, `a[0]`, `value, addressable, assignable`},
{`package a5; func _(x int) { _ = x }`, `x`, `value, addressable, assignable`},
{`package a6; func _()(x int) { _ = x; return }`, `x`, `value, addressable, assignable`},
{`package a7; type T int; func (x T) _() { _ = x }`, `x`, `value, addressable, assignable`},
// composite literals are not addressable
// assignable but not addressable values
{`package s0; var (m map[int]int; _ = m[0])`, `m[0]`, `value, assignable, hasOk`},
{`package s1; var (m map[int]int; _, _ = m[0])`, `m[0]`, `value, assignable, hasOk`},
// hasOk expressions
{`package k0; var (ch chan int; _ = <-ch)`, `<-ch`, `value, hasOk`},
{`package k1; var (ch chan int; _, _ = <-ch)`, `<-ch`, `value, hasOk`},
// missing entries
// - package names are collected in the Uses map
// - identifiers being declared are collected in the Defs map
{`package m0; import "os"; func _() { _ = os.Stdout }`, `os`, `<missing>`},
{`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, `<missing>`},
{`package m2; const c = 0`, `c`, `<missing>`},
{`package m3; type T int`, `T`, `<missing>`},
{`package m4; var v int`, `v`, `<missing>`},
{`package m5; func f() {}`, `f`, `<missing>`},
{`package m6; func _(x int) {}`, `x`, `<missing>`},
{`package m6; func _()(x int) { return }`, `x`, `<missing>`},
{`package m6; type T int; func (x T) _() {}`, `x`, `<missing>`},
}
for _, test := range tests {
info := Info{Types: make(map[ast.Expr]TypeAndValue)}
name := mustTypecheck(t, "PredicatesInfo", test.src, &info)
// look for expression predicates
got := "<missing>"
for e, tv := range info.Types {
//println(name, ExprString(e))
if ExprString(e) == test.expr {
got = predString(tv)
break
}
}
if got != test.pred {
t.Errorf("package %s: got %s; want %s", name, got, test.pred)
}
}
}
func TestScopesInfo(t *testing.T) {
var tests = []struct {
src string

View File

@ -20,9 +20,10 @@ const (
trace = false // turn on for detailed type resolution traces
)
// exprInfo stores type and constant value for an untyped expression.
// exprInfo stores information about an untyped expression.
type exprInfo struct {
isLhs bool // expression is lhs operand of a shift with delayed type-check
mode operandMode
typ *Basic
val exact.Value // constant value; or nil (if not a constant)
}
@ -98,13 +99,13 @@ func (check *Checker) assocMethod(tname string, meth *Func) {
m[tname] = append(m[tname], meth)
}
func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, typ *Basic, val exact.Value) {
func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val exact.Value) {
m := check.untyped
if m == nil {
m = make(map[ast.Expr]exprInfo)
check.untyped = m
}
m[e] = exprInfo{lhs, typ, val}
m[e] = exprInfo{lhs, mode, typ, val}
}
func (check *Checker) later(name string, decl *declInfo, sig *Signature, body *ast.BlockStmt) {
@ -239,17 +240,23 @@ func (check *Checker) recordUntyped() {
check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
unreachable()
}
check.recordTypeAndValue(x, info.typ, info.val)
check.recordTypeAndValue(x, info.mode, info.typ, info.val)
}
}
func (check *Checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) {
assert(x != nil && typ != nil)
if val != nil {
func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val exact.Value) {
assert(x != nil)
assert(typ != nil)
if mode == invalid {
return // omit
}
assert(typ != nil)
if mode == constant {
assert(val != nil)
assert(isConstType(typ))
}
if m := check.Types; m != nil {
m[x] = TypeAndValue{typ, val}
m[x] = TypeAndValue{mode, typ, val}
}
}
@ -259,7 +266,7 @@ func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) {
// 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)
check.recordTypeAndValue(f, builtin, sig, nil)
switch p := f.(type) {
case *ast.Ident:
return // we're done
@ -313,7 +320,8 @@ func (check *Checker) recordUse(id *ast.Ident, obj Object) {
}
func (check *Checker) recordImplicit(node ast.Node, obj Object) {
assert(node != nil && obj != nil)
assert(node != nil)
assert(obj != nil)
if m := check.Implicits; m != nil {
m[node] = obj
}
@ -329,7 +337,8 @@ func (check *Checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, r
}
func (check *Checker) recordScope(node ast.Node, scope *Scope) {
assert(node != nil && scope != nil)
assert(node != nil)
assert(scope != nil)
if m := check.Scopes; m != nil {
m[node] = scope
}

View File

@ -451,7 +451,7 @@ func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) {
}
// Everything's fine, record final type and value for x.
check.recordTypeAndValue(x, typ, old.val)
check.recordTypeAndValue(x, old.mode, typ, old.val)
}
// updateExprVal updates the value of x to val.
@ -908,6 +908,7 @@ func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
kind := check.exprInternal(x, e, hint)
// convert x into a user-friendly set of values
// TODO(gri) this code can be simplified
var typ Type
var val exact.Value
switch x.mode {
@ -926,9 +927,9 @@ func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
if isUntyped(typ) {
// delay type and value recording until we know the type
// or until the end of type checking
check.rememberUntyped(x.expr, false, typ.(*Basic), val)
check.rememberUntyped(x.expr, false, x.mode, typ.(*Basic), val)
} else {
check.recordTypeAndValue(e, typ, val)
check.recordTypeAndValue(e, x.mode, typ, val)
}
return kind

View File

@ -15,7 +15,7 @@ import (
)
// An operandMode specifies the (addressing) mode of an operand.
type operandMode int
type operandMode byte
const (
invalid operandMode = iota // operand is invalid

View File

@ -129,7 +129,7 @@ func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type)
T = check.typExprInternal(e, def, path)
assert(isTyped(T))
check.recordTypeAndValue(e, T, nil)
check.recordTypeAndValue(e, typexpr, T, nil)
return
}