equal.go (2668B)
1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package astutil 6 7 import ( 8 "go/ast" 9 "go/token" 10 "reflect" 11 ) 12 13 // Equal reports whether two nodes are structurally equal, 14 // ignoring fields of type [token.Pos], [ast.Object], 15 // and [ast.Scope], and comments. 16 // 17 // The operands x and y may be nil. 18 // A nil slice is not equal to an empty slice. 19 // 20 // The provided function determines whether two identifiers 21 // should be considered identical. 22 func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool { 23 if x == nil || y == nil { 24 return x == y 25 } 26 return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical) 27 } 28 29 // EqualSyntax reports whether x and y are equal. 30 // Identifiers are considered equal if they are spelled the same. 31 // Comments are ignored. 32 func EqualSyntax(x, y ast.Expr) bool { 33 sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name } 34 return Equal(x, y, sameName) 35 } 36 37 func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool { 38 // Ensure types are the same 39 if x.Type() != y.Type() { 40 return false 41 } 42 switch x.Kind() { 43 case reflect.Pointer: 44 if x.IsNil() || y.IsNil() { 45 return x.IsNil() == y.IsNil() 46 } 47 switch t := x.Interface().(type) { 48 // Skip fields of types potentially involved in cycles. 49 case *ast.Object, *ast.Scope, *ast.CommentGroup: 50 return true 51 case *ast.Ident: 52 return identical(t, y.Interface().(*ast.Ident)) 53 default: 54 return equal(x.Elem(), y.Elem(), identical) 55 } 56 57 case reflect.Interface: 58 if x.IsNil() || y.IsNil() { 59 return x.IsNil() == y.IsNil() 60 } 61 return equal(x.Elem(), y.Elem(), identical) 62 63 case reflect.Struct: 64 for i := range x.NumField() { 65 xf := x.Field(i) 66 yf := y.Field(i) 67 // Skip position fields. 68 if xpos, ok := xf.Interface().(token.Pos); ok { 69 ypos := yf.Interface().(token.Pos) 70 // Numeric value of a Pos is not significant but its "zeroness" is, 71 // because it is often significant, e.g. CallExpr.Variadic(Ellipsis), ChanType.Arrow. 72 if xpos.IsValid() != ypos.IsValid() { 73 return false 74 } 75 } else if !equal(xf, yf, identical) { 76 return false 77 } 78 } 79 return true 80 81 case reflect.Slice: 82 if x.IsNil() || y.IsNil() { 83 return x.IsNil() == y.IsNil() 84 } 85 if x.Len() != y.Len() { 86 return false 87 } 88 for i := range x.Len() { 89 if !equal(x.Index(i), y.Index(i), identical) { 90 return false 91 } 92 } 93 return true 94 95 case reflect.String: 96 return x.String() == y.String() 97 98 case reflect.Bool: 99 return x.Bool() == y.Bool() 100 101 case reflect.Int: 102 return x.Int() == y.Int() 103 104 default: 105 panic(x) 106 } 107 }