mirror of
https://github.com/golang/go
synced 2024-11-26 07:27:59 -07:00
[dev.typeparams] go/types: refactor untyped conversion for typeparams
Some logic was missing in the merge from dev.go2go to deal with untyped conversion of generic types. Part of this was due to the complexity of the merge, as untyped conversion had been refactored on master. Rather than back out the refactoring of untyped conversion, in this CL I have decided to take it one step further. It was always problematic that isRepresentable and canConvertUntyped mutated their arguments. In retrospect the refactoring was perhaps too conservative. This CL performs the following refactoring: + Replace 'isRepresentable' with 'representation': a Checker method produces the rounded representation of an untyped constant operand as a target type. + Make some functions return error codes rather than errors, and factor out the construction of the error message for invalid conversion. This avoided some indirect code. + Replace implicitType with implicitTypeAndValue, and have it handle the case of a constant basic operand, returning the rounded value. + Eliminate canConvertUntyped, lifting the logic to update expr types and values to the two callers. + Add handling for Sum types in implicitTypeAndValue. Here, the decision was made to depart from dev.go2go (and types2), and produce a Sum type as output. This seemed most correct on first principles, and tests still passed (though some logic for recording types had to be updated to allow for Sum types). Change-Id: Ic93901f69e6671b83b14ee2bf185a4ed767e31ee Reviewed-on: https://go-review.googlesource.com/c/go/+/284256 Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org> Trust: Robert Griesemer <gri@golang.org> Trust: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
d8796b5670
commit
734cb8be0a
@ -7,7 +7,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
@ -46,27 +45,30 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
|
||||
}
|
||||
target = Default(x.typ)
|
||||
}
|
||||
if err := check.canConvertUntyped(x, target); err != nil {
|
||||
newType, val, code := check.implicitTypeAndValue(x, target)
|
||||
if code != 0 {
|
||||
msg := check.sprintf("cannot use %s as %s value in %s", x, target, context)
|
||||
code := _IncompatibleAssign
|
||||
var ierr Error
|
||||
if errors.As(err, &ierr) {
|
||||
// Preserve these inner errors, as they are informative.
|
||||
switch ierr.go116code {
|
||||
switch code {
|
||||
case _TruncatedFloat:
|
||||
msg += " (truncated)"
|
||||
code = ierr.go116code
|
||||
case _NumericOverflow:
|
||||
msg += " (overflows)"
|
||||
code = ierr.go116code
|
||||
}
|
||||
default:
|
||||
code = _IncompatibleAssign
|
||||
}
|
||||
check.error(x, code, msg)
|
||||
x.mode = invalid
|
||||
return
|
||||
}
|
||||
if val != nil {
|
||||
x.val = val
|
||||
check.updateExprVal(x.expr, val)
|
||||
}
|
||||
if newType != x.typ {
|
||||
x.typ = newType
|
||||
check.updateExprType(x.expr, newType, false)
|
||||
}
|
||||
}
|
||||
// x.typ is typed
|
||||
|
||||
// A generic (non-instantiated) function value cannot be assigned to a variable.
|
||||
if sig := asSignature(x.typ); sig != nil && len(sig.tparams) > 0 {
|
||||
|
@ -338,17 +338,18 @@ func representableConst(x constant.Value, check *Checker, typ *Basic, rounded *c
|
||||
// representable checks that a constant operand is representable in the given
|
||||
// basic type.
|
||||
func (check *Checker) representable(x *operand, typ *Basic) {
|
||||
if err := check.isRepresentable(x, typ); err != nil {
|
||||
if v, code := check.representation(x, typ); code != 0 {
|
||||
check.invalidConversion(code, x, typ)
|
||||
x.mode = invalid
|
||||
check.err(err)
|
||||
} else if v != nil {
|
||||
x.val = v
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) isRepresentable(x *operand, typ *Basic) error {
|
||||
func (check *Checker) representation(x *operand, typ *Basic) (constant.Value, errorCode) {
|
||||
assert(x.mode == constant_)
|
||||
if !representableConst(x.val, check, typ, &x.val) {
|
||||
var msg string
|
||||
var code errorCode
|
||||
v := x.val
|
||||
if !representableConst(x.val, check, typ, &v) {
|
||||
if isNumeric(x.typ) && isNumeric(typ) {
|
||||
// numeric conversion : error msg
|
||||
//
|
||||
@ -358,19 +359,25 @@ func (check *Checker) isRepresentable(x *operand, typ *Basic) error {
|
||||
// float -> float : overflows
|
||||
//
|
||||
if !isInteger(x.typ) && isInteger(typ) {
|
||||
return nil, _TruncatedFloat
|
||||
} else {
|
||||
return nil, _NumericOverflow
|
||||
}
|
||||
}
|
||||
return nil, _InvalidConstVal
|
||||
}
|
||||
return v, 0
|
||||
}
|
||||
|
||||
func (check *Checker) invalidConversion(code errorCode, x *operand, target Type) {
|
||||
msg := "cannot convert %s to %s"
|
||||
switch code {
|
||||
case _TruncatedFloat:
|
||||
msg = "%s truncated to %s"
|
||||
code = _TruncatedFloat
|
||||
} else {
|
||||
case _NumericOverflow:
|
||||
msg = "%s overflows %s"
|
||||
code = _NumericOverflow
|
||||
}
|
||||
} else {
|
||||
msg = "cannot convert %s to %s"
|
||||
code = _InvalidConstVal
|
||||
}
|
||||
return check.newErrorf(x, code, false, msg, x, typ)
|
||||
}
|
||||
return nil
|
||||
check.errorf(x, code, msg, x, target)
|
||||
}
|
||||
|
||||
// updateExprType updates the type of x to typ and invokes itself
|
||||
@ -506,16 +513,29 @@ func (check *Checker) updateExprVal(x ast.Expr, val constant.Value) {
|
||||
|
||||
// convertUntyped attempts to set the type of an untyped value to the target type.
|
||||
func (check *Checker) convertUntyped(x *operand, target Type) {
|
||||
if err := check.canConvertUntyped(x, target); err != nil {
|
||||
newType, val, code := check.implicitTypeAndValue(x, target)
|
||||
if code != 0 {
|
||||
check.invalidConversion(code, x, target.Underlying())
|
||||
x.mode = invalid
|
||||
check.err(err)
|
||||
return
|
||||
}
|
||||
if val != nil {
|
||||
x.val = val
|
||||
check.updateExprVal(x.expr, val)
|
||||
}
|
||||
if newType != x.typ {
|
||||
x.typ = newType
|
||||
check.updateExprType(x.expr, newType, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (check *Checker) canConvertUntyped(x *operand, target Type) error {
|
||||
// implicitTypeAndValue returns the implicit type of x when used in a context
|
||||
// where the target type is expected. If no such implicit conversion is
|
||||
// possible, it returns a nil Type.
|
||||
func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, constant.Value, errorCode) {
|
||||
target = expand(target)
|
||||
if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] {
|
||||
return nil
|
||||
return x.typ, nil, 0
|
||||
}
|
||||
|
||||
if isUntyped(target) {
|
||||
@ -524,43 +544,23 @@ func (check *Checker) canConvertUntyped(x *operand, target Type) error {
|
||||
tkind := target.(*Basic).kind
|
||||
if isNumeric(x.typ) && isNumeric(target) {
|
||||
if xkind < tkind {
|
||||
x.typ = target
|
||||
check.updateExprType(x.expr, target, false)
|
||||
return target, nil, 0
|
||||
}
|
||||
} else if xkind != tkind {
|
||||
return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target)
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
return nil
|
||||
return x.typ, nil, 0
|
||||
}
|
||||
|
||||
if t, ok := target.Underlying().(*Basic); ok && x.mode == constant_ {
|
||||
if err := check.isRepresentable(x, t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Expression value may have been rounded - update if needed.
|
||||
check.updateExprVal(x.expr, x.val)
|
||||
} else {
|
||||
newTarget := check.implicitType(x, target)
|
||||
if newTarget == nil {
|
||||
return check.newErrorf(x, _InvalidUntypedConversion, false, "cannot convert %s to %s", x, target)
|
||||
}
|
||||
target = newTarget
|
||||
}
|
||||
x.typ = target
|
||||
// Even though implicitType can return UntypedNil, this value is final: the
|
||||
// predeclared identifier nil has no type.
|
||||
check.updateExprType(x.expr, target, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// implicitType returns the implicit type of x when used in a context where the
|
||||
// target type is expected. If no such implicit conversion is possible, it
|
||||
// returns nil.
|
||||
func (check *Checker) implicitType(x *operand, target Type) Type {
|
||||
assert(isUntyped(x.typ))
|
||||
switch t := target.Underlying().(type) {
|
||||
switch t := optype(target).(type) {
|
||||
case *Basic:
|
||||
assert(x.mode != constant_)
|
||||
if x.mode == constant_ {
|
||||
v, code := check.representation(x, t)
|
||||
if code != 0 {
|
||||
return nil, nil, code
|
||||
}
|
||||
return target, v, code
|
||||
}
|
||||
// Non-constant untyped values may appear as the
|
||||
// result of comparisons (untyped bool), intermediate
|
||||
// (delayed-checked) rhs operands of shifts, and as
|
||||
@ -568,26 +568,39 @@ func (check *Checker) implicitType(x *operand, target Type) Type {
|
||||
switch x.typ.(*Basic).kind {
|
||||
case UntypedBool:
|
||||
if !isBoolean(target) {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex:
|
||||
if !isNumeric(target) {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
case UntypedString:
|
||||
// Non-constant untyped string values are not permitted by the spec and
|
||||
// should not occur during normal typechecking passes, but this path is
|
||||
// reachable via the AssignableTo API.
|
||||
if !isString(target) {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
case UntypedNil:
|
||||
// Unsafe.Pointer is a basic type that includes nil.
|
||||
if !hasNil(target) {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
// TODO(rFindley) return UntypedNil here (golang.org/issues/13061).
|
||||
default:
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
case *Sum:
|
||||
ok := t.is(func(t Type) bool {
|
||||
target, _, _ := check.implicitTypeAndValue(x, t)
|
||||
return target != nil
|
||||
})
|
||||
if !ok {
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
// keep nil untyped (was bug #39755)
|
||||
if x.isNil() {
|
||||
return Typ[UntypedNil], nil, 0
|
||||
}
|
||||
case *Interface:
|
||||
// Values must have concrete dynamic types. If the value is nil,
|
||||
@ -595,24 +608,24 @@ func (check *Checker) implicitType(x *operand, target Type) Type {
|
||||
// need the dynamic type for argument checking of say, print
|
||||
// functions)
|
||||
if x.isNil() {
|
||||
return Typ[UntypedNil]
|
||||
return Typ[UntypedNil], nil, 0
|
||||
}
|
||||
// cannot assign untyped values to non-empty interfaces
|
||||
check.completeInterface(token.NoPos, t)
|
||||
if !t.Empty() {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
return Default(x.typ)
|
||||
return Default(x.typ), nil, 0
|
||||
case *Pointer, *Signature, *Slice, *Map, *Chan:
|
||||
if !x.isNil() {
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
// Keep nil untyped - see comment for interfaces, above.
|
||||
return Typ[UntypedNil]
|
||||
return Typ[UntypedNil], nil, 0
|
||||
default:
|
||||
return nil
|
||||
return nil, nil, _InvalidUntypedConversion
|
||||
}
|
||||
return target
|
||||
return target, nil, 0
|
||||
}
|
||||
|
||||
func (check *Checker) comparison(x, y *operand, op token.Token) {
|
||||
|
@ -242,20 +242,15 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
|
||||
|
||||
// x is an untyped value representable by a value of type T.
|
||||
if isUntyped(Vu) {
|
||||
// TODO(rFindley) synchronize this block of code with types2
|
||||
switch t := Tu.(type) {
|
||||
case *Basic:
|
||||
if x.mode == constant_ {
|
||||
return representableConst(x.val, check, t, nil), _IncompatibleAssign
|
||||
}
|
||||
case *Sum:
|
||||
if t, ok := Tu.(*Sum); ok {
|
||||
return t.is(func(t Type) bool {
|
||||
// TODO(gri) this could probably be more efficient
|
||||
ok, _ := x.assignableTo(check, t, reason)
|
||||
return ok
|
||||
}), _IncompatibleAssign
|
||||
}
|
||||
return check.implicitType(x, Tu) != nil, _IncompatibleAssign
|
||||
newType, _, _ := check.implicitTypeAndValue(x, Tu)
|
||||
return newType != nil, _IncompatibleAssign
|
||||
}
|
||||
// Vu is typed
|
||||
|
||||
|
@ -75,11 +75,7 @@ func isUntyped(typ Type) bool {
|
||||
}
|
||||
|
||||
func isOrdered(typ Type) bool { return is(typ, IsOrdered) }
|
||||
|
||||
func isConstType(typ Type) bool {
|
||||
t := asBasic(typ)
|
||||
return t != nil && t.info&IsConstType != 0
|
||||
}
|
||||
func isConstType(typ Type) bool { return is(typ, IsConstType) }
|
||||
|
||||
// IsInterface reports whether typ is an interface type.
|
||||
func IsInterface(typ Type) bool {
|
||||
|
Loading…
Reference in New Issue
Block a user