Files
tailscale/tstest/typewalk/typewalk.go
T
Brad Fitzpatrick bd2a2d53d3 all: use Go 1.26 things, run most gofix modernizers
I omitted a lot of the min/max modernizers because they didn't
result in more clear code.

Some of it's older "for x := range 123".

Also: errors.AsType, any, fmt.Appendf, etc.

Updates #18682

Change-Id: I83a451577f33877f962766a5b65ce86f7696471c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-03-06 13:32:03 -08:00

106 lines
2.6 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package typewalk provides utilities to walk Go types using reflection.
package typewalk
import (
"iter"
"reflect"
"strings"
)
// Path describes a path via a type where a private key may be found,
// along with a function to test whether a reflect.Value at that path is
// non-zero.
type Path struct {
// Name is the path from the root type, suitable for using as a t.Run name.
Name string
// Walk returns the reflect.Value at the end of the path, given a root
// reflect.Value.
Walk func(root reflect.Value) (leaf reflect.Value)
}
// MatchingPaths returns a sequence of [Path] for all paths
// within the given type that end in a type matching match.
func MatchingPaths(rt reflect.Type, match func(reflect.Type) bool) iter.Seq[Path] {
// valFromRoot is a function that, given a reflect.Value of the root struct,
// returns the reflect.Value at some path within it.
type valFromRoot func(reflect.Value) reflect.Value
return func(yield func(Path) bool) {
var walk func(reflect.Type, valFromRoot)
var path []string
var done bool
seen := map[reflect.Type]bool{}
walk = func(t reflect.Type, getV valFromRoot) {
if seen[t] {
return
}
seen[t] = true
defer func() { seen[t] = false }()
if done {
return
}
if match(t) {
if !yield(Path{
Name: strings.Join(path, "."),
Walk: getV,
}) {
done = true
}
return
}
switch t.Kind() {
case reflect.Pointer, reflect.Slice, reflect.Array:
walk(t.Elem(), func(root reflect.Value) reflect.Value {
v := getV(root)
return v.Elem()
})
case reflect.Struct:
for sf := range t.Fields() {
fieldName := sf.Name
if fieldName == "_" {
continue
}
path = append(path, fieldName)
walk(sf.Type, func(root reflect.Value) reflect.Value {
return getV(root).FieldByName(fieldName)
})
path = path[:len(path)-1]
if done {
return
}
}
case reflect.Map:
walk(t.Elem(), func(root reflect.Value) reflect.Value {
v := getV(root)
if v.Len() == 0 {
return reflect.Zero(t.Elem())
}
iter := v.MapRange()
iter.Next()
return iter.Value()
})
if done {
return
}
walk(t.Key(), func(root reflect.Value) reflect.Value {
v := getV(root)
if v.Len() == 0 {
return reflect.Zero(t.Key())
}
iter := v.MapRange()
iter.Next()
return iter.Key()
})
}
}
path = append(path, rt.Name())
walk(rt, func(v reflect.Value) reflect.Value { return v })
}
}