mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
In the cases where we do an early call to typecheckaste() in noder to expose CONVIFACE nodes, we need a preceding call to transformArgs(). This is needed to allow typecheckaste() to run correctly, in the case of f(g()), where g has multiple return values. I also cleaned up the code a bit and commented the code in Call(), and we do the call to typecheckaste() in several more cases. In stencil.go:stencil(), I moved the transformCall earlier for the OCALLMETH/ODOTMETH case, just as I did in my previous CL for OCALL/OFUNCINST. By doing this, transformArgs no longer needs to deal with the extra dictionary args. Therefore, I was able to simply transformArgs() to look like typecheckargs() again, and make use of RewriteMultiValue directly. Updates #47514 Change-Id: I49eb82ac05707e50c2e2fb03e39458a70491d406 Reviewed-on: https://go-review.googlesource.com/c/go/+/340531 Trust: Dan Scales <danscales@google.com> Run-TryBot: Dan Scales <danscales@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
360 lines
10 KiB
Go
360 lines
10 KiB
Go
// 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
|
|
switch fun.BuiltinOp {
|
|
case ir.OMAKE, ir.OREAL, ir.OIMAG, ir.OAPPEND, ir.ODELETE:
|
|
hasTParam := false
|
|
for _, arg := range args {
|
|
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)
|
|
}
|