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:
parent
a16d58355f
commit
961ab3ca8d
|
|
@ -121,24 +121,23 @@ type Config struct {
|
||||||
// in a client of go/types will initialize DefaultImport to gcimporter.Import.
|
// in a client of go/types will initialize DefaultImport to gcimporter.Import.
|
||||||
var DefaultImport Importer
|
var DefaultImport Importer
|
||||||
|
|
||||||
type TypeAndValue struct {
|
|
||||||
Type Type
|
|
||||||
Value exact.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info holds result type information for a type-checked package.
|
// Info holds result type information for a type-checked package.
|
||||||
// Only the information for which a map is provided is collected.
|
// Only the information for which a map is provided is collected.
|
||||||
// If the package has type errors, the collected information may
|
// If the package has type errors, the collected information may
|
||||||
// be incomplete.
|
// be incomplete.
|
||||||
type Info struct {
|
type Info struct {
|
||||||
// Types maps expressions to their types, and for constant
|
// Types maps expressions to their types, and for constant
|
||||||
// expressions, their values.
|
// expressions, their values. Invalid expressions are omitted.
|
||||||
// Identifiers are collected in Defs and Uses, not Types.
|
|
||||||
//
|
//
|
||||||
// For an expression denoting a predeclared built-in function
|
// For (possibly parenthesized) identifiers denoting built-in
|
||||||
// the recorded signature is call-site specific. If the call
|
// functions, the recorded signatures are call-site specific:
|
||||||
// result is not a constant, the recorded type is an argument-
|
// if the call result is not a constant, the recorded type is
|
||||||
// specific signature. Otherwise, the recorded type is invalid.
|
// 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
|
Types map[ast.Expr]TypeAndValue
|
||||||
|
|
||||||
// Defs maps identifiers to the objects they define (including
|
// Defs maps identifiers to the objects they define (including
|
||||||
|
|
@ -207,6 +206,69 @@ type Info struct {
|
||||||
InitOrder []*Initializer
|
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
|
// An Initializer describes a package-level variable, or a list of variables in case
|
||||||
// of a multi-valued initialization expression, and the corresponding initialization
|
// of a multi-valued initialization expression, and the corresponding initialization
|
||||||
// expression.
|
// expression.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"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) {
|
func TestScopesInfo(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
src string
|
src string
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,10 @@ const (
|
||||||
trace = false // turn on for detailed type resolution traces
|
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 {
|
type exprInfo struct {
|
||||||
isLhs bool // expression is lhs operand of a shift with delayed type-check
|
isLhs bool // expression is lhs operand of a shift with delayed type-check
|
||||||
|
mode operandMode
|
||||||
typ *Basic
|
typ *Basic
|
||||||
val exact.Value // constant value; or nil (if not a constant)
|
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)
|
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
|
m := check.untyped
|
||||||
if m == nil {
|
if m == nil {
|
||||||
m = make(map[ast.Expr]exprInfo)
|
m = make(map[ast.Expr]exprInfo)
|
||||||
check.untyped = m
|
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) {
|
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)
|
check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ)
|
||||||
unreachable()
|
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) {
|
func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val exact.Value) {
|
||||||
assert(x != nil && typ != nil)
|
assert(x != nil)
|
||||||
if val != nil {
|
assert(typ != nil)
|
||||||
|
if mode == invalid {
|
||||||
|
return // omit
|
||||||
|
}
|
||||||
|
assert(typ != nil)
|
||||||
|
if mode == constant {
|
||||||
|
assert(val != nil)
|
||||||
assert(isConstType(typ))
|
assert(isConstType(typ))
|
||||||
}
|
}
|
||||||
if m := check.Types; m != nil {
|
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
|
// we don't record their signatures, so we don't see qualified idents
|
||||||
// here): record the signature for f and possible children.
|
// here): record the signature for f and possible children.
|
||||||
for {
|
for {
|
||||||
check.recordTypeAndValue(f, sig, nil)
|
check.recordTypeAndValue(f, builtin, sig, nil)
|
||||||
switch p := f.(type) {
|
switch p := f.(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
return // we're done
|
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) {
|
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 {
|
if m := check.Implicits; m != nil {
|
||||||
m[node] = obj
|
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) {
|
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 {
|
if m := check.Scopes; m != nil {
|
||||||
m[node] = scope
|
m[node] = scope
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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.
|
// 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)
|
kind := check.exprInternal(x, e, hint)
|
||||||
|
|
||||||
// convert x into a user-friendly set of values
|
// convert x into a user-friendly set of values
|
||||||
|
// TODO(gri) this code can be simplified
|
||||||
var typ Type
|
var typ Type
|
||||||
var val exact.Value
|
var val exact.Value
|
||||||
switch x.mode {
|
switch x.mode {
|
||||||
|
|
@ -926,9 +927,9 @@ func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
|
||||||
if isUntyped(typ) {
|
if isUntyped(typ) {
|
||||||
// delay type and value recording until we know the type
|
// delay type and value recording until we know the type
|
||||||
// or until the end of type checking
|
// 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 {
|
} else {
|
||||||
check.recordTypeAndValue(e, typ, val)
|
check.recordTypeAndValue(e, x.mode, typ, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
return kind
|
return kind
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// An operandMode specifies the (addressing) mode of an operand.
|
// An operandMode specifies the (addressing) mode of an operand.
|
||||||
type operandMode int
|
type operandMode byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
invalid operandMode = iota // operand is invalid
|
invalid operandMode = iota // operand is invalid
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type)
|
||||||
|
|
||||||
T = check.typExprInternal(e, def, path)
|
T = check.typExprInternal(e, def, path)
|
||||||
assert(isTyped(T))
|
assert(isTyped(T))
|
||||||
check.recordTypeAndValue(e, T, nil)
|
check.recordTypeAndValue(e, typexpr, T, nil)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue