1
0
mirror of https://github.com/golang/go synced 2024-11-19 03:54:42 -07:00
go/ssa/source.go
Alan Donovan 4da31df1c8 go.tools/ssa: (another) major refactoring of method-set logic.
We now use LookupFieldOrMethod for all SelectorExprs, and
simplify the logic to discriminate the various cases.

We inline static calls to promoted/indirected functions,
dramatically reducing the number of functions created.

More tests are needed, but I'd like to submit this as-is.

In this CL, we:
- rely less on Id strings.  Internally we now use
  *types.Method (and its components) almost everywhere.
- stop thinking of types.Methods as objects. They don't
  have stable identities. (Hopefully they will become
  plain-old structs soon.)
- eliminate receiver indirection wrappers:
  indirection and promotion are handled together by makeWrapper.
- Handle the interactions of promotion, indirection and
  abstract methods much more cleanly.
- support receiver-bound interface method closures.
- break up builder.selectField so we can re-use parts
  (emitFieldSelection).
- add importer.PackageInfo.classifySelector utility.
- delete interfaceMethodIndex()
- delete namedTypeMethodIndex()
- delete isSuperInterface() (replaced by types.IsAssignable)
- call memberFromObject on each declared concrete method's
  *types.Func, not on every Method frem each method set, in the
  CREATE phase for packages loaded by gcimporter.

go/types:
- document Func, Signature.Recv() better.
- use fmt in {Package,Label}.String
- reimplement Func.String to be prettier and to include method
  receivers.

API changes:
- Function.method now holds the types.Method (soon to be
  not-an-object) for synthetic wrappers.
- CallCommon.Method now contains an abstract (interface)
  method object; was an abstract method index.
- CallCommon.MethodId() gone.
- Program.LookupMethod now takes a *Method not an Id string.

R=gri
CC=golang-dev
https://golang.org/cl/11674043
2013-07-26 11:22:34 -04:00

324 lines
8.7 KiB
Go

