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 cmp
import (
)
// Path is a list of PathSteps 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 are
// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and 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 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 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.MyField
func ( Path) () string {
var []string
for , := 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].MyField
func ( Path) () string {
var , []string
var int
for , := range {
var PathStep
if +1 < len() {
= [+1]
}
switch s := .(type) {
case Indirect:
++
, := "(", ")"
switch .(type) {
case Indirect:
continue // Next step is indirection, so let them batch up
case StructField:
-- // Automatic indirection on struct fields
case nil:
, = "", "" // Last step; no need for parenthesis
}
if > 0 {
= append(, +strings.Repeat("*", ))
= append(, )
}
= 0
continue
case Transform:
= append(, .trans.name+"(")
= append(, ")")
continue
}
= append(, .String())
}
for , := 0, len()-1; < ; , = +1, -1 {
[], [] = [], []
}
return strings.Join(, "") + strings.Join(, "")
}
type pathStep struct {
typ reflect.Type
vx, 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 represents a struct field access on a field called Name.
type StructField struct{ *structField }
type structField struct {
pathStep
name string
idx int
// These fields are used for forcibly accessing an unexported field.
// pvx, pvy, and field are only valid if unexported is true.
unexported bool
mayForce bool // Forcibly allow visibility
paddr 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 an index operation on a slice or array at some index Key.
type SliceIndex struct{ *sliceIndex }
type sliceIndex struct {
pathStep
xkey, ykey int
isSlice 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 state
func ( 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.
//
// 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 an index operation on a map at some index Key.
type MapIndex struct{ *mapIndex }
type mapIndex struct {
pathStep
key 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 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 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 transformation from the parent type to the current type.
type Transform struct{ *transform }
type transform struct {
pathStep
trans *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 together
return , 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.4.9. (GOOS=linux GOARCH=amd64)