blob: b4ec43d551c60931e149e76f949efb08f8199807 [file] [log] [blame]
// Copyright 2023 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 inline
// This file defines the analysis of the callee function.
import (
"bytes"
"encoding/gob"
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
)
// A Callee holds information about an inlinable function. Gob-serializable.
type Callee struct {
impl gobCallee
}
func (callee *Callee) String() string { return callee.impl.Name }
type gobCallee struct {
Content []byte // file content, compacted to a single func decl
// results of type analysis (does not reach go/types data structures)
PkgPath string // package path of declaring package
Name string // user-friendly name for error messages
Unexported []string // names of free objects that are unexported
FreeRefs []freeRef // locations of references to free objects
FreeObjs []object // descriptions of free objects
ValidForCallStmt bool // function body is "return expr" where expr is f() or <-ch
NumResults int // number of results (according to type, not ast.FieldList)
Params []*paramInfo // information about parameters (incl. receiver)
Results []*paramInfo // information about result variables
Effects []int // order in which parameters are evaluated (see calleefx)
HasDefer bool // uses defer
HasBareReturn bool // uses bare return in non-void function
Returns [][]returnOperandFlags // metadata about result expressions for each return
Labels []string // names of all control labels
Falcon falconResult // falcon constraint system
}
// returnOperandFlags records metadata about a single result expression in a return
// statement.
type returnOperandFlags int
const (
nonTrivialResult returnOperandFlags = 1 << iota // return operand has non-trivial conversion to result type
untypedNilResult // return operand is nil literal
)
// A freeRef records a reference to a free object. Gob-serializable.
// (This means free relative to the FuncDecl as a whole, i.e. excluding parameters.)
type freeRef struct {
Offset int // byte offset of the reference relative to the FuncDecl
Object int // index into Callee.freeObjs
}
// An object abstracts a free types.Object referenced by the callee. Gob-serializable.
type object struct {
Name string // Object.Name()
Kind string // one of {var,func,const,type,pkgname,nil,builtin}
PkgPath string // path of object's package (or imported package if kind="pkgname")
PkgName string // name of object's package (or imported package if kind="pkgname")
// TODO(rfindley): should we also track LocalPkgName here? Do we want to
// preserve the local package name?
ValidPos bool // Object.Pos().IsValid()
Shadow shadowMap // shadowing info for the object's refs
}
// AnalyzeCallee analyzes a function that is a candidate for inlining
// and returns a Callee that describes it. The Callee object, which is
// serializable, can be passed to one or more subsequent calls to
// Inline, each with a different Caller.
//
// This design allows separate analysis of callers and callees in the
// golang.org/x/tools/go/analysis framework: the inlining information
// about a callee can be recorded as a "fact".
//
// The content should be the actual input to the compiler, not the
// apparent source file according to any //line directives that
// may be present within it.
func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Package, info *types.Info, decl *ast.FuncDecl, content []byte) (*Callee, error) {
checkInfoFields(info)
// The client is expected to have determined that the callee
// is a function with a declaration (not a built-in or var).
fn := info.Defs[decl.Name].(*types.Func)
sig := fn.Type().(*types.Signature)
logf("analyzeCallee %v @ %v", fn, fset.PositionFor(decl.Pos(), false))
// Create user-friendly name ("pkg.Func" or "(pkg.T).Method")
var name string
if sig.Recv() == nil {
name = fmt.Sprintf("%s.%s", fn.Pkg().Name(), fn.Name())
} else {
name = fmt.Sprintf("(%s).%s", types.TypeString(sig.Recv().Type(), (*types.Package).Name), fn.Name())
}
if decl.Body == nil {
return nil, fmt.Errorf("cannot inline function %s as it has no body", name)
}
// TODO(adonovan): support inlining of instantiated generic
// functions by replacing each occurrence of a type parameter
// T by its instantiating type argument (e.g. int). We'll need
// to wrap the instantiating type in parens when it's not an
// ident or qualified ident to prevent "if x == struct{}"
// parsing ambiguity, or "T(x)" where T = "*int" or "func()"
// from misparsing.
if funcHasTypeParams(decl) {
return nil, fmt.Errorf("cannot inline generic function %s: type parameters are not yet supported", name)
}
// Record the location of all free references in the FuncDecl.
// (Parameters are not free by this definition.)
var (
fieldObjs = fieldObjs(sig)
freeObjIndex = make(map[types.Object]int)
freeObjs []object
freeRefs []freeRef // free refs that may need renaming
unexported []string // free refs to unexported objects, for later error checks
)
var f func(n ast.Node) bool
visit := func(n ast.Node) { ast.Inspect(n, f) }
var stack []ast.Node
stack = append(stack, decl.Type) // for scope of function itself
f = func(n ast.Node) bool {
if n != nil {
stack = append(stack, n) // push
} else {
stack = stack[:len(stack)-1] // pop
}
switch n := n.(type) {
case *ast.SelectorExpr:
// Check selections of free fields/methods.
if sel, ok := info.Selections[n]; ok &&
!within(sel.Obj().Pos(), decl) &&
!n.Sel.IsExported() {
sym := fmt.Sprintf("(%s).%s", info.TypeOf(n.X), n.Sel.Name)
unexported = append(unexported, sym)
}
// Don't recur into SelectorExpr.Sel.
visit(n.X)
return false
case *ast.CompositeLit:
// Check for struct literals that refer to unexported fields,
// whether keyed or unkeyed. (Logic assumes well-typedness.)
litType := typeparams.Deref(info.TypeOf(n))
if s, ok := typeparams.CoreType(litType).(*types.Struct); ok {
if n.Type != nil {
visit(n.Type)
}
for i, elt := range n.Elts {
var field *types.Var
var value ast.Expr
if kv, ok := elt.(*ast.KeyValueExpr); ok {
field = info.Uses[kv.Key.(*ast.Ident)].(*types.Var)
value = kv.Value
} else {
field = s.Field(i)
value = elt
}
if !within(field.Pos(), decl) && !field.Exported() {
sym := fmt.Sprintf("(%s).%s", litType, field.Name())
unexported = append(unexported, sym)
}
// Don't recur into KeyValueExpr.Key.
visit(value)
}
return false
}
case *ast.Ident:
if obj, ok := info.Uses[n]; ok {
// Methods and fields are handled by SelectorExpr and CompositeLit.
if isField(obj) || isMethod(obj) {
panic(obj)
}
// Inv: id is a lexical reference.
// A reference to an unexported package-level declaration
// cannot be inlined into another package.
if !n.IsExported() &&
obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() {
unexported = append(unexported, n.Name)
}
// Record free reference (incl. self-reference).
if obj == fn || !within(obj.Pos(), decl) {
objidx, ok := freeObjIndex[obj]
if !ok {
objidx = len(freeObjIndex)
var pkgPath, pkgName string
if pn, ok := obj.(*types.PkgName); ok {
pkgPath = pn.Imported().Path()
pkgName = pn.Imported().Name()
} else if obj.Pkg() != nil {
pkgPath = obj.Pkg().Path()
pkgName = obj.Pkg().Name()
}
freeObjs = append(freeObjs, object{
Name: obj.Name(),
Kind: objectKind(obj),
PkgName: pkgName,
PkgPath: pkgPath,
ValidPos: obj.Pos().IsValid(),
})
freeObjIndex[obj] = objidx
}
freeObjs[objidx].Shadow = freeObjs[objidx].Shadow.add(info, fieldObjs, obj.Name(), stack)
freeRefs = append(freeRefs, freeRef{
Offset: int(n.Pos() - decl.Pos()),
Object: objidx,
})
}
}
}
return true
}
visit(decl)
// Analyze callee body for "return expr" form,
// where expr is f() or <-ch. These forms are
// safe to inline as a standalone statement.
validForCallStmt := false
if len(decl.Body.List) != 1 {
// not just a return statement
} else if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
validForCallStmt = func() bool {
switch expr := ast.Unparen(ret.Results[0]).(type) {
case *ast.CallExpr: // f(x)
callee := typeutil.Callee(info, expr)
if callee == nil {
return false // conversion T(x)
}
// The only non-void built-in functions that may be
// called as a statement are copy and recover
// (though arguably a call to recover should never
// be inlined as that changes its behavior).
if builtin, ok := callee.(*types.Builtin); ok {
return builtin.Name() == "copy" ||
builtin.Name() == "recover"
}
return true // ordinary call f()
case *ast.UnaryExpr: // <-x
return expr.Op == token.ARROW // channel receive <-ch
}
// No other expressions are valid statements.
return false
}()
}
// Record information about control flow in the callee
// (but not any nested functions).
var (
hasDefer = false
hasBareReturn = false
returnInfo [][]returnOperandFlags
labels []string
)
ast.Inspect(decl.Body, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.FuncLit:
return false // prune traversal
case *ast.DeferStmt:
hasDefer = true
case *ast.LabeledStmt:
labels = append(labels, n.Label.Name)
case *ast.ReturnStmt:
// Are implicit assignment conversions
// to result variables all trivial?
var resultInfo []returnOperandFlags
if len(n.Results) > 0 {
argInfo := func(i int) (ast.Expr, types.Type) {
expr := n.Results[i]
return expr, info.TypeOf(expr)
}
if len(n.Results) == 1 && sig.Results().Len() > 1 {
// Spread return: return f() where f.Results > 1.
tuple := info.TypeOf(n.Results[0]).(*types.Tuple)
argInfo = func(i int) (ast.Expr, types.Type) {
return nil, tuple.At(i).Type()
}
}
for i := 0; i < sig.Results().Len(); i++ {
expr, typ := argInfo(i)
var flags returnOperandFlags
if typ == types.Typ[types.UntypedNil] { // untyped nil is preserved by go/types
flags |= untypedNilResult
}
if !trivialConversion(info.Types[expr].Value, typ, sig.Results().At(i).Type()) {
flags |= nonTrivialResult
}
resultInfo = append(resultInfo, flags)
}
} else if sig.Results().Len() > 0 {
hasBareReturn = true
}
returnInfo = append(returnInfo, resultInfo)
}
return true
})
// Reject attempts to inline cgo-generated functions.
for _, obj := range freeObjs {
// There are others (iconst fconst sconst fpvar macro)
// but this is probably sufficient.
if strings.HasPrefix(obj.Name, "_Cfunc_") ||
strings.HasPrefix(obj.Name, "_Ctype_") ||
strings.HasPrefix(obj.Name, "_Cvar_") {
return nil, fmt.Errorf("cannot inline cgo-generated functions")
}
}
// Compact content to just the FuncDecl.
//
// As a space optimization, we don't retain the complete
// callee file content; all we need is "package _; func f() { ... }".
// This reduces the size of analysis facts.
//
// Offsets in the callee information are "relocatable"
// since they are all relative to the FuncDecl.
content = append([]byte("package _\n"),
content[offsetOf(fset, decl.Pos()):offsetOf(fset, decl.End())]...)
// Sanity check: re-parse the compacted content.
if _, _, err := parseCompact(content); err != nil {
return nil, err
}
params, results, effects, falcon := analyzeParams(logf, fset, info, decl)
return &Callee{gobCallee{
Content: content,
PkgPath: pkg.Path(),
Name: name,
Unexported: unexported,
FreeObjs: freeObjs,
FreeRefs: freeRefs,
ValidForCallStmt: validForCallStmt,
NumResults: sig.Results().Len(),
Params: params,
Results: results,
Effects: effects,
HasDefer: hasDefer,
HasBareReturn: hasBareReturn,
Returns: returnInfo,
Labels: labels,
Falcon: falcon,
}}, nil
}
// parseCompact parses a Go source file of the form "package _\n func f() { ... }"
// and returns the sole function declaration.
func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) {
fset := token.NewFileSet()
const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors
f, err := parser.ParseFile(fset, "callee.go", content, mode)
if err != nil {
return nil, nil, fmt.Errorf("internal error: cannot compact file: %v", err)
}
return fset, f.Decls[0].(*ast.FuncDecl), nil
}
// A paramInfo records information about a callee receiver, parameter, or result variable.
type paramInfo struct {
Name string // parameter name (may be blank, or even "")
Index int // index within signature
IsResult bool // false for receiver or parameter, true for result variable
IsInterface bool // parameter has a (non-type parameter) interface type
Assigned bool // parameter appears on left side of an assignment statement
Escapes bool // parameter has its address taken
Refs []refInfo // information about references to parameter within body
Shadow shadowMap // shadowing info for the above refs; see [shadowMap]
FalconType string // name of this parameter's type (if basic) in the falcon system
}
type refInfo struct {
Offset int // FuncDecl-relative byte offset of parameter ref within body
Assignable bool // ref appears in context of assignment to known type
IfaceAssignment bool // ref is being assigned to an interface
AffectsInference bool // ref type may affect type inference
// IsSelectionOperand indicates whether the parameter reference is the
// operand of a selection (param.f). If so, and param's argument is itself
// a receiver parameter (a common case), we don't need to desugar (&v or *ptr)
// the selection: if param.Method is a valid selection, then so is param.fieldOrMethod.
IsSelectionOperand bool
}
// analyzeParams computes information about parameters of function fn,
// including a simple "address taken" escape analysis.
//
// It returns two new arrays, one of the receiver and parameters, and
// the other of the result variables of function fn.
//
// The input must be well-typed.
func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.Info, decl *ast.FuncDecl) (params, results []*paramInfo, effects []int, _ falconResult) {
fnobj, ok := info.Defs[decl.Name]
if !ok {
panic(fmt.Sprintf("%s: no func object for %q",
fset.PositionFor(decl.Name.Pos(), false), decl.Name)) // ill-typed?
}
sig := fnobj.Type().(*types.Signature)
paramInfos := make(map[*types.Var]*paramInfo)
{
newParamInfo := func(param *types.Var, isResult bool) *paramInfo {
info := &paramInfo{
Name: param.Name(),
IsResult: isResult,
Index: len(paramInfos),
IsInterface: isNonTypeParamInterface(param.Type()),
}
paramInfos[param] = info
return info
}
if sig.Recv() != nil {
params = append(params, newParamInfo(sig.Recv(), false))
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, newParamInfo(sig.Params().At(i), false))
}
for i := 0; i < sig.Results().Len(); i++ {
results = append(results, newParamInfo(sig.Results().At(i), true))
}
}
// Search function body for operations &x, x.f(), and x = y
// where x is a parameter, and record it.
escape(info, decl, func(v *types.Var, escapes bool) {
if info := paramInfos[v]; info != nil {
if escapes {
info.Escapes = true
} else {
info.Assigned = true
}
}
})
// Record locations of all references to parameters.
// And record the set of intervening definitions for each parameter.
//
// TODO(adonovan): combine this traversal with the one that computes
// FreeRefs. The tricky part is that calleefx needs this one first.
fieldObjs := fieldObjs(sig)
var stack []ast.Node
stack = append(stack, decl.Type) // for scope of function itself
ast.Inspect(decl.Body, func(n ast.Node) bool {
if n != nil {
stack = append(stack, n) // push
} else {
stack = stack[:len(stack)-1] // pop
}
if id, ok := n.(*ast.Ident); ok {
if v, ok := info.Uses[id].(*types.Var); ok {
if pinfo, ok := paramInfos[v]; ok {
// Record ref information, and any intervening (shadowing) names.
//
// If the parameter v has an interface type, and the reference id
// appears in a context where assignability rules apply, there may be
// an implicit interface-to-interface widening. In that case it is
// not necessary to insert an explicit conversion from the argument
// to the parameter's type.
//
// Contrapositively, if param is not an interface type, then the
// assignment may lose type information, for example in the case that
// the substituted expression is an untyped constant or unnamed type.
assignable, ifaceAssign, affectsInference := analyzeAssignment(info, stack)
ref := refInfo{
Offset: int(n.Pos() - decl.Pos()),
Assignable: assignable,
IfaceAssignment: ifaceAssign,
AffectsInference: affectsInference,
IsSelectionOperand: isSelectionOperand(stack),
}
pinfo.Refs = append(pinfo.Refs, ref)
pinfo.Shadow = pinfo.Shadow.add(info, fieldObjs, pinfo.Name, stack)
}
}
}
return true
})
// Compute subset and order of parameters that are strictly evaluated.
// (Depends on Refs computed above.)
effects = calleefx(info, decl.Body, paramInfos)
logf("effects list = %v", effects)
falcon := falcon(logf, fset, paramInfos, info, decl)
return params, results, effects, falcon
}
// -- callee helpers --
// analyzeAssignment looks at the the given stack, and analyzes certain
// attributes of the innermost expression.
//
// In all cases we 'fail closed' when we cannot detect (or for simplicity
// choose not to detect) the condition in question, meaning we err on the side
// of the more restrictive rule. This is noted for each result below.
//
// - assignable reports whether the expression is used in a position where
// assignability rules apply, such as in an actual assignment, as call
// argument, or in a send to a channel. Defaults to 'false'. If assignable
// is false, the other two results are irrelevant.
// - ifaceAssign reports whether that assignment is to an interface type.
// This is important as we want to preserve the concrete type in that
// assignment. Defaults to 'true'. Notably, if the assigned type is a type
// parameter, we assume that it could have interface type.
// - affectsInference is (somewhat vaguely) defined as whether or not the
// type of the operand may affect the type of the surrounding syntax,
// through type inference. It is infeasible to completely reverse engineer
// type inference, so we over approximate: if the expression is an argument
// to a call to a generic function (but not method!) that uses type
// parameters, assume that unification of that argument may affect the
// inferred types.
func analyzeAssignment(info *types.Info, stack []ast.Node) (assignable, ifaceAssign, affectsInference bool) {
remaining, parent, expr := exprContext(stack)
if parent == nil {
return false, false, false
}
// TODO(golang/go#70638): simplify when types.Info records implicit conversions.
// Types do not need to match for assignment to a variable.
if assign, ok := parent.(*ast.AssignStmt); ok {
for i, v := range assign.Rhs {
if v == expr {
if i >= len(assign.Lhs) {
return false, false, false // ill typed
}
// Check to see if the assignment is to an interface type.
if i < len(assign.Lhs) {
// TODO: We could handle spread calls here, but in current usage expr
// is an ident.
if id, _ := assign.Lhs[i].(*ast.Ident); id != nil && info.Defs[id] != nil {
// Types must match for a defining identifier in a short variable
// declaration.
return false, false, false
}
// In all other cases, types should be known.
typ := info.TypeOf(assign.Lhs[i])
return true, typ == nil || types.IsInterface(typ), false
}
// Default:
return assign.Tok == token.ASSIGN, true, false
}
}
}
// Types do not need to match for an initializer with known type.
if spec, ok := parent.(*ast.ValueSpec); ok && spec.Type != nil {
for _, v := range spec.Values {
if v == expr {
typ := info.TypeOf(spec.Type)
return true, typ == nil || types.IsInterface(typ), false
}
}
}
// Types do not need to match for index expresions.
if ix, ok := parent.(*ast.IndexExpr); ok {
if ix.Index == expr {
typ := info.TypeOf(ix.X)
if typ == nil {
return true, true, false
}
m, _ := typeparams.CoreType(typ).(*types.Map)
return true, m == nil || types.IsInterface(m.Key()), false
}
}
// Types do not need to match for composite literal keys, values, or
// fields.
if kv, ok := parent.(*ast.KeyValueExpr); ok {
var under types.Type
if len(remaining) > 0 {
if complit, ok := remaining[len(remaining)-1].(*ast.CompositeLit); ok {
if typ := info.TypeOf(complit); typ != nil {
// Unpointer to allow for pointers to slices or arrays, which are
// permitted as the types of nested composite literals without a type
// name.
under = typesinternal.Unpointer(typeparams.CoreType(typ))
}
}
}
if kv.Key == expr { // M{expr: ...}: assign to map key
m, _ := under.(*types.Map)
return true, m == nil || types.IsInterface(m.Key()), false
}
if kv.Value == expr {
switch under := under.(type) {
case interface{ Elem() types.Type }: // T{...: expr}: assign to map/array/slice element
return true, types.IsInterface(under.Elem()), false
case *types.Struct: // Struct{k: expr}
if id, _ := kv.Key.(*ast.Ident); id != nil {
for fi := 0; fi < under.NumFields(); fi++ {
field := under.Field(fi)
if info.Uses[id] == field {
return true, types.IsInterface(field.Type()), false
}
}
}
default:
return true, true, false
}
}
}
if lit, ok := parent.(*ast.CompositeLit); ok {
for i, v := range lit.Elts {
if v == expr {
typ := info.TypeOf(lit)
if typ == nil {
return true, true, false
}
// As in the KeyValueExpr case above, unpointer to handle pointers to
// array/slice literals.
under := typesinternal.Unpointer(typeparams.CoreType(typ))
switch under := under.(type) {
case interface{ Elem() types.Type }: // T{expr}: assign to map/array/slice element
return true, types.IsInterface(under.Elem()), false
case *types.Struct: // Struct{expr}: assign to unkeyed struct field
if i < under.NumFields() {
return true, types.IsInterface(under.Field(i).Type()), false
}
}
return true, true, false
}
}
}
// Types do not need to match for values sent to a channel.
if send, ok := parent.(*ast.SendStmt); ok {
if send.Value == expr {
typ := info.TypeOf(send.Chan)
if typ == nil {
return true, true, false
}
ch, _ := typeparams.CoreType(typ).(*types.Chan)
return true, ch == nil || types.IsInterface(ch.Elem()), false
}
}
// Types do not need to match for an argument to a call, unless the
// corresponding parameter has type parameters, as in that case the
// argument type may affect inference.
if call, ok := parent.(*ast.CallExpr); ok {
if _, ok := isConversion(info, call); ok {
return false, false, false // redundant conversions are handled at the call site
}
// Ordinary call. Could be a call of a func, builtin, or function value.
for i, arg := range call.Args {
if arg == expr {
typ := info.TypeOf(call.Fun)
if typ == nil {
return true, true, false
}
sig, _ := typeparams.CoreType(typ).(*types.Signature)
if sig != nil {
// Find the relevant parameter type, accounting for variadics.
paramType := paramTypeAtIndex(sig, call, i)
ifaceAssign := paramType == nil || types.IsInterface(paramType)
affectsInference := false
if fn := typeutil.StaticCallee(info, call); fn != nil {
if sig2 := fn.Type().(*types.Signature); sig2.Recv() == nil {
originParamType := paramTypeAtIndex(sig2, call, i)
affectsInference = originParamType == nil || new(typeparams.Free).Has(originParamType)
}
}
return true, ifaceAssign, affectsInference
}
}
}
}
return false, false, false
}
// paramTypeAtIndex returns the effective parameter type at the given argument
// index in call, if valid.
func paramTypeAtIndex(sig *types.Signature, call *ast.CallExpr, index int) types.Type {
if plen := sig.Params().Len(); sig.Variadic() && index >= plen-1 && !call.Ellipsis.IsValid() {
if s, ok := sig.Params().At(plen - 1).Type().(*types.Slice); ok {
return s.Elem()
}
} else if index < plen {
return sig.Params().At(index).Type()
}
return nil // ill typed
}
// exprContext returns the innermost parent->child expression nodes for the
// given outer-to-inner stack, after stripping parentheses, along with the
// remaining stack up to the parent node.
//
// If no such context exists, returns (nil, nil).
func exprContext(stack []ast.Node) (remaining []ast.Node, parent ast.Node, expr ast.Expr) {
expr, _ = stack[len(stack)-1].(ast.Expr)
if expr == nil {
return nil, nil, nil
}
i := len(stack) - 2
for ; i >= 0; i-- {
if pexpr, ok := stack[i].(*ast.ParenExpr); ok {
expr = pexpr
} else {
parent = stack[i]
break
}
}
if parent == nil {
return nil, nil, nil
}
// inv: i is the index of parent in the stack.
return stack[:i], parent, expr
}
// isSelectionOperand reports whether the innermost node of stack is operand
// (x) of a selection x.f.
func isSelectionOperand(stack []ast.Node) bool {
_, parent, expr := exprContext(stack)
if parent == nil {
return false
}
sel, ok := parent.(*ast.SelectorExpr)
return ok && sel.X == expr
}
// A shadowMap records information about shadowing at any of the parameter's
// references within the callee decl.
//
// For each name shadowed at a reference to the parameter within the callee
// body, shadow map records the 1-based index of the callee decl parameter
// causing the shadowing, or -1, if the shadowing is not due to a callee decl.
// A value of zero (or missing) indicates no shadowing. By convention,
// self-shadowing is excluded from the map.
//
// For example, in the following callee
//
// func f(a, b int) int {
// c := 2 + b
// return a + c
// }
//
// the shadow map of a is {b: 2, c: -1}, because b is shadowed by the 2nd
// parameter. The shadow map of b is {a: 1}, because c is not shadowed at the
// use of b.
type shadowMap map[string]int
// add returns the [shadowMap] augmented by the set of names
// locally shadowed at the location of the reference in the callee
// (identified by the stack). The name of the reference itself is
// excluded.
//
// These shadowed names may not be used in a replacement expression
// for the reference.
func (s shadowMap) add(info *types.Info, paramIndexes map[types.Object]int, exclude string, stack []ast.Node) shadowMap {
for _, n := range stack {
if scope := scopeFor(info, n); scope != nil {
for _, name := range scope.Names() {
if name != exclude {
if s == nil {
s = make(shadowMap)
}
obj := scope.Lookup(name)
if idx, ok := paramIndexes[obj]; ok {
s[name] = idx + 1
} else {
s[name] = -1
}
}
}
}
}
return s
}
// fieldObjs returns a map of each types.Object defined by the given signature
// to its index in the parameter list. Parameters with missing or blank name
// are skipped.
func fieldObjs(sig *types.Signature) map[types.Object]int {
m := make(map[types.Object]int)
for i := range sig.Params().Len() {
if p := sig.Params().At(i); p.Name() != "" && p.Name() != "_" {
m[p] = i
}
}
return m
}
func isField(obj types.Object) bool {
if v, ok := obj.(*types.Var); ok && v.IsField() {
return true
}
return false
}
func isMethod(obj types.Object) bool {
if f, ok := obj.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil {
return true
}
return false
}
// -- serialization --
var (
_ gob.GobEncoder = (*Callee)(nil)
_ gob.GobDecoder = (*Callee)(nil)
)
func (callee *Callee) GobEncode() ([]byte, error) {
var out bytes.Buffer
if err := gob.NewEncoder(&out).Encode(callee.impl); err != nil {
return nil, err
}
return out.Bytes(), nil
}
func (callee *Callee) GobDecode(data []byte) error {
return gob.NewDecoder(bytes.NewReader(data)).Decode(&callee.impl)
}