blob: 4d7f1e3b63f274b6ce30aefa010f1037f104e6dd [file] [log] [blame]
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines operands and associated operations.
package types
import (
"bytes"
"go/ast"
"go/constant"
"go/token"
)
// An operandMode specifies the (addressing) mode of an operand.
type operandMode byte
const (
invalid operandMode = iota // operand is invalid
novalue // operand represents no value (result of a function call w/o result)
builtin // operand is a built-in function
typexpr // operand is a type
constant_ // operand is a constant; the operand's typ is a Basic type
variable // operand is an addressable variable
mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
value // operand is a computed value
commaok // like value, but operand may be used in a comma,ok expression
commaerr // like commaok, but second value is error, not boolean
cgofunc // operand is a cgo function
)
var operandModeString = [...]string{
invalid: "invalid operand",
novalue: "no value",
builtin: "built-in",
typexpr: "type",
constant_: "constant",
variable: "variable",
mapindex: "map index expression",
value: "value",
commaok: "comma, ok expression",
commaerr: "comma, error expression",
cgofunc: "cgo function",
}
// An operand represents an intermediate value during type checking.
// Operands have an (addressing) mode, the expression evaluating to
// the operand, the operand's type, a value for constants, and an id
// for built-in functions.
// The zero value of operand is a ready to use invalid operand.
//
type operand struct {
mode operandMode
expr ast.Expr
typ Type
val constant.Value
id builtinId
}
// Pos returns the position of the expression corresponding to x.
// If x is invalid the position is token.NoPos.
//
func (x *operand) Pos() token.Pos {
// x.expr may not be set if x is invalid
if x.expr == nil {
return token.NoPos
}
return x.expr.Pos()
}
// Operand string formats
// (not all "untyped" cases can appear due to the type system,
// but they fall out naturally here)
//
// mode format
//
// invalid <expr> ( <mode> )
// novalue <expr> ( <mode> )
// builtin <expr> ( <mode> )
// typexpr <expr> ( <mode> )
//
// constant <expr> (<untyped kind> <mode> )
// constant <expr> ( <mode> of type <typ>)
// constant <expr> (<untyped kind> <mode> <val> )
// constant <expr> ( <mode> <val> of type <typ>)
//
// variable <expr> (<untyped kind> <mode> )
// variable <expr> ( <mode> of type <typ>)
//
// mapindex <expr> (<untyped kind> <mode> )
// mapindex <expr> ( <mode> of type <typ>)
//
// value <expr> (<untyped kind> <mode> )
// value <expr> ( <mode> of type <typ>)
//
// commaok <expr> (<untyped kind> <mode> )
// commaok <expr> ( <mode> of type <typ>)
//
// commaerr <expr> (<untyped kind> <mode> )
// commaerr <expr> ( <mode> of type <typ>)
//
// cgofunc <expr> (<untyped kind> <mode> )
// cgofunc <expr> ( <mode> of type <typ>)
//
func operandString(x *operand, qf Qualifier) string {
// special-case nil
if x.mode == value && x.typ == Typ[UntypedNil] {
return "nil"
}
var buf bytes.Buffer
var expr string
if x.expr != nil {
expr = ExprString(x.expr)
} else {
switch x.mode {
case builtin:
expr = predeclaredFuncs[x.id].name
case typexpr:
expr = TypeString(x.typ, qf)
case constant_:
expr = x.val.String()
}
}
// <expr> (
if expr != "" {
buf.WriteString(expr)
buf.WriteString(" (")
}
// <untyped kind>
hasType := false
switch x.mode {
case invalid, novalue, builtin, typexpr:
// no type
default:
// should have a type, but be cautious (don't crash during printing)
if x.typ != nil {
if isUntyped(x.typ) {
buf.WriteString(x.typ.(*Basic).name)
buf.WriteByte(' ')
break
}
hasType = true
}
}
// <mode>
buf.WriteString(operandModeString[x.mode])
// <val>
if x.mode == constant_ {
if s := x.val.String(); s != expr {
buf.WriteByte(' ')
buf.WriteString(s)
}
}
// <typ>
if hasType {
if x.typ != Typ[Invalid] {
var intro string
if isGeneric(x.typ) {
intro = " of parameterized type "
} else {
intro = " of type "
}
buf.WriteString(intro)
WriteType(&buf, x.typ, qf)
if tpar, _ := x.typ.(*TypeParam); tpar != nil {
buf.WriteString(" constrained by ")
WriteType(&buf, tpar.bound, qf) // do not compute interface type sets here
}
} else {
buf.WriteString(" with invalid type")
}
}
// )
if expr != "" {
buf.WriteByte(')')
}
return buf.String()
}
func (x *operand) String() string {
return operandString(x, nil)
}
// setConst sets x to the untyped constant for literal lit.
func (x *operand) setConst(tok token.Token, lit string) {
var kind BasicKind
switch tok {
case token.INT:
kind = UntypedInt
case token.FLOAT:
kind = UntypedFloat
case token.IMAG:
kind = UntypedComplex
case token.CHAR:
kind = UntypedRune
case token.STRING:
kind = UntypedString
default:
unreachable()
}
val := constant.MakeFromLiteral(lit, tok, 0)
if val.Kind() == constant.Unknown {
x.mode = invalid
x.typ = Typ[Invalid]
return
}
x.mode = constant_
x.typ = Typ[kind]
x.val = val
}
// isNil reports whether x is the nil value.
func (x *operand) isNil() bool {
return x.mode == value && x.typ == Typ[UntypedNil]
}
// assignableTo reports whether x is assignable to a variable of type T. If the
// result is false and a non-nil reason is provided, it may be set to a more
// detailed explanation of the failure (result != ""). The returned error code
// is only valid if the (first) result is false. The check parameter may be nil
// if assignableTo is invoked through an exported API call, i.e., when all
// methods have been type-checked.
func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, errorCode) {
if x.mode == invalid || T == Typ[Invalid] {
return true, 0 // avoid spurious errors
}
V := x.typ
// x's type is identical to T
if Identical(V, T) {
return true, 0
}
Vu := under(V)
Tu := under(T)
Vp, _ := V.(*TypeParam)
Tp, _ := T.(*TypeParam)
// x is an untyped value representable by a value of type T.
if isUntyped(Vu) {
assert(Vp == nil)
if Tp != nil {
// T is a type parameter: x is assignable to T if it is
// representable by each specific type in the type set of T.
return Tp.is(func(t *term) bool {
if t == nil {
return false
}
// A term may be a tilde term but the underlying
// type of an untyped value doesn't change so we
// don't need to do anything special.
newType, _, _ := check.implicitTypeAndValue(x, t.typ)
return newType != nil
}), _IncompatibleAssign
}
newType, _, _ := check.implicitTypeAndValue(x, T)
return newType != nil, _IncompatibleAssign
}
// Vu is typed
// x's type V and T have identical underlying types
// and at least one of V or T is not a named type
// and neither V nor T is a type parameter.
if Identical(Vu, Tu) && (!hasName(V) || !hasName(T)) && Vp == nil && Tp == nil {
return true, 0
}
// T is an interface type and x implements T and T is not a type parameter.
// Also handle the case where T is a pointer to an interface.
if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
if err := check.implements(V, T); err != nil {
if reason != nil {
*reason = err.Error()
}
return false, _InvalidIfaceAssign
}
return true, 0
}
// If V is an interface, check if a missing type assertion is the problem.
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
if check.implements(T, V) == nil {
// T implements V, so give hint about type assertion.
if reason != nil {
*reason = "need type assertion"
}
return false, _IncompatibleAssign
}
}
// x is a bidirectional channel value, T is a channel
// type, x's type V and T have identical element types,
// and at least one of V or T is not a named type.
if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv {
if Tc, ok := Tu.(*Chan); ok && Identical(Vc.elem, Tc.elem) {
return !hasName(V) || !hasName(T), _InvalidChanAssign
}
}
// optimization: if we don't have type parameters, we're done
if Vp == nil && Tp == nil {
return false, _IncompatibleAssign
}
errorf := func(format string, args ...any) {
if check != nil && reason != nil {
msg := check.sprintf(format, args...)
if *reason != "" {
msg += "\n\t" + *reason
}
*reason = msg
}
}
// x's type V is not a named type and T is a type parameter, and
// x is assignable to each specific type in T's type set.
if !hasName(V) && Tp != nil {
ok := false
code := _IncompatibleAssign
Tp.is(func(T *term) bool {
if T == nil {
return false // no specific types
}
ok, code = x.assignableTo(check, T.typ, reason)
if !ok {
errorf("cannot assign %s to %s (in %s)", x.typ, T.typ, Tp)
return false
}
return true
})
return ok, code
}
// x's type V is a type parameter and T is not a named type,
// and values x' of each specific type in V's type set are
// assignable to T.
if Vp != nil && !hasName(T) {
x := *x // don't clobber outer x
ok := false
code := _IncompatibleAssign
Vp.is(func(V *term) bool {
if V == nil {
return false // no specific types
}
x.typ = V.typ
ok, code = x.assignableTo(check, T, reason)
if !ok {
errorf("cannot assign %s (in %s) to %s", V.typ, Vp, T)
return false
}
return true
})
return ok, code
}
return false, _IncompatibleAssign
}