Update go version

This commit is contained in:
dwrz
2026-08-21 10:23:37 +00:00
parent 78248a6145
commit c2e2d9ea02
466 changed files with 67766 additions and 2881 deletions

View File

@@ -0,0 +1,18 @@
// Copyright 2024 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 aliases
import (
"go/token"
"go/types"
)
// New creates a new TypeName in Package pkg that
// is an alias for the type rhs.
func New(pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName {
tname := types.NewTypeName(pos, pkg, name, nil)
types.NewAlias(tname, rhs).SetTypeParams(tparams)
return tname
}

View File

@@ -0,0 +1,33 @@
// Copyright 2025 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 typeindex defines an analyzer that provides a
// [honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex.Index].
//
// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
// intended to be used as a helper by other analyzers; it reports no
// diagnostics of its own.
package typeindex
import (
"reflect"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex"
)
var Analyzer = &analysis.Analyzer{
Name: "typeindex",
Doc: "indexes of type information for later passes",
URL: "https://pkg.go.dev/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex",
Run: func(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
},
RunDespiteErrors: true,
Requires: []*analysis.Analyzer{inspect.Analyzer},
ResultType: reflect.TypeFor[*typeindex.Index](),
}

View File

@@ -0,0 +1,71 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import (
"go/ast"
"reflect"
)
// CloneNode returns a deep copy of a Node.
// It omits pointers to ast.{Scope,Object} variables.
func CloneNode[T ast.Node](n T) T {
return cloneNode(n).(T)
}
func cloneNode(n ast.Node) ast.Node {
var clone func(x reflect.Value) reflect.Value
set := func(dst, src reflect.Value) {
src = clone(src)
if src.IsValid() {
dst.Set(src)
}
}
clone = func(x reflect.Value) reflect.Value {
switch x.Kind() {
case reflect.Pointer:
if x.IsNil() {
return x
}
// Skip fields of types potentially involved in cycles.
switch x.Interface().(type) {
case *ast.Object, *ast.Scope:
return reflect.Zero(x.Type())
}
y := reflect.New(x.Type().Elem())
set(y.Elem(), x.Elem())
return y
case reflect.Struct:
y := reflect.New(x.Type()).Elem()
for i := 0; i < x.Type().NumField(); i++ {
set(y.Field(i), x.Field(i))
}
return y
case reflect.Slice:
if x.IsNil() {
return x
}
y := reflect.MakeSlice(x.Type(), x.Len(), x.Cap())
for i := 0; i < x.Len(); i++ {
set(y.Index(i), x.Index(i))
}
return y
case reflect.Interface:
y := reflect.New(x.Type()).Elem()
set(y, x.Elem())
return y
case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer:
panic(x) // unreachable in AST
default:
return x // bool, string, number
}
}
return clone(reflect.ValueOf(n)).Interface().(ast.Node)
}

View File

@@ -0,0 +1,143 @@
// Copyright 2025 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 astutil
import (
"go/ast"
"go/token"
"iter"
"sort"
"strings"
)
// Deprecation returns the paragraph of the doc comment that starts with the
// conventional "Deprecation: " marker, as defined by
// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
// deprecated.
func Deprecation(doc *ast.CommentGroup) string {
for p := range strings.SplitSeq(doc.Text(), "\n\n") {
// There is still some ambiguity for deprecation message. This function
// only returns the paragraph introduced by "Deprecated: ". More
// information related to the deprecation may follow in additional
// paragraphs, but the deprecation message should be able to stand on
// its own. See golang/go#38743.
if strings.HasPrefix(p, "Deprecated: ") {
return p
}
}
return ""
}
// -- plundered from the future (CL 605517, issue #68021) --
// TODO(adonovan): replace with ast.Directive in go1.26 (#68021).
// Beware of our local mods to handle analysistest
// "want" comments on the same line.
// A directive is a comment line with special meaning to the Go
// toolchain or another tool. It has the form:
//
// //tool:name args
//
// The "tool:" portion is missing for the three directives named
// line, extern, and export.
//
// See https://go.dev/doc/comment#Syntax for details of Go comment
// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives
// for details of directives used by the Go compiler.
type Directive struct {
Pos token.Pos // of preceding "//"
Tool string
Name string
Args string // may contain internal spaces
}
// isDirective reports whether c is a comment directive.
// This code is also in go/printer.
func isDirective(c string) bool {
// "//line " is a line directive.
// "//extern " is for gccgo.
// "//export " is for cgo.
// (The // has been removed.)
if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") {
return true
}
// "//[a-z0-9]+:[a-z0-9]"
// (The // has been removed.)
colon := strings.Index(c, ":")
if colon <= 0 || colon+1 >= len(c) {
return false
}
for i := 0; i <= colon+1; i++ {
if i == colon {
continue
}
b := c[i]
if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') {
return false
}
}
return true
}
// Directives returns the directives within the comment.
func Directives(g *ast.CommentGroup) (res []*Directive) {
if g != nil {
// Avoid (*ast.CommentGroup).Text() as it swallows directives.
for _, c := range g.List {
if len(c.Text) > 2 &&
c.Text[1] == '/' &&
c.Text[2] != ' ' &&
isDirective(c.Text[2:]) {
tool, nameargs, ok := strings.Cut(c.Text[2:], ":")
if !ok {
// Must be one of {line,extern,export}.
tool, nameargs = "", tool
}
name, args, _ := strings.Cut(nameargs, " ") // tab??
// Permit an additional line comment after the args, chiefly to support
// [golang.org/x/tools/go/analysis/analysistest].
args, _, _ = strings.Cut(args, "//")
res = append(res, &Directive{
Pos: c.Slash,
Tool: tool,
Name: name,
Args: strings.TrimSpace(args),
})
}
}
}
return
}
// Comments returns an iterator over the comments overlapping the specified interval.
// Comments are sorted by position in the file, so we can use binary search.
func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
return func(yield func(*ast.Comment) bool) {
// Find the first comment group that overlaps the range.
i := sort.Search(len(file.Comments), func(i int) bool {
return file.Comments[i].End() >= start
})
for _, cg := range file.Comments[i:] {
if cg.Pos() > end {
return
}
// Find the first comment in the group that overlaps the range.
j := sort.Search(len(cg.List), func(j int) bool {
return cg.List[j].End() >= start
})
for _, co := range cg.List[j:] {
if co.Pos() > end {
return
}
if !yield(co) {
return
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
// 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 astutil
import (
"go/ast"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
)
// UnparenCursor returns the cursor for an expression with any
// enclosing parentheses removed, similar to [ast.Unparen].
// It is often prudent to call this before switching on the
// type of cur.Node().
//
// See also [UnparenEnclosingCursor].
func UnparenCursor(cur inspector.Cursor) inspector.Cursor {
for is[*ast.ParenExpr](cur) {
cur, _ = cur.FirstChild()
}
return cur
}
// UnparenEnclosingCursor returns the first element of
// the [Cursor.Enclosing] sequence that is not itself enclosed
// in parens. It is often prudent to call this before switching on
// cur.ParentEdge().
//
// See also [UnparenCursor].
func UnparenEnclosingCursor(cur inspector.Cursor) inspector.Cursor {
for cur.ParentEdgeKind() == edge.ParenExpr_X {
cur = cur.Parent()
}
return cur
}

View File

@@ -0,0 +1,107 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import (
"go/ast"
"go/token"
"reflect"
)
// Equal reports whether two nodes are structurally equal,
// ignoring fields of type [token.Pos], [ast.Object],
// and [ast.Scope], and comments.
//
// The operands x and y may be nil.
// A nil slice is not equal to an empty slice.
//
// The provided function determines whether two identifiers
// should be considered identical.
func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool {
if x == nil || y == nil {
return x == y
}
return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical)
}
// EqualSyntax reports whether x and y are equal.
// Identifiers are considered equal if they are spelled the same.
// Comments are ignored.
func EqualSyntax(x, y ast.Expr) bool {
sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name }
return Equal(x, y, sameName)
}
func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool {
// Ensure types are the same
if x.Type() != y.Type() {
return false
}
switch x.Kind() {
case reflect.Pointer:
if x.IsNil() || y.IsNil() {
return x.IsNil() == y.IsNil()
}
switch t := x.Interface().(type) {
// Skip fields of types potentially involved in cycles.
case *ast.Object, *ast.Scope, *ast.CommentGroup:
return true
case *ast.Ident:
return identical(t, y.Interface().(*ast.Ident))
default:
return equal(x.Elem(), y.Elem(), identical)
}
case reflect.Interface:
if x.IsNil() || y.IsNil() {
return x.IsNil() == y.IsNil()
}
return equal(x.Elem(), y.Elem(), identical)
case reflect.Struct:
for i := range x.NumField() {
xf := x.Field(i)
yf := y.Field(i)
// Skip position fields.
if xpos, ok := xf.Interface().(token.Pos); ok {
ypos := yf.Interface().(token.Pos)
// Numeric value of a Pos is not significant but its "zeroness" is,
// because it is often significant, e.g. CallExpr.Variadic(Ellipsis), ChanType.Arrow.
if xpos.IsValid() != ypos.IsValid() {
return false
}
} else if !equal(xf, yf, identical) {
return false
}
}
return true
case reflect.Slice:
if x.IsNil() || y.IsNil() {
return x.IsNil() == y.IsNil()
}
if x.Len() != y.Len() {
return false
}
for i := range x.Len() {
if !equal(x.Index(i), y.Index(i), identical) {
return false
}
}
return true
case reflect.String:
return x.String() == y.String()
case reflect.Bool:
return x.Bool() == y.Bool()
case reflect.Int:
return x.Int() == y.Int()
default:
panic(x)
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2024 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 astutil
import (
"go/ast"
"iter"
)
// FlatFields 'flattens' an ast.FieldList, returning an iterator over each
// (name, field) combination in the list. For unnamed fields, the identifier is
// nil.
func FlatFields(list *ast.FieldList) iter.Seq2[*ast.Ident, *ast.Field] {
return func(yield func(*ast.Ident, *ast.Field) bool) {
if list == nil {
return
}
for _, field := range list.List {
if len(field.Names) == 0 {
if !yield(nil, field) {
return
}
} else {
for _, name := range field.Names {
if !yield(name, field) {
return
}
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package astutil provides various AST utility functions for gopls.
package astutil
import (
"bytes"
"go/scanner"
"go/token"
)
// PurgeFuncBodies returns a copy of src in which the contents of each
// outermost {...} region have been deleted, except for struct and
// interface type bodies and the bodies of length-elided array
// literals ([...]T), whose element count is part of the type. It
// includes function bodies, function-literal bodies, and the bodies
// of slice, map, and explicitly-sized array composite literals (whose
// contents don't affect the type of the enclosing declaration). This
// reduces the amount of work required to parse the top-level
// declarations.
//
// PurgeFuncBodies does not preserve newlines or position information.
// Also, if the input is invalid, parsing the output of
// PurgeFuncBodies may result in a different tree due to its effects
// on parser error recovery.
func PurgeFuncBodies(src []byte) []byte {
// Destroy the content of any {...}-bracketed regions that are
// not immediately preceded by a "struct" or "interface" token,
// and that are not the body of a length-elided array literal.
// That includes function bodies, switch/select bodies, and most
// composite literals; this will lead to non-void functions that
// don't have return statements, which of course is a type error,
// but that's ok.
var out bytes.Buffer
file := token.NewFileSet().AddFile("", -1, len(src))
var sc scanner.Scanner
sc.Init(file, src, nil, 0)
var prev token.Token
var cursor int // last consumed src offset
var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve
var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{"
for {
pos, tok, _ := sc.Scan()
if tok == token.EOF {
break
}
switch tok {
case token.COMMENT:
// TODO(adonovan): opt: skip, to save an estimated 20% of time.
case token.SEMICOLON:
ellipsis = false
case token.RBRACK:
// "...]" occurs only in the array-type prefix of a
// composite literal; variadic "..." is followed by
// a type or ")", never "]".
if prev == token.ELLIPSIS {
ellipsis = true
}
case token.LBRACE:
if prev == token.STRUCT || prev == token.INTERFACE {
pos = -1 // type body: preserve (don't consume ellipsis)
} else if ellipsis {
pos = -1 // [...]T literal body: preserve
ellipsis = false
}
braces = append(braces, pos)
case token.RBRACE:
if last := len(braces) - 1; last >= 0 {
top := braces[last]
braces = braces[:last]
if top < 0 {
// preserve
} else if len(braces) == 0 { // toplevel only
// Delete {...} body.
start := file.Offset(top)
end := file.Offset(pos)
out.Write(src[cursor : start+len("{")])
cursor = end
}
}
}
prev = tok
}
out.Write(src[cursor:])
return out.Bytes()
}

View File

@@ -0,0 +1,103 @@
// Copyright 2025 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 astutil
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"unicode/utf8"
)
// RangeInStringLiteral calculates the positional range within a string literal
// corresponding to the specified start and end byte offsets within the logical string.
func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (Range, error) {
startPos, err := PosInStringLiteral(lit, start)
if err != nil {
return Range{}, fmt.Errorf("start: %v", err)
}
endPos, err := PosInStringLiteral(lit, end)
if err != nil {
return Range{}, fmt.Errorf("end: %v", err)
}
return Range{startPos, endPos}, nil
}
// PosInStringLiteral returns the position within a string literal
// corresponding to the specified byte offset within the logical
// string that it denotes.
func PosInStringLiteral(lit *ast.BasicLit, offset int) (token.Pos, error) {
raw := lit.Value
value, err := strconv.Unquote(raw)
if err != nil {
return 0, err
}
if !(0 <= offset && offset <= len(value)) {
return 0, fmt.Errorf("invalid offset")
}
pos, _ := walkStringLiteral(lit, lit.End(), offset)
return pos, nil
}
// OffsetInStringLiteral returns the byte offset within the logical (unquoted)
// string corresponding to the specified source position.
func OffsetInStringLiteral(lit *ast.BasicLit, pos token.Pos) (int, error) {
if !NodeContainsPos(lit, pos) {
return 0, fmt.Errorf("invalid position")
}
raw := lit.Value
value, err := strconv.Unquote(raw)
if err != nil {
return 0, err
}
_, offset := walkStringLiteral(lit, pos, len(value))
return offset, nil
}
// walkStringLiteral iterates through the raw string literal to map between
// a file position and a logical byte offset. It stops when it reaches
// either the targetPos or the targetOffset.
//
// TODO(hxjiang): consider making an iterator.
func walkStringLiteral(lit *ast.BasicLit, targetPos token.Pos, targetOffset int) (token.Pos, int) {
raw := lit.Value
norm := int(lit.End()-lit.Pos()) > len(lit.Value)
// remove quotes
quote := raw[0] // '"' or '`'
raw = raw[1 : len(raw)-1]
var (
i = 0 // byte index within logical value
pos = lit.Pos() + 1 // position within literal
)
for raw != "" {
r, _, rest, _ := strconv.UnquoteChar(raw, quote) // can't fail
sz := len(raw) - len(rest) // length of literal char in raw bytes
nextPos := pos + token.Pos(sz)
if norm && r == '\n' {
nextPos++
}
nextI := i + utf8.RuneLen(r) // length of logical char in "cooked" bytes
if nextPos > targetPos || nextI > targetOffset {
break
}
raw = raw[sz:]
i = nextI
pos = nextPos
}
return pos, i
}

View File

@@ -0,0 +1,61 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package astutil
import (
"go/ast"
"honnef.co/go/tools/internal/xtools-internal/typeparams"
)
// UnpackRecv unpacks a receiver type expression, reporting whether it is a
// pointer receiver, along with the type name identifier and any receiver type
// parameter identifiers.
//
// Copied (with modifications) from go/types.
func UnpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) {
L: // unpack receiver type
// This accepts invalid receivers such as ***T and does not
// work for other invalid receivers, but we don't care. The
// validity of receiver expressions is checked elsewhere.
for {
switch t := rtyp.(type) {
case *ast.ParenExpr:
rtyp = t.X
case *ast.StarExpr:
ptr = true
rtyp = t.X
default:
break L
}
}
// unpack type parameters, if any
switch rtyp.(type) {
case *ast.IndexExpr, *ast.IndexListExpr:
var indices []ast.Expr
rtyp, _, indices, _ = typeparams.UnpackIndexExpr(rtyp)
for _, arg := range indices {
var par *ast.Ident
switch arg := arg.(type) {
case *ast.Ident:
par = arg
default:
// ignore errors
}
if par == nil {
par = &ast.Ident{NamePos: arg.Pos(), Name: "_"}
}
tparams = append(tparams, par)
}
}
// unpack receiver name
if name, _ := rtyp.(*ast.Ident); name != nil {
rname = name
}
return
}

View File

@@ -0,0 +1,261 @@
// Copyright 2025 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 astutil
import (
"fmt"
"go/ast"
"go/printer"
"go/token"
"strings"
"golang.org/x/tools/go/ast/inspector"
"honnef.co/go/tools/internal/xtools-internal/moreiters"
)
// NodeContains reports whether the Pos/End range of node n encloses
// the given range.
//
// It is inclusive of both end points, to allow hovering (etc) when
// the cursor is immediately after a node.
//
// Like [NodeRange], it treats the range of an [ast.File] as the
// file's complete extent.
//
// Precondition: n must not be nil.
func NodeContains(n ast.Node, rng Range) bool {
return NodeRange(n).Contains(rng)
}
// NodeContainsPos reports whether the Pos/End range of node n encloses
// the given pos.
//
// Like [NodeRange], it treats the range of an [ast.File] as the
// file's complete extent.
func NodeContainsPos(n ast.Node, pos token.Pos) bool {
return NodeRange(n).ContainsPos(pos)
}
// EnclosingFile returns the syntax tree for the file enclosing c.
//
// TODO(adonovan): promote this to a method of Cursor.
func EnclosingFile(c inspector.Cursor) *ast.File {
c, _ = moreiters.First(c.Enclosing((*ast.File)(nil)))
return c.Node().(*ast.File)
}
// DocComment returns the doc comment for a node, if any.
func DocComment(n ast.Node) *ast.CommentGroup {
switch n := n.(type) {
case *ast.FuncDecl:
return n.Doc
case *ast.GenDecl:
return n.Doc
case *ast.ValueSpec:
return n.Doc
case *ast.TypeSpec:
return n.Doc
case *ast.File:
return n.Doc
case *ast.ImportSpec:
return n.Doc
case *ast.Field:
return n.Doc
}
return nil
}
// Format returns a string representation of the node n.
func Format(fset *token.FileSet, n ast.Node) string {
var buf strings.Builder
printer.Fprint(&buf, fset, n) // ignore errors
return buf.String()
}
// -- Range --
// Range is a Pos interval.
// It implements [analysis.Range] and [ast.Node].
type Range struct{ Start, EndPos token.Pos }
// RangeOf constructs a Range.
//
// RangeOf exists to pacify the "unkeyed literal" (composites) vet
// check. It would be nice if there were a way for a type to add
// itself to the allowlist.
func RangeOf(start, end token.Pos) Range { return Range{start, end} }
// NodeRange returns the extent of node n as a Range.
//
// For unfortunate historical reasons, the Pos/End extent of an
// ast.File runs from the start of its package declaration---excluding
// copyright comments, build tags, and package documentation---to the
// end of its last declaration, excluding any trailing comments. So,
// as a special case, if n is an [ast.File], NodeContains uses
// n.FileStart <= pos && pos <= n.FileEnd to report whether the
// position lies anywhere within the file.
func NodeRange(n ast.Node) Range {
if file, ok := n.(*ast.File); ok {
return Range{file.FileStart, file.FileEnd} // entire file
}
return Range{n.Pos(), n.End()}
}
func (r Range) Pos() token.Pos { return r.Start }
func (r Range) End() token.Pos { return r.EndPos }
// ContainsPos reports whether the range (inclusive of both end points)
// includes the specified position.
func (r Range) ContainsPos(pos token.Pos) bool {
return r.Contains(RangeOf(pos, pos))
}
// Contains reports whether the range (inclusive of both end points)
// includes the specified range.
func (r Range) Contains(rng Range) bool {
return r.Start <= rng.Start && rng.EndPos <= r.EndPos
}
// IsValid reports whether the range is valid.
func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos }
// --
// Select returns the syntax nodes identified by a user's text
// selection. It returns three nodes: the innermost node that wholly
// encloses the selection; and the first and last nodes that are
// wholly enclosed by the selection.
//
// For example, given this selection:
//
// { f(); g(); /* comment */ }
// ~~~~~~~~~~~
//
// Select returns the enclosing BlockStmt, the f() CallExpr, and the g() CallExpr.
//
// If the selection does not wholly enclose any nodes, Select returns an error
// and invalid start/end nodes, but it may return a valid enclosing node.
//
// Callers that require exactly one syntax tree (e.g. just f() or just
// g()) should check that the returned start and end nodes are
// identical.
//
// This function is intended to be called early in the handling of a
// user's request, since it is tolerant of sloppy selection including
// extraneous whitespace and comments. Use it in new code instead of
// PathEnclosingInterval. When the exact extent of a node is known,
// use [Cursor.FindByPos] instead.
//
// TODO(hxjiang): Consider refactoring the function signature. It is currently
// confusing that an error is returned even when a valid enclosing node is
// successfully found. Consider grouping all cursors into one struct.
func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) {
curEnclosing, ok := curFile.FindByPos(start, end)
if !ok {
return noCursor, noCursor, noCursor, fmt.Errorf("invalid selection")
}
// Find the first and last node wholly within the (start, end) range.
// We'll narrow the effective selection to them, to exclude whitespace.
// (This matches the functionality of PathEnclosingInterval.)
var curStart, curEnd inspector.Cursor
rng := RangeOf(start, end)
for cur := range curEnclosing.Preorder() {
if rng.Contains(NodeRange(cur.Node())) {
// The start node has the least Pos.
if !curStart.Valid() {
curStart = cur
}
// The end node has the greatest End.
// End positions do not change monotonically,
// so we must compute the max.
if !curEnd.Valid() ||
cur.Node().End() > curEnd.Node().End() {
curEnd = cur
}
}
}
if !curStart.Valid() {
// The selection is valid (inside curEnclosing) but contains no
// complete nodes. This happens for point selections (start == end),
// or selections covering only only spaces, comments, and punctuation
// tokens.
// Return the enclosing node so the caller can still use the context.
return curEnclosing, noCursor, noCursor, fmt.Errorf("invalid selection")
}
return curEnclosing, curStart, curEnd, nil
}
var noCursor inspector.Cursor
// MaybeParenthesize returns new, possibly wrapped in parens if needed
// to preserve operator precedence when it replaces old, whose parent
// is parentNode.
//
// (This would be more naturally written in terms of Cursor, but one of
// the callers--the inliner--does not have cursors handy.)
func MaybeParenthesize(parentNode ast.Node, old, new ast.Expr) ast.Expr {
if needsParens(parentNode, old, new) {
new = &ast.ParenExpr{X: new}
}
return new
}
func needsParens(parentNode ast.Node, old, new ast.Expr) bool {
// An expression beneath a non-expression
// has no precedence ambiguity.
parent, ok := parentNode.(ast.Expr)
if !ok {
return false
}
precedence := func(n ast.Node) int {
switch n := n.(type) {
case *ast.UnaryExpr, *ast.StarExpr:
return token.UnaryPrec
case *ast.BinaryExpr:
return n.Op.Precedence()
}
return -1
}
// Parens are not required if the new node
// is not unary or binary.
newprec := precedence(new)
if newprec < 0 {
return false
}
// Parens are required if parent and child are both
// unary or binary and the parent has higher precedence.
if precedence(parent) > newprec {
return true
}
// Was the old node the operand of a postfix operator?
// f().sel
// f()[i:j]
// f()[i]
// f().(T)
// f()(x)
switch parent := parent.(type) {
case *ast.SelectorExpr:
return parent.X == old
case *ast.IndexExpr:
return parent.X == old
case *ast.SliceExpr:
return parent.X == old
case *ast.TypeAssertExpr:
return parent.X == old
case *ast.CallExpr:
return parent.Fun == old
}
return false
}
func is[T any](n any) bool {
_, ok := n.(T)
return ok
}

View File

@@ -0,0 +1,24 @@
// 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 graph
// AllPaths returns the set of nodes that are part of at least one path from src to dst.
func AllPaths[NodeID comparable](g Graph[NodeID], src, dst NodeID) map[NodeID]bool {
// We intersect the forward closure of 'src' with
// the reverse closure of 'dst'. This is not the most
// efficient implementation, but it's the clearest,
// and the previous one had bugs.
fwd := Reachable(g, src)
rev := Reachable(Transpose(g), dst)
// Intersection
for n := range fwd {
if !rev[n] {
delete(fwd, n)
}
}
return fwd
}

View File

@@ -0,0 +1,89 @@
// 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 graph
import "iter"
// A CompactGraph is a Graph with nodes that are compactly numbered from [0,
// NumNodes()).
//
// Compactly numbered graphs are useful for many graph algorithms, and many
// graph representations are naturally compact.
//
// To compact an arbitrary graph, use [Compact].
type CompactGraph interface {
Graph[int]
IsCompact()
}
// A nodePreserving graph is a transformation of another graph that preserves
// node IDs.
type nodePreserving interface {
Graph[int]
unwrapPreservingNodes() Graph[int]
}
// Compact takes a Graph with arbitrary NodeIDs and returns a compact graph.
//
// If g implements [CompactGraph], it assumes g is already compact and simply
// returns g and an identity mapping.
func Compact[NodeID comparable](g Graph[NodeID]) (CompactGraph, *Index[NodeID]) {
// If it's already compact, simply return it.
if gc, ok := g.(CompactGraph); ok {
// The above assertion ensures NodeID is int, so we know this type
// assertion will always succeed.
return gc, any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID])
}
// If it's a transformation and the underlying graph is compact, we can use
// an identity index. Though we still need to build a compactGraph to
// satisfy the CompactGraph interface.
g2, _ := g.(nodePreserving)
for g2 != nil {
unwrapped := g2.unwrapPreservingNodes()
if gc, ok := unwrapped.(CompactGraph); ok {
index := any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID])
cg := compactGraph[NodeID]{g, index}
return &cg, index
}
g2, _ = unwrapped.(nodePreserving)
}
// Nope, just build an index.
cg := compactGraph[NodeID]{g, NewIndex(g.Nodes())}
return &cg, cg.m
}
type compactGraph[NodeID comparable] struct {
g Graph[NodeID]
m *Index[NodeID]
}
func (g *compactGraph[NodeID]) Nodes() iter.Seq[int] {
return func(yield func(int) bool) {
for i := range g.g.NumNodes() {
if !yield(i) {
break
}
}
}
}
func (g *compactGraph[NodeID]) NumNodes() int {
return g.g.NumNodes()
}
func (g *compactGraph[NodeID]) Out(node int) iter.Seq[int] {
id := g.m.Value(node)
return func(yield func(int) bool) {
for nid := range g.g.Out(id) {
if !yield(g.m.Index(nid)) {
break
}
}
}
}
func (g *compactGraph[NodeID]) IsCompact() {}

View File

@@ -0,0 +1,33 @@
// 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 graph provides a common abstraction for directed graphs and standard
// graph algorithms.
//
// In general, this package does not provide or assume any concrete graph
// representation. It's up to the caller of this package to implement the
// [Graph] interface, either directly or as an adapter around another type.
package graph
import "iter"
// A Graph implements a directed graph where nodes in the graph are identified
// by the NodeID type.
//
// If a concrete graph type stores additional information about nodes and/or
// edges, it will conventionally provide methods of the form:
//
// Node(node NodeID) nodeInfo
// Edge(from, to NodeID) edgeInfo
type Graph[NodeID comparable] interface {
// Nodes yields all nodes in this graph.
Nodes() iter.Seq[NodeID]
// NumNodes returns the total number of nodes in this graph.
NumNodes() int
// Out yields the out-edges of node. Out must be deterministic, though
// otherwise there is no constraint on the order of the returned sequence.
Out(node NodeID) iter.Seq[NodeID]
}

View File

@@ -0,0 +1,96 @@
// 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 graph
import (
"fmt"
"iter"
"slices"
)
// An Index is an immutable, bijective map between [0, N) and an ordered list of keys.
type Index[Key comparable] struct {
// There are three Index representations:
//
// - If identN > 0, an identity map of [0, identN).
// - If index == nil, a sorted integer index in values.
// - Otherwise, a full index in values and index.
identN int
values []Key
index map[Key]int
}
// NewIndex returns an index for the specified list of values.
func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] {
vs := slices.Collect(values)
if len(vs) == 0 {
return new(Index[Key])
}
// Fast path: a naturally sorted list needs no index. (Sadly, there's no way
// to ask "is Key ordered?")
if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) {
return &Index[Key]{values: vs}
}
index := make(map[Key]int, len(vs))
for i, v := range vs {
index[v] = i
}
return &Index[Key]{values: vs, index: index}
}
// NewIdentityIndex returns an index that maps [0, n) to [0, n).
func NewIdentityIndex(n int) *Index[int] {
if n < 0 {
panic("n < 0")
}
// If n == 0, this is actually a "sorted integer index", but it doesn't
// matter because everything is out of bounds either way.
return &Index[int]{identN: n}
}
// Value maps an index to a key.
func (ix *Index[Key]) Value(index int) Key {
if ix.identN > 0 {
if index < 0 || index >= ix.identN {
panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN))
}
return any(index).(Key)
}
if index < 0 || index >= len(ix.values) {
panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN))
}
return ix.values[index]
}
// Index maps a key to an index.
func (ix *Index[Key]) Index(key Key) int {
if key, ok := any(key).(int); ok {
// Integer-only optimizations.
switch {
case ix.identN > 0:
// Identity.
if 0 <= key && key < ix.identN {
return key
}
goto oob
case ix.index == nil:
// Sorted integers.
if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok {
return i
}
goto oob
}
}
if i, ok := ix.index[key]; ok {
return i
}
oob:
panic(fmt.Sprintf("key %v not in index", key))
}

