diff --git a/go/types/api.go b/go/types/api.go index e35425f7..cec106a4 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -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. diff --git a/go/types/api_test.go b/go/types/api_test.go index e201030f..09730b54 100644 --- a/go/types/api_test.go +++ b/go/types/api_test.go @@ -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`, ``}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, ``}, + {`package m2; const c = 0`, `c`, ``}, + {`package m3; type T int`, `T`, ``}, + {`package m4; var v int`, `v`, ``}, + {`package m5; func f() {}`, `f`, ``}, + {`package m6; func _(x int) {}`, `x`, ``}, + {`package m6; func _()(x int) { return }`, `x`, ``}, + {`package m6; type T int; func (x T) _() {}`, `x`, ``}, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "" + 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 diff --git a/go/types/check.go b/go/types/check.go index 71bbfe79..08789f61 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -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 } diff --git a/go/types/expr.go b/go/types/expr.go index be11a040..6cc572d9 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -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 diff --git a/go/types/operand.go b/go/types/operand.go index 27aaa5f1..2545da7b 100644 --- a/go/types/operand.go +++ b/go/types/operand.go @@ -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 diff --git a/go/types/typexpr.go b/go/types/typexpr.go index 57861e4c..fa239ac0 100644 --- a/go/types/typexpr.go +++ b/go/types/typexpr.go @@ -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 }