Skip to content

Commit 29800b7

Browse files
committed
feat(reflect): add KindUintptr + TypeFor/NewValue/MakeSlice/MakeMap/CopyValue/MakeFunc + StructField
Phase 2 v0.10.4 gap-fill — genuine reflect gaps consumers reach for that reflect.go wrapped no equivalent of: - KindUintptr — missing sibling Kind constant (consumers used reflect.Uintptr 16x) - StructField type alias — field descriptor from Type.Field - TypeFor[T] — type-safe replacement for TypeOf((*T)(nil)).Elem() (10 uses) - NewValue (reflect.New, renamed — core.New is the constructor) — 69 uses - MakeSlice/MakeMap/MakeMapWithSize — reflective make() (31+7+4 uses) - CopyValue (reflect.Copy, renamed — core.Copy is io.Copy) — 9 uses - MakeFunc — runtime func synthesis (4 uses) Additive only. Good/Bad/Ugly per symbol, runnable godoc examples, ReportAllocs benchmarks for the allocating constructors. Deliberately NOT wrapped (subtle/unsafe per AX reflect caution): reflect.NewAt (unsafe.Pointer arg). reflect.Ptr (deprecated Kind alias, consumers should use KindPointer = non-compliance). All bare Kind consts (reflect.Func/Slice/Map/String/Int*/etc.) already wrapped as Kind* = non-compliance noise, not gaps. Co-Authored-By: Virgil <virgil@lethean.io>
1 parent d101d03 commit 29800b7

4 files changed

Lines changed: 291 additions & 0 deletions

File tree

reflect.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,18 @@ const (
6161
KindSlice = reflect.Slice
6262
KindString = reflect.String
6363
KindStruct = reflect.Struct
64+
KindUintptr = reflect.Uintptr
6465
KindUnsafePointer = reflect.UnsafePointer
6566
)
6667

