package interp

import (
	"errors"
	"go/constant"
	"math"
	"reflect"
)

type opPredicates map[action]func(reflect.Type) bool

// typecheck handles all type checking following "go/types" logic.
//
// Due to variant type systems (itype vs reflect.Type) a single
// type system should used, namely reflect.Type with exception
// of the untyped flag on itype.
type typecheck struct{}

// op type checks an expression against a set of expression predicates.
func (check typecheck) op(p opPredicates, a action, n, c *node, t reflect.Type) error {
	if pred := p[a]; pred != nil {
		if !pred(t) {
			return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, c.typ.id())
		}
	} else {
		return n.cfgErrorf("invalid operation: unknown operator %v", n.action)
	}
	return nil
}

// assignment checks if n can be assigned to typ.
//
// Use typ == nil to indicate assignment to an untyped blank identifier.
func (check typecheck) assignment(n *node, typ *itype, context string) error {
	if n.typ.untyped {
		if typ == nil || isInterface(typ) {
			if typ == nil && n.typ.cat == nilT {
				return n.cfgErrorf("use of untyped nil in %s", context)
			}
			typ = n.typ.defaultType(n.rval)
		}
		if err := check.convertUntyped(n, typ); err != nil {
			return err
		}
	}

	if typ == nil {
		return nil
	}

	if !n.typ.assignableTo(typ) {
		if context == "" {
			return n.cfgErrorf("cannot use type %s as type %s", n.typ.id(), typ.id())
		}
		return n.cfgErrorf("cannot use type %s as type %s in %s", n.typ.id(), typ.id(), context)
	}
	return nil
}

// assignExpr type checks an assign expression.
//
// This is done per pair of assignments.
func (check typecheck) assignExpr(n, dest, src *node) error {
	if n.action == aAssign {
		isConst := n.anc.kind == constDecl
		if !isConst {
			// var operations must be typed
			dest.typ = dest.typ.defaultType(src.rval)
		}

		return check.assignment(src, dest.typ, "assignment")
	}

	// assignment operations.
	if n.nleft > 1 || n.nright > 1 {
		return n.cfgErrorf("assignment operation %s requires single-valued expressions", n.action)
	}

	return check.binaryExpr(n)
}

// addressExpr type checks a unary address expression.
func (check typecheck) addressExpr(n *node) error {
	c0 := n.child[0]
	found := false
	for !found {
		switch c0.kind {
		case parenExpr:
			c0 = c0.child[0]
			continue
		case selectorExpr:
			c0 = c0.child[1]
			continue
		case starExpr:
			c0 = c0.child[0]
			continue
		case indexExpr, sliceExpr:
			c := c0.child[0]
			if isArray(c.typ) || isMap(c.typ) {
				c0 = c
				continue
			}
		case compositeLitExpr, identExpr:
			found = true
			continue
		}
		return n.cfgErrorf("invalid operation: cannot take address of %s [kind: %s]", c0.typ.id(), kinds[c0.kind])
	}
	return nil
}

// starExpr type checks a star expression on a variable.
func (check typecheck) starExpr(n *node) error {
	if n.typ.TypeOf().Kind() != reflect.Ptr {
		return n.cfgErrorf("invalid operation: cannot indirect %q", n.name())
	}
	return nil
}

var unaryOpPredicates = opPredicates{
	aPos:    isNumber,
	aNeg:    isNumber,
	aBitNot: isInt,
	aNot:    isBoolean,
}

// unaryExpr type checks a unary expression.
func (check typecheck) unaryExpr(n *node) error {
	c0 := n.child[0]
	t0 := c0.typ.TypeOf()

	if n.action == aRecv {
		if !isChan(c0.typ) {
			return n.cfgErrorf("invalid operation: cannot receive from non-channel %s", c0.typ.id())
		}
		if isSendChan(c0.typ) {
			return n.cfgErrorf("invalid operation: cannot receive from send-only channel %s", c0.typ.id())
		}
		return nil
	}

	if err := check.op(unaryOpPredicates, n.action, n, c0, t0); err != nil {
		return err
	}
	return nil
}

// shift type checks a shift binary expression.
func (check typecheck) shift(n *node) error {
	c0, c1 := n.child[0], n.child[1]
	t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf()

	var v0 constant.Value
	if c0.typ.untyped && c0.rval.IsValid() {
		v0 = constant.ToInt(c0.rval.Interface().(constant.Value))
		c0.rval = reflect.ValueOf(v0)
	}

	if !(c0.typ.untyped && v0 != nil && v0.Kind() == constant.Int || isInt(t0)) {
		return n.cfgErrorf("invalid operation: shift of type %v", c0.typ.id())
	}

	switch {
	case c1.typ.untyped:
		if err := check.convertUntyped(c1, &itype{cat: uintT, name: "uint"}); err != nil {
			return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
		}
	case isInt(t1):
		// nothing to do
	default:
		return n.cfgErrorf("invalid operation: shift count type %v, must be integer", c1.typ.id())
	}
	return nil
}

