go/ssa: build generic function bodies

ssa now always builds the bodies of generic functions and methods.
Within the bodies of generic functions, parameterized types may appear
in Instructions.

When ssa.InstantiateGenerics is on, calls to generic functions
with ground (non-parameterized) types are built as instantiations of the
generic function bodies. Otherwise, calls to generic functions are built
as a wrapper function that delegates to the generic function body.

The ChangeType instruction can now represent a coercion to or from a
parameterized type to an instance of the type.

*ssa.Const values may now represent the zero value of any type.

The build mode of go/analysis/passes/buildssa is again
ssa.BuilderMode(0) (i.e. ssa.InstantiateGenerics is off).

This change is a stack of already reviewed CLs.

Fixes golang/go#48525

Change-Id: Ib516eba43963674c804a63e3dcdae6d4116353c9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/425496
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Run-TryBot: Tim King <taking@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
Tim King 2022-08-25 11:09:24 -04:00
parent 85bf7a8fb4
commit 36a5c6a8a6
51 changed files with 2404 additions and 417 deletions

View File

@ -48,8 +48,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
// Some Analyzers may need GlobalDebug, in which case we'll have
// to set it globally, but let's wait till we need it.
// Monomorphize at least until type parameters are available.
mode := ssa.InstantiateGenerics
mode := ssa.BuilderMode(0)
prog := ssa.NewProgram(pass.Fset, mode)

View File

@ -62,7 +62,6 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (interface{}, error) {
ssainput := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA)
// TODO(48525): ssainput.SrcFuncs is missing fn._Instances(). runFunc will be skipped.
for _, fn := range ssainput.SrcFuncs {
runFunc(pass, fn)
}
@ -307,9 +306,9 @@ func nilnessOf(stack []fact, v ssa.Value) nilness {
return isnonnil
case *ssa.Const:
if v.IsNil() {
return isnil
return isnil // nil or zero value of a pointer-like type
} else {
return isnonnil
return unknown // non-pointer
}
}

View File

@ -209,3 +209,10 @@ func f13() {
var d *Y
print(d.value) // want "nil dereference in field selection"
}
func f14() {
var x struct{ f string }
if x == struct{ f string }{} { // we don't catch this tautology as we restrict to reference types
print(x)
}
}

View File

@ -2,7 +2,7 @@ package c
func instantiated[X any](x *X) int {
if x == nil {
print(*x) // not reported until _Instances are added to SrcFuncs
print(*x) // want "nil dereference in load"
}
return 1
}

View File