View File

@@ -0,0 +1,94 @@
// 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 graph
import "slices"
// Postorder returns the sequence of nodes in the spanning DAG of g, in
// postorder.
//
// For rootless subgraphs, it breaks cycles by starting at the lowest numbered
// node.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID {
cg, nodeMap := Compact(g)
numNodes := cg.NumNodes()
if numNodes == 0 {
return nil
}
result := make([]NodeID, 0, numNodes)
visited := newBitset(numNodes)
onStack := newBitset(numNodes)
// visit performs a Depth-First Search.
var visit func(u int)
visit = func(u int) {
if !visited.add(u) {
return
}
onStack.add(u)
for v := range cg.Out(u) {
if onStack.contains(v) {
// Cycle detected (back-edge).
// To resolve, we simply skip processing this edge further in the
// current recursion, effectively "breaking" the cycle at this point.
continue
}
visit(v)
}
onStack.remove(u)
// Post-order: add to result after all descendants are processed.
result = append(result, nodeMap.Value(u))
}
// Visit every node in ascending order to ensure stability.
for u := range numNodes {
visit(u)
}
return result
}
// ReversePostorder returns the nodes of the graph in reverse post-order.
//
// If g is a directed acyclic graph (DAG), the result is a topological sort of
// g.
//
// See [Postorder] for how this handles back-edges and cycles.
//
// This algorithm runs in O(V + E) time and O(V + E) space.
func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID {
result := Postorder(g)
slices.Reverse(result)
return result
}
// bitset is a simple fixed-size bitset used to reduce memory overhead.
type bitset []uint64
func newBitset(n int) bitset {
return make(bitset, (n+63)/64)
}
func (b bitset) add(u int) bool {
if b.contains(u) {
return false
}
b[u/64] |= 1 << (u % 64)
return true
}
func (b bitset) remove(u int) {
b[u/64] &= ^(1 << (u % 64))
}
func (b bitset) contains(u int) bool {
return b[u/64]&(1<<(u%64)) != 0
}

