go.tools/go/types: do rounding in constant numeric conversions

- cleaned up surrounding code
- adjusted error message positions for too few/many argument errors
- added more conversion tests
- added all tests under test/ken to std tests

R=adonovan, r
TBR=adonovan
CC=golang-dev
https://golang.org/cl/12711043
This commit is contained in:
Robert Griesemer 2013-08-12 14:57:08 -07:00
parent 508ae115ae
commit d13d94afe7
10 changed files with 293 additions and 125 deletions

View File

@ -173,7 +173,6 @@ func IsAssignableTo(V, T Type) bool {
return x.isAssignableTo(nil, T) // config not needed for non-constant x
}
// BUG(gri): Conversions of constants only change the type, not the value (e.g., int(1.1) is wrong).
// BUG(gri): Some built-ins don't check parameters fully, yet (e.g. append).
// BUG(gri): Use of labels is only partially checked.
// BUG(gri): Unused variables and imports are not reported.

View File

@ -26,13 +26,29 @@ func (check *checker) call(x *operand, e *ast.CallExpr) {
}
if x.mode == typexpr {
check.conversion(x, e, x.typ)
// conversion
T := x.typ
x.mode = invalid
switch n := len(e.Args); n {
case 0:
check.errorf(e.Rparen, "missing argument in conversion to %s", T)
case 1:
check.expr(x, e.Args[0])
if x.mode != invalid {
check.conversion(x, T)
if x.mode != invalid {
check.conversions[e] = true // for cap/len checking
}
}
default:
check.errorf(e.Args[n-1].Pos(), "too many arguments in conversion to %s", T)
}
x.expr = e
return
}
if sig, ok := x.typ.Underlying().(*Signature); ok {
// function/method call
passSlice := false
if e.Ellipsis.IsValid() {
// last argument is of the form x...
@ -84,7 +100,7 @@ func (check *checker) call(x *operand, e *ast.CallExpr) {
n++
}
if n < sig.params.Len() {
check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun)
check.errorf(e.Rparen, "too few arguments in call to %s", e.Fun)
// ok to continue
}

View File

@ -6,41 +6,18 @@
package types
import (
"go/ast"
import "code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/exact"
)
// conversion typechecks the type conversion conv to type typ.
// The result of the conversion is returned via x.
// If the conversion has type errors, the returned
// x is marked as invalid (x.mode == invalid).
//
func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type) {
var final Type // declare before gotos
// all conversions have one argument
if len(conv.Args) != 1 {
check.invalidOp(conv.Pos(), "%s conversion requires exactly one argument", conv)
goto Error
}
// evaluate argument
check.expr(x, conv.Args[0])
if x.mode == invalid {
goto Error
}
if x.mode == constant && isConstType(typ) {
// constant conversion
typ := typ.Underlying().(*Basic)
// For now just implement string(x) where x is an integer,
// as a temporary work-around for issue 4982, which is a
// common issue.
if typ.kind == String {
// Conversion type-checks the conversion T(x).
// The result is in x.
func (check *checker) conversion(x *operand, T Type) {
switch {
case x.isInteger():
case x.mode == constant && isConstType(T):
// constant conversion
switch t := T.Underlying().(*Basic); {
case isRepresentableConst(x.val, check.conf, t.kind, &x.val):
// nothing to do
case x.isInteger() && isString(t):
codepoint := int64(-1)
if i, ok := exact.Int64Val(x.val); ok {
codepoint = i
@ -49,46 +26,37 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type) {
// conversion. This is the same as converting any other out-of-range
// value - let string(codepoint) do the work.
x.val = exact.MakeString(string(codepoint))
case isString(x.typ):
// nothing to do
default:
goto ErrorMsg
x.mode = invalid
}
}
// TODO(gri) verify the remaining conversions.
} else {
case x.isConvertible(check.conf, T):
// non-constant conversion
if !x.isConvertible(check.conf, typ) {
goto ErrorMsg
}
x.mode = value
default:
x.mode = invalid
}
if x.mode == invalid {
check.errorf(x.pos(), "cannot convert %s to %s", x, T)
return
}
// The conversion argument types are final. For untyped values the
// conversion provides the type, per the spec: "A constant may be
// given a type explicitly by a constant declaration or conversion,...".
final = x.typ
final := x.typ
if isUntyped(final) {
final = typ
final = T
// For conversions to interfaces, use the argument type's
// default type instead. Keep untyped nil for untyped nil
// arguments.
if _, ok := typ.Underlying().(*Interface); ok {
if _, ok := T.Underlying().(*Interface); ok {
final = defaultType(x.typ)
}
}
x.typ = T
check.updateExprType(x.expr, final, true)
check.conversions[conv] = true // for cap/len checking
x.expr = conv
x.typ = typ
return
ErrorMsg:
check.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ)
Error:
x.mode = invalid
x.expr = conv
}
func (x *operand) isConvertible(conf *Config, T Type) bool {

View File

@ -158,18 +158,46 @@ func isComparison(op token.Token) bool {
func fitsFloat32(x exact.Value) bool {
f, _ := exact.Float64Val(x)
// We assume that float32(f) returns an Inf if f
// cannot be represented as a float32, or if f
// is an Inf. This is not supported by the spec.
// spec: "In all non-constant conversions involving floating-point
// or complex values, if the result type cannot represent the value
// the conversion succeeds but the result value is implementation-
// dependent."
//
// We assume that float32(f) returns an Inf if f cannot be represented
// as a float32, or if f is an Inf.
return !math.IsInf(float64(float32(f)), 0)
}
func roundFloat32(x exact.Value) exact.Value {
f, _ := exact.Float64Val(x)
f = float64(float32(f))
if !math.IsInf(f, 0) {
return exact.MakeFloat64(f)
}
return nil
}
func fitsFloat64(x exact.Value) bool {
f, _ := exact.Float64Val(x)
return !math.IsInf(f, 0)
}
func isRepresentableConst(x exact.Value, conf *Config, as BasicKind) bool {
func roundFloat64(x exact.Value) exact.Value {
f, _ := exact.Float64Val(x)
if !math.IsInf(f, 0) {
return exact.MakeFloat64(f)
}
return nil
}
// isRepresentableConst reports whether x can be represented as
// value of the given basic type kind and for the configuration
// provided (only needed for int/uint sizes).
//
// If rounded != nil, *rounded is set to the rounded value of x for
// representable floating-point values; it is left alone otherwise.
// It is ok to provide the addressof the first argument for rounded.
func isRepresentableConst(x exact.Value, conf *Config, as BasicKind, rounded *exact.Value) bool {
switch x.Kind() {
case exact.Unknown:
return true
@ -224,9 +252,23 @@ func isRepresentableConst(x exact.Value, conf *Config, as BasicKind) bool {
case Uint64:
return exact.Sign(x) >= 0 && n <= 64
case Float32, Complex64:
if rounded == nil {
return fitsFloat32(x)
}
r := roundFloat32(x)
if r != nil {
*rounded = r
return true
}
case Float64, Complex128:
if rounded == nil {
return fitsFloat64(x)
}
r := roundFloat64(x)
if r != nil {
*rounded = r
return true
}
case UntypedInt, UntypedFloat, UntypedComplex:
return true
}
@ -234,9 +276,23 @@ func isRepresentableConst(x exact.Value, conf *Config, as BasicKind) bool {
case exact.Float:
switch as {
case Float32, Complex64:
if rounded == nil {
return fitsFloat32(x)
}
r := roundFloat32(x)
if r != nil {
*rounded = r
return true
}
case Float64, Complex128:
if rounded == nil {
return fitsFloat64(x)
}
r := roundFloat64(x)
if r != nil {
*rounded = r
return true
}
case UntypedFloat, UntypedComplex:
return true
}
@ -244,9 +300,25 @@ func isRepresentableConst(x exact.Value, conf *Config, as BasicKind) bool {
case exact.Complex:
switch as {
case Complex64:
if rounded == nil {
return fitsFloat32(exact.Real(x)) && fitsFloat32(exact.Imag(x))
}
re := roundFloat32(exact.Real(x))
im := roundFloat32(exact.Imag(x))
if re != nil && im != nil {
*rounded = exact.BinaryOp(re, token.ADD, exact.MakeImag(im))
return true
}
case Complex128:
if rounded == nil {
return fitsFloat64(exact.Real(x)) && fitsFloat64(exact.Imag(x))
}
re := roundFloat64(exact.Real(x))
im := roundFloat64(exact.Imag(x))
if re != nil && im != nil {
*rounded = exact.BinaryOp(re, token.ADD, exact.MakeImag(im))
return true
}
case UntypedComplex:
return true
}
@ -270,7 +342,7 @@ func (check *checker) isRepresentable(x *operand, typ *Basic) {
return
}
if !isRepresentableConst(x.val, check.conf, typ.kind) {
if !isRepresentableConst(x.val, check.conf, typ.kind, &x.val) {
var msg string
if isNumeric(x.typ) && isNumeric(typ) {
msg = "%s overflows (or cannot be accurately represented as) %s"
@ -512,7 +584,7 @@ func (check *checker) shift(x, y *operand, op token.Token) {
// The lhs must be of integer type or be representable
// as an integer; otherwise the shift has no chance.
if !isInteger(x.typ) && (!untypedx || !isRepresentableConst(x.val, nil, UntypedInt)) {
if !isInteger(x.typ) && (!untypedx || !isRepresentableConst(x.val, nil, UntypedInt, nil)) {
check.invalidOp(x.pos(), "shifted operand %s must be integer", x)
x.mode = invalid
return

View File

@ -184,7 +184,7 @@ func (x *operand) isAssignableTo(conf *Config, T Type) bool {
switch t := Tu.(type) {
case *Basic:
if x.mode == constant {
return isRepresentableConst(x.val, conf, t.kind)
return isRepresentableConst(x.val, conf, t.kind, nil)
}
// The result of a comparison is an untyped boolean,
// but may not be a constant.
@ -205,5 +205,5 @@ func (x *operand) isAssignableTo(conf *Config, T Type) bool {
func (x *operand) isInteger() bool {
return x.mode == invalid ||
isInteger(x.typ) ||
x.mode == constant && isRepresentableConst(x.val, nil, UntypedInt) // no *Config required for UntypedInt
x.mode == constant && isRepresentableConst(x.val, nil, UntypedInt, nil) // no *Config required for UntypedInt
}

View File

@ -105,9 +105,10 @@ func TestStdtest(t *testing.T) {
}
func TestStdfixed(t *testing.T) {
testTestDir(t, filepath.Join(runtime.GOROOT(), "test/fixedbugs"),
testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"),
"bug050.go", "bug088.go", "bug106.go", // TODO(gri) parser loses comments when bailing out early
"bug222.go", "bug282.go", "bug306.go", // TODO(gri) parser loses comments when bailing out early
"issue4776.go", // TODO(gri) parser loses comments when bailing out early
"bug136.go", "bug179.go", "bug344.go", // TODO(gri) implement missing label checks
"bug251.go", // TODO(gri) incorrect cycle checks for interface types
"bug165.go", // TODO(gri) isComparable not working for incomplete struct type
@ -123,6 +124,10 @@ func TestStdfixed(t *testing.T) {
)
}
func TestStdken(t *testing.T) {
testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken"))
}
// Package paths of excluded packages.
var excluded = map[string]bool{
"builtin": true,

View File

@ -56,138 +56,249 @@ const (
maxFloat64 = 1<<1023 * (1<<53 - 1) / (1.0<<52)
)
var (
const (
_ int8 = minInt8 /* ERROR "overflows" */ - 1
_ int8 = minInt8
_ int8 = maxInt8
_ int8 = maxInt8 /* ERROR "overflows" */ + 1
_ int8 = smallestFloat64 /* ERROR "overflows" */
_ = int8(minInt8 /* ERROR "cannot convert" */ - 1)
_ = int8(minInt8)
_ = int8(maxInt8)
_ = int8(maxInt8 /* ERROR "cannot convert" */ + 1)
_ = int8(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ int16 = minInt16 /* ERROR "overflows" */ - 1
_ int16 = minInt16
_ int16 = maxInt16
_ int16 = maxInt16 /* ERROR "overflows" */ + 1
_ int16 = smallestFloat64 /* ERROR "overflows" */
_ = int16(minInt16 /* ERROR "cannot convert" */ - 1)
_ = int16(minInt16)
_ = int16(maxInt16)
_ = int16(maxInt16 /* ERROR "cannot convert" */ + 1)
_ = int16(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ int32 = minInt32 /* ERROR "overflows" */ - 1
_ int32 = minInt32
_ int32 = maxInt32
_ int32 = maxInt32 /* ERROR "overflows" */ + 1
_ int32 = smallestFloat64 /* ERROR "overflows" */
_ = int32(minInt32 /* ERROR "cannot convert" */ - 1)
_ = int32(minInt32)
_ = int32(maxInt32)
_ = int32(maxInt32 /* ERROR "cannot convert" */ + 1)
_ = int32(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ int64 = minInt64 /* ERROR "overflows" */ - 1
_ int64 = minInt64
_ int64 = maxInt64
_ int64 = maxInt64 /* ERROR "overflows" */ + 1
_ int64 = smallestFloat64 /* ERROR "overflows" */
_ = int64(minInt64 /* ERROR "cannot convert" */ - 1)
_ = int64(minInt64)
_ = int64(maxInt64)
_ = int64(maxInt64 /* ERROR "cannot convert" */ + 1)
_ = int64(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ int = minInt /* ERROR "overflows" */ - 1
_ int = minInt
_ int = maxInt
_ int = maxInt /* ERROR "overflows" */ + 1
_ int = smallestFloat64 /* ERROR "overflows" */
_ = int(minInt /* ERROR "cannot convert" */ - 1)
_ = int(minInt)
_ = int(maxInt)
_ = int(maxInt /* ERROR "cannot convert" */ + 1)
_ = int(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uint8 = 0 /* ERROR "overflows" */ - 1
_ uint8 = 0
_ uint8 = maxUint8
_ uint8 = maxUint8 /* ERROR "overflows" */ + 1
_ uint8 = smallestFloat64 /* ERROR "overflows" */
_ = uint8(0 /* ERROR "cannot convert" */ - 1)
_ = uint8(0)
_ = uint8(maxUint8)
_ = uint8(maxUint8 /* ERROR "cannot convert" */ + 1)
_ = uint8(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uint16 = 0 /* ERROR "overflows" */ - 1
_ uint16 = 0
_ uint16 = maxUint16
_ uint16 = maxUint16 /* ERROR "overflows" */ + 1
_ uint16 = smallestFloat64 /* ERROR "overflows" */
_ = uint16(0 /* ERROR "cannot convert" */ - 1)
_ = uint16(0)
_ = uint16(maxUint16)
_ = uint16(maxUint16 /* ERROR "cannot convert" */ + 1)
_ = uint16(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uint32 = 0 /* ERROR "overflows" */ - 1
_ uint32 = 0
_ uint32 = maxUint32
_ uint32 = maxUint32 /* ERROR "overflows" */ + 1
_ uint32 = smallestFloat64 /* ERROR "overflows" */
_ = uint32(0 /* ERROR "cannot convert" */ - 1)
_ = uint32(0)
_ = uint32(maxUint32)
_ = uint32(maxUint32 /* ERROR "cannot convert" */ + 1)
_ = uint32(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uint64 = 0 /* ERROR "overflows" */ - 1
_ uint64 = 0
_ uint64 = maxUint64
_ uint64 = maxUint64 /* ERROR "overflows" */ + 1
_ uint64 = smallestFloat64 /* ERROR "overflows" */
_ = uint64(0 /* ERROR "cannot convert" */ - 1)
_ = uint64(0)
_ = uint64(maxUint64)
_ = uint64(maxUint64 /* ERROR "cannot convert" */ + 1)
_ = uint64(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uint = 0 /* ERROR "overflows" */ - 1
_ uint = 0
_ uint = maxUint
_ uint = maxUint /* ERROR "overflows" */ + 1
_ uint = smallestFloat64 /* ERROR "overflows" */
_ = uint(0 /* ERROR "cannot convert" */ - 1)
_ = uint(0)
_ = uint(maxUint)
_ = uint(maxUint /* ERROR "cannot convert" */ + 1)
_ = uint(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ uintptr = 0 /* ERROR "overflows" */ - 1
_ uintptr = 0
_ uintptr = maxUintptr
_ uintptr = maxUintptr /* ERROR "overflows" */ + 1
_ uintptr = smallestFloat64 /* ERROR "overflows" */
_ = uintptr(0 /* ERROR "cannot convert" */ - 1)
_ = uintptr(0)
_ = uintptr(maxUintptr)
_ = uintptr(maxUintptr /* ERROR "cannot convert" */ + 1)
_ = uintptr(smallestFloat64 /* ERROR "cannot convert" */)
)
var (
const (
_ float32 = minInt64
_ float64 = minInt64
_ complex64 = minInt64
_ complex128 = minInt64
_ = float32(minInt64)
_ = float64(minInt64)
_ = complex64(minInt64)
_ = complex128(minInt64)
)
var (
const (
_ float32 = maxUint64
_ float64 = maxUint64
_ complex64 = maxUint64
_ complex128 = maxUint64
_ = float32(maxUint64)
_ = float64(maxUint64)
_ = complex64(maxUint64)
_ = complex128(maxUint64)
)
// TODO(gri) find smaller deltas below
const delta32 = maxFloat32 >> 23
const delta32 = maxFloat32/(1 << 23)
var (
const (
_ float32 = - /* ERROR "overflow" */ (maxFloat32 + delta32)
_ float32 = -maxFloat32
_ float32 = maxFloat32
_ float32 = maxFloat32 /* ERROR "overflow" */ + delta32
_ = float32(- /* ERROR "cannot convert" */ (maxFloat32 + delta32))
_ = float32(-maxFloat32)
_ = float32(maxFloat32)
_ = float32(maxFloat32 /* ERROR "cannot convert" */ + delta32)
)
const delta64 = maxFloat64 >> 52
const delta64 = maxFloat64/(1 << 52)
var (
const (
_ float64 = - /* ERROR "overflow" */ (maxFloat64 + delta64)
_ float64 = -maxFloat64
_ float64 = maxFloat64
_ float64 = maxFloat64 /* ERROR "overflow" */ + delta64
_ = float64(- /* ERROR "cannot convert" */ (maxFloat64 + delta64))
_ = float64(-maxFloat64)
_ = float64(maxFloat64)
_ = float64(maxFloat64 /* ERROR "cannot convert" */ + delta64)
)
var (
const (
_ complex64 = - /* ERROR "overflow" */ (maxFloat32 + delta32)
_ complex64 = -maxFloat32
_ complex64 = maxFloat32
_ complex64 = maxFloat32 /* ERROR "overflow" */ + delta32
_ = complex64(- /* ERROR "cannot convert" */ (maxFloat32 + delta32))
_ = complex64(-maxFloat32)
_ = complex64(maxFloat32)
_ = complex64(maxFloat32 /* ERROR "cannot convert" */ + delta32)
)
var (
const (
_ complex128 = - /* ERROR "overflow" */ (maxFloat64 + delta64)
_ complex128 = -maxFloat64
_ complex128 = maxFloat64
_ complex128 = maxFloat64 /* ERROR "overflow" */ + delta64
_ = complex128(- /* ERROR "cannot convert" */ (maxFloat64 + delta64))
_ = complex128(-maxFloat64)
_ = complex128(maxFloat64)
_ = complex128(maxFloat64 /* ERROR "cannot convert" */ + delta64)
)
// Initialization of typed constant and conversion are the same:
const (
f32 = 1 + smallestFloat32
x32 float32 = f32
y32 = float32(f32)
_ = assert(x32 - y32 == 0)
)
const (
f64 = 1 + smallestFloat64
x64 float64 = f64
y64 = float64(f64)
_ = assert(x64 - y64 == 0)
)

View File

@ -8,10 +8,12 @@ package conversions
// argument count
var (
_ = int /* ERROR "one argument" */ ()
_ = int /* ERROR "one argument" */ (1, 2)
_ = int() /* ERROR "missing argument" */
_ = int(1, 2 /* ERROR "too many arguments" */ )
)
// numeric constant conversions are in const1.src.
func string_conversions() {
const A = string(65)
assert(A == "A")
@ -25,16 +27,11 @@ func string_conversions() {
type mystring string
const _ mystring = mystring("foo")
const _ = string /* ERROR "cannot convert" */ (true)
const _ = string /* ERROR "cannot convert" */ (1.2)
const _ = string /* ERROR "cannot convert" */ (nil)
const _ = string(true /* ERROR "cannot convert" */ )
const _ = string(1.2 /* ERROR "cannot convert" */ )
const _ = string(nil /* ERROR "cannot convert" */ )
}
//
var (
_ = int8(0)
)
func interface_conversions() {
type E interface{}
@ -63,20 +60,20 @@ func interface_conversions() {
_ = E(i1)
_ = E(i2)
_ = I1 /* ERROR "cannot convert" */ (0)
_ = I1(0 /* ERROR "cannot convert" */ )
_ = I1(nil)
_ = I1(i1)
_ = I1 /* ERROR "cannot convert" */ (e)
_ = I1(e /* ERROR "cannot convert" */ )
_ = I1(i2)
_ = I2(nil)
_ = I2 /* ERROR "cannot convert" */ (i1)
_ = I2(i1 /* ERROR "cannot convert" */ )
_ = I2(i2)
_ = I2 /* ERROR "cannot convert" */ (i3)
_ = I2(i3 /* ERROR "cannot convert" */ )
_ = I3(nil)
_ = I3 /* ERROR "cannot convert" */ (i1)
_ = I3 /* ERROR "cannot convert" */ (i2)
_ = I3(i1 /* ERROR "cannot convert" */ )
_ = I3(i2 /* ERROR "cannot convert" */ )
_ = I3(i3)
// TODO(gri) add more tests, improve error message

View File

@ -317,7 +317,7 @@ func _calls() {
f1(0)
f1(x)
f1(10.0)
f1 /* ERROR "too few arguments" */ ()
f1() /* ERROR "too few arguments" */
f1(x, y /* ERROR "too many arguments" */ )
f1(s /* ERROR "cannot pass" */ )
f1(x ... /* ERROR "cannot use ..." */ )
@ -325,15 +325,15 @@ func _calls() {
f1(g1())
// f1(g2()) // TODO(gri) missing position in error message
f2 /* ERROR "too few arguments" */ ()
f2 /* ERROR "too few arguments" */ (3.14)
f2() /* ERROR "too few arguments" */
f2(3.14) /* ERROR "too few arguments" */
f2(3.14, "foo")
f2(x /* ERROR "cannot pass" */ , "foo")
f2(g0 /* ERROR "used as value" */ ())
f2 /* ERROR "too few arguments" */ (g1 /* ERROR "cannot pass" */ ())
f2(g1 /* ERROR "cannot pass" */ ()) /* ERROR "too few arguments" */
f2(g2())
fs /* ERROR "too few arguments" */ ()
fs() /* ERROR "too few arguments" */
fs(g0 /* ERROR "used as value" */ ())
fs(g1 /* ERROR "cannot pass" */ ())
fs(g2 /* ERROR "cannot pass" */ /* ERROR "too many arguments" */ ())

View File

@ -54,7 +54,7 @@ func assignments() {
g := func(int, bool){}
var m map[int]int
g/* ERROR "too few arguments" */ (m[0])
g(m[0]) /* ERROR "too few arguments" */
// assignments to _
_ = nil /* ERROR "use of untyped nil" */