68+
// StructField is an alias for reflect.StructField — one field's
69+
// descriptor (Name, Type, Tag, offset) returned by Type.Field during
70+
// struct introspection.
71+
//
72+
// f := core.TypeOf(opts).Field(0)
73+
// tag := f.Tag.Get("json")
74+
type StructField = reflect.StructField
75+
6776
// TypeOf returns the runtime type of v. Returns nil if v is a nil
6877
// interface value.
6978
//
@@ -100,3 +109,68 @@ func DeepEqual(x, y any) bool {
100109
func Zero(t Type) Value {
101110
return reflect.Zero(t)
102111
}
112+
113+
// TypeFor returns the Type for the compile-time type T. The type-safe
114+
// replacement for TypeOf((*T)(nil)).Elem() — no nil-pointer dance, no
115+
// runtime value needed.
116+
//
117+
// t := core.TypeFor[MyStruct]()
118+
// if t.Kind() == core.KindStruct { ... }
119+
func TypeFor[T any]() Type {
120+
return reflect.TypeFor[T]()
121+
}
122+
123+
// NewValue returns a Value representing a pointer to a new zero value
124+
// of type t — the reflective equivalent of new(T). Named NewValue (not
125+
// New) because core.New is the framework constructor.
126+
//
127+
// ptr := core.NewValue(core.TypeFor[Config]()) // *Config, zeroed
128+
// cfg := ptr.Elem().Interface().(Config)
129+
func NewValue(t Type) Value {
130+
return reflect.New(t)
131+
}
132+
133+
// MakeSlice returns a Value representing a new slice of element type's
134+
// slice t with the given length and capacity. t must have Kind Slice;
135+
// callers control that, so this stays infallible (panics on a non-slice
136+
// type, matching the stdlib contract).
137+
//
138+
// s := core.MakeSlice(core.TypeFor[[]int](), 0, 8)
139+
func MakeSlice(t Type, len, cap int) Value {
140+
return reflect.MakeSlice(t, len, cap)
141+
}
142+
143+
// MakeMap returns a Value representing a new empty map of map type t.
144+
//
145+
// m := core.MakeMap(core.TypeFor[map[string]int]())
146+
func MakeMap(t Type) Value {
147+
return reflect.MakeMap(t)
148+
}
149+
150+
// MakeMapWithSize returns a new empty map of type t pre-sized for about
151+
// n entries — the reflective make(map, n) hint.
152+
//
153+
// m := core.MakeMapWithSize(core.TypeFor[map[string]int](), 64)
154+
func MakeMapWithSize(t Type, n int) Value {
155+
return reflect.MakeMapWithSize(t, n)
156+
}
157+
158+
// CopyValue copies the contents of src into dst until dst is full or
159+
// src is exhausted, returning the number of elements copied. Both must
160+
// be slices (or dst an array) with assignable element types. Named
161+
// CopyValue (not Copy) because core.Copy is the io stream copier.
162+
//
163+
// n := core.CopyValue(dstVal, srcVal)
164+
func CopyValue(dst, src Value) int {
165+
return reflect.Copy(dst, src)
166+
}
167+
168+
// MakeFunc returns a new function Value of type t whose body calls fn
169+
// with the in-arguments and returns fn's results. Used to synthesise
170+
// functions (proxies, generic adapters) at runtime — reach for it only
171+
// when a closure over a concrete signature genuinely cannot.
172+
//
173+
// fn := core.MakeFunc(t, func(args []core.Value) []core.Value { ... })
174+
func MakeFunc(t Type, fn func(args []Value) (results []Value)) Value {
175+
return reflect.MakeFunc(t, fn)
176+
}

reflect_bench_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var (
2020
reflectSinkValue Value
2121
reflectSinkBool bool
2222
reflectSinkKind Kind
23+
reflectSinkInt int
2324
)
2425

2526
// Fixtures
@@ -122,3 +123,45 @@ func BenchmarkReflect_Zero_Struct(b *B) {
122123
reflectSinkValue = Zero(t)
123124
}
124125
}
126+
127+
// --- TypeFor / NewValue / MakeSlice / MakeMap / CopyValue ---
128+
129+
func BenchmarkReflect_TypeFor_Struct(b *B) {
130+
b.ReportAllocs()
131+
for i := 0; i < b.N; i++ {
132+
reflectSinkType = TypeFor[reflectStruct]()
133+
}
134+
}
135+
136+
func BenchmarkReflect_NewValue_Struct(b *B) {
137+
t := TypeFor[reflectStruct]()
138+
b.ReportAllocs()
139+
for i := 0; i < b.N; i++ {
140+
reflectSinkValue = NewValue(t)
141+
}
142+
}
143+
144+
func BenchmarkReflect_MakeSlice_Int(b *B) {
145+
t := TypeFor[[]int]()
146+
b.ReportAllocs()
147+
for i := 0; i < b.N; i++ {
148+
reflectSinkValue = MakeSlice(t, 0, 8)
149+
}
150+
}
151+
152+
func BenchmarkReflect_MakeMap_StringInt(b *B) {
153+
t := TypeFor[map[string]int]()
154+
b.ReportAllocs()
155+
for i := 0; i < b.N; i++ {
156+
reflectSinkValue = MakeMap(t)
157+
}
158+
}
159+
160+
func BenchmarkReflect_CopyValue_Int(b *B) {
161+
src := ValueOf(reflectFixSlice)
162+
dst := MakeSlice(TypeFor[[]int](), len(reflectFixSlice), len(reflectFixSlice))
163+
b.ReportAllocs()
164+
for i := 0; i < b.N; i++ {
165+
reflectSinkInt = CopyValue(dst, src)
166+
}
167+
}

