blob: 128d61e4267b890d4f056b595c63cf958557689e [file] [log] [blame]
// Copyright 2022 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 ssa
import (
"go/types"
"golang.org/x/tools/internal/typeparams"
)
// Utilities for dealing with core types.
// isBytestring returns true if T has the same terms as interface{[]byte | string}.
// These act like a core type for some operations: slice expressions, append and copy.
//
// See https://go.dev/ref/spec#Core_types for the details on bytestring.
func isBytestring(T types.Type) bool {
U := T.Underlying()
if _, ok := U.(*types.Interface); !ok {
return false
}
tset := typeSetOf(U)
if tset.Len() != 2 {
return false
}
hasBytes, hasString := false, false
underIs(tset, func(t types.Type) bool {
switch {
case isString(t):
hasString = true
case isByteSlice(t):
hasBytes = true
}
return hasBytes || hasString
})
return hasBytes && hasString
}
// termList is a list of types.
type termList []*typeparams.Term // type terms of the type set
func (s termList) Len() int { return len(s) }
func (s termList) At(i int) types.Type { return s[i].Type() }
// typeSetOf returns the type set of typ. Returns an empty typeset on an error.
func typeSetOf(typ types.Type) termList {
// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
var terms []*typeparams.Term
var err error
switch typ := typ.(type) {
case *typeparams.TypeParam:
terms, err = typeparams.StructuralTerms(typ)
case *typeparams.Union:
terms, err = typeparams.UnionTermSet(typ)
case *types.Interface:
terms, err = typeparams.InterfaceTermSet(typ)
default:
// Common case.
// Specializing the len=1 case to avoid a slice
// had no measurable space/time benefit.
terms = []*typeparams.Term{typeparams.NewTerm(false, typ)}
}
if err != nil {
return termList(nil)
}
return termList(terms)
}
// underIs calls f with the underlying types of the specific type terms
// of s and reports whether all calls to f returned true. If there are
// no specific terms, underIs returns the result of f(nil).
func underIs(s termList, f func(types.Type) bool) bool {
if s.Len() == 0 {
return f(nil)
}
for i := 0; i < s.Len(); i++ {
u := s.At(i).Underlying()
if !f(u) {
return false
}
}
return true
}
// indexType returns the element type and index mode of a IndexExpr over a type.
// It returns (nil, invalid) if the type is not indexable; this should never occur in a well-typed program.
func indexType(typ types.Type) (types.Type, indexMode) {
switch U := typ.Underlying().(type) {
case *types.Array:
return U.Elem(), ixArrVar
case *types.Pointer:
if arr, ok := U.Elem().Underlying().(*types.Array); ok {
return arr.Elem(), ixVar
}
case *types.Slice:
return U.Elem(), ixVar
case *types.Map:
return U.Elem(), ixMap
case *types.Basic:
return tByte, ixValue // must be a string
case *types.Interface:
tset := typeSetOf(U)
if tset.Len() == 0 {
return nil, ixInvalid // no underlying terms or error is empty.
}
elem, mode := indexType(tset.At(0))
for i := 1; i < tset.Len() && mode != ixInvalid; i++ {
e, m := indexType(tset.At(i))
if !types.Identical(elem, e) { // if type checked, just a sanity check
return nil, ixInvalid
}
// Update the mode to the most constrained address type.
mode = mode.meet(m)
}
if mode != ixInvalid {
return elem, mode
}
}
return nil, ixInvalid
}
// An indexMode specifies the (addressing) mode of an index operand.
//
// Addressing mode of an index operation is based on the set of
// underlying types.
// Hasse diagram of the indexMode meet semi-lattice:
//
// ixVar ixMap
// | |
// ixArrVar |
// | |
// ixValue |
// \ /
// ixInvalid
type indexMode byte
const (
ixInvalid indexMode = iota // index is invalid
ixValue // index is a computed value (not addressable)
ixArrVar // like ixVar, but index operand contains an array
ixVar // index is an addressable variable
ixMap // index is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
)
// meet is the address type that is constrained by both x and y.
func (x indexMode) meet(y indexMode) indexMode {
if (x == ixMap || y == ixMap) && x != y {
return ixInvalid
}
// Use int representation and return min.
if x < y {
return y
}
return x
}