Add staticcheck tool

This commit is contained in:
dwrz
2026-06-06 01:17:20 +00:00
parent 6fb63c8c90
commit ad58cd78ff
315 changed files with 56337 additions and 4 deletions

View File

@@ -0,0 +1,243 @@
package pattern
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
)
var astTypes = map[string]reflect.Type{
"Ellipsis": reflect.TypeFor[ast.Ellipsis](),
"RangeStmt": reflect.TypeFor[ast.RangeStmt](),
"AssignStmt": reflect.TypeFor[ast.AssignStmt](),
"IndexExpr": reflect.TypeFor[ast.IndexExpr](),
"IndexListExpr": reflect.TypeFor[ast.IndexListExpr](),
"Ident": reflect.TypeFor[ast.Ident](),
"ValueSpec": reflect.TypeFor[ast.ValueSpec](),
"GenDecl": reflect.TypeFor[ast.GenDecl](),
"BinaryExpr": reflect.TypeFor[ast.BinaryExpr](),
"ForStmt": reflect.TypeFor[ast.ForStmt](),
"ArrayType": reflect.TypeFor[ast.ArrayType](),
"DeferStmt": reflect.TypeFor[ast.DeferStmt](),
"MapType": reflect.TypeFor[ast.MapType](),
"ReturnStmt": reflect.TypeFor[ast.ReturnStmt](),
"SliceExpr": reflect.TypeFor[ast.SliceExpr](),
"StarExpr": reflect.TypeFor[ast.StarExpr](),
"UnaryExpr": reflect.TypeFor[ast.UnaryExpr](),
"SendStmt": reflect.TypeFor[ast.SendStmt](),
"SelectStmt": reflect.TypeFor[ast.SelectStmt](),
"ImportSpec": reflect.TypeFor[ast.ImportSpec](),
"IfStmt": reflect.TypeFor[ast.IfStmt](),
"GoStmt": reflect.TypeFor[ast.GoStmt](),
"Field": reflect.TypeFor[ast.Field](),
"SelectorExpr": reflect.TypeFor[ast.SelectorExpr](),
"StructType": reflect.TypeFor[ast.StructType](),
"KeyValueExpr": reflect.TypeFor[ast.KeyValueExpr](),
"FuncType": reflect.TypeFor[ast.FuncType](),
"FuncLit": reflect.TypeFor[ast.FuncLit](),
"FuncDecl": reflect.TypeFor[ast.FuncDecl](),
"ChanType": reflect.TypeFor[ast.ChanType](),
"CallExpr": reflect.TypeFor[ast.CallExpr](),
"CaseClause": reflect.TypeFor[ast.CaseClause](),
"CommClause": reflect.TypeFor[ast.CommClause](),
"CompositeLit": reflect.TypeFor[ast.CompositeLit](),
"EmptyStmt": reflect.TypeFor[ast.EmptyStmt](),
"SwitchStmt": reflect.TypeFor[ast.SwitchStmt](),
"TypeSwitchStmt": reflect.TypeFor[ast.TypeSwitchStmt](),
"TypeAssertExpr": reflect.TypeFor[ast.TypeAssertExpr](),
"TypeSpec": reflect.TypeFor[ast.TypeSpec](),
"InterfaceType": reflect.TypeFor[ast.InterfaceType](),
"BranchStmt": reflect.TypeFor[ast.BranchStmt](),
"IncDecStmt": reflect.TypeFor[ast.IncDecStmt](),
"BasicLit": reflect.TypeFor[ast.BasicLit](),
}
func ASTToNode(node any) Node {
switch node := node.(type) {
case *ast.File:
panic("cannot convert *ast.File to Node")
case nil:
return Nil{}
case string:
return String(node)
case token.Token:
return Token(node)
case *ast.ExprStmt:
return ASTToNode(node.X)
case *ast.BlockStmt:
if node == nil {
return Nil{}
}
return ASTToNode(node.List)
case *ast.FieldList:
if node == nil {
return Nil{}
}
return ASTToNode(node.List)
case *ast.BasicLit:
if node == nil {
return Nil{}
}
case *ast.ParenExpr:
return ASTToNode(node.X)
}
if node, ok := node.(ast.Node); ok {
name := reflect.TypeOf(node).Elem().Name()
T, ok := structNodes[name]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
if reflect.ValueOf(node).IsNil() {
return Nil{}
}
v := reflect.ValueOf(node).Elem()
objs := make([]Node, T.NumField())
for i := 0; i < T.NumField(); i++ {
f := v.FieldByName(T.Field(i).Name)
objs[i] = ASTToNode(f.Interface())
}
n, err := populateNode(name, objs, false)
if err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
return n
}
s := reflect.ValueOf(node)
if s.Kind() == reflect.Slice {
if s.Len() == 0 {
return List{}
}
if s.Len() == 1 {
return ASTToNode(s.Index(0).Interface())
}
tail := List{}
for i := s.Len() - 1; i >= 0; i-- {
head := ASTToNode(s.Index(i).Interface())
l := List{
Head: head,
Tail: tail,
}
tail = l
}
return tail
}
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
func NodeToAST(node Node, state State) any {
switch node := node.(type) {
case Binding:
v, ok := state[node.Name]
if !ok {
// really we want to return an error here
panic("XXX")
}
switch v := v.(type) {
case types.Object:
return &ast.Ident{Name: v.Name()}
default:
return v
}
case Builtin, Any, Object, Symbol, Not, Or:
panic("XXX")
case List:
if (node == List{}) {
return []ast.Node{}
}
x := []ast.Node{NodeToAST(node.Head, state).(ast.Node)}
x = append(x, NodeToAST(node.Tail, state).([]ast.Node)...)
return x
case Token:
return token.Token(node)
case String:
return string(node)
case Nil:
return nil
}
name := reflect.TypeOf(node).Name()
T, ok := astTypes[name]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
v := reflect.ValueOf(node)
out := reflect.New(T)
for i := 0; i < T.NumField(); i++ {
fNode := v.FieldByName(T.Field(i).Name)
if (fNode == reflect.Value{}) {
continue
}
fAST := out.Elem().FieldByName(T.Field(i).Name)
switch fAST.Type().Kind() {
case reflect.Slice:
c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state))
if c.Kind() != reflect.Slice {
// it's a single node in the pattern, we have to wrap
// it in a slice
slice := reflect.MakeSlice(fAST.Type(), 1, 1)
slice.Index(0).Set(c)
c = slice
}
switch fAST.Interface().(type) {
case []ast.Node:
switch cc := c.Interface().(type) {
case []ast.Node:
fAST.Set(c)
case []ast.Expr:
var slice []ast.Node
for _, el := range cc {
slice = append(slice, el)
}
fAST.Set(reflect.ValueOf(slice))
default:
panic("XXX")
}
case []ast.Expr:
switch cc := c.Interface().(type) {
case []ast.Node:
var slice []ast.Expr
for _, el := range cc {
slice = append(slice, el.(ast.Expr))
}
fAST.Set(reflect.ValueOf(slice))
case []ast.Expr:
fAST.Set(c)
default:
panic("XXX")
}
default:
panic("XXX")
}
case reflect.Int:
c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state))
switch c.Kind() {
case reflect.String:
tok, ok := tokensByString[c.Interface().(string)]
if !ok {
// really we want to return an error here
panic("XXX")
}
fAST.SetInt(int64(tok))
case reflect.Int:
fAST.Set(c)
default:
panic(fmt.Sprintf("internal error: unexpected kind %s", c.Kind()))
}
default:
r := NodeToAST(fNode.Interface().(Node), state)
if r != nil {
fAST.Set(reflect.ValueOf(r))
}
}
}
return out.Interface().(ast.Node)
}

272
vendor/honnef.co/go/tools/pattern/doc.go vendored Normal file
View File