// comparison type checks a comparison binary expression.
func (check typecheck) comparison(n *node) error {
	c0, c1 := n.child[0], n.child[1]

	if !c0.typ.assignableTo(c1.typ) && !c1.typ.assignableTo(c0.typ) {
		return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
	}

	ok := false
	switch n.action {
	case aEqual, aNotEqual:
		ok = c0.typ.comparable() && c1.typ.comparable() || c0.typ.isNil() && c1.typ.hasNil() || c1.typ.isNil() && c0.typ.hasNil()
	case aLower, aLowerEqual, aGreater, aGreaterEqual:
		ok = c0.typ.ordered() && c1.typ.ordered()
	}
	if !ok {
		typ := c0.typ
		if typ.isNil() {
			typ = c1.typ
		}
		return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".")
	}
	return nil
}

var binaryOpPredicates = opPredicates{
	aAdd: func(typ reflect.Type) bool { return isNumber(typ) || isString(typ) },
	aSub: isNumber,
	aMul: isNumber,
	aQuo: isNumber,
	aRem: isInt,

	aAnd:    isInt,
	aOr:     isInt,
	aXor:    isInt,
	aAndNot: isInt,

	aLand: isBoolean,
	aLor:  isBoolean,
}

// binaryExpr type checks a binary expression.
func (check typecheck) binaryExpr(n *node) error {
	c0, c1 := n.child[0], n.child[1]

	a := n.action
	if isAssignAction(a) {
		a--
	}

	if isShiftAction(a) {
		return check.shift(n)
	}

	switch n.action {
	case aRem:
		if zeroConst(c1) {
			return n.cfgErrorf("invalid operation: division by zero")
		}
	case aQuo:
		if zeroConst(c1) {
			return n.cfgErrorf("invalid operation: division by zero")
		}
		if c0.rval.IsValid() && c1.rval.IsValid() {
			// Avoid constant conversions below to ensure correct constant integer quotient.
			return nil
		}
	}

	_ = check.convertUntyped(c0, c1.typ)
	_ = check.convertUntyped(c1, c0.typ)

	if isComparisonAction(a) {
		return check.comparison(n)
	}

	if !c0.typ.equals(c1.typ) {
		return n.cfgErrorf("invalid operation: mismatched types %s and %s", c0.typ.id(), c1.typ.id())
	}

	t0 := c0.typ.TypeOf()
	if err := check.op(binaryOpPredicates, a, n, c0, t0); err != nil {
		return err
	}
	return nil
}

func zeroConst(n *node) bool {
	return n.typ.untyped && constant.Sign(n.rval.Interface().(constant.Value)) == 0
}

func (check typecheck) index(n *node, max int) error {
	if err := check.convertUntyped(n, &itype{cat: intT, name: "int"}); err != nil {
		return err
	}

	if !isInt(n.typ.TypeOf()) {
		return n.cfgErrorf("index %s must be integer", n.typ.id())
	}

	if !n.rval.IsValid() || max < 1 {
		return nil
	}

	if int(vInt(n.rval)) >= max {
		return n.cfgErrorf("index %s is out of bounds", n.typ.id())
	}

	return nil
}

// arrayLitExpr type checks an array composite literal expression.
func (check typecheck) arrayLitExpr(child []*node, typ *itype, length int) error {
	visited := make(map[int]bool, len(child))
	index := 0
	for _, c := range child {
		n := c
		switch {
		case c.kind == keyValueExpr:
			if err := check.index(c.child[0], length); err != nil {
				return c.cfgErrorf("index %s must be integer constant", c.child[0].typ.id())
			}
			n = c.child[1]
			index = int(vInt(c.child[0].rval))
		case length > 0 && index >= length:
			return c.cfgErrorf("index %d is out of bounds (>= %d)", index, length)
		}

		if visited[index] {
			return n.cfgErrorf("duplicate index %d in array or slice literal", index)
		}
		visited[index] = true
		index++

		if err := check.assignment(n, typ, "array or slice literal"); err != nil {
			return err
		}
	}
	return nil
}

