blob: c3b1b9b021edf150d78a75342116180f757fc310 [file]
// Copyright 2026 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 ptrtoerror
import (
"go/ast"
"go/types"
"iter"
"golang.org/x/tools/go/ast/inspector"
)
// A conversion represents a syntax node at which an assignment or explicit
// conversion occurs, along with the source and destination types.
type conversion struct {
tLHS, tRHS types.Type
expr ast.Expr
}
// conversions returns an iterator over all (LHS, RHS) assignment and
// conversion pairs in the package, where LHS is the destination type
// (types.Type) and RHS is the expression (ast.Expr). Some of these
// may involve a widening.
//
// TODO(adonovan): eliminate when https://go.dev/issue/70638 is done.
func conversions(root inspector.Cursor, info *types.Info) iter.Seq[conversion] {
return func(yield func(conversion) bool) {
// nodes where assignability conversions occur
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.ValueSpec)(nil),
(*ast.CallExpr)(nil),
(*ast.ReturnStmt)(nil),
(*ast.CompositeLit)(nil),
(*ast.SendStmt)(nil),
(*ast.TypeAssertExpr)(nil),
(*ast.TypeSwitchStmt)(nil),
}
for c := range root.Preorder(nodeFilter...) {
switch n := c.Node().(type) {
case *ast.AssignStmt:
if len(n.Lhs) == len(n.Rhs) {
// simple or tuple assignment
for i, lhs := range n.Lhs {
if tLHS := info.TypeOf(lhs); tLHS != nil &&
!yield(conversion{tLHS, info.TypeOf(n.Rhs[i]), n.Rhs[i]}) {
return
}
}
} else if len(n.Lhs) > 1 && len(n.Rhs) == 1 {
// spread assignment
if tuple, ok := info.TypeOf(n.Rhs[0]).(*types.Tuple); ok && tuple.Len() == len(n.Lhs) {
for i, lhs := range n.Lhs {
if tLHS := info.TypeOf(lhs); tLHS != nil &&
!yield(conversion{info.TypeOf(lhs), tuple.At(i).Type(), n.Rhs[0]}) {
return
}
}
}
}
case *ast.ValueSpec:
if len(n.Values) > 0 {
if len(n.Names) == len(n.Values) {
// simple or tuple initialization
for i := range n.Names {
if obj := info.ObjectOf(n.Names[i]); obj != nil {
if !yield(conversion{obj.Type(), info.TypeOf(n.Values[i]), n.Values[i]}) {
return
}
}
}
} else if len(n.Names) > 1 && len(n.Values) == 1 {
// spread initialization
if tuple, ok := info.TypeOf(n.Values[0]).(*types.Tuple); ok && tuple.Len() == len(n.Names) {
for i := range n.Names {
if obj := info.ObjectOf(n.Names[i]); obj != nil {
if !yield(conversion{obj.Type(), tuple.At(i).Type(), n.Values[0]}) {
return
}
}
}
}
}
}
case *ast.CallExpr:
tv := info.Types[n.Fun]
// explicit conversion?
if tv.IsType() {
if !yield(conversion{tv.Type, info.TypeOf(n.Args[0]), n.Args[0]}) {
return
}
break
}
sig, ok := tv.Type.Underlying().(*types.Signature)
if !ok {
break
}
// spread call?
if len(n.Args) == 1 && sig.Params().Len() > 1 {
if tuple, ok := info.TypeOf(n.Args[0]).(*types.Tuple); ok {
for i := 0; i < sig.Params().Len() && i < tuple.Len(); i++ {
if !yield(conversion{sig.Params().At(i).Type(), tuple.At(i).Type(), n.Args[0]}) {
return
}
}
}
break
}
// argument -> parameter assignment
last := sig.Params().Len() - 1
for i, arg := range n.Args {
if sig.Variadic() && i >= last {
// variadic callee
if slice, ok := sig.Params().At(last).Type().(*types.Slice); ok {
var t types.Type = slice // f(args...) call of func(...T)
if !n.Ellipsis.IsValid() {
t = slice.Elem() // f(a, b, c) call of func(...T)
}
if !yield(conversion{t, info.TypeOf(arg), arg}) {
return
}
}
} else {
if !yield(conversion{sig.Params().At(i).Type(), info.TypeOf(arg), arg}) {
return
}
}
}
case *ast.ReturnStmt:
// Handle return statements by matching results against enclosing function signatures.
var sig *types.Signature
for anc := range c.Enclosing((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
if fd, ok := anc.Node().(*ast.FuncDecl); ok {
if obj := info.Defs[fd.Name]; obj != nil {
sig = obj.Type().Underlying().(*types.Signature)
}
break
}
if fl, ok := anc.Node().(*ast.FuncLit); ok {
sig = info.TypeOf(fl).Underlying().(*types.Signature)
break
}
}
if sig == nil {
break // can't happen?
}
// spread return?
if len(n.Results) == 1 && sig.Results().Len() > 1 {
if tuple, ok := info.TypeOf(n.Results[0]).(*types.Tuple); ok {
for i := 0; i < sig.Results().Len() && i < tuple.Len(); i++ {
if !yield(conversion{sig.Results().At(i).Type(), tuple.At(i).Type(), n.Results[0]}) {
return
}
}
}
break
}
// simple or tuple return
if len(n.Results) == sig.Results().Len() {
for i, res := range n.Results {
if !yield(conversion{sig.Results().At(i).Type(), info.TypeOf(res), res}) {
return
}
}
}
case *ast.CompositeLit:
// element assignments in slice, array, map, and struct literals.
switch t := info.TypeOf(n).Underlying().(type) {
case *types.Slice, *types.Array:
for _, elt := range n.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
elt = kv.Value
}
if !yield(conversion{t.(interface{ Elem() types.Type }).Elem(), info.TypeOf(elt), elt}) {
return
}
}
case *types.Map:
for _, elt := range n.Elts {
kv := elt.(*ast.KeyValueExpr)
if !yield(conversion{t.Key(), info.TypeOf(kv.Key), kv.Key}) ||
!yield(conversion{t.Elem(), info.TypeOf(kv.Value), kv.Value}) {
return
}
}
case *types.Struct:
for i, elt := range n.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
if id, ok := kv.Key.(*ast.Ident); ok {
var fieldType types.Type
if obj := info.Uses[id]; obj != nil {
fieldType = obj.Type()
} else {
for field := range t.Fields() {
if field.Name() == id.Name {
fieldType = field.Type()
break
}
}
}
if !yield(conversion{fieldType, info.TypeOf(kv.Value), kv.Value}) {
return
}
}
} else if i < t.NumFields() {
if !yield(conversion{t.Field(i).Type(), info.TypeOf(elt), elt}) {
return
}
}
}
}
case *ast.SendStmt:
// channel send
if chanType, ok := info.TypeOf(n.Chan).Underlying().(*types.Chan); ok {
if !yield(conversion{chanType.Elem(), info.TypeOf(n.Value), n.Value}) {
return
}
}
case *ast.TypeAssertExpr:
// I(x).(E) acts like a pseudoconversion from E to E.
if n.Type != nil && // (not beneath type switch)
!yield(conversion{info.TypeOf(n.X), info.TypeOf(n.Type), n.Type}) {
return
}
case *ast.TypeSwitchStmt:
// switch I(x).(type) { case E: } depends on E being assignable to I.
// Report the (pseudo)conversion of type (not term) E to I.
var assert *ast.TypeAssertExpr
switch assign := n.Assign.(type) {
case *ast.ExprStmt:
assert = assign.X.(*ast.TypeAssertExpr)
case *ast.AssignStmt:
assert = assign.Rhs[0].(*ast.TypeAssertExpr)
}
for _, cc := range n.Body.List {
for _, typ := range cc.(*ast.CaseClause).List {
if !yield(conversion{info.TypeOf(assert.X), info.TypeOf(typ), typ}) {
return
}
}
}
}
}
// Yield conversions from type parameter instantiations
// e.g. errors.AsType[*E](err)
for id, inst := range info.Instances {
t := info.ObjectOf(id).Type()
type hasTypeParams interface{ TypeParams() *types.TypeParamList } // = Signature, Named, Alias
if t, ok := t.(hasTypeParams); ok {
tparams := t.TypeParams()
for i := 0; i < tparams.Len(); i++ {
tparam := tparams.At(i)
targ := inst.TypeArgs.At(i)
if !yield(conversion{tparam.Constraint(), targ, id}) {
return
}
}
}
}
}
}