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

go.tools/go/types: only report constants with constant types

The issue is addressed with the change in conversions.go.

Also:
- added corresponding API test
- made names for untyped ints, bools consistent with others
- use *Basic with names byte/rune instead of uint8/int32 for better output
- minor cleanups

Fixes golang/go#6949.

R=adonovan
CC=golang-codereviews
https://golang.org/cl/50520043
This commit is contained in:
Robert Griesemer 2014-01-10 15:21:51 -08:00
parent d2fe54b33c
commit 179e0b3699
12 changed files with 138 additions and 54 deletions

View File

@ -13,6 +13,7 @@ import (
"strings" "strings"
"testing" "testing"
"code.google.com/p/go.tools/go/exact"
_ "code.google.com/p/go.tools/go/gcimporter" _ "code.google.com/p/go.tools/go/gcimporter"
. "code.google.com/p/go.tools/go/types" . "code.google.com/p/go.tools/go/types"
) )
@ -40,11 +41,95 @@ func mustTypecheck(t *testing.T, path, source string, info *Info) string {
return pkg.Name() return pkg.Name()
} }
func TestValues(t *testing.T) {
var tests = []struct {
src string
expr string // constant expression
typ string // constant type
val string // constant value
}{
{`package a0; const _ = false`, `false`, `untyped bool`, `false`},
{`package a1; const _ = 0`, `0`, `untyped int`, `0`},
{`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`},
{`package a3; const _ = 0.`, `0.`, `untyped float`, `0`},
{`package a4; const _ = 0i`, `0i`, `untyped complex`, `0`},
{`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`},
{`package b0; var _ = false`, `false`, `bool`, `false`},
{`package b1; var _ = 0`, `0`, `int`, `0`},
{`package b2; var _ = 'A'`, `'A'`, `rune`, `65`},
{`package b3; var _ = 0.`, `0.`, `float64`, `0`},
{`package b4; var _ = 0i`, `0i`, `complex128`, `0`},
{`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`},
{`package c0a; var _ = bool(false)`, `false`, `bool`, `false`},
{`package c0b; var _ = bool(false)`, `bool(false)`, `bool`, `false`},
{`package c0c; type T bool; var _ = T(false)`, `T(false)`, `c0c.T`, `false`},
{`package c1a; var _ = int(0)`, `0`, `int`, `0`},
{`package c1b; var _ = int(0)`, `int(0)`, `int`, `0`},
{`package c1c; type T int; var _ = T(0)`, `T(0)`, `c1c.T`, `0`},
{`package c2a; var _ = rune('A')`, `'A'`, `rune`, `65`},
{`package c2b; var _ = rune('A')`, `rune('A')`, `rune`, `65`},
{`package c2c; type T rune; var _ = T('A')`, `T('A')`, `c2c.T`, `65`},
{`package c3a; var _ = float32(0.)`, `0.`, `float32`, `0`},
{`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`},
{`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`},
{`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `0`},
{`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `0`},
{`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `0`},
{`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`},
{`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`},
{`package c5c; type T string; var _ = T("foo")`, `T("foo")`, `c5c.T`, `"foo"`},
{`package d0; var _ = []byte("foo")`, `"foo"`, `string`, `"foo"`},
{`package d1; var _ = []byte(string("foo"))`, `"foo"`, `string`, `"foo"`},
{`package d2; var _ = []byte(string("foo"))`, `string("foo")`, `string`, `"foo"`},
{`package d3; type T []byte; var _ = T("foo")`, `"foo"`, `string`, `"foo"`},
}
for _, test := range tests {
info := Info{
Types: make(map[ast.Expr]Type),
Values: make(map[ast.Expr]exact.Value),
}
name := mustTypecheck(t, "Values", test.src, &info)
// look for constant expression
var expr ast.Expr
for e := range info.Values {
if ExprString(e) == test.expr {
expr = e
break
}
}
if expr == nil {
t.Errorf("package %s: no expression found for %s", name, test.expr)
continue
}
// check that type is correct
if got := info.Types[expr].String(); got != test.typ {
t.Errorf("package %s: got type %s; want %s", name, got, test.typ)
continue
}
// check that value is correct
if got := info.Values[expr].String(); got != test.val {
t.Errorf("package %s: got value %s; want %s", name, got, test.val)
}
}
}
func TestCommaOkTypes(t *testing.T) { func TestCommaOkTypes(t *testing.T) {
var tests = []struct { var tests = []struct {
src string src string
expr string // comma-ok expression string expr string // comma-ok expression
typ string // typestring of comma-ok value typ string // comma-ok value type
}{ }{
{`package p0; var x interface{}; var _, _ = x.(int)`, {`package p0; var x interface{}; var _, _ = x.(int)`,
`x.(int)`, `x.(int)`,

View File

@ -81,14 +81,14 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
// spec: "As a special case, append also accepts a first argument assignable // spec: "As a special case, append also accepts a first argument assignable
// to type []byte with a second argument of string type followed by ... . // to type []byte with a second argument of string type followed by ... .
// This form appends the bytes of the string. // This form appends the bytes of the string.
if nargs == 2 && call.Ellipsis.IsValid() && x.isAssignableTo(check.conf, NewSlice(Typ[Byte])) { if nargs == 2 && call.Ellipsis.IsValid() && x.isAssignableTo(check.conf, NewSlice(universeByte)) {
arg(x, 1) arg(x, 1)
if x.mode == invalid { if x.mode == invalid {
return return
} }
if isString(x.typ) { if isString(x.typ) {
if check.Types != nil { if check.Types != nil {
sig := makeSig(S, S, NewSlice(Typ[Byte])) sig := makeSig(S, S, NewSlice(universeByte))
sig.isVariadic = true sig.isVariadic = true
check.recordBuiltinType(call.Fun, sig) check.recordBuiltinType(call.Fun, sig)
} }
@ -274,7 +274,7 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
switch t := y.typ.Underlying().(type) { switch t := y.typ.Underlying().(type) {
case *Basic: case *Basic:
if isString(y.typ) { if isString(y.typ) {
src = Typ[Byte] src = universeByte
} }
case *Slice: case *Slice:
src = t.elem src = t.elem

View File

@ -24,8 +24,8 @@ var builtinCalls = []struct {
// Note that ...uint8 (instead of ..byte) appears below because that is the type // Note that ...uint8 (instead of ..byte) appears below because that is the type
// that corresponds to Typ[byte] (an alias) - in the other cases, the type name // that corresponds to Typ[byte] (an alias) - in the other cases, the type name
// is chosen by the source. Either way, byte and uint8 denote identical types. // is chosen by the source. Either way, byte and uint8 denote identical types.
{"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, ...uint8) []byte`}, {"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, ...byte) []byte`},
{"append", `type T []byte; var s T; _ = append(s, "foo"...)`, `func(p.T, ...uint8) p.T`}, {"append", `type T []byte; var s T; _ = append(s, "foo"...)`, `func(p.T, ...byte) 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 [10]int; _ = cap(&s)`, `invalid type`}, // constant {"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant

View File

@ -96,6 +96,7 @@ func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value)
m[x] = typ m[x] = typ
} }
if val != nil { if val != nil {
assert(isConstType(typ))
if m := check.Values; m != nil { if m := check.Values; m != nil {
m[x] = val m[x] = val
} }

View File

@ -11,9 +11,11 @@ import "code.google.com/p/go.tools/go/exact"
// Conversion type-checks the conversion T(x). // Conversion type-checks the conversion T(x).
// The result is in x. // The result is in x.
func (check *checker) conversion(x *operand, T Type) { func (check *checker) conversion(x *operand, T Type) {
constArg := x.mode == constant
var ok bool var ok bool
switch { switch {
case x.mode == constant && isConstType(T): case constArg && isConstType(T):
// constant conversion // constant conversion
switch t := T.Underlying().(*Basic); { switch t := T.Underlying().(*Basic); {
case isRepresentableConst(x.val, check.conf, t.kind, &x.val): case isRepresentableConst(x.val, check.conf, t.kind, &x.val):
@ -45,12 +47,14 @@ func (check *checker) conversion(x *operand, T Type) {
// conversion provides the type, per the spec: "A constant may be // conversion provides the type, per the spec: "A constant may be
// given a type explicitly by a constant declaration or conversion,...". // given a type explicitly by a constant declaration or conversion,...".
final := x.typ final := x.typ
if isUntyped(final) { if isUntyped(x.typ) {
final = T final = T
// For conversions to interfaces, use the argument type's // - For conversions to interfaces, use the argument's default type.
// default type instead. Keep untyped nil for untyped nil // - For conversions of untyped constants to non-constant types, also
// arguments. // use the default type (e.g., []byte("foo") should report string
if isInterface(T) { // not []byte as type for the constant "foo").
// - Keep untyped nil for untyped nil arguments.
if isInterface(T) || constArg && !isConstType(T) {
final = defaultType(x.typ) final = defaultType(x.typ)
} }
} }

View File

@ -122,7 +122,7 @@ func f(a int, s string) float64 {
funcScope := fileScope.Child(0) funcScope := fileScope.Child(0)
var tests = []string{ var tests = []string{
`true => true, untyped boolean`, `true => true, untyped bool`,
`fmt.Println => , func(a ...interface{}) (n int, err error)`, `fmt.Println => , func(a ...interface{}) (n int, err error)`,
`c => 3, untyped float`, `c => 3, untyped float`,
`T => , p.T`, `T => , p.T`,
@ -132,7 +132,7 @@ func f(a int, s string) float64 {
`x => , int`, `x => , int`,
`d/c => 1, int`, `d/c => 1, int`,
`c/2 => 3/2, untyped float`, `c/2 => 3/2, untyped float`,
`m.Pi < m.E => false, untyped boolean`, `m.Pi < m.E => false, untyped bool`,
} }
for _, test := range tests { for _, test := range tests {
str, typ := split(test, ", ") str, typ := split(test, ", ")

View File

@ -447,7 +447,7 @@ func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) {
} }
// Otherwise we have the final (typed or untyped type). // Otherwise we have the final (typed or untyped type).
// Remove it from the map. // Remove it from the map of yet untyped expressions.
delete(check.untyped, x) delete(check.untyped, x)
// If x is the lhs of a shift, its final type must be integer. // If x is the lhs of a shift, its final type must be integer.
@ -895,18 +895,20 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
if trace { if trace {
check.trace(e.Pos(), "%s", e) check.trace(e.Pos(), "%s", e)
check.indent++ check.indent++
defer func() {
check.indent--
check.trace(e.Pos(), "=> %s", x)
}()
} }
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
record := true
var typ Type var typ Type
var val exact.Value var val exact.Value
switch x.mode { switch x.mode {
case invalid: case invalid:
typ = Typ[Invalid] typ = Typ[Invalid]
record = false // nothing to do
case novalue: case novalue:
typ = (*Tuple)(nil) typ = (*Tuple)(nil)
case constant: case constant:
@ -921,18 +923,10 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind {
// delay notification until it becomes typed // delay notification until it becomes typed
// or until the end of type checking // or until the end of type checking
check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val} check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val}
} else if record { } else {
// TODO(gri) ensure that literals always report
// their dynamic (never interface) type.
// This is not the case yet.
check.recordTypeAndValue(e, typ, val) check.recordTypeAndValue(e, typ, val)
} }
if trace {
check.indent--
check.trace(e.Pos(), "=> %s", x)
}
return kind return kind
} }
@ -1148,7 +1142,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
// (not a constant) even if the string and the // (not a constant) even if the string and the
// index are constant // index are constant
x.mode = value x.mode = value
x.typ = Typ[Byte] x.typ = universeByte // use 'byte' name
} }
case *Array: case *Array:

View File

@ -288,22 +288,20 @@ func isIdenticalInternal(x, y Type, p *ifacePair) bool {
// //
func defaultType(typ Type) Type { func defaultType(typ Type) Type {
if t, ok := typ.(*Basic); ok { if t, ok := typ.(*Basic); ok {
k := t.kind switch t.kind {
switch k {
case UntypedBool: case UntypedBool:
k = Bool return Typ[Bool]
case UntypedInt: case UntypedInt:
k = Int return Typ[Int]
case UntypedRune: case UntypedRune:
k = Rune return universeRune // use 'rune' name
case UntypedFloat: case UntypedFloat:
k = Float64 return Typ[Float64]
case UntypedComplex: case UntypedComplex:
k = Complex128 return Typ[Complex128]
case UntypedString: case UntypedString:
k = String return Typ[String]
} }
typ = Typ[k]
} }
return typ return typ
} }

View File

@ -609,7 +609,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
case *Basic: case *Basic:
if isString(typ) { if isString(typ) {
key = Typ[Int] key = Typ[Int]
val = Typ[Rune] val = universeRune // use 'rune' name
} }
case *Array: case *Array:
key = Typ[Int] key = Typ[Int]

View File

@ -19,8 +19,8 @@ func f(x int, m map[string]int) {
// constants // constants
const c1 = 991 const c1 = 991
const c2 float32 = 0.5 const c2 float32 = 0.5
0 /* ERROR "0 \(untyped integer constant\) is not used" */ 0 /* ERROR "0 \(untyped int constant\) is not used" */
c1 /* ERROR "c1 \(untyped integer constant 991\) is not used" */ c1 /* ERROR "c1 \(untyped int constant 991\) is not used" */
c2 /* ERROR "c2 \(constant 1/2 of type float32\) is not used" */ c2 /* ERROR "c2 \(constant 1/2 of type float32\) is not used" */
c1 /* ERROR "c1 \+ c2 \(constant 1983/2 of type float32\) is not used" */ + c2 c1 /* ERROR "c1 \+ c2 \(constant 1983/2 of type float32\) is not used" */ + c2
@ -28,7 +28,7 @@ func f(x int, m map[string]int) {
x /* ERROR "x \(variable of type int\) is not used" */ x /* ERROR "x \(variable of type int\) is not used" */
// values // values
x /* ERROR "x != x \(untyped boolean value\) is not used" */ != x x /* ERROR "x != x \(untyped bool value\) is not used" */ != x
x /* ERROR "x \+ x \(value of type int\) is not used" */ + x x /* ERROR "x \+ x \(value of type int\) is not used" */ + x
// value, ok's // value, ok's

View File

@ -123,23 +123,21 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
// If cycleOk is set, e (or elements of e) may refer to a named type that is not // If cycleOk is set, e (or elements of e) may refer to a named type that is not
// yet completely set up. // yet completely set up.
// //
func (check *checker) typ(e ast.Expr, def *Named, cycleOk bool) Type { func (check *checker) typ(e ast.Expr, def *Named, cycleOk bool) (T Type) {
if trace { if trace {
check.trace(e.Pos(), "%s", e) check.trace(e.Pos(), "%s", e)
check.indent++ check.indent++
} defer func() {
t := check.typInternal(e, def, cycleOk)
assert(e != nil && t != nil && isTyped(t))
check.recordTypeAndValue(e, t, nil)
if trace {
check.indent-- check.indent--
check.trace(e.Pos(), "=> %s", t) check.trace(e.Pos(), "=> %s", T)
}()
} }
return t T = check.typInternal(e, def, cycleOk)
assert(isTyped(T))
check.recordTypeAndValue(e, T, nil)
return
} }
// funcType type-checks a function or method type and returns its signature. // funcType type-checks a function or method type and returns its signature.

View File

@ -17,6 +17,8 @@ var (
Universe *Scope Universe *Scope
Unsafe *Package Unsafe *Package
universeIota *Const universeIota *Const
universeByte *Basic
universeRune *Basic
) )
var Typ = [...]*Basic{ var Typ = [...]*Basic{
@ -41,8 +43,8 @@ var Typ = [...]*Basic{
String: {String, IsString, 0, "string"}, String: {String, IsString, 0, "string"},
UnsafePointer: {UnsafePointer, 0, 0, "Pointer"}, UnsafePointer: {UnsafePointer, 0, 0, "Pointer"},
UntypedBool: {UntypedBool, IsBoolean | IsUntyped, 0, "untyped boolean"}, UntypedBool: {UntypedBool, IsBoolean | IsUntyped, 0, "untyped bool"},
UntypedInt: {UntypedInt, IsInteger | IsUntyped, 0, "untyped integer"}, UntypedInt: {UntypedInt, IsInteger | IsUntyped, 0, "untyped int"},
UntypedRune: {UntypedRune, IsInteger | IsUntyped, 0, "untyped rune"}, UntypedRune: {UntypedRune, IsInteger | IsUntyped, 0, "untyped rune"},
UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, 0, "untyped float"}, UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, 0, "untyped float"},
UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, 0, "untyped complex"}, UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, 0, "untyped complex"},
@ -185,6 +187,8 @@ func init() {
defPredeclaredFuncs() defPredeclaredFuncs()
universeIota = Universe.Lookup("iota").(*Const) universeIota = Universe.Lookup("iota").(*Const)
universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic)
universeRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic)
} }
// Objects with names containing blanks are internal and not entered into // Objects with names containing blanks are internal and not entered into