package ssa
// This file defines utilities for working with source positions.
import (
"go/ast"
"go/token"
"code.google.com/p/go.tools/go/types"
)
// EnclosingFunction returns the function that contains the syntax
// node denoted by path.
//
// Syntax associated with package-level variable specifications is
// enclosed by the package's init() function.
//
// Returns nil if not found; reasons might include:
// - the node is not enclosed by any function.
// - the node is within an anonymous function (FuncLit) and
// its SSA function has not been created yet (pkg.BuildPackage()
// has not yet been called).
//
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
// Start with package-level function...
fn := findEnclosingPackageLevelFunction(pkg, path)
if fn == nil {
return nil // not in any function
}
// ...then walk down the nested anonymous functions.
n := len(path)
outer:
for i := range path {
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
for _, anon := range fn.AnonFuncs {
if anon.Pos() == lit.Type.Func {
fn = anon
continue outer
}
}
// SSA function not found:
// - package not yet built, or maybe
// - builder skipped FuncLit in dead block
// (in principle; but currently the Builder
// generates even dead FuncLits).
return nil
}
}
return fn
}
// HasEnclosingFunction returns true if the AST node denoted by path
// is contained within the declaration of some function or
// package-level variable.
//
// Unlike EnclosingFunction, the behaviour of this function does not
// depend on whether SSA code for pkg has been built, so it can be
// used to quickly reject check inputs that will cause
// EnclosingFunction to fail, prior to SSA building.
//
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
return findEnclosingPackageLevelFunction(pkg, path) != nil
}
// findEnclosingPackageLevelFunction returns the Function
// corresponding to the package-level function enclosing path.
//
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
switch decl := path[n-2].(type) {
case *ast.GenDecl:
if decl.Tok == token.VAR && n >= 3 {
// Package-level 'var' initializer.
return pkg.init
}
case *ast.FuncDecl:
if decl.Recv == nil && decl.Name.Name == "init" {
// Explicit init() function.
return pkg.init
}
// Declared function/method.
return findNamedFunc(pkg, decl.Name.NamePos)
}
}
return nil // not in any function
}
// findNamedFunc returns the named function whose FuncDecl.Ident is at
// position pos.
//
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// Look at all package members and method sets of named types.
// Not very efficient.
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *Function:
if mem.Pos() == pos {
return mem
}
case *Type:
for _, meth := range pkg.Prog.MethodSet(mem.Type()) {
if meth.Synthetic == "" && meth.Pos() == pos {
return meth
}
}
for _, meth := range pkg.Prog.MethodSet(types.NewPointer(mem.Type())) {
if meth.Synthetic == "" && meth.Pos() == pos {
return meth
}
}
}
}
return nil
}
// CanonicalPos returns the canonical position of the AST node n,
//
// For each Node kind that may generate an SSA Value or Instruction,
// exactly one token within it is designated as "canonical". The
// position of that token is returned by {Value,Instruction}.Pos().
// The specifications of those methods determine the implementation of
// this function.
//
// TODO(adonovan): test coverage.
//
func CanonicalPos(n ast.Node) token.Pos {
// Comments show the Value/Instruction kinds v that may be
// created by n such that CanonicalPos(n) == v.Pos().
switch n := n.(type) {
case *ast.ParenExpr:
return CanonicalPos(n.X)
case *ast.CallExpr:
// f(x): *Call, *Go, *Defer.
// T(x): *ChangeType, *Convert, *MakeInterface, *ChangeInterface.
// make(): *MakeMap, *MakeChan, *MakeSlice.
// new(): *Alloc.
// panic(): *Panic.
return n.Lparen
case *ast.Ident:
return n.NamePos // *Parameter, *Alloc, *Capture
case *ast.TypeAssertExpr:
return n.Lparen // *ChangeInterface or *TypeAssertExpr
case *ast.SelectorExpr:
return n.Sel.NamePos // *MakeClosure, *Field, *FieldAddr
case *ast.FuncLit:
return n.Type.Func // *Function or *MakeClosure
case *ast.CompositeLit:
return n.Lbrace // *Alloc or *Slice
case *ast.BinaryExpr:
return n.OpPos // *Phi or *BinOp
case *ast.UnaryExpr:
return n.OpPos // *Phi or *UnOp
case *ast.IndexExpr:
return n.Lbrack // *Index or *IndexAddr
case *ast.SliceExpr:
return n.Lbrack // *Slice
case *ast.SelectStmt:
return n.Select // *Select
case *ast.RangeStmt:
return n.For // *Range
case *ast.ReturnStmt:
return n.Return // *Ret
case *ast.SendStmt:
return n.Arrow // *Send
case *ast.StarExpr:
return n.Star // *Store
case *ast.KeyValueExpr:
return n.Colon // *MapUpdate
}
return token.NoPos
}
// --- Lookup functions for source-level named entities (types.Objects) ---
// Package returns the SSA Package corresponding to the specified
// type-checker package object.
// It returns nil if no such SSA package has been created.
//
func (prog *Program) Package(obj *types.Package) *Package {
return prog.packages[obj]
}
// packageLevelValue returns the package-level value corresponding to
// the specified named object, which may be a package-level const
// (*Const), var (*Global) or func (*Function) of some package in
// prog. It returns nil if the object is not found.
//
func (prog *Program) packageLevelValue(obj types.Object) Value {
if pkg, ok := prog.packages[obj.Pkg()]; ok {
return pkg.values[obj]
}
return nil
}
// FuncValue returns the SSA Value denoted by the source-level named
// function obj. The result may be a *Function or a *Builtin, or nil
// if not found.
//
func (prog *Program) FuncValue(obj *types.Func) Value {
// Universal built-in?
if v, ok := prog.builtins[obj]; ok {
return v
}
// Package-level function?
if v := prog.packageLevelValue(obj); v != nil {
return v
}
// Concrete method?
if v := prog.concreteMethods[obj]; v != nil {
return v
}
// TODO(adonovan): interface method wrappers? other wrappers?
return nil
}
// ConstValue returns the SSA Value denoted by the source-level named
// constant obj. The result may be a *Const, or nil if not found.
//
func (prog *Program) ConstValue(obj *types.Const) *Const {
// TODO(adonovan): opt: share (don't reallocate)
// Consts for const objects.
// Universal constant? {true,false,nil}
if obj.Parent() == types.Universe {
return NewConst(obj.Val(), obj.Type())
}
// Package-level named constant?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Const)
}
return NewConst(obj.Val(), obj.Type())
}
// VarValue returns the SSA Value that corresponds to a specific
// identifier denoting the source-level named variable obj.
//
// VarValue returns nil if a local variable was not found, perhaps
// because its package was not built, the DebugInfo flag was not set
// during SSA construction, or the value was optimized away.
//
// TODO(adonovan): test on x.f where x is a field.
//
// ref must be the path to an ast.Ident (e.g. from
// PathEnclosingInterval), and that ident must resolve to obj.
//
// The Value of a defining (as opposed to referring) identifier is the
// value assigned to it in its definition.
//
// In many cases where the identifier appears in an lvalue context,
// the resulting Value is the var's address, not its value.
// For example, x in all these examples:
// x.y = 0
// x[0] = 0
// _ = x[:]
// x = X{}
// _ = &x
// x.method() (iff method is on &x)
// and all package-level vars. (This situation can be detected by
// comparing the types of the Var and Value.)
//
func (prog *Program) VarValue(obj *types.Var, ref []ast.Node) Value {
id := ref[0].(*ast.Ident)
// Package-level variable?
if v := prog.packageLevelValue(obj); v != nil {
return v.(*Global)
}
// It's a local variable (or param) of some function.
// The reference may occur inside a lexically nested function,
// so find that first.
pkg := prog.packages[obj.Pkg()]
if pkg == nil {
panic("no package for " + obj.String())
}
fn := EnclosingFunction(pkg, ref)
if fn == nil {
return nil // e.g. SSA not built
}
// Defining ident of a parameter?
if id.Pos() == obj.Pos() {
for _, param := range fn.Params {
if param.Object() == obj {
return param
}
}
}
// Other ident?
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if ref, ok := instr.(*DebugRef); ok {
if ref.Pos() == id.Pos() {
return ref.X
}
}
}
}
return nil // e.g. DebugInfo unset, or var optimized away
}