View File

@@ -0,0 +1,23 @@
// 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 graph
// Reachable returns the set of nodes reachable from the given roots.
func Reachable[NodeID comparable](g Graph[NodeID], roots ...NodeID) map[NodeID]bool {
seen := make(map[NodeID]bool)
var visit func(node NodeID)
visit = func(node NodeID) {
if !seen[node] {
seen[node] = true
for e := range g.Out(node) {
visit(e)
}
}
}
for _, root := range roots {
visit(root)
}
return seen
}

View File

@@ -0,0 +1,39 @@
// 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 graph
import "slices"
// SCCs computes the strongly connected components of the graph g.
func SCCs[NodeID comparable](g Graph[NodeID]) [][]NodeID {
// Use Kosaraju's algorithm. Tarjan is overkill here.
// Forward pass
S := Postorder(g)
// Reverse pass
gt := Transpose(g)
seen := make(map[NodeID]bool)
var scc []NodeID
var sccs [][]NodeID
var rvisit func(NodeID)
rvisit = func(u NodeID) {
if !seen[u] {
seen[u] = true
scc = append(scc, u)
for v := range gt.Out(u) {
rvisit(v)
}
}
}
for _, root := range slices.Backward(S) {
if !seen[root] {
scc = nil
rvisit(root)
sccs = append(sccs, scc)
}
}
return sccs
}

View File

@@ -0,0 +1,45 @@
// 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 graph
import "slices"
// ShortestPath returns a shortest path from src to dst in g.
// It returns the path as a slice of nodes starting with src and ending with dst.
// If no path is found, it returns nil.
func ShortestPath[NodeID comparable](g Graph[NodeID], src, dst NodeID) []NodeID {
if src == dst {
return []NodeID{src}
}
pred := make(map[NodeID]NodeID)
queue := []NodeID{src}
// Mark src as seen.
pred[src] = src
for len(queue) > 0 {
n := queue[0]
queue = queue[1:]
if n == dst {
// Reconstruct path
var path []NodeID
for curr := dst; curr != src; curr = pred[curr] {
path = append(path, curr)
}
path = append(path, src)
slices.Reverse(path)
return path
}
for v := range g.Out(n) {
if _, seen := pred[v]; !seen {
pred[v] = n
queue = append(queue, v)
}
}
}
return nil
}

View File

@@ -0,0 +1,51 @@
// 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 graph
import (
"iter"
"slices"
)
type transpose[NodeID comparable] struct {
Graph Graph[NodeID]
preds map[NodeID][]NodeID
}
// transpose returns a graph like g but with all edges reversed. Node IDs are
// identical to the underlying graph.
//
// Transpose preserves compactness.
func Transpose[NodeID comparable](g Graph[NodeID]) Graph[NodeID] {
if g, ok := g.(transpose[NodeID]); ok {
// Transpose(Transpose(g)) == g
return g.Graph
}
preds := make(map[NodeID][]NodeID)
for nid := range g.Nodes() {
for succ := range g.Out(nid) {
preds[succ] = append(preds[succ], nid)
}
}
return transpose[NodeID]{g, preds}
}
func (t transpose[NodeID]) NumNodes() int {
return len(t.preds)
}
func (t transpose[NodeID]) Nodes() iter.Seq[NodeID] {
return t.Graph.Nodes()
}
func (t transpose[NodeID]) Out(n NodeID) iter.Seq[NodeID] {
return slices.Values(t.preds[n])
}
//lint:ignore U1000 False positive in Staticcheck 2026.1 and older.
func (t transpose[NodeID]) unwrapPreservingNodes() Graph[NodeID] {
return t.Graph
}

View File

@@ -0,0 +1,63 @@
// Copyright 2025 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 moreiters
import "iter"
// First returns the first value of seq and true.
// If seq is empty, it returns the zero value of T and false.
func First[T any](seq iter.Seq[T]) (z T, ok bool) {
for t := range seq {
return t, true
}
return z, false
}
// Contains reports whether x is an element of the sequence seq.
func Contains[T comparable](seq iter.Seq[T], x T) bool {
for cand := range seq {
if cand == x {
return true
}
}
return false
}
// Every reports whether every pred(t) for t in seq returns true,
// stopping at the first false element.
func Every[T any](seq iter.Seq[T], pred func(T) bool) bool {
for t := range seq {
if !pred(t) {
return false
}
}
return true
}
// Any reports whether any pred(t) for t in seq returns true.
func Any[T any](seq iter.Seq[T], pred func(T) bool) bool {
for t := range seq {
if pred(t) {
return true
}
}
return false
}
// Len returns the number of elements in the sequence (by iterating).
func Len[T any](seq iter.Seq[T]) (n int) {
for range seq {
n++
}
return
}
// Empty reports whether the sequence contains no elements.
func Empty[T any](seq iter.Seq[T]) bool {
for range seq {
return false
}
return true
}

View File

@@ -0,0 +1,68 @@
// Copyright 2021 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 typeparams contains common utilities for writing tools that
// interact with generic Go code, as introduced with Go 1.18. It
// supplements the standard library APIs. Notably, the StructuralTerms
// API computes a minimal representation of the structural
// restrictions on a type parameter.
//
// An external version of these APIs is available in the
// golang.org/x/exp/typeparams module.
package typeparams
import (
"go/ast"
"go/token"
"go/types"
)
// UnpackIndexExpr extracts data from AST nodes that represent index
// expressions.
//
// For an ast.IndexExpr, the resulting indices slice will contain exactly one
// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable
// number of index expressions.
//
// For nodes that don't represent index expressions, the first return value of
// UnpackIndexExpr will be nil.
func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) {
switch e := n.(type) {
case *ast.IndexExpr:
return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
case *ast.IndexListExpr:
return e.X, e.Lbrack, e.Indices, e.Rbrack
}
return nil, token.NoPos, nil, token.NoPos
}
// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on
// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0
// will panic.
func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr {
switch len(indices) {
case 0:
panic("empty indices")
case 1:
return &ast.IndexExpr{
X: x,
Lbrack: lbrack,
Index: indices[0],
Rbrack: rbrack,
}
default:
return &ast.IndexListExpr{
X: x,
Lbrack: lbrack,
Indices: indices,
Rbrack: rbrack,
}
}
}
// IsTypeParam reports whether t is a type parameter (or an alias of one).
func IsTypeParam(t types.Type) bool {
_, ok := types.Unalias(t).(*types.TypeParam)
return ok
}

View File

@@ -0,0 +1,157 @@
// 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 typeparams
import (
"fmt"
"go/types"
)
// CoreType returns the core type of T or nil if T does not have a core type.
//
// As of Go1.25, the notion of a core type has been removed from the language spec.
// See https://go.dev/blog/coretypes for more details.
// TODO(mkalil): We should eventually consider removing all uses of CoreType.
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) {
// From the deprecated core types spec:
// "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.
}
// From the deprecated core types spec:
// "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
}
// 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.
func NormalTerms(T types.Type) ([]*types.Term, error) {
// typeSetOf(T) == typeSetOf(Unalias(T))
typ := types.Unalias(T)
if named, ok := typ.(*types.Named); ok {
typ = named.Underlying()
}
switch typ := typ.(type) {
case *types.TypeParam:
return StructuralTerms(typ)
case *types.Union:
return UnionTermSet(typ)
case *types.Interface:
return InterfaceTermSet(typ)
default:
return []*types.Term{types.NewTerm(false, T)}, nil
}
}
// Deref returns the type of the variable pointed to by t,
// if t's core type is a pointer; otherwise it returns t.
//
// Do not assume that Deref(T)==T implies T is not a pointer:
// consider "type T *T", for example.
//
// TODO(adonovan): ideally this would live in typesinternal, but that
// creates an import cycle. Move there when we melt this package down.
func Deref(t types.Type) types.Type {
if ptr, ok := CoreType(t).(*types.Pointer); ok {
return ptr.Elem()
}
return t
}
// MustDeref returns the type of the variable pointed to by t.
// It panics if t's core type is not a pointer.
//
// TODO(adonovan): ideally this would live in typesinternal, but that
// creates an import cycle. Move there when we melt this package down.
func MustDeref(t types.Type) types.Type {
if ptr, ok := CoreType(t).(*types.Pointer); ok {
return ptr.Elem()
}
panic(fmt.Sprintf("%v is not a pointer", t))
}

View File

@@ -0,0 +1,129 @@
// Copyright 2024 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 typeparams
import (
"go/types"
)
// Free is a memoization of the set of free type parameters within a
// type. It makes a sequence of calls to [Free.Has] for overlapping
// types more efficient. The zero value is ready for use.
//
// NOTE: Adapted from go/types/infer.go. If it is later exported, factor.
type Free struct {
seen map[types.Type]bool
}
// Has reports whether the specified type has a free type parameter.
func (w *Free) Has(typ types.Type) (res bool) {
// detect cycles
if x, ok := w.seen[typ]; ok {
return x
}
if w.seen == nil {
w.seen = make(map[types.Type]bool)
}
w.seen[typ] = false
defer func() {
w.seen[typ] = res
}()
switch t := typ.(type) {
case nil, *types.Basic: // TODO(gri) should nil be handled here?
break
case *types.Alias:
if t.TypeParams().Len() > t.TypeArgs().Len() {
return true // This is an uninstantiated Alias.
}
// The expansion of an alias can have free type parameters,
// whether or not the alias itself has type parameters:
//
// func _[K comparable]() {
// type Set = map[K]bool // free(Set) = {K}
// type MapTo[V] = map[K]V // free(Map[foo]) = {V}
// }
//
// So, we must Unalias.
return w.Has(types.Unalias(t))
case *types.Array:
return w.Has(t.Elem())
case *types.Slice:
return w.Has(t.Elem())
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
if w.Has(t.Field(i).Type()) {
return true
}
}
case *types.Pointer:
return w.Has(t.Elem())
case *types.Tuple:
n := t.Len()
for i := range n {
if w.Has(t.At(i).Type()) {
return true
}
}
case *types.Signature:
// t.tparams may not be nil if we are looking at a signature
// of a generic function type (or an interface method) that is
// part of the type we're testing. We don't care about these type
// parameters.
// Similarly, the receiver of a method may declare (rather than
// use) type parameters, we don't care about those either.
// Thus, we only need to look at the input and result parameters.
return w.Has(t.Params()) || w.Has(t.Results())
case *types.Interface:
for i, n := 0, t.NumMethods(); i < n; i++ {
if w.Has(t.Method(i).Type()) {
return true
}
}
terms, err := InterfaceTermSet(t)
if err != nil {
return false // ill typed
}
for _, term := range terms {
if w.Has(term.Type()) {
return true
}
}
case *types.Map:
return w.Has(t.Key()) || w.Has(t.Elem())
case *types.Chan:
return w.Has(t.Elem())
case *types.Named:
args := t.TypeArgs()
if params := t.TypeParams(); params.Len() > args.Len() {
return true // this is an uninstantiated named type.
}
for i, n := 0, args.Len(); i < n; i++ {
if w.Has(args.At(i)) {
return true
}
}
return w.Has(t.Underlying()) // recurse for types local to parameterized functions
case *types.TypeParam:
return true
default:
panic(t) // unreachable
}
return false
}

View File

@@ -0,0 +1,214 @@
// Copyright 2021 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 typeparams
import (
"errors"
"fmt"
"go/types"
"os"
"strings"
)
const debug = false
var ErrEmptyTypeSet = errors.New("empty type set")
// StructuralTerms returns a slice of terms representing the normalized
// structural type restrictions of a type parameter, if any.
//
// 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.
//
// StructuralTerms 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, StructuralTerms
// returns a slice of tilde terms corresponding to the terms of the union in
// the normalized structural restriction. An error is returned if the
// constraint interface is invalid, exceeds complexity bounds, or has an empty
// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet.
//
// StructuralTerms makes no guarantees about the order of terms, except that it
// is deterministic.
func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) {
constraint := tparam.Constraint()
if constraint == nil {
return nil, fmt.Errorf("%s has nil constraint", tparam)
}
iface, _ := constraint.Underlying().(*types.Interface)
if iface == nil {
return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying())
}
return InterfaceTermSet(iface)
}
// InterfaceTermSet computes the normalized terms for a constraint interface,
// returning an error if the term set cannot be computed or is empty. In the
// latter case, the error will be ErrEmptyTypeSet.
//
// See the documentation of StructuralTerms for more information on
// normalization.
func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) {
return computeTermSet(iface)
}
// UnionTermSet computes the normalized terms for a union, returning an error
// if the term set cannot be computed or is empty. In the latter case, the
// error will be ErrEmptyTypeSet.
//
// See the documentation of StructuralTerms for more information on
// normalization.
func UnionTermSet(union *types.Union) ([]*types.Term, error) {
return computeTermSet(union)
}
func computeTermSet(typ types.Type) ([]*types.Term, error) {
tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
if err != nil {
return nil, err
}
if tset.terms.isEmpty() {
return nil, ErrEmptyTypeSet
}
if tset.terms.isAll() {
return nil, nil
}
var terms []*types.Term
for _, term := range tset.terms {
terms = append(terms, types.NewTerm(term.tilde, term.typ))
}
return terms, nil
}
// A termSet holds the normalized set of terms for a given type.
//
// The name termSet is intentionally distinct from 'type set': a type set is
// all types that implement a type (and includes method restrictions), whereas
// a term set just represents the structural restrictions on a type.
type termSet struct {
complete bool
terms termlist
}
func indentf(depth int, format string, args ...any) {
fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...)
}
func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) {
if t == nil {
panic("nil type")
}
if debug {
indentf(depth, "%s", t.String())
defer func() {
if err != nil {
indentf(depth, "=> %s", err)
} else {
indentf(depth, "=> %s", res.terms.String())
}
}()
}
const maxTermCount = 100
if tset, ok := seen[t]; ok {
if !tset.complete {
return nil, fmt.Errorf("cycle detected in the declaration of %s", t)
}
return tset, nil
}
// Mark the current type as seen to avoid infinite recursion.
tset := new(termSet)
defer func() {
tset.complete = true
}()
seen[t] = tset
switch u := t.Underlying().(type) {
case *types.Interface:
// The term set of an interface is the intersection of the term sets of its
// embedded types.
tset.terms = allTermlist
for embedded := range u.EmbeddedTypes() {
if _, ok := embedded.Underlying().(*types.TypeParam); ok {
return nil, fmt.Errorf("invalid embedded type %T", embedded)
}
tset2, err := computeTermSetInternal(embedded, seen, depth+1)
if err != nil {
return nil, err
}
tset.terms = tset.terms.intersect(tset2.terms)
}
case *types.Union:
// The term set of a union is the union of term sets of its terms.
tset.terms = nil
for t := range u.Terms() {
var terms termlist
switch t.Type().Underlying().(type) {
case *types.Interface:
tset2, err := computeTermSetInternal(t.Type(), seen, depth+1)
if err != nil {
return nil, err
}
terms = tset2.terms
case *types.TypeParam, *types.Union:
// A stand-alone type parameter or union is not permitted as union
// term.
return nil, fmt.Errorf("invalid union term %T", t)
default:
if t.Type() == types.Typ[types.Invalid] {
continue
}
terms = termlist{{t.Tilde(), t.Type()}}
}
tset.terms = tset.terms.union(terms)
if len(tset.terms) > maxTermCount {
return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
}
}
case *types.TypeParam:
panic("unreachable")
default:
// For all other types, the term set is just a single non-tilde term
// holding the type itself.
if u != types.Typ[types.Invalid] {
tset.terms = termlist{{false, t}}
}
}
return tset, nil
}
// under is a facade for the go/types internal function of the same name. It is
// used by typeterm.go.
func under(t types.Type) types.Type {
return t.Underlying()
}

View File

@@ -0,0 +1,169 @@
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
// Source: ../../cmd/compile/internal/types2/termlist.go
// Copyright 2021 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.
// Code generated by copytermlist.go DO NOT EDIT.
package typeparams
import (
"go/types"
"strings"
)
// A termlist represents the type set represented by the union
// t1 y2 ... tn of the type sets of the terms t1 to tn.
// A termlist is in normal form if all terms are disjoint.
// termlist operations don't require the operands to be in
// normal form.
type termlist []*term
// allTermlist represents the set of all types.
// It is in normal form.
var allTermlist = termlist{new(term)}
// termSep is the separator used between individual terms.
const termSep = " | "
// String prints the termlist exactly (without normalization).
func (xl termlist) String() string {
if len(xl) == 0 {
return "∅"
}
var buf strings.Builder
for i, x := range xl {
if i > 0 {
buf.WriteString(termSep)
}
buf.WriteString(x.String())
}
return buf.String()
}
// isEmpty reports whether the termlist xl represents the empty set of types.
func (xl termlist) isEmpty() bool {
// If there's a non-nil term, the entire list is not empty.
// If the termlist is in normal form, this requires at most
// one iteration.
for _, x := range xl {
if x != nil {
return false
}
}
return true
}
// isAll reports whether the termlist xl represents the set of all types.
func (xl termlist) isAll() bool {
// If there's a 𝓤 term, the entire list is 𝓤.
// If the termlist is in normal form, this requires at most
// one iteration.
for _, x := range xl {
if x != nil && x.typ == nil {
return true
}
}
return false
}
// norm returns the normal form of xl.
func (xl termlist) norm() termlist {
// Quadratic algorithm, but good enough for now.
// TODO(gri) fix asymptotic performance
used := make([]bool, len(xl))
var rl termlist
for i, xi := range xl {
if xi == nil || used[i] {
continue
}
for j := i + 1; j < len(xl); j++ {
xj := xl[j]
if xj == nil || used[j] {
continue
}
if u1, u2 := xi.union(xj); u2 == nil {
// If we encounter a 𝓤 term, the entire list is 𝓤.
// Exit early.
// (Note that this is not just an optimization;
// if we continue, we may end up with a 𝓤 term
// and other terms and the result would not be
// in normal form.)
if u1.typ == nil {
return allTermlist
}
xi = u1
used[j] = true // xj is now unioned into xi - ignore it in future iterations
}
}
rl = append(rl, xi)
}
return rl
}
// union returns the union xl yl.
func (xl termlist) union(yl termlist) termlist {
return append(xl, yl...).norm()
}
// intersect returns the intersection xl ∩ yl.
func (xl termlist) intersect(yl termlist) termlist {
if xl.isEmpty() || yl.isEmpty() {
return nil
}
// Quadratic algorithm, but good enough for now.
// TODO(gri) fix asymptotic performance
var rl termlist
for _, x := range xl {
for _, y := range yl {
if r := x.intersect(y); r != nil {
rl = append(rl, r)
}
}
}
return rl.norm()
}
// equal reports whether xl and yl represent the same type set.
func (xl termlist) equal(yl termlist) bool {
// TODO(gri) this should be more efficient
return xl.subsetOf(yl) && yl.subsetOf(xl)
}
// includes reports whether t ∈ xl.
func (xl termlist) includes(t types.Type) bool {
for _, x := range xl {
if x.includes(t) {
return true
}
}
return false
}
// supersetOf reports whether y ⊆ xl.
func (xl termlist) supersetOf(y *term) bool {
for _, x := range xl {
if y.subsetOf(x) {
return true
}
}
return false
}
// subsetOf reports whether xl ⊆ yl.
func (xl termlist) subsetOf(yl termlist) bool {
if yl.isEmpty() {
return xl.isEmpty()
}
// each term x of xl must be a subset of yl
for _, x := range xl {
if !yl.supersetOf(x) {
return false // x is not a subset yl
}
}
return true
}

View File

@@ -0,0 +1,172 @@
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
// Source: ../../cmd/compile/internal/types2/typeterm.go
// Copyright 2021 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.
// Code generated by copytermlist.go DO NOT EDIT.
package typeparams
import "go/types"
// A term describes elementary type sets:
//
// ∅: (*term)(nil) == ∅ // set of no types (empty set)
// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse)
// T: &term{false, T} == {T} // set of type T
// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t
type term struct {
tilde bool // valid if typ != nil
typ types.Type
}
func (x *term) String() string {
switch {
case x == nil:
return "∅"
case x.typ == nil:
return "𝓤"
case x.tilde:
return "~" + x.typ.String()
default:
return x.typ.String()
}
}
// equal reports whether x and y represent the same type set.
func (x *term) equal(y *term) bool {
// easy cases
switch {
case x == nil || y == nil:
return x == y
case x.typ == nil || y.typ == nil:
return x.typ == y.typ
}
// ∅ ⊂ x, y ⊂ 𝓤
return x.tilde == y.tilde && types.Identical(x.typ, y.typ)
}
// union returns the union x y: zero, one, or two non-nil terms.
func (x *term) union(y *term) (_, _ *term) {
// easy cases
switch {
case x == nil && y == nil:
return nil, nil // ∅ ∅ == ∅
case x == nil:
return y, nil // ∅ y == y
case y == nil:
return x, nil // x ∅ == x
case x.typ == nil:
return x, nil // 𝓤 y == 𝓤
case y.typ == nil:
return y, nil // x 𝓤 == 𝓤
}
// ∅ ⊂ x, y ⊂ 𝓤
if x.disjoint(y) {
return x, y // x y == (x, y) if x ∩ y == ∅
}
// x.typ == y.typ
// ~t ~t == ~t
// ~t T == ~t
// T ~t == ~t
// T T == T
if x.tilde || !y.tilde {
return x, nil
}
return y, nil
}
// intersect returns the intersection x ∩ y.
func (x *term) intersect(y *term) *term {
// easy cases
switch {
case x == nil || y == nil:
return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅
case x.typ == nil:
return y // 𝓤 ∩ y == y
case y.typ == nil:
return x // x ∩ 𝓤 == x
}
// ∅ ⊂ x, y ⊂ 𝓤
if x.disjoint(y) {
return nil // x ∩ y == ∅ if x ∩ y == ∅
}
// x.typ == y.typ
// ~t ∩ ~t == ~t
// ~t ∩ T == T
// T ∩ ~t == T
// T ∩ T == T
if !x.tilde || y.tilde {
return x
}
return y
}
// includes reports whether t ∈ x.
func (x *term) includes(t types.Type) bool {
// easy cases
switch {
case x == nil:
return false // t ∈ ∅ == false
case x.typ == nil:
return true // t ∈ 𝓤 == true
}
// ∅ ⊂ x ⊂ 𝓤
u := t
if x.tilde {
u = under(u)
}
return types.Identical(x.typ, u)
}
// subsetOf reports whether x ⊆ y.
func (x *term) subsetOf(y *term) bool {
// easy cases
switch {
case x == nil:
return true // ∅ ⊆ y == true
case y == nil:
return false // x ⊆ ∅ == false since x != ∅
case y.typ == nil:
return true // x ⊆ 𝓤 == true
case x.typ == nil:
return false // 𝓤 ⊆ y == false since y != 𝓤
}
// ∅ ⊂ x, y ⊂ 𝓤
if x.disjoint(y) {
return false // x ⊆ y == false if x ∩ y == ∅
}
// x.typ == y.typ
// ~t ⊆ ~t == true
// ~t ⊆ T == false
// T ⊆ ~t == true
// T ⊆ T == true
return !x.tilde || y.tilde
}
// disjoint reports whether x ∩ y == ∅.
// x.typ and y.typ must not be nil.
func (x *term) disjoint(y *term) bool {
if debug && (x.typ == nil || y.typ == nil) {
panic("invalid argument(s)")
}
ux := x.typ
if y.tilde {
ux = under(ux)
}
uy := y.typ
if x.tilde {
uy = under(uy)
}
return !types.Identical(ux, uy)
}

View File

@@ -0,0 +1,137 @@
// Copyright 2018 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 typesinternal
import (
"fmt"
"go/ast"
"go/types"
_ "unsafe" // for go:linkname hack
)
// CallKind describes the function position of an [*ast.CallExpr].
type CallKind int
const (
CallStatic CallKind = iota // static call to known function
CallInterface // dynamic call through an interface method
CallDynamic // dynamic call of a func value
CallBuiltin // call to a builtin function
CallConversion // a conversion (not a call)
)
var callKindNames = []string{
"CallStatic",
"CallInterface",
"CallDynamic",
"CallBuiltin",
"CallConversion",
}
func (k CallKind) String() string {
if i := int(k); i >= 0 && i < len(callKindNames) {
return callKindNames[i]
}
return fmt.Sprintf("typeutil.CallKind(%d)", k)
}
// ClassifyCall classifies the function position of a call expression ([*ast.CallExpr]).
// It distinguishes among true function calls, calls to builtins, and type conversions,
// and further classifies function calls as static calls (where the function is known),
// dynamic interface calls, and other dynamic calls.
//
// For the declarations:
//
// func f() {}
// func g[T any]() {}
// var v func()
// var s []func()
// type I interface { M() }
// var i I
//
// ClassifyCall returns the following:
//
// f() CallStatic
// g[int]() CallStatic
// i.M() CallInterface
// min(1, 2) CallBuiltin
// v() CallDynamic
// s[0]() CallDynamic
// int(x) CallConversion
// []byte("") CallConversion
func ClassifyCall(info *types.Info, call *ast.CallExpr) CallKind {
if info.Types == nil {
panic("ClassifyCall: info.Types is nil")
}
tv := info.Types[call.Fun]
if tv.IsType() {
return CallConversion
}
if tv.IsBuiltin() {
return CallBuiltin
}
obj := info.Uses[UsedIdent(info, call.Fun)]
// Classify the call by the type of the object, if any.
switch obj := obj.(type) {
case *types.Func:
if interfaceMethod(obj) {
return CallInterface
}
return CallStatic
default:
return CallDynamic
}
}
// UsedIdent returns the identifier such that info.Uses[UsedIdent(info, e)]
// is the [types.Object] used by e, if any.
//
// If e is one of various forms of reference:
//
// f, c, v, T lexical reference
// pkg.X qualified identifier
// f[T] or pkg.F[K,V] instantiations of the above kinds
// expr.f field or method value selector
// T.f method expression selector
//
// UsedIdent returns the identifier whose is associated value in [types.Info.Uses]
// is the object to which it refers.
//
// For the declarations:
//
// func F[T any] {...}
// type I interface { M() }
// var (
// x int
// s struct { f int }
// a []int
// i I
// )
//
// UsedIdent returns the following:
//
// Expr UsedIdent
// x x
// s.f f
// F[int] F
// i.M M
// I.M M
// min min
// int int
// 1 nil
// a[0] nil
// []byte nil
//
// Note: if e is an instantiated function or method, UsedIdent returns
// the corresponding generic function or method on the generic type.
func UsedIdent(info *types.Info, e ast.Expr) *ast.Ident {
return usedIdent(info, e)
}
//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent
func usedIdent(info *types.Info, e ast.Expr) *ast.Ident
//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod
func interfaceMethod(f *types.Func) bool

View File

@@ -0,0 +1,137 @@
// Copyright 2024 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 typesinternal
import (
"fmt"
"go/types"
"golang.org/x/tools/go/types/typeutil"
)
// ForEachElement calls f for type T and each type reachable from its
// type through reflection. It does this by recursively stripping off
// type constructors; in addition, for each named type N, the type *N
// is added to the result as it may have additional methods.
//
// The caller must provide an initially empty set used to de-duplicate
// identical types, potentially across multiple calls to ForEachElement.
// (Its final value holds all the elements seen, matching the arguments
// passed to f.)
//
// TODO(adonovan): share/harmonize with go/callgraph/rta.
func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) {
var visit func(T types.Type, skip bool)
visit = func(T types.Type, skip bool) {
if !skip {
if seen, _ := rtypes.Set(T, true).(bool); seen {
return // de-dup
}
f(T) // notify caller of new element type
}
// Recursion over signatures of each method.
tmset := msets.MethodSet(T)
for method := range tmset.Methods() {
sig := method.Type().(*types.Signature)
if sig.TypeParams() != nil {
continue // skip type-parameterized methods
}
// It is tempting to call visit(sig, false)
// but, as noted in golang.org/cl/65450043,
// the Signature.Recv field is ignored by
// types.Identical and typeutil.Map, which
// is confusing at best.
//
// More importantly, the true signature rtype
// reachable from a method using reflection
// has no receiver but an extra ordinary parameter.
// For the Read method of io.Reader we want:
// func(Reader, []byte) (int, error)
// but here sig is:
// func([]byte) (int, error)
// with .Recv = Reader (though it is hard to
// notice because it doesn't affect Signature.String
// or types.Identical).
//
// TODO(adonovan): construct and visit the correct
// non-method signature with an extra parameter
// (though since unnamed func types have no methods
// there is essentially no actual demand for this).
//
// TODO(adonovan): document whether or not it is
// safe to skip non-exported methods (as RTA does).
visit(sig.Params(), true) // skip the Tuple
visit(sig.Results(), true) // skip the Tuple
}
switch T := T.(type) {
case *types.Alias:
visit(types.Unalias(T), skip) // emulates the pre-Alias behavior
case *types.Basic:
// nop
case *types.Interface:
// nop---handled by recursion over method set.
case *types.Pointer:
visit(T.Elem(), false)
case *types.Slice:
visit(T.Elem(), false)
case *types.Chan:
visit(T.Elem(), false)
case *types.Map:
visit(T.Key(), false)
visit(T.Elem(), false)
case *types.Signature:
if T.Recv() != nil {
panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv()))
}
visit(T.Params(), true) // skip the Tuple
visit(T.Results(), true) // skip the Tuple
case *types.Named:
// A pointer-to-named type can be derived from a named
// type via reflection. It may have methods too.
visit(types.NewPointer(T), false)
// Consider 'type T struct{S}' where S has methods.
// Reflection provides no way to get from T to struct{S},
// only to S, so the method set of struct{S} is unwanted,
// so set 'skip' flag during recursion.
visit(T.Underlying(), true) // skip the unnamed type
case *types.Array:
visit(T.Elem(), false)
case *types.Struct:
for i, n := 0, T.NumFields(); i < n; i++ {
// TODO(adonovan): document whether or not
// it is safe to skip non-exported fields.
visit(T.Field(i).Type(), false)
}
case *types.Tuple:
for i, n := 0, T.Len(); i < n; i++ {
visit(T.At(i).Type(), false)
}
case *types.TypeParam, *types.Union:
// forEachReachable must not be called on parameterized types.
panic(fmt.Sprintf("ForEachElement called on type containing %T", T))
default:
panic(fmt.Sprintf("ForEachElement called on unexpected type %T", T))
}
}
visit(T, false)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.
package typesinternal
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[InvalidSyntaxTree - -1]
_ = x[Test-1]
_ = x[BlankPkgName-2]
_ = x[MismatchedPkgName-3]
_ = x[InvalidPkgUse-4]
_ = x[BadImportPath-5]
_ = x[BrokenImport-6]
_ = x[ImportCRenamed-7]
_ = x[UnusedImport-8]
_ = x[InvalidInitCycle-9]
_ = x[DuplicateDecl-10]
_ = x[InvalidDeclCycle-11]
_ = x[InvalidTypeCycle-12]
_ = x[InvalidConstInit-13]
_ = x[InvalidConstVal-14]
_ = x[InvalidConstType-15]
_ = x[UntypedNilUse-16]
_ = x[WrongAssignCount-17]
_ = x[UnassignableOperand-18]
_ = x[NoNewVar-19]
_ = x[MultiValAssignOp-20]
_ = x[InvalidIfaceAssign-21]
_ = x[InvalidChanAssign-22]
_ = x[IncompatibleAssign-23]
_ = x[UnaddressableFieldAssign-24]
_ = x[NotAType-25]
_ = x[InvalidArrayLen-26]
_ = x[BlankIfaceMethod-27]
_ = x[IncomparableMapKey-28]
_ = x[InvalidIfaceEmbed-29]
_ = x[InvalidPtrEmbed-30]
_ = x[BadRecv-31]
_ = x[InvalidRecv-32]
_ = x[DuplicateFieldAndMethod-33]
_ = x[DuplicateMethod-34]
_ = x[InvalidBlank-35]
_ = x[InvalidIota-36]
_ = x[MissingInitBody-37]
_ = x[InvalidInitSig-38]
_ = x[InvalidInitDecl-39]
_ = x[InvalidMainDecl-40]
_ = x[TooManyValues-41]
_ = x[NotAnExpr-42]
_ = x[TruncatedFloat-43]
_ = x[NumericOverflow-44]
_ = x[UndefinedOp-45]
_ = x[MismatchedTypes-46]
_ = x[DivByZero-47]
_ = x[NonNumericIncDec-48]
_ = x[UnaddressableOperand-49]
_ = x[InvalidIndirection-50]
_ = x[NonIndexableOperand-51]
_ = x[InvalidIndex-52]
_ = x[SwappedSliceIndices-53]
_ = x[NonSliceableOperand-54]
_ = x[InvalidSliceExpr-55]
_ = x[InvalidShiftCount-56]
_ = x[InvalidShiftOperand-57]
_ = x[InvalidReceive-58]
_ = x[InvalidSend-59]
_ = x[DuplicateLitKey-60]
_ = x[MissingLitKey-61]
_ = x[InvalidLitIndex-62]
_ = x[OversizeArrayLit-63]
_ = x[MixedStructLit-64]
_ = x[InvalidStructLit-65]
_ = x[MissingLitField-66]
_ = x[DuplicateLitField-67]
_ = x[UnexportedLitField-68]
_ = x[InvalidLitField-69]
_ = x[UntypedLit-70]
_ = x[InvalidLit-71]
_ = x[AmbiguousSelector-72]
_ = x[UndeclaredImportedName-73]
_ = x[UnexportedName-74]
_ = x[UndeclaredName-75]
_ = x[MissingFieldOrMethod-76]
_ = x[BadDotDotDotSyntax-77]
_ = x[NonVariadicDotDotDot-78]
_ = x[MisplacedDotDotDot-79]
_ = x[InvalidDotDotDotOperand-80]
_ = x[InvalidDotDotDot-81]
_ = x[UncalledBuiltin-82]
_ = x[InvalidAppend-83]
_ = x[InvalidCap-84]
_ = x[InvalidClose-85]
_ = x[InvalidCopy-86]
_ = x[InvalidComplex-87]
_ = x[InvalidDelete-88]
_ = x[InvalidImag-89]
_ = x[InvalidLen-90]
_ = x[SwappedMakeArgs-91]
_ = x[InvalidMake-92]
_ = x[InvalidReal-93]
_ = x[InvalidAssert-94]
_ = x[ImpossibleAssert-95]
_ = x[InvalidConversion-96]
_ = x[InvalidUntypedConversion-97]
_ = x[BadOffsetofSyntax-98]
_ = x[InvalidOffsetof-99]
_ = x[UnusedExpr-100]
_ = x[UnusedVar-101]
_ = x[MissingReturn-102]
_ = x[WrongResultCount-103]
_ = x[OutOfScopeResult-104]
_ = x[InvalidCond-105]
_ = x[InvalidPostDecl-106]
_ = x[InvalidChanRange-107]
_ = x[InvalidIterVar-108]
_ = x[InvalidRangeExpr-109]
_ = x[MisplacedBreak-110]
_ = x[MisplacedContinue-111]
_ = x[MisplacedFallthrough-112]
_ = x[DuplicateCase-113]
_ = x[DuplicateDefault-114]
_ = x[BadTypeKeyword-115]
_ = x[InvalidTypeSwitch-116]
_ = x[InvalidExprSwitch-117]
_ = x[InvalidSelectCase-118]
_ = x[UndeclaredLabel-119]
_ = x[DuplicateLabel-120]
_ = x[MisplacedLabel-121]
_ = x[UnusedLabel-122]
_ = x[JumpOverDecl-123]
_ = x[JumpIntoBlock-124]
_ = x[InvalidMethodExpr-125]
_ = x[WrongArgCount-126]
_ = x[InvalidCall-127]
_ = x[UnusedResults-128]
_ = x[InvalidDefer-129]
_ = x[InvalidGo-130]
_ = x[BadDecl-131]
_ = x[RepeatedDecl-132]
_ = x[InvalidUnsafeAdd-133]
_ = x[InvalidUnsafeSlice-134]
_ = x[UnsupportedFeature-135]
_ = x[NotAGenericType-136]
_ = x[WrongTypeArgCount-137]
_ = x[CannotInferTypeArgs-138]
_ = x[InvalidTypeArg-139]
_ = x[InvalidInstanceCycle-140]
_ = x[InvalidUnion-141]
_ = x[MisplacedConstraintIface-142]
_ = x[InvalidMethodTypeParams-143]
_ = x[MisplacedTypeParam-144]
_ = x[InvalidUnsafeSliceData-145]
_ = x[InvalidUnsafeString-146]
}
const (
_ErrorCode_name_0 = "InvalidSyntaxTree"
_ErrorCode_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKeyInvalidIfaceEmbedInvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDotInvalidDotDotDotOperandInvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDeclInvalidChanRangeInvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString"
)
var (
_ErrorCode_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411, 428, 443, 450, 461, 484, 499, 511, 522, 537, 551, 566, 581, 594, 603, 617, 632, 643, 658, 667, 683, 703, 721, 740, 752, 771, 790, 806, 823, 842, 856, 867, 882, 895, 910, 926, 940, 956, 971, 988, 1006, 1021, 1031, 1041, 1058, 1080, 1094, 1108, 1128, 1146, 1166, 1184, 1207, 1223, 1238, 1251, 1261, 1273, 1284, 1298, 1311, 1322, 1332, 1347, 1358, 1369, 1382, 1398, 1415, 1439, 1456, 1471, 1481, 1490, 1503, 1519, 1535, 1546, 1561, 1577, 1591, 1607, 1621, 1638, 1658, 1671, 1687, 1701, 1718, 1735, 1752, 1767, 1781, 1795, 1806, 1818, 1831, 1848, 1861, 1872, 1885, 1897, 1906, 1913, 1925, 1941, 1959, 1977, 1992, 2009, 2028, 2042, 2062, 2074, 2098, 2121, 2139, 2161, 2180}
)
func (i ErrorCode) String() string {
switch {
case i == -1:
return _ErrorCode_name_0
case 1 <= i && i <= 146:
i -= 1
return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
default:
return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@@ -0,0 +1,88 @@
// Copyright 2025 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 typesinternal
import (
"go/ast"
"go/token"
"go/types"
)
// NoEffects reports whether the expression has no side effects, i.e., it
// does not modify the memory state. This function is conservative: it may
// return false even when the expression has no effect.
func NoEffects(info *types.Info, expr ast.Expr) bool {
noEffects := true
ast.Inspect(expr, func(n ast.Node) bool {
switch v := n.(type) {
case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr,
*ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr,
*ast.StarExpr, *ast.CompositeLit,
// non-expressions that may appear within expressions
*ast.KeyValueExpr,
*ast.FieldList,
*ast.Field,
*ast.Ellipsis,
*ast.IndexListExpr:
// No effect.
case *ast.ArrayType,
*ast.StructType,
*ast.ChanType,
*ast.FuncType,
*ast.MapType,
*ast.InterfaceType:
// Type syntax: no effects, recursively.
// Prune descent.
return false
case *ast.UnaryExpr:
// Channel send <-ch has effects.
if v.Op == token.ARROW {
noEffects = false
}
case *ast.CallExpr:
// Type conversion has no effects.
if !info.Types[v.Fun].IsType() {
if CallsPureBuiltin(info, v) {
// A call such as len(e) has no effects of its
// own, though the subexpression e might.
} else {
noEffects = false
}
}
case *ast.FuncLit:
// A FuncLit has no effects, but do not descend into it.
return false
default:
// All other expressions have effects
noEffects = false
}
return noEffects
})
return noEffects
}
// CallsPureBuiltin reports whether call is a call of a built-in
// function that is a pure computation over its operands (analogous to
// a + operator). Because it does not depend on program state, it may
// be evaluated at any point--though not necessarily at multiple
// points (consider new, make).
func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool {
if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok {
if b, ok := info.ObjectOf(id).(*types.Builtin); ok {
switch b.Name() {
case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min":
return true
}
// Not: append clear close copy delete panic print println recover
}
}
return false
}

View File

@@ -0,0 +1,71 @@
// Copyright 2025 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 typesinternal
import (
"go/types"
"slices"
)
// IsTypeNamed reports whether t is (or is an alias for) a
// package-level defined type with the given package path and one of
// the given names. It returns false if t is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool {
if named, ok := types.Unalias(t).(*types.Named); ok {
tname := named.Obj()
return tname != nil &&
IsPackageLevel(tname) &&
tname.Pkg().Path() == pkgPath &&
slices.Contains(names, tname.Name())
}
return false
}
// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a
// package-level defined type with the given package path and one of the given
// names. It returns false if t is not a pointer type.
func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool {
r := Unpointer(t)
if r == t {
return false
}
return IsTypeNamed(r, pkgPath, names...)
}
// IsFunctionNamed reports whether obj is a package-level function
// defined in the given package and has one of the given names.
// It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
f, ok := obj.(*types.Func)
return ok &&
IsPackageLevel(obj) &&
f.Pkg().Path() == pkgPath &&
f.Signature().Recv() == nil &&
slices.Contains(names, f.Name())
}
// IsMethodNamed reports whether obj is a method defined on a
// package-level type with the given package and type name, and has
// one of the given names. It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.TypeName.Name",
// which is important for the performance of syntax matching.
func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool {
if fn, ok := obj.(*types.Func); ok {
if recv := fn.Signature().Recv(); recv != nil {
_, T := ReceiverNamed(recv)
return T != nil &&
IsTypeNamed(T, pkgPath, typeName) &&
slices.Contains(names, fn.Name())
}
}
return false
}

View File

@@ -0,0 +1,54 @@
// Copyright 2024 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 typesinternal
import (
"go/ast"
"go/types"
"strconv"
)
// FileQualifier returns a [types.Qualifier] function that qualifies
// imported symbols appropriately based on the import environment of a given
// file.
// If the same package is imported multiple times, the last appearance is
// recorded.
//
// TODO(adonovan): this function ignores the effect of shadowing. It
// should accept a [token.Pos] and a [types.Info] and compute only the
// set of imports that are not shadowed at that point, analogous to
// [analysis.AddImport]. It could also compute (as a side
// effect) the set of additional imports required to ensure that there
// is an accessible import for each necessary package, making it
// converge even more closely with AddImport.
func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier {
// Construct mapping of import paths to their defined names.
// It is only necessary to look at renaming imports.
imports := make(map[string]string)
for _, imp := range f.Imports {
if imp.Name != nil && imp.Name.Name != "_" {
path, _ := strconv.Unquote(imp.Path.Value)
imports[path] = imp.Name.Name
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(p *types.Package) string {
if p == nil || p == pkg {
return ""
}
if name, ok := imports[p.Path()]; ok {
if name == "." {
return ""
} else {
return name
}
}
// If there is no local renaming, fall back to the package name.
return p.Name()
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2024 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 typesinternal
import (
"go/types"
)
// ReceiverNamed returns the named type (if any) associated with the
// type of recv, which may be of the form N or *N, or aliases thereof.
// It also reports whether a Pointer was present.
//
// The named result may be nil if recv is from a method on an
// anonymous interface or struct types or in ill-typed code.
func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) {
t := recv.Type()
if ptr, ok := types.Unalias(t).(*types.Pointer); ok {
isPtr = true
t = ptr.Elem()
}
named, _ = types.Unalias(t).(*types.Named)
return
}
// Unpointer returns T given *T or an alias thereof.
// For all other types it is the identity function.
// It does not look at underlying types.
// The result may be an alias.
//
// Use this function to strip off the optional pointer on a receiver
// in a field or method selection, without losing the named type
// (which is needed to compute the method set).
//
// See also [typeparams.MustDeref], which removes one level of
// indirection from the type, regardless of named types (analogous to
// a LOAD instruction).
func Unpointer(t types.Type) types.Type {
if ptr, ok := types.Unalias(t).(*types.Pointer); ok {
return ptr.Elem()
}
return t
}

View File

@@ -0,0 +1,256 @@
// Copyright 2025 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 typeindex provides an [Index] of type information for a
// package, allowing efficient lookup of, say, whether a given symbol
// is referenced and, if so, where from; or of the [inspector.Cursor] for
// the declaration of a particular [types.Object] symbol.
package typeindex
import (
"encoding/binary"
"go/ast"
"go/types"
"iter"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"honnef.co/go/tools/internal/xtools-internal/astutil"
"honnef.co/go/tools/internal/xtools-internal/typesinternal"
)
// New constructs an Index for the package of type-annotated syntax
//
// TODO(adonovan): accept a FileSet too?
// We regret not requiring one in inspector.New.
func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *Index {
ix := &Index{
inspect: inspect,
info: info,
packages: make(map[string]*types.Package),
def: make(map[types.Object]inspector.Cursor),
uses: make(map[types.Object]*uses),
}
addPackage := func(pkg2 *types.Package) {
if pkg2 != nil && pkg2 != pkg {
ix.packages[pkg2.Path()] = pkg2
}
}
for cur := range inspect.Root().Preorder((*ast.ImportSpec)(nil), (*ast.Ident)(nil)) {
switch n := cur.Node().(type) {
case *ast.ImportSpec:
// Index direct imports, including blank ones.
if pkgname := info.PkgNameOf(n); pkgname != nil {
addPackage(pkgname.Imported())
}
case *ast.Ident:
// Index all defining and using identifiers.
if obj := info.Defs[n]; obj != nil {
ix.def[obj] = cur
}
if obj := info.Uses[n]; obj != nil {
// Index indirect dependencies (via fields and methods).
if !typesinternal.IsPackageLevel(obj) {
addPackage(obj.Pkg())
}
for {
us, ok := ix.uses[obj]
if !ok {
us = &uses{}
us.code = us.initial[:0]
ix.uses[obj] = us
}
delta := cur.Index() - us.last
if delta < 0 {
panic("non-monotonic")
}
us.code = binary.AppendUvarint(us.code, uint64(delta))
us.last = cur.Index()
// If n is a selection of a field or method of an instantiated
// type, also record a use of the generic field or method.
obj, ok = objectOrigin(obj)
if !ok {
break
}
}
}
}
}
return ix
}
// objectOrigin returns the generic object for obj if it is a field or
// method of an instantied type; zero otherwise.
//
// (This operation is appropriate only for selections.
// Lexically resolved references always resolve to the generic.
// Although Named and Alias types also use Origin to express
// an instance/generic distinction, that's in the domain
// of Types; their TypeName objects always refer to the generic.)
func objectOrigin(obj types.Object) (types.Object, bool) {
var origin types.Object
switch obj := obj.(type) {
case *types.Func:
if obj.Signature().Recv() != nil {
origin = obj.Origin() // G[int].method -> G[T].method
}
case *types.Var:
if obj.IsField() {
origin = obj.Origin() // G[int].field -> G[T].field
}
}
if origin != nil && origin != obj {
return origin, true
}
return nil, false
}
// An Index holds an index mapping [types.Object] symbols to their syntax.
// In effect, it is the inverse of [types.Info].
type Index struct {
inspect *inspector.Inspector
info *types.Info
packages map[string]*types.Package // packages of all symbols referenced from this package
def map[types.Object]inspector.Cursor // Cursor of *ast.Ident that defines the Object
uses map[types.Object]*uses // Cursors of *ast.Idents that use the Object
}
// A uses holds the list of Cursors of Idents that use a given symbol.
//
// The Uses map of [types.Info] is substantial, so it pays to compress
// its inverse mapping here, both in space and in CPU due to reduced
// allocation. A Cursor is 2 words; a Cursor.Index is 4 bytes; but
// since Cursors are naturally delivered in ascending order, we can
// use varint-encoded deltas at a cost of only ~1.7-2.2 bytes per use.
//
// Many variables have only one or two uses, so their encoded uses may
// fit in the 4 bytes of initial, saving further CPU and space
// essentially for free since the struct's size class is 4 words.
type uses struct {
code []byte // varint-encoded deltas of successive Cursor.Index values
last int32 // most recent Cursor.Index value; used during encoding
initial [4]byte // use slack in size class as initial space for code
}
// Uses returns the sequence of Cursors of [*ast.Ident]s in this package
// that refer to obj. If obj is nil, the sequence is empty.
//
// Uses, unlike the Uses field of [types.Info], records additional
// entries mapping fields and methods of generic types to references
// through their corresponding instantiated objects.
func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
if uses := ix.uses[obj]; uses != nil {
var last int32
for code := uses.code; len(code) > 0; {
delta, n := binary.Uvarint(code)
last += int32(delta)
if !yield(ix.inspect.At(last)) {
return
}
code = code[n:]
}
}
}
}
// Used reports whether any of the specified objects are used, in
// other words, obj != nil && Uses(obj) is non-empty for some obj in objs.
//
// (This treatment of nil allows Used to be called directly on the
// result of [Index.Object] so that analyzers can conveniently skip
// packages that don't use a symbol of interest.)
func (ix *Index) Used(objs ...types.Object) bool {
for _, obj := range objs {
if obj != nil && ix.uses[obj] != nil {
return true
}
}
return false
}
// Def returns the Cursor of the [*ast.Ident] in this package
// that declares the specified object, if any.
func (ix *Index) Def(obj types.Object) (inspector.Cursor, bool) {
cur, ok := ix.def[obj]
return cur, ok
}
// Package returns the package of the specified path,
// or nil if it is not referenced from this package.
func (ix *Index) Package(path string) *types.Package {
return ix.packages[path]
}
// Object returns the package-level symbol name within the package of
// the specified path, or nil if the package or symbol does not exist
// or is not visible from this package.
func (ix *Index) Object(path, name string) types.Object {
if pkg := ix.Package(path); pkg != nil {
return pkg.Scope().Lookup(name)
}
return nil
}
// Selection returns the named method or field belonging to the
// package-level type returned by Object(path, typename).
func (ix *Index) Selection(path, typename, name string) types.Object {
if obj := ix.Object(path, typename); obj != nil {
if tname, ok := obj.(*types.TypeName); ok {
obj, _, _ := types.LookupFieldOrMethod(tname.Type(), true, obj.Pkg(), name)
return obj
}
}
return nil
}
// Calls returns the sequence of cursors for *ast.CallExpr nodes that
// call the specified callee, as defined by [typeutil.Callee].
// If callee is nil, the sequence is empty.
func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] {
return func(yield func(inspector.Cursor) bool) {
for cur := range ix.Uses(callee) {
// The call may be of the form f() or x.f(),
// optionally with parens; ascend from f to call.
// See logic in [typesinternal.UsedIdent], to which this is dual.
//
// It is tempting but wrong to use the first
// CallExpr ancestor: we have to make sure the
// ident is in the CallExpr.Fun position, otherwise
// f(f, f) would have two spurious matches.
// Avoiding Enclosing is also significantly faster.
// inverse unparen: f -> (f)
cur = astutil.UnparenEnclosingCursor(cur)
// ascend selector (or qualified identifier): f -> x.f
if cur.ParentEdgeKind() == edge.SelectorExpr_Sel {
cur = astutil.UnparenEnclosingCursor(cur.Parent())
}
// ascend typeparams: f -> f[T]; f -> f[T1, T2]
if ek := cur.ParentEdgeKind(); ek == edge.IndexExpr_X || ek == edge.IndexListExpr_X {
cur = astutil.UnparenEnclosingCursor(cur.Parent())
}
// ascend from f or x.f to call
if cur.ParentEdgeKind() == edge.CallExpr_Fun {
curCall := cur.Parent()
call := curCall.Node().(*ast.CallExpr)
if typeutil.Callee(ix.info, call) == callee {
if !yield(curCall) {
return
}
}
}
}
}
}

View File

@@ -0,0 +1,272 @@
// Copyright 2020 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 typesinternal provides helpful operators for dealing with
// go/types:
//
// - operators for querying typed syntax trees (e.g. [Imports], [IsFunctionNamed]);
// - functions for converting types to strings or syntax (e.g. [TypeExpr], FileQualifier]);
// - helpers for working with the [go/types] API (e.g. [NewTypesInfo]);
// - access to internal go/types APIs that are not yet
// exported (e.g. [SetUsesCgo], [ErrorCodeStartEnd], [VarKind]); and
// - common algorithms related to types (e.g. [TooNewStdSymbols]).
//
// See also:
// - [honnef.co/go/tools/internal/xtools-internal/astutil], for operations on untyped syntax;
// - [honnef.co/go/tools/internal/xtools-internal/analysisinernal], for helpers for analyzers;
// - [honnef.co/go/tools/internal/xtools-internal/refactor], for operators to compute text edits.
package typesinternal
import (
"go/ast"
"go/token"
"go/types"
"iter"
"reflect"
"golang.org/x/tools/go/ast/inspector"
)
func SetUsesCgo(conf *types.Config) bool {
v := reflect.ValueOf(conf).Elem()
f := v.FieldByName("go115UsesCgo")
if !f.IsValid() {
f = v.FieldByName("UsesCgo")
if !f.IsValid() {
return false
}
}
*(*bool)(f.Addr().UnsafePointer()) = true
return true
}
// ErrorCodeStartEnd extracts additional information from types.Error values
// generated by Go version 1.16 and later: the error code, start position, and
// end position. If all positions are valid, start <= err.Pos <= end.
//
// If the data could not be read, the final result parameter will be false.
//
// TODO(adonovan): eliminate start/end when proposal #71803 is accepted.
func ErrorCodeStartEnd(err types.Error) (code ErrorCode, start, end token.Pos, ok bool) {
var data [3]int
// By coincidence all of these fields are ints, which simplifies things.
v := reflect.ValueOf(err)
for i, name := range []string{"go116code", "go116start", "go116end"} {
f := v.FieldByName(name)
if !f.IsValid() {
return 0, 0, 0, false
}
data[i] = int(f.Int())
}
return ErrorCode(data[0]), token.Pos(data[1]), token.Pos(data[2]), true
}
// NameRelativeTo returns a types.Qualifier that qualifies members of
// all packages other than pkg, using only the package name.
// (By contrast, [types.RelativeTo] uses the complete package path,
// which is often excessive.)
//
// If pkg is nil, it is equivalent to [*types.Package.Name].
//
// TODO(adonovan): all uses of this with TypeString should be
// eliminated when https://go.dev/issues/75604 is resolved.
func NameRelativeTo(pkg *types.Package) types.Qualifier {
return func(other *types.Package) string {
if pkg != nil && pkg == other {
return "" // same package; unqualified
}
return other.Name()
}
}
// TypeNameFor returns the type name symbol for the specified type, if
// it is a [*types.Alias], [*types.Named], [*types.TypeParam], or a
// [*types.Basic] representing a type.
//
// For all other types, and for Basic types representing a builtin,
// constant, or nil, it returns nil. Be careful not to convert the
// resulting nil pointer to a [types.Object]!
//
// If t is the type of a constant, it may be an "untyped" type, which
// has no TypeName. To access the name of such types (e.g. "untyped
// int"), use [types.Basic.Name].
func TypeNameFor(t types.Type) *types.TypeName {
switch t := t.(type) {
case *types.Alias:
return t.Obj()
case *types.Named:
return t.Obj()
case *types.TypeParam:
return t.Obj()
case *types.Basic:
// See issues #71886 and #66890 for some history.
if tname, ok := types.Universe.Lookup(t.Name()).(*types.TypeName); ok {
return tname
}
}
return nil
}
// A NamedOrAlias is a [types.Type] that is named (as
// defined by the spec) and capable of bearing type parameters: it
// abstracts aliases ([types.Alias]) and defined types
// ([types.Named]).
//
// Every type declared by an explicit "type" declaration is a
// NamedOrAlias. (Built-in type symbols may additionally
// have type [types.Basic], which is not a NamedOrAlias,
// though the spec regards them as "named"; see [TypeNameFor].)
//
// NamedOrAlias cannot expose the Origin method, because
// [types.Alias.Origin] and [types.Named.Origin] have different
// (covariant) result types; use [Origin] instead.
type NamedOrAlias interface {
types.Type
Obj() *types.TypeName
TypeArgs() *types.TypeList
TypeParams() *types.TypeParamList
SetTypeParams(tparams []*types.TypeParam)
}
var (
_ NamedOrAlias = (*types.Alias)(nil)
_ NamedOrAlias = (*types.Named)(nil)
)
// Origin returns the generic type of the Named or Alias type t if it
// is instantiated, otherwise it returns t.
func Origin(t NamedOrAlias) NamedOrAlias {
switch t := t.(type) {
case *types.Alias:
return t.Origin()
case *types.Named:
return t.Origin()
}
return t
}
// IsPackageLevel reports whether obj is a package-level symbol.
func IsPackageLevel(obj types.Object) bool {
return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope()
}
// NewTypesInfo returns a *types.Info with all maps populated.
func NewTypesInfo() *types.Info {
return &types.Info{
Types: map[ast.Expr]types.TypeAndValue{},
Instances: map[*ast.Ident]types.Instance{},
Defs: map[*ast.Ident]types.Object{},
Uses: map[*ast.Ident]types.Object{},
Implicits: map[ast.Node]types.Object{},
Selections: map[*ast.SelectorExpr]*types.Selection{},
Scopes: map[ast.Node]*types.Scope{},
FileVersions: map[*ast.File]string{},
}
}
// EnclosingScope returns the innermost block logically enclosing the cursor.
func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope {
for cur := range cur.Enclosing() {
n := cur.Node()
// A function's Scope is associated with its FuncType.
switch f := n.(type) {
case *ast.FuncDecl:
n = f.Type
case *ast.FuncLit:
n = f.Type
}
if b := info.Scopes[n]; b != nil {
return b
}
}
panic("no Scope for *ast.File")
}
// Imports reports whether path is imported by pkg.
func Imports(pkg *types.Package, path string) bool {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return true
}
}
return false
}
// ObjectKind returns a description of the object's kind.
//
// from objectKind in go/types
func ObjectKind(obj types.Object) string {
switch obj := obj.(type) {
case *types.PkgName:
return "package name"
case *types.Const:
return "constant"
case *types.TypeName:
if obj.IsAlias() {
return "type alias"
} else if _, ok := obj.Type().(*types.TypeParam); ok {
return "type parameter"
} else {
return "defined type"
}
case *types.Var:
switch obj.Kind() {
case PackageVar:
return "package-level variable"
case LocalVar:
return "local variable"
case RecvVar:
return "receiver"
case ParamVar:
return "parameter"
case ResultVar:
return "result variable"
case FieldVar:
return "struct field"
}
case *types.Func:
if obj.Signature().Recv() != nil {
return "method"
} else {
return "function"
}
case *types.Label:
return "label"
case *types.Builtin:
return "built-in function"
case *types.Nil:
return "untyped nil"
}
return "unknown symbol"
}
// ImplicitFieldSelections returns the sequence of implicit embedded fields
// traversed by the given selection. It skips the final leaf field or method.
// The boolean component indicates whether the traversal traversed a pointer.
func ImplicitFieldSelections(seln types.Selection) iter.Seq2[*types.Var, bool] {
return func(yield func(*types.Var, bool) bool) {
var (
t = seln.Recv()
indices = seln.Index()
)
for _, idx := range indices[:len(indices)-1] {
ptr, isPtr := t.Underlying().(*types.Pointer)
if isPtr {
t = ptr.Elem()
}
structType, ok := t.Underlying().(*types.Struct)
if !ok {
break
}
field := structType.Field(idx)
if !yield(field, isPtr) {
break
}
t = field.Type()
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2024 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.
//go:build go1.25
package typesinternal
import "go/types"
type VarKind = types.VarKind
const (
PackageVar = types.PackageVar
LocalVar = types.LocalVar
RecvVar = types.RecvVar
ParamVar = types.ParamVar
ResultVar = types.ResultVar
FieldVar = types.FieldVar
)
func GetVarKind(v *types.Var) VarKind { return v.Kind() }
func SetVarKind(v *types.Var, kind VarKind) { v.SetKind(kind) }

View File

@@ -0,0 +1,39 @@
// Copyright 2024 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.
//go:build !go1.25
package typesinternal
import "go/types"
type VarKind uint8
const (
_ VarKind = iota // (not meaningful)
PackageVar // a package-level variable
LocalVar // a local variable
RecvVar // a method receiver variable
ParamVar // a function parameter variable
ResultVar // a function result variable
FieldVar // a struct field
)
func (kind VarKind) String() string {
return [...]string{
0: "VarKind(0)",
PackageVar: "PackageVar",
LocalVar: "LocalVar",
RecvVar: "RecvVar",
ParamVar: "ParamVar",
ResultVar: "ResultVar",
FieldVar: "FieldVar",
}[kind]
}
// GetVarKind returns an invalid VarKind.
func GetVarKind(v *types.Var) VarKind { return 0 }
// SetVarKind has no effect.
func SetVarKind(v *types.Var, kind VarKind) {}

View File

@@ -0,0 +1,381 @@
// Copyright 2024 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 typesinternal
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
)
// ZeroString returns the string representation of the zero value for any type t.
// The boolean result indicates whether the type is or contains an invalid type
// or a non-basic (constraint) interface type.
//
// Even for invalid input types, ZeroString may return a partially correct
// string representation. The caller should use the returned isValid boolean
// to determine the validity of the expression.
//
// When assigning to a wider type (such as 'any'), it's the caller's
// responsibility to handle any necessary type conversions.
//
// This string can be used on the right-hand side of an assignment where the
// left-hand side has that explicit type.
// References to named types are qualified by an appropriate (optional)
// qualifier function.
// Exception: This does not apply to tuples. Their string representation is
// informational only and cannot be used in an assignment.
//
// See [ZeroExpr] for a variant that returns an [ast.Expr].
func ZeroString(t types.Type, qual types.Qualifier) (_ string, isValid bool) {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return "false", true
case t.Info()&types.IsNumeric != 0:
return "0", true
case t.Info()&types.IsString != 0:
return `""`, true
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return "nil", true
case t.Kind() == types.Invalid:
return "invalid", false
default:
panic(fmt.Sprintf("ZeroString for unexpected type %v", t))
}
case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
return "nil", true
case *types.Interface:
if !t.IsMethodSet() {
return "invalid", false
}
return "nil", true
case *types.Named:
switch under := t.Underlying().(type) {
case *types.Struct, *types.Array:
return types.TypeString(t, qual) + "{}", true
default:
return ZeroString(under, qual)
}
case *types.Alias:
switch t.Underlying().(type) {
case *types.Struct, *types.Array:
return types.TypeString(t, qual) + "{}", true
default:
// A type parameter can have alias but alias type's underlying type
// can never be a type parameter.
// Use types.Unalias to preserve the info of type parameter instead
// of call Underlying() going right through and get the underlying
// type of the type parameter which is always an interface.
return ZeroString(types.Unalias(t), qual)
}
case *types.Array, *types.Struct:
return types.TypeString(t, qual) + "{}", true
case *types.TypeParam:
// Assumes func new is not shadowed.
return "*new(" + types.TypeString(t, qual) + ")", true
case *types.Tuple:
// Tuples are not normal values.
// We are currently format as "(t[0], ..., t[n])". Could be something else.
isValid := true
components := make([]string, t.Len())
for i := 0; i < t.Len(); i++ {
comp, ok := ZeroString(t.At(i).Type(), qual)
components[i] = comp
isValid = isValid && ok
}
return "(" + strings.Join(components, ", ") + ")", isValid
case *types.Union:
// Variables of these types cannot be created, so it makes
// no sense to ask for their zero value.
panic(fmt.Sprintf("invalid type for a variable: %v", t))
default:
panic(t) // unreachable.
}
}
// ZeroExpr returns the ast.Expr representation of the zero value for any type t.
// The boolean result indicates whether the type is or contains an invalid type
// or a non-basic (constraint) interface type.
//
// Even for invalid input types, ZeroExpr may return a partially correct ast.Expr
// representation. The caller should use the returned isValid boolean to determine
// the validity of the expression.
//
// This function is designed for types suitable for variables and should not be
// used with Tuple or Union types.References to named types are qualified by an
// appropriate (optional) qualifier function.
//
// See [ZeroString] for a variant that returns a string.
func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
return &ast.Ident{Name: "false"}, true
case t.Info()&types.IsNumeric != 0:
return &ast.BasicLit{Kind: token.INT, Value: "0"}, true
case t.Info()&types.IsString != 0:
return &ast.BasicLit{Kind: token.STRING, Value: `""`}, true
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
return ast.NewIdent("nil"), true
case t.Kind() == types.Invalid:
return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false
default:
panic(fmt.Sprintf("ZeroExpr for unexpected type %v", t))
}
case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
return ast.NewIdent("nil"), true
case *types.Interface:
if !t.IsMethodSet() {
return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false
}
return ast.NewIdent("nil"), true
case *types.Named:
switch under := t.Underlying().(type) {
case *types.Struct, *types.Array:
return &ast.CompositeLit{
Type: TypeExpr(t, qual),
}, true
default:
return ZeroExpr(under, qual)
}
case *types.Alias:
switch t.Underlying().(type) {
case *types.Struct, *types.Array:
return &ast.CompositeLit{
Type: TypeExpr(t, qual),
}, true
default:
return ZeroExpr(types.Unalias(t), qual)
}
case *types.Array, *types.Struct:
return &ast.CompositeLit{
Type: TypeExpr(t, qual),
}, true
case *types.TypeParam:
return &ast.StarExpr{ // *new(T)
X: &ast.CallExpr{
// Assumes func new is not shadowed.
Fun: ast.NewIdent("new"),
Args: []ast.Expr{
ast.NewIdent(t.Obj().Name()),
},
},
}, true
case *types.Tuple:
// Unlike ZeroString, there is no ast.Expr can express tuple by
// "(t[0], ..., t[n])".
panic(fmt.Sprintf("invalid type for a variable: %v", t))
case *types.Union:
// Variables of these types cannot be created, so it makes
// no sense to ask for their zero value.
panic(fmt.Sprintf("invalid type for a variable: %v", t))
default:
panic(t) // unreachable.
}
}
// TypeExpr returns syntax for the specified type. References to named types
// are qualified by an appropriate (optional) qualifier function.
// It may panic for types such as Tuple or Union.
//
// See also https://go.dev/issues/75604, which will provide a robust
// Type-to-valid-Go-syntax formatter.
func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.UnsafePointer:
return &ast.SelectorExpr{X: ast.NewIdent(qual(types.NewPackage("unsafe", "unsafe"))), Sel: ast.NewIdent("Pointer")}
default:
return ast.NewIdent(t.Name())
}
case *types.Pointer:
return &ast.UnaryExpr{
Op: token.MUL,
X: TypeExpr(t.Elem(), qual),
}
case *types.Array:
return &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: fmt.Sprintf("%d", t.Len()),
},
Elt: TypeExpr(t.Elem(), qual),
}
case *types.Slice:
return &ast.ArrayType{
Elt: TypeExpr(t.Elem(), qual),
}
case *types.Map:
return &ast.MapType{
Key: TypeExpr(t.Key(), qual),
Value: TypeExpr(t.Elem(), qual),
}
case *types.Chan:
dir := ast.ChanDir(t.Dir())
if t.Dir() == types.SendRecv {
dir = ast.SEND | ast.RECV
}
return &ast.ChanType{
Dir: dir,
Value: TypeExpr(t.Elem(), qual),
}
case *types.Signature:
var params []*ast.Field
for v := range t.Params().Variables() {
params = append(params, &ast.Field{
Type: TypeExpr(v.Type(), qual),
Names: []*ast.Ident{
{
Name: v.Name(),
},
},
})
}
if t.Variadic() {
last := params[len(params)-1]
last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt}
}
var returns []*ast.Field
for v := range t.Results().Variables() {
returns = append(returns, &ast.Field{
Type: TypeExpr(v.Type(), qual),
})
}
return &ast.FuncType{
Params: &ast.FieldList{
List: params,
},
Results: &ast.FieldList{
List: returns,
},
}
case *types.TypeParam:
pkgName := qual(t.Obj().Pkg())
if pkgName == "" || t.Obj().Pkg() == nil {
return ast.NewIdent(t.Obj().Name())
}
return &ast.SelectorExpr{
X: ast.NewIdent(pkgName),
Sel: ast.NewIdent(t.Obj().Name()),
}
// types.TypeParam also implements interface NamedOrAlias. To differentiate,
// case TypeParam need to be present before case NamedOrAlias.
// TODO(hxjiang): remove this comment once TypeArgs() is added to interface
// NamedOrAlias.
case NamedOrAlias:
var expr ast.Expr = ast.NewIdent(t.Obj().Name())
if pkgName := qual(t.Obj().Pkg()); pkgName != "." && pkgName != "" {
expr = &ast.SelectorExpr{
X: ast.NewIdent(pkgName),
Sel: expr.(*ast.Ident),
}
}
// TODO(hxjiang): call t.TypeArgs after adding method TypeArgs() to
// typesinternal.NamedOrAlias.
if hasTypeArgs, ok := t.(interface{ TypeArgs() *types.TypeList }); ok {
if typeArgs := hasTypeArgs.TypeArgs(); typeArgs != nil && typeArgs.Len() > 0 {
var indices []ast.Expr
for t0 := range typeArgs.Types() {
indices = append(indices, TypeExpr(t0, qual))
}
expr = &ast.IndexListExpr{
X: expr,
Indices: indices,
}
}
}
return expr
case *types.Struct:
return ast.NewIdent(t.String())
case *types.Interface:
return ast.NewIdent(t.String())
case *types.Union:
if t.Len() == 0 {
panic("Union type should have at least one term")
}
// Same as go/ast, the return expression will put last term in the
// Y field at topmost level of BinaryExpr.
// For union of type "float32 | float64 | int64", the structure looks
// similar to:
// {
// X: {
// X: float32,
// Op: |
// Y: float64,
// }
// Op: |,
// Y: int64,
// }
var union ast.Expr
for i := range t.Len() {
term := t.Term(i)
termExpr := TypeExpr(term.Type(), qual)
if term.Tilde() {
termExpr = &ast.UnaryExpr{
Op: token.TILDE,
X: termExpr,
}
}
if i == 0 {
union = termExpr
} else {
union = &ast.BinaryExpr{
X: union,
Op: token.OR,
Y: termExpr,
}
}
}
return union
case *types.Tuple:
panic("invalid input type types.Tuple")
default:
panic("unreachable")
}
}

View File

@@ -0,0 +1,49 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package versions
// This file contains predicates for working with file versions to
// decide when a tool should consider a language feature enabled.
// named constants, to avoid misspelling
const (
Go1_17 = "go1.17"
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
Go1_21 = "go1.21"
Go1_22 = "go1.22"
Go1_23 = "go1.23"
Go1_24 = "go1.24"
Go1_25 = "go1.25"
Go1_26 = "go1.26"
Go1_27 = "go1.27"
)
// Future is an invalid unknown Go version sometime in the future.
// Do not use directly with Compare.
const Future = ""
// AtLeast reports whether the file version v comes after a Go release.
//
// Use this predicate to enable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func AtLeast(v, release string) bool {
if v == Future {
return true // an unknown future version is always after y.
}
return Compare(Lang(v), Lang(release)) >= 0
}
// Before reports whether the file version v is strictly before a Go release.
//
// Use this predicate to disable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func Before(v, release string) bool {
if v == Future {
return false // an unknown future version happens after y.
}
return Compare(Lang(v), Lang(release)) < 0
}

View File

@@ -0,0 +1,172 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This is a fork of internal/gover for use by x/tools until
// go1.21 and earlier are no longer supported by x/tools.
package versions
import "strings"
// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]]
// The numbers are the original decimal strings to avoid integer overflows
// and since there is very little actual math. (Probably overflow doesn't matter in practice,
// but at the time this code was written, there was an existing test that used
// go1.99999999999, which does not fit in an int on 32-bit platforms.
// The "big decimal" representation avoids the problem entirely.)
type gover struct {
major string // decimal
minor string // decimal or ""
patch string // decimal or ""
kind string // "", "alpha", "beta", "rc"
pre string // decimal or ""
}
// compare returns -1, 0, or +1 depending on whether
// x < y, x == y, or x > y, interpreted as toolchain versions.
// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
// Malformed versions compare less than well-formed versions and equal to each other.
// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
func compare(x, y string) int {
vx := parse(x)
vy := parse(y)
if c := cmpInt(vx.major, vy.major); c != 0 {
return c
}
if c := cmpInt(vx.minor, vy.minor); c != 0 {
return c
}
if c := cmpInt(vx.patch, vy.patch); c != 0 {
return c
}
if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc
return c
}
if c := cmpInt(vx.pre, vy.pre); c != 0 {
return c
}
return 0
}
// lang returns the Go language version. For example, lang("1.2.3") == "1.2".
func lang(x string) string {
v := parse(x)
if v.minor == "" || v.major == "1" && v.minor == "0" {
return v.major
}
return v.major + "." + v.minor
}
// isValid reports whether the version x is valid.
func isValid(x string) bool {
return parse(x) != gover{}
}
// parse parses the Go version string x into a version.
// It returns the zero version if x is malformed.
func parse(x string) gover {
var v gover
// Parse major version.
var ok bool
v.major, x, ok = cutInt(x)
if !ok {
return gover{}
}
if x == "" {
// Interpret "1" as "1.0.0".
v.minor = "0"
v.patch = "0"
return v
}
// Parse . before minor version.
if x[0] != '.' {
return gover{}
}
// Parse minor version.
v.minor, x, ok = cutInt(x[1:])
if !ok {
return gover{}
}
if x == "" {
// Patch missing is same as "0" for older versions.
// Starting in Go 1.21, patch missing is different from explicit .0.
if cmpInt(v.minor, "21") < 0 {
v.patch = "0"
}
return v
}
// Parse patch if present.
if x[0] == '.' {
v.patch, x, ok = cutInt(x[1:])
if !ok || x != "" {
// Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
// Allowing them would be a bit confusing because we already have:
// 1.21 < 1.21rc1
// But a prerelease of a patch would have the opposite effect:
// 1.21.3rc1 < 1.21.3
// We've never needed them before, so let's not start now.
return gover{}
}
return v
}
// Parse prerelease.
i := 0
for i < len(x) && (x[i] < '0' || '9' < x[i]) {
if x[i] < 'a' || 'z' < x[i] {
return gover{}
}
i++
}
if i == 0 {
return gover{}
}
v.kind, x = x[:i], x[i:]
if x == "" {
return v
}
v.pre, x, ok = cutInt(x)
if !ok || x != "" {
return gover{}
}
return v
}
// cutInt scans the leading decimal number at the start of x to an integer
// and returns that value and the rest of the string.
func cutInt(x string) (n, rest string, ok bool) {
i := 0
for i < len(x) && '0' <= x[i] && x[i] <= '9' {
i++
}
if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero
return "", "", false
}
return x[:i], x[i:], true
}
// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
// (Copied from golang.org/x/mod/semver's compareInt.)
func cmpInt(x, y string) int {
if x == y {
return 0
}
if len(x) < len(y) {
return -1
}
if len(x) > len(y) {
return +1
}
if x < y {
return -1
} else {
return +1
}
}

View File

@@ -0,0 +1,33 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package versions
import (
"go/ast"
"go/types"
)
// FileVersion returns a file's Go version.
// The reported version is an unknown Future version if a
// version cannot be determined.
func FileVersion(info *types.Info, file *ast.File) string {
// In tools built with Go >= 1.22, the Go version of a file
// follow a cascades of sources:
// 1) types.Info.FileVersion, which follows the cascade:
// 1.a) file version (ast.File.GoVersion),
// 1.b) the package version (types.Config.GoVersion), or
// 2) is some unknown Future version.
//
// File versions require a valid package version to be provided to types
// in Config.GoVersion. Config.GoVersion is either from the package's module
// or the toolchain (go run). This value should be provided by go/packages
// or unitchecker.Config.GoVersion.
if v := info.FileVersions[file]; IsValid(v) {
return v
}
// Note: we could instead return runtime.Version() [if valid].
// This would act as a max version on what a tool can support.
return Future
}

View File

@@ -0,0 +1,57 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package versions
import (
"strings"
)
// Note: If we use build tags to use go/versions when go >=1.22,
// we run into go.dev/issue/53737. Under some operations users would see an
// import of "go/versions" even if they would not compile the file.
// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include
// For this reason, this library just a clone of go/versions for the moment.
// Lang returns the Go language version for version x.
// If x is not a valid version, Lang returns the empty string.
// For example:
//
// Lang("go1.21rc2") = "go1.21"
// Lang("go1.21.2") = "go1.21"
// Lang("go1.21") = "go1.21"
// Lang("go1") = "go1"
// Lang("bad") = ""
// Lang("1.21") = ""
func Lang(x string) string {
v := lang(stripGo(x))
if v == "" {
return ""
}
return x[:2+len(v)] // "go"+v without allocation
}
// Compare returns -1, 0, or +1 depending on whether
// x < y, x == y, or x > y, interpreted as Go versions.
// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21".
// Invalid versions, including the empty string, compare less than
// valid versions and equal to each other.
// The language version "go1.21" compares less than the
// release candidate and eventual releases "go1.21rc1" and "go1.21.0".
// Custom toolchain suffixes are ignored during comparison:
// "go1.21.0" and "go1.21.0-bigcorp" are equal.
func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) }
// IsValid reports whether the version x is valid.
func IsValid(x string) bool { return isValid(stripGo(x)) }
// stripGo converts from a "go1.21" version to a "1.21" version.
// If v does not start with "go", stripGo returns the empty string (a known invalid version).
func stripGo(v string) string {
v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix.
if len(v) < 2 || v[:2] != "go" {
return ""
}
return v[2:]
}