зеркало из https://github.com/golang/tools.git
893 строки
24 KiB
Go
893 строки
24 KiB
Go
// 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 callee side of the "fallible constant" analysis.
|
||
|
||
import (
|
||
"fmt"
|
||
"go/ast"
|
||
"go/constant"
|
||
"go/format"
|
||
"go/token"
|
||
"go/types"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"golang.org/x/tools/go/types/typeutil"
|
||
"golang.org/x/tools/internal/typeparams"
|
||
)
|
||
|
||
// falconResult is the result of the analysis of the callee.
|
||
type falconResult struct {
|
||
Types []falconType // types for falcon constraint environment
|
||
Constraints []string // constraints (Go expressions) on values of fallible constants
|
||
}
|
||
|
||
// A falconType specifies the name and underlying type of a synthetic
|
||
// defined type for use in falcon constraints.
|
||
//
|
||
// Unique types from callee code are bijectively mapped onto falcon
|
||
// types so that constraints are independent of callee type
|
||
// information but preserve type equivalence classes.
|
||
//
|
||
// Fresh names are deliberately obscure to avoid shadowing even if a
|
||
// callee parameter has a nanme like "int" or "any".
|
||
type falconType struct {
|
||
Name string
|
||
Kind types.BasicKind // string/number/bool
|
||
}
|
||
|
||
// falcon identifies "fallible constant" expressions, which are
|
||
// expressions that may fail to compile if one or more of their
|
||
// operands is changed from non-constant to constant.
|
||
//
|
||
// Consider:
|
||
//
|
||
// func sub(s string, i, j int) string { return s[i:j] }
|
||
//
|
||
// If parameters are replaced by constants, the compiler is
|
||
// required to perform these additional checks:
|
||
//
|
||
// - if i is constant, 0 <= i.
|
||
// - if s and i are constant, i <= len(s).
|
||
// - ditto for j.
|
||
// - if i and j are constant, i <= j.
|
||
//
|
||
// s[i:j] is thus a "fallible constant" expression dependent on {s, i,
|
||
// j}. Each falcon creates a set of conditional constraints across one
|
||
// or more parameter variables.
|
||
//
|
||
// - When inlining a call such as sub("abc", -1, 2), the parameter i
|
||
// cannot be eliminated by substitution as its argument value is
|
||
// negative.
|
||
//
|
||
// - When inlining sub("", 2, 1), all three parameters cannot be
|
||
// simultaneously eliminated by substitution without violating i
|
||
// <= len(s) and j <= len(s), but the parameters i and j could be
|
||
// safely eliminated without s.
|
||
//
|
||
// Parameters that cannot be eliminated must remain non-constant,
|
||
// either in the form of a binding declaration:
|
||
//
|
||
// { var i int = -1; return "abc"[i:2] }
|
||
//
|
||
// or a parameter of a literalization:
|
||
//
|
||
// func (i int) string { return "abc"[i:2] }(-1)
|
||
//
|
||
// These example expressions are obviously doomed to fail at run
|
||
// time, but in realistic cases such expressions are dominated by
|
||
// appropriate conditions that make them reachable only when safe:
|
||
//
|
||
// if 0 <= i && i <= j && j <= len(s) { _ = s[i:j] }
|
||
//
|
||
// (In principle a more sophisticated inliner could entirely eliminate
|
||
// such unreachable blocks based on the condition being always-false
|
||
// for the given parameter substitution, but this is tricky to do safely
|
||
// because the type-checker considers only a single configuration.
|
||
// Consider: if runtime.GOOS == "linux" { ... }.)
|
||
//
|
||
// We believe this is an exhaustive list of "fallible constant" operations:
|
||
//
|
||
// - switch z { case x: case y } // duplicate case values
|
||
// - s[i], s[i:j], s[i:j:k] // index out of bounds (0 <= i <= j <= k <= len(s))
|
||
// - T{x: 0} // index out of bounds, duplicate index
|
||
// - x/y, x%y, x/=y, x%=y // integer division by zero; minint/-1 overflow
|
||
// - x+y, x-y, x*y // arithmetic overflow
|
||
// - x<<y // shift out of range
|
||
// - -x // negation of minint
|
||
// - T(x) // value out of range
|
||
//
|
||
// The fundamental reason for this elaborate algorithm is that the
|
||
// "separate analysis" of callee and caller, as required when running
|
||
// in an environment such as unitchecker, means that there is no way
|
||
// for us to simply invoke the type checker on the combination of
|
||
// caller and callee code, as by the time we analyze the caller, we no
|
||
// longer have access to type information for the callee (and, in
|
||
// particular, any of its direct dependencies that are not direct
|
||
// dependencies of the caller). So, in effect, we are forced to map
|
||
// the problem in a neutral (callee-type-independent) constraint
|
||
// system that can be verified later.
|
||
func falcon(logf func(string, ...any), fset *token.FileSet, params map[*types.Var]*paramInfo, info *types.Info, decl *ast.FuncDecl) falconResult {
|
||
|
||
st := &falconState{
|
||
logf: logf,
|
||
fset: fset,
|
||
params: params,
|
||
info: info,
|
||
decl: decl,
|
||
}
|
||
|
||
// type mapping
|
||
st.int = st.typename(types.Typ[types.Int])
|
||
st.any = "interface{}" // don't use "any" as it may be shadowed
|
||
for obj, info := range st.params {
|
||
if isBasic(obj.Type(), types.IsConstType) {
|
||
info.FalconType = st.typename(obj.Type())
|
||
}
|
||
}
|
||
|
||
st.stmt(st.decl.Body)
|
||
|
||
return st.result
|
||
}
|
||
|
||
type falconState struct {
|
||
// inputs
|
||
logf func(string, ...any)
|
||
fset *token.FileSet
|
||
params map[*types.Var]*paramInfo
|
||
info *types.Info
|
||
decl *ast.FuncDecl
|
||
|
||
// working state
|
||
int string
|
||
any string
|
||
typenames typeutil.Map
|
||
|
||
result falconResult
|
||
}
|
||
|
||
// typename returns the name in the falcon constraint system
|
||
// of a given string/number/bool type t. Falcon types are
|
||
// specified directly in go/types data structures rather than
|
||
// by name, avoiding potential shadowing conflicts with
|
||
// confusing parameter names such as "int".
|
||
//
|
||
// Also, each distinct type (as determined by types.Identical)
|
||
// is mapped to a fresh type in the falcon system so that we
|
||
// can map the types in the callee code into a neutral form
|
||
// that does not depend on imports, allowing us to detect
|
||
// potential conflicts such as
|
||
//
|
||
// map[any]{T1(1): 0, T2(1): 0}
|
||
//
|
||
// where T1=T2.
|
||
func (st *falconState) typename(t types.Type) string {
|
||
name, ok := st.typenames.At(t).(string)
|
||
if !ok {
|
||
basic := t.Underlying().(*types.Basic)
|
||
|
||
// That dot ۰ is an Arabic zero numeral U+06F0.
|
||
// It is very unlikely to appear in a real program.
|
||
// TODO(adonovan): use a non-heuristic solution.
|
||
name = fmt.Sprintf("%s۰%d", basic, st.typenames.Len())
|
||
st.typenames.Set(t, name)
|
||
st.logf("falcon: emit type %s %s // %q", name, basic, t)
|
||
st.result.Types = append(st.result.Types, falconType{
|
||
Name: name,
|
||
Kind: basic.Kind(),
|
||
})
|
||
}
|
||
return name
|
||
}
|
||
|
||
// -- constraint emission --
|
||
|
||
// emit emits a Go expression that must have a legal type.
|
||
// In effect, we let the go/types constant folding algorithm
|
||
// do most of the heavy lifting (though it may be hard to
|
||
// believe from the complexity of this algorithm!).
|
||
func (st *falconState) emit(constraint ast.Expr) {
|
||
var out strings.Builder
|
||
if err := format.Node(&out, st.fset, constraint); err != nil {
|
||
panic(err) // can't happen
|
||
}
|
||
syntax := out.String()
|
||
st.logf("falcon: emit constraint %s", syntax)
|
||
st.result.Constraints = append(st.result.Constraints, syntax)
|
||
}
|
||
|
||
// emitNonNegative emits an []T{}[index] constraint,
|
||
// which ensures index is non-negative if constant.
|
||
func (st *falconState) emitNonNegative(index ast.Expr) {
|
||
st.emit(&ast.IndexExpr{
|
||
X: &ast.CompositeLit{
|
||
Type: &ast.ArrayType{
|
||
Elt: makeIdent(st.int),
|
||
},
|
||
},
|
||
Index: index,
|
||
})
|
||
}
|
||
|
||
// emitMonotonic emits an []T{}[i:j] constraint,
|
||
// which ensures i <= j if both are constant.
|
||
func (st *falconState) emitMonotonic(i, j ast.Expr) {
|
||
st.emit(&ast.SliceExpr{
|
||
X: &ast.CompositeLit{
|
||
Type: &ast.ArrayType{
|
||
Elt: makeIdent(st.int),
|
||
},
|
||
},
|
||
Low: i,
|
||
High: j,
|
||
})
|
||
}
|
||
|
||
// emitUnique emits a T{elem1: 0, ... elemN: 0} constraint,
|
||
// which ensures that all constant elems are unique.
|
||
// T may be a map, slice, or array depending
|
||
// on the desired check semantics.
|
||
func (st *falconState) emitUnique(typ ast.Expr, elems []ast.Expr) {
|
||
if len(elems) > 1 {
|
||
var elts []ast.Expr
|
||
for _, elem := range elems {
|
||
elts = append(elts, &ast.KeyValueExpr{
|
||
Key: elem,
|
||
Value: makeIntLit(0),
|
||
})
|
||
}
|
||
st.emit(&ast.CompositeLit{
|
||
Type: typ,
|
||
Elts: elts,
|
||
})
|
||
}
|
||
}
|
||
|
||
// -- traversal --
|
||
|
||
// The traversal functions scan the callee body for expressions that
|
||
// are not constant but would become constant if the parameter vars
|
||
// were redeclared as constants, and emits for each one a constraint
|
||
// (a Go expression) with the property that it will not type-check
|
||
// (using types.CheckExpr) if the particular argument values are
|
||
// unsuitable.
|
||
//
|
||
// These constraints are checked by Inline with the actual
|
||
// constant argument values. Violations cause it to reject
|
||
// parameters as candidates for substitution.
|
||
|
||
func (st *falconState) stmt(s ast.Stmt) {
|
||
ast.Inspect(s, func(n ast.Node) bool {
|
||
switch n := n.(type) {
|
||
case ast.Expr:
|
||
_ = st.expr(n)
|
||
return false // skip usual traversal
|
||
|
||
case *ast.AssignStmt:
|
||
switch n.Tok {
|
||
case token.QUO_ASSIGN, token.REM_ASSIGN:
|
||
// x /= y
|
||
// Possible "integer division by zero"
|
||
// Emit constraint: 1/y.
|
||
_ = st.expr(n.Lhs[0])
|
||
kY := st.expr(n.Rhs[0])
|
||
if kY, ok := kY.(ast.Expr); ok {
|
||
op := token.QUO
|
||
if n.Tok == token.REM_ASSIGN {
|
||
op = token.REM
|
||
}
|
||
st.emit(&ast.BinaryExpr{
|
||
Op: op,
|
||
X: makeIntLit(1),
|
||
Y: kY,
|
||
})
|
||
}
|
||
return false // skip usual traversal
|
||
}
|
||
|
||
case *ast.SwitchStmt:
|
||
if n.Init != nil {
|
||
st.stmt(n.Init)
|
||
}
|
||
tBool := types.Type(types.Typ[types.Bool])
|
||
tagType := tBool // default: true
|
||
if n.Tag != nil {
|
||
st.expr(n.Tag)
|
||
tagType = st.info.TypeOf(n.Tag)
|
||
}
|
||
|
||
// Possible "duplicate case value".
|
||
// Emit constraint map[T]int{v1: 0, ..., vN:0}
|
||
// to ensure all maybe-constant case values are unique
|
||
// (unless switch tag is boolean, which is relaxed).
|
||
var unique []ast.Expr
|
||
for _, clause := range n.Body.List {
|
||
clause := clause.(*ast.CaseClause)
|
||
for _, caseval := range clause.List {
|
||
if k := st.expr(caseval); k != nil {
|
||
unique = append(unique, st.toExpr(k))
|
||
}
|
||
}
|
||
for _, stmt := range clause.Body {
|
||
st.stmt(stmt)
|
||
}
|
||
}
|
||
if unique != nil && !types.Identical(tagType.Underlying(), tBool) {
|
||
tname := st.any
|
||
if !types.IsInterface(tagType) {
|
||
tname = st.typename(tagType)
|
||
}
|
||
t := &ast.MapType{
|
||
Key: makeIdent(tname),
|
||
Value: makeIdent(st.int),
|
||
}
|
||
st.emitUnique(t, unique)
|
||
}
|
||
}
|
||
return true
|
||
})
|
||
}
|
||
|
||
// fieldTypes visits the .Type of each field in the list.
|
||
func (st *falconState) fieldTypes(fields *ast.FieldList) {
|
||
if fields != nil {
|
||
for _, field := range fields.List {
|
||
_ = st.expr(field.Type)
|
||
}
|
||
}
|
||
}
|
||
|
||
// expr visits the expression (or type) and returns a
|
||
// non-nil result if the expression is constant or would
|
||
// become constant if all suitable function parameters were
|
||
// redeclared as constants.
|
||
//
|
||
// If the expression is constant, st.expr returns its type
|
||
// and value (types.TypeAndValue). If the expression would
|
||
// become constant, st.expr returns an ast.Expr tree whose
|
||
// leaves are literals and parameter references, and whose
|
||
// interior nodes are operations that may become constant,
|
||
// such as -x, x+y, f(x), and T(x). We call these would-be
|
||
// constant expressions "fallible constants", since they may
|
||
// fail to type-check for some values of x, i, and j. (We
|
||
// refer to the non-nil cases collectively as "maybe
|
||
// constant", and the nil case as "definitely non-constant".)
|
||
//
|
||
// As a side effect, st.expr emits constraints for each
|
||
// fallible constant expression; this is its main purpose.
|
||
//
|
||
// Consequently, st.expr must visit the entire subtree so
|
||
// that all necessary constraints are emitted. It may not
|
||
// short-circuit the traversal when it encounters a constant
|
||
// subexpression as constants may contain arbitrary other
|
||
// syntax that may impose constraints. Consider (as always)
|
||
// this contrived but legal example of a type parameter (!)
|
||
// that contains statement syntax:
|
||
//
|
||
// func f[T [unsafe.Sizeof(func() { stmts })]int]()
|
||
//
|
||
// There is no need to emit constraints for (e.g.) s[i] when s
|
||
// and i are already constants, because we know the expression
|
||
// is sound, but it is sometimes easier to emit these
|
||
// redundant constraints than to avoid them.
|
||
func (st *falconState) expr(e ast.Expr) (res any) { // = types.TypeAndValue | ast.Expr
|
||
tv := st.info.Types[e]
|
||
if tv.Value != nil {
|
||
// A constant value overrides any other result.
|
||
defer func() { res = tv }()
|
||
}
|
||
|
||
switch e := e.(type) {
|
||
case *ast.Ident:
|
||
if v, ok := st.info.Uses[e].(*types.Var); ok {
|
||
if _, ok := st.params[v]; ok && isBasic(v.Type(), types.IsConstType) {
|
||
return e // reference to constable parameter
|
||
}
|
||
}
|
||
// (References to *types.Const are handled by the defer.)
|
||
|
||
case *ast.BasicLit:
|
||
// constant
|
||
|
||
case *ast.ParenExpr:
|
||
return st.expr(e.X)
|
||
|
||
case *ast.FuncLit:
|
||
_ = st.expr(e.Type)
|
||
st.stmt(e.Body)
|
||
// definitely non-constant
|
||
|
||
case *ast.CompositeLit:
|
||
// T{k: v, ...}, where T ∈ {array,*array,slice,map},
|
||
// imposes a constraint that all constant k are
|
||
// distinct and, for arrays [n]T, within range 0-n.
|
||
//
|
||
// Types matter, not just values. For example,
|
||
// an interface-keyed map may contain keys
|
||
// that are numerically equal so long as they
|
||
// are of distinct types. For example:
|
||
//
|
||
// type myint int
|
||
// map[any]bool{1: true, 1: true} // error: duplicate key
|
||
// map[any]bool{1: true, int16(1): true} // ok
|
||
// map[any]bool{1: true, myint(1): true} // ok
|
||
//
|
||
// This can be asserted by emitting a
|
||
// constraint of the form T{k1: 0, ..., kN: 0}.
|
||
if e.Type != nil {
|
||
_ = st.expr(e.Type)
|
||
}
|
||
t := types.Unalias(typeparams.Deref(tv.Type))
|
||
var uniques []ast.Expr
|
||
for _, elt := range e.Elts {
|
||
if kv, ok := elt.(*ast.KeyValueExpr); ok {
|
||
if !is[*types.Struct](t) {
|
||
if k := st.expr(kv.Key); k != nil {
|
||
uniques = append(uniques, st.toExpr(k))
|
||
}
|
||
}
|
||
_ = st.expr(kv.Value)
|
||
} else {
|
||
_ = st.expr(elt)
|
||
}
|
||
}
|
||
if uniques != nil {
|
||
// Inv: not a struct.
|
||
|
||
// The type T in constraint T{...} depends on the CompLit:
|
||
// - for a basic-keyed map, use map[K]int;
|
||
// - for an interface-keyed map, use map[any]int;
|
||
// - for a slice, use []int;
|
||
// - for an array or *array, use [n]int.
|
||
// The last two entail progressively stronger index checks.
|
||
var ct ast.Expr // type syntax for constraint
|
||
switch t := t.(type) {
|
||
case *types.Map:
|
||
if types.IsInterface(t.Key()) {
|
||
ct = &ast.MapType{
|
||
Key: makeIdent(st.any),
|
||
Value: makeIdent(st.int),
|
||
}
|
||
} else {
|
||
ct = &ast.MapType{
|
||
Key: makeIdent(st.typename(t.Key())),
|
||
Value: makeIdent(st.int),
|
||
}
|
||
}
|
||
case *types.Array: // or *array
|
||
ct = &ast.ArrayType{
|
||
Len: makeIntLit(t.Len()),
|
||
Elt: makeIdent(st.int),
|
||
}
|
||
default:
|
||
panic(t)
|
||
}
|
||
st.emitUnique(ct, uniques)
|
||
}
|
||
// definitely non-constant
|
||
|
||
case *ast.SelectorExpr:
|
||
_ = st.expr(e.X)
|
||
_ = st.expr(e.Sel)
|
||
// The defer is sufficient to handle
|
||
// qualified identifiers (pkg.Const).
|
||
// All other cases are definitely non-constant.
|
||
|
||
case *ast.IndexExpr:
|
||
if tv.IsType() {
|
||
// type C[T]
|
||
_ = st.expr(e.X)
|
||
_ = st.expr(e.Index)
|
||
} else {
|
||
// term x[i]
|
||
//
|
||
// Constraints (if x is slice/string/array/*array, not map):
|
||
// - i >= 0
|
||
// if i is a fallible constant
|
||
// - i < len(x)
|
||
// if x is array/*array and
|
||
// i is a fallible constant;
|
||
// or if s is a string and both i,
|
||
// s are maybe-constants,
|
||
// but not both are constants.
|
||
kX := st.expr(e.X)
|
||
kI := st.expr(e.Index)
|
||
if kI != nil && !is[*types.Map](st.info.TypeOf(e.X).Underlying()) {
|
||
if kI, ok := kI.(ast.Expr); ok {
|
||
st.emitNonNegative(kI)
|
||
}
|
||
// Emit constraint to check indices against known length.
|
||
// TODO(adonovan): factor with SliceExpr logic.
|
||
var x ast.Expr
|
||
if kX != nil {
|
||
// string
|
||
x = st.toExpr(kX)
|
||
} else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok {
|
||
// array, *array
|
||
x = &ast.CompositeLit{
|
||
Type: &ast.ArrayType{
|
||
Len: makeIntLit(arr.Len()),
|
||
Elt: makeIdent(st.int),
|
||
},
|
||
}
|
||
}
|
||
if x != nil {
|
||
st.emit(&ast.IndexExpr{
|
||
X: x,
|
||
Index: st.toExpr(kI),
|
||
})
|
||
}
|
||
}
|
||
}
|
||
// definitely non-constant
|
||
|
||
case *ast.SliceExpr:
|
||
// x[low:high:max]
|
||
//
|
||
// Emit non-negative constraints for each index,
|
||
// plus low <= high <= max <= len(x)
|
||
// for each pair that are maybe-constant
|
||
// but not definitely constant.
|
||
|
||
kX := st.expr(e.X)
|
||
var kLow, kHigh, kMax any
|
||
if e.Low != nil {
|
||
kLow = st.expr(e.Low)
|
||
if kLow != nil {
|
||
if kLow, ok := kLow.(ast.Expr); ok {
|
||
st.emitNonNegative(kLow)
|
||
}
|
||
}
|
||
}
|
||
if e.High != nil {
|
||
kHigh = st.expr(e.High)
|
||
if kHigh != nil {
|
||
if kHigh, ok := kHigh.(ast.Expr); ok {
|
||
st.emitNonNegative(kHigh)
|
||
}
|
||
if kLow != nil {
|
||
st.emitMonotonic(st.toExpr(kLow), st.toExpr(kHigh))
|
||
}
|
||
}
|
||
}
|
||
if e.Max != nil {
|
||
kMax = st.expr(e.Max)
|
||
if kMax != nil {
|
||
if kMax, ok := kMax.(ast.Expr); ok {
|
||
st.emitNonNegative(kMax)
|
||
}
|
||
if kHigh != nil {
|
||
st.emitMonotonic(st.toExpr(kHigh), st.toExpr(kMax))
|
||
}
|
||
}
|
||
}
|
||
|
||
// Emit constraint to check indices against known length.
|
||
var x ast.Expr
|
||
if kX != nil {
|
||
// string
|
||
x = st.toExpr(kX)
|
||
} else if arr, ok := typeparams.CoreType(typeparams.Deref(st.info.TypeOf(e.X))).(*types.Array); ok {
|
||
// array, *array
|
||
x = &ast.CompositeLit{
|
||
Type: &ast.ArrayType{
|
||
Len: makeIntLit(arr.Len()),
|
||
Elt: makeIdent(st.int),
|
||
},
|
||
}
|
||
}
|
||
if x != nil {
|
||
// Avoid slice[::max] if kHigh is nonconstant (nil).
|
||
high, max := st.toExpr(kHigh), st.toExpr(kMax)
|
||
if high == nil {
|
||
high = max // => slice[:max:max]
|
||
}
|
||
st.emit(&ast.SliceExpr{
|
||
X: x,
|
||
Low: st.toExpr(kLow),
|
||
High: high,
|
||
Max: max,
|
||
})
|
||
}
|
||
// definitely non-constant
|
||
|
||
case *ast.TypeAssertExpr:
|
||
_ = st.expr(e.X)
|
||
if e.Type != nil {
|
||
_ = st.expr(e.Type)
|
||
}
|
||
|
||
case *ast.CallExpr:
|
||
_ = st.expr(e.Fun)
|
||
if tv, ok := st.info.Types[e.Fun]; ok && tv.IsType() {
|
||
// conversion T(x)
|
||
//
|
||
// Possible "value out of range".
|
||
kX := st.expr(e.Args[0])
|
||
if kX != nil && isBasic(tv.Type, types.IsConstType) {
|
||
conv := convert(makeIdent(st.typename(tv.Type)), st.toExpr(kX))
|
||
if is[ast.Expr](kX) {
|
||
st.emit(conv)
|
||
}
|
||
return conv
|
||
}
|
||
return nil // definitely non-constant
|
||
}
|
||
|
||
// call f(x)
|
||
|
||
all := true // all args are possibly-constant
|
||
kArgs := make([]ast.Expr, len(e.Args))
|
||
for i, arg := range e.Args {
|
||
if kArg := st.expr(arg); kArg != nil {
|
||
kArgs[i] = st.toExpr(kArg)
|
||
} else {
|
||
all = false
|
||
}
|
||
}
|
||
|
||
// Calls to built-ins with fallibly constant arguments
|
||
// may become constant. All other calls are either
|
||
// constant or non-constant
|
||
if id, ok := e.Fun.(*ast.Ident); ok && all && tv.Value == nil {
|
||
if builtin, ok := st.info.Uses[id].(*types.Builtin); ok {
|
||
switch builtin.Name() {
|
||
case "len", "imag", "real", "complex", "min", "max":
|
||
return &ast.CallExpr{
|
||
Fun: id,
|
||
Args: kArgs,
|
||
Ellipsis: e.Ellipsis,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
case *ast.StarExpr: // *T, *ptr
|
||
_ = st.expr(e.X)
|
||
|
||
case *ast.UnaryExpr:
|
||
// + - ! ^ & <- ~
|
||
//
|
||
// Possible "negation of minint".
|
||
// Emit constraint: -x
|
||
kX := st.expr(e.X)
|
||
if kX != nil && !is[types.TypeAndValue](kX) {
|
||
if e.Op == token.SUB {
|
||
st.emit(&ast.UnaryExpr{
|
||
Op: e.Op,
|
||
X: st.toExpr(kX),
|
||
})
|
||
}
|
||
|
||
return &ast.UnaryExpr{
|
||
Op: e.Op,
|
||
X: st.toExpr(kX),
|
||
}
|
||
}
|
||
|
||
case *ast.BinaryExpr:
|
||
kX := st.expr(e.X)
|
||
kY := st.expr(e.Y)
|
||
switch e.Op {
|
||
case token.QUO, token.REM:
|
||
// x/y, x%y
|
||
//
|
||
// Possible "integer division by zero" or
|
||
// "minint / -1" overflow.
|
||
// Emit constraint: x/y or 1/y
|
||
if kY != nil {
|
||
if kX == nil {
|
||
kX = makeIntLit(1)
|
||
}
|
||
st.emit(&ast.BinaryExpr{
|
||
Op: e.Op,
|
||
X: st.toExpr(kX),
|
||
Y: st.toExpr(kY),
|
||
})
|
||
}
|
||
|
||
case token.ADD, token.SUB, token.MUL:
|
||
// x+y, x-y, x*y
|
||
//
|
||
// Possible "arithmetic overflow".
|
||
// Emit constraint: x+y
|
||
if kX != nil && kY != nil {
|
||
st.emit(&ast.BinaryExpr{
|
||
Op: e.Op,
|
||
X: st.toExpr(kX),
|
||
Y: st.toExpr(kY),
|
||
})
|
||
}
|
||
|
||
case token.SHL, token.SHR:
|
||
// x << y, x >> y
|
||
//
|
||
// Possible "constant shift too large".
|
||
// Either operand may be too large individually,
|
||
// and they may be too large together.
|
||
// Emit constraint:
|
||
// x << y (if both maybe-constant)
|
||
// x << 0 (if y is non-constant)
|
||
// 1 << y (if x is non-constant)
|
||
if kX != nil || kY != nil {
|
||
x := st.toExpr(kX)
|
||
if x == nil {
|
||
x = makeIntLit(1)
|
||
}
|
||
y := st.toExpr(kY)
|
||
if y == nil {
|
||
y = makeIntLit(0)
|
||
}
|
||
st.emit(&ast.BinaryExpr{
|
||
Op: e.Op,
|
||
X: x,
|
||
Y: y,
|
||
})
|
||
}
|
||
|
||
case token.LSS, token.GTR, token.EQL, token.NEQ, token.LEQ, token.GEQ:
|
||
// < > == != <= <=
|
||
//
|
||
// A "x cmp y" expression with constant operands x, y is
|
||
// itself constant, but I can't see how a constant bool
|
||
// could be fallible: the compiler doesn't reject duplicate
|
||
// boolean cases in a switch, presumably because boolean
|
||
// switches are less like n-way branches and more like
|
||
// sequential if-else chains with possibly overlapping
|
||
// conditions; and there is (sadly) no way to convert a
|
||
// boolean constant to an int constant.
|
||
}
|
||
if kX != nil && kY != nil {
|
||
return &ast.BinaryExpr{
|
||
Op: e.Op,
|
||
X: st.toExpr(kX),
|
||
Y: st.toExpr(kY),
|
||
}
|
||
}
|
||
|
||
// types
|
||
//
|
||
// We need to visit types (and even type parameters)
|
||
// in order to reach all the places where things could go wrong:
|
||
//
|
||
// const (
|
||
// s = ""
|
||
// i = 0
|
||
// )
|
||
// type C[T [unsafe.Sizeof(func() { _ = s[i] })]int] bool
|
||
|
||
case *ast.IndexListExpr:
|
||
_ = st.expr(e.X)
|
||
for _, expr := range e.Indices {
|
||
_ = st.expr(expr)
|
||
}
|
||
|
||
case *ast.Ellipsis:
|
||
if e.Elt != nil {
|
||
_ = st.expr(e.Elt)
|
||
}
|
||
|
||
case *ast.ArrayType:
|
||
if e.Len != nil {
|
||
_ = st.expr(e.Len)
|
||
}
|
||
_ = st.expr(e.Elt)
|
||
|
||
case *ast.StructType:
|
||
st.fieldTypes(e.Fields)
|
||
|
||
case *ast.FuncType:
|
||
st.fieldTypes(e.TypeParams)
|
||
st.fieldTypes(e.Params)
|
||
st.fieldTypes(e.Results)
|
||
|
||
case *ast.InterfaceType:
|
||
st.fieldTypes(e.Methods)
|
||
|
||
case *ast.MapType:
|
||
_ = st.expr(e.Key)
|
||
_ = st.expr(e.Value)
|
||
|
||
case *ast.ChanType:
|
||
_ = st.expr(e.Value)
|
||
}
|
||
return
|
||
}
|
||
|
||
// toExpr converts the result of visitExpr to a falcon expression.
|
||
// (We don't do this in visitExpr as we first need to discriminate
|
||
// constants from maybe-constants.)
|
||
func (st *falconState) toExpr(x any) ast.Expr {
|
||
switch x := x.(type) {
|
||
case nil:
|
||
return nil
|
||
|
||
case types.TypeAndValue:
|
||
lit := makeLiteral(x.Value)
|
||
if !isBasic(x.Type, types.IsUntyped) {
|
||
// convert to "typed" type
|
||
lit = &ast.CallExpr{
|
||
Fun: makeIdent(st.typename(x.Type)),
|
||
Args: []ast.Expr{lit},
|
||
}
|
||
}
|
||
return lit
|
||
|
||
case ast.Expr:
|
||
return x
|
||
|
||
default:
|
||
panic(x)
|
||
}
|
||
}
|
||
|
||
func makeLiteral(v constant.Value) ast.Expr {
|
||
switch v.Kind() {
|
||
case constant.Bool:
|
||
// Rather than refer to the true or false built-ins,
|
||
// which could be shadowed by poorly chosen parameter
|
||
// names, we use 0 == 0 for true and 0 != 0 for false.
|
||
op := token.EQL
|
||
if !constant.BoolVal(v) {
|
||
op = token.NEQ
|
||
}
|
||
return &ast.BinaryExpr{
|
||
Op: op,
|
||
X: makeIntLit(0),
|
||
Y: makeIntLit(0),
|
||
}
|
||
|
||
case constant.String:
|
||
return &ast.BasicLit{
|
||
Kind: token.STRING,
|
||
Value: v.ExactString(),
|
||
}
|
||
|
||
case constant.Int:
|
||
return &ast.BasicLit{
|
||
Kind: token.INT,
|
||
Value: v.ExactString(),
|
||
}
|
||
|
||
case constant.Float:
|
||
return &ast.BasicLit{
|
||
Kind: token.FLOAT,
|
||
Value: v.ExactString(),
|
||
}
|
||
|
||
case constant.Complex:
|
||
// The components could be float or int.
|
||
y := makeLiteral(constant.Imag(v))
|
||
y.(*ast.BasicLit).Value += "i" // ugh
|
||
if re := constant.Real(v); !consteq(re, kZeroInt) {
|
||
// complex: x + yi
|
||
y = &ast.BinaryExpr{
|
||
Op: token.ADD,
|
||
X: makeLiteral(re),
|
||
Y: y,
|
||
}
|
||
}
|
||
return y
|
||
|
||
default:
|
||
panic(v.Kind())
|
||
}
|
||
}
|
||
|
||
func makeIntLit(x int64) *ast.BasicLit {
|
||
return &ast.BasicLit{
|
||
Kind: token.INT,
|
||
Value: strconv.FormatInt(x, 10),
|
||
}
|
||
}
|
||
|
||
func isBasic(t types.Type, info types.BasicInfo) bool {
|
||
basic, ok := t.Underlying().(*types.Basic)
|
||
return ok && basic.Info()&info != 0
|
||
}
|