From 6e310ffcc245495662c0b251cf5f761634bcd6f7 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 13 Apr 2026 21:35:40 +0200 Subject: [PATCH] Use `reflect.TypeFor` instead of `reflect.TypeOf((*T)(nil)).Elem()` Replace the verbose pre-Go 1.22 idiom with `reflect.TypeFor[T]()`, added in Go 1.22 (https://go.dev/doc/go1.22#reflect). Delete the now-redundant `calladapt.TypeOf` helper and add a ruleguard lint rule to prevent reintroduction of the old pattern. Co-authored-by: Isaac --- bundle/direct/dresources/adapter.go | 20 +++++------ libs/calladapt/calladapt.go | 11 ++---- libs/calladapt/calladapt_test.go | 54 ++++++++++++++-------------- libs/calladapt/validate_test.go | 7 ++-- libs/dyn/convert/struct_info.go | 2 +- libs/gorules/rule_reflect_typefor.go | 9 +++++ libs/utils/utils.go | 2 +- 7 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 libs/gorules/rule_reflect_typefor.go diff --git a/bundle/direct/dresources/adapter.go b/bundle/direct/dresources/adapter.go index bd4e7c750a..f931208c3c 100644 --- a/bundle/direct/dresources/adapter.go +++ b/bundle/direct/dresources/adapter.go @@ -154,7 +154,7 @@ func loadKeyedSlices(call *calladapt.BoundCaller) (map[string]any, error) { } func (a *Adapter) initMethods(resource any) error { - err := calladapt.EnsureNoExtraMethods(resource, calladapt.TypeOf[IResource]()) + err := calladapt.EnsureNoExtraMethods(resource, reflect.TypeFor[IResource]()) if err != nil { return err } @@ -164,7 +164,7 @@ func (a *Adapter) initMethods(resource any) error { } // RemapState is optional when remote type already matches state type. - a.remapState, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "RemapState") + a.remapState, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "RemapState") if err != nil { return err } @@ -186,37 +186,37 @@ func (a *Adapter) initMethods(resource any) error { // Optional methods with varying signatures: - a.doUpdate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoUpdate") + a.doUpdate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoUpdate") if err != nil { return err } - a.doUpdateWithID, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoUpdateWithID") + a.doUpdateWithID, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoUpdateWithID") if err != nil { return err } - a.waitAfterCreate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "WaitAfterCreate") + a.waitAfterCreate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "WaitAfterCreate") if err != nil { return err } - a.waitAfterUpdate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "WaitAfterUpdate") + a.waitAfterUpdate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "WaitAfterUpdate") if err != nil { return err } - a.overrideChangeDesc, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "OverrideChangeDesc") + a.overrideChangeDesc, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "OverrideChangeDesc") if err != nil { return err } - a.doResize, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoResize") + a.doResize, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoResize") if err != nil { return err } - keyedSlicesCall, err := calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "KeyedSlices") + keyedSlicesCall, err := calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "KeyedSlices") if err != nil { return err } @@ -535,7 +535,7 @@ func (a *Adapter) KeyedSlices() map[string]any { // prepareCallRequired prepares a call and ensures the method is found. func prepareCallRequired(resource any, methodName string) (*calladapt.BoundCaller, error) { - caller, err := calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), methodName) + caller, err := calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), methodName) if err != nil { return nil, fmt.Errorf("%s: %w", methodName, err) } diff --git a/libs/calladapt/calladapt.go b/libs/calladapt/calladapt.go index e99dfd743a..db7e2733d4 100644 --- a/libs/calladapt/calladapt.go +++ b/libs/calladapt/calladapt.go @@ -5,13 +5,6 @@ import ( "reflect" ) -// TypeOf returns reflect.Type for type parameter T, analogous to -// reflect.TypeOf((*T)(nil)).Elem(). -func TypeOf[T any]() reflect.Type { - var t *T - return reflect.TypeOf(t).Elem() -} - // BoundCaller encapsulates a bound method and metadata about its signature. // It can invoke the underlying function and returns all non-error outputs and // the error (if the method returns one as the last return value). @@ -102,8 +95,8 @@ func (c *BoundCaller) Call(args ...any) ([]any, error) { } var ( - errType = TypeOf[error]() - anyType = TypeOf[any]() + errType = reflect.TypeFor[error]() + anyType = reflect.TypeFor[any]() ) // PrepareCall creates a unified BoundCaller for the given method on receiver that matches the ifaceType method. diff --git a/libs/calladapt/calladapt_test.go b/libs/calladapt/calladapt_test.go index ceedf2b396..e91923f9be 100644 --- a/libs/calladapt/calladapt_test.go +++ b/libs/calladapt/calladapt_test.go @@ -118,39 +118,39 @@ func TestPrepareCallErrors(t *testing.T) { { name: "void method is supported", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodVoid() }](), + ifaceType: reflect.TypeFor[interface{ PMethodVoid() }](), method: "PMethodVoid", }, { name: "correct number of args - concrete matching argument type", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data Data) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data Data) error }](), method: "PMethodAcceptData", }, { name: "correct number of args - interface argument is any", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data any) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data any) error }](), method: "PMethodAcceptData", }, { name: "correct number of args - concrete mismatching argument type", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data NewData) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data NewData) error }](), method: "PMethodAcceptData", errMsg: "interface { PMethodAcceptData(calladapt.NewData) error }.PMethodAcceptData: param 0 mismatch: interface calladapt.NewData, concrete calladapt.Data", }, { name: "incorrect number of args", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData() error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData() error }](), method: "PMethodAcceptData", errMsg: "interface { PMethodAcceptData() error }.PMethodAcceptData: param count mismatch: interface 0, concrete 1", }, { name: "incorrect number of return values", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(any) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(any) (any, error) }](), method: "PMethodAcceptData", errMsg: "interface { PMethodAcceptData(interface {}) (interface {}, error) }.PMethodAcceptData: return count mismatch: interface 2, concrete 1", unexpected: true, @@ -158,7 +158,7 @@ func TestPrepareCallErrors(t *testing.T) { { name: "error return convertible to any", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(any) any }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(any) any }](), method: "PMethodAcceptData", }, { @@ -171,34 +171,34 @@ func TestPrepareCallErrors(t *testing.T) { { name: "untyped nil receiver", recv: nil, - ifaceType: TypeOf[interface{ PMethodAcceptData(any) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(any) (any, error) }](), method: "PMethodAcceptData", errMsg: "first argument must not be untyped nil", }, { name: "method is not on interface", recv: (*MyStruct)(nil), - ifaceType: TypeOf[any](), + ifaceType: reflect.TypeFor[any](), method: "PMethodAcceptData", errMsg: "interface {} has no method \"PMethodAcceptData\"", }, { name: "method is not on receiver", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ Hello(any) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ Hello(any) (any, error) }](), method: "Hello", methodNotFound: true, }, { name: "any instead of error allowed", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data Data) any }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data Data) any }](), method: "PMethodAcceptData", }, { name: "error type mismatch", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ GetCustomError() error }](), + ifaceType: reflect.TypeFor[interface{ GetCustomError() error }](), method: "GetCustomError", errMsg: "interface { GetCustomError() error }.GetCustomError: result 0 mismatch: interface error, concrete calladapt.CustomError", unexpected: true, @@ -206,7 +206,7 @@ func TestPrepareCallErrors(t *testing.T) { { name: "two returns without error are supported", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ BadMethod() (int, string) }](), + ifaceType: reflect.TypeFor[interface{ BadMethod() (int, string) }](), method: "BadMethod", }, } @@ -248,7 +248,7 @@ func TestCall(t *testing.T) { { name: "nil receiver - PMethodAcceptData ok", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data Data) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data Data) error }](), method: "PMethodAcceptData", args: []any{Data{}}, expect: []any{}, @@ -256,7 +256,7 @@ func TestCall(t *testing.T) { { name: "error return", recv: (*MyStruct)(nil), - ifaceType: TypeOf[interface{ PMethodAcceptData(data Data) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptData(data Data) error }](), method: "PMethodAcceptData", args: []any{Data{1}}, errMsg: "X cannot be 1", @@ -264,7 +264,7 @@ func TestCall(t *testing.T) { { name: "value return", recv: my, - ifaceType: TypeOf[interface{ VMethodTransformNoError(any) any }](), + ifaceType: reflect.TypeFor[interface{ VMethodTransformNoError(any) any }](), method: "VMethodTransformNoError", args: []any{Data{2}}, expect: []any{NewData{Y: 12}}, @@ -272,7 +272,7 @@ func TestCall(t *testing.T) { { name: "value return with ptr args", recv: &my, - ifaceType: TypeOf[interface{ PMethodTransformPtrNoError(any) any }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformPtrNoError(any) any }](), method: "PMethodTransformPtrNoError", args: []any{&Data{2}}, expect: []any{&NewData{Y: 12}}, @@ -280,7 +280,7 @@ func TestCall(t *testing.T) { { name: "any+error return", recv: &my, - ifaceType: TypeOf[interface{ PMethodTransformData(data Data) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformData(data Data) (any, error) }](), method: "PMethodTransformData", args: []any{Data{2}}, expect: []any{NewData{Y: 22}}, @@ -288,7 +288,7 @@ func TestCall(t *testing.T) { { name: "any+error return, error case", recv: &MyStruct{State: 0}, - ifaceType: TypeOf[interface{ PMethodTransformData(data Data) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformData(data Data) (any, error) }](), method: "PMethodTransformData", args: []any{Data{1}}, errMsg: "X cannot be 1", @@ -296,7 +296,7 @@ func TestCall(t *testing.T) { { name: "ptr any+error return", recv: &MyStruct{State: 0}, - ifaceType: TypeOf[interface{ PMethodTransformDataPtr(data *Data) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformDataPtr(data *Data) (any, error) }](), method: "PMethodTransformDataPtr", args: []any{&Data{2}}, expect: []any{&NewData{Y: 12}}, @@ -304,7 +304,7 @@ func TestCall(t *testing.T) { { name: "ptr any+error return, error case (nil)", recv: &MyStruct{State: 0}, - ifaceType: TypeOf[interface{ PMethodTransformDataPtr(data *Data) (any, error) }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformDataPtr(data *Data) (any, error) }](), method: "PMethodTransformDataPtr", args: []any{nil}, errMsg: "data is nil", @@ -312,7 +312,7 @@ func TestCall(t *testing.T) { { name: "void method call returns no outs", recv: &my, - ifaceType: TypeOf[interface{ PMethodVoid() }](), + ifaceType: reflect.TypeFor[interface{ PMethodVoid() }](), method: "PMethodVoid", args: []any{}, expect: []any{}, @@ -320,7 +320,7 @@ func TestCall(t *testing.T) { { name: "too many args error", recv: my, - ifaceType: TypeOf[interface{ VMethodTransformNoError(data Data) any }](), + ifaceType: reflect.TypeFor[interface{ VMethodTransformNoError(data Data) any }](), method: "VMethodTransformNoError", args: []any{Data{1}, Data{2}}, errMsg: "VMethodTransformNoError: want 1 args, got 2", @@ -328,7 +328,7 @@ func TestCall(t *testing.T) { { name: "wrong arg type error (different pointer)", recv: &my, - ifaceType: TypeOf[interface{ PMethodTransformPtrNoError(data *Data) any }](), + ifaceType: reflect.TypeFor[interface{ PMethodTransformPtrNoError(data *Data) any }](), method: "PMethodTransformPtrNoError", args: []any{&NewData{}}, errMsg: "PMethodTransformPtrNoError: arg 0 type mismatch: want *calladapt.Data, got *calladapt.NewData", @@ -336,7 +336,7 @@ func TestCall(t *testing.T) { { name: "nil interface param allowed", recv: &my, - ifaceType: TypeOf[interface{ PMethodAcceptAny(v any) error }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptAny(v any) error }](), method: "PMethodAcceptAny", args: []any{nil}, errMsg: "PMethodAcceptAny: arg 0 type mismatch: want interface {}, got nil", @@ -344,7 +344,7 @@ func TestCall(t *testing.T) { { name: "nil slice param allowed", recv: &my, - ifaceType: TypeOf[interface{ PMethodAcceptSlice(s []int) int }](), + ifaceType: reflect.TypeFor[interface{ PMethodAcceptSlice(s []int) int }](), method: "PMethodAcceptSlice", args: []any{nil}, errMsg: "PMethodAcceptSlice: arg 0 type mismatch: want []int, got nil", @@ -352,7 +352,7 @@ func TestCall(t *testing.T) { { name: "DoCreate returns id", recv: &my, - ifaceType: TypeOf[interface { + ifaceType: reflect.TypeFor[interface { DoCreate(ctx context.Context, data *Data) (string, error) }](), method: "DoCreate", diff --git a/libs/calladapt/validate_test.go b/libs/calladapt/validate_test.go index e26466d20c..41a4ce0e0b 100644 --- a/libs/calladapt/validate_test.go +++ b/libs/calladapt/validate_test.go @@ -1,6 +1,7 @@ package calladapt_test import ( + "reflect" "testing" "github.com/databricks/cli/libs/calladapt" @@ -32,19 +33,19 @@ func (*badType) Extra() {} func TestEnsureNoExtraMethods_AllowsPartial(t *testing.T) { typedNil := (*partialType)(nil) - err := calladapt.EnsureNoExtraMethods(typedNil, calladapt.TypeOf[testIface]()) + err := calladapt.EnsureNoExtraMethods(typedNil, reflect.TypeFor[testIface]()) require.NoError(t, err) } func TestEnsureNoExtraMethods_AllowsGood(t *testing.T) { typedNil := (*goodType)(nil) - err := calladapt.EnsureNoExtraMethods(typedNil, calladapt.TypeOf[testIface]()) + err := calladapt.EnsureNoExtraMethods(typedNil, reflect.TypeFor[testIface]()) require.NoError(t, err) } func TestEnsureNoExtraMethods_RejectsExtra(t *testing.T) { typedNil := (*badType)(nil) - err := calladapt.EnsureNoExtraMethods(typedNil, calladapt.TypeOf[testIface]()) + err := calladapt.EnsureNoExtraMethods(typedNil, reflect.TypeFor[testIface]()) require.Error(t, err) assert.Equal(t, "unexpected method Extra on *calladapt_test.badType; only methods from [calladapt_test.testIface] are allowed", err.Error()) } diff --git a/libs/dyn/convert/struct_info.go b/libs/dyn/convert/struct_info.go index 45a1eab960..7e5b0bc741 100644 --- a/libs/dyn/convert/struct_info.go +++ b/libs/dyn/convert/struct_info.go @@ -190,7 +190,7 @@ func (s *structInfo) FieldValues(v reflect.Value) []FieldValue { } // Type of [dyn.Value]. -var configValueType = reflect.TypeOf((*dyn.Value)(nil)).Elem() +var configValueType = reflect.TypeFor[dyn.Value]() // getForceSendFieldsValues collects ForceSendFields reflect.Values // Returns map[structKey]reflect.Value where structKey is -1 for direct fields, embedded index for embedded fields diff --git a/libs/gorules/rule_reflect_typefor.go b/libs/gorules/rule_reflect_typefor.go new file mode 100644 index 0000000000..4a5993d87b --- /dev/null +++ b/libs/gorules/rule_reflect_typefor.go @@ -0,0 +1,9 @@ +package gorules + +import "github.com/quasilyte/go-ruleguard/dsl" + +// UseReflectTypeFor detects reflect.TypeOf((*T)(nil)).Elem() and suggests reflect.TypeFor[T]() instead. +func UseReflectTypeFor(m dsl.Matcher) { + m.Match(`reflect.TypeOf(($x)(nil)).Elem()`). + Report(`Use reflect.TypeFor instead of reflect.TypeOf((*T)(nil)).Elem()`) +} diff --git a/libs/utils/utils.go b/libs/utils/utils.go index 1ffca44f68..3d202f863b 100644 --- a/libs/utils/utils.go +++ b/libs/utils/utils.go @@ -19,7 +19,7 @@ func SortedKeys[T any](m map[string]T) []string { // We must use that when copying structs because JSON marshaller in SDK crashes if it sees unknown field. func FilterFields[T any](fields []string, excludeFields ...string) []string { var result []string - typeOfT := reflect.TypeOf((*T)(nil)).Elem() + typeOfT := reflect.TypeFor[T]() excludeMap := make(map[string]bool) for _, exclude := range excludeFields {