@ -37,6 +37,8 @@ package callgraph // import "golang.org/x/tools/go/callgraph"
// More generally, we could eliminate "uninteresting" nodes such as
// nodes from packages we don't care about.
// TODO(zpavlinovic): decide how callgraphs handle calls to and from generic function bodies.
import (
"fmt"
"go/token"

View File

@ -22,6 +22,8 @@
// partial programs, such as libraries without a main or test function.
package cha // import "golang.org/x/tools/go/callgraph/cha"
// TODO(zpavlinovic): update CHA for how it handles generic function bodies.
import (
"go/types"

View File

@ -41,5 +41,9 @@ func f(h func(), g func(I), k func(A), a A, b B) {
// f --> instantiated[main.A]
// f --> instantiated[main.A]
// f --> instantiated[main.B]
// instantiated --> (*A).Foo
// instantiated --> (*B).Foo
// instantiated --> (A).Foo
// instantiated --> (B).Foo
// instantiated[main.A] --> (A).Foo
// instantiated[main.B] --> (B).Foo

View File

@ -6,6 +6,8 @@
// only static call edges.
package static // import "golang.org/x/tools/go/callgraph/static"
// TODO(zpavlinovic): update static for how it handles generic function bodies.
import (
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"

View File

@ -9,6 +9,7 @@ import (
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/typeparams"
)
func canAlias(n1, n2 node) bool {
@ -117,19 +118,27 @@ func functionUnderPtr(t types.Type) types.Type {
}
// sliceArrayElem returns the element type of type `t` that is
// expected to be a (pointer to) array or slice, consistent with
// expected to be a (pointer to) array, slice or string, consistent with
// the ssa.Index and ssa.IndexAddr instructions. Panics otherwise.
func sliceArrayElem(t types.Type) types.Type {
u := t.Underlying()
if p, ok := u.(*types.Pointer); ok {
u = p.Elem().Underlying()
switch u := t.Underlying().(type) {
case *types.Pointer:
return u.Elem().Underlying().(*types.Array).Elem()
case *types.Array:
return u.Elem()
case *types.Slice:
return u.Elem()
case *types.Basic:
return types.Typ[types.Byte]
case *types.Interface: // type param.
terms, err := typeparams.InterfaceTermSet(u)
if err != nil || len(terms) == 0 {
panic(t)
}
return sliceArrayElem(terms[0].Type()) // Element types must match.
default:
panic(t)
}
if a, ok := u.(*types.Array); ok {
return a.Elem()
}
return u.(*types.Slice).Elem()
}
// siteCallees computes a set of callees for call site `c` given program `callgraph`.

View File

@ -54,6 +54,8 @@
// reaching the node representing the call site to create a set of callees.
package vta
// TODO(zpavlinovic): update VTA for how it handles generic function bodies and instantiation wrappers.
import (
"go/types"

View File

@ -16,6 +16,7 @@ import (
"runtime"
"runtime/debug"
"sort"
"strings"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
@ -377,12 +378,27 @@ func (a *analysis) callEdge(caller *cgnode, site *callsite, calleeid nodeid) {
fmt.Fprintf(a.log, "\tcall edge %s -> %s\n", site, callee)
}
// Warn about calls to non-intrinsic external functions.
// Warn about calls to functions that are handled unsoundly.
// TODO(adonovan): de-dup these messages.
if fn := callee.fn; fn.Blocks == nil && a.findIntrinsic(fn) == nil {
fn := callee.fn
// Warn about calls to non-intrinsic external functions.
if fn.Blocks == nil && a.findIntrinsic(fn) == nil {
a.warnf(site.pos(), "unsound call to unknown intrinsic: %s", fn)
a.warnf(fn.Pos(), " (declared here)")
}
// Warn about calls to generic function bodies.
if fn.TypeParams().Len() > 0 && len(fn.TypeArgs()) == 0 {
a.warnf(site.pos(), "unsound call to generic function body: %s (build with ssa.InstantiateGenerics)", fn)
a.warnf(fn.Pos(), " (declared here)")
}
// Warn about calls to instantiation wrappers of generics functions.
if fn.Origin() != nil && strings.HasPrefix(fn.Synthetic, "instantiation wrapper ") {
a.warnf(site.pos(), "unsound call to instantiation wrapper of generic: %s (build with ssa.InstantiateGenerics)", fn)
a.warnf(fn.Pos(), " (declared here)")
}
}
// dumpSolution writes the PTS solution to the specified file.

View File

@ -28,7 +28,11 @@ type Config struct {
// dependencies of any main package may still affect the
// analysis result, because they contribute runtime types and
// thus methods.
//
// TODO(adonovan): investigate whether this is desirable.
//
// Calls to generic functions will be unsound unless packages
// are built using the ssa.InstantiateGenerics builder mode.
Mains []*ssa.Package
// Reflection determines whether to handle reflection

View File

@ -358,6 +358,14 @@ A. Control-flow joins would merge interfaces ({T1}, {V1}) and ({T2},
type-unsafe combination (T1,V2). Treating the value and its concrete
type as inseparable makes the analysis type-safe.)
Type parameters:
Type parameters are not directly supported by the analysis.
Calls to generic functions will be left as if they had empty bodies.
Users of the package are expected to use the ssa.InstantiateGenerics
builder mode when building code that uses or depends on code
containing generics.
reflect.Value:
A reflect.Value is modelled very similar to an interface{}, i.e. as

View File

@ -14,9 +14,11 @@ import (
"fmt"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/typeparams"
)
var (
@ -978,7 +980,10 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
a.sizeof(instr.Type()))
case *ssa.Index:
a.copy(a.valueNode(instr), 1+a.valueNode(instr.X), a.sizeof(instr.Type()))
_, isstring := typeparams.CoreType(instr.X.Type()).(*types.Basic)
if !isstring {
a.copy(a.valueNode(instr), 1+a.valueNode(instr.X), a.sizeof(instr.Type()))
}
case *ssa.Select:
recv := a.valueOffsetNode(instr, 2) // instr : (index, recvOk, recv0, ... recv_n-1)
@ -1202,6 +1207,19 @@ func (a *analysis) genFunc(cgn *cgnode) {
return
}
if fn.TypeParams().Len() > 0 && len(fn.TypeArgs()) == 0 {
// Body of generic function.
// We'll warn about calls to such functions at the end.
return
}
if strings.HasPrefix(fn.Synthetic, "instantiation wrapper ") {
// instantiation wrapper of a generic function.
// These may contain type coercions which are not currently supported.
// We'll warn about calls to such functions at the end.
return
}
if a.log != nil {
fmt.Fprintln(a.log, "; Creating nodes for local values")
}

View File

@ -240,9 +240,14 @@ func doOneInput(t *testing.T, input, fpath string) bool {
// Find all calls to the built-in print(x). Analytically,
// print is a no-op, but it's a convenient hook for testing
// the PTS of an expression, so our tests use it.
// Exclude generic bodies as these should be dead code for pointer.
// Instance of generics are included.
probes := make(map[*ssa.CallCommon]bool)
for fn := range ssautil.AllFunctions(prog) {
// TODO(taking): Switch to a more principled check like fn.declaredPackage() == mainPkg if _Origin is exported.
if isGenericBody(fn) {
continue // skip generic bodies
}
// TODO(taking): Switch to a more principled check like fn.declaredPackage() == mainPkg if Origin is exported.
if fn.Pkg == mainpkg || (fn.Pkg == nil && mainFiles[prog.Fset.File(fn.Pos())]) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
@ -656,6 +661,15 @@ func TestInput(t *testing.T) {
}
}
// isGenericBody returns true if fn is the body of a generic function.
func isGenericBody(fn *ssa.Function) bool {
sig := fn.Signature
if typeparams.ForSignature(sig).Len() > 0 || typeparams.RecvTypeParams(sig).Len() > 0 {
return fn.Synthetic == ""
}
return false
}
// join joins the elements of multiset with " | "s.
func join(set map[string]int) string {
var buf bytes.Buffer

View File

@ -1024,7 +1024,7 @@ func ext۰reflect۰ChanOf(a *analysis, cgn *cgnode) {
var dir reflect.ChanDir // unknown
if site := cgn.callersite; site != nil {
if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok {
v, _ := constant.Int64Val(c.Value)
v := c.Int64()
if 0 <= v && v <= int64(reflect.BothDir) {
dir = reflect.ChanDir(v)
}
@ -1751,8 +1751,7 @@ func ext۰reflect۰rtype۰InOut(a *analysis, cgn *cgnode, out bool) {
index := -1
if site := cgn.callersite; site != nil {
if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok {
v, _ := constant.Int64Val(c.Value)
index = int(v)
index = int(c.Int64())
}
}
a.addConstraint(&rtypeInOutConstraint{

View File

@ -8,12 +8,13 @@ import (
"bytes"
"fmt"
"go/types"
exec "golang.org/x/sys/execabs"
"log"
"os"
"runtime"
"time"
exec "golang.org/x/sys/execabs"
"golang.org/x/tools/container/intsets"
)
@ -125,7 +126,7 @@ func (a *analysis) flatten(t types.Type) []*fieldInfo {
// Debuggability hack: don't remove
// the named type from interfaces as
// they're very verbose.
fl = append(fl, &fieldInfo{typ: t})
fl = append(fl, &fieldInfo{typ: t}) // t may be a type param
} else {
fl = a.flatten(u)
}

16
go/ssa/TODO Normal file
View File

@ -0,0 +1,16 @@
-*- text -*-
SSA Generics to-do list
===========================
DOCUMENTATION:
- Read me for internals
TYPE PARAMETERIZED GENERIC FUNCTIONS:
- sanity.go updates.
- Check source functions going to generics.
- Tests, tests, tests...
USAGE:
- Back fill users for handling ssa.InstantiateGenerics being off.

View File

@ -101,6 +101,9 @@ package ssa
//
// This is a low level operation for creating functions that do not exist in
// the source. Use with caution.
//
// TODO(taking): Use consistent terminology for "concrete".
// TODO(taking): Use consistent terminology for "monomorphization"/"instantiate"/"expand".
import (
"fmt"
@ -272,7 +275,7 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value {
return fn.emit(&c)
case *ast.IndexExpr:
mapt := fn.typeOf(e.X).Underlying().(*types.Map)
mapt := coreType(fn.typeOf(e.X)).(*types.Map) // ,ok must be a map.
lookup := &Lookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key()),
@ -309,7 +312,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ
typ = fn.typ(typ)
switch obj.Name() {
case "make":
switch typ.Underlying().(type) {
switch ct := coreType(typ).(type) {
case *types.Slice:
n := b.expr(fn, args[1])
m := n
@ -319,7 +322,7 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ
if m, ok := m.(*Const); ok {
// treat make([]T, n, m) as new([m]T)[:n]
cap := m.Int64()
at := types.NewArray(typ.Underlying().(*types.Slice).Elem(), cap)
at := types.NewArray(ct.Elem(), cap)
alloc := emitNew(fn, at, pos)
alloc.Comment = "makeslice"
v := &Slice{
@ -370,6 +373,8 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ
// We must still evaluate the value, though. (If it
// was side-effect free, the whole call would have
// been constant-folded.)
//
// Type parameters are always non-constant so use Underlying.
t := deref(fn.typeOf(args[0])).Underlying()
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
@ -465,27 +470,27 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
return &lazyAddress{addr: emit, t: fld.Type(), pos: e.Sel.Pos(), expr: e.Sel}
case *ast.IndexExpr:
xt := fn.typeOf(e.X)
elem, mode := indexType(xt)
var x Value
var et types.Type
switch t := fn.typeOf(e.X).Underlying().(type) {
case *types.Array:
switch mode {
case ixArrVar: // array, array|slice, array|*array, or array|*array|slice.
x = b.addr(fn, e.X, escaping).address(fn)
et = types.NewPointer(t.Elem())
case *types.Pointer: // *array
et = types.NewPointer(elem)
case ixVar: // *array, slice, *array|slice
x = b.expr(fn, e.X)
et = types.NewPointer(t.Elem().Underlying().(*types.Array).Elem())
case *types.Slice:
x = b.expr(fn, e.X)
et = types.NewPointer(t.Elem())
case *types.Map:
et = types.NewPointer(elem)
case ixMap:
mt := coreType(xt).(*types.Map)
return &element{
m: b.expr(fn, e.X),
k: emitConv(fn, b.expr(fn, e.Index), t.Key()),
t: t.Elem(),
k: emitConv(fn, b.expr(fn, e.Index), mt.Key()),
t: mt.Elem(),
pos: e.Lbrack,
}
default:
panic("unexpected container type in IndexExpr: " + t.String())
panic("unexpected container type in IndexExpr: " + xt.String())
}
index := b.expr(fn, e.Index)
if isUntyped(index.Type()) {
@ -562,7 +567,7 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *
}
if _, ok := loc.(*address); ok {
if isInterface(loc.typ()) {
if isNonTypeParamInterface(loc.typ()) {
// e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value.
// Fall back to copying.
@ -632,18 +637,19 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case *ast.FuncLit:
fn2 := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
Signature: fn.typeOf(e.Type).Underlying().(*types.Signature),
pos: e.Type.Func,
parent: fn,
Pkg: fn.Pkg,
Prog: fn.Prog,
syntax: e,
_Origin: nil, // anon funcs do not have an origin.
_TypeParams: fn._TypeParams, // share the parent's type parameters.
_TypeArgs: fn._TypeArgs, // share the parent's type arguments.
info: fn.info,
subst: fn.subst, // share the parent's type substitutions.
name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
Signature: fn.typeOf(e.Type).(*types.Signature),
pos: e.Type.Func,
parent: fn,
anonIdx: int32(len(fn.AnonFuncs)),
Pkg: fn.Pkg,
Prog: fn.Prog,
syntax: e,
topLevelOrigin: nil, // use anonIdx to lookup an anon instance's origin.
typeparams: fn.typeparams, // share the parent's type parameters.
typeargs: fn.typeargs, // share the parent's type arguments.
info: fn.info,
subst: fn.subst, // share the parent's type substitutions.
}
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
b.created.Add(fn2)
@ -745,14 +751,20 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case *ast.SliceExpr:
var low, high, max Value
var x Value
switch fn.typeOf(e.X).Underlying().(type) {
xtyp := fn.typeOf(e.X)
switch coreType(xtyp).(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).address(fn)
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
panic("unreachable")
// coreType exception?
if isBytestring(xtyp) {
x = b.expr(fn, e.X) // bytestring is handled as string and []byte.
} else {
panic("unexpected sequence type in SliceExpr")
}
}
if e.Low != nil {
low = b.expr(fn, e.Low)
@ -780,7 +792,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case *types.Builtin:
return &Builtin{name: obj.Name(), sig: fn.instanceType(e).(*types.Signature)}
case *types.Nil:
return nilConst(fn.instanceType(e))
return zeroConst(fn.instanceType(e))
}
// Package-level func or var?
if v := fn.Prog.packageLevelMember(obj); v != nil {
@ -788,7 +800,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return emitLoad(fn, g) // var (address)
}
callee := v.(*Function) // (func)
if len(callee._TypeParams) > 0 {
if callee.typeparams.Len() > 0 {
targs := fn.subst.types(instanceArgs(fn.info, e))
callee = fn.Prog.needsInstance(callee, targs, b.created)
}
@ -822,11 +834,32 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
wantAddr := isPointer(rt)
escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel)
if isInterface(rt) {
// If v has interface type I,
if types.IsInterface(rt) {
// If v may be an interface type I (after instantiating),
// we must emit a check that v is non-nil.
// We use: typeassert v.(I).
emitTypeAssert(fn, v, rt, token.NoPos)
if recv, ok := sel.recv.(*typeparams.TypeParam); ok {
// Emit a nil check if any possible instantiation of the
// type parameter is an interface type.
if len(typeSetOf(recv)) > 0 {
// recv has a concrete term its typeset.
// So it cannot be instantiated as an interface.
//
// Example:
// func _[T interface{~int; Foo()}] () {
// var v T
// _ = v.Foo // <-- MethodVal
// }
} else {
// rt may be instantiated as an interface.
// Emit nil check: typeassert (any(v)).(any).
emitTypeAssert(fn, emitConv(fn, v, tEface), tEface, token.NoPos)
}
} else {
// non-type param interface
// Emit nil check: typeassert v.(I).
emitTypeAssert(fn, v, rt, token.NoPos)
}
}
if targs := receiverTypeArgs(obj); len(targs) > 0 {
// obj is generic.
@ -863,9 +896,17 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases.
}
// not a generic instantiation.
switch t := fn.typeOf(e.X).Underlying().(type) {
case *types.Array:
// Non-addressable array (in a register).
xt := fn.typeOf(e.X)
switch et, mode := indexType(xt); mode {
case ixVar:
// Addressable slice/array; use IndexAddr and Load.
return b.addr(fn, e, false).load(fn)
case ixArrVar, ixValue:
// An array in a register, a string or a combined type that contains
// either an [_]array (ixArrVar) or string (ixValue).
// Note: for ixArrVar and coreType(xt)==nil can be IndexAddr and Load.
index := b.expr(fn, e.Index)
if isUntyped(index.Type()) {
index = emitConv(fn, index, tInt)
@ -875,38 +916,20 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
Index: index,
}
v.setPos(e.Lbrack)
v.setType(t.Elem())
v.setType(et)
return fn.emit(v)
case *types.Map:
case ixMap:
ct := coreType(xt).(*types.Map)
v := &Lookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), t.Key()),
Index: emitConv(fn, b.expr(fn, e.Index), ct.Key()),
}
v.setPos(e.Lbrack)
v.setType(t.Elem())
v.setType(ct.Elem())
return fn.emit(v)
case *types.Basic: // => string
// Strings are not addressable.
index := b.expr(fn, e.Index)
if isUntyped(index.Type()) {
index = emitConv(fn, index, tInt)
}
v := &Lookup{
X: b.expr(fn, e.X),
Index: index,
}
v.setPos(e.Lbrack)
v.setType(tByte)
return fn.emit(v)
case *types.Slice, *types.Pointer: // *array
// Addressable slice/array; use IndexAddr and Load.
return b.addr(fn, e, false).load(fn)
default:
panic("unexpected container type in IndexExpr: " + t.String())
panic("unexpected container type in IndexExpr: " + xt.String())
}
case *ast.CompositeLit, *ast.StarExpr:
@ -967,14 +990,14 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
wantAddr := isPointer(recv)
escaping := true
v := b.receiver(fn, selector.X, wantAddr, escaping, sel)
if isInterface(recv) {
if types.IsInterface(recv) {
// Invoke-mode call.
c.Value = v
c.Value = v // possibly type param
c.Method = obj
} else {
// "Call"-mode call.
callee := fn.Prog.originFunc(obj)
if len(callee._TypeParams) > 0 {
if callee.typeparams.Len() > 0 {
callee = fn.Prog.needsInstance(callee, receiverTypeArgs(obj), b.created)
}
c.Value = callee
@ -1065,7 +1088,7 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx
st := sig.Params().At(np).Type().(*types.Slice)
vt := st.Elem()
if len(varargs) == 0 {
args = append(args, nilConst(st))
args = append(args, zeroConst(st))
} else {
// Replace a suffix of args with a slice containing it.
at := types.NewArray(vt, int64(len(varargs)))
@ -1097,7 +1120,7 @@ func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {
b.setCallFunc(fn, e, c)
// Then append the other actual parameters.
sig, _ := fn.typeOf(e.Fun).Underlying().(*types.Signature)
sig, _ := coreType(fn.typeOf(e.Fun)).(*types.Signature)
if sig == nil {
panic(fmt.Sprintf("no signature for call of %s", e.Fun))
}
@ -1230,8 +1253,32 @@ func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 {
// literal has type *T behaves like &T{}.
// In that case, addr must hold a T, not a *T.
func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) {
typ := deref(fn.typeOf(e))
switch t := typ.Underlying().(type) {
typ := deref(fn.typeOf(e)) // type with name [may be type param]
t := deref(coreType(typ)).Underlying() // core type for comp lit case
// Computing typ and t is subtle as these handle pointer types.
// For example, &T{...} is valid even for maps and slices.
// Also typ should refer to T (not *T) while t should be the core type of T.
//
// To show the ordering to take into account, consider the composite literal
// expressions `&T{f: 1}` and `{f: 1}` within the expression `[]S{{f: 1}}` here:
// type N struct{f int}
// func _[T N, S *N]() {
// _ = &T{f: 1}
// _ = []S{{f: 1}}
// }
// For `&T{f: 1}`, we compute `typ` and `t` as:
// typeOf(&T{f: 1}) == *T
// deref(*T) == T (typ)
// coreType(T) == N
// deref(N) == N
// N.Underlying() == struct{f int} (t)
// For `{f: 1}` in `[]S{{f: 1}}`, we compute `typ` and `t` as:
// typeOf({f: 1}) == S
// deref(S) == S (typ)
// coreType(S) == *N
// deref(*N) == N
// N.Underlying() == struct{f int} (t)
switch t := t.(type) {
case *types.Struct:
if !isZero && len(e.Elts) != t.NumFields() {
// memclear
@ -1259,6 +1306,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero
X: addr,
Field: fieldIndex,
}
faddr.setPos(pos)
faddr.setType(types.NewPointer(sf.Type()))
fn.emit(faddr)
b.assign(fn, &address{addr: faddr, pos: pos, expr: e}, e, isZero, sb)
@ -1529,7 +1577,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl
casetype = fn.typeOf(cond)
var condv Value
if casetype == tUntypedNil {
condv = emitCompare(fn, token.EQL, x, nilConst(x.Type()), cond.Pos())
condv = emitCompare(fn, token.EQL, x, zeroConst(x.Type()), cond.Pos())
ti = x
} else {
yok := emitTypeTest(fn, x, casetype, cc.Case)
@ -1612,7 +1660,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
case *ast.SendStmt: // ch<- i
ch := b.expr(fn, comm.Chan)
chtyp := fn.typ(ch.Type()).Underlying().(*types.Chan)
chtyp := coreType(fn.typ(ch.Type())).(*types.Chan)
st = &SelectState{
Dir: types.SendOnly,
Chan: ch,
@ -1669,9 +1717,8 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
vars = append(vars, varIndex, varOk)
for _, st := range states {
if st.Dir == types.RecvOnly {
chtyp := fn.typ(st.Chan.Type()).Underlying().(*types.Chan)
tElem := chtyp.Elem()
vars = append(vars, anonVar(tElem))
chtyp := coreType(fn.typ(st.Chan.Type())).(*types.Chan)
vars = append(vars, anonVar(chtyp.Elem()))
}
}
sel.setType(types.NewTuple(vars...))
@ -1835,6 +1882,8 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P
// elimination if x is pure, static unrolling, etc.
// Ranging over a nil *array may have >0 iterations.
// We still generate code for x, in case it has effects.
//
// TypeParams do not have constant length. Use underlying instead of core type.
length = intConst(arr.Len())
} else {
// length = len(x).
@ -1867,7 +1916,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P
k = emitLoad(fn, index)
if tv != nil {
switch t := x.Type().Underlying().(type) {
switch t := coreType(x.Type()).(type) {
case *types.Array:
instr := &Index{
X: x,
@ -1937,11 +1986,9 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.
emitJump(fn, loop)
fn.currentBlock = loop
_, isString := x.Type().Underlying().(*types.Basic)
okv := &Next{
Iter: it,
IsString: isString,
IsString: isBasic(coreType(x.Type())),
}
okv.setType(types.NewTuple(
varOk,
@ -1991,7 +2038,7 @@ func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos)
}
recv.setPos(pos)
recv.setType(types.NewTuple(
newVar("k", x.Type().Underlying().(*types.Chan).Elem()),
newVar("k", coreType(x.Type()).(*types.Chan).Elem()),
varOk,
))
ko := fn.emit(recv)
@ -2035,7 +2082,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
var k, v Value
var loop, done *BasicBlock
switch rt := x.Type().Underlying().(type) {
switch rt := coreType(x.Type()).(type) {
case *types.Slice, *types.Array, *types.Pointer: // *array
k, v, loop, done = b.rangeIndexed(fn, x, tv, s.For)
@ -2113,11 +2160,11 @@ start:
b.expr(fn, s.X)
case *ast.SendStmt:
chtyp := coreType(fn.typeOf(s.Chan)).(*types.Chan)
fn.emit(&Send{
Chan: b.expr(fn, s.Chan),
X: emitConv(fn, b.expr(fn, s.Value),
fn.typeOf(s.Chan).Underlying().(*types.Chan).Elem()),
pos: s.Arrow,
X: emitConv(fn, b.expr(fn, s.Value), chtyp.Elem()),
pos: s.Arrow,
})
case *ast.IncDecStmt:
@ -2295,11 +2342,9 @@ func (b *builder) buildFunctionBody(fn *Function) {
var functype *ast.FuncType
switch n := fn.syntax.(type) {
case nil:
// TODO(taking): Temporarily this can be the body of a generic function.
if fn.Params != nil {
return // not a Go source function. (Synthetic, or from object file.)
}
// fn.Params == nil is handled within body == nil case.
case *ast.FuncDecl:
functype = n.Type
recvField = n.Recv
@ -2331,6 +2376,13 @@ func (b *builder) buildFunctionBody(fn *Function) {
}
return
}
// Build instantiation wrapper around generic body?
if fn.topLevelOrigin != nil && fn.subst == nil {
buildInstantiationWrapper(fn)
return
}
if fn.Prog.mode&LogSource != 0 {
defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))()
}
@ -2435,7 +2487,17 @@ func (p *Package) build() {
// TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order.
for name, mem := range p.Members {
if ast.IsExported(name) && !isGeneric(mem) {
isGround := func(m Member) bool {
switch m := m.(type) {
case *Type:
named, _ := m.Type().(*types.Named)
return named == nil || typeparams.ForNamed(named) == nil
case *Function:
return m.typeparams.Len() == 0
}
return true // *NamedConst, *Global
}
if ast.IsExported(name) && isGround(mem) {
p.Prog.needMethodsOf(mem.Type(), &p.created)
}
}

View File

@ -0,0 +1,664 @@
// 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_test
import (
"fmt"
"go/parser"
"go/token"
"reflect"
"sort"
"testing"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/typeparams"
)
// TestGenericBodies tests that bodies of generic functions and methods containing
// different constructs can be built in BuilderMode(0).
//
// Each test specifies the contents of package containing a single go file.
// Each call print(arg0, arg1, ...) to the builtin print function
// in ssa is correlated a comment at the end of the line of the form:
//
// //@ types(a, b, c)
//
// where a, b and c are the types of the arguments to the print call
// serialized using go/types.Type.String().
// See x/tools/go/expect for details on the syntax.
func TestGenericBodies(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestGenericBodies requires type parameters")
}
for _, test := range []struct {
pkg string // name of the package.
contents string // contents of the Go package.
}{
{
pkg: "p",
contents: `
package p
func f(x int) {
var i interface{}
print(i, 0) //@ types("interface{}", int)
print() //@ types()
print(x) //@ types(int)
}
`,
},
{
pkg: "q",
contents: `
package q
func f[T any](x T) {
print(x) //@ types(T)
}
`,
},
{
pkg: "r",
contents: `
package r
func f[T ~int]() {
var x T
print(x) //@ types(T)
}
`,
},
{
pkg: "s",
contents: `
package s
func a[T ~[4]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func b[T ~*[4]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func c[T ~[]byte](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, byte)
}
}
func d[T ~string](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, rune)
}
}
func e[T ~map[int]string](x T) {
for k, v := range x {
print(x, k, v) //@ types(T, int, string)
}
}
func f[T ~chan string](x T) {
for v := range x {
print(x, v) //@ types(T, string)
}
}
func From() {
type A [4]byte
print(a[A]) //@ types("func(x s.A)")
type B *[4]byte
print(b[B]) //@ types("func(x s.B)")
type C []byte
print(c[C]) //@ types("func(x s.C)")
type D string
print(d[D]) //@ types("func(x s.D)")
type E map[int]string
print(e[E]) //@ types("func(x s.E)")
type F chan string
print(f[F]) //@ types("func(x s.F)")
}
`,
},
{
pkg: "t",
contents: `
package t
func f[S any, T ~chan S](x T) {
for v := range x {
print(x, v) //@ types(T, S)
}
}
func From() {
type F chan string
print(f[string, F]) //@ types("func(x t.F)")
}
`,
},
{
pkg: "u",
contents: `
package u
func fibonacci[T ~chan int](c, quit T) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
print(c, quit, x, y) //@ types(T, T, int, int)
return
}
}
}
func start[T ~chan int](c, quit T) {
go func() {
for i := 0; i < 10; i++ {
print(<-c) //@ types(int)
}
quit <- 0
}()
}
func From() {
type F chan int
c := make(F)
quit := make(F)
print(start[F], c, quit) //@ types("func(c u.F, quit u.F)", "u.F", "u.F")
print(fibonacci[F], c, quit) //@ types("func(c u.F, quit u.F)", "u.F", "u.F")
}
`,
},
{
pkg: "v",
contents: `
package v
func f[T ~struct{ x int; y string }](i int) T {
u := []T{ T{0, "lorem"}, T{1, "ipsum"}}
return u[i]
}
func From() {
type S struct{ x int; y string }
print(f[S]) //@ types("func(i int) v.S")
}
`,
},
{
pkg: "w",
contents: `
package w
func f[T ~[4]int8](x T, l, h int) []int8 {
return x[l:h]
}
func g[T ~*[4]int16](x T, l, h int) []int16 {
return x[l:h]
}
func h[T ~[]int32](x T, l, h int) T {
return x[l:h]
}
func From() {
type F [4]int8
type G *[4]int16
type H []int32
print(f[F](F{}, 0, 0)) //@ types("[]int8")
print(g[G](nil, 0, 0)) //@ types("[]int16")
print(h[H](nil, 0, 0)) //@ types("w.H")
}
`,
},
{
pkg: "x",
contents: `
package x
func h[E any, T ~[]E](x T, l, h int) []E {
s := x[l:h]
print(s) //@ types("T")
return s
}
func From() {
type H []int32
print(h[int32, H](nil, 0, 0)) //@ types("[]int32")
}
`,
},
{
pkg: "y",
contents: `
package y
// Test "make" builtin with different forms on core types and
// when capacities are constants or variable.
func h[E any, T ~[]E](m, n int) {
print(make(T, 3)) //@ types(T)
print(make(T, 3, 5)) //@ types(T)
print(make(T, m)) //@ types(T)
print(make(T, m, n)) //@ types(T)
}
func i[K comparable, E any, T ~map[K]E](m int) {
print(make(T)) //@ types(T)
print(make(T, 5)) //@ types(T)
print(make(T, m)) //@ types(T)
}
func j[E any, T ~chan E](m int) {
print(make(T)) //@ types(T)
print(make(T, 6)) //@ types(T)
print(make(T, m)) //@ types(T)
}
func From() {
type H []int32
h[int32, H](3, 4)
type I map[int8]H
i[int8, H, I](5)
type J chan I
j[I, J](6)
}
`,
},
{
pkg: "z",
contents: `
package z
func h[T ~[4]int](x T) {
print(len(x), cap(x)) //@ types(int, int)
}
func i[T ~[4]byte | []int | ~chan uint8](x T) {
print(len(x), cap(x)) //@ types(int, int)
}
func j[T ~[4]int | any | map[string]int]() {
print(new(T)) //@ types("*T")
}
func k[T ~[4]int | any | map[string]int](x T) {
print(x) //@ types(T)
panic(x)
}
`,
},
{
pkg: "a",
contents: `
package a
func f[E any, F ~func() E](x F) {
print(x, x()) //@ types(F, E)
}
func From() {
type T func() int
f[int, T](func() int { return 0 })
f[int, func() int](func() int { return 1 })
}
`,
},
{
pkg: "b",
contents: `
package b
func f[E any, M ~map[string]E](m M) {
y, ok := m["lorem"]
print(m, y, ok) //@ types(M, E, bool)
}
func From() {
type O map[string][]int
f(O{"lorem": []int{0, 1, 2, 3}})
}
`,
},
{
pkg: "c",
contents: `
package c
func a[T interface{ []int64 | [5]int64 }](x T) int64 {
print(x, x[2], x[3]) //@ types(T, int64, int64)
x[2] = 5
return x[3]
}
func b[T interface{ []byte | string }](x T) byte {
print(x, x[3]) //@ types(T, byte)
return x[3]
}
func c[T interface{ []byte }](x T) byte {
print(x, x[2], x[3]) //@ types(T, byte, byte)
x[2] = 'b'
return x[3]
}
func d[T interface{ map[int]int64 }](x T) int64 {
print(x, x[2], x[3]) //@ types(T, int64, int64)
x[2] = 43
return x[3]
}
func e[T ~string](t T) {
print(t, t[0]) //@ types(T, uint8)
}
func f[T ~string|[]byte](t T) {
print(t, t[0]) //@ types(T, uint8)
}
func g[T []byte](t T) {
print(t, t[0]) //@ types(T, byte)
}
func h[T ~[4]int|[]int](t T) {
print(t, t[0]) //@ types(T, int)
}
func i[T ~[4]int|*[4]int|[]int](t T) {
print(t, t[0]) //@ types(T, int)
}
func j[T ~[4]int|*[4]int|[]int](t T) {
print(t, &t[0]) //@ types(T, "*int")
}
`,
},
{
pkg: "d",
contents: `
package d
type MyInt int
type Other int
type MyInterface interface{ foo() }
// ChangeType tests
func ct0(x int) { v := MyInt(x); print(x, v) /*@ types(int, "d.MyInt")*/ }
func ct1[T MyInt | Other, S int ](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
func ct2[T int, S MyInt | int ](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
func ct3[T MyInt | Other, S MyInt | int ](x S) { v := T(x) ; print(x, v) /*@ types(S, T)*/ }
// Convert tests
func co0[T int | int8](x MyInt) { v := T(x); print(x, v) /*@ types("d.MyInt", T)*/}
func co1[T int | int8](x T) { v := MyInt(x); print(x, v) /*@ types(T, "d.MyInt")*/ }
func co2[S, T int | int8](x T) { v := S(x); print(x, v) /*@ types(T, S)*/ }
// MakeInterface tests
func mi0[T MyInterface](x T) { v := MyInterface(x); print(x, v) /*@ types(T, "d.MyInterface")*/ }
// NewConst tests
func nc0[T any]() { v := (*T)(nil); print(v) /*@ types("*T")*/}
// SliceToArrayPointer
func sl0[T *[4]int | *[2]int](x []int) { v := T(x); print(x, v) /*@ types("[]int", T)*/ }
func sl1[T *[4]int | *[2]int, S []int](x S) { v := T(x); print(x, v) /*@ types(S, T)*/ }
`,
},
{
pkg: "e",
contents: `
package e
func c[T interface{ foo() string }](x T) {
print(x, x.foo, x.foo()) /*@ types(T, "func() string", string)*/
}
`,
},
{
pkg: "f",
contents: `package f
func eq[T comparable](t T, i interface{}) bool {
return t == i
}
`,
},
{
pkg: "g",
contents: `package g
type S struct{ f int }
func c[P *S]() []P { return []P{{f: 1}} }
`,
},
{
pkg: "h",
contents: `package h
func sign[bytes []byte | string](s bytes) (bool, bool) {
neg := false
if len(s) > 0 && (s[0] == '-' || s[0] == '+') {
neg = s[0] == '-'
s = s[1:]
}
return !neg, len(s) > 0
}`,
},
{
pkg: "i",
contents: `package i
func digits[bytes []byte | string](s bytes) bool {
for _, c := range []byte(s) {
if c < '0' || '9' < c {
return false
}
}
return true
}`,
},
} {
test := test
t.Run(test.pkg, func(t *testing.T) {
// Parse
conf := loader.Config{ParserMode: parser.ParseComments}
fname := test.pkg + ".go"
f, err := conf.ParseFile(fname, test.contents)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles(test.pkg, f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build SSA
prog := ssa.NewProgram(lprog.Fset, ssa.SanityCheckFunctions)
for _, info := range lprog.AllPackages {
if info.TransitivelyErrorFree {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
}
p := prog.Package(lprog.Package(test.pkg).Pkg)
p.Build()
// Collect calls to the builtin print function.
probes := make(map[*ssa.CallCommon]bool)
for _, mem := range p.Members {
if fn, ok := mem.(*ssa.Function); ok {
for _, bb := range fn.Blocks {
for _, i := range bb.Instrs {
if i, ok := i.(ssa.CallInstruction); ok {
call := i.Common()
if b, ok := call.Value.(*ssa.Builtin); ok && b.Name() == "print" {
probes[i.Common()] = true
}
}
}
}
}
}
// Collect all notes in f, i.e. comments starting with "//@ types".
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Errorf("expect.ExtractGo: %v", err)
}
// Matches each probe with a note that has the same line.
sameLine := func(x, y token.Pos) bool {
xp := prog.Fset.Position(x)
yp := prog.Fset.Position(y)
return xp.Filename == yp.Filename && xp.Line == yp.Line
}
expectations := make(map[*ssa.CallCommon]*expect.Note)
for call := range probes {
var match *expect.Note
for _, note := range notes {
if note.Name == "types" && sameLine(call.Pos(), note.Pos) {
match = note // first match is good enough.
break
}
}
if match != nil {
expectations[call] = match
} else {
t.Errorf("Unmatched probe: %v", call)
}
}
// Check each expectation.
for call, note := range expectations {
var args []string
for _, a := range call.Args {
args = append(args, a.Type().String())
}
if got, want := fmt.Sprint(args), fmt.Sprint(note.Args); got != want {
t.Errorf("Arguments to print() were expected to be %q. got %q", want, got)
}
}
})
}
}
// TestInstructionString tests serializing instructions via Instruction.String().
func TestInstructionString(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestInstructionString requires type parameters")
}
// Tests (ssa.Instruction).String(). Instructions are from a single go file.
// The Instructions tested are those that match a comment of the form:
//
// //@ instrs(f, kind, strs...)
//
// where f is the name of the function, kind is the type of the instructions matched
// within the function, and tests that the String() value for all of the instructions
// matched of String() is strs (in some order).
// See x/tools/go/expect for details on the syntax.
const contents = `
package p
//@ instrs("f", "*ssa.TypeAssert")
//@ instrs("f", "*ssa.Call", "print(nil:interface{}, 0:int)")
func f(x int) { // non-generic smoke test.
var i interface{}
print(i, 0)
}
//@ instrs("h", "*ssa.Alloc", "local T (u)")
//@ instrs("h", "*ssa.FieldAddr", "&t0.x [#0]")
func h[T ~struct{ x string }]() T {
u := T{"lorem"}
return u
}
//@ instrs("c", "*ssa.TypeAssert", "typeassert t0.(interface{})")
//@ instrs("c", "*ssa.Call", "invoke x.foo()")
func c[T interface{ foo() string }](x T) {
_ = x.foo
_ = x.foo()
}
//@ instrs("d", "*ssa.TypeAssert", "typeassert t0.(interface{})")
//@ instrs("d", "*ssa.Call", "invoke x.foo()")
func d[T interface{ foo() string; comparable }](x T) {
_ = x.foo
_ = x.foo()
}
`
// Parse
conf := loader.Config{ParserMode: parser.ParseComments}
const fname = "p.go"
f, err := conf.ParseFile(fname, contents)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
// Create and build SSA
prog := ssa.NewProgram(lprog.Fset, ssa.SanityCheckFunctions)
for _, info := range lprog.AllPackages {
if info.TransitivelyErrorFree {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
}
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
// Collect all notes in f, i.e. comments starting with "//@ instr".
notes, err := expect.ExtractGo(prog.Fset, f)
if err != nil {
t.Errorf("expect.ExtractGo: %v", err)
}
// Expectation is a {function, type string} -> {want, matches}
// where matches is all Instructions.String() that match the key.
// Each expecation is that some permutation of matches is wants.
type expKey struct {
function string
kind string
}
type expValue struct {
wants []string
matches []string
}
expectations := make(map[expKey]*expValue)
for _, note := range notes {
if note.Name == "instrs" {
if len(note.Args) < 2 {
t.Error("Had @instrs annotation without at least 2 arguments")
continue
}
fn, kind := fmt.Sprint(note.Args[0]), fmt.Sprint(note.Args[1])
var wants []string
for _, arg := range note.Args[2:] {
wants = append(wants, fmt.Sprint(arg))
}
expectations[expKey{fn, kind}] = &expValue{wants, nil}
}
}
// Collect all Instructions that match the expectations.
for _, mem := range p.Members {
if fn, ok := mem.(*ssa.Function); ok {
for _, bb := range fn.Blocks {
for _, i := range bb.Instrs {
kind := fmt.Sprintf("%T", i)
if e := expectations[expKey{fn.Name(), kind}]; e != nil {
e.matches = append(e.matches, i.String())
}
}
}
}
}
// Check each expectation.
for key, value := range expectations {
if _, ok := p.Members[key.function]; !ok {
t.Errorf("Expectation on %s does not match a member in %s", key.function, p.Pkg.Name())
}
got, want := value.matches, value.wants
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(want, got) {
t.Errorf("Within %s wanted instructions of kind %s: %q. got %q", key.function, key.kind, want, got)
}
}
}

View File

@ -25,6 +25,10 @@ func TestBuildPackageGo120(t *testing.T) {
importer types.Importer
}{
{"slice to array", "package p; var s []byte; var _ = ([4]byte)(s)", nil},
{"slice to zero length array", "package p; var s []byte; var _ = ([0]byte)(s)", nil},
{"slice to zero length array type parameter", "package p; var s []byte; func f[T ~[0]byte]() { tmp := (T)(s); var z T; _ = tmp == z}", nil},
{"slice to non-zero length array type parameter", "package p; var s []byte; func h[T ~[1]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{"slice to maybe-zero length array type parameter", "package p; var s []byte; func g[T ~[0]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
}
for _, tc := range tests {

View File

@ -226,6 +226,18 @@ func TestRuntimeTypes(t *testing.T) {
nil,
},
}
if typeparams.Enabled {
tests = append(tests, []struct {
input string
want []string
}{
// MakeInterface does not create runtime type for parameterized types.
{`package N; var g interface{}; func f[S any]() { var v []S; g = v }; `,
nil,
},
}...)
}
for _, test := range tests {
// Parse the file.
fset := token.NewFileSet()

View File

@ -12,65 +12,73 @@ import (
"go/token"
"go/types"
"strconv"
"strings"
"golang.org/x/tools/internal/typeparams"
)
// NewConst returns a new constant of the specified value and type.
// val must be valid according to the specification of Const.Value.
func NewConst(val constant.Value, typ types.Type) *Const {
if val == nil {
switch soleTypeKind(typ) {
case types.IsBoolean:
val = constant.MakeBool(false)
case types.IsInteger:
val = constant.MakeInt64(0)
case types.IsString:
val = constant.MakeString("")
}
}
return &Const{typ, val}
}
// soleTypeKind returns a BasicInfo for which constant.Value can
// represent all zero values for the types in the type set.
//
// types.IsBoolean for false is a representative.
// types.IsInteger for 0
// types.IsString for ""
// 0 otherwise.
func soleTypeKind(typ types.Type) types.BasicInfo {
// State records the set of possible zero values (false, 0, "").
// Candidates (perhaps all) are eliminated during the type-set
// iteration, which executes at least once.
state := types.IsBoolean | types.IsInteger | types.IsString
typeSetOf(typ).underIs(func(t types.Type) bool {
var c types.BasicInfo
if t, ok := t.(*types.Basic); ok {
c = t.Info()
}
if c&types.IsNumeric != 0 { // int/float/complex
c = types.IsInteger
}
state = state & c
return state != 0
})
return state
}
// intConst returns an 'int' constant that evaluates to i.
// (i is an int64 in case the host is narrower than the target.)
func intConst(i int64) *Const {
return NewConst(constant.MakeInt64(i), tInt)
}
// nilConst returns a nil constant of the specified type, which may
// be any reference type, including interfaces.
func nilConst(typ types.Type) *Const {
return NewConst(nil, typ)
}
// stringConst returns a 'string' constant that evaluates to s.
func stringConst(s string) *Const {
return NewConst(constant.MakeString(s), tString)
}
// zeroConst returns a new "zero" constant of the specified type,
// which must not be an array or struct type: the zero values of
// aggregates are well-defined but cannot be represented by Const.
// zeroConst returns a new "zero" constant of the specified type.
func zeroConst(t types.Type) *Const {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return NewConst(constant.MakeBool(false), t)
case t.Info()&types.IsNumeric != 0:
return NewConst(constant.MakeInt64(0), t)
case t.Info()&types.IsString != 0:
return NewConst(constant.MakeString(""), t)
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return nilConst(t)
default:
panic(fmt.Sprint("zeroConst for unexpected type:", t))
}
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
return nilConst(t)
case *types.Named:
return NewConst(zeroConst(t.Underlying()).Value, t)
case *types.Array, *types.Struct, *types.Tuple:
panic(fmt.Sprint("zeroConst applied to aggregate:", t))
}
panic(fmt.Sprint("zeroConst: unexpected ", t))
return NewConst(nil, t)
}
func (c *Const) RelString(from *types.Package) string {
var s string
if c.Value == nil {
s = "nil"
s = zeroString(c.typ, from)
} else if c.Value.Kind() == constant.String {
s = constant.StringVal(c.Value)
const max = 20
@ -85,6 +93,44 @@ func (c *Const) RelString(from *types.Package) string {
return s + ":" + relType(c.Type(), from)
}
// zeroString returns the string representation of the "zero" value of the type t.
func zeroString(t types.Type, from *types.Package) string {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return "false"
case t.Info()&types.IsNumeric != 0:
return "0"
case t.Info()&types.IsString != 0:
return `""`
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return "nil"
default:
panic(fmt.Sprint("zeroString for unexpected type:", t))
}
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
return "nil"
case *types.Named:
return zeroString(t.Underlying(), from)
case *types.Array, *types.Struct:
return relType(t, from) + "{}"
case *types.Tuple:
// Tuples are not normal values.
// We are currently format as "(t[0], ..., t[n])". Could be something else.
components := make([]string, t.Len())
for i := 0; i < t.Len(); i++ {
components[i] = zeroString(t.At(i).Type(), from)
}
return "(" + strings.Join(components, ", ") + ")"
case *typeparams.TypeParam:
return "*new(" + relType(t, from) + ")"
}
panic(fmt.Sprint("zeroString: unexpected ", t))
}
func (c *Const) Name() string {
return c.RelString(nil)
}
@ -107,9 +153,26 @@ func (c *Const) Pos() token.Pos {
return token.NoPos
}
// IsNil returns true if this constant represents a typed or untyped nil value.
// IsNil returns true if this constant represents a typed or untyped nil value
// with an underlying reference type: pointer, slice, chan, map, function, or
// *basic* interface.
//
// Note: a type parameter whose underlying type is a basic interface is
// considered a reference type.
func (c *Const) IsNil() bool {
return c.Value == nil
return c.Value == nil && nillable(c.typ)
}
// nillable reports whether *new(T) == nil is legal for type T.
func nillable(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
return true
case *types.Interface:
return len(typeSetOf(t)) == 0 // basic interface.
default:
return false
}
}
// TODO(adonovan): move everything below into golang.org/x/tools/go/ssa/interp.
@ -149,14 +212,16 @@ func (c *Const) Uint64() uint64 {
// Float64 returns the numeric value of this constant truncated to fit
// a float64.
func (c *Const) Float64() float64 {
f, _ := constant.Float64Val(c.Value)
x := constant.ToFloat(c.Value) // (c.Value == nil) => x.Kind() == Unknown
f, _ := constant.Float64Val(x)
return f
}
// Complex128 returns the complex value of this constant truncated to
// fit a complex128.
func (c *Const) Complex128() complex128 {
re, _ := constant.Float64Val(constant.Real(c.Value))
im, _ := constant.Float64Val(constant.Imag(c.Value))
x := constant.ToComplex(c.Value) // (c.Value == nil) => x.Kind() == Unknown
re, _ := constant.Float64Val(constant.Real(x))
im, _ := constant.Float64Val(constant.Imag(x))
return complex(re, im)
}

104
go/ssa/const_test.go Normal file
View File

@ -0,0 +1,104 @@
// 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_test
import (
"go/ast"
"go/constant"
"go/parser"
"go/token"
"go/types"
"math/big"
"strings"
"testing"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/typeparams"
)
func TestConstString(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestConstString requires type parameters.")
}
const source = `
package P
type Named string
func fn() (int, bool, string)
func gen[T int]() {}
`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", source, 0)
if err != nil {
t.Fatal(err)
}
var conf types.Config
pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
expr string // type expression
constant interface{} // constant value
want string // expected String() value
}{
{"int", int64(0), "0:int"},
{"int64", int64(0), "0:int64"},
{"float32", int64(0), "0:float32"},
{"float32", big.NewFloat(1.5), "1.5:float32"},
{"bool", false, "false:bool"},
{"string", "", `"":string`},
{"Named", "", `"":P.Named`},
{"struct{x string}", nil, "struct{x string}{}:struct{x string}"},
{"[]int", nil, "nil:[]int"},
{"[3]int", nil, "[3]int{}:[3]int"},
{"*int", nil, "nil:*int"},
{"interface{}", nil, "nil:interface{}"},
{"interface{string}", nil, `"":interface{string}`},
{"interface{int|int64}", nil, "0:interface{int|int64}"},
{"interface{bool}", nil, "false:interface{bool}"},
{"interface{bool|int}", nil, "nil:interface{bool|int}"},
{"interface{int|string}", nil, "nil:interface{int|string}"},
{"interface{bool|string}", nil, "nil:interface{bool|string}"},
{"interface{struct{x string}}", nil, "nil:interface{struct{x string}}"},
{"interface{int|int64}", int64(1), "1:interface{int|int64}"},
{"interface{~bool}", true, "true:interface{~bool}"},
{"interface{Named}", "lorem ipsum", `"lorem ipsum":interface{P.Named}`},
{"func() (int, bool, string)", nil, "nil:func() (int, bool, string)"},
} {
// Eval() expr for its type.
tv, err := types.Eval(fset, pkg, 0, test.expr)
if err != nil {
t.Fatalf("Eval(%s) failed: %v", test.expr, err)
}
var val constant.Value
if test.constant != nil {
val = constant.Make(test.constant)
}
c := ssa.NewConst(val, tv.Type)
got := strings.ReplaceAll(c.String(), " | ", "|") // Accept both interface{a | b} and interface{a|b}.
if got != test.want {
t.Errorf("ssa.NewConst(%v, %s).String() = %v, want %v", val, tv.Type, got, test.want)
}
}
// Test tuples
fn := pkg.Scope().Lookup("fn")
tup := fn.Type().(*types.Signature).Results()
if got, want := ssa.NewConst(nil, tup).String(), `(0, false, ""):(int, bool, string)`; got != want {
t.Errorf("ssa.NewConst(%v, %s).String() = %v, want %v", nil, tup, got, want)
}
// Test type-param
gen := pkg.Scope().Lookup("gen")
tp := typeparams.ForSignature(gen.Type().(*types.Signature)).At(0)
if got, want := ssa.NewConst(nil, tp).String(), "0:T"; got != want {
t.Errorf("ssa.NewConst(%v, %s).String() = %v, want %v", nil, tup, got, want)
}
}

256
go/ssa/coretype.go Normal file
View File

@ -0,0 +1,256 @@
// 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.
// coreType returns the core type of T or nil if T does not have a core type.
//
// See https://go.dev/ref/spec#Core_types for the definition of a core type.
func coreType(T types.Type) types.Type {
U := T.Underlying()
if _, ok := U.(*types.Interface); !ok {
return U // for non-interface types,
}
terms, err := _NormalTerms(U)
if len(terms) == 0 || err != nil {
// len(terms) -> empty type set of interface.
// err != nil => U is invalid, exceeds complexity bounds, or has an empty type set.
return nil // no core type.
}
U = terms[0].Type().Underlying()
var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying())
for identical = 1; identical < len(terms); identical++ {
if !types.Identical(U, terms[identical].Type().Underlying()) {
break
}
}
if identical == len(terms) {
// https://go.dev/ref/spec#Core_types
// "There is a single type U which is the underlying type of all types in the type set of T"
return U
}
ch, ok := U.(*types.Chan)
if !ok {
return nil // no core type as identical < len(terms) and U is not a channel.
}
// https://go.dev/ref/spec#Core_types
// "the type chan E if T contains only bidirectional channels, or the type chan<- E or
// <-chan E depending on the direction of the directional channels present."
for chans := identical; chans < len(terms); chans++ {
curr, ok := terms[chans].Type().Underlying().(*types.Chan)
if !ok {
return nil
}
if !types.Identical(ch.Elem(), curr.Elem()) {
return nil // channel elements are not identical.
}
if ch.Dir() == types.SendRecv {
// ch is bidirectional. We can safely always use curr's direction.
ch = curr
} else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() {
// ch and curr are not bidirectional and not the same direction.
return nil
}
}
return ch
}
// isBytestring returns true if T has the same terms as interface{[]byte | string}.
// These act like a coreType 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 len(tset) != 2 {
return false
}
hasBytes, hasString := false, false
tset.underIs(func(t types.Type) bool {
switch {
case isString(t):
hasString = true
case isByteSlice(t):
hasBytes = true
}
return hasBytes || hasString
})
return hasBytes && hasString
}
// _NormalTerms returns a slice of terms representing the normalized structural
// type restrictions of a type, if any.
//
// For all types other than *types.TypeParam, *types.Interface, and
// *types.Union, this is just a single term with Tilde() == false and
// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see
// below.
//
// Structural type restrictions of a type parameter are created via
// non-interface types embedded in its constraint interface (directly, or via a
// chain of interface embeddings). For example, in the declaration type
// T[P interface{~int; m()}] int the structural restriction of the type
// parameter P is ~int.
//
// With interface embedding and unions, the specification of structural type
// restrictions may be arbitrarily complex. For example, consider the
// following:
//
// type A interface{ ~string|~[]byte }
//
// type B interface{ int|string }
//
// type C interface { ~string|~int }
//
// type T[P interface{ A|B; C }] int
//
// In this example, the structural type restriction of P is ~string|int: A|B
// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
// which when intersected with C (~string|~int) yields ~string|int.
//
// _NormalTerms computes these expansions and reductions, producing a
// "normalized" form of the embeddings. A structural restriction is normalized
// if it is a single union containing no interface terms, and is minimal in the
// sense that removing any term changes the set of types satisfying the
// constraint. It is left as a proof for the reader that, modulo sorting, there
// is exactly one such normalized form.
//
// Because the minimal representation always takes this form, _NormalTerms
// returns a slice of tilde terms corresponding to the terms of the union in
// the normalized structural restriction. An error is returned if the type is
// invalid, exceeds complexity bounds, or has an empty type set. In the latter
// case, _NormalTerms returns ErrEmptyTypeSet.
//
// _NormalTerms makes no guarantees about the order of terms, except that it
// is deterministic.
//
// This is a copy of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
// TODO(taking): Remove this copy when possible.
func _NormalTerms(typ types.Type) ([]*typeparams.Term, error) {
switch typ := typ.(type) {
case *typeparams.TypeParam:
return typeparams.StructuralTerms(typ)
case *typeparams.Union:
return typeparams.UnionTermSet(typ)
case *types.Interface:
return typeparams.InterfaceTermSet(typ)
default:
return []*typeparams.Term{typeparams.NewTerm(false, typ)}, nil
}
}
// typeSetOf returns the type set of typ. Returns an empty typeset on an error.
func typeSetOf(typ types.Type) typeSet {
terms, err := _NormalTerms(typ)
if err != nil {
return nil
}
return terms
}
type typeSet []*typeparams.Term // type terms of the type set
// 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 (s typeSet) underIs(f func(types.Type) bool) bool {
if len(s) == 0 {
return f(nil)
}
for _, t := range s {
u := t.Type().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:
terms, err := _NormalTerms(U)
if len(terms) == 0 || err != nil {
return nil, ixInvalid // no underlying terms or error is empty.
}
elem, mode := indexType(terms[0].Type())
for i := 1; i < len(terms) && mode != ixInvalid; i++ {
e, m := indexType(terms[i].Type())
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
}

105
go/ssa/coretype_test.go Normal file
View File

@ -0,0 +1,105 @@
// 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/ast"
"go/parser"
"go/token"
"go/types"
"testing"
"golang.org/x/tools/internal/typeparams"
)
func TestCoreType(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestCoreType requires type parameters.")
}
const source = `
package P
type Named int
type A any
type B interface{~int}
type C interface{int}
type D interface{Named}
type E interface{~int|interface{Named}}
type F interface{~int|~float32}
type G interface{chan int|interface{chan int}}
type H interface{chan int|chan float32}
type I interface{chan<- int|chan int}
type J interface{chan int|chan<- int}
type K interface{<-chan int|chan int}
type L interface{chan int|<-chan int}
type M interface{chan int|chan Named}
type N interface{<-chan int|chan<- int}
type O interface{chan int|bool}
type P struct{ Named }
type Q interface{ Foo() }
type R interface{ Foo() ; Named }
type S interface{ Foo() ; ~int }
type T interface{chan int|interface{chan int}|<-chan int}
`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "hello.go", source, 0)
if err != nil {
t.Fatal(err)
}
var conf types.Config
pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
expr string // type expression of Named type
want string // expected core type (or "<nil>" if none)
}{
{"Named", "int"}, // Underlying type is not interface.
{"A", "<nil>"}, // Interface has no terms.
{"B", "int"}, // Tilde term.
{"C", "int"}, // Non-tilde term.
{"D", "int"}, // Named term.
{"E", "int"}, // Identical underlying types.
{"F", "<nil>"}, // Differing underlying types.
{"G", "chan int"}, // Identical Element types.
{"H", "<nil>"}, // Element type int has differing underlying type to float32.
{"I", "chan<- int"}, // SendRecv followed by SendOnly
{"J", "chan<- int"}, // SendOnly followed by SendRecv
{"K", "<-chan int"}, // RecvOnly followed by SendRecv
{"L", "<-chan int"}, // SendRecv followed by RecvOnly
{"M", "<nil>"}, // Element type int is not *identical* to Named.
{"N", "<nil>"}, // Differing channel directions
{"O", "<nil>"}, // A channel followed by a non-channel.
{"P", "struct{P.Named}"}, // Embedded type.
{"Q", "<nil>"}, // interface type with no terms and functions
{"R", "int"}, // interface type with both terms and functions.
{"S", "int"}, // interface type with a tilde term
{"T", "<-chan int"}, // Prefix of 2 terms that are identical before switching to channel.
} {
// Eval() expr for its type.
tv, err := types.Eval(fset, pkg, 0, test.expr)
if err != nil {
t.Fatalf("Eval(%s) failed: %v", test.expr, err)
}
ct := coreType(tv.Type)
var got string
if ct == nil {
got = "<nil>"
} else {
got = ct.String()
}
if got != test.want {
t.Errorf("coreType(%s) = %v, want %v", test.expr, got, test.want)
}
}
}

View File

@ -91,37 +91,31 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
}
// Collect type parameters if this is a generic function/method.
var tparams []*typeparams.TypeParam
for i, rtparams := 0, typeparams.RecvTypeParams(sig); i < rtparams.Len(); i++ {
tparams = append(tparams, rtparams.At(i))
}
for i, sigparams := 0, typeparams.ForSignature(sig); i < sigparams.Len(); i++ {
tparams = append(tparams, sigparams.At(i))
var tparams *typeparams.TypeParamList
if rtparams := typeparams.RecvTypeParams(sig); rtparams.Len() > 0 {
tparams = rtparams
} else if sigparams := typeparams.ForSignature(sig); sigparams.Len() > 0 {
tparams = sigparams
}
fn := &Function{
name: name,
object: obj,
Signature: sig,
syntax: syntax,
pos: obj.Pos(),
Pkg: pkg,
Prog: pkg.Prog,
_TypeParams: tparams,
info: pkg.info,
name: name,
object: obj,
Signature: sig,
syntax: syntax,
pos: obj.Pos(),
Pkg: pkg,
Prog: pkg.Prog,
typeparams: tparams,
info: pkg.info,
}
pkg.created.Add(fn)
if syntax == nil {
fn.Synthetic = "loaded from gc object file"
}
if len(tparams) > 0 {
if tparams.Len() > 0 {
fn.Prog.createInstanceSet(fn)
}
if len(tparams) > 0 && syntax != nil {
fn.Synthetic = "generic function"
// TODO(taking): Allow for the function to be built once type params are supported.
fn.syntax = nil // Treating as an external function temporarily.
}
pkg.objects[obj] = fn
if sig.Recv() == nil {

View File

@ -121,9 +121,9 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value {
if types.Identical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
} else if isNonTypeParamInterface(x.Type()) {
y = emitConv(f, y, x.Type())
} else if _, ok := yt.(*types.Interface); ok {
} else if isNonTypeParamInterface(y.Type()) {
x = emitConv(f, x, y.Type())
} else if _, ok := x.(*Const); ok {
x = emitConv(f, x, y.Type())
@ -166,6 +166,32 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool {
return false
}
// isSliceToArrayPointer reports whether ut_src is a slice type
// that can be converted to a pointer to an array type ut_dst.
// Precondition: neither argument is a named type.
func isSliceToArrayPointer(ut_src, ut_dst types.Type) bool {
if slice, ok := ut_src.(*types.Slice); ok {
if ptr, ok := ut_dst.(*types.Pointer); ok {
if arr, ok := ptr.Elem().Underlying().(*types.Array); ok {
return types.Identical(slice.Elem(), arr.Elem())
}
}
}
return false
}
// isSliceToArray reports whether ut_src is a slice type
// that can be converted to an array type ut_dst.
// Precondition: neither argument is a named type.
func isSliceToArray(ut_src, ut_dst types.Type) bool {
if slice, ok := ut_src.(*types.Slice); ok {
if arr, ok := ut_dst.(*types.Array); ok {
return types.Identical(slice.Elem(), arr.Elem())
}
}
return false
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are required
// by language assignability rules in assignments, parameter passing,
@ -180,17 +206,25 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
ut_dst := typ.Underlying()
ut_src := t_src.Underlying()
dst_types := typeSetOf(ut_dst)
src_types := typeSetOf(ut_src)
// Just a change of type, but not value or representation?
if isValuePreserving(ut_src, ut_dst) {
preserving := src_types.underIs(func(s types.Type) bool {
return dst_types.underIs(func(d types.Type) bool {
return s != nil && d != nil && isValuePreserving(s, d) // all (s -> d) are value preserving.
})
})
if preserving {
c := &ChangeType{X: val}
c.setType(typ)
return f.emit(c)
}
// Conversion to, or construction of a value of, an interface type?
if _, ok := ut_dst.(*types.Interface); ok {
if isNonTypeParamInterface(typ) {
// Assignment from one interface type to another?
if _, ok := ut_src.(*types.Interface); ok {
if isNonTypeParamInterface(t_src) {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c)
@ -198,7 +232,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
// Untyped nil constant? Return interface-typed nil constant.
if ut_src == tUntypedNil {
return nilConst(typ)
return zeroConst(typ)
}
// Convert (non-nil) "untyped" literals to their default type.
@ -213,7 +247,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
// Conversion of a compile-time constant value?
if c, ok := val.(*Const); ok {
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
if isBasic(ut_dst) || c.Value == nil {
// Conversion of a compile-time constant to
// another constant type results in a new
// constant of the destination type and
@ -227,41 +261,30 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
}
// Conversion from slice to array pointer?
if slice, ok := ut_src.(*types.Slice); ok {
switch t := ut_dst.(type) {
case *types.Pointer:
ptr := t
if arr, ok := ptr.Elem().Underlying().(*types.Array); ok && types.Identical(slice.Elem(), arr.Elem()) {
c := &SliceToArrayPointer{X: val}
// TODO(taking): Check if this should be ut_dst or ptr.
c.setType(ptr)
return f.emit(c)
}
case *types.Array:
arr := t
if arr.Len() == 0 {
return zeroValue(f, arr)
}
if types.Identical(slice.Elem(), arr.Elem()) {
c := &SliceToArrayPointer{X: val}
c.setType(types.NewPointer(arr))
x := f.emit(c)
unOp := &UnOp{
Op: token.MUL,
X: x,
CommaOk: false,
}
unOp.setType(typ)
return f.emit(unOp)
}
}
slice2ptr := src_types.underIs(func(s types.Type) bool {
return dst_types.underIs(func(d types.Type) bool {
return s != nil && d != nil && isSliceToArrayPointer(s, d) // all (s->d) are slice to array pointer conversion.
})
})
if slice2ptr {
c := &SliceToArrayPointer{X: val}
c.setType(typ)
return f.emit(c)
}
// Conversion from slice to array?
slice2array := src_types.underIs(func(s types.Type) bool {
return dst_types.underIs(func(d types.Type) bool {
return s != nil && d != nil && isSliceToArray(s, d) // all (s->d) are slice to array conversion.
})
})
if slice2array {
return emitSliceToArray(f, val, typ)
}
// A representation-changing conversion?
// At least one of {ut_src,ut_dst} must be *Basic.
// (The other may be []byte or []rune.)
_, ok1 := ut_src.(*types.Basic)
_, ok2 := ut_dst.(*types.Basic)
if ok1 || ok2 {
// All of ut_src or ut_dst is basic, byte slice, or rune slice?
if isBasicConvTypes(src_types) || isBasicConvTypes(dst_types) {
c := &Convert{X: val}
c.setType(typ)
return f.emit(c)
@ -270,6 +293,33 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
}
// emitTypeCoercion emits to f code to coerce the type of a
// Value v to exactly type typ, and returns the coerced value.
//
// Requires that coercing v.Typ() to typ is a value preserving change.
//
// Currently used only when v.Type() is a type instance of typ or vice versa.
// A type v is a type instance of a type t if there exists a
// type parameter substitution σ s.t. σ(v) == t. Example:
//
// σ(func(T) T) == func(int) int for σ == [T ↦ int]
//
// This happens in instantiation wrappers for conversion
// from an instantiation to a parameterized type (and vice versa)
// with σ substituting f.typeparams by f.typeargs.
func emitTypeCoercion(f *Function, v Value, typ types.Type) Value {
if types.Identical(v.Type(), typ) {
return v // no coercion needed
}
// TODO(taking): for instances should we record which side is the instance?
c := &ChangeType{
X: v,
}
c.setType(typ)
f.emit(c)
return c
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignability rules.
func emitStore(f *Function, addr, val Value, pos token.Pos) *Store {
@ -378,7 +428,7 @@ func emitTailCall(f *Function, call *Call) {
// value of a field.
func emitImplicitSelections(f *Function, v Value, indices []int, pos token.Pos) Value {
for _, index := range indices {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
fld := coreType(deref(v.Type())).(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
@ -412,7 +462,7 @@ func emitImplicitSelections(f *Function, v Value, indices []int, pos token.Pos)
// field's value.
// Ident id is used for position and debug info.
func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value {
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
fld := coreType(deref(v.Type())).(*types.Struct).Field(index)
if isPointer(v.Type()) {
instr := &FieldAddr{
X: v,
@ -438,6 +488,48 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.
return v
}
// emitSliceToArray emits to f code to convert a slice value to an array value.
//
// Precondition: all types in type set of typ are arrays and convertible to all
// types in the type set of val.Type().
func emitSliceToArray(f *Function, val Value, typ types.Type) Value {
// Emit the following:
// if val == nil && len(typ) == 0 {
// ptr = &[0]T{}
// } else {
// ptr = SliceToArrayPointer(val)
// }
// v = *ptr
ptype := types.NewPointer(typ)
p := &SliceToArrayPointer{X: val}
p.setType(ptype)
ptr := f.emit(p)
nilb := f.newBasicBlock("slicetoarray.nil")
nonnilb := f.newBasicBlock("slicetoarray.nonnil")
done := f.newBasicBlock("slicetoarray.done")
cond := emitCompare(f, token.EQL, ptr, zeroConst(ptype), token.NoPos)
emitIf(f, cond, nilb, nonnilb)
f.currentBlock = nilb
zero := f.addLocal(typ, token.NoPos)
emitJump(f, done)
f.currentBlock = nonnilb
emitJump(f, done)
f.currentBlock = done
phi := &Phi{Edges: []Value{zero, ptr}, Comment: "slicetoarray"}
phi.pos = val.Pos()
phi.setType(typ)
x := f.emit(phi)
unOp := &UnOp{Op: token.MUL, X: x}
unOp.setType(typ)
return f.emit(unOp)
}
// zeroValue emits to f code to produce a zero value of type t,
// and returns it.
func zeroValue(f *Function, t types.Type) Value {

View File

@ -251,7 +251,10 @@ func buildReferrers(f *Function) {
}
// mayNeedRuntimeTypes returns all of the types in the body of fn that might need runtime types.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
func mayNeedRuntimeTypes(fn *Function) []types.Type {
// Collect all types that may need rtypes, i.e. those that flow into an interface.
var ts []types.Type
for _, bb := range fn.Blocks {
for _, instr := range bb.Instrs {
@ -260,7 +263,21 @@ func mayNeedRuntimeTypes(fn *Function) []types.Type {
}
}
}
return ts
// Types that contain a parameterized type are considered to not be runtime types.
if fn.typeparams.Len() == 0 {
return ts // No potentially parameterized types.
}
// Filter parameterized types, in place.
fn.Prog.methodsMu.Lock()
defer fn.Prog.methodsMu.Unlock()
filtered := ts[:0]
for _, t := range ts {
if !fn.Prog.parameterized.isParameterized(t) {
filtered = append(filtered, t)
}
}
return filtered
}
// finishBody() finalizes the contents of the function after SSA code generation of its body.
@ -518,8 +535,8 @@ func (fn *Function) declaredPackage() *Package {
switch {
case fn.Pkg != nil:
return fn.Pkg // non-generic function
case fn._Origin != nil:
return fn._Origin.Pkg // instance of a named generic function
case fn.topLevelOrigin != nil:
return fn.topLevelOrigin.Pkg // instance of a named generic function
case fn.parent != nil:
return fn.parent.declaredPackage() // instance of an anonymous [generic] function
default:

View File

@ -18,7 +18,7 @@ import (
//
// This is an experimental interface! It may change without warning.
func (prog *Program) _Instances(fn *Function) []*Function {
if len(fn._TypeParams) == 0 {
if fn.typeparams.Len() == 0 || len(fn.typeargs) > 0 {
return nil
}
@ -29,7 +29,7 @@ func (prog *Program) _Instances(fn *Function) []*Function {
// A set of instantiations of a generic function fn.
type instanceSet struct {
fn *Function // len(fn._TypeParams) > 0 and len(fn._TypeArgs) == 0.
fn *Function // fn.typeparams.Len() > 0 and len(fn.typeargs) == 0.
instances map[*typeList]*Function // canonical type arguments to an instance.
syntax *ast.FuncDecl // fn.syntax copy for instantiating after fn is done. nil on synthetic packages.
info *types.Info // fn.pkg.info copy for building after fn is done.. nil on synthetic packages.
@ -56,7 +56,7 @@ func (insts *instanceSet) list() []*Function {
//
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodMu)
func (prog *Program) createInstanceSet(fn *Function) {
assert(len(fn._TypeParams) > 0 && len(fn._TypeArgs) == 0, "Can only create instance sets for generic functions")
assert(fn.typeparams.Len() > 0 && len(fn.typeargs) == 0, "Can only create instance sets for generic functions")
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
@ -73,7 +73,7 @@ func (prog *Program) createInstanceSet(fn *Function) {
}
}
// needsInstance returns an Function that that is the instantiation of fn with the type arguments targs.
// needsInstance returns a Function that is the instantiation of fn with the type arguments targs.
//
// Any CREATEd instance is added to cr.
//
@ -82,41 +82,45 @@ func (prog *Program) needsInstance(fn *Function, targs []types.Type, cr *creator
prog.methodsMu.Lock()
defer prog.methodsMu.Unlock()
return prog.instances[fn].lookupOrCreate(targs, cr)
return prog.lookupOrCreateInstance(fn, targs, cr)
}
// lookupOrCreateInstance returns a Function that is the instantiation of fn with the type arguments targs.
//
// Any CREATEd instance is added to cr.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodMu)
func (prog *Program) lookupOrCreateInstance(fn *Function, targs []types.Type, cr *creator) *Function {
return prog.instances[fn].lookupOrCreate(targs, &prog.parameterized, cr)
}
// lookupOrCreate returns the instantiation of insts.fn using targs.
// If the instantiation is reported, this is added to cr.
func (insts *instanceSet) lookupOrCreate(targs []types.Type, cr *creator) *Function {
// If the instantiation is created, this is added to cr.
func (insts *instanceSet) lookupOrCreate(targs []types.Type, parameterized *tpWalker, cr *creator) *Function {
if insts.instances == nil {
insts.instances = make(map[*typeList]*Function)
}
fn := insts.fn
prog := fn.Prog
// canonicalize on a tuple of targs. Sig is not unique.
//
// func A[T any]() {
// var x T
// fmt.Println("%T", x)
// }
key := insts.fn.Prog.canon.List(targs)
key := prog.canon.List(targs)
if inst, ok := insts.instances[key]; ok {
return inst
}
// CREATE instance/instantiation wrapper
var syntax ast.Node
if insts.syntax != nil {
syntax = insts.syntax
}
instance := createInstance(insts.fn, targs, insts.info, syntax, cr)
insts.instances[key] = instance
return instance
}
// createInstance returns an CREATEd instantiation of fn using targs.
//
// Function is added to cr.
func createInstance(fn *Function, targs []types.Type, info *types.Info, syntax ast.Node, cr *creator) *Function {
prog := fn.Prog
var sig *types.Signature
var obj *types.Func
if recv := fn.Signature.Recv(); recv != nil {
@ -137,25 +141,36 @@ func createInstance(fn *Function, targs []types.Type, info *types.Info, syntax a
sig = prog.canon.Type(instance).(*types.Signature)
}
var synthetic string
var subst *subster
concrete := !parameterized.anyParameterized(targs)
if prog.mode&InstantiateGenerics != 0 && concrete {
synthetic = fmt.Sprintf("instance of %s", fn.Name())
subst = makeSubster(prog.ctxt, fn.typeparams, targs, false)
} else {
synthetic = fmt.Sprintf("instantiation wrapper of %s", fn.Name())
}
name := fmt.Sprintf("%s%s", fn.Name(), targs) // may not be unique
synthetic := fmt.Sprintf("instantiation of %s", fn.Name())
instance := &Function{
name: name,
object: obj,
Signature: sig,
Synthetic: synthetic,
_Origin: fn,
pos: obj.Pos(),
Pkg: nil,
Prog: fn.Prog,
_TypeParams: fn._TypeParams,
_TypeArgs: targs,
info: info, // on synthetic packages info is nil.
subst: makeSubster(prog.ctxt, fn._TypeParams, targs, false),
}
if prog.mode&InstantiateGenerics != 0 {
instance.syntax = syntax // otherwise treat instance as an external function.
name: name,
object: obj,
Signature: sig,
Synthetic: synthetic,
syntax: syntax,
topLevelOrigin: fn,
pos: obj.Pos(),
Pkg: nil,
Prog: fn.Prog,
typeparams: fn.typeparams, // share with origin
typeargs: targs,
info: insts.info, // on synthetic packages info is nil.
subst: subst,
}
cr.Add(instance)
insts.instances[key] = instance
return instance
}

View File

@ -4,19 +4,52 @@
package ssa
// Note: Tests use unexported functions.
// Note: Tests use unexported method _Instances.
import (
"bytes"
"fmt"
"go/types"
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/internal/typeparams"
)
// loadProgram creates loader.Program out of p.
func loadProgram(p string) (*loader.Program, error) {
// Parse
var conf loader.Config
f, err := conf.ParseFile("<input>", p)
if err != nil {
return nil, fmt.Errorf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
return nil, fmt.Errorf("Load: %v", err)
}
return lprog, nil
}
// buildPackage builds and returns ssa representation of package pkg of lprog.
func buildPackage(lprog *loader.Program, pkg string, mode BuilderMode) *Package {
prog := NewProgram(lprog.Fset, mode)
for _, info := range lprog.AllPackages {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
p := prog.Package(lprog.Package(pkg).Pkg)
p.Build()
return p
}
// TestNeedsInstance ensures that new method instances can be created via needsInstance,
// that TypeArgs are as expected, and can be accessed via _Instances.
func TestNeedsInstance(t *testing.T) {
@ -45,30 +78,15 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
// func init func()
// var init$guard bool
// Parse
var conf loader.Config
f, err := conf.ParseFile("<input>", input)
if err != nil {
t.Fatalf("parse: %v", err)
}
conf.CreateFromFiles("p", f)
// Load
lprog, err := conf.Load()
if err != nil {
t.Fatalf("Load: %v", err)
lprog, err := loadProgram(input)
if err != err {
t.Fatal(err)
}
for _, mode := range []BuilderMode{BuilderMode(0), InstantiateGenerics} {
// Create and build SSA
prog := NewProgram(lprog.Fset, mode)
for _, info := range lprog.AllPackages {
prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
}
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
p := buildPackage(lprog, "p", mode)
prog := p.Prog
ptr := p.Type("Pointer").Type().(*types.Named)
if ptr.NumMethods() != 1 {
@ -88,11 +106,11 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
if len(cr) != 1 {
t.Errorf("Expected first instance to create a function. got %d created functions", len(cr))
}
if instance._Origin != meth {
t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance._Origin)
if instance.Origin() != meth {
t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance.Origin())
}
if len(instance._TypeArgs) != 1 || !types.Identical(instance._TypeArgs[0], intSliceTyp) {
t.Errorf("Expected TypeArgs of %s to be %v. got %v", instance, []types.Type{intSliceTyp}, instance._TypeArgs)
if len(instance.TypeArgs()) != 1 || !types.Identical(instance.TypeArgs()[0], intSliceTyp) {
t.Errorf("Expected TypeArgs of %s to be %v. got %v", instance, []types.Type{intSliceTyp}, instance.typeargs)
}
instances := prog._Instances(meth)
if want := []*Function{instance}; !reflect.DeepEqual(instances, want) {
@ -126,3 +144,218 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
}
}
}
// TestCallsToInstances checks that calles of calls to generic functions,
// without monomorphization, are wrappers around the origin generic function.
func TestCallsToInstances(t *testing.T) {
if !typeparams.Enabled {
return
}
const input = `
package p
type I interface {
Foo()
}
type A int
func (a A) Foo() {}
type J[T any] interface{ Bar() T }
type K[T any] struct{ J[T] }
func Id[T any] (t T) T {
return t
}
func Lambda[T I]() func() func(T) {
return func() func(T) {
return T.Foo
}
}
func NoOp[T any]() {}
func Bar[T interface { Foo(); ~int | ~string }, U any] (t T, u U) {
Id[U](u)
Id[T](t)
}
func Make[T any]() interface{} {
NoOp[K[T]]()
return nil
}
func entry(i int, a A) int {
Lambda[A]()()(a)
x := Make[int]()
if j, ok := x.(interface{ Bar() int }); ok {
print(j)
}
Bar[A, int](a, i)
return Id[int](i)
}
`
lprog, err := loadProgram(input)
if err != err {
t.Fatal(err)
}
p := buildPackage(lprog, "p", SanityCheckFunctions)
prog := p.Prog
for _, ti := range []struct {
orig string
instance string
tparams string
targs string
chTypeInstrs int // number of ChangeType instructions in f's body
}{
{"Id", "Id[int]", "[T]", "[int]", 2},
{"Lambda", "Lambda[p.A]", "[T]", "[p.A]", 1},
{"Make", "Make[int]", "[T]", "[int]", 0},
{"NoOp", "NoOp[p.K[T]]", "[T]", "[p.K[T]]", 0},
} {
test := ti
t.Run(test.instance, func(t *testing.T) {
f := p.Members[test.orig].(*Function)
if f == nil {
t.Fatalf("origin function not found")
}
i := instanceOf(f, test.instance, prog)
if i == nil {
t.Fatalf("instance not found")
}
// for logging on failures
var body strings.Builder
i.WriteTo(&body)
t.Log(body.String())
if len(i.Blocks) != 1 {
t.Fatalf("body has more than 1 block")
}
if instrs := changeTypeInstrs(i.Blocks[0]); instrs != test.chTypeInstrs {
t.Errorf("want %v instructions; got %v", test.chTypeInstrs, instrs)
}
if test.tparams != tparams(i) {
t.Errorf("want %v type params; got %v", test.tparams, tparams(i))
}
if test.targs != targs(i) {
t.Errorf("want %v type arguments; got %v", test.targs, targs(i))
}
})
}
}
func instanceOf(f *Function, name string, prog *Program) *Function {
for _, i := range prog._Instances(f) {
if i.Name() == name {
return i
}
}
return nil
}
func tparams(f *Function) string {
tplist := f.TypeParams()
var tps []string
for i := 0; i < tplist.Len(); i++ {
tps = append(tps, tplist.At(i).String())
}
return fmt.Sprint(tps)
}
func targs(f *Function) string {
var tas []string
for _, ta := range f.TypeArgs() {
tas = append(tas, ta.String())
}
return fmt.Sprint(tas)
}
func changeTypeInstrs(b *BasicBlock) int {
cnt := 0
for _, i := range b.Instrs {
if _, ok := i.(*ChangeType); ok {
cnt++
}
}
return cnt
}
func TestInstanceUniqueness(t *testing.T) {
if !typeparams.Enabled {
return
}
const input = `
package p
func H[T any](t T) {
print(t)
}
func F[T any](t T) {
H[T](t)
H[T](t)
H[T](t)
}
func G[T any](t T) {
H[T](t)
H[T](t)
}
func Foo[T any, S any](t T, s S) {
Foo[S, T](s, t)
Foo[T, S](t, s)
}
`
lprog, err := loadProgram(input)
if err != err {
t.Fatal(err)
}
p := buildPackage(lprog, "p", SanityCheckFunctions)
prog := p.Prog
for _, test := range []struct {
orig string
instances string
}{
{"H", "[p.H[T] p.H[T]]"},
{"Foo", "[p.Foo[S T] p.Foo[T S]]"},
} {
t.Run(test.orig, func(t *testing.T) {
f := p.Members[test.orig].(*Function)
if f == nil {
t.Fatalf("origin function not found")
}
instances := prog._Instances(f)
sort.Slice(instances, func(i, j int) bool { return instances[i].Name() < instances[j].Name() })
if got := fmt.Sprintf("%v", instances); !reflect.DeepEqual(got, test.instances) {
t.Errorf("got %v instances, want %v", got, test.instances)
}
})
}
}
// instancesStr returns a sorted slice of string
// representation of instances.
func instancesStr(instances []*Function) []string {
var is []string
for _, i := range instances {
is = append(is, fmt.Sprintf("%v", i))
}
sort.Strings(is)
return is
}

View File

@ -51,7 +51,6 @@ import (
"os"
"reflect"
"runtime"
"strings"
"sync/atomic"
"golang.org/x/tools/go/ssa"
@ -335,7 +334,17 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
}
case *ssa.Index:
fr.env[instr] = fr.get(instr.X).(array)[asInt64(fr.get(instr.Index))]
x := fr.get(instr.X)
idx := fr.get(instr.Index)
switch x := x.(type) {
case array:
fr.env[instr] = x[asInt64(idx)]
case string:
fr.env[instr] = x[asInt64(idx)]
default:
panic(fmt.Sprintf("unexpected x type in Index: %T", x))
}
case *ssa.Lookup:
fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index))
@ -506,13 +515,15 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function,
return ext(fr, args)
}
if fn.Blocks == nil {
var reason string // empty by default
if strings.HasPrefix(fn.Synthetic, "instantiation") {
reason = " (interp requires ssa.BuilderMode to include InstantiateGenerics on generics)"
}
panic("no code for function: " + name + reason)
panic("no code for function: " + name)
}
}
// generic function body?
if fn.TypeParams().Len() > 0 && len(fn.TypeArgs()) == 0 {
panic("interp requires ssa.BuilderMode to include InstantiateGenerics to execute generics")
}
fr.env = make(map[ssa.Value]value)
fr.block = fn.Blocks[0]
fr.locals = make([]value, len(fn.Locals))

View File

@ -127,12 +127,14 @@ var testdataTests = []string{
"width32.go",
"fixedbugs/issue52342.go",
"fixedbugs/issue55086.go",
}
func init() {
if typeparams.Enabled {
testdataTests = append(testdataTests, "fixedbugs/issue52835.go")
testdataTests = append(testdataTests, "fixedbugs/issue55086.go")
testdataTests = append(testdataTests, "typeassert.go")
testdataTests = append(testdataTests, "zeros.go")
}
}

View File

@ -34,9 +34,10 @@ type exitPanic int
// constValue returns the value of the constant with the
// dynamic type tag appropriate for c.Type().
func constValue(c *ssa.Const) value {
if c.IsNil() {
return zero(c.Type()) // typed nil
if c.Value == nil {
return zero(c.Type()) // typed zero
}
// c is not a type parameter so it's underlying type is basic.
if t, ok := c.Type().Underlying().(*types.Basic); ok {
// TODO(adonovan): eliminate untyped constants from SSA form.
@ -307,7 +308,7 @@ func slice(x, lo, hi, max value) value {
panic(fmt.Sprintf("slice: unexpected X type: %T", x))
}
// lookup returns x[idx] where x is a map or string.
// lookup returns x[idx] where x is a map.
func lookup(instr *ssa.Lookup, x, idx value) value {
switch x := x.(type) { // map or string
case map[value]value, *hashmap:
@ -327,8 +328,6 @@ func lookup(instr *ssa.Lookup, x, idx value) value {
v = tuple{v, ok}
}
return v
case string:
return x[asInt64(idx)]
}
panic(fmt.Sprintf("unexpected x type in Lookup: %T", x))
}

View File

@ -123,7 +123,8 @@ func nilInterfaceMethodValue() {
r := fmt.Sprint(recover())
// runtime panic string varies across toolchains
if r != "interface conversion: interface is nil, not error" &&
r != "runtime error: invalid memory address or nil pointer dereference" {
r != "runtime error: invalid memory address or nil pointer dereference" &&
r != "method value: interface is nil" {
panic("want runtime panic from nil interface method value, got " + r)
}
}()

View File

@ -19,7 +19,7 @@ func main() {
{
var s []int
a:= ([0]int)(s)
a := ([0]int)(s)
if a != [0]int{} {
panic("zero len array is not equal")
}
@ -31,6 +31,20 @@ func main() {
if !threeToFourDoesPanic() {
panic("panic expected from threeToFourDoesPanic()")
}
if !fourPanicsWhileOneDoesNot[[4]int]() {
panic("panic expected from fourPanicsWhileOneDoesNot[[4]int]()")
}
if fourPanicsWhileOneDoesNot[[1]int]() {
panic("no panic expected from fourPanicsWhileOneDoesNot[[1]int]()")
}
if !fourPanicsWhileZeroDoesNot[[4]int]() {
panic("panic expected from fourPanicsWhileZeroDoesNot[[4]int]()")
}
if fourPanicsWhileZeroDoesNot[[0]int]() {
panic("no panic expected from fourPanicsWhileZeroDoesNot[[0]int]()")
}
}
func emptyToEmptyDoesNotPanic() (raised bool) {
@ -53,4 +67,26 @@ func threeToFourDoesPanic() (raised bool) {
s := make([]int, 3, 5)
_ = ([4]int)(s)
return false
}
}
func fourPanicsWhileOneDoesNot[T [1]int | [4]int]() (raised bool) {
defer func() {
if e := recover(); e != nil {
raised = true
}
}()
s := make([]int, 3, 5)
_ = T(s)
return false
}
func fourPanicsWhileZeroDoesNot[T [0]int | [4]int]() (raised bool) {
defer func() {
if e := recover(); e != nil {
raised = true
}
}()
var s []int
_ = T(s)
return false
}

32
go/ssa/interp/testdata/typeassert.go vendored Normal file
View File

@ -0,0 +1,32 @@
// Tests of type asserts.
// Requires type parameters.
package typeassert
type fooer interface{ foo() string }
type X int
func (_ X) foo() string { return "x" }
func f[T fooer](x T) func() string {
return x.foo
}
func main() {
if f[X](0)() != "x" {
panic("f[X]() != 'x'")
}
p := false
func() {
defer func() {
if recover() != nil {
p = true
}
}()
f[fooer](nil) // panics on x.foo when T is an interface and nil.
}()
if !p {
panic("f[fooer] did not panic")
}
}

45
go/ssa/interp/testdata/zeros.go vendored Normal file
View File

@ -0,0 +1,45 @@
// 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.
// Test interpretation on zero values with type params.
package zeros
func assert(cond bool, msg string) {
if !cond {
panic(msg)
}
}
func tp0[T int | string | float64]() T { return T(0) }
func tpFalse[T ~bool]() T { return T(false) }
func tpEmptyString[T string | []byte]() T { return T("") }
func tpNil[T *int | []byte]() T { return T(nil) }
func main() {
// zero values
var zi int
var zf float64
var zs string
assert(zi == int(0), "zero value of int is int(0)")
assert(zf == float64(0), "zero value of float64 is float64(0)")
assert(zs != string(0), "zero value of string is not string(0)")
assert(zi == tp0[int](), "zero value of int is int(0)")
assert(zf == tp0[float64](), "zero value of float64 is float64(0)")
assert(zs != tp0[string](), "zero value of string is not string(0)")
assert(zf == -0.0, "constant -0.0 is converted to 0.0")
assert(!tpFalse[bool](), "zero value of bool is false")
assert(tpEmptyString[string]() == zs, `zero value of string is string("")`)
assert(len(tpEmptyString[[]byte]()) == 0, `[]byte("") is empty`)
assert(tpNil[*int]() == nil, "nil is nil")
assert(tpNil[[]byte]() == nil, "nil is nil")
}

View File

@ -44,6 +44,8 @@ import (
"go/types"
"math/big"
"os"
"golang.org/x/tools/internal/typeparams"
)
// If true, show diagnostic information at each step of lifting.
@ -381,10 +383,9 @@ type newPhiMap map[*BasicBlock][]newPhi
//
// fresh is a source of fresh ids for phi nodes.
func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool {
// Don't lift aggregates into registers, because we don't have
// a way to express their zero-constants.
// TODO(taking): zero constants of aggregated types can now be lifted.
switch deref(alloc.Type()).Underlying().(type) {
case *types.Array, *types.Struct:
case *types.Array, *types.Struct, *typeparams.TypeParam:
return false
}

View File

@ -56,12 +56,12 @@ func (a *address) typ() types.Type {
}
// An element is an lvalue represented by m[k], the location of an
// element of a map or string. These locations are not addressable
// element of a map. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load(), and in the case of maps, store().
// load() and store().
type element struct {
m, k Value // map or string
t types.Type // map element type or string byte type
m, k Value // map
t types.Type // map element type
pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v)
}
@ -86,7 +86,7 @@ func (e *element) store(fn *Function, v Value) {
}
func (e *element) address(fn *Function) Value {
panic("map/string elements are not addressable")
panic("map elements are not addressable")
}
func (e *element) typ() types.Type {

View File

@ -27,8 +27,8 @@ func (prog *Program) MethodValue(sel *types.Selection) *Function {
panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel))
}
T := sel.Recv()
if isInterface(T) {
return nil // abstract method (interface)
if types.IsInterface(T) {
return nil // abstract method (interface, possibly type param)
}
if prog.mode&LogSource != 0 {
defer logStack("MethodValue %s %v", T, sel)()
@ -76,7 +76,7 @@ type methodSet struct {
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) createMethodSet(T types.Type) *methodSet {
if prog.mode&SanityCheckFunctions != 0 {
if isInterface(T) || prog.parameterized.isParameterized(T) {
if types.IsInterface(T) || prog.parameterized.isParameterized(T) {
panic("type is interface or parameterized")
}
}
@ -107,9 +107,9 @@ func (prog *Program) addMethod(mset *methodSet, sel *types.Selection, cr *creato
fn = makeWrapper(prog, sel, cr)
} else {
fn = prog.originFunc(obj)
if len(fn._TypeParams) > 0 { // instantiate
if fn.typeparams.Len() > 0 { // instantiate
targs := receiverTypeArgs(obj)
fn = prog.instances[fn].lookupOrCreate(targs, cr)
fn = prog.lookupOrCreateInstance(fn, targs, cr)
}
}
if fn.Signature.Recv() == nil {
@ -190,7 +190,7 @@ func (prog *Program) needMethods(T types.Type, skip bool, cr *creator) {
tmset := prog.MethodSets.MethodSet(T)
if !skip && !isInterface(T) && tmset.Len() > 0 {
if !skip && !types.IsInterface(T) && tmset.Len() > 0 {
// Create methods of T.
mset := prog.createMethodSet(T)
if !mset.complete {

View File

@ -111,3 +111,12 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
return false
}
func (w *tpWalker) anyParameterized(ts []types.Type) bool {
for _, t := range ts {
if w.isParameterized(t) {
return true
}
}
return false
}

View File

@ -232,7 +232,7 @@ func (v *MakeChan) String() string {
}
func (v *FieldAddr) String() string {
st := deref(v.X.Type()).Underlying().(*types.Struct)
st := coreType(deref(v.X.Type())).(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {
@ -242,7 +242,7 @@ func (v *FieldAddr) String() string {
}
func (v *Field) String() string {
st := v.X.Type().Underlying().(*types.Struct)
st := coreType(v.X.Type()).(*types.Struct)
// Be robust against a bad index.
name := "?"
if 0 <= v.Field && v.Field < st.NumFields() {

View File

@ -132,9 +132,9 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *ChangeType:
case *SliceToArrayPointer:
case *Convert:
if _, ok := instr.X.Type().Underlying().(*types.Basic); !ok {
if _, ok := instr.Type().Underlying().(*types.Basic); !ok {
s.errorf("convert %s -> %s: at least one type must be basic", instr.X.Type(), instr.Type())
if from := instr.X.Type(); !isBasicConvTypes(typeSetOf(from)) {
if to := instr.Type(); !isBasicConvTypes(typeSetOf(to)) {
s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to)
}
}
@ -403,7 +403,7 @@ func (s *sanity) checkFunction(fn *Function) bool {
// - check transient fields are nil
// - warn if any fn.Locals do not appear among block instructions.
// TODO(taking): Sanity check _Origin, _TypeParams, and _TypeArgs.
// TODO(taking): Sanity check origin, typeparams, and typeargs.
s.fn = fn
if fn.Prog == nil {
s.errorf("nil Prog")
@ -420,16 +420,19 @@ func (s *sanity) checkFunction(fn *Function) bool {
strings.HasPrefix(fn.Synthetic, "bound ") ||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
strings.HasSuffix(fn.name, "Error") ||
strings.HasPrefix(fn.Synthetic, "instantiation") ||
(fn.parent != nil && len(fn._TypeArgs) > 0) /* anon fun in instance */ {
strings.HasPrefix(fn.Synthetic, "instance ") ||
strings.HasPrefix(fn.Synthetic, "instantiation ") ||
(fn.parent != nil && len(fn.typeargs) > 0) /* anon fun in instance */ {
// ok
} else {
s.errorf("nil Pkg")
}
}
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
if strings.HasPrefix(fn.Synthetic, "instantiation") && fn.Prog.mode&InstantiateGenerics != 0 {
// ok
if len(fn.typeargs) > 0 && fn.Prog.mode&InstantiateGenerics != 0 {
// ok (instantiation with InstantiateGenerics on)
} else if fn.topLevelOrigin != nil && len(fn.typeargs) > 0 {
// ok (we always have the syntax set for instantiation)
} else {
s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
}
@ -494,6 +497,9 @@ func (s *sanity) checkFunction(fn *Function) bool {
if anon.Parent() != fn {
s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent())
}
if i != int(anon.anonIdx) {
s.errorf("AnonFuncs[%d]=%s but %s.anonIdx=%d", i, anon, anon, anon.anonIdx)
}
}
s.fn = nil
return !s.insane

View File

@ -294,16 +294,15 @@ type Node interface {
//
// Type() returns the function's Signature.
//
// A function is generic iff it has a non-empty TypeParams list and an
// empty TypeArgs list. TypeParams lists the type parameters of the
// function's Signature or the receiver's type parameters for a method.
//
// The instantiation of a generic function is a concrete function. These
// are a list of n>0 TypeParams and n TypeArgs. An instantiation will
// have a generic Origin function. There is at most one instantiation
// of each origin type per Identical() type list. Instantiations do not
// belong to any Pkg. The generic function and the instantiations will
// share the same source Pos for the functions and the instructions.
// A generic function is a function or method that has uninstantiated type
// parameters (TypeParams() != nil). Consider a hypothetical generic
// method, (*Map[K,V]).Get. It may be instantiated with all ground
// (non-parameterized) types as (*Map[string,int]).Get or with
// parameterized types as (*Map[string,U]).Get, where U is a type parameter.
// In both instantiations, Origin() refers to the instantiated generic
// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of
// the generic method. TypeArgs() refers to [string,U] or [string,int],
// respectively, and is nil in the generic method.
type Function struct {
name string
object types.Object // a declared *types.Func or one of its wrappers
@ -324,10 +323,11 @@ type Function struct {
AnonFuncs []*Function // anonymous functions directly beneath this one
referrers []Instruction // referring instructions (iff Parent() != nil)
built bool // function has completed both CREATE and BUILD phase.
anonIdx int32 // position of a nested function in parent's AnonFuncs. fn.Parent()!=nil => fn.Parent().AnonFunc[fn.anonIdx] == fn.
_Origin *Function // the origin function if this the instantiation of a generic function. nil if Parent() != nil.
_TypeParams []*typeparams.TypeParam // the type paramaters of this function. len(TypeParams) == len(_TypeArgs) => runtime function
_TypeArgs []types.Type // type arguments for for an instantiation. len(_TypeArgs) != 0 => instantiation
typeparams *typeparams.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function
typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function
topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil.
// The following fields are set transiently during building,
// then cleared.
@ -337,7 +337,7 @@ type Function struct {
targets *targets // linked stack of branch targets
lblocks map[types.Object]*lblock // labelled blocks
info *types.Info // *types.Info to build from. nil for wrappers.
subst *subster // type substitution cache
subst *subster // non-nil => expand generic body using this type substitution of ground types
}
// BasicBlock represents an SSA basic block.
@ -409,26 +409,28 @@ type Parameter struct {
referrers []Instruction
}
// A Const represents the value of a constant expression.
// A Const represents a value known at build time.
//
// The underlying type of a constant may be any boolean, numeric, or
// string type. In addition, a Const may represent the nil value of
// any reference type---interface, map, channel, pointer, slice, or
// function---but not "untyped nil".
// Consts include true constants of boolean, numeric, and string types, as
// defined by the Go spec; these are represented by a non-nil Value field.
//
// All source-level constant expressions are represented by a Const
// of the same type and value.
//
// Value holds the value of the constant, independent of its Type(),
// using go/constant representation, or nil for a typed nil value.
// Consts also include the "zero" value of any type, of which the nil values
// of various pointer-like types are a special case; these are represented
// by a nil Value field.
//
// Pos() returns token.NoPos.
//
// Example printed form:
// Example printed forms:
//
// 42:int
// "hello":untyped string
// 3+4i:MyComplex
// 42:int
// "hello":untyped string
// 3+4i:MyComplex
// nil:*int
// nil:[]string
// [3]int{}:[3]int
// struct{x string}{}:struct{x string}
// 0:interface{int|int64}
// nil:interface{bool|int} // no go/constant representation
type Const struct {
typ types.Type
Value constant.Value
@ -603,9 +605,17 @@ type UnOp struct {
// - between (possibly named) pointers to identical base types.
// - from a bidirectional channel to a read- or write-channel,
// optionally adding/removing a name.
// - between a type (t) and an instance of the type (tσ), i.e.
// Type() == σ(X.Type()) (or X.Type()== σ(Type())) where
// σ is the type substitution of Parent().TypeParams by
// Parent().TypeArgs.
//
// This operation cannot fail dynamically.
//
// Type changes may to be to or from a type parameter (or both). All
// types in the type set of X.Type() have a value-preserving type
// change to all types in the type set of Type().
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
@ -631,6 +641,10 @@ type ChangeType struct {
//
// A conversion may imply a type name change also.
//
// Conversions may to be to or from a type parameter. All types in
// the type set of X.Type() can be converted to all types in the type
// set of Type().
//
// This operation cannot fail dynamically.
//
// Conversions of untyped string/number/bool constants to a specific
@ -670,6 +684,11 @@ type ChangeInterface struct {
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Conversion may to be to or from a type parameter. All types in
// the type set of X.Type() must be a slice types that can be converted to
// all types in the type set of Type() which must all be pointer to array
// types.
//
// Example printed form:
//
// t1 = slice to array pointer *[4]byte <- []byte (t0)
@ -809,7 +828,9 @@ type Slice struct {
//
// Pos() returns the position of the ast.SelectorExpr.Sel for the
// field, if explicit in the source. For implicit selections, returns
// the position of the inducing explicit selection.
// the position of the inducing explicit selection. If produced for a
// struct literal S{f: e}, it returns the position of the colon; for
// S{e} it returns the start of expression e.
//
// Example printed form:
//
@ -817,7 +838,7 @@ type Slice struct {
type FieldAddr struct {
register
X Value // *struct
Field int // field is X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct).Field(Field)
Field int // field is typeparams.CoreType(X.Type().Underlying().(*types.Pointer).Elem()).(*types.Struct).Field(Field)
}
// The Field instruction yields the Field of struct X.
@ -836,14 +857,14 @@ type FieldAddr struct {
type Field struct {
register
X Value // struct
Field int // index into X.Type().(*types.Struct).Fields
Field int // index into typeparams.CoreType(X.Type()).(*types.Struct).Fields
}
// The IndexAddr instruction yields the address of the element at
// index Index of collection X. Index is an integer expression.
//
// The elements of maps and strings are not addressable; use Lookup or
// MapUpdate instead.
// The elements of maps and strings are not addressable; use Lookup (map),
// Index (string), or MapUpdate instead.
//
// Dynamically, this instruction panics if X evaluates to a nil *array
// pointer.
@ -858,11 +879,13 @@ type Field struct {
// t2 = &t0[t1]
type IndexAddr struct {
register
X Value // slice or *array,
X Value // *array, slice or type parameter with types array, *array, or slice.
Index Value // numeric index
}
// The Index instruction yields element Index of array X.
// The Index instruction yields element Index of collection X, an array,
// string or type parameter containing an array, a string, a pointer to an,
// array or a slice.
//
// Pos() returns the ast.IndexExpr.Lbrack for the index operation, if
// explicit in the source.
@ -872,13 +895,12 @@ type IndexAddr struct {
// t2 = t0[t1]
type Index struct {
register
X Value // array
X Value // array, string or type parameter with types array, *array, slice, or string.
Index Value // integer index
}
// The Lookup instruction yields element Index of collection X, a map
// or string. Index is an integer expression if X is a string or the
// appropriate key type if X is a map.
// The Lookup instruction yields element Index of collection map X.
// Index is the appropriate key type.
//
// If CommaOk, the result is a 2-tuple of the value above and a
// boolean indicating the result of a map membership test for the key.
@ -892,8 +914,8 @@ type Index struct {
// t5 = t3[t4],ok
type Lookup struct {
register
X Value // string or map
Index Value // numeric or key-typed index
X Value // map
Index Value // key-typed index
CommaOk bool // return a value,ok pair
}
@ -1337,9 +1359,10 @@ type anInstruction struct {
// 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon
// represents a dynamically dispatched call to an interface method.
// In this mode, Value is the interface value and Method is the
// interface's abstract method. Note: an abstract method may be
// shared by multiple interfaces due to embedding; Value.Type()
// provides the specific interface used for this call.
// interface's abstract method. The interface value may be a type
// parameter. Note: an abstract method may be shared by multiple
// interfaces due to embedding; Value.Type() provides the specific
// interface used for this call.
//
// Value is implicitly supplied to the concrete method implementation
// as the receiver parameter; in other words, Args[0] holds not the
@ -1378,7 +1401,7 @@ func (c *CallCommon) Signature() *types.Signature {
if c.Method != nil {
return c.Method.Type().(*types.Signature)
}
return c.Value.Type().Underlying().(*types.Signature)
return coreType(c.Value.Type()).(*types.Signature)
}
// StaticCallee returns the callee if this is a trivially static
@ -1469,6 +1492,29 @@ func (v *Function) Referrers() *[]Instruction {
return nil
}
// TypeParams are the function's type parameters if generic or the
// type parameters that were instantiated if fn is an instantiation.
//
// TODO(taking): declare result type as *types.TypeParamList
// after we drop support for go1.17.
func (fn *Function) TypeParams() *typeparams.TypeParamList {
return fn.typeparams
}
// TypeArgs are the types that TypeParams() were instantiated by to create fn
// from fn.Origin().
func (fn *Function) TypeArgs() []types.Type { return fn.typeargs }
// Origin is the function fn is an instantiation of. Returns nil if fn is not
// an instantiation.
func (fn *Function) Origin() *Function {
if fn.parent != nil && len(fn.typeargs) > 0 {
// Nested functions are BUILT at a different time than there instances.
return fn.parent.Origin().AnonFuncs[fn.anonIdx]
}
return fn.topLevelOrigin
}
func (v *Parameter) Type() types.Type { return v.typ }
func (v *Parameter) Name() string { return v.name }
func (v *Parameter) Object() types.Object { return v.object }

View File

@ -21,12 +21,10 @@ import (
"testing"
"time"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
"golang.org/x/tools/internal/typeparams/genericfeatures"
)
func bytesAllocated() uint64 {
@ -51,22 +49,6 @@ func TestStdlib(t *testing.T) {
if err != nil {
t.Fatal(err)
}
var nonGeneric int
for i := 0; i < len(pkgs); i++ {
pkg := pkgs[i]
inspect := inspector.New(pkg.Syntax)
features := genericfeatures.ForPackage(inspect, pkg.TypesInfo)
// Skip standard library packages that use generics. This won't be
// sufficient if any standard library packages start _importing_ packages
// that use generics.
if features != 0 {
t.Logf("skipping package %q which uses generics", pkg.PkgPath)
continue
}
pkgs[nonGeneric] = pkg
nonGeneric++
}
pkgs = pkgs[:nonGeneric]
t1 := time.Now()
alloc1 := bytesAllocated()

View File

@ -18,6 +18,8 @@ import (
//
// Not concurrency-safe.
type subster struct {
// TODO(zpavlinovic): replacements can contain type params
// when generating instances inside of a generic function body.
replacements map[*typeparams.TypeParam]types.Type // values should contain no type params
cache map[types.Type]types.Type // cache of subst results
ctxt *typeparams.Context
@ -27,17 +29,17 @@ type subster struct {
// Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache.
// targs should not contain any types in tparams.
func makeSubster(ctxt *typeparams.Context, tparams []*typeparams.TypeParam, targs []types.Type, debug bool) *subster {
assert(len(tparams) == len(targs), "makeSubster argument count must match")
func makeSubster(ctxt *typeparams.Context, tparams *typeparams.TypeParamList, targs []types.Type, debug bool) *subster {
assert(tparams.Len() == len(targs), "makeSubster argument count must match")
subst := &subster{
replacements: make(map[*typeparams.TypeParam]types.Type, len(tparams)),
replacements: make(map[*typeparams.TypeParam]types.Type, tparams.Len()),
cache: make(map[types.Type]types.Type),
ctxt: ctxt,
debug: debug,
}
for i, tpar := range tparams {
subst.replacements[tpar] = targs[i]
for i := 0; i < tparams.Len(); i++ {
subst.replacements[tparams.At(i)] = targs[i]
}
if subst.debug {
if err := subst.wellFormed(); err != nil {
@ -331,9 +333,9 @@ func (subst *subster) named(t *types.Named) types.Type {
// type N[A any] func() A
// func Foo[T](g N[T]) {}
// To instantiate Foo[string], one goes through {T->string}. To get the type of g
// one subsitutes T with string in {N with TypeArgs == {T} and TypeParams == {A} }
// to get {N with TypeArgs == {string} and TypeParams == {A} }.
assert(targs.Len() == tparams.Len(), "TypeArgs().Len() must match TypeParams().Len() if present")
// one subsitutes T with string in {N with typeargs == {T} and typeparams == {A} }
// to get {N with TypeArgs == {string} and typeparams == {A} }.
assert(targs.Len() == tparams.Len(), "typeargs.Len() must match typeparams.Len() if present")
for i, n := 0, targs.Len(); i < n; i++ {
inst := subst.typ(targs.At(i)) // TODO(generic): Check with rfindley for mutual recursion
insts[i] = inst

View File

@ -99,12 +99,8 @@ var _ L[int] = Fn0[L[int]](nil)
}
T := tv.Type.(*types.Named)
var tparams []*typeparams.TypeParam
for i, l := 0, typeparams.ForNamed(T); i < l.Len(); i++ {
tparams = append(tparams, l.At(i))
}
subst := makeSubster(typeparams.NewContext(), tparams, targs, true)
subst := makeSubster(typeparams.NewContext(), typeparams.ForNamed(T), targs, true)
sub := subst.typ(T.Underlying())
if got := sub.String(); got != test.want {
t.Errorf("subst{%v->%v}.typ(%s) = %v, want %v", test.expr, test.args, T.Underlying(), got, test.want)

View File

@ -1,3 +1,4 @@
//go:build ignore
// +build ignore
package main

View File

@ -49,7 +49,56 @@ func isPointer(typ types.Type) bool {
return ok
}
func isInterface(T types.Type) bool { return types.IsInterface(T) }
// isNonTypeParamInterface reports whether t is an interface type but not a type parameter.
func isNonTypeParamInterface(t types.Type) bool {
return !typeparams.IsTypeParam(t) && types.IsInterface(t)
}
// isBasic reports whether t is a basic type.
func isBasic(t types.Type) bool {
_, ok := t.(*types.Basic)
return ok
}
// isString reports whether t is exactly a string type.
func isString(t types.Type) bool {
return isBasic(t) && t.(*types.Basic).Info()&types.IsString != 0
}
// isByteSlice reports whether t is []byte.
func isByteSlice(t types.Type) bool {
if b, ok := t.(*types.Slice); ok {
e, _ := b.Elem().(*types.Basic)
return e != nil && e.Kind() == types.Byte
}
return false
}
// isRuneSlice reports whether t is []rune.
func isRuneSlice(t types.Type) bool {
if b, ok := t.(*types.Slice); ok {
e, _ := b.Elem().(*types.Basic)
return e != nil && e.Kind() == types.Rune
}
return false
}
// isBasicConvType returns true when a type set can be
// one side of a Convert operation. This is when:
// - All are basic, []byte, or []rune.
// - At least 1 is basic.
// - At most 1 is []byte or []rune.
func isBasicConvTypes(tset typeSet) bool {
basics := 0
all := tset.underIs(func(t types.Type) bool {
if isBasic(t) {
basics++
return true
}
return isByteSlice(t) || isRuneSlice(t)
})
return all && basics >= 1 && len(tset)-basics <= 1
}
// deref returns a pointer's element type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
@ -113,7 +162,7 @@ func nonbasicTypes(ts []types.Type) []types.Type {
added := make(map[types.Type]bool) // additionally filter duplicates
var filtered []types.Type
for _, T := range ts {
if _, basic := T.(*types.Basic); !basic {
if !isBasic(T) {
if !added[T] {
added[T] = true
filtered = append(filtered, T)
@ -123,22 +172,6 @@ func nonbasicTypes(ts []types.Type) []types.Type {
return filtered
}
// isGeneric returns true if a package-level member is generic.
func isGeneric(m Member) bool {
switch m := m.(type) {
case *NamedConst, *Global:
return false
case *Type:
// lifted from types.isGeneric.
named, _ := m.Type().(*types.Named)
return named != nil && named.Obj() != nil && typeparams.NamedTypeArgs(named) == nil && typeparams.ForNamed(named) != nil
case *Function:
return len(m._TypeParams) != len(m._TypeArgs)
default:
panic("unreachable")
}
}
// receiverTypeArgs returns the type arguments to a function's reciever.
// Returns an empty list if obj does not have a reciever or its reciever does not have type arguments.
func receiverTypeArgs(obj *types.Func) []types.Type {

View File

@ -120,19 +120,19 @@ func makeWrapper(prog *Program, sel *selection, cr *creator) *Function {
// address of implicit C field.
var c Call
if r := recvType(obj); !isInterface(r) { // concrete method
if r := recvType(obj); !types.IsInterface(r) { // concrete method
if !isPointer(r) {
v = emitLoad(fn, v)
}
callee := prog.originFunc(obj)
if len(callee._TypeParams) > 0 {
callee = prog.instances[callee].lookupOrCreate(receiverTypeArgs(obj), cr)
if callee.typeparams.Len() > 0 {
callee = prog.lookupOrCreateInstance(callee, receiverTypeArgs(obj), cr)
}
c.Call.Value = callee
c.Call.Args = append(c.Call.Args, v)
} else {
c.Call.Method = obj
c.Call.Value = emitLoad(fn, v)
c.Call.Value = emitLoad(fn, v) // interface (possibly a typeparam)
}
for _, arg := range fn.Params[1:] {
c.Call.Args = append(c.Call.Args, arg)
@ -208,16 +208,16 @@ func makeBound(prog *Program, obj *types.Func, cr *creator) *Function {
createParams(fn, 0)
var c Call
if !isInterface(recvType(obj)) { // concrete
if !types.IsInterface(recvType(obj)) { // concrete
callee := prog.originFunc(obj)
if len(callee._TypeParams) > 0 {
callee = prog.instances[callee].lookupOrCreate(targs, cr)
if callee.typeparams.Len() > 0 {
callee = prog.lookupOrCreateInstance(callee, targs, cr)
}
c.Call.Value = callee
c.Call.Args = []Value{fv}
} else {
c.Call.Value = fv
c.Call.Method = obj
c.Call.Value = fv // interface (possibly a typeparam)
}
for _, arg := range fn.Params {
c.Call.Args = append(c.Call.Args, arg)
@ -324,3 +324,63 @@ func toSelection(sel *types.Selection) *selection {
indirect: sel.Indirect(),
}
}
// -- instantiations --------------------------------------------------
// buildInstantiationWrapper creates a body for an instantiation
// wrapper fn. The body calls the original generic function,
// bracketed by ChangeType conversions on its arguments and results.
func buildInstantiationWrapper(fn *Function) {
orig := fn.topLevelOrigin
sig := fn.Signature
fn.startBody()
if sig.Recv() != nil {
fn.addParamObj(sig.Recv())
}
createParams(fn, 0)
// Create body. Add a call to origin generic function
// and make type changes between argument and parameters,
// as well as return values.
var c Call
c.Call.Value = orig
if res := orig.Signature.Results(); res.Len() == 1 {
c.typ = res.At(0).Type()
} else {
c.typ = res
}
// parameter of instance becomes an argument to the call
// to the original generic function.
argOffset := 0
for i, arg := range fn.Params {
var typ types.Type
if i == 0 && sig.Recv() != nil {
typ = orig.Signature.Recv().Type()
argOffset = 1
} else {
typ = orig.Signature.Params().At(i - argOffset).Type()
}
c.Call.Args = append(c.Call.Args, emitTypeCoercion(fn, arg, typ))
}
results := fn.emit(&c)
var ret Return
switch res := sig.Results(); res.Len() {
case 0:
// no results, do nothing.
case 1:
ret.Results = []Value{emitTypeCoercion(fn, results, res.At(0).Type())}
default:
for i := 0; i < sig.Results().Len(); i++ {
v := emitExtract(fn, results, i)
ret.Results = append(ret.Results, emitTypeCoercion(fn, v, res.At(i).Type()))
}
}
fn.emit(&ret)
fn.currentBlock = nil
fn.finishBody()
}