// Copyright 2013 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 ir

// This file defines utilities for working with source positions
// or source-level named entities ("objects").

// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
// the originating syntax, as specified.

import (
	"go/ast"
	"go/token"
	"go/types"

	"golang.org/x/exp/typeparams"
)

// 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 IR function has not been created yet
//     (pkg.Build() 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
				}
			}
			// IR 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 IR code for pkg has been built, so it can be
// used to quickly reject check inputs that will cause
// EnclosingFunction to fail, prior to IR 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:
			// Declared function/method.
			fn := findNamedFunc(pkg, decl.Pos())
			if fn == nil && decl.Recv == nil && decl.Name.Name == "init" {
				// Hack: return non-nil when IR is not yet
				// built so that HasEnclosingFunction works.
				return pkg.init
			}
			return fn
		}
	}
	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 {
	for _, fn := range pkg.Functions {
		if fn.Pos() == pos {
			return fn
		}
	}
	return nil
}

// ValueForExpr returns the IR Value that corresponds to non-constant
// expression e.
//
// It returns nil if no value was found, e.g.
//   - the expression is not lexically contained within f;
//   - f was not built with debug information; or
//   - e is a constant expression.  (For efficiency, no debug
//     information is stored for constants. Use
//     go/types.Info.Types[e].Value instead.)
//   - e is a reference to nil or a built-in function.
//   - the value was optimised away.
//
// If e is an addressable expression used in an lvalue context,
// value is the address denoted by e, and isAddr is true.
//
// The types of e (or &e, if isAddr) and the result are equal
// (modulo "untyped" bools resulting from comparisons).
//
// (Tip: to find the ir.Value given a source position, use
// astutil.PathEnclosingInterval to locate the ast.Node, then
// EnclosingFunction to locate the Function, then ValueForExpr to find
// the ir.Value.)
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
	if f.debugInfo() { // (opt)
		e = unparen(e)
		for _, b := range f.Blocks {
			for _, instr := range b.Instrs {
				if ref, ok := instr.(*DebugRef); ok {
					if ref.Expr == e {
						return ref.X, ref.IsAddr
					}
				}
			}
		}
	}
	return
}

// --- Lookup functions for source-level named entities (types.Objects) ---

// Package returns the IR Package corresponding to the specified
// type-checker package object.
// It returns nil if no such IR 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 concrete Function denoted by the source-level
// named function obj, or nil if obj denotes an interface method.
//
// TODO(adonovan): check the invariant that obj.Type() matches the
// result's Signature, both in the params/results and in the receiver.
func (prog *Program) FuncValue(obj *types.Func) *Function {
	obj = typeparams.OriginMethod(obj)
	fn, _ := prog.packageLevelValue(obj).(*Function)
	return fn
}

// ConstValue returns the IR Value denoted by the source-level named
// constant obj.
func (prog *Program) ConstValue(obj *types.Const) *Const {
	// TODO(adonovan): opt: share (don't reallocate)
	// Consts for const objects and constant ast.Exprs.

	// 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 IR 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 debug information was not
// requested during IR construction, or the value was optimized away.
//
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
// and that ident must resolve to obj.
//
// pkg is the package enclosing the reference.  (A reference to a var
// always occurs within a function, so we need to know where to find it.)
//
// If the identifier is a field selector and its base expression is
// non-addressable, then VarValue returns the value of that field.
// For example:
//
//	func f() struct {x int}
//	f().x  // VarValue(x) returns a *Field instruction of type int
//
// All other identifiers denote addressable locations (variables).
// For them, VarValue may return either the variable's address or its
// value, even when the expression is evaluated only for its value; the
// situation is reported by isAddr, the second component of the result.
//
// If !isAddr, the returned value is the one associated with the
// specific identifier.  For example,
//
//	var x int    // VarValue(x) returns Const 0 here
//	x = 1        // VarValue(x) returns Const 1 here
//
// It is not specified whether the value or the address is returned in
// any particular case, as it may depend upon optimizations performed
// during IR code generation, such as registerization, constant
// folding, avoidance of materialization of subexpressions, etc.
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
	// All references to a var are local to some function, possibly init.
	fn := EnclosingFunction(pkg, ref)
	if fn == nil {
		return // e.g. def of struct field; IR not built?
	}

	id := ref[0].(*ast.Ident)

	// Defining ident of a parameter?
	if id.Pos() == obj.Pos() {
		for _, param := range fn.Params {
			if param.Object() == obj {
				return param, false
			}
		}
	}

	// Other ident?
	for _, b := range fn.Blocks {
		for _, instr := range b.Instrs {
			if dr, ok := instr.(*DebugRef); ok {
				if dr.Pos() == id.Pos() {
					return dr.X, dr.IsAddr
				}
			}
		}
	}

	// Defining ident of package-level var?
	if v := prog.packageLevelValue(obj); v != nil {
		return v.(*Global), true
	}

	return // e.g. debug info not requested, or var optimized away
}
