Path: blob/main/vendor/github.com/google/go-cmp/cmp/path.go
2880 views
// Copyright 2017, The Go Authors. All rights reserved.1// Use of this source code is governed by a BSD-style2// license that can be found in the LICENSE file.34package cmp56import (7"fmt"8"reflect"9"strings"10"unicode"11"unicode/utf8"1213"github.com/google/go-cmp/cmp/internal/value"14)1516// Path is a list of [PathStep] describing the sequence of operations to get17// from some root type to the current position in the value tree.18// The first Path element is always an operation-less [PathStep] that exists19// simply to identify the initial type.20//21// When traversing structs with embedded structs, the embedded struct will22// always be accessed as a field before traversing the fields of the23// embedded struct themselves. That is, an exported field from the24// embedded struct will never be accessed directly from the parent struct.25type Path []PathStep2627// PathStep is a union-type for specific operations to traverse28// a value's tree structure. Users of this package never need to implement29// these types as values of this type will be returned by this package.30//31// Implementations of this interface:32// - [StructField]33// - [SliceIndex]34// - [MapIndex]35// - [Indirect]36// - [TypeAssertion]37// - [Transform]38type PathStep interface {39String() string4041// Type is the resulting type after performing the path step.42Type() reflect.Type4344// Values is the resulting values after performing the path step.45// The type of each valid value is guaranteed to be identical to Type.46//47// In some cases, one or both may be invalid or have restrictions:48// - For StructField, both are not interface-able if the current field49// is unexported and the struct type is not explicitly permitted by50// an Exporter to traverse unexported fields.51// - For SliceIndex, one may be invalid if an element is missing from52// either the x or y slice.53// - For MapIndex, one may be invalid if an entry is missing from54// either the x or y map.55//56// The provided values must not be mutated.57Values() (vx, vy reflect.Value)58}5960var (61_ PathStep = StructField{}62_ PathStep = SliceIndex{}63_ PathStep = MapIndex{}64_ PathStep = Indirect{}65_ PathStep = TypeAssertion{}66_ PathStep = Transform{}67)6869func (pa *Path) push(s PathStep) {70*pa = append(*pa, s)71}7273func (pa *Path) pop() {74*pa = (*pa)[:len(*pa)-1]75}7677// Last returns the last [PathStep] in the Path.78// If the path is empty, this returns a non-nil [PathStep]79// that reports a nil [PathStep.Type].80func (pa Path) Last() PathStep {81return pa.Index(-1)82}8384// Index returns the ith step in the Path and supports negative indexing.85// A negative index starts counting from the tail of the Path such that -186// refers to the last step, -2 refers to the second-to-last step, and so on.87// If index is invalid, this returns a non-nil [PathStep]88// that reports a nil [PathStep.Type].89func (pa Path) Index(i int) PathStep {90if i < 0 {91i = len(pa) + i92}93if i < 0 || i >= len(pa) {94return pathStep{}95}96return pa[i]97}9899// String returns the simplified path to a node.100// The simplified path only contains struct field accesses.101//102// For example:103//104// MyMap.MySlices.MyField105func (pa Path) String() string {106var ss []string107for _, s := range pa {108if _, ok := s.(StructField); ok {109ss = append(ss, s.String())110}111}112return strings.TrimPrefix(strings.Join(ss, ""), ".")113}114115// GoString returns the path to a specific node using Go syntax.116//117// For example:118//119// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField120func (pa Path) GoString() string {121var ssPre, ssPost []string122var numIndirect int123for i, s := range pa {124var nextStep PathStep125if i+1 < len(pa) {126nextStep = pa[i+1]127}128switch s := s.(type) {129case Indirect:130numIndirect++131pPre, pPost := "(", ")"132switch nextStep.(type) {133case Indirect:134continue // Next step is indirection, so let them batch up135case StructField:136numIndirect-- // Automatic indirection on struct fields137case nil:138pPre, pPost = "", "" // Last step; no need for parenthesis139}140if numIndirect > 0 {141ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))142ssPost = append(ssPost, pPost)143}144numIndirect = 0145continue146case Transform:147ssPre = append(ssPre, s.trans.name+"(")148ssPost = append(ssPost, ")")149continue150}151ssPost = append(ssPost, s.String())152}153for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {154ssPre[i], ssPre[j] = ssPre[j], ssPre[i]155}156return strings.Join(ssPre, "") + strings.Join(ssPost, "")157}158159type pathStep struct {160typ reflect.Type161vx, vy reflect.Value162}163164func (ps pathStep) Type() reflect.Type { return ps.typ }165func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy }166func (ps pathStep) String() string {167if ps.typ == nil {168return "<nil>"169}170s := value.TypeString(ps.typ, false)171if s == "" || strings.ContainsAny(s, "{}\n") {172return "root" // Type too simple or complex to print173}174return fmt.Sprintf("{%s}", s)175}176177// StructField is a [PathStep] that represents a struct field access178// on a field called [StructField.Name].179type StructField struct{ *structField }180type structField struct {181pathStep182name string183idx int184185// These fields are used for forcibly accessing an unexported field.186// pvx, pvy, and field are only valid if unexported is true.187unexported bool188mayForce bool // Forcibly allow visibility189paddr bool // Was parent addressable?190pvx, pvy reflect.Value // Parent values (always addressable)191field reflect.StructField // Field information192}193194func (sf StructField) Type() reflect.Type { return sf.typ }195func (sf StructField) Values() (vx, vy reflect.Value) {196if !sf.unexported {197return sf.vx, sf.vy // CanInterface reports true198}199200// Forcibly obtain read-write access to an unexported struct field.201if sf.mayForce {202vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr)203vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr)204return vx, vy // CanInterface reports true205}206return sf.vx, sf.vy // CanInterface reports false207}208func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) }209210// Name is the field name.211func (sf StructField) Name() string { return sf.name }212213// Index is the index of the field in the parent struct type.214// See [reflect.Type.Field].215func (sf StructField) Index() int { return sf.idx }216217// SliceIndex is a [PathStep] that represents an index operation on218// a slice or array at some index [SliceIndex.Key].219type SliceIndex struct{ *sliceIndex }220type sliceIndex struct {221pathStep222xkey, ykey int223isSlice bool // False for reflect.Array224}225226func (si SliceIndex) Type() reflect.Type { return si.typ }227func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy }228func (si SliceIndex) String() string {229switch {230case si.xkey == si.ykey:231return fmt.Sprintf("[%d]", si.xkey)232case si.ykey == -1:233// [5->?] means "I don't know where X[5] went"234return fmt.Sprintf("[%d->?]", si.xkey)235case si.xkey == -1:236// [?->3] means "I don't know where Y[3] came from"237return fmt.Sprintf("[?->%d]", si.ykey)238default:239// [5->3] means "X[5] moved to Y[3]"240return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)241}242}243244// Key is the index key; it may return -1 if in a split state245func (si SliceIndex) Key() int {246if si.xkey != si.ykey {247return -1248}249return si.xkey250}251252// SplitKeys are the indexes for indexing into slices in the253// x and y values, respectively. These indexes may differ due to the254// insertion or removal of an element in one of the slices, causing255// all of the indexes to be shifted. If an index is -1, then that256// indicates that the element does not exist in the associated slice.257//258// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes259// returned by SplitKeys are not the same. SplitKeys will never return -1 for260// both indexes.261func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey }262263// MapIndex is a [PathStep] that represents an index operation on a map at some index Key.264type MapIndex struct{ *mapIndex }265type mapIndex struct {266pathStep267key reflect.Value268}269270func (mi MapIndex) Type() reflect.Type { return mi.typ }271func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy }272func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) }273274// Key is the value of the map key.275func (mi MapIndex) Key() reflect.Value { return mi.key }276277// Indirect is a [PathStep] that represents pointer indirection on the parent type.278type Indirect struct{ *indirect }279type indirect struct {280pathStep281}282283func (in Indirect) Type() reflect.Type { return in.typ }284func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy }285func (in Indirect) String() string { return "*" }286287// TypeAssertion is a [PathStep] that represents a type assertion on an interface.288type TypeAssertion struct{ *typeAssertion }289type typeAssertion struct {290pathStep291}292293func (ta TypeAssertion) Type() reflect.Type { return ta.typ }294func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy }295func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) }296297// Transform is a [PathStep] that represents a transformation298// from the parent type to the current type.299type Transform struct{ *transform }300type transform struct {301pathStep302trans *transformer303}304305func (tf Transform) Type() reflect.Type { return tf.typ }306func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy }307func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) }308309// Name is the name of the [Transformer].310func (tf Transform) Name() string { return tf.trans.name }311312// Func is the function pointer to the transformer function.313func (tf Transform) Func() reflect.Value { return tf.trans.fnc }314315// Option returns the originally constructed [Transformer] option.316// The == operator can be used to detect the exact option used.317func (tf Transform) Option() Option { return tf.trans }318319// pointerPath represents a dual-stack of pointers encountered when320// recursively traversing the x and y values. This data structure supports321// detection of cycles and determining whether the cycles are equal.322// In Go, cycles can occur via pointers, slices, and maps.323//324// The pointerPath uses a map to represent a stack; where descension into a325// pointer pushes the address onto the stack, and ascension from a pointer326// pops the address from the stack. Thus, when traversing into a pointer from327// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles328// by checking whether the pointer has already been visited. The cycle detection329// uses a separate stack for the x and y values.330//331// If a cycle is detected we need to determine whether the two pointers332// should be considered equal. The definition of equality chosen by Equal333// requires two graphs to have the same structure. To determine this, both the334// x and y values must have a cycle where the previous pointers were also335// encountered together as a pair.336//337// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and338// MapIndex with pointer information for the x and y values.339// Suppose px and py are two pointers to compare, we then search the340// Path for whether px was ever encountered in the Path history of x, and341// similarly so with py. If either side has a cycle, the comparison is only342// equal if both px and py have a cycle resulting from the same PathStep.343//344// Using a map as a stack is more performant as we can perform cycle detection345// in O(1) instead of O(N) where N is len(Path).346type pointerPath struct {347// mx is keyed by x pointers, where the value is the associated y pointer.348mx map[value.Pointer]value.Pointer349// my is keyed by y pointers, where the value is the associated x pointer.350my map[value.Pointer]value.Pointer351}352353func (p *pointerPath) Init() {354p.mx = make(map[value.Pointer]value.Pointer)355p.my = make(map[value.Pointer]value.Pointer)356}357358// Push indicates intent to descend into pointers vx and vy where359// visited reports whether either has been seen before. If visited before,360// equal reports whether both pointers were encountered together.361// Pop must be called if and only if the pointers were never visited.362//363// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map364// and be non-nil.365func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) {366px := value.PointerOf(vx)367py := value.PointerOf(vy)368_, ok1 := p.mx[px]369_, ok2 := p.my[py]370if ok1 || ok2 {371equal = p.mx[px] == py && p.my[py] == px // Pointers paired together372return equal, true373}374p.mx[px] = py375p.my[py] = px376return false, false377}378379// Pop ascends from pointers vx and vy.380func (p pointerPath) Pop(vx, vy reflect.Value) {381delete(p.mx, value.PointerOf(vx))382delete(p.my, value.PointerOf(vy))383}384385// isExported reports whether the identifier is exported.386func isExported(id string) bool {387r, _ := utf8.DecodeRuneInString(id)388return unicode.IsUpper(r)389}390391392