// mapLitExpr type checks an map composite literal expression.
func (check typecheck) mapLitExpr(child []*node, ktyp, vtyp *itype) error {
	visited := make(map[interface{}]bool, len(child))
	for _, c := range child {
		if c.kind != keyValueExpr {
			return c.cfgErrorf("missing key in map literal")
		}

		key, val := c.child[0], c.child[1]
		if err := check.assignment(key, ktyp, "map literal"); err != nil {
			return err
		}

		if key.rval.IsValid() {
			kval := key.rval.Interface()
			if visited[kval] {
				return c.cfgErrorf("duplicate key %s in map literal", kval)
			}
			visited[kval] = true
		}

		if err := check.assignment(val, vtyp, "map literal"); err != nil {
			return err
		}
	}
	return nil
}

// structLitExpr type checks a struct composite literal expression.
func (check typecheck) structLitExpr(child []*node, typ *itype) error {
	if len(child) == 0 {
		return nil
	}

	if child[0].kind == keyValueExpr {
		// All children must be keyValueExpr
		visited := make([]bool, len(typ.field))
		for _, c := range child {
			if c.kind != keyValueExpr {
				return c.cfgErrorf("mixture of field:value and value elements in struct literal")
			}

			key, val := c.child[0], c.child[1]
			name := key.ident
			if name == "" {
				return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
			}
			i := typ.fieldIndex(name)
			if i < 0 {
				return c.cfgErrorf("unknown field %s in struct literal", name)
			}
			field := typ.field[i]

			if err := check.assignment(val, field.typ, "struct literal"); err != nil {
				return err
			}

			if visited[i] {
				return c.cfgErrorf("duplicate field name %s in struct literal", name)
			}
			visited[i] = true
		}
		return nil
	}

	// No children can be keyValueExpr
	for i, c := range child {
		if c.kind == keyValueExpr {
			return c.cfgErrorf("mixture of field:value and value elements in struct literal")
		}

		if i >= len(typ.field) {
			return c.cfgErrorf("too many values in struct literal")
		}
		field := typ.field[i]
		// TODO(nick): check if this field is not exported and in a different package.

		if err := check.assignment(c, field.typ, "struct literal"); err != nil {
			return err
		}
	}
	if len(child) < len(typ.field) {
		return child[len(child)-1].cfgErrorf("too few values in struct literal")
	}
	return nil
}

// structBinLitExpr type checks a struct composite literal expression on a binary type.
func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error {
	if len(child) == 0 {
		return nil
	}

	if child[0].kind == keyValueExpr {
		// All children must be keyValueExpr
		visited := make(map[string]bool, typ.NumField())
		for _, c := range child {
			if c.kind != keyValueExpr {
				return c.cfgErrorf("mixture of field:value and value elements in struct literal")
			}

			key, val := c.child[0], c.child[1]
			name := key.ident
			if name == "" {
				return c.cfgErrorf("invalid field name %s in struct literal", key.typ.id())
			}
			field, ok := typ.FieldByName(name)
			if !ok {
				return c.cfgErrorf("unknown field %s in struct literal", name)
			}

			if err := check.assignment(val, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
				return err
			}

			if visited[field.Name] {
				return c.cfgErrorf("duplicate field name %s in struct literal", name)
			}
			visited[field.Name] = true
		}
		return nil
	}

	// No children can be keyValueExpr
	for i, c := range child {
		if c.kind == keyValueExpr {
			return c.cfgErrorf("mixture of field:value and value elements in struct literal")
		}

		if i >= typ.NumField() {
			return c.cfgErrorf("too many values in struct literal")
		}
		field := typ.Field(i)
		if !canExport(field.Name) {
			return c.cfgErrorf("implicit assignment to unexported field %s in %s literal", field.Name, typ)
		}

		if err := check.assignment(c, &itype{cat: valueT, rtype: field.Type}, "struct literal"); err != nil {
			return err
		}
	}
	if len(child) < typ.NumField() {
		return child[len(child)-1].cfgErrorf("too few values in struct literal")
	}
	return nil
}

