blob: 9487e76336cea7f8938ebd8ee288ea8751d6e613 [file] [log] [blame]
// Copyright 2021 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.
package noder
import (
"go/constant"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/src"
)
// Helpers for constructing typed IR nodes.
//
// TODO(mdempsky): Move into their own package so they can be easily
// reused by iimport and frontend optimizations.
type ImplicitNode interface {
ir.Node
SetImplicit(x bool)
}
// Implicit returns n after marking it as Implicit.
func Implicit(n ImplicitNode) ImplicitNode {
n.SetImplicit(true)
return n
}
// typed returns n after setting its type to typ.
func typed(typ *types.Type, n ir.Node) ir.Node {
n.SetType(typ)
n.SetTypecheck(1)
return n
}
// Values
func Const(pos src.XPos, typ *types.Type, val constant.Value) ir.Node {
return typed(typ, ir.NewBasicLit(pos, val))
}
func OrigConst(pos src.XPos, typ *types.Type, val constant.Value, op ir.Op, raw string) ir.Node {
orig := ir.NewRawOrigExpr(pos, op, raw)
return ir.NewConstExpr(val, typed(typ, orig))
}
// FixValue returns val after converting and truncating it as
// appropriate for typ.
func FixValue(typ *types.Type, val constant.Value) constant.Value {
assert(typ.Kind() != types.TFORW)
switch {
case typ.IsInteger():
val = constant.ToInt(val)
case typ.IsFloat():
val = constant.ToFloat(val)
case typ.IsComplex():
val = constant.ToComplex(val)
}
if !typ.IsUntyped() {
val = typecheck.DefaultLit(ir.NewBasicLit(src.NoXPos, val), typ).Val()
}
if !typ.IsTypeParam() {
ir.AssertValidTypeForConst(typ, val)
}
return val
}
func Nil(pos src.XPos, typ *types.Type) ir.Node {
return typed(typ, ir.NewNilExpr(pos))
}
// Expressions
func Addr(pos src.XPos, x ir.Node) *ir.AddrExpr {
n := typecheck.NodAddrAt(pos, x)
switch x.Op() {
case ir.OARRAYLIT, ir.OMAPLIT, ir.OSLICELIT, ir.OSTRUCTLIT:
n.SetOp(ir.OPTRLIT)
}
typed(types.NewPtr(x.Type()), n)
return n
}
func Assert(pos src.XPos, x ir.Node, typ *types.Type) ir.Node {
return typed(typ, ir.NewTypeAssertExpr(pos, x, nil))
}
func Binary(pos src.XPos, op ir.Op, typ *types.Type, x, y ir.Node) ir.Node {
switch op {
case ir.OANDAND, ir.OOROR:
return typed(x.Type(), ir.NewLogicalExpr(pos, op, x, y))
case ir.OADD:
n := ir.NewBinaryExpr(pos, op, x, y)
if x.Type().HasTParam() || y.Type().HasTParam() {
// Delay transformAdd() if either arg has a type param,
// since it needs to know the exact types to decide whether
// to transform OADD to OADDSTR.
n.SetType(typ)
n.SetTypecheck(3)
return n
}
typed(typ, n)
return transformAdd(n)
default:
return typed(x.Type(), ir.NewBinaryExpr(pos, op, x, y))
}
}
func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) ir.Node {
n := ir.NewCallExpr(pos, ir.OCALL, fun, args)
n.IsDDD = dots
if fun.Op() == ir.OTYPE {
// Actually a type conversion, not a function call.
if !fun.Type().IsInterface() &&
(fun.Type().HasTParam() || args[0].Type().HasTParam()) {
// For type params, we can transform if fun.Type() is known
// to be an interface (in which case a CONVIFACE node will be
// inserted). Otherwise, don't typecheck until we actually
// know the type.
return typed(typ, n)
}
typed(typ, n)
return transformConvCall(n)
}
if fun, ok := fun.(*ir.Name); ok && fun.BuiltinOp != 0 {
// For most Builtin ops, we delay doing transformBuiltin if any of the
// args have type params, for a variety of reasons:
//
// OMAKE: transformMake can't choose specific ops OMAKESLICE, etc.
// until arg type is known
// OREAL/OIMAG: transformRealImag can't determine type float32/float64
// until arg type known
// OAPPEND: transformAppend requires that the arg is a slice
// ODELETE: transformDelete requires that the arg is a map
// OALIGNOF, OSIZEOF: can be eval'ed to a constant until types known.
switch fun.BuiltinOp {
case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.ODELETE, ir.OALIGNOF, ir.OOFFSETOF, ir.OSIZEOF:
hasTParam := false
for _, arg := range args {
if fun.BuiltinOp == ir.OOFFSETOF {
// It's the type of left operand of the
// selection that matters, not the type of
// the field itself (which is irrelevant for
// offsetof).
arg = arg.(*ir.SelectorExpr).X
}
if arg.Type().HasTParam() {
hasTParam = true
break
}
}
if hasTParam {
return typed(typ, n)
}
}
typed(typ, n)
return transformBuiltin(n)
}
// Add information, now that we know that fun is actually being called.
switch fun := fun.(type) {
case *ir.SelectorExpr:
if fun.Op() == ir.OMETHVALUE {
op := ir.ODOTMETH
if fun.X.Type().IsInterface() {
op = ir.ODOTINTER
}
fun.SetOp(op)
// Set the type to include the receiver, since that's what
// later parts of the compiler expect
fun.SetType(fun.Selection.Type)
}
}
if fun.Type().HasTParam() || fun.Op() == ir.OXDOT || fun.Op() == ir.OFUNCINST {
// If the fun arg is or has a type param, we can't do all the
// transformations, since we may not have needed properties yet
// (e.g. number of return values, etc). The same applies if a fun
// which is an XDOT could not be transformed yet because of a generic
// type in the X of the selector expression.
//
// A function instantiation (even if fully concrete) shouldn't be
// transformed yet, because we need to add the dictionary during the
// transformation.
//
// However, if we have a function type (even though it is
// parameterized), then we can add in any needed CONVIFACE nodes via
// typecheckaste(). We need to call transformArgs() to deal first
// with the f(g(()) case where g returns multiple return values. We
// can't do anything if fun is a type param (which is probably
// described by a structural constraint)
if fun.Type().Kind() == types.TFUNC {
transformArgs(n)
typecheckaste(ir.OCALL, fun, n.IsDDD, fun.Type().Params(), n.Args, true)
}
return typed(typ, n)
}
// If no type params, do the normal call transformations. This
// will convert OCALL to OCALLFUNC.
typed(typ, n)
transformCall(n)
return n
}
func Compare(pos src.XPos, typ *types.Type, op ir.Op, x, y ir.Node) ir.Node {
n := ir.NewBinaryExpr(pos, op, x, y)
if x.Type().HasTParam() || y.Type().HasTParam() {
xIsInt := x.Type().IsInterface()
yIsInt := y.Type().IsInterface()
if !(xIsInt && !yIsInt || !xIsInt && yIsInt) {
// If either arg is a type param, then we can still do the
// transformCompare() if we know that one arg is an interface
// and the other is not. Otherwise, we delay
// transformCompare(), since it needs to know the exact types
// to decide on any needed conversions.
n.SetType(typ)
n.SetTypecheck(3)
return n
}
}
typed(typ, n)
transformCompare(n)
return n
}
func Deref(pos src.XPos, typ *types.Type, x ir.Node) *ir.StarExpr {
n := ir.NewStarExpr(pos, x)
typed(typ, n)
return n
}
func DotField(pos src.XPos, x ir.Node, index int) *ir.SelectorExpr {
op, typ := ir.ODOT, x.Type()
if typ.IsPtr() {
op, typ = ir.ODOTPTR, typ.Elem()
}
if !typ.IsStruct() {
base.FatalfAt(pos, "DotField of non-struct: %L", x)
}
// TODO(mdempsky): This is the backend's responsibility.
types.CalcSize(typ)
field := typ.Field(index)
return dot(pos, field.Type, op, x, field)
}
func DotMethod(pos src.XPos, x ir.Node, index int) *ir.SelectorExpr {
method := method(x.Type(), index)
// Method value.
typ := typecheck.NewMethodType(method.Type, nil)
return dot(pos, typ, ir.OMETHVALUE, x, method)
}
// MethodExpr returns a OMETHEXPR node with the indicated index into the methods
// of typ. The receiver type is set from recv, which is different from typ if the
// method was accessed via embedded fields. Similarly, the X value of the
// ir.SelectorExpr is recv, the original OTYPE node before passing through the
// embedded fields.
func MethodExpr(pos src.XPos, recv ir.Node, embed *types.Type, index int) *ir.SelectorExpr {
method := method(embed, index)
typ := typecheck.NewMethodType(method.Type, recv.Type())
// The method expression T.m requires a wrapper when T
// is different from m's declared receiver type. We
// normally generate these wrappers while writing out
// runtime type descriptors, which is always done for
// types declared at package scope. However, we need
// to make sure to generate wrappers for anonymous
// receiver types too.
if recv.Sym() == nil {
typecheck.NeedRuntimeType(recv.Type())
}
return dot(pos, typ, ir.OMETHEXPR, recv, method)
}
func dot(pos src.XPos, typ *types.Type, op ir.Op, x ir.Node, selection *types.Field) *ir.SelectorExpr {
n := ir.NewSelectorExpr(pos, op, x, selection.Sym)
n.Selection = selection
typed(typ, n)
return n
}
// TODO(mdempsky): Move to package types.
func method(typ *types.Type, index int) *types.Field {
if typ.IsInterface() {
return typ.AllMethods().Index(index)
}
return types.ReceiverBaseType(typ).Methods().Index(index)
}
func Index(pos src.XPos, typ *types.Type, x, index ir.Node) ir.Node {
n := ir.NewIndexExpr(pos, x, index)
if x.Type().HasTParam() {
// transformIndex needs to know exact type
n.SetType(typ)
n.SetTypecheck(3)
return n
}
typed(typ, n)
// transformIndex will modify n.Type() for OINDEXMAP.
transformIndex(n)
return n
}
func Slice(pos src.XPos, typ *types.Type, x, low, high, max ir.Node) ir.Node {
op := ir.OSLICE
if max != nil {
op = ir.OSLICE3
}
n := ir.NewSliceExpr(pos, op, x, low, high, max)
if x.Type().HasTParam() {
// transformSlice needs to know if x.Type() is a string or an array or a slice.
n.SetType(typ)
n.SetTypecheck(3)
return n
}
typed(typ, n)
transformSlice(n)
return n
}
func Unary(pos src.XPos, typ *types.Type, op ir.Op, x ir.Node) ir.Node {
switch op {
case ir.OADDR:
return Addr(pos, x)
case ir.ODEREF:
return Deref(pos, typ, x)
}
if op == ir.ORECV {
if typ.IsFuncArgStruct() && typ.NumFields() == 2 {
// Remove the second boolean type (if provided by type2),
// since that works better with the rest of the compiler
// (which will add it back in later).
assert(typ.Field(1).Type.Kind() == types.TBOOL)
typ = typ.Field(0).Type
}
}
return typed(typ, ir.NewUnaryExpr(pos, op, x))
}
// Statements
var one = constant.MakeInt64(1)
func IncDec(pos src.XPos, op ir.Op, x ir.Node) *ir.AssignOpStmt {
assert(x.Type() != nil)
bl := ir.NewBasicLit(pos, one)
if x.Type().HasTParam() {
// If the operand is generic, then types2 will have proved it must be
// a type that fits with increment/decrement, so just set the type of
// "one" to n.Type(). This works even for types that are eventually
// float or complex.
typed(x.Type(), bl)
} else {
bl = typecheck.DefaultLit(bl, x.Type())
}
return ir.NewAssignOpStmt(pos, op, x, bl)
}