1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-14 11:23:09 +02:00
lazygit/vendor/github.com/sanity-io/litter/pointers.go
2022-01-26 10:58:33 +11:00

168 lines
3.2 KiB
Go

package litter
import (
"fmt"
"reflect"
"sort"
)
// mapReusedPointers takes a structure, and recursively maps all pointers mentioned in the tree,
// detecting circular references, and providing a list of all pointers that was referenced at
// least twice by the provided structure.
func mapReusedPointers(v reflect.Value) ptrmap {
pm := &pointerVisitor{}
pm.consider(v)
return pm.reused
}
// A map of pointers.
type ptrinfo struct {
id int
parent *ptrmap
}
func (p *ptrinfo) label() string {
if p.id == -1 {
p.id = p.parent.count
p.parent.count++
}
return fmt.Sprintf("p%d", p.id)
}
type ptrkey struct {
p uintptr
t reflect.Type
}
func ptrkeyFor(v reflect.Value) (k ptrkey) {
k.p = v.Pointer()
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.IsValid() {
k.t = v.Type()
}
return
}
type ptrmap struct {
m map[ptrkey]*ptrinfo
count int
}
// Returns true if contains a pointer.
func (pm *ptrmap) contains(v reflect.Value) bool {
if pm.m != nil {
_, ok := pm.m[ptrkeyFor(v)]
return ok
}
return false
}
// Gets a pointer.
func (pm *ptrmap) get(v reflect.Value) (*ptrinfo, bool) {
if pm.m != nil {
p, ok := pm.m[ptrkeyFor(v)]
return p, ok
}
return nil, false
}
// Removes a pointer.
func (pm *ptrmap) remove(v reflect.Value) {
if pm.m != nil {
delete(pm.m, ptrkeyFor(v))
}
}
// Adds a pointer.
func (pm *ptrmap) add(p reflect.Value) bool {
if pm.contains(p) {
return false
}
pm.put(p)
return true
}
// Adds a pointer (slow path).
func (pm *ptrmap) put(v reflect.Value) {
if pm.m == nil {
pm.m = make(map[ptrkey]*ptrinfo, 31)
}
key := ptrkeyFor(v)
if _, ok := pm.m[key]; !ok {
pm.m[key] = &ptrinfo{id: -1, parent: pm}
}
}
type pointerVisitor struct {
pointers ptrmap
reused ptrmap
}
// Recursively consider v and each of its children, updating the map according to the
// semantics of MapReusedPointers
func (pv *pointerVisitor) consider(v reflect.Value) {
if v.Kind() == reflect.Invalid {
return
}
if isPointerValue(v) { // pointer is 0 for unexported fields
if pv.tryAddPointer(v) {
// No use descending inside this value, since it have been seen before and all its descendants
// have been considered
return
}
}
// Now descend into any children of this value
switch v.Kind() {
case reflect.Slice, reflect.Array:
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
pv.consider(v.Index(i))
}
case reflect.Interface:
pv.consider(v.Elem())
case reflect.Ptr:
pv.consider(v.Elem())
case reflect.Map:
keys := v.MapKeys()
sort.Sort(mapKeySorter{
keys: keys,
options: &Config,
})
for _, key := range keys {
pv.consider(v.MapIndex(key))
}
case reflect.Struct:
numFields := v.NumField()
for i := 0; i < numFields; i++ {
pv.consider(v.Field(i))
}
}
}
// addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused
func (pv *pointerVisitor) tryAddPointer(v reflect.Value) bool {
// Is this allready known to be reused?
if pv.reused.contains(v) {
return true
}
// Have we seen it once before?
if pv.pointers.contains(v) {
// Add it to the register of pointers we have seen more than once
pv.reused.add(v)
return true
}
// This pointer was new to us
pv.pointers.add(v)
return false
}