// sliceExpr type checks a slice expression.
func (check typecheck) sliceExpr(n *node) error {
	c, child := n.child[0], n.child[1:]

	t := c.typ.TypeOf()
	var low, high, max *node
	if len(child) >= 1 {
		if n.action == aSlice {
			low = child[0]
		} else {
			high = child[0]
		}
	}
	if len(child) >= 2 {
		if n.action == aSlice {
			high = child[1]
		} else {
			max = child[1]
		}
	}
	if len(child) == 3 && n.action == aSlice {
		max = child[2]
	}

	l := -1
	valid := false
	switch t.Kind() {
	case reflect.String:
		valid = true
		if c.rval.IsValid() {
			l = len(vString(c.rval))
		}
		if max != nil {
			return max.cfgErrorf("invalid operation: 3-index slice of string")
		}
	case reflect.Array:
		valid = true
		l = t.Len()
		// TODO(marc): check addressable status of array object (i.e. composite arrays are not).
	case reflect.Slice:
		valid = true
	case reflect.Ptr:
		if t.Elem().Kind() == reflect.Array {
			valid = true
			l = t.Elem().Len()
		}
	}
	if !valid {
		return c.cfgErrorf("cannot slice type %s", c.typ.id())
	}

	var ind [3]int64
	for i, nod := range []*node{low, high, max} {
		x := int64(-1)
		switch {
		case nod != nil:
			max := -1
			if l >= 0 {
				max = l + 1
			}
			if err := check.index(nod, max); err != nil {
				return err
			}
			if nod.rval.IsValid() {
				x = vInt(nod.rval)
			}
		case i == 0:
			x = 0
		case l >= 0:
			x = int64(l)
		}
		ind[i] = x
	}

	for i, x := range ind[:len(ind)-1] {
		if x <= 0 {
			continue
		}
		for _, y := range ind[i+1:] {
			if y < 0 || x <= y {
				continue
			}
			return n.cfgErrorf("invalid index values, must be low <= high <= max")
		}
	}
	return nil
}

// typeAssertionExpr type checks a type assert expression.
func (check typecheck) typeAssertionExpr(n *node, typ *itype) error {
	// TODO(nick): This type check is not complete and should be revisited once
	// https://github.com/golang/go/issues/39717 lands. It is currently impractical to
	// type check Named types as they cannot be asserted.

	if n.typ.TypeOf().Kind() != reflect.Interface {
		return n.cfgErrorf("invalid type assertion: non-interface type %s on left", n.typ.id())
	}
	ims := n.typ.methods()
	if len(ims) == 0 {
		// Empty interface must be a dynamic check.
		return nil
	}

	if isInterface(typ) {
		// Asserting to an interface is a dynamic check as we must look to the
		// underlying struct.
		return nil
	}

	for name := range ims {
		im := lookupFieldOrMethod(n.typ, name)
		tm := lookupFieldOrMethod(typ, name)
		if im == nil {
			// This should not be possible.
			continue
		}
		if tm == nil {
			return n.cfgErrorf("impossible type assertion: %s does not implement %s (missing %v method)", typ.id(), n.typ.id(), name)
		}
		if tm.recv != nil && tm.recv.TypeOf().Kind() == reflect.Ptr && typ.TypeOf().Kind() != reflect.Ptr {
			return n.cfgErrorf("impossible type assertion: %s does not implement %s as %q method has a pointer receiver", typ.id(), n.typ.id(), name)
		}

		err := n.cfgErrorf("impossible type assertion: %s does not implement %s", typ.id(), n.typ.id())
		if im.numIn() != tm.numIn() || im.numOut() != tm.numOut() {
			return err
		}
		for i := 0; i < im.numIn(); i++ {
			if !im.in(i).equals(tm.in(i)) {
				return err
			}
		}
		for i := 0; i < im.numOut(); i++ {
			if !im.out(i).equals(tm.out(i)) {
				return err
			}
		}
	}
	return nil
}

// conversion type checks the conversion of n to typ.
func (check typecheck) conversion(n *node, typ *itype) error {
	var c constant.Value
	if n.rval.IsValid() {
		if con, ok := n.rval.Interface().(constant.Value); ok {
			c = con
		}
	}

	var ok bool
	switch {
	case c != nil && isConstType(typ):
		switch t := typ.TypeOf(); {
		case representableConst(c, t):
			ok = true
		case isInt(n.typ.TypeOf()) && isString(t):
			codepoint := int64(-1)
			if i, ok := constant.Int64Val(c); ok {
				codepoint = i
			}
			n.rval = reflect.ValueOf(constant.MakeString(string(rune(codepoint))))
			ok = true
		}

	case n.typ.convertibleTo(typ):
		ok = true
	}
	if !ok {
		return n.cfgErrorf("cannot convert expression of type %s to type %s", n.typ.id(), typ.id())
	}
	if !n.typ.untyped || c == nil {
		return nil
	}
	if isInterface(typ) || !isConstType(typ) {
		typ = n.typ.defaultType(n.rval)
	}
	return check.convertUntyped(n, typ)
}

type param struct {
	nod *node
	typ *itype
}

func (p param) Type() *itype {
	if p.typ != nil {
		return p.typ
	}
	return p.nod.typ
}

