package types
import (
"go/ast"
"go/constant"
"go/token"
. "internal/types/errors"
)
func (check *Checker ) builtin (x *operand , call *ast .CallExpr , id builtinId ) (_ bool ) {
argList := call .Args
bin := predeclaredFuncs [id ]
if hasDots (call ) && id != _Append {
check .errorf (dddErrPos (call ),
InvalidDotDotDot ,
invalidOp +"invalid use of ... with built-in %s" , bin .name )
check .use (argList ...)
return
}
if id == _Len || id == _Cap {
defer func (b bool ) {
check .hasCallOrRecv = b
}(check .hasCallOrRecv )
check .hasCallOrRecv = false
}
var args []*operand
var nargs int
switch id {
default :
args = check .exprList (argList )
nargs = len (args )
for _ , a := range args {
if a .mode == invalid {
return
}
}
if nargs > 0 {
*x = *args [0 ]
}
case _Make , _New , _Offsetof , _Trace :
nargs = len (argList )
}
{
msg := ""
if nargs < bin .nargs {
msg = "not enough"
} else if !bin .variadic && nargs > bin .nargs {
msg = "too many"
}
if msg != "" {
check .errorf (argErrPos (call ), WrongArgCount , invalidOp +"%s arguments for %v (expected %d, found %d)" , msg , call , bin .nargs , nargs )
return
}
}
switch id {
case _Append :
S := x .typ
var T Type
if s , _ := coreType (S ).(*Slice ); s != nil {
T = s .elem
} else {
var cause string
switch {
case x .isNil ():
cause = "have untyped nil"
case isTypeParam (S ):
if u := coreType (S ); u != nil {
cause = check .sprintf ("%s has core type %s" , x , u )
} else {
cause = check .sprintf ("%s has no core type" , x )
}
default :
cause = check .sprintf ("have %s" , x )
}
check .errorf (x , InvalidAppend , "first argument to append must be a slice; %s" , cause )
return
}
if nargs == 2 && hasDots (call ) {
if ok , _ := x .assignableTo (check , NewSlice (universeByte ), nil ); ok {
y := args [1 ]
if t := coreString (y .typ ); t != nil && isString (t ) {
if check .recordTypes () {
sig := makeSig (S , S , y .typ )
sig .variadic = true
check .recordBuiltinType (call .Fun , sig )
}
x .mode = value
x .typ = S
break
}
}
}
sig := makeSig (S , S , NewSlice (T ))
sig .variadic = true
check .arguments (call , sig , nil , nil , args , nil , nil )
x .mode = value
x .typ = S
if check .recordTypes () {
check .recordBuiltinType (call .Fun , sig )
}
case _Cap , _Len :
mode := invalid
var val constant .Value
switch t := arrayPtrDeref (under (x .typ )).(type ) {
case *Basic :
if isString (t ) && id == _Len {
if x .mode == constant_ {
mode = constant_
val = constant .MakeInt64 (int64 (len (constant .StringVal (x .val ))))
} else {
mode = value
}
}
case *Array :
mode = value
if !check .hasCallOrRecv {
mode = constant_
if t .len >= 0 {
val = constant .MakeInt64 (t .len )
} else {
val = constant .MakeUnknown ()
}
}
case *Slice , *Chan :
mode = value
case *Map :
if id == _Len {
mode = value
}
case *Interface :
if !isTypeParam (x .typ ) {
break
}
if t .typeSet ().underIs (func (t Type ) bool {
switch t := arrayPtrDeref (t ).(type ) {
case *Basic :
if isString (t ) && id == _Len {
return true
}
case *Array , *Slice , *Chan :
return true
case *Map :
if id == _Len {
return true
}
}
return false
}) {
mode = value
}
}
if mode == invalid {
if isValid (under (x .typ )) {
code := InvalidCap
if id == _Len {
code = InvalidLen
}
check .errorf (x , code , invalidArg +"%s for built-in %s" , x , bin .name )
}
return
}
if check .recordTypes () && mode != constant_ {
check .recordBuiltinType (call .Fun , makeSig (Typ [Int ], x .typ ))
}
x .mode = mode
x .typ = Typ [Int ]
x .val = val
case _Clear :
check .verifyVersionf (call .Fun , go1_21 , "clear" )
if !underIs (x .typ , func (u Type ) bool {
switch u .(type ) {
case *Map , *Slice :
return true
}
check .errorf (x , InvalidClear , invalidArg +"cannot clear %s: argument must be (or constrained by) map or slice" , x )
return false
}) {
return
}
x .mode = novalue
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (nil , x .typ ))
}
case _Close :
if !underIs (x .typ , func (u Type ) bool {
uch , _ := u .(*Chan )
if uch == nil {
check .errorf (x , InvalidClose , invalidOp +"cannot close non-channel %s" , x )
return false
}
if uch .dir == RecvOnly {
check .errorf (x , InvalidClose , invalidOp +"cannot close receive-only channel %s" , x )
return false
}
return true
}) {
return
}
x .mode = novalue
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (nil , x .typ ))
}
case _Complex :
y := args [1 ]
d := 0
if isUntyped (x .typ ) {
d |= 1
}
if isUntyped (y .typ ) {
d |= 2
}
switch d {
case 0 :
case 1 :
check .convertUntyped (x , y .typ )
case 2 :
check .convertUntyped (y , x .typ )
case 3 :
if x .mode == constant_ && y .mode == constant_ {
toFloat := func (x *operand ) {
if isNumeric (x .typ ) && constant .Sign (constant .Imag (x .val )) == 0 {
x .typ = Typ [UntypedFloat ]
}
}
toFloat (x )
toFloat (y )
} else {
check .convertUntyped (x , Typ [Float64 ])
check .convertUntyped (y , Typ [Float64 ])
}
}
if x .mode == invalid || y .mode == invalid {
return
}
if !Identical (x .typ , y .typ ) {
check .errorf (x , InvalidComplex , invalidOp +"%v (mismatched types %s and %s)" , call , x .typ , y .typ )
return
}
f := func (typ Type ) Type {
assert (!isTypeParam (typ ))
if t , _ := under (typ ).(*Basic ); t != nil {
switch t .kind {
case Float32 :
return Typ [Complex64 ]
case Float64 :
return Typ [Complex128 ]
case UntypedFloat :
return Typ [UntypedComplex ]
}
}
return nil
}
resTyp := check .applyTypeFunc (f , x , id )
if resTyp == nil {
check .errorf (x , InvalidComplex , invalidArg +"arguments have type %s, expected floating-point" , x .typ )
return
}
if x .mode == constant_ && y .mode == constant_ {
x .val = constant .BinaryOp (constant .ToFloat (x .val ), token .ADD , constant .MakeImag (constant .ToFloat (y .val )))
} else {
x .mode = value
}
if check .recordTypes () && x .mode != constant_ {
check .recordBuiltinType (call .Fun , makeSig (resTyp , x .typ , x .typ ))
}
x .typ = resTyp
case _Copy :
dst , _ := coreType (x .typ ).(*Slice )
y := args [1 ]
src0 := coreString (y .typ )
if src0 != nil && isString (src0 ) {
src0 = NewSlice (universeByte )
}
src , _ := src0 .(*Slice )
if dst == nil || src == nil {
check .errorf (x , InvalidCopy , invalidArg +"copy expects slice arguments; found %s and %s" , x , y )
return
}
if !Identical (dst .elem , src .elem ) {
check .errorf (x , InvalidCopy , invalidArg +"arguments to copy %s and %s have different element types %s and %s" , x , y , dst .elem , src .elem )
return
}
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (Typ [Int ], x .typ , y .typ ))
}
x .mode = value
x .typ = Typ [Int ]
case _Delete :
map_ := x .typ
var key Type
if !underIs (map_ , func (u Type ) bool {
map_ , _ := u .(*Map )
if map_ == nil {
check .errorf (x , InvalidDelete , invalidArg +"%s is not a map" , x )
return false
}
if key != nil && !Identical (map_ .key , key ) {
check .errorf (x , InvalidDelete , invalidArg +"maps of %s must have identical key types" , x )
return false
}
key = map_ .key
return true
}) {
return
}
*x = *args [1 ]
check .assignment (x , key , "argument to delete" )
if x .mode == invalid {
return
}
x .mode = novalue
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (nil , map_ , key ))
}
case _Imag , _Real :
if isUntyped (x .typ ) {
if x .mode == constant_ {
if isNumeric (x .typ ) {
x .typ = Typ [UntypedComplex ]
}
} else {
check .convertUntyped (x , Typ [Complex128 ])
if x .mode == invalid {
return
}
}
}
f := func (typ Type ) Type {
assert (!isTypeParam (typ ))
if t , _ := under (typ ).(*Basic ); t != nil {
switch t .kind {
case Complex64 :
return Typ [Float32 ]
case Complex128 :
return Typ [Float64 ]
case UntypedComplex :
return Typ [UntypedFloat ]
}
}
return nil
}
resTyp := check .applyTypeFunc (f , x , id )
if resTyp == nil {
code := InvalidImag
if id == _Real {
code = InvalidReal
}
check .errorf (x , code , invalidArg +"argument has type %s, expected complex type" , x .typ )
return
}
if x .mode == constant_ {
if id == _Real {
x .val = constant .Real (x .val )
} else {
x .val = constant .Imag (x .val )
}
} else {
x .mode = value
}
if check .recordTypes () && x .mode != constant_ {
check .recordBuiltinType (call .Fun , makeSig (resTyp , x .typ ))
}
x .typ = resTyp
case _Make :
arg0 := argList [0 ]
T := check .varType (arg0 )
if !isValid (T ) {
return
}
var min int
switch coreType (T ).(type ) {
case *Slice :
min = 2
case *Map , *Chan :
min = 1
case nil :
check .errorf (arg0 , InvalidMake , invalidArg +"cannot make %s: no core type" , arg0 )
return
default :
check .errorf (arg0 , InvalidMake , invalidArg +"cannot make %s; type must be slice, map, or channel" , arg0 )
return
}
if nargs < min || min +1 < nargs {
check .errorf (call , WrongArgCount , invalidOp +"%v expects %d or %d arguments; found %d" , call , min , min +1 , nargs )
return
}
types := []Type {T }
var sizes []int64
for _ , arg := range argList [1 :] {
typ , size := check .index (arg , -1 )
types = append (types , typ )
if size >= 0 {
sizes = append (sizes , size )
}
}
if len (sizes ) == 2 && sizes [0 ] > sizes [1 ] {
check .error (argList [1 ], SwappedMakeArgs , invalidArg +"length and capacity swapped" )
}
x .mode = value
x .typ = T
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , types ...))
}
case _Max , _Min :
check .verifyVersionf (call .Fun , go1_21 , "built-in %s" , bin .name )
op := token .LSS
if id == _Max {
op = token .GTR
}
for i , a := range args {
if a .mode == invalid {
return
}
if !allOrdered (a .typ ) {
check .errorf (a , InvalidMinMaxOperand , invalidArg +"%s cannot be ordered" , a )
return
}
if i > 0 {
check .matchTypes (x , a )
if x .mode == invalid {
return
}
if !Identical (x .typ , a .typ ) {
check .errorf (a , MismatchedTypes , invalidArg +"mismatched types %s (previous argument) and %s (type of %s)" , x .typ , a .typ , a .expr )
return
}
if x .mode == constant_ && a .mode == constant_ {
if constant .Compare (a .val , op , x .val ) {
*x = *a
}
} else {
x .mode = value
}
}
}
if x .mode != constant_ {
x .mode = value
check .assignment (x , &emptyInterface , "argument to built-in " +bin .name )
if x .mode == invalid {
return
}
}
for _ , a := range args {
check .updateExprType (a .expr , x .typ , true )
}
if check .recordTypes () && x .mode != constant_ {
types := make ([]Type , nargs )
for i := range types {
types [i ] = x .typ
}
check .recordBuiltinType (call .Fun , makeSig (x .typ , types ...))
}
case _New :
T := check .varType (argList [0 ])
if !isValid (T ) {
return
}
x .mode = value
x .typ = &Pointer {base : T }
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , T ))
}
case _Panic :
if check .sig != nil && check .sig .results .Len () > 0 {
p := check .isPanic
if p == nil {
p = make (map [*ast .CallExpr ]bool )
check .isPanic = p
}
p [call ] = true
}
check .assignment (x , &emptyInterface , "argument to panic" )
if x .mode == invalid {
return
}
x .mode = novalue
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (nil , &emptyInterface ))
}
case _Print , _Println :
var params []Type
if nargs > 0 {
params = make ([]Type , nargs )
for i , a := range args {
check .assignment (a , nil , "argument to built-in" +predeclaredFuncs [id ].name )
if a .mode == invalid {
return
}
params [i ] = a .typ
}
}
x .mode = novalue
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (nil , params ...))
}
case _Recover :
x .mode = value
x .typ = &emptyInterface
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ ))
}
case _Add :
check .verifyVersionf (call .Fun , go1_17 , "unsafe.Add" )
check .assignment (x , Typ [UnsafePointer ], "argument to unsafe.Add" )
if x .mode == invalid {
return
}
y := args [1 ]
if !check .isValidIndex (y , InvalidUnsafeAdd , "length" , true ) {
return
}
x .mode = value
x .typ = Typ [UnsafePointer ]
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , x .typ , y .typ ))
}
case _Alignof :
check .assignment (x , nil , "argument to unsafe.Alignof" )
if x .mode == invalid {
return
}
if hasVarSize (x .typ , nil ) {
x .mode = value
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (Typ [Uintptr ], x .typ ))
}
} else {
x .mode = constant_
x .val = constant .MakeInt64 (check .conf .alignof (x .typ ))
}
x .typ = Typ [Uintptr ]
case _Offsetof :
arg0 := argList [0 ]
selx , _ := ast .Unparen (arg0 ).(*ast .SelectorExpr )
if selx == nil {
check .errorf (arg0 , BadOffsetofSyntax , invalidArg +"%s is not a selector expression" , arg0 )
check .use (arg0 )
return
}
check .expr (nil , x , selx .X )
if x .mode == invalid {
return
}
base := derefStructPtr (x .typ )
sel := selx .Sel .Name
obj , index , indirect := lookupFieldOrMethod (base , false , check .pkg , sel , false )
switch obj .(type ) {
case nil :
check .errorf (x , MissingFieldOrMethod , invalidArg +"%s has no single field %s" , base , sel )
return
case *Func :
check .errorf (arg0 , InvalidOffsetof , invalidArg +"%s is a method value" , arg0 )
return
}
if indirect {
check .errorf (x , InvalidOffsetof , invalidArg +"field %s is embedded via a pointer in %s" , sel , base )
return
}
check .recordSelection (selx , FieldVal , base , obj , index , false )
{
mode := value
if x .mode == variable || indirect {
mode = variable
}
check .record (&operand {mode , selx , obj .Type (), nil , 0 })
}
if hasVarSize (base , nil ) {
x .mode = value
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (Typ [Uintptr ], obj .Type ()))
}
} else {
offs := check .conf .offsetof (base , index )
if offs < 0 {
check .errorf (x , TypeTooLarge , "%s is too large" , x )
return
}
x .mode = constant_
x .val = constant .MakeInt64 (offs )
}
x .typ = Typ [Uintptr ]
case _Sizeof :
check .assignment (x , nil , "argument to unsafe.Sizeof" )
if x .mode == invalid {
return
}
if hasVarSize (x .typ , nil ) {
x .mode = value
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (Typ [Uintptr ], x .typ ))
}
} else {
size := check .conf .sizeof (x .typ )
if size < 0 {
check .errorf (x , TypeTooLarge , "%s is too large" , x )
return
}
x .mode = constant_
x .val = constant .MakeInt64 (size )
}
x .typ = Typ [Uintptr ]
case _Slice :
check .verifyVersionf (call .Fun , go1_17 , "unsafe.Slice" )
ptr , _ := coreType (x .typ ).(*Pointer )
if ptr == nil {
check .errorf (x , InvalidUnsafeSlice , invalidArg +"%s is not a pointer" , x )
return
}
y := args [1 ]
if !check .isValidIndex (y , InvalidUnsafeSlice , "length" , false ) {
return
}
x .mode = value
x .typ = NewSlice (ptr .base )
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , ptr , y .typ ))
}
case _SliceData :
check .verifyVersionf (call .Fun , go1_20 , "unsafe.SliceData" )
slice , _ := coreType (x .typ ).(*Slice )
if slice == nil {
check .errorf (x , InvalidUnsafeSliceData , invalidArg +"%s is not a slice" , x )
return
}
x .mode = value
x .typ = NewPointer (slice .elem )
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , slice ))
}
case _String :
check .verifyVersionf (call .Fun , go1_20 , "unsafe.String" )
check .assignment (x , NewPointer (universeByte ), "argument to unsafe.String" )
if x .mode == invalid {
return
}
y := args [1 ]
if !check .isValidIndex (y , InvalidUnsafeString , "length" , false ) {
return
}
x .mode = value
x .typ = Typ [String ]
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , NewPointer (universeByte ), y .typ ))
}
case _StringData :
check .verifyVersionf (call .Fun , go1_20 , "unsafe.StringData" )
check .assignment (x , Typ [String ], "argument to unsafe.StringData" )
if x .mode == invalid {
return
}
x .mode = value
x .typ = NewPointer (universeByte )
if check .recordTypes () {
check .recordBuiltinType (call .Fun , makeSig (x .typ , Typ [String ]))
}
case _Assert :
if x .mode != constant_ || !isBoolean (x .typ ) {
check .errorf (x , Test , invalidArg +"%s is not a boolean constant" , x )
return
}
if x .val .Kind () != constant .Bool {
check .errorf (x , Test , "internal error: value of %s should be a boolean constant" , x )
return
}
if !constant .BoolVal (x .val ) {
check .errorf (call , Test , "%v failed" , call )
}
case _Trace :
if nargs == 0 {
check .dump ("%v: trace() without arguments" , call .Pos ())
x .mode = novalue
break
}
var t operand
x1 := x
for _ , arg := range argList {
check .rawExpr (nil , x1 , arg , nil , false )
check .dump ("%v: %s" , x1 .Pos (), x1 )
x1 = &t
}
if x .mode == invalid {
return
}
default :
panic ("unreachable" )
}
assert (x .mode != invalid )
return true
}
func hasVarSize(t Type , seen map [*Named ]bool ) (varSized bool ) {
if named := asNamed (t ); named != nil {
if v , ok := seen [named ]; ok {
return v
}
if seen == nil {
seen = make (map [*Named ]bool )
}
seen [named ] = true
defer func () {
seen [named ] = varSized
}()
}
switch u := under (t ).(type ) {
case *Array :
return hasVarSize (u .elem , seen )
case *Struct :
for _ , f := range u .fields {
if hasVarSize (f .typ , seen ) {
return true
}
}
case *Interface :
return isTypeParam (t )
case *Named , *Union :
panic ("unreachable" )
}
return false
}
func (check *Checker ) applyTypeFunc (f func (Type ) Type , x *operand , id builtinId ) Type {
if tp , _ := Unalias (x .typ ).(*TypeParam ); tp != nil {
var terms []*Term
if !tp .is (func (t *term ) bool {
if t == nil {
return false
}
if r := f (t .typ ); r != nil {
terms = append (terms , NewTerm (t .tilde , r ))
return true
}
return false
}) {
return nil
}
var code Code
switch id {
case _Real :
code = InvalidReal
case _Imag :
code = InvalidImag
case _Complex :
code = InvalidComplex
default :
panic ("unreachable" )
}
check .softErrorf (x , code , "%s not supported as argument to built-in %s for go1.18 (see go.dev/issue/50937)" , x , predeclaredFuncs [id ].name )
tpar := NewTypeName (nopos , check .pkg , tp .obj .name , nil )
ptyp := check .newTypeParam (tpar , NewInterfaceType (nil , []Type {NewUnion (terms )}))
ptyp .index = tp .index
return ptyp
}
return f (x .typ )
}
func makeSig(res Type , args ...Type ) *Signature {
list := make ([]*Var , len (args ))
for i , param := range args {
list [i ] = NewVar (nopos , nil , "" , Default (param ))
}
params := NewTuple (list ...)
var result *Tuple
if res != nil {
assert (!isUntyped (res ))
result = NewTuple (NewVar (nopos , nil , "" , res ))
}
return &Signature {params : params , results : result }
}
func arrayPtrDeref(typ Type ) Type {
if p , ok := Unalias (typ ).(*Pointer ); ok {
if a , _ := under (p .base ).(*Array ); a != nil {
return a
}
}
return typ
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .