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,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
}