// unpackParams unpacks child parameters into a slice of param.
// If there is only 1 child and it is a callExpr with an n-value return,
// the return types are returned, otherwise the original child nodes are
// returned with nil typ.
func (check typecheck) unpackParams(child []*node) (params []param) {
	if len(child) == 1 && isCall(child[0]) && child[0].child[0].typ.numOut() > 1 {
		c0 := child[0]
		ftyp := child[0].child[0].typ
		for i := 0; i < ftyp.numOut(); i++ {
			params = append(params, param{nod: c0, typ: ftyp.out(i)})
		}
		return params
	}

	for _, c := range child {
		params = append(params, param{nod: c})
	}
	return params
}

var builtinFuncs = map[string]struct {
	args     int
	variadic bool
}{
	bltnAppend:  {args: 1, variadic: true},
	bltnCap:     {args: 1, variadic: false},
	bltnClose:   {args: 1, variadic: false},
	bltnComplex: {args: 2, variadic: false},
	bltnImag:    {args: 1, variadic: false},
	bltnCopy:    {args: 2, variadic: false},
	bltnDelete:  {args: 2, variadic: false},
	bltnLen:     {args: 1, variadic: false},
	bltnMake:    {args: 1, variadic: true},
	bltnNew:     {args: 1, variadic: false},
	bltnPanic:   {args: 1, variadic: false},
	bltnPrint:   {args: 0, variadic: true},
	bltnPrintln: {args: 0, variadic: true},
	bltnReal:    {args: 1, variadic: false},
	bltnRecover: {args: 0, variadic: false},
}