@@ -0,0 +1,272 @@
/*
Package pattern implements a simple language for pattern matching Go ASTs.
# Design decisions and trade-offs
The language is designed specifically for the task of filtering ASTs
to simplify the implementation of analyses in staticcheck.
It is also intended to be trivial to parse and execute.
To that end, we make certain decisions that make the language more
suited to its task, while making certain queries infeasible.
Furthermore, it is fully expected that the majority of analyses will still require ordinary Go code
to further process the filtered AST, to make use of type information and to enforce complex invariants.
It is not our goal to design a scripting language for writing entire checks in.
# The language
At its core, patterns are a representation of Go ASTs, allowing for the use of placeholders to enable pattern matching.
Their syntax is inspired by LISP and Haskell, but unlike LISP, the core unit of patterns isn't the list, but the node.
There is a fixed set of nodes, identified by name, and with the exception of the Or node, all nodes have a fixed number of arguments.
In addition to nodes, there are atoms, which represent basic units such as strings or the nil value.
Pattern matching is implemented via bindings, represented by the Binding node.
A Binding can match nodes and associate them with names, to later recall the nodes.
This allows for expressing "this node must be equal to that node" constraints.
To simplify writing and reading patterns, a small amount of additional syntax exists on top of nodes and atoms.
This additional syntax doesn't add any new features of its own, it simply provides shortcuts to creating nodes and atoms.
To show an example of a pattern, first consider this snippet of Go code:
if x := fn(); x != nil {
for _, v := range x {
println(v, x)
}
}
The corresponding AST expressed as an idiomatic pattern would look as follows:
(IfStmt
(AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") []))
(BinaryExpr (Ident "x") "!=" (Ident "nil"))
(RangeStmt
(Ident "_") (Ident "v") ":=" (Ident "x")
(CallExpr (Ident "println") [(Ident "v") (Ident "x")]))
nil)
Two things are worth noting about this representation.
First, the [el1 el2 ...] syntax is a short-hand for creating lists.
It is a short-hand for el1:el2:[], which itself is a short-hand for (List el1 (List el2 (List nil nil)).
Second, note the absence of a lot of lists in places that normally accept lists.
For example, assignment assigns a number of right-hands to a number of left-hands, yet our AssignStmt is lacking any form of list.
This is due to the fact that a single node can match a list of exactly one element.
Thus, the two following forms have identical matching behavior:
(AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") []))
(AssignStmt [(Ident "x")] ":=" [(CallExpr (Ident "fn") [])])
This section serves as an overview of the language's syntax.
More in-depth explanations of the matching behavior as well as an exhaustive list of node types follows in the coming sections.
# Pattern matching
# TODO write about pattern matching
- inspired by haskell syntax, but much, much simpler and naive
# Node types
The language contains two kinds of nodes: those that map to nodes in the AST, and those that implement additional logic.
Nodes that map directly to AST nodes are named identically to the types in the go/ast package.
What follows is an exhaustive list of these nodes:
(ArrayType len elt)
(AssignStmt lhs tok rhs)
(BasicLit kind value)
(BinaryExpr x op y)
(BranchStmt tok label)
(CallExpr fun args)
(CaseClause list body)
(ChanType dir value)
(CommClause comm body)
(CompositeLit type elts)
(DeferStmt call)
(Ellipsis elt)
(EmptyStmt)
(Field names type tag)
(ForStmt init cond post body)
(FuncDecl recv name type body)
(FuncLit type body)
(FuncType params results)
(GenDecl specs)
(GoStmt call)
(Ident name)
(IfStmt init cond body else)
(ImportSpec name path)
(IncDecStmt x tok)
(IndexExpr x index)
(InterfaceType methods)
(KeyValueExpr key value)
(MapType key value)
(RangeStmt key value tok x body)
(ReturnStmt results)
(SelectStmt body)
(SelectorExpr x sel)
(SendStmt chan value)
(SliceExpr x low high max)
(StarExpr x)
(StructType fields)
(SwitchStmt init tag body)
(TypeAssertExpr)
(TypeSpec name type)
(TypeSwitchStmt init assign body)
(UnaryExpr op x)
(ValueSpec names type values)
Additionally, there are the String, Token and nil atoms.
Strings are double-quoted string literals, as in (Ident "someName").
Tokens are also represented as double-quoted string literals, but are converted to token.Token values in contexts that require tokens,
such as in (BinaryExpr x "<" y), where "<" is transparently converted to token.LSS during matching.
The keyword 'nil' denotes the nil value, which represents the absence of any value.
We also define the (List head tail) node, which is used to represent sequences of elements as a singly linked list.
The head is a single element, and the tail is the remainder of the list.
For example,
(List "foo" (List "bar" (List "baz" (List nil nil))))
represents a list of three elements, "foo", "bar" and "baz". There is dedicated syntax for writing lists, which looks as follows:
["foo" "bar" "baz"]
This syntax is itself syntactic sugar for the following form:
"foo":"bar":"baz":[]
This form is of particular interest for pattern matching, as it allows matching on the head and tail. For example,
"foo":"bar":_
would match any list with at least two elements, where the first two elements are "foo" and "bar". This is equivalent to writing
(List "foo" (List "bar" _))
Note that it is not possible to match from the end of the list.
That is, there is no way to express a query such as "a list of any length where the last element is foo".
Note that unlike in LISP, nil and empty lists are distinct from one another.
In patterns, with respect to lists, nil is akin to Go's untyped nil.
It will match a nil ast.Node, but it will not match a nil []ast.Expr. Nil will, however, match pointers to named types such as *ast.Ident.
Similarly, lists are akin to Go's
slices. An empty list will match both a nil and an empty []ast.Expr, but it will not match a nil ast.Node.
Due to the difference between nil and empty lists, an empty list is represented as (List nil nil), i.e. a list with no head or tail.
Similarly, a list of one element is represented as (List el (List nil nil)). Unlike in LISP, it cannot be represented by (List el nil).
Finally, there are nodes that implement special logic or matching behavior.
(Any) matches any value. The underscore (_) maps to this node, making the following two forms equivalent:
(Ident _)
(Ident (Any))
(Builtin name) matches a built-in identifier or function by name.
This is a type-aware variant of (Ident name).
Instead of only comparing the name, it resolves the object behind the name and makes sure it's a pre-declared identifier.
For example, in the following piece of code
func fn() {
println(true)
true := false
println(true)
}
the pattern
(Builtin "true")
will match exactly once, on the first use of 'true' in the function.
Subsequent occurrences of 'true' no longer refer to the pre-declared identifier.
(Object name) matches an identifier by name, but yields the
types.Object it refers to.
(Symbol name) matches ast.Idents and ast.SelectorExprs that refer to a symbol with a given fully qualified name.
For example, "net/url.PathEscape" matches the PathEscape function in the net/url package,
and "(net/url.EscapeError).Error" refers to the Error method on the net/url.EscapeError type,
either on an instance of the type, or on the type itself.
For example, the following patterns match the following lines of code:
(CallExpr (Symbol "fmt.Println") _) // pattern 1
(CallExpr (Symbol "(net/url.EscapeError).Error") _) // pattern 2
fmt.Println("hello, world") // matches pattern 1
var x url.EscapeError
x.Error() // matches pattern 2
(url.EscapeError).Error(x) // also matches pattern 2
(Binding name node) creates or uses a binding.
Bindings work like variable assignments, allowing referring to already matched nodes.
As an example, bindings are necessary to match self-assignment of the form "x = x",
since we need to express that the right-hand side is identical to the left-hand side.
If a binding's node is not nil, the matcher will attempt to match a node according to the pattern.
If a binding's node is nil, the binding will either recall an existing value, or match the Any node.
It is an error to provide a non-nil node to a binding that has already been bound.
Referring back to the earlier example, the following pattern will match self-assignment of idents:
(AssignStmt (Binding "lhs" (Ident _)) "=" (Binding "lhs" nil))
Because bindings are a crucial component of pattern matching, there is special syntax for creating and recalling bindings.
Lower-case names refer to bindings. If standing on its own, the name "foo" will be equivalent to (Binding "foo" nil).
If a name is followed by an at-sign (@) then it will create a binding for the node that follows.
Together, this allows us to rewrite the earlier example as follows:
(AssignStmt lhs@(Ident _) "=" lhs)
(Or nodes...) is a variadic node that tries matching each node until one succeeds. For example, the following pattern matches all idents of name "foo" or "bar":
(Ident (Or "foo" "bar"))
We could also have written
(Or (Ident "foo") (Ident "bar"))
and achieved the same result. We can also mix different kinds of nodes:
(Or (Ident "foo") (CallExpr (Ident "bar") _))
When using bindings inside of nodes used inside Or, all or none of the bindings will be bound.
That is, partially matched nodes that ultimately failed to match will not produce any bindings observable outside of the matching attempt.
We can thus write
(Or (Ident name) (CallExpr name))
and 'name' will either be a String if the first option matched, or an Ident or SelectorExpr if the second option matched.
(Not node)
The Not node negates a match. For example, (Not (Ident _)) will match all nodes that aren't identifiers.
ChanDir(0)
# Automatic unnesting of AST nodes
The Go AST has several types of nodes that wrap other nodes.
To simplify matching, we automatically unwrap some of these nodes.
These nodes are ExprStmt (for using expressions in a statement context),
ParenExpr (for parenthesized expressions),
DeclStmt (for declarations in a statement context),
and LabeledStmt (for labeled statements).
Thus, the query
(FuncLit _ [(CallExpr _ _)]
will match a function literal containing a single function call,
even though in the actual Go AST, the CallExpr is nested inside an ExprStmt,
as function bodies are made up of sequences of statements.
On the flip-side, there is no way to specifically match these wrapper nodes.
For example, there is no way of searching for unnecessary parentheses, like in the following piece of Go code:
((x)) += 2
*/
package pattern

View File

