Source File
path.go
Belonging Package
github.com/google/go-cmp/cmp
// Copyright 2017, 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 cmpimport ()// Path is a list of [PathStep] describing the sequence of operations to get// from some root type to the current position in the value tree.// The first Path element is always an operation-less [PathStep] that exists// simply to identify the initial type.//// When traversing structs with embedded structs, the embedded struct will// always be accessed as a field before traversing the fields of the// embedded struct themselves. That is, an exported field from the// embedded struct will never be accessed directly from the parent struct.type Path []PathStep// PathStep is a union-type for specific operations to traverse// a value's tree structure. Users of this package never need to implement// these types as values of this type will be returned by this package.//// Implementations of this interface:// - [StructField]// - [SliceIndex]// - [MapIndex]// - [Indirect]// - [TypeAssertion]// - [Transform]type PathStep interface {String() string// Type is the resulting type after performing the path step.Type() reflect.Type// Values is the resulting values after performing the path step.// The type of each valid value is guaranteed to be identical to Type.//// In some cases, one or both may be invalid or have restrictions:// - For StructField, both are not interface-able if the current field// is unexported and the struct type is not explicitly permitted by// an Exporter to traverse unexported fields.// - For SliceIndex, one may be invalid if an element is missing from// either the x or y slice.// - For MapIndex, one may be invalid if an entry is missing from// either the x or y map.//// The provided values must not be mutated.Values() (vx, vy reflect.Value)}var (_ PathStep = StructField{}_ PathStep = SliceIndex{}_ PathStep = MapIndex{}_ PathStep = Indirect{}_ PathStep = TypeAssertion{}_ PathStep = Transform{})func ( *Path) ( PathStep) {* = append(*, )}func ( *Path) () {* = (*)[:len(*)-1]}// Last returns the last [PathStep] in the Path.// If the path is empty, this returns a non-nil [PathStep]// that reports a nil [PathStep.Type].func ( Path) () PathStep {return .Index(-1)}// Index returns the ith step in the Path and supports negative indexing.// A negative index starts counting from the tail of the Path such that -1// refers to the last step, -2 refers to the second-to-last step, and so on.// If index is invalid, this returns a non-nil [PathStep]// that reports a nil [PathStep.Type].func ( Path) ( int) PathStep {if < 0 {= len() +}if < 0 || >= len() {return pathStep{}}return []}// String returns the simplified path to a node.// The simplified path only contains struct field accesses.//// For example://// MyMap.MySlices.MyFieldfunc ( Path) () string {var []stringfor , := range {if , := .(StructField); {= append(, .String())}}return strings.TrimPrefix(strings.Join(, ""), ".")}// GoString returns the path to a specific node using Go syntax.//// For example://// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyFieldfunc ( Path) () string {var , []stringvar intfor , := range {var PathStepif +1 < len() {= [+1]}switch s := .(type) {case Indirect:++, := "(", ")"switch .(type) {case Indirect:continue // Next step is indirection, so let them batch upcase StructField:-- // Automatic indirection on struct fieldscase nil:, = "", "" // Last step; no need for parenthesis}if > 0 {= append(, +strings.Repeat("*", ))= append(, )}= 0continuecase Transform:= append(, .trans.name+"(")= append(, ")")continue}= append(, .String())}for , := 0, len()-1; < ; , = +1, -1 {[], [] = [], []}return strings.Join(, "") + strings.Join(, "")}type pathStep struct {typ reflect.Typevx, vy reflect.Value}func ( pathStep) () reflect.Type { return .typ }func ( pathStep) () (, reflect.Value) { return .vx, .vy }func ( pathStep) () string {if .typ == nil {return "<nil>"}:= value.TypeString(.typ, false)if == "" || strings.ContainsAny(, "{}\n") {return "root" // Type too simple or complex to print}return fmt.Sprintf("{%s}", )}// StructField is a [PathStep] that represents a struct field access// on a field called [StructField.Name].type StructField struct{ *structField }type structField struct {pathStepname stringidx int// These fields are used for forcibly accessing an unexported field.// pvx, pvy, and field are only valid if unexported is true.unexported boolmayForce bool // Forcibly allow visibilitypaddr bool // Was parent addressable?pvx, pvy reflect.Value // Parent values (always addressable)field reflect.StructField // Field information}func ( StructField) () reflect.Type { return .typ }func ( StructField) () (, reflect.Value) {if !.unexported {return .vx, .vy // CanInterface reports true}// Forcibly obtain read-write access to an unexported struct field.if .mayForce {= retrieveUnexportedField(.pvx, .field, .paddr)= retrieveUnexportedField(.pvy, .field, .paddr)return , // CanInterface reports true}return .vx, .vy // CanInterface reports false}func ( StructField) () string { return fmt.Sprintf(".%s", .name) }// Name is the field name.func ( StructField) () string { return .name }// Index is the index of the field in the parent struct type.// See [reflect.Type.Field].func ( StructField) () int { return .idx }// SliceIndex is a [PathStep] that represents an index operation on// a slice or array at some index [SliceIndex.Key].type SliceIndex struct{ *sliceIndex }type sliceIndex struct {pathStepxkey, ykey intisSlice bool // False for reflect.Array}func ( SliceIndex) () reflect.Type { return .typ }func ( SliceIndex) () (, reflect.Value) { return .vx, .vy }func ( SliceIndex) () string {switch {case .xkey == .ykey:return fmt.Sprintf("[%d]", .xkey)case .ykey == -1:// [5->?] means "I don't know where X[5] went"return fmt.Sprintf("[%d->?]", .xkey)case .xkey == -1:// [?->3] means "I don't know where Y[3] came from"return fmt.Sprintf("[?->%d]", .ykey)default:// [5->3] means "X[5] moved to Y[3]"return fmt.Sprintf("[%d->%d]", .xkey, .ykey)}}// Key is the index key; it may return -1 if in a split statefunc ( SliceIndex) () int {if .xkey != .ykey {return -1}return .xkey}// SplitKeys are the indexes for indexing into slices in the// x and y values, respectively. These indexes may differ due to the// insertion or removal of an element in one of the slices, causing// all of the indexes to be shifted. If an index is -1, then that// indicates that the element does not exist in the associated slice.//// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes// returned by SplitKeys are not the same. SplitKeys will never return -1 for// both indexes.func ( SliceIndex) () (, int) { return .xkey, .ykey }// MapIndex is a [PathStep] that represents an index operation on a map at some index Key.type MapIndex struct{ *mapIndex }type mapIndex struct {pathStepkey reflect.Value}func ( MapIndex) () reflect.Type { return .typ }func ( MapIndex) () (, reflect.Value) { return .vx, .vy }func ( MapIndex) () string { return fmt.Sprintf("[%#v]", .key) }// Key is the value of the map key.func ( MapIndex) () reflect.Value { return .key }// Indirect is a [PathStep] that represents pointer indirection on the parent type.type Indirect struct{ *indirect }type indirect struct {pathStep}func ( Indirect) () reflect.Type { return .typ }func ( Indirect) () (, reflect.Value) { return .vx, .vy }func ( Indirect) () string { return "*" }// TypeAssertion is a [PathStep] that represents a type assertion on an interface.type TypeAssertion struct{ *typeAssertion }type typeAssertion struct {pathStep}func ( TypeAssertion) () reflect.Type { return .typ }func ( TypeAssertion) () (, reflect.Value) { return .vx, .vy }func ( TypeAssertion) () string { return fmt.Sprintf(".(%v)", value.TypeString(.typ, false)) }// Transform is a [PathStep] that represents a transformation// from the parent type to the current type.type Transform struct{ *transform }type transform struct {pathSteptrans *transformer}func ( Transform) () reflect.Type { return .typ }func ( Transform) () (, reflect.Value) { return .vx, .vy }func ( Transform) () string { return fmt.Sprintf("%s()", .trans.name) }// Name is the name of the [Transformer].func ( Transform) () string { return .trans.name }// Func is the function pointer to the transformer function.func ( Transform) () reflect.Value { return .trans.fnc }// Option returns the originally constructed [Transformer] option.// The == operator can be used to detect the exact option used.func ( Transform) () Option { return .trans }// pointerPath represents a dual-stack of pointers encountered when// recursively traversing the x and y values. This data structure supports// detection of cycles and determining whether the cycles are equal.// In Go, cycles can occur via pointers, slices, and maps.//// The pointerPath uses a map to represent a stack; where descension into a// pointer pushes the address onto the stack, and ascension from a pointer// pops the address from the stack. Thus, when traversing into a pointer from// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles// by checking whether the pointer has already been visited. The cycle detection// uses a separate stack for the x and y values.//// If a cycle is detected we need to determine whether the two pointers// should be considered equal. The definition of equality chosen by Equal// requires two graphs to have the same structure. To determine this, both the// x and y values must have a cycle where the previous pointers were also// encountered together as a pair.//// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and// MapIndex with pointer information for the x and y values.// Suppose px and py are two pointers to compare, we then search the// Path for whether px was ever encountered in the Path history of x, and// similarly so with py. If either side has a cycle, the comparison is only// equal if both px and py have a cycle resulting from the same PathStep.//// Using a map as a stack is more performant as we can perform cycle detection// in O(1) instead of O(N) where N is len(Path).type pointerPath struct {// mx is keyed by x pointers, where the value is the associated y pointer.mx map[value.Pointer]value.Pointer// my is keyed by y pointers, where the value is the associated x pointer.my map[value.Pointer]value.Pointer}func ( *pointerPath) () {.mx = make(map[value.Pointer]value.Pointer).my = make(map[value.Pointer]value.Pointer)}// Push indicates intent to descend into pointers vx and vy where// visited reports whether either has been seen before. If visited before,// equal reports whether both pointers were encountered together.// Pop must be called if and only if the pointers were never visited.//// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map// and be non-nil.func ( pointerPath) (, reflect.Value) (, bool) {:= value.PointerOf():= value.PointerOf(), := .mx[], := .my[]if || {= .mx[] == && .my[] == // Pointers paired togetherreturn , true}.mx[] =.my[] =return false, false}// Pop ascends from pointers vx and vy.func ( pointerPath) (, reflect.Value) {delete(.mx, value.PointerOf())delete(.my, value.PointerOf())}// isExported reports whether the identifier is exported.func ( string) bool {, := utf8.DecodeRuneInString()return unicode.IsUpper()}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)