blob: b044c7e66c100bc5d26933f4383f2d8ebc8b7cd7 [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),
}
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
}
}
}
}
}
}