github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/path.go (about)

     1  // Copyright 2017, 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.md file.
     4  
     5  package cmp
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  )
    14  
    15  type (
    16  	// Path is a list of PathSteps describing the sequence of operations to get
    17  	// from some root type to the current position in the value tree.
    18  	// The first Path element is always an operation-less PathStep that exists
    19  	// simply to identify the initial type.
    20  	//
    21  	// When traversing structs with embedded structs, the embedded struct will
    22  	// always be accessed as a field before traversing the fields of the
    23  	// embedded struct themselves. That is, an exported field from the
    24  	// embedded struct will never be accessed directly from the parent struct.
    25  	Path []PathStep
    26  
    27  	// PathStep is a union-type for specific operations to traverse
    28  	// a value's tree structure. Users of this package never need to implement
    29  	// these types as values of this type will be returned by this package.
    30  	PathStep interface {
    31  		String() string
    32  		Type() reflect.Type // Resulting type after performing the path step
    33  		ToJSON() string
    34  		ParentX() reflect.Value
    35  		ParentY() reflect.Value
    36  		isPathStep()
    37  	}
    38  
    39  	// SliceIndex is an index operation on a slice or array at some index Key.
    40  	SliceIndex interface {
    41  		PathStep
    42  		Key() int // May return -1 if in a split state
    43  
    44  		// SplitKeys returns the indexes for indexing into slices in the
    45  		// x and y values, respectively. These indexes may differ due to the
    46  		// insertion or removal of an element in one of the slices, causing
    47  		// all of the indexes to be shifted. If an index is -1, then that
    48  		// indicates that the element does not exist in the associated slice.
    49  		//
    50  		// Key is guaranteed to return -1 if and only if the indexes returned
    51  		// by SplitKeys are not the same. SplitKeys will never return -1 for
    52  		// both indexes.
    53  		SplitKeys() (x int, y int)
    54  
    55  		isSliceIndex()
    56  	}
    57  	// MapIndex is an index operation on a map at some index Key.
    58  	MapIndex interface {
    59  		PathStep
    60  		Key() reflect.Value
    61  		isMapIndex()
    62  	}
    63  	// TypeAssertion represents a type assertion on an interface.
    64  	TypeAssertion interface {
    65  		PathStep
    66  		isTypeAssertion()
    67  	}
    68  	// StructField represents a struct field access on a field called Name.
    69  	StructField interface {
    70  		PathStep
    71  		Name() string
    72  		Index() int
    73  		isStructField()
    74  	}
    75  	// Indirect represents pointer indirection on the parent type.
    76  	Indirect interface {
    77  		PathStep
    78  		isIndirect()
    79  	}
    80  	// Transform is a transformation from the parent type to the current type.
    81  	Transform interface {
    82  		PathStep
    83  		Name() string
    84  		Func() reflect.Value
    85  
    86  		// Option returns the originally constructed Transformer option.
    87  		// The == operator can be used to detect the exact option used.
    88  		Option() Option
    89  
    90  		isTransform()
    91  	}
    92  )
    93  
    94  func (pa *Path) push(s PathStep) {
    95  	*pa = append(*pa, s)
    96  }
    97  
    98  func (pa *Path) pop() {
    99  	*pa = (*pa)[:len(*pa)-1]
   100  }
   101  
   102  // Last returns the last PathStep in the Path.
   103  // If the path is empty, this returns a non-nil PathStep that reports a nil Type.
   104  func (pa Path) Last() PathStep {
   105  	return pa.Index(-1)
   106  }
   107  
   108  // Index returns the ith step in the Path and supports negative indexing.
   109  // A negative index starts counting from the tail of the Path such that -1
   110  // refers to the last step, -2 refers to the second-to-last step, and so on.
   111  // If index is invalid, this returns a non-nil PathStep that reports a nil Type.
   112  func (pa Path) Index(i int) PathStep {
   113  	if i < 0 {
   114  		i = len(pa) + i
   115  	}
   116  	if i < 0 || i >= len(pa) {
   117  		return pathStep{}
   118  	}
   119  	return pa[i]
   120  }
   121  
   122  // String returns the simplified path to a node.
   123  // The simplified path only contains struct field accesses.
   124  //
   125  // For example:
   126  //	MyMap.MySlices.MyField
   127  func (pa Path) String() string {
   128  	var ss []string
   129  	for _, s := range pa {
   130  		if _, ok := s.(*structField); ok {
   131  			ss = append(ss, s.String())
   132  		}
   133  	}
   134  	return strings.TrimPrefix(strings.Join(ss, ""), ".")
   135  }
   136  
   137  func (pa Path) ToJSON() string {
   138  	var ss []string
   139  	for _, s := range pa {
   140  		if jsPath := s.ToJSON(); jsPath != "" {
   141  			ss = append(ss, jsPath)
   142  		}
   143  	}
   144  	return strings.TrimPrefix(strings.Join(ss, ""), ".")
   145  }
   146  
   147  func (pa Path) ParentX() reflect.Value {
   148  	return nothing
   149  }
   150  
   151  func (pa Path) ParentY() reflect.Value {
   152  	return nothing
   153  }
   154  
   155  // GoString returns the path to a specific node using Go syntax.
   156  //
   157  // For example:
   158  //	(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
   159  func (pa Path) GoString() string {
   160  	var ssPre, ssPost []string
   161  	var numIndirect int
   162  	for i, s := range pa {
   163  		var nextStep PathStep
   164  		if i+1 < len(pa) {
   165  			nextStep = pa[i+1]
   166  		}
   167  		switch s := s.(type) {
   168  		case *indirect:
   169  			numIndirect++
   170  			pPre, pPost := "(", ")"
   171  			switch nextStep.(type) {
   172  			case *indirect:
   173  				continue // Next step is indirection, so let them batch up
   174  			case *structField:
   175  				numIndirect-- // Automatic indirection on struct fields
   176  			case nil:
   177  				pPre, pPost = "", "" // Last step; no need for parenthesis
   178  			}
   179  			if numIndirect > 0 {
   180  				ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect))
   181  				ssPost = append(ssPost, pPost)
   182  			}
   183  			numIndirect = 0
   184  			continue
   185  		case *transform:
   186  			ssPre = append(ssPre, s.trans.name+"(")
   187  			ssPost = append(ssPost, ")")
   188  			continue
   189  		case *typeAssertion:
   190  			// As a special-case, elide type assertions on anonymous types
   191  			// since they are typically generated dynamically and can be very
   192  			// verbose. For example, some transforms return interface{} because
   193  			// of Go's lack of generics, but typically take in and return the
   194  			// exact same concrete type.
   195  			if s.Type().PkgPath() == "" {
   196  				continue
   197  			}
   198  		}
   199  		ssPost = append(ssPost, s.String())
   200  	}
   201  	for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 {
   202  		ssPre[i], ssPre[j] = ssPre[j], ssPre[i]
   203  	}
   204  	return strings.Join(ssPre, "") + strings.Join(ssPost, "")
   205  }
   206  
   207  type (
   208  	pathStep struct {
   209  		typ     reflect.Type
   210  		parentX reflect.Value
   211  		parentY reflect.Value
   212  	}
   213  
   214  	sliceIndex struct {
   215  		pathStep
   216  		xkey, ykey int
   217  	}
   218  	mapIndex struct {
   219  		pathStep
   220  		key reflect.Value
   221  	}
   222  	typeAssertion struct {
   223  		pathStep
   224  	}
   225  	structField struct {
   226  		pathStep
   227  		name string
   228  		idx  int
   229  
   230  		// These fields are used for forcibly accessing an unexported field.
   231  		// pvx, pvy, and field are only valid if unexported is true.
   232  		unexported bool
   233  		force      bool                // Forcibly allow visibility
   234  		pvx, pvy   reflect.Value       // Parent values
   235  		field      reflect.StructField // Field information
   236  	}
   237  	indirect struct {
   238  		pathStep
   239  	}
   240  	transform struct {
   241  		pathStep
   242  		trans *transformer
   243  	}
   244  )
   245  
   246  func (ps pathStep) Type() reflect.Type     { return ps.typ }
   247  func (ps pathStep) ParentX() reflect.Value { return ps.parentX }
   248  func (ps pathStep) ParentY() reflect.Value { return ps.parentY }
   249  func (ps pathStep) String() string {
   250  	if ps.typ == nil {
   251  		return "<nil>"
   252  	}
   253  	s := ps.typ.String()
   254  	if s == "" || strings.ContainsAny(s, "{}\n") {
   255  		return "root" // Type too simple or complex to print
   256  	}
   257  	return fmt.Sprintf("{%s}", s)
   258  }
   259  func (ps pathStep) ToJSON() string {
   260  	return ""
   261  }
   262  
   263  func (si sliceIndex) String() string {
   264  	switch {
   265  	case si.xkey == si.ykey:
   266  		return fmt.Sprintf("[%d]", si.xkey)
   267  	case si.ykey == -1:
   268  		// [5->?] means "I don't know where X[5] went"
   269  		return fmt.Sprintf("[%d->?]", si.xkey)
   270  	case si.xkey == -1:
   271  		// [?->3] means "I don't know where Y[3] came from"
   272  		return fmt.Sprintf("[?->%d]", si.ykey)
   273  	default:
   274  		// [5->3] means "X[5] moved to Y[3]"
   275  		return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey)
   276  	}
   277  }
   278  func (mi mapIndex) String() string      { return fmt.Sprintf("[%#v]", mi.key) }
   279  func (ta typeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) }
   280  func (sf structField) String() string   { return fmt.Sprintf(".%s", sf.name) }
   281  func (in indirect) String() string      { return "*" }
   282  func (tf transform) String() string     { return fmt.Sprintf("%s()", tf.trans.name) }
   283  
   284  func (si sliceIndex) ToJSON() string {
   285  	switch {
   286  	case si.xkey == si.ykey:
   287  		return fmt.Sprintf("[%d]", si.xkey)
   288  	case si.ykey == -1:
   289  		return fmt.Sprintf("[%d]", si.xkey)
   290  	case si.xkey == -1:
   291  		return fmt.Sprintf("[%d]", si.ykey)
   292  	default:
   293  		return ""
   294  	}
   295  }
   296  func (mi mapIndex) ToJSON() string      { return fmt.Sprintf(".%s", mi.key.String()) }
   297  func (ta typeAssertion) ToJSON() string { return fmt.Sprintf(".(%v)", ta.typ) }
   298  func (sf structField) ToJSON() string   { return fmt.Sprintf(".%s", sf.name) }
   299  func (in indirect) ToJSON() string      { return "*" }
   300  func (tf transform) ToJSON() string     { return fmt.Sprintf("%s()", tf.trans.name) }
   301  
   302  func (si sliceIndex) Key() int {
   303  	if si.xkey != si.ykey {
   304  		return -1
   305  	}
   306  	return si.xkey
   307  }
   308  func (si sliceIndex) SplitKeys() (x, y int) { return si.xkey, si.ykey }
   309  func (mi mapIndex) Key() reflect.Value      { return mi.key }
   310  func (sf structField) Name() string         { return sf.name }
   311  func (sf structField) Index() int           { return sf.idx }
   312  func (tf transform) Name() string           { return tf.trans.name }
   313  func (tf transform) Func() reflect.Value    { return tf.trans.fnc }
   314  func (tf transform) Option() Option         { return tf.trans }
   315  
   316  func (pathStep) isPathStep()           {}
   317  func (sliceIndex) isSliceIndex()       {}
   318  func (mapIndex) isMapIndex()           {}
   319  func (typeAssertion) isTypeAssertion() {}
   320  func (structField) isStructField()     {}
   321  func (indirect) isIndirect()           {}
   322  func (transform) isTransform()         {}
   323  
   324  var (
   325  	_ SliceIndex    = sliceIndex{}
   326  	_ MapIndex      = mapIndex{}
   327  	_ TypeAssertion = typeAssertion{}
   328  	_ StructField   = structField{}
   329  	_ Indirect      = indirect{}
   330  	_ Transform     = transform{}
   331  
   332  	_ PathStep = sliceIndex{}
   333  	_ PathStep = mapIndex{}
   334  	_ PathStep = typeAssertion{}
   335  	_ PathStep = structField{}
   336  	_ PathStep = indirect{}
   337  	_ PathStep = transform{}
   338  )
   339  
   340  // isExported reports whether the identifier is exported.
   341  func isExported(id string) bool {
   342  	r, _ := utf8.DecodeRuneInString(id)
   343  	return unicode.IsUpper(r)
   344  }
   345  
   346  // isValid reports whether the identifier is valid.
   347  // Empty and underscore-only strings are not valid.
   348  func isValid(id string) bool {
   349  	ok := id != "" && id != "_"
   350  	for j, c := range id {
   351  		ok = ok && (j > 0 || !unicode.IsDigit(c))
   352  		ok = ok && (c == '_' || unicode.IsLetter(c) || unicode.IsDigit(c))
   353  	}
   354  	return ok
   355  }