@@ -0,0 +1,257 @@
package pattern
import (
"fmt"
"go/token"
"iter"
"unicode"
"unicode/utf8"
)
// lex returns the sequence of tokens in the input.
func lex(f *token.File, input string) iter.Seq[item] {
return func(yield func(item) bool) {
lex := &lexer{
f: f,
input: input,
yield: yield,
}
lex.run()
}
}
// lexer holds the state of a single [lex] iteration.
type lexer struct {
f *token.File
input string
start int
pos int
width int
yield func(item) bool
}
type itemType int
const eof = -1
const (
itemError itemType = iota
itemLeftParen
itemRightParen
itemLeftBracket
itemRightBracket
itemTypeName
itemVariable
itemAt
itemColon
itemBlank
itemString
itemEOF
)
func (typ itemType) String() string {
switch typ {
case itemError:
return "ERROR"
case itemLeftParen:
return "("
case itemRightParen:
return ")"
case itemLeftBracket:
return "["
case itemRightBracket:
return "]"
case itemTypeName:
return "TYPE"
case itemVariable:
return "VAR"
case itemAt:
return "@"
case itemColon:
return ":"
case itemBlank:
return "_"
case itemString:
return "STRING"
case itemEOF:
return "EOF"
default:
return fmt.Sprintf("itemType(%d)", typ)
}
}
type item struct {
typ itemType
val string
pos int
}
type stateFn func(*lexer) stateFn
func (l *lexer) run() {
for state := lexStart; state != nil; {
state = state(l)
}
}
func (l *lexer) emitValue(t itemType, value string) bool {
ok := l.yield(item{t, value, l.start})
l.start = l.pos
return ok
}
func (l *lexer) emit(t itemType) bool {
ok := l.yield(item{t, l.input[l.start:l.pos], l.start})
l.start = l.pos
return ok
}
func lexStart(l *lexer) stateFn {
switch r := l.next(); {
case r == eof:
_ = l.emit(itemEOF)
return nil
case unicode.IsSpace(r):
l.ignore()
case r == '(':
if !l.emit(itemLeftParen) {
return nil
}
case r == ')':
if !l.emit(itemRightParen) {
return nil
}
case r == '[':
if !l.emit(itemLeftBracket) {
return nil
}
case r == ']':
if !l.emit(itemRightBracket) {
return nil
}
case r == '@':
if !l.emit(itemAt) {
return nil
}
case r == ':':
if !l.emit(itemColon) {
return nil
}
case r == '_':
if !l.emit(itemBlank) {
return nil
}
case r == '"':
l.backup()
return lexString
case unicode.IsUpper(r):
l.backup()
return lexType
case unicode.IsLower(r):
l.backup()
return lexVariable
default:
return l.errorf("unexpected character %c", r)
}
return lexStart
}
func (l *lexer) next() (r rune) {
if l.pos >= len(l.input) {
l.width = 0
return eof
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
if r == '\n' {
l.f.AddLine(l.pos)
}
l.pos += l.width
return r
}
func (l *lexer) ignore() {
l.start = l.pos
}
func (l *lexer) backup() {
l.pos -= l.width
}
func (l *lexer) errorf(format string, args ...any) stateFn {
// TODO(dh): emit position information in errors
_ = l.yield(item{
itemError,
fmt.Sprintf(format, args...),
l.start,
})
return nil
}
func isAlphaNumeric(r rune) bool {
return r >= '0' && r <= '9' ||
r >= 'a' && r <= 'z' ||
r >= 'A' && r <= 'Z'
}
func lexString(l *lexer) stateFn {
l.next() // skip quote
escape := false
var runes []rune
for {
switch r := l.next(); r {
case eof:
return l.errorf("unterminated string")
case '"':
if !escape {
if !l.emitValue(itemString, string(runes)) {
return nil
}
return lexStart
} else {
runes = append(runes, '"')
escape = false
}
case '\\':
if escape {
runes = append(runes, '\\')
escape = false
} else {
escape = true
}
default:
runes = append(runes, r)
}
}
}
func lexType(l *lexer) stateFn {
l.next()
for {
if !isAlphaNumeric(l.next()) {
l.backup()
if !l.emit(itemTypeName) {
return nil
}
return lexStart
}
}
}
func lexVariable(l *lexer) stateFn {
l.next()
for {
if !isAlphaNumeric(l.next()) {
l.backup()
if !l.emit(itemVariable) {
return nil
}
return lexStart
}
}
}

View File

@@ -0,0 +1,712 @@
package pattern
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"reflect"
)
var tokensByString = map[string]Token{
"INT": Token(token.INT),
"FLOAT": Token(token.FLOAT),
"IMAG": Token(token.IMAG),
"CHAR": Token(token.CHAR),
"STRING": Token(token.STRING),
"+": Token(token.ADD),
"-": Token(token.SUB),
"*": Token(token.MUL),
"/": Token(token.QUO),
"%": Token(token.REM),
"&": Token(token.AND),
"|": Token(token.OR),
"^": Token(token.XOR),
"<<": Token(token.SHL),
">>": Token(token.SHR),
"&^": Token(token.AND_NOT),
"+=": Token(token.ADD_ASSIGN),
"-=": Token(token.SUB_ASSIGN),
"*=": Token(token.MUL_ASSIGN),
"/=": Token(token.QUO_ASSIGN),
"%=": Token(token.REM_ASSIGN),
"&=": Token(token.AND_ASSIGN),
"|=": Token(token.OR_ASSIGN),
"^=": Token(token.XOR_ASSIGN),
"<<=": Token(token.SHL_ASSIGN),
">>=": Token(token.SHR_ASSIGN),
"&^=": Token(token.AND_NOT_ASSIGN),
"&&": Token(token.LAND),
"||": Token(token.LOR),
"<-": Token(token.ARROW),
"++": Token(token.INC),
"--": Token(token.DEC),
"==": Token(token.EQL),
"<": Token(token.LSS),
">": Token(token.GTR),
"=": Token(token.ASSIGN),
"!": Token(token.NOT),
"!=": Token(token.NEQ),
"<=": Token(token.LEQ),
">=": Token(token.GEQ),
":=": Token(token.DEFINE),
"...": Token(token.ELLIPSIS),
"IMPORT": Token(token.IMPORT),
"VAR": Token(token.VAR),
"TYPE": Token(token.TYPE),
"CONST": Token(token.CONST),
"BREAK": Token(token.BREAK),
"CONTINUE": Token(token.CONTINUE),
"GOTO": Token(token.GOTO),
"FALLTHROUGH": Token(token.FALLTHROUGH),
}
func maybeToken(node Node) (Node, bool) {
if node, ok := node.(String); ok {
if tok, ok := tokensByString[string(node)]; ok {
return tok, true
}
return node, false
}
return node, false
}
func isNil(v any) bool {
if v == nil {
return true
}
if _, ok := v.(Nil); ok {
return true
}
return false
}
type matcher interface {
Match(*Matcher, any) (any, bool)
}
type State = map[string]any
type Matcher struct {
TypesInfo *types.Info
State State
bindingsMapping []string
setBindings []uint64
}
func (m *Matcher) set(b Binding, value any) {
m.State[b.Name] = value
m.setBindings[len(m.setBindings)-1] |= 1 << b.idx
}
func (m *Matcher) push() {
m.setBindings = append(m.setBindings, 0)
}
func (m *Matcher) pop() {
set := m.setBindings[len(m.setBindings)-1]
if set != 0 {
for i := 0; i < len(m.bindingsMapping); i++ {
if (set & (1 << i)) != 0 {
key := m.bindingsMapping[i]
delete(m.State, key)
}
}
}
m.setBindings = m.setBindings[:len(m.setBindings)-1]
}
func (m *Matcher) merge() {
m.setBindings = m.setBindings[:len(m.setBindings)-1]
}
func (m *Matcher) Match(a Pattern, b ast.Node) bool {
m.bindingsMapping = a.Bindings
m.State = State{}
m.push()
_, ok := match(m, a.Root, b)
m.merge()
if len(m.setBindings) != 0 {
panic(fmt.Sprintf("%d entries left on the stack, expected none", len(m.setBindings)))
}
return ok
}
func Match(a Pattern, b ast.Node) (*Matcher, bool) {
m := &Matcher{}
ret := m.Match(a, b)
return m, ret
}
// Match two items, which may be (Node, AST) or (AST, AST)
func match(m *Matcher, l, r any) (any, bool) {
if _, ok := r.(Node); ok {
panic("Node mustn't be on right side of match")
}
switch l := l.(type) {
case *ast.ParenExpr:
return match(m, l.X, r)
case *ast.ExprStmt:
return match(m, l.X, r)
case *ast.DeclStmt:
return match(m, l.Decl, r)
case *ast.LabeledStmt:
return match(m, l.Stmt, r)
case *ast.BlockStmt:
return match(m, l.List, r)
case *ast.FieldList:
if l == nil {
return match(m, nil, r)
} else {
return match(m, l.List, r)
}
}
switch r := r.(type) {
case *ast.ParenExpr:
return match(m, l, r.X)
case *ast.ExprStmt:
return match(m, l, r.X)
case *ast.DeclStmt:
return match(m, l, r.Decl)
case *ast.LabeledStmt:
return match(m, l, r.Stmt)
case *ast.BlockStmt:
if r == nil {
return match(m, l, nil)
}
return match(m, l, r.List)
case *ast.FieldList:
if r == nil {
return match(m, l, nil)
}
return match(m, l, r.List)
case *ast.BasicLit:
if r == nil {
return match(m, l, nil)
}
}
if l, ok := l.(matcher); ok {
return l.Match(m, r)
}
if l, ok := l.(Node); ok {
// Matching of pattern with concrete value
return matchNodeAST(m, l, r)
}
if l == nil || r == nil {
return nil, l == r
}
{
ln, ok1 := l.(ast.Node)
rn, ok2 := r.(ast.Node)
if ok1 && ok2 {
return matchAST(m, ln, rn)
}
}
{
obj, ok := l.(types.Object)
if ok {
switch r := r.(type) {
case *ast.Ident:
return obj, obj == m.TypesInfo.ObjectOf(r)
case *ast.SelectorExpr:
return obj, obj == m.TypesInfo.ObjectOf(r.Sel)
default:
return obj, false
}
}
}
// TODO(dh): the three blocks handling slices can be combined into a single block if we use reflection
{
ln, ok1 := l.([]ast.Expr)
rn, ok2 := r.([]ast.Expr)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(ast.Expr)
if !ok {
return nil, false
}
rn = []ast.Expr{cast}
} else if !ok1 && ok2 {
cast, ok := l.(ast.Expr)
if !ok {
return nil, false
}
ln = []ast.Expr{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
{
ln, ok1 := l.([]ast.Stmt)
rn, ok2 := r.([]ast.Stmt)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(ast.Stmt)
if !ok {
return nil, false
}
rn = []ast.Stmt{cast}
} else if !ok1 && ok2 {
cast, ok := l.(ast.Stmt)
if !ok {
return nil, false
}
ln = []ast.Stmt{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
{
ln, ok1 := l.([]*ast.Field)
rn, ok2 := r.([]*ast.Field)
if ok1 || ok2 {
if ok1 && !ok2 {
cast, ok := r.(*ast.Field)
if !ok {
return nil, false
}
rn = []*ast.Field{cast}
} else if !ok1 && ok2 {
cast, ok := l.(*ast.Field)
if !ok {
return nil, false
}
ln = []*ast.Field{cast}
}
if len(ln) != len(rn) {
return nil, false
}
for i, ll := range ln {
if _, ok := match(m, ll, rn[i]); !ok {
return nil, false
}
}
return r, true
}
}
return nil, false
}
// Match a Node with an AST node
func matchNodeAST(m *Matcher, a Node, b any) (any, bool) {
switch b := b.(type) {
case []ast.Stmt:
// 'a' is not a List or we'd be using its Match
// implementation.
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case []ast.Expr:
// 'a' is not a List or we'd be using its Match
// implementation.
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case []*ast.Field:
// 'a' is not a List or we'd be using its Match
// implementation
if len(b) != 1 {
return nil, false
}
return match(m, a, b[0])
case ast.Node:
ra := reflect.ValueOf(a)
rb := reflect.ValueOf(b).Elem()
if ra.Type().Name() != rb.Type().Name() {
return nil, false
}
for i := 0; i < ra.NumField(); i++ {
af := ra.Field(i)
fieldName := ra.Type().Field(i).Name
bf := rb.FieldByName(fieldName)
if (bf == reflect.Value{}) {
panic(fmt.Sprintf("internal error: could not find field %s in type %t when comparing with %T", fieldName, b, a))
}
ai := af.Interface()
bi := bf.Interface()
if ai == nil {
return b, bi == nil
}
if _, ok := match(m, ai.(Node), bi); !ok {
return b, false
}
}
return b, true
case nil:
return nil, a == Nil{}
case string, token.Token:
// 'a' can't be a String, Token, or Binding or we'd be using their Match implementations.
return nil, false
default:
panic(fmt.Sprintf("unhandled type %T", b))
}
}
// Match two AST nodes
func matchAST(m *Matcher, a, b ast.Node) (any, bool) {
ra := reflect.ValueOf(a)
rb := reflect.ValueOf(b)
if ra.Type() != rb.Type() {
return nil, false
}
if ra.IsNil() || rb.IsNil() {
return rb, ra.IsNil() == rb.IsNil()
}
ra = ra.Elem()
rb = rb.Elem()
for i := 0; i < ra.NumField(); i++ {
af := ra.Field(i)
bf := rb.Field(i)
if af.Type() == rtTokPos || af.Type() == rtObject || af.Type() == rtCommentGroup {
continue
}
switch af.Kind() {
case reflect.Slice:
if af.Len() != bf.Len() {
return nil, false
}
for j := 0; j < af.Len(); j++ {
if _, ok := match(m, af.Index(j).Interface().(ast.Node), bf.Index(j).Interface().(ast.Node)); !ok {
return nil, false
}
}
case reflect.String:
if af.String() != bf.String() {
return nil, false
}
case reflect.Int:
if af.Int() != bf.Int() {
return nil, false
}
case reflect.Bool:
if af.Bool() != bf.Bool() {
return nil, false
}
case reflect.Pointer, reflect.Interface:
if _, ok := match(m, af.Interface(), bf.Interface()); !ok {
return nil, false
}
default:
panic(fmt.Sprintf("internal error: unhandled kind %s (%T)", af.Kind(), af.Interface()))
}
}
return b, true
}
func (b Binding) Match(m *Matcher, node any) (any, bool) {
if isNil(b.Node) {
v, ok := m.State[b.Name]
if ok {
// Recall value
return match(m, v, node)
}
// Matching anything
b.Node = Any{}
}
// Store value
if _, ok := m.State[b.Name]; ok {
panic(fmt.Sprintf("binding already created: %s", b.Name))
}
new, ret := match(m, b.Node, node)
if ret {
m.set(b, new)
}
return new, ret
}
func (Any) Match(m *Matcher, node any) (any, bool) {
return node, true
}
func (l List) Match(m *Matcher, node any) (any, bool) {
v := reflect.ValueOf(node)
if v.Kind() == reflect.Slice {
if isNil(l.Head) {
return node, v.Len() == 0
}
if v.Len() == 0 {
return nil, false
}
// OPT(dh): don't check the entire tail if head didn't match
_, ok1 := match(m, l.Head, v.Index(0).Interface())
_, ok2 := match(m, l.Tail, v.Slice(1, v.Len()).Interface())
return node, ok1 && ok2
}
// Our empty list does not equal an untyped Go nil. This way, we can
// tell apart an if with no else and an if with an empty else.
return nil, false
}
func (s String) Match(m *Matcher, node any) (any, bool) {
switch o := node.(type) {
case token.Token:
if tok, ok := maybeToken(s); ok {
return match(m, tok, node)
}
return nil, false
case string:
return o, string(s) == o
case types.TypeAndValue:
return o, o.Value != nil && o.Value.String() == string(s)
default:
return nil, false
}
}
func (tok Token) Match(m *Matcher, node any) (any, bool) {
o, ok := node.(token.Token)
if !ok {
return nil, false
}
return o, token.Token(tok) == o
}
func (Nil) Match(m *Matcher, node any) (any, bool) {
if isNil(node) {
return nil, true
}
v := reflect.ValueOf(node)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return nil, v.IsNil()
default:
return nil, false
}
}
func (builtin Builtin) Match(m *Matcher, node any) (any, bool) {
r, ok := match(m, Ident(builtin), node)
if !ok {
return nil, false
}
ident := r.(*ast.Ident)
obj := m.TypesInfo.ObjectOf(ident)
if obj != types.Universe.Lookup(ident.Name) {
return nil, false
}
return ident, true
}
func (obj Object) Match(m *Matcher, node any) (any, bool) {
r, ok := match(m, Ident(obj), node)
if !ok {
return nil, false
}
ident := r.(*ast.Ident)
id := m.TypesInfo.ObjectOf(ident)
_, ok = match(m, obj.Name, ident.Name)
return id, ok
}
func (fn Symbol) Match(m *Matcher, node any) (any, bool) {
var name string
var obj types.Object
base := []Node{
Ident{Any{}},
SelectorExpr{Any{}, Any{}},
}
p := Or{
Nodes: append(base,
IndexExpr{Or{Nodes: base}, Any{}},
IndexListExpr{Or{Nodes: base}, Any{}})}
r, ok := match(m, p, node)
if !ok {
return nil, false
}
fun := r.(ast.Expr)
switch idx := fun.(type) {
case *ast.IndexExpr:
fun = idx.X
case *ast.IndexListExpr:
fun = idx.X
}
switch fun := ast.Unparen(fun).(type) {
case *ast.Ident:
obj = m.TypesInfo.ObjectOf(fun)
case *ast.SelectorExpr:
obj = m.TypesInfo.ObjectOf(fun.Sel)
default:
panic("unreachable")
}
switch obj := obj.(type) {
case *types.Func:
// OPT(dh): optimize this similar to code.FuncName
name = obj.FullName()
case *types.Builtin:
name = obj.Name()
case *types.TypeName:
origObj := obj
for {
if obj.Parent() != obj.Pkg().Scope() {
return nil, false
}
name = types.TypeString(obj.Type(), nil)
_, ok = match(m, fn.Name, name)
if ok || !obj.IsAlias() {
return origObj, ok
} else {
// FIXME(dh): we should peel away one layer of alias at a time; this is blocked on
// github.com/golang/go/issues/66559
switch typ := types.Unalias(obj.Type()).(type) {
case interface{ Obj() *types.TypeName }:
obj = typ.Obj()
case *types.Basic:
return match(m, fn.Name, typ.Name())
default:
return nil, false
}
}
}
case *types.Const, *types.Var:
if obj.Pkg() == nil {
return nil, false
}
if obj.Parent() != obj.Pkg().Scope() {
return nil, false
}
name = fmt.Sprintf("%s.%s", obj.Pkg().Path(), obj.Name())
default:
return nil, false
}
_, ok = match(m, fn.Name, name)
return obj, ok
}
func (or Or) Match(m *Matcher, node any) (any, bool) {
for _, opt := range or.Nodes {
m.push()
if ret, ok := match(m, opt, node); ok {
m.merge()
return ret, true
} else {
m.pop()
}
}
return nil, false
}
func (not Not) Match(m *Matcher, node any) (any, bool) {
_, ok := match(m, not.Node, node)
if ok {
return nil, false
}
return node, true
}
var integerLiteralQ = MustParse(`(Or (BasicLit "INT" _) (UnaryExpr (Or "+" "-") (IntegerLiteral _)))`)
func (lit IntegerLiteral) Match(m *Matcher, node any) (any, bool) {
matched, ok := match(m, integerLiteralQ.Root, node)
if !ok {
return nil, false
}
tv, ok := m.TypesInfo.Types[matched.(ast.Expr)]
if !ok {
return nil, false
}
if tv.Value == nil {
return nil, false
}
_, ok = match(m, lit.Value, tv)
return matched, ok
}
func (texpr TrulyConstantExpression) Match(m *Matcher, node any) (any, bool) {
expr, ok := node.(ast.Expr)
if !ok {
return nil, false
}
tv, ok := m.TypesInfo.Types[expr]
if !ok {
return nil, false
}
if tv.Value == nil {
return nil, false
}
truly := true
ast.Inspect(expr, func(node ast.Node) bool {
if _, ok := node.(*ast.Ident); ok {
truly = false
return false
}
return true
})
if !truly {
return nil, false
}
_, ok = match(m, texpr.Value, tv)
return expr, ok
}
var (
// Types of fields in go/ast structs that we want to skip
rtTokPos = reflect.TypeFor[token.Pos]()
//lint:ignore SA1019 It's deprecated, but we still want to skip the field.
rtObject = reflect.TypeFor[*ast.Object]()
rtCommentGroup = reflect.TypeFor[*ast.CommentGroup]()
)
var (
_ matcher = Binding{}
_ matcher = Any{}
_ matcher = List{}
_ matcher = String("")
_ matcher = Token(0)
_ matcher = Nil{}
_ matcher = Builtin{}
_ matcher = Object{}
_ matcher = Symbol{}
_ matcher = Or{}
_ matcher = Not{}
_ matcher = IntegerLiteral{}
_ matcher = TrulyConstantExpression{}
)

View File

@@ -0,0 +1,714 @@
package pattern
import (
"errors"
"fmt"
"go/ast"
"go/token"
"iter"
"reflect"
"strings"
)
type Pattern struct {
Root Node
// EntryNodes contains instances of ast.Node that could potentially
// initiate a successful match of the pattern.
EntryNodes []ast.Node
// SymbolsPattern is a pattern consisting or Any, Or, And, and IndexSymbol,
// that can be used to implement fast rejection of whole packages using
// typeindex.
SymbolsPattern Node
// If non-empty, all possible candidate nodes for this pattern can be found
// by finding all call expressions for this list of symbols.
RootCallSymbols []IndexSymbol
// Mapping from binding index to binding name
Bindings []string
}
func MustParse(s string) Pattern {
p := &Parser{AllowTypeInfo: true}
pat, err := p.Parse(s)
if err != nil {
panic(err)
}
return pat
}
func symbolToIndexSymbol(name string) IndexSymbol {
if len(name) == 0 {
return IndexSymbol{}
}
if name[0] == '(' {
end := strings.IndexAny(name, ")")
// Ensure there's a ), and also that there are at least two more
// characters after it, for a dot and an identifier.
if end == -1 || end > len(name)-2 {
return IndexSymbol{}
}
pathAndType := strings.TrimPrefix(name[1:end], "*")
dot := strings.LastIndex(pathAndType, ".")
if dot == -1 {
return IndexSymbol{}
}
path := pathAndType[:dot]
typ := pathAndType[dot+1:]
ident := name[end+2:]
return IndexSymbol{path, typ, ident}
} else {
dot := strings.LastIndex(name, ".")
if dot == -1 {
return IndexSymbol{"", "", name}
}
path := name[:dot]
ident := name[dot+1:]
return IndexSymbol{path, "", ident}
}
}
func collectSymbols(node Node, inSymbol bool) Node {
and := func(c Node, out *And) {
switch cc := c.(type) {
case And:
out.Nodes = append(out.Nodes, cc.Nodes...)
case Any:
case nil:
default:
out.Nodes = append(out.Nodes, c)
}
}
switch node := node.(type) {
case Or:
s := Or{}
for _, el := range node.Nodes {
c := collectSymbols(el, inSymbol)
switch cc := c.(type) {
case Or:
s.Nodes = append(s.Nodes, cc.Nodes...)
case Any:
return Any{}
case nil:
default:
s.Nodes = append(s.Nodes, c)
}
}
switch len(s.Nodes) {
case 0:
return nil
case 1:
return s.Nodes[0]
default:
return s
}
case Not, Token, nil:
return Any{}
case Symbol:
return collectSymbols(node.Name, true)
case String:
if !inSymbol {
return Any{}
}
// In logically correct patterns, all Strings that are children of
// Symbols describe the names of symbols.
return symbolToIndexSymbol(string(node))
case Binding:
return collectSymbols(node.Node, inSymbol)
case Any:
return Any{}
case List:
var out And
and(collectSymbols(node.Head, inSymbol), &out)
and(collectSymbols(node.Tail, inSymbol), &out)
switch len(out.Nodes) {
case 0:
return Any{}
case 1:
return out.Nodes[0]
default:
return out
}
default:
var out And
rv := reflect.ValueOf(node)
for i := range rv.NumField() {
c := collectSymbols(rv.Field(i).Interface().(Node), inSymbol)
and(c, &out)
}
switch len(out.Nodes) {
case 0:
return Any{}
case 1:
return out.Nodes[0]
default:
return out
}
}
}
func collectRootCallSymbols(node Node) []IndexSymbol {
root, ok := node.(CallExpr)
if !ok {
return nil
}
var names []String
var handleSymName func(name Node) bool
handleSymName = func(name Node) bool {
switch name := name.(type) {
case String:
names = append(names, name)
case Or:
for _, node := range name.Nodes {
if name, ok := node.(String); ok {
names = append(names, name)
} else {
return false
}
}
case Binding:
return handleSymName(name.Node)
default:
return false
}
return true
}
var handleRootFun func(node Node) bool
handleRootFun = func(node Node) bool {
switch fun := node.(type) {
case Binding:
return handleRootFun(fun.Node)
case Symbol:
return handleSymName(fun.Name)
case Or:
for _, node := range fun.Nodes {
if sym, ok := node.(Symbol); !ok || !handleSymName(sym.Name) {
return false
}
}
return true
default:
return false
}
}
if !handleRootFun(root.Fun) {
return nil
}
out := make([]IndexSymbol, len(names))
for i, name := range names {
out[i] = symbolToIndexSymbol(string(name))
}
return out
}
func collectEntryNodes(node Node, m map[reflect.Type]struct{}) {
switch node := node.(type) {
case Or:
for _, el := range node.Nodes {
collectEntryNodes(el, m)
}
case Not:
collectEntryNodes(node.Node, m)
case Binding:
collectEntryNodes(node.Node, m)
case Nil, nil:
// this branch is reached via bindings
for _, T := range allTypes {
m[T] = struct{}{}
}
default:
Ts, ok := nodeToASTTypes[reflect.TypeOf(node)]
if !ok {
panic(fmt.Sprintf("internal error: unhandled type %T", node))
}
for _, T := range Ts {
m[T] = struct{}{}
}
}
}
var allTypes = []reflect.Type{
reflect.TypeFor[*ast.RangeStmt](),
reflect.TypeFor[*ast.AssignStmt](),
reflect.TypeFor[*ast.IndexExpr](),
reflect.TypeFor[*ast.Ident](),
reflect.TypeFor[*ast.ValueSpec](),
reflect.TypeFor[*ast.GenDecl](),
reflect.TypeFor[*ast.BinaryExpr](),
reflect.TypeFor[*ast.ForStmt](),
reflect.TypeFor[*ast.ArrayType](),
reflect.TypeFor[*ast.DeferStmt](),
reflect.TypeFor[*ast.MapType](),
reflect.TypeFor[*ast.ReturnStmt](),
reflect.TypeFor[*ast.SliceExpr](),
reflect.TypeFor[*ast.StarExpr](),
reflect.TypeFor[*ast.UnaryExpr](),
reflect.TypeFor[*ast.SendStmt](),
reflect.TypeFor[*ast.SelectStmt](),
reflect.TypeFor[*ast.ImportSpec](),
reflect.TypeFor[*ast.IfStmt](),
reflect.TypeFor[*ast.GoStmt](),
reflect.TypeFor[*ast.Field](),
reflect.TypeFor[*ast.SelectorExpr](),
reflect.TypeFor[*ast.StructType](),
reflect.TypeFor[*ast.KeyValueExpr](),
reflect.TypeFor[*ast.FuncType](),
reflect.TypeFor[*ast.FuncLit](),
reflect.TypeFor[*ast.FuncDecl](),
reflect.TypeFor[*ast.ChanType](),
reflect.TypeFor[*ast.CallExpr](),
reflect.TypeFor[*ast.CaseClause](),
reflect.TypeFor[*ast.CommClause](),
reflect.TypeFor[*ast.CompositeLit](),
reflect.TypeFor[*ast.EmptyStmt](),
reflect.TypeFor[*ast.SwitchStmt](),
reflect.TypeFor[*ast.TypeSwitchStmt](),
reflect.TypeFor[*ast.TypeAssertExpr](),
reflect.TypeFor[*ast.TypeSpec](),
reflect.TypeFor[*ast.InterfaceType](),
reflect.TypeFor[*ast.BranchStmt](),
reflect.TypeFor[*ast.IncDecStmt](),
reflect.TypeFor[*ast.BasicLit](),
}
var nodeToASTTypes = map[reflect.Type][]reflect.Type{
reflect.TypeFor[String](): nil,
reflect.TypeFor[Token](): nil,
reflect.TypeFor[List](): {reflect.TypeFor[*ast.BlockStmt](), reflect.TypeFor[*ast.FieldList]()},
reflect.TypeFor[Builtin](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[Object](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[Symbol](): {reflect.TypeFor[*ast.Ident](), reflect.TypeFor[*ast.SelectorExpr]()},
reflect.TypeFor[Any](): allTypes,
reflect.TypeFor[RangeStmt](): {reflect.TypeFor[*ast.RangeStmt]()},
reflect.TypeFor[AssignStmt](): {reflect.TypeFor[*ast.AssignStmt]()},
reflect.TypeFor[IndexExpr](): {reflect.TypeFor[*ast.IndexExpr]()},
reflect.TypeFor[Ident](): {reflect.TypeFor[*ast.Ident]()},
reflect.TypeFor[ValueSpec](): {reflect.TypeFor[*ast.ValueSpec]()},
reflect.TypeFor[GenDecl](): {reflect.TypeFor[*ast.GenDecl]()},
reflect.TypeFor[BinaryExpr](): {reflect.TypeFor[*ast.BinaryExpr]()},
reflect.TypeFor[ForStmt](): {reflect.TypeFor[*ast.ForStmt]()},
reflect.TypeFor[ArrayType](): {reflect.TypeFor[*ast.ArrayType]()},
reflect.TypeFor[DeferStmt](): {reflect.TypeFor[*ast.DeferStmt]()},
reflect.TypeFor[MapType](): {reflect.TypeFor[*ast.MapType]()},
reflect.TypeFor[ReturnStmt](): {reflect.TypeFor[*ast.ReturnStmt]()},
reflect.TypeFor[SliceExpr](): {reflect.TypeFor[*ast.SliceExpr]()},
reflect.TypeFor[StarExpr](): {reflect.TypeFor[*ast.StarExpr]()},
reflect.TypeFor[UnaryExpr](): {reflect.TypeFor[*ast.UnaryExpr]()},
reflect.TypeFor[SendStmt](): {reflect.TypeFor[*ast.SendStmt]()},
reflect.TypeFor[SelectStmt](): {reflect.TypeFor[*ast.SelectStmt]()},
reflect.TypeFor[ImportSpec](): {reflect.TypeFor[*ast.ImportSpec]()},
reflect.TypeFor[IfStmt](): {reflect.TypeFor[*ast.IfStmt]()},
reflect.TypeFor[GoStmt](): {reflect.TypeFor[*ast.GoStmt]()},
reflect.TypeFor[Field](): {reflect.TypeFor[*ast.Field]()},
reflect.TypeFor[SelectorExpr](): {reflect.TypeFor[*ast.SelectorExpr]()},
reflect.TypeFor[StructType](): {reflect.TypeFor[*ast.StructType]()},
reflect.TypeFor[KeyValueExpr](): {reflect.TypeFor[*ast.KeyValueExpr]()},
reflect.TypeFor[FuncType](): {reflect.TypeFor[*ast.FuncType]()},
reflect.TypeFor[FuncLit](): {reflect.TypeFor[*ast.FuncLit]()},
reflect.TypeFor[FuncDecl](): {reflect.TypeFor[*ast.FuncDecl]()},
reflect.TypeFor[ChanType](): {reflect.TypeFor[*ast.ChanType]()},
reflect.TypeFor[CallExpr](): {reflect.TypeFor[*ast.CallExpr]()},
reflect.TypeFor[CaseClause](): {reflect.TypeFor[*ast.CaseClause]()},
reflect.TypeFor[CommClause](): {reflect.TypeFor[*ast.CommClause]()},
reflect.TypeFor[CompositeLit](): {reflect.TypeFor[*ast.CompositeLit]()},
reflect.TypeFor[EmptyStmt](): {reflect.TypeFor[*ast.EmptyStmt]()},
reflect.TypeFor[SwitchStmt](): {reflect.TypeFor[*ast.SwitchStmt]()},
reflect.TypeFor[TypeSwitchStmt](): {reflect.TypeFor[*ast.TypeSwitchStmt]()},
reflect.TypeFor[TypeAssertExpr](): {reflect.TypeFor[*ast.TypeAssertExpr]()},
reflect.TypeFor[TypeSpec](): {reflect.TypeFor[*ast.TypeSpec]()},
reflect.TypeFor[InterfaceType](): {reflect.TypeFor[*ast.InterfaceType]()},
reflect.TypeFor[BranchStmt](): {reflect.TypeFor[*ast.BranchStmt]()},
reflect.TypeFor[IncDecStmt](): {reflect.TypeFor[*ast.IncDecStmt]()},
reflect.TypeFor[BasicLit](): {reflect.TypeFor[*ast.BasicLit]()},
reflect.TypeFor[IntegerLiteral](): {reflect.TypeFor[*ast.BasicLit](), reflect.TypeFor[*ast.UnaryExpr]()},
reflect.TypeFor[TrulyConstantExpression](): allTypes, // this is an over-approximation, which is fine
}
var requiresTypeInfo = map[string]bool{
"Symbol": true,
"Builtin": true,
"Object": true,
"IntegerLiteral": true,
"TrulyConstantExpression": true,
}
type Parser struct {
// Allow nodes that rely on type information
AllowTypeInfo bool
f *token.File
cur item
last *item
nextItem func() (item, bool)
bindings map[string]int
}
func (p *Parser) bindingIndex(name string) int {
if p.bindings == nil {
p.bindings = map[string]int{}
}
if idx, ok := p.bindings[name]; ok {
return idx
}
idx := len(p.bindings)
p.bindings[name] = idx
return idx
}
func (p *Parser) Parse(s string) (Pattern, error) {
f := token.NewFileSet().AddFile("<input>", -1, len(s))
// Run the lexer iterator as a coroutine.
// The parser will call 'next' to consume each item.
// After the parser returns, we must call 'stop' to
// terminate the coroutine.
next, stop := iter.Pull(lex(f, s))
defer stop()
p.cur = item{}
p.last = nil
p.f = f
p.nextItem = next
// Parse.
root, err := p.node()
if err != nil {
return Pattern{}, err
}
// Consume final EOF token.
if item, ok := next(); !ok || item.typ != itemEOF {
return Pattern{}, fmt.Errorf("unexpected token %s after end of pattern", item.typ)
}
if len(p.bindings) > 64 {
return Pattern{}, errors.New("encountered more than 64 bindings")
}
bindings := make([]string, len(p.bindings))
for name, idx := range p.bindings {
bindings[idx] = name
}
_, isSymbol := root.(Symbol)
sym := collectSymbols(root, isSymbol)
rootSyms := collectRootCallSymbols(root)
relevantMap := map[reflect.Type]struct{}{}
collectEntryNodes(root, relevantMap)
relevantNodes := make([]ast.Node, 0, len(relevantMap))
for k := range relevantMap {
relevantNodes = append(relevantNodes, reflect.Zero(k).Interface().(ast.Node))
}
return Pattern{
Root: root,
EntryNodes: relevantNodes,
SymbolsPattern: sym,
RootCallSymbols: rootSyms,
Bindings: bindings,
}, nil
}
func (p *Parser) next() item {
if p.last != nil {
n := *p.last
p.last = nil
return n
}
var ok bool
p.cur, ok = p.nextItem()
if !ok {
p.cur = item{typ: eof}
}
return p.cur
}
func (p *Parser) rewind() {
p.last = &p.cur
}
func (p *Parser) peek() item {
n := p.next()
p.rewind()
return n
}
func (p *Parser) accept(typ itemType) (item, bool) {
n := p.next()
if n.typ == typ {
return n, true
}
p.rewind()
return item{}, false
}
func (p *Parser) unexpectedToken(valid string) error {
if p.cur.typ == itemError {
return fmt.Errorf("error lexing input: %s", p.cur.val)
}
var got string
switch p.cur.typ {
case itemTypeName, itemVariable, itemString:
got = p.cur.val
default:
got = "'" + p.cur.typ.String() + "'"
}
pos := p.f.Position(token.Pos(p.cur.pos))
return fmt.Errorf("%s: expected %s, found %s", pos, valid, got)
}
func (p *Parser) node() (Node, error) {
if _, ok := p.accept(itemLeftParen); !ok {
return nil, p.unexpectedToken("'('")
}
typ, ok := p.accept(itemTypeName)
if !ok {
return nil, p.unexpectedToken("Node type")
}
var objs []Node
for {
if _, ok := p.accept(itemRightParen); ok {
break
} else {
p.rewind()
obj, err := p.object()
if err != nil {
return nil, err
}
objs = append(objs, obj)
}
}
node, err := p.populateNode(typ.val, objs)
if err != nil {
return nil, err
}
if node, ok := node.(Binding); ok {
node.idx = p.bindingIndex(node.Name)
}
return node, nil
}
func populateNode(typ string, objs []Node, allowTypeInfo bool) (Node, error) {
T, ok := structNodes[typ]
if !ok {
return nil, fmt.Errorf("unknown node %s", typ)
}
if !allowTypeInfo && requiresTypeInfo[typ] {
return nil, fmt.Errorf("Node %s requires type information", typ)
}
pv := reflect.New(T)
v := pv.Elem()
if v.NumField() == 1 {
f := v.Field(0)
if f.Type().Kind() == reflect.Slice {
// Variadic node
f.Set(reflect.AppendSlice(f, reflect.ValueOf(objs)))
return v.Interface().(Node), nil
}
}
n := -1
for i := 0; i < T.NumField(); i++ {
if !T.Field(i).IsExported() {
break
}
n = i
}
if len(objs) != n+1 {
return nil, fmt.Errorf("tried to initialize node %s with %d values, expected %d", typ, len(objs), n+1)
}
for i := 0; i < v.NumField(); i++ {
if !T.Field(i).IsExported() {
break
}
f := v.Field(i)
if f.Kind() == reflect.String {
if obj, ok := objs[i].(String); ok {
f.Set(reflect.ValueOf(string(obj)))
} else {
return nil, fmt.Errorf("first argument of (Binding name node) must be string, but got %s", objs[i])
}
} else {
f.Set(reflect.ValueOf(objs[i]))
}
}
return v.Interface().(Node), nil
}
func (p *Parser) populateNode(typ string, objs []Node) (Node, error) {
return populateNode(typ, objs, p.AllowTypeInfo)
}
var structNodes = map[string]reflect.Type{
"Any": reflect.TypeFor[Any](),
"Ellipsis": reflect.TypeFor[Ellipsis](),
"List": reflect.TypeFor[List](),
"Binding": reflect.TypeFor[Binding](),
"RangeStmt": reflect.TypeFor[RangeStmt](),
"AssignStmt": reflect.TypeFor[AssignStmt](),
"IndexExpr": reflect.TypeFor[IndexExpr](),
"Ident": reflect.TypeFor[Ident](),
"Builtin": reflect.TypeFor[Builtin](),
"ValueSpec": reflect.TypeFor[ValueSpec](),
"GenDecl": reflect.TypeFor[GenDecl](),
"BinaryExpr": reflect.TypeFor[BinaryExpr](),
"ForStmt": reflect.TypeFor[ForStmt](),
"ArrayType": reflect.TypeFor[ArrayType](),
"DeferStmt": reflect.TypeFor[DeferStmt](),
"MapType": reflect.TypeFor[MapType](),
"ReturnStmt": reflect.TypeFor[ReturnStmt](),
"SliceExpr": reflect.TypeFor[SliceExpr](),
"StarExpr": reflect.TypeFor[StarExpr](),
"UnaryExpr": reflect.TypeFor[UnaryExpr](),
"SendStmt": reflect.TypeFor[SendStmt](),
"SelectStmt": reflect.TypeFor[SelectStmt](),
"ImportSpec": reflect.TypeFor[ImportSpec](),
"IfStmt": reflect.TypeFor[IfStmt](),
"GoStmt": reflect.TypeFor[GoStmt](),
"Field": reflect.TypeFor[Field](),
"SelectorExpr": reflect.TypeFor[SelectorExpr](),
"StructType": reflect.TypeFor[StructType](),
"KeyValueExpr": reflect.TypeFor[KeyValueExpr](),
"FuncType": reflect.TypeFor[FuncType](),
"FuncLit": reflect.TypeFor[FuncLit](),
"FuncDecl": reflect.TypeFor[FuncDecl](),
"ChanType": reflect.TypeFor[ChanType](),
"CallExpr": reflect.TypeFor[CallExpr](),
"CaseClause": reflect.TypeFor[CaseClause](),
"CommClause": reflect.TypeFor[CommClause](),
"CompositeLit": reflect.TypeFor[CompositeLit](),
"EmptyStmt": reflect.TypeFor[EmptyStmt](),
"SwitchStmt": reflect.TypeFor[SwitchStmt](),
"TypeSwitchStmt": reflect.TypeFor[TypeSwitchStmt](),
"TypeAssertExpr": reflect.TypeFor[TypeAssertExpr](),
"TypeSpec": reflect.TypeFor[TypeSpec](),
"InterfaceType": reflect.TypeFor[InterfaceType](),
"BranchStmt": reflect.TypeFor[BranchStmt](),
"IncDecStmt": reflect.TypeFor[IncDecStmt](),
"BasicLit": reflect.TypeFor[BasicLit](),
"Object": reflect.TypeFor[Object](),
"Symbol": reflect.TypeFor[Symbol](),
"Or": reflect.TypeFor[Or](),
"Not": reflect.TypeFor[Not](),
"IntegerLiteral": reflect.TypeFor[IntegerLiteral](),
"TrulyConstantExpression": reflect.TypeFor[TrulyConstantExpression](),
}
func (p *Parser) object() (Node, error) {
n := p.next()
switch n.typ {
case itemLeftParen:
p.rewind()
node, err := p.node()
if err != nil {
return node, err
}
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return node, err
}
return List{Head: node, Tail: tail}, nil
}
return node, nil
case itemLeftBracket:
p.rewind()
return p.array()
case itemVariable:
v := n
if v.val == "nil" {
return Nil{}, nil
}
var b Binding
if _, ok := p.accept(itemAt); ok {
o, err := p.node()
if err != nil {
return nil, err
}
b = Binding{
Name: v.val,
Node: o,
idx: p.bindingIndex(v.val),
}
} else {
p.rewind()
b = Binding{
Name: v.val,
idx: p.bindingIndex(v.val),
}
}
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return b, err
}
return List{Head: b, Tail: tail}, nil
}
return b, nil
case itemBlank:
if p.peek().typ == itemColon {
p.next()
tail, err := p.object()
if err != nil {
return Any{}, err
}
return List{Head: Any{}, Tail: tail}, nil
}
return Any{}, nil
case itemString:
return String(n.val), nil
default:
return nil, p.unexpectedToken("object")
}
}
func (p *Parser) array() (Node, error) {
if _, ok := p.accept(itemLeftBracket); !ok {
return nil, p.unexpectedToken("'['")
}
var objs []Node
for {
if _, ok := p.accept(itemRightBracket); ok {
break
} else {
p.rewind()
obj, err := p.object()
if err != nil {
return nil, err
}
objs = append(objs, obj)
}
}
tail := List{}
for i := len(objs) - 1; i >= 0; i-- {
l := List{
Head: objs[i],
Tail: tail,
}
tail = l
}
return tail, nil
}
/*
Node ::= itemLeftParen itemTypeName Object* itemRightParen
Object ::= Node | Array | Binding | itemVariable | itemBlank | itemString
Array := itemLeftBracket Object* itemRightBracket
Array := Object itemColon Object
Binding ::= itemVariable itemAt Node
*/

View File

@@ -0,0 +1,551 @@
package pattern
import (
"fmt"
"go/token"
"reflect"
"strings"
)
var (
_ Node = Ellipsis{}
_ Node = Binding{}
_ Node = RangeStmt{}
_ Node = AssignStmt{}
_ Node = IndexExpr{}
_ Node = IndexListExpr{}
_ Node = Ident{}
_ Node = Builtin{}
_ Node = String("")
_ Node = Any{}
_ Node = ValueSpec{}
_ Node = List{}
_ Node = GenDecl{}
_ Node = BinaryExpr{}
_ Node = ForStmt{}
_ Node = ArrayType{}
_ Node = DeferStmt{}
_ Node = MapType{}
_ Node = ReturnStmt{}
_ Node = SliceExpr{}
_ Node = StarExpr{}
_ Node = UnaryExpr{}
_ Node = SendStmt{}
_ Node = SelectStmt{}
_ Node = ImportSpec{}
_ Node = IfStmt{}
_ Node = GoStmt{}
_ Node = Field{}
_ Node = SelectorExpr{}
_ Node = StructType{}
_ Node = KeyValueExpr{}
_ Node = FuncType{}
_ Node = FuncLit{}
_ Node = FuncDecl{}
_ Node = Token(0)
_ Node = ChanType{}
_ Node = CallExpr{}
_ Node = CaseClause{}
_ Node = CommClause{}
_ Node = CompositeLit{}
_ Node = EmptyStmt{}
_ Node = SwitchStmt{}
_ Node = TypeSwitchStmt{}
_ Node = TypeAssertExpr{}
_ Node = TypeSpec{}
_ Node = InterfaceType{}
_ Node = BranchStmt{}
_ Node = IncDecStmt{}
_ Node = BasicLit{}
_ Node = Nil{}
_ Node = Object{}
_ Node = Symbol{}
_ Node = Not{}
_ Node = Or{}
_ Node = IntegerLiteral{}
_ Node = TrulyConstantExpression{}
)
type Symbol struct {
Name Node
}
type Token token.Token
type Nil struct {
}
type Ellipsis struct {
Elt Node
}
type IncDecStmt struct {
X Node
Tok Node
}
type BranchStmt struct {
Tok Node
Label Node
}
type InterfaceType struct {
Methods Node
}
type TypeSpec struct {
Name Node
Type Node
}
type TypeAssertExpr struct {
X Node
Type Node
}
type TypeSwitchStmt struct {
Init Node
Assign Node
Body Node
}
type SwitchStmt struct {
Init Node
Tag Node
Body Node
}
type EmptyStmt struct {
}
type CompositeLit struct {
Type Node
Elts Node
}
type CommClause struct {
Comm Node
Body Node
}
type CaseClause struct {
List Node
Body Node
}
type CallExpr struct {
Fun Node
Args Node
// XXX handle ellipsis
}
// TODO(dh): add a ChanDir node, and a way of instantiating it.
type ChanType struct {
Dir Node
Value Node
}
type FuncDecl struct {
Recv Node
Name Node
Type Node
Body Node
}
type FuncLit struct {
Type Node
Body Node
}
type FuncType struct {
Params Node
Results Node
}
type KeyValueExpr struct {
Key Node
Value Node
}
type StructType struct {
Fields Node
}
type SelectorExpr struct {
X Node
Sel Node
}
type Field struct {
Names Node
Type Node
Tag Node
}
type GoStmt struct {
Call Node
}
type IfStmt struct {
Init Node
Cond Node
Body Node
Else Node
}
type ImportSpec struct {
Name Node
Path Node
}
type SelectStmt struct {
Body Node
}
type ArrayType struct {
Len Node
Elt Node
}
type DeferStmt struct {
Call Node
}
type MapType struct {
Key Node
Value Node
}
type ReturnStmt struct {
Results Node
}
type SliceExpr struct {
X Node
Low Node
High Node
Max Node
}
type StarExpr struct {
X Node
}
type UnaryExpr struct {
Op Node
X Node
}
type SendStmt struct {
Chan Node
Value Node
}
type Binding struct {
Name string
Node Node
idx int
}
type RangeStmt struct {
Key Node
Value Node
Tok Node
X Node
Body Node
}
type AssignStmt struct {
Lhs Node
Tok Node
Rhs Node
}
type IndexExpr struct {
X Node
Index Node
}
type IndexListExpr struct {
X Node
Indices Node
}
type Node interface {
String() string
isNode()
}
type Ident struct {
Name Node
}
type Object struct {
Name Node
}
type Builtin struct {
Name Node
}
type String string
type Any struct{}
type ValueSpec struct {
Names Node
Type Node
Values Node
}
type List struct {
Head Node
Tail Node
}
type GenDecl struct {
Tok Node
Specs Node
}
type BasicLit struct {
Kind Node
Value Node
}
// An IntegerLiteral is a constant expression made up of only integer basic literals and the "+" and "-" unary operators.
// That is, 0, -4, -+42 are all integer literals, but 1 + 2 is not.
type IntegerLiteral struct {
Value Node
}
type BinaryExpr struct {
X Node
Op Node
Y Node
}
type ForStmt struct {
Init Node
Cond Node
Post Node
Body Node
}
type Or struct {
Nodes []Node
}
type And struct {
Nodes []Node
}
type Not struct {
Node Node
}
// A TrulyConstantExpression is a constant expression that does not make use of any identifiers.
// It is constant even under varying build tags.
type TrulyConstantExpression struct {
Value Node
}
type IndexSymbol struct {
Path string
Type string
Ident string
}
func stringify(n Node) string {
v := reflect.ValueOf(n)
var parts []string
parts = append(parts, v.Type().Name())
for i := 0; i < v.NumField(); i++ {
parts = append(parts, fmt.Sprintf("%s", v.Field(i)))
}
return "(" + strings.Join(parts, " ") + ")"
}
func (stmt AssignStmt) String() string { return stringify(stmt) }
func (expr IndexExpr) String() string { return stringify(expr) }
func (expr IndexListExpr) String() string { return stringify(expr) }
func (id Ident) String() string { return stringify(id) }
func (spec ValueSpec) String() string { return stringify(spec) }
func (decl GenDecl) String() string { return stringify(decl) }
func (lit BasicLit) String() string { return stringify(lit) }
func (expr BinaryExpr) String() string { return stringify(expr) }
func (stmt ForStmt) String() string { return stringify(stmt) }
func (stmt RangeStmt) String() string { return stringify(stmt) }
func (typ ArrayType) String() string { return stringify(typ) }
func (stmt DeferStmt) String() string { return stringify(stmt) }
func (typ MapType) String() string { return stringify(typ) }
func (stmt ReturnStmt) String() string { return stringify(stmt) }
func (expr SliceExpr) String() string { return stringify(expr) }
func (expr StarExpr) String() string { return stringify(expr) }
func (expr UnaryExpr) String() string { return stringify(expr) }
func (stmt SendStmt) String() string { return stringify(stmt) }
func (spec ImportSpec) String() string { return stringify(spec) }
func (stmt SelectStmt) String() string { return stringify(stmt) }
func (stmt IfStmt) String() string { return stringify(stmt) }
func (stmt IncDecStmt) String() string { return stringify(stmt) }
func (stmt GoStmt) String() string { return stringify(stmt) }
func (field Field) String() string { return stringify(field) }
func (expr SelectorExpr) String() string { return stringify(expr) }
func (typ StructType) String() string { return stringify(typ) }
func (expr KeyValueExpr) String() string { return stringify(expr) }
func (typ FuncType) String() string { return stringify(typ) }
func (lit FuncLit) String() string { return stringify(lit) }
func (decl FuncDecl) String() string { return stringify(decl) }
func (stmt BranchStmt) String() string { return stringify(stmt) }
func (expr CallExpr) String() string { return stringify(expr) }
func (clause CaseClause) String() string { return stringify(clause) }
func (typ ChanType) String() string { return stringify(typ) }
func (clause CommClause) String() string { return stringify(clause) }
func (lit CompositeLit) String() string { return stringify(lit) }
func (stmt EmptyStmt) String() string { return stringify(stmt) }
func (typ InterfaceType) String() string { return stringify(typ) }
func (stmt SwitchStmt) String() string { return stringify(stmt) }
func (expr TypeAssertExpr) String() string { return stringify(expr) }
func (spec TypeSpec) String() string { return stringify(spec) }
func (stmt TypeSwitchStmt) String() string { return stringify(stmt) }
func (nil Nil) String() string { return "nil" }
func (builtin Builtin) String() string { return stringify(builtin) }
func (obj Object) String() string { return stringify(obj) }
func (fn Symbol) String() string { return stringify(fn) }
func (el Ellipsis) String() string { return stringify(el) }
func (not Not) String() string { return stringify(not) }
func (lit IntegerLiteral) String() string { return stringify(lit) }
func (expr TrulyConstantExpression) String() string { return stringify(expr) }
func (sym IndexSymbol) String() string {
return fmt.Sprintf("(IndexSymbol %q %q %q)", sym.Path, sym.Type, sym.Ident)
}
func (or Or) String() string {
var s strings.Builder
s.WriteString("(Or")
for _, node := range or.Nodes {
s.WriteString(" ")
s.WriteString(node.String())
}
s.WriteString(")")
return s.String()
}
func (and And) String() string {
var s strings.Builder
s.WriteString("(And")
for _, node := range and.Nodes {
s.WriteString(" ")
s.WriteString(node.String())
}
s.WriteString(")")
return s.String()
}
func isProperList(l List) bool {
if l.Head == nil && l.Tail == nil {
return true
}
switch tail := l.Tail.(type) {
case nil:
return false
case List:
return isProperList(tail)
default:
return false
}
}
func (l List) String() string {
if l.Head == nil && l.Tail == nil {
return "[]"
}
if isProperList(l) {
// pretty-print the list
var objs []string
for l.Head != nil {
objs = append(objs, l.Head.String())
l = l.Tail.(List)
}
return fmt.Sprintf("[%s]", strings.Join(objs, " "))
}
return fmt.Sprintf("%s:%s", l.Head, l.Tail)
}
func (bind Binding) String() string {
if bind.Node == nil {
return bind.Name
}
return fmt.Sprintf("%s@%s", bind.Name, bind.Node)
}
func (s String) String() string { return fmt.Sprintf("%q", string(s)) }
func (tok Token) String() string {
return fmt.Sprintf("%q", strings.ToUpper(token.Token(tok).String()))
}
func (Any) String() string { return "_" }
func (AssignStmt) isNode() {}
func (IndexExpr) isNode() {}
func (IndexListExpr) isNode() {}
func (Ident) isNode() {}
func (ValueSpec) isNode() {}
func (GenDecl) isNode() {}
func (BasicLit) isNode() {}
func (BinaryExpr) isNode() {}
func (ForStmt) isNode() {}
func (RangeStmt) isNode() {}
func (ArrayType) isNode() {}
func (DeferStmt) isNode() {}
func (MapType) isNode() {}
func (ReturnStmt) isNode() {}
func (SliceExpr) isNode() {}
func (StarExpr) isNode() {}
func (UnaryExpr) isNode() {}
func (SendStmt) isNode() {}
func (ImportSpec) isNode() {}
func (SelectStmt) isNode() {}
func (IfStmt) isNode() {}
func (IncDecStmt) isNode() {}
func (GoStmt) isNode() {}
func (Field) isNode() {}
func (SelectorExpr) isNode() {}
func (StructType) isNode() {}
func (KeyValueExpr) isNode() {}
func (FuncType) isNode() {}
func (FuncLit) isNode() {}
func (FuncDecl) isNode() {}
func (BranchStmt) isNode() {}
func (CallExpr) isNode() {}
func (CaseClause) isNode() {}
func (ChanType) isNode() {}
func (CommClause) isNode() {}
func (CompositeLit) isNode() {}
func (EmptyStmt) isNode() {}
func (InterfaceType) isNode() {}
func (SwitchStmt) isNode() {}
func (TypeAssertExpr) isNode() {}
func (TypeSpec) isNode() {}
func (TypeSwitchStmt) isNode() {}
func (Nil) isNode() {}
func (Builtin) isNode() {}
func (Object) isNode() {}
func (Symbol) isNode() {}
func (Ellipsis) isNode() {}
func (Or) isNode() {}
func (And) isNode() {}
func (List) isNode() {}
func (String) isNode() {}
func (Token) isNode() {}
func (Any) isNode() {}
func (Binding) isNode() {}
func (Not) isNode() {}
func (IntegerLiteral) isNode() {}
func (TrulyConstantExpression) isNode() {}
func (IndexSymbol) isNode() {}