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:
parent
a16d58355f
commit
961ab3ca8d
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user