func (check typecheck) builtin(name string, n *node, child []*node, ellipsis bool) error {
	fun := builtinFuncs[name]
	if ellipsis && name != bltnAppend {
		return n.cfgErrorf("invalid use of ... with builtin %s", name)
	}

	var params []param
	nparams := len(child)
	switch name {
	case bltnMake, bltnNew:
		// Special param handling
	default:
		params = check.unpackParams(child)
		nparams = len(params)
	}

	if nparams < fun.args {
		return n.cfgErrorf("not enough arguments in call to %s", name)
	} else if !fun.variadic && nparams > fun.args {
		return n.cfgErrorf("too many arguments for %s", name)
	}

	switch name {
	case bltnAppend:
		typ := params[0].Type()
		t := typ.TypeOf()
		if t.Kind() != reflect.Slice {
			return params[0].nod.cfgErrorf("first argument to append must be slice; have %s", typ.id())
		}

		// Special case append([]byte, "test"...) is allowed.
		t1 := params[1].Type()
		if nparams == 2 && ellipsis && t.Elem().Kind() == reflect.Uint8 && t1.TypeOf().Kind() == reflect.String {
			if t1.untyped {
				return check.convertUntyped(params[1].nod, &itype{cat: stringT, name: "string"})
			}
			return nil
		}
		// We cannot check a recursive type.
		if isRecursiveType(typ, typ.TypeOf()) {
			return nil
		}

		fun := &node{
			typ: &itype{
				cat: funcT,
				arg: []*itype{
					typ,
					{cat: variadicT, val: &itype{cat: valueT, rtype: t.Elem()}},
				},
				ret: []*itype{typ},
			},
			ident: "append",
		}
		return check.arguments(n, child, fun, ellipsis)
	case bltnCap, bltnLen:
		typ := arrayDeref(params[0].Type())
		ok := false
		switch typ.TypeOf().Kind() {
		case reflect.Array, reflect.Slice, reflect.Chan:
			ok = true
		case reflect.String, reflect.Map:
			ok = name == bltnLen
		}
		if !ok {
			return params[0].nod.cfgErrorf("invalid argument for %s", name)
		}
	case bltnClose:
		p := params[0]
		typ := p.Type()
		t := typ.TypeOf()
		if t.Kind() != reflect.Chan {
			return p.nod.cfgErrorf("invalid operation: non-chan type %s", p.nod.typ.id())
		}
		if t.ChanDir() == reflect.RecvDir {
			return p.nod.cfgErrorf("invalid operation: cannot close receive-only channel")
		}
	case bltnComplex:
		var err error
		p0, p1 := params[0], params[1]
		typ0, typ1 := p0.Type(), p1.Type()
		switch {
		case typ0.untyped && !typ1.untyped:
			err = check.convertUntyped(p0.nod, typ1)
		case !typ0.untyped && typ1.untyped:
			err = check.convertUntyped(p1.nod, typ0)
		case typ0.untyped && typ1.untyped:
			fltType := &itype{cat: float64T, name: "float64"}
			err = check.convertUntyped(p0.nod, fltType)
			if err != nil {
				break
			}
			err = check.convertUntyped(p1.nod, fltType)
		}
		if err != nil {
			return err
		}

		// check we have the correct types after conversion.
		typ0, typ1 = p0.Type(), p1.Type()
		if !typ0.equals(typ1) {
			return n.cfgErrorf("invalid operation: mismatched types %s and %s", typ0.id(), typ1.id())
		}
		if !isFloat(typ0.TypeOf()) {
			return n.cfgErrorf("invalid operation: arguments have type %s, expected floating-point", typ0.id())
		}
	case bltnImag, bltnReal:
		p := params[0]
		typ := p.Type()
		if typ.untyped {
			if err := check.convertUntyped(p.nod, &itype{cat: complex128T, name: "complex128"}); err != nil {
				return err
			}
		}
		typ = p.Type()
		if !isComplex(typ.TypeOf()) {
			return p.nod.cfgErrorf("invalid argument type %s for %s", typ.id(), name)
		}
	case bltnCopy:
		typ0, typ1 := params[0].Type(), params[1].Type()
		var t0, t1 reflect.Type
		if t := typ0.TypeOf(); t.Kind() == reflect.Slice {
			t0 = t.Elem()
		}

		switch t := typ1.TypeOf(); t.Kind() {
		case reflect.String:
			t1 = reflect.TypeOf(byte(1))
		case reflect.Slice:
			t1 = t.Elem()
		}

		if t0 == nil || t1 == nil {
			return n.cfgErrorf("copy expects slice arguments")
		}
		if !reflect.DeepEqual(t0, t1) {
			return n.cfgErrorf("arguments to copy have different element types %s and %s", typ0.id(), typ1.id())
		}
	case bltnDelete:
		typ := params[0].Type()
		if typ.TypeOf().Kind() != reflect.Map {
			return params[0].nod.cfgErrorf("first argument to delete must be map; have %s", typ.id())
		}
		ktyp := params[1].Type()
		if !ktyp.assignableTo(typ.key) {
			return params[1].nod.cfgErrorf("cannot use %s as type %s in delete", ktyp.id(), typ.key.id())
		}
	case bltnMake:
		var min int
		switch child[0].typ.TypeOf().Kind() {
		case reflect.Slice:
			min = 2
		case reflect.Map, reflect.Chan:
			min = 1
		default:
			return child[0].cfgErrorf("cannot make %s; type must be slice, map, or channel", child[0].typ.id())
		}
		if nparams < min {
			return n.cfgErrorf("not enough arguments in call to make")
		} else if nparams > min+1 {
			return n.cfgErrorf("too many arguments for make")
		}

		var sizes []int
		for _, c := range child[1:] {
			if err := check.index(c, -1); err != nil {
				return err
			}
			if c.rval.IsValid() {
				sizes = append(sizes, int(vInt(c.rval)))
			}
		}
		for len(sizes) == 2 && sizes[0] > sizes[1] {
			return n.cfgErrorf("len larger than cap in make")
		}

	case bltnPanic:
		return check.assignment(params[0].nod, &itype{cat: interfaceT}, "argument to panic")
	case bltnPrint, bltnPrintln:
		for _, param := range params {
			if param.typ != nil {
				continue
			}

			if err := check.assignment(param.nod, nil, "argument to "+name); err != nil {
				return err
			}
		}
	case bltnRecover, bltnNew:
		// Nothing to do.
	default:
		return n.cfgErrorf("unsupported builtin %s", name)
	}
	return nil
}

// arrayDeref returns A if typ is *A, otherwise typ.
func arrayDeref(typ *itype) *itype {
	if typ.cat == valueT && typ.TypeOf().Kind() == reflect.Ptr {
		t := typ.TypeOf()
		if t.Elem().Kind() == reflect.Array {
			return &itype{cat: valueT, rtype: t.Elem()}
		}
		return typ
	}

	if typ.cat == ptrT && typ.val.cat == arrayT && typ.val.sizedef {
		return typ.val
	}
	return typ
}

// arguments type checks the call expression arguments.
func (check typecheck) arguments(n *node, child []*node, fun *node, ellipsis bool) error {
	params := check.unpackParams(child)
	l := len(child)
	if ellipsis {
		if !fun.typ.isVariadic() {
			return n.cfgErrorf("invalid use of ..., corresponding parameter is non-variadic")
		}
		if len(params) > l {
			return child[0].cfgErrorf("cannot use ... with %d-valued %s", child[0].child[0].typ.numOut(), child[0].child[0].typ.id())
		}
	}

	var cnt int
	for i, param := range params {
		ellip := i == l-1 && ellipsis
		if err := check.argument(param, fun.typ, cnt, l, ellip); err != nil {
			return err
		}
		cnt++
	}

	if fun.typ.isVariadic() {
		cnt++
	}
	if cnt < fun.typ.numIn() {
		return n.cfgErrorf("not enough arguments in call to %s", fun.name())
	}
	return nil
}

