diff --git a/cmd/tailscale/cli/cli_test.go b/cmd/tailscale/cli/cli_test.go index 385388641..2de396bdd 100644 --- a/cmd/tailscale/cli/cli_test.go +++ b/cmd/tailscale/cli/cli_test.go @@ -802,7 +802,7 @@ func TestPrefFlagMapping(t *testing.T) { } } - prefType := reflect.TypeOf(ipn.Prefs{}) + prefType := reflect.TypeFor[ipn.Prefs]() for i := 0; i < prefType.NumField(); i++ { prefName := prefType.Field(i).Name if prefHasFlag[prefName] { diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index 955e3ccc0..3aa66865f 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -726,7 +726,7 @@ func init() { func addPrefFlagMapping(flagName string, prefNames ...string) { prefsOfFlag[flagName] = prefNames - prefType := reflect.TypeOf(ipn.Prefs{}) + prefType := reflect.TypeFor[ipn.Prefs]() for _, pref := range prefNames { t := prefType for _, name := range strings.Split(pref, ".") { diff --git a/control/controlclient/controlclient_test.go b/control/controlclient/controlclient_test.go index bb0b598e6..c608ef6cc 100644 --- a/control/controlclient/controlclient_test.go +++ b/control/controlclient/controlclient_test.go @@ -20,7 +20,7 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestStatusEqual(t *testing.T) { // Verify that the Equal method stays in sync with reality equalHandles := []string{"Err", "URL", "NetMap", "Persist", "state"} - if have := fieldsOf(reflect.TypeOf(Status{})); !reflect.DeepEqual(have, equalHandles) { + if have := fieldsOf(reflect.TypeFor[Status]()); !reflect.DeepEqual(have, equalHandles) { t.Errorf("Status.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, equalHandles) } diff --git a/control/controlclient/map.go b/control/controlclient/map.go index 01e96f42f..6231d4b64 100644 --- a/control/controlclient/map.go +++ b/control/controlclient/map.go @@ -538,7 +538,7 @@ func (ms *mapSession) patchifyPeersChanged(resp *tailcfg.MapResponse) { // getNodeFields returns the fails of tailcfg.Node. func getNodeFields() []string { - rt := reflect.TypeOf((*tailcfg.Node)(nil)).Elem() + rt := reflect.TypeFor[tailcfg.Node]() ret := make([]string, rt.NumField()) for i := 0; i < rt.NumField(); i++ { ret[i] = rt.Field(i).Name diff --git a/control/controlknobs/controlknobs_test.go b/control/controlknobs/controlknobs_test.go index 266a4708d..a78a486f3 100644 --- a/control/controlknobs/controlknobs_test.go +++ b/control/controlknobs/controlknobs_test.go @@ -15,7 +15,7 @@ func TestAsDebugJSON(t *testing.T) { } k := new(Knobs) got := k.AsDebugJSON() - if want := reflect.TypeOf(Knobs{}).NumField(); len(got) != want { + if want := reflect.TypeFor[Knobs]().NumField(); len(got) != want { t.Errorf("AsDebugJSON map has %d fields; want %v", len(got), want) } } diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 787b75d51..9251bb2bb 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -64,7 +64,7 @@ func TestPrefsEqual(t *testing.T) { "NetfilterKind", "Persist", } - if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) { + if have := fieldsOf(reflect.TypeFor[Prefs]()); !reflect.DeepEqual(have, prefsHandles) { t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, prefsHandles) } @@ -615,14 +615,14 @@ func TestLoadPrefsFileWithZeroInIt(t *testing.T) { func TestMaskedPrefsFields(t *testing.T) { have := map[string]bool{} - for _, f := range fieldsOf(reflect.TypeOf(Prefs{})) { + for _, f := range fieldsOf(reflect.TypeFor[Prefs]()) { if f == "Persist" { // This one can't be edited. continue } have[f] = true } - for _, f := range fieldsOf(reflect.TypeOf(MaskedPrefs{})) { + for _, f := range fieldsOf(reflect.TypeFor[MaskedPrefs]()) { if f == "Prefs" { continue } @@ -644,8 +644,8 @@ func TestMaskedPrefsFields(t *testing.T) { // And also make sure they line up in the right order, which // ApplyEdits assumes. - pt := reflect.TypeOf(Prefs{}) - mt := reflect.TypeOf(MaskedPrefs{}) + pt := reflect.TypeFor[Prefs]() + mt := reflect.TypeFor[MaskedPrefs]() for i := 0; i < mt.NumField(); i++ { name := mt.Field(i).Name if i == 0 { diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index 8d6b8110c..d193f55e3 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -1154,7 +1154,7 @@ func TestPathFromPAMEnvLineOnNixOS(t *testing.T) { } func TestStdOsUserUserAssumptions(t *testing.T) { - v := reflect.TypeOf(user.User{}) + v := reflect.TypeFor[user.User]() if got, want := v.NumField(), 5; got != want { t.Errorf("os/user.User has %v fields; this package assumes %v", got, want) } diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 8b678209a..bf8e48bdd 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -67,7 +67,7 @@ func TestHostinfoEqual(t *testing.T) { "AppConnector", "Location", } - if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) { + if have := fieldsOf(reflect.TypeFor[Hostinfo]()); !reflect.DeepEqual(have, hiHandles) { t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, hiHandles) } @@ -364,7 +364,7 @@ func TestNodeEqual(t *testing.T) { "DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer", "SelfNodeV6MasqAddrForThisPeer", "IsWireGuardOnly", "ExitNodeDNSResolvers", } - if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) { + if have := fieldsOf(reflect.TypeFor[Node]()); !reflect.DeepEqual(have, nodeHandles) { t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, nodeHandles) } @@ -632,7 +632,7 @@ func TestNetInfoFields(t *testing.T) { "DERPLatency", "FirewallMode", } - if have := fieldsOf(reflect.TypeOf(NetInfo{})); !reflect.DeepEqual(have, handled) { + if have := fieldsOf(reflect.TypeFor[NetInfo]()); !reflect.DeepEqual(have, handled) { t.Errorf("NetInfo.Clone/BasicallyEqually check might be out of sync\nfields: %q\nhandled: %q\n", have, handled) } diff --git a/types/dnstype/dnstype_test.go b/types/dnstype/dnstype_test.go index bd8986e7f..e3a941a20 100644 --- a/types/dnstype/dnstype_test.go +++ b/types/dnstype/dnstype_test.go @@ -13,7 +13,7 @@ func TestResolverEqual(t *testing.T) { var fieldNames []string - for _, field := range reflect.VisibleFields(reflect.TypeOf(Resolver{})) { + for _, field := range reflect.VisibleFields(reflect.TypeFor[Resolver]()) { fieldNames = append(fieldNames, field.Name) } sort.Strings(fieldNames) diff --git a/types/netmap/nodemut.go b/types/netmap/nodemut.go index 1cc2a8a67..be224efed 100644 --- a/types/netmap/nodemut.go +++ b/types/netmap/nodemut.go @@ -72,7 +72,7 @@ func (m NodeMutationLastSeen) Apply(n *tailcfg.Node) { var peerChangeFields = sync.OnceValue(func() []reflect.StructField { var fields []reflect.StructField - rt := reflect.TypeOf((*tailcfg.PeerChange)(nil)).Elem() + rt := reflect.TypeFor[tailcfg.PeerChange]() for i := 0; i < rt.NumField(); i++ { fields = append(fields, rt.Field(i)) } diff --git a/types/netmap/nodemut_test.go b/types/netmap/nodemut_test.go index ce20286aa..ef20ca6f5 100644 --- a/types/netmap/nodemut_test.go +++ b/types/netmap/nodemut_test.go @@ -27,7 +27,7 @@ func TestMapResponseContainsNonPatchFields(t *testing.T) { case reflect.Bool: return reflect.ValueOf(true) case reflect.String: - if reflect.TypeOf(opt.Bool("")) == t { + if reflect.TypeFor[opt.Bool]() == t { return reflect.ValueOf("true").Convert(t) } return reflect.ValueOf("foo").Convert(t) @@ -43,7 +43,7 @@ func TestMapResponseContainsNonPatchFields(t *testing.T) { panic(fmt.Sprintf("unhandled %v", t)) } - rt := reflect.TypeOf(tailcfg.MapResponse{}) + rt := reflect.TypeFor[tailcfg.MapResponse]() for i := 0; i < rt.NumField(); i++ { f := rt.Field(i) diff --git a/types/persist/persist_test.go b/types/persist/persist_test.go index bfbc92c7e..99128f891 100644 --- a/types/persist/persist_test.go +++ b/types/persist/persist_test.go @@ -22,7 +22,7 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestPersistEqual(t *testing.T) { persistHandles := []string{"LegacyFrontendPrivateMachineKey", "PrivateNodeKey", "OldPrivateNodeKey", "Provider", "UserProfile", "NetworkLockKey", "NodeID", "DisallowedTKAStateIDs"} - if have := fieldsOf(reflect.TypeOf(Persist{})); !reflect.DeepEqual(have, persistHandles) { + if have := fieldsOf(reflect.TypeFor[Persist]()); !reflect.DeepEqual(have, persistHandles) { t.Errorf("Persist.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, persistHandles) } diff --git a/util/ctxkey/key.go b/util/ctxkey/key.go index 0e2173b1f..e2b0e9d4c 100644 --- a/util/ctxkey/key.go +++ b/util/ctxkey/key.go @@ -24,11 +24,6 @@ "reflect" ) -// TODO(https://go.dev/issue/60088): Use reflect.TypeFor instead. -func reflectTypeFor[T any]() reflect.Type { - return reflect.TypeOf((*T)(nil)).Elem() -} - // Key is a generic key type associated with a specific value type. // // A zero Key is valid where the Value type itself is used as the context key. @@ -65,7 +60,7 @@ func New[Value any](name string, defaultValue Value) Key[Value] { // since newly allocated pointers are globally unique within a process. key := Key[Value]{name: new(stringer[string])} if name == "" { - name = reflectTypeFor[Value]().String() + name = reflect.TypeFor[Value]().String() } key.name.v = name if v := reflect.ValueOf(defaultValue); v.IsValid() && !v.IsZero() { @@ -78,7 +73,7 @@ func New[Value any](name string, defaultValue Value) Key[Value] { func (key Key[Value]) contextKey() any { if key.name == nil { // Use the reflect.Type of the Value (implies key not created by New). - return reflectTypeFor[Value]() + return reflect.TypeFor[Value]() } else { // Use the name pointer directly (implies key created by New). return key.name @@ -119,7 +114,7 @@ func (key Key[Value]) Has(ctx context.Context) (ok bool) { // String returns the name of the key. func (key Key[Value]) String() string { if key.name == nil { - return reflectTypeFor[Value]().String() + return reflect.TypeFor[Value]().String() } return key.name.String() } diff --git a/util/deephash/deephash.go b/util/deephash/deephash.go index bff0fc435..1993649e3 100644 --- a/util/deephash/deephash.go +++ b/util/deephash/deephash.go @@ -248,7 +248,7 @@ func Hash[T any](v *T) Sum { // Always treat the Hash input as if it were an interface by including // a hash of the type. This ensures that hashing of two different types // but with the same value structure produces different hashes. - t := reflect.TypeOf(v).Elem() + t := reflect.TypeFor[T]() h.hashType(t) if v == nil { h.HashUint8(0) // indicates nil @@ -300,8 +300,7 @@ func ExcludeFields[T any](fields ...string) Option { } func newFieldFilter[T any](include bool, fields []string) Option { - var zero T - t := reflect.TypeOf(&zero).Elem() + t := reflect.TypeFor[T]() fieldSet := set.Set[string]{} for _, f := range fields { if _, ok := t.FieldByName(f); !ok { @@ -321,12 +320,11 @@ func newFieldFilter[T any](include bool, fields []string) Option { // be removed in the future, along with documentation about their precedence // when combined. func HasherForType[T any](opts ...Option) func(*T) Sum { - var v *T seedOnce.Do(initSeed) if len(opts) > 1 { panic("HasherForType only accepts one optional argument") // for now } - t := reflect.TypeOf(v).Elem() + t := reflect.TypeFor[T]() var hash typeHasherFunc for _, o := range opts { switch o := o.(type) { diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index 76d8dde95..52b874f89 100644 --- a/util/deephash/deephash_test.go +++ b/util/deephash/deephash_test.go @@ -823,7 +823,7 @@ func TestHashMapAcyclic(t *testing.T) { hb := &hashBuffer{Hash: sha256.New()} - hash := lookupTypeHasher(reflect.TypeOf(m)) + hash := lookupTypeHasher(reflect.TypeFor[map[int]string]()) for i := 0; i < 20; i++ { va := reflect.ValueOf(&m).Elem() hb.Reset() diff --git a/util/deephash/types.go b/util/deephash/types.go index e7ea54eb1..54edcbffc 100644 --- a/util/deephash/types.go +++ b/util/deephash/types.go @@ -10,9 +10,9 @@ ) var ( - timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem() - netipAddrType = reflect.TypeOf((*netip.Addr)(nil)).Elem() - selfHasherType = reflect.TypeOf((*SelfHasher)(nil)).Elem() + timeTimeType = reflect.TypeFor[time.Time]() + netipAddrType = reflect.TypeFor[netip.Addr]() + selfHasherType = reflect.TypeFor[SelfHasher]() ) // typeIsSpecialized reports whether this type has specialized hashing. diff --git a/wgengine/router/router_test.go b/wgengine/router/router_test.go index 0b20f8807..4b0f51f57 100644 --- a/wgengine/router/router_test.go +++ b/wgengine/router/router_test.go @@ -26,7 +26,7 @@ func TestConfigEqual(t *testing.T) { "SubnetRoutes", "SNATSubnetRoutes", "NetfilterMode", "NetfilterKind", } - configType := reflect.TypeOf(Config{}) + configType := reflect.TypeFor[Config]() configFields := []string{} for i := 0; i < configType.NumField(); i++ { configFields = append(configFields, configType.Field(i).Name) diff --git a/wgengine/wgint/wgint.go b/wgengine/wgint/wgint.go index dc9fe08d2..09c6ccab4 100644 --- a/wgengine/wgint/wgint.go +++ b/wgengine/wgint/wgint.go @@ -20,7 +20,7 @@ ) func getPeerStatsOffset(name string) uintptr { - peerType := reflect.TypeOf(device.Peer{}) + peerType := reflect.TypeFor[device.Peer]() field, ok := peerType.FieldByName(name) if !ok { panic("no " + name + " field in device.Peer")