reflect_example_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,20 @@ func ExampleKind() {
3434
Println(k == KindString)
3535
// Output: true
3636
}
37+
38+
// ExampleTypeFor names a type at compile time through `TypeFor`, with no
39+
// nil-pointer dance. Reflection stays behind a narrow core surface for
40+
// rare inspection code.
41+
func ExampleTypeFor() {
42+
Println(TypeFor[string]().Kind())
43+
// Output: string
44+
}
45+
46+
// ExampleNewValue allocates a zeroed value of a reflected type through
47+
// `NewValue` — the reflective new(T).
48+
func ExampleNewValue() {
49+
ptr := NewValue(TypeFor[int]())
50+
ptr.Elem().SetInt(7)
51+
Println(ptr.Elem().Int())
52+
// Output: 7
53+
}

reflect_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,160 @@ func TestReflect_Zero_Ugly(t *T) {
6262
AssertEqual(t, KindPointer, z.Kind())
6363
AssertTrue(t, z.IsNil())
6464
}
65+
66+
func TestReflect_KindUintptr_Good(t *T) {
67+
var p uintptr
68+
AssertEqual(t, KindUintptr, TypeOf(p).Kind())
69+
}
70+
71+
func TestReflect_KindUintptr_Bad(t *T) {
72+
AssertNotEqual(t, KindUintptr, TypeOf(42).Kind())
73+
}
74+
75+
func TestReflect_KindUintptr_Ugly(t *T) {
76+
// UnsafePointer is a distinct kind, not Uintptr.
77+
AssertNotEqual(t, KindUintptr, KindUnsafePointer)
78+
}
79+
80+
type reflectTagged struct {
81+
Name string `json:"name"`
82+
}
83+
84+
func TestReflect_StructField_Good(t *T) {
85+
var f StructField = TypeFor[reflectTagged]().Field(0)
86+
AssertEqual(t, "Name", f.Name)
87+
AssertEqual(t, "name", f.Tag.Get("json"))
88+
}
89+
90+
func TestReflect_StructField_Bad(t *T) {
91+
// Indexing past the field count panics.
92+
AssertPanics(t, func() { _ = TypeFor[reflectTagged]().Field(5) })
93+
}
94+
95+
func TestReflect_StructField_Ugly(t *T) {
96+
AssertEqual(t, KindString, TypeFor[reflectTagged]().Field(0).Type.Kind())
97+
}
98+
99+
func TestReflect_TypeFor_Good(t *T) {
100+
AssertEqual(t, KindString, TypeFor[string]().Kind())
101+
}
102+
103+
func TestReflect_TypeFor_Bad(t *T) {
104+
AssertNotEqual(t, TypeFor[int](), TypeFor[string]())
105+
}
106+
107+
func TestReflect_TypeFor_Ugly(t *T) {
108+
// TypeFor[any] yields a nil-interface element type.
109+
AssertEqual(t, KindInterface, TypeFor[any]().Kind())
110+
}
111+
112+
func TestReflect_NewValue_Good(t *T) {
113+
ptr := NewValue(TypeFor[int]())
114+
AssertEqual(t, KindPointer, ptr.Kind())
115+
AssertEqual(t, int64(0), ptr.Elem().Int())
116+
}
117+
118+
func TestReflect_NewValue_Bad(t *T) {
119+
AssertPanics(t, func() { _ = NewValue(nil) })
120+
}
121+
122+
func TestReflect_NewValue_Ugly(t *T) {
123+
// The new value is addressable and settable through its pointer.
124+
ptr := NewValue(TypeFor[int]())
125+
ptr.Elem().SetInt(7)
126+
AssertEqual(t, int64(7), ptr.Elem().Int())
127+
}
128+
129+
func TestReflect_MakeSlice_Good(t *T) {
130+
s := MakeSlice(TypeFor[[]int](), 0, 4)
131+
AssertEqual(t, 0, s.Len())
132+
AssertEqual(t, 4, s.Cap())
133+
}
134+
135+
func TestReflect_MakeSlice_Bad(t *T) {
136+
// A non-slice type panics.
137+
AssertPanics(t, func() { _ = MakeSlice(TypeFor[int](), 0, 0) })
138+
}
139+
140+
func TestReflect_MakeSlice_Ugly(t *T) {
141+
s := MakeSlice(TypeFor[[]int](), 2, 2)
142+
AssertEqual(t, 2, s.Len())
143+
AssertEqual(t, int64(0), s.Index(0).Int())
144+
}
145+
146+
func TestReflect_MakeMap_Good(t *T) {
147+
m := MakeMap(TypeFor[map[string]int]())
148+
AssertEqual(t, KindMap, m.Kind())
149+
AssertEqual(t, 0, m.Len())
150+
}
151+
152+
func TestReflect_MakeMap_Bad(t *T) {
153+
AssertPanics(t, func() { _ = MakeMap(TypeFor[int]()) })
154+
}
155+
156+
func TestReflect_MakeMap_Ugly(t *T) {
157+
m := MakeMap(TypeFor[map[string]int]())
158+
m.SetMapIndex(ValueOf("k"), ValueOf(9))
159+
AssertEqual(t, 1, m.Len())
160+
}
161+
162+
func TestReflect_MakeMapWithSize_Good(t *T) {
163+
m := MakeMapWithSize(TypeFor[map[string]int](), 16)
164+
AssertEqual(t, 0, m.Len())
165+
}
166+
167+
func TestReflect_MakeMapWithSize_Bad(t *T) {
168+
AssertPanics(t, func() { _ = MakeMapWithSize(TypeFor[int](), 4) })
169+
}
170+
171+
func TestReflect_MakeMapWithSize_Ugly(t *T) {
172+
// A zero size hint is valid; the map is still usable.
173+
m := MakeMapWithSize(TypeFor[map[string]int](), 0)
174+
m.SetMapIndex(ValueOf("k"), ValueOf(1))
175+
AssertEqual(t, 1, m.Len())
176+
}
177+
178+
func TestReflect_CopyValue_Good(t *T) {
179+
src := ValueOf([]int{1, 2, 3})
180+
dst := MakeSlice(TypeFor[[]int](), 3, 3)
181+
n := CopyValue(dst, src)
182+
AssertEqual(t, 3, n)
183+
AssertEqual(t, int64(2), dst.Index(1).Int())
184+
}
185+
186+
func TestReflect_CopyValue_Bad(t *T) {
187+
// Copying into a zero-length destination copies nothing.
188+
src := ValueOf([]int{1, 2, 3})
189+
dst := MakeSlice(TypeFor[[]int](), 0, 0)
190+
AssertEqual(t, 0, CopyValue(dst, src))
191+
}
192+
193+
func TestReflect_CopyValue_Ugly(t *T) {
194+
// Copy stops at the shorter length (dst here).
195+
src := ValueOf([]int{1, 2, 3, 4})
196+
dst := MakeSlice(TypeFor[[]int](), 2, 2)
197+
AssertEqual(t, 2, CopyValue(dst, src))
198+
}
199+
200+
func TestReflect_MakeFunc_Good(t *T) {
201+
// Synthesise func(int) int that doubles its argument.
202+
doubler := MakeFunc(TypeFor[func(int) int](), func(args []Value) []Value {
203+
return []Value{ValueOf(int(args[0].Int()) * 2)}
204+
})
205+
fn := doubler.Interface().(func(int) int)
206+
AssertEqual(t, 10, fn(5))
207+
}
208+
209+
func TestReflect_MakeFunc_Bad(t *T) {
210+
// A non-func type panics.
211+
AssertPanics(t, func() {
212+
_ = MakeFunc(TypeFor[int](), func(args []Value) []Value { return nil })
213+
})
214+
}
215+
216+
func TestReflect_MakeFunc_Ugly(t *T) {
217+
// A no-arg, no-result func is valid and callable.
218+
noop := MakeFunc(TypeFor[func()](), func(args []Value) []Value { return nil })
219+
fn := noop.Interface().(func())
220+
AssertNotPanics(t, fn)
221+
}

0 commit comments

Comments
 (0)