func (check typecheck) argument(p param, ftyp *itype, i, l int, ellipsis bool) error {
	atyp := getArg(ftyp, i)
	if atyp == nil {
		return p.nod.cfgErrorf("too many arguments")
	}

	if p.typ == nil && isCall(p.nod) && p.nod.child[0].typ.numOut() != 1 {
		if l == 1 {
			return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), getArgsID(ftyp))
		}
		return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), atyp.id())
	}

	if ellipsis {
		if i != ftyp.numIn()-1 {
			return p.nod.cfgErrorf("can only use ... with matching parameter")
		}
		t := p.Type().TypeOf()
		if t.Kind() != reflect.Slice || !(&itype{cat: valueT, rtype: t.Elem()}).assignableTo(atyp) {
			return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.typ.id(), (&itype{cat: arrayT, val: atyp}).id())
		}
		return nil
	}

	if p.typ != nil {
		if !p.typ.assignableTo(atyp) {
			return p.nod.cfgErrorf("cannot use %s as type %s", p.nod.child[0].typ.id(), getArgsID(ftyp))
		}
		return nil
	}
	return check.assignment(p.nod, atyp, "")
}

func getArg(ftyp *itype, i int) *itype {
	l := ftyp.numIn()
	switch {
	case ftyp.isVariadic() && i >= l-1:
		arg := ftyp.in(l - 1).val
		return arg
	case i < l:
		return ftyp.in(i)
	default:
		return nil
	}
}

func getArgsID(ftyp *itype) string {
	res := "("
	for i, arg := range ftyp.arg {
		if i > 0 {
			res += ","
		}
		res += arg.id()
	}
	res += ")"
	return res
}

var errCantConvert = errors.New("cannot convert")

func (check typecheck) convertUntyped(n *node, typ *itype) error {
	if n.typ == nil || !n.typ.untyped || typ == nil {
		return nil
	}

	convErr := n.cfgErrorf("cannot convert %s to %s", n.typ.id(), typ.id())

	ntyp, ttyp := n.typ.TypeOf(), typ.TypeOf()
	if typ.untyped {
		// Both n and target are untyped.
		nkind, tkind := ntyp.Kind(), ttyp.Kind()
		if isNumber(ntyp) && isNumber(ttyp) {
			if nkind < tkind {
				n.typ = typ
			}
		} else if nkind != tkind {
			return convErr
		}
		return nil
	}

	var (
		ityp *itype
		rtyp reflect.Type
		err  error
	)
	switch {
	case typ.isNil() && n.typ.isNil():
		n.typ = typ
		return nil
	case isNumber(ttyp) || isString(ttyp) || isBoolean(ttyp):
		ityp = typ
		rtyp = ttyp
	case isInterface(typ):
		if n.typ.isNil() {
			return nil
		}
		if len(n.typ.methods()) > 0 { // untyped cannot be set to iface
			return convErr
		}
		ityp = n.typ.defaultType(n.rval)
		rtyp = ntyp
	case isArray(typ) || isMap(typ) || isChan(typ) || isFunc(typ) || isPtr(typ):
		// TODO(nick): above we are acting on itype, but really it is an rtype check. This is not clear which type
		// 		 	   plain we are in. Fix this later.
		if !n.typ.isNil() {
			return convErr
		}
		return nil
	default:
		return convErr
	}

	if err := check.representable(n, rtyp); err != nil {
		return err
	}
	n.rval, err = check.convertConst(n.rval, rtyp)
	if err != nil {
		if errors.Is(err, errCantConvert) {
			return convErr
		}
		return n.cfgErrorf(err.Error())
	}
	n.typ = ityp
	return nil
}

func (check typecheck) representable(n *node, t reflect.Type) error {
	if !n.rval.IsValid() {
		// TODO(nick): This should be an error as the const is in the frame which is undesirable.
		return nil
	}
	c, ok := n.rval.Interface().(constant.Value)
	if !ok {
		// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
		return nil
	}

	if !representableConst(c, t) {
		typ := n.typ.TypeOf()
		if isNumber(typ) && isNumber(t) {
			// numeric conversion : error msg
			//
			// integer -> integer : overflows
			// integer -> float   : overflows (actually not possible)
			// float   -> integer : truncated
			// float   -> float   : overflows
			//
			if !isInt(typ) && isInt(t) {
				return n.cfgErrorf("%s truncated to %s", c.ExactString(), t.Kind().String())
			}
			return n.cfgErrorf("%s overflows %s", c.ExactString(), t.Kind().String())
		}
		return n.cfgErrorf("cannot convert %s to %s", c.ExactString(), t.Kind().String())
	}
	return nil
}

func (check typecheck) convertConst(v reflect.Value, t reflect.Type) (reflect.Value, error) {
	if !v.IsValid() {
		// TODO(nick): This should be an error as the const is in the frame which is undesirable.
		return v, nil
	}
	c, ok := v.Interface().(constant.Value)
	if !ok {
		// TODO(nick): This should be an error as untyped strings and bools should be constant.Values.
		return v, nil
	}

	kind := t.Kind()
	switch kind {
	case reflect.Bool:
		v = reflect.ValueOf(constant.BoolVal(c))
	case reflect.String:
		v = reflect.ValueOf(constant.StringVal(c))
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		i, _ := constant.Int64Val(constant.ToInt(c))
		v = reflect.ValueOf(i).Convert(t)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		i, _ := constant.Uint64Val(constant.ToInt(c))
		v = reflect.ValueOf(i).Convert(t)
	case reflect.Float32:
		f, _ := constant.Float32Val(constant.ToFloat(c))
		v = reflect.ValueOf(f)
	case reflect.Float64:
		f, _ := constant.Float64Val(constant.ToFloat(c))
		v = reflect.ValueOf(f)
	case reflect.Complex64:
		r, _ := constant.Float32Val(constant.Real(c))
		i, _ := constant.Float32Val(constant.Imag(c))
		v = reflect.ValueOf(complex(r, i)).Convert(t)
	case reflect.Complex128:
		r, _ := constant.Float64Val(constant.Real(c))
		i, _ := constant.Float64Val(constant.Imag(c))
		v = reflect.ValueOf(complex(r, i)).Convert(t)
	default:
		return v, errCantConvert
	}
	return v, nil
}

var bitlen = [...]int{
	reflect.Int:     64,
	reflect.Int8:    8,
	reflect.Int16:   16,
	reflect.Int32:   32,
	reflect.Int64:   64,
	reflect.Uint:    64,
	reflect.Uint8:   8,
	reflect.Uint16:  16,
	reflect.Uint32:  32,
	reflect.Uint64:  64,
	reflect.Uintptr: 64,
}

func representableConst(c constant.Value, t reflect.Type) bool {
	switch {
	case isInt(t):
		x := constant.ToInt(c)
		if x.Kind() != constant.Int {
			return false
		}
		switch t.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if _, ok := constant.Int64Val(x); !ok {
				return false
			}
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
			if _, ok := constant.Uint64Val(x); !ok {
				return false
			}
		default:
			return false
		}
		return constant.BitLen(x) <= bitlen[t.Kind()]
	case isFloat(t):
		x := constant.ToFloat(c)
		if x.Kind() != constant.Float {
			return false
		}
		switch t.Kind() {
		case reflect.Float32:
			f, _ := constant.Float32Val(x)
			return !math.IsInf(float64(f), 0)
		case reflect.Float64:
			f, _ := constant.Float64Val(x)
			return !math.IsInf(f, 0)
		default:
			return false
		}
	case isComplex(t):
		x := constant.ToComplex(c)
		if x.Kind() != constant.Complex {
			return false
		}
		switch t.Kind() {
		case reflect.Complex64:
			r, _ := constant.Float32Val(constant.Real(x))
			i, _ := constant.Float32Val(constant.Imag(x))
			return !math.IsInf(float64(r), 0) && !math.IsInf(float64(i), 0)
		case reflect.Complex128:
			r, _ := constant.Float64Val(constant.Real(x))
			i, _ := constant.Float64Val(constant.Imag(x))
			return !math.IsInf(r, 0) && !math.IsInf(i, 0)
		default:
			return false
		}
	case isString(t):
		return c.Kind() == constant.String
	case isBoolean(t):
		return c.Kind() == constant.Bool
	default:
		return false
	}
}

func isShiftAction(a action) bool {
	switch a {
	case aShl, aShr, aShlAssign, aShrAssign:
		return true
	}
	return false
}

func isComparisonAction(a action) bool {
	switch a {
	case aEqual, aNotEqual, aGreater, aGreaterEqual, aLower, aLowerEqual:
		return true
	}
	return false
}
