Files
reflux/rfx.go
T
what ba0fd4b3bc perf: Keys() 原生实现 map 分支, 字符串键不再绕 fmt.Sprint
Keys() 的 struct 分支早就是原生的了, map 分支一直委托给 refx, 而那边用的是
fmt.Sprint(k.Interface()) —— 要先把 key 装箱成 interface 再走一遍格式化。
对字符串键完全没必要。

  Keys() 3 个字符串键的 map   193.3ns / 9 allocs -> 119.7ns / 5 allocs   1.61x

非字符串键仍旧走 fmt.Sprint: k.String() 对非字符串 Value 返回的是
"<int Value>" 这种占位串而不是键本身, 无脑替换会静默给出错误的键名。
按 m.Type().Key().Kind() 分支处理。

顺带把 normalize() 提到开头, 这样经指针或 interface 拿到的 map/struct 也能
走原生分支, 而不是掉进 slow()。

测试期间发现并修掉一处自己引入的差异: nil map 上旧实现返回 []string{},
我最初加的 IsNil 提前返回给出的是 nil。两者在 reflect.DeepEqual 和 JSON
序列化(`[]` vs `null`)上不同。MapKeys() 对 nil map 本来就返回空切片而不是
panic, 那个提前返回是多余的。

测试覆盖 11 种输入与旧实现对拍(string/具名 string/int/int64/float/bool 键、
空 map、nil map、struct、切片、嵌套 map), 外加经路径取到的 map、
interface 包着的 map、指针指向的 map, 以及非字符串键必须是真实键值的断言。
map 迭代顺序随机, 比较前排序。
2026-08-31 11:14:24 +08:00

1074 lines
28 KiB
Go

package reflux
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strconv"
"unsafe"
"git.fsdpf.net/go/reflux/valuex"
"github.com/spf13/cast"
)
// rfx 是 R 接口的实现。
//
// 它的内部表示只有三个字段(24 字节), 而不是一个 reflect.Value:
// - td: 当前值的类型布局描述符(全局缓存, 不重复构建)
// - ptr: 指向一个 td.rtype 类型值的地址
// - writable: 该地址是否允许写入
//
// 见 unsafeptr.go 顶部的不变式 I1~I4。
type rfx struct {
td *rfxTypeDesc
ptr unsafe.Pointer
writable bool
// ptrRoot 标记"这个 rfx 是由 New(&x) 直接构造的根"。
// reflux 的 rfx.value 在这种情况下存的是**指针**本身, 所以 Raw() 返回
// reflect.Ptr; Get 出来的子值则存字段值。这个标记只为对齐 Raw() 的行为,
// 不参与路径遍历。(放在 writable 后面, 落在原有的对齐填充里, rfx 仍是 24 字节)
ptrRoot bool
}
// 编译期断言: 原型必须完整实现 R, 保证对外 API 签名不变。
var _ R = (*rfx)(nil)
// invalid 表示"路径不存在", 全局共享一份, 命中不存在的路径时不产生分配。
var invalid = &rfx{}
func (v *rfx) valid() bool { return v != nil && v.td != nil && v.ptr != nil }
// ---------------------------------------------------------------------------
// 路径遍历
//
// 遍历过程中的中间状态全部用**值传递**的 rfx(24 字节, 完全落在栈上),
// 只有最终要返回给调用方的那一个才装箱到堆上。这样一次 Get 无论路径多长
// 都只有 1 次分配 —— 而这 1 次是 R 链式 API 形状决定的下限。
// ---------------------------------------------------------------------------
// normalize 把当前值解引用到"实际类型": 穿过指针链, 拆开 interface。
// 与 reflux 的 derefWithAccessor 对应, 同样支持解开 valuex.Accessor。
func (v rfx) normalize() (rfx, bool) {
for {
if v.td == nil || v.ptr == nil {
return rfx{}, false
}
switch v.td.Kind {
case reflect.Ptr:
p := loadPtr(v.ptr)
if p == nil {
return rfx{}, false
}
v.ptr = p
v.td = v.td.Elem
case reflect.Interface:
// interface 没有固定布局可以做偏移量运算, 退回 reflect 拆包
rv := valueAt(v.td, v.ptr)
if rv.IsNil() {
return rfx{}, false
}
// 支持 interface 里装着 valuex.Accessor / R 的情况
if acc, ok := rv.Interface().(valuex.Accessor); ok {
raw := acc.Raw()
if !raw.IsValid() {
return rfx{}, false
}
return fromReflect(raw, v.writable)
}
return fromReflect(rv.Elem(), v.writable)
default:
return v, true
}
}
}
// fromReflect 把一个 reflect.Value 转换成 rfx 表示。
// 不可寻址的值会被拷贝到堆上(boxCopy), 并标记为不可写 —— 与 reflect 里
// "map 元素、interface 拆包结果不可寻址" 的语义一致。
func fromReflect(rv reflect.Value, writable bool) (rfx, bool) {
if !rv.IsValid() {
return rfx{}, false
}
switch {
case rv.Kind() == reflect.Ptr:
if rv.IsNil() {
return rfx{}, false
}
return rfx{
td: rfxDescriptorOf(rv.Type().Elem()),
ptr: rv.UnsafePointer(),
writable: writable,
}.normalize()
case rv.CanAddr():
return rfx{
td: rfxDescriptorOf(rv.Type()),
ptr: addrOf(rv),
writable: writable && rv.CanSet(),
}.normalize()
default:
return rfx{
td: rfxDescriptorOf(rv.Type()),
ptr: boxCopy(rv),
writable: false,
}.normalize()
}
}
// step 沿路径前进一段。
func (v rfx) step(seg string) (rfx, bool) {
cur, ok := v.normalize()
if !ok {
return rfx{}, false
}
switch cur.td.Kind {
case reflect.Struct:
fd := cur.td.lookupField(seg)
if fd == nil {
return rfx{}, false
}
// I3: 未导出字段一律拒绝。reflect.NewAt 出来的 rfx 不带只读标记,
// 放行会让调用方绕过 Go 的导出规则直接读写私有字段。
if !fd.Exported {
return rfx{}, false
}
return rfx{
td: fd.Type,
ptr: fieldAt(cur.ptr, fd.Offset),
writable: cur.writable,
}, true
case reflect.Slice:
idx, ok := parseIndex(seg)
if !ok {
return rfx{}, false
}
p := sliceElemAt(cur.ptr, idx, cur.td.elemSize)
if p == nil {
return rfx{}, false
}
// 切片元素落在底层数组里, 本身总是可寻址的 —— 与 reflect 的
// rfx.Index(i).CanSet() == true 保持一致。
return rfx{td: cur.td.Elem, ptr: p, writable: true}, true
case reflect.Array:
idx, ok := parseIndex(seg)
if !ok {
return rfx{}, false
}
p := arrayElemAt(cur.ptr, idx, cur.td.arrayLen, cur.td.elemSize)
if p == nil {
return rfx{}, false
}
return rfx{td: cur.td.Elem, ptr: p, writable: cur.writable}, true
case reflect.Map:
// map 没有稳定布局, 退回 reflect
m := valueAt(cur.td, cur.ptr)
if m.IsNil() {
return rfx{}, false
}
mv := tryMapFieldValue(m, seg)
if !mv.IsValid() {
return rfx{}, false
}
return fromReflect(mv, false)
default:
return rfx{}, false
}
}
// walk 沿整条路径前进, 全程不分配。
func (v rfx) walk(it pathIter) (rfx, bool) {
cur := v
for {
seg, more := it.next()
if !more {
break
}
var ok bool
cur, ok = cur.step(seg)
if !ok {
return rfx{}, false
}
}
return cur.normalize()
}
// boxed 把栈上的遍历结果装箱成返回给调用方的 *rfx(唯一一次分配)。
func boxed(v rfx, ok bool) R {
if !ok {
return invalid
}
out := v
return &out
}
// ---------------------------------------------------------------------------
// R 接口: 热路径
// ---------------------------------------------------------------------------
// Get 通过路径获取嵌套字段, struct 字段查找走 map。
func (v *rfx) Get(p ...string) R {
if !v.valid() {
return invalid
}
return boxed(v.walk(newPathIter(p)))
}
// 下面五个 xxxValue 是标量直取的**值接收者**辅助方法。
//
// 它们存在的唯一理由是挡住逃逸: 如果直接在 getXxx 里写 (&cur).String(),
// 局部变量 cur 就会逃逸到堆 —— Go 的逃逸分析是流不敏感的, 只要有一条路径
// 把 &cur 交出去, cur 在所有路径上都得堆分配, 快路径那 0 分配立刻退化成 1 次。
// 换成值接收者之后, 逃逸的是本方法的参数副本, 与调用方的 cur 无关。
func (v rfx) stringValue() string {
if v.td != nil && v.ptr != nil && v.td.Kind == reflect.String {
return loadString(v.ptr)
}
return (&v).String()
}
func (v rfx) intValue() int {
if n, ok := v.scalarInt64(); ok {
return int(n)
}
return (&v).Int()
}
func (v rfx) int64Value() int64 {
if n, ok := v.scalarInt64(); ok {
return n
}
return (&v).Int64()
}
func (v rfx) boolValue() bool {
if v.td != nil && v.ptr != nil && v.td.Kind == reflect.Bool {
return loadBool(v.ptr)
}
return (&v).Bool()
}
func (v rfx) float64Value() float64 {
if f, ok := v.scalarFloat64(); ok {
return f
}
return (&v).Float64()
}
// getString 等价于 Get(p...).String(), 但不产生中间的 R 包装对象。
func (v *rfx) getString(p ...string) string {
if !v.valid() {
return ""
}
cur, ok := v.walk(newPathIter(p))
if !ok {
return ""
}
return cur.stringValue()
}
// getInt 等价于 Get(p...).Int(), 但不分配。
func (v *rfx) getInt(p ...string) int {
if !v.valid() {
return 0
}
cur, ok := v.walk(newPathIter(p))
if !ok {
return 0
}
return cur.intValue()
}
// getInt64 等价于 Get(p...).Int64(), 但不分配。
func (v *rfx) getInt64(p ...string) int64 {
if !v.valid() {
return 0
}
cur, ok := v.walk(newPathIter(p))
if !ok {
return 0
}
return cur.int64Value()
}
// getBool 等价于 Get(p...).Bool(), 但不分配。
func (v *rfx) getBool(p ...string) bool {
if !v.valid() {
return false
}
cur, ok := v.walk(newPathIter(p))
if !ok {
return false
}
return cur.boolValue()
}
// getFloat64 等价于 Get(p...).Float64(), 但不分配。
func (v *rfx) getFloat64(p ...string) float64 {
if !v.valid() {
return 0
}
cur, ok := v.walk(newPathIter(p))
if !ok {
return 0
}
return cur.float64Value()
}
func (v *rfx) Set(key string, val any) R {
if !v.valid() {
panic(NewErrInvalidValueForPath(key))
}
parentIt, last, ok := splitLastStr(key)
if !ok {
panic(ErrEmptyPath)
}
parent, ok := v.walk(parentIt)
if !ok || !parent.writable {
// 两种情况交回 reflux 现有实现处理:
// 1. 路径不存在 —— 让它给出与现状一致的错误信息
// 2. 父级不可写 —— 典型是路径穿过了 map: map 元素不可寻址, 取到的是
// 副本, 直接写副本不会反映到原 map 上。reflux 有完整的
// "取出-修改-写回 SetMapIndex" 逻辑, 复用它而不是重新实现一遍。
v.slow().Set(key, val)
return v
}
if err := parent.setField(last, val); err != nil {
panic(NewErrSetFailed(key, err))
}
return v
}
// setField 在 parent 上设置名为 seg 的字段/键/下标。
func (v rfx) setField(seg string, val any) error {
switch v.td.Kind {
case reflect.Struct:
fd := v.td.lookupField(seg)
if fd == nil {
return NewErrFieldNotFoundInStruct(seg, v.td.rtype)
}
// I3 + I4: 未导出字段、或整条访问链本身不可写, 都必须拒绝。
// reflect 路径上这两件事是 CanSet() 免费帮我们做的, unsafe 路径必须手写。
if !fd.Exported {
return NewErrFieldCannotSet(seg, fd.Type.rtype)
}
if !v.writable {
return NewErrFieldCannotSet(seg, fd.Type.rtype)
}
return v.assignField(fd.Type, fieldAt(v.ptr, fd.Offset), seg, val)
case reflect.Slice:
idx, ok := parseIndex(seg)
if !ok {
return NewErrInvalidSliceIndex(seg, fmt.Errorf("不是合法下标"))
}
if idx == -1 {
// reflux 用 -1 表示前插, 要重新分配底层数组, 交给它处理
return v.slowSetField(seg, val)
}
p := sliceElemAt(v.ptr, idx, v.td.elemSize)
if p == nil {
// 与 reflux 一致: 越界是错误, 不会自动扩容。
// 这里直接构造错误而不是委托, 否则错误信息会被包装两层。
return NewErrSliceIndexOutOfRange(idx, sliceLen(v.ptr))
}
return v.assignField(v.td.Elem, p, seg, val)
case reflect.Array:
idx, ok := parseIndex(seg)
if !ok {
return NewErrInvalidArrayIndex(seg, fmt.Errorf("不是合法下标"))
}
p := arrayElemAt(v.ptr, idx, v.td.arrayLen, v.td.elemSize)
if p == nil {
return NewErrArrayIndexOutOfRange(idx, v.td.arrayLen)
}
if !v.writable {
return NewErrArrayElementCannotSet(idx)
}
return v.assignField(v.td.Elem, p, seg, val)
case reflect.Map:
return v.slowSetField(seg, val)
default:
return NewErrUnsupportedTargetType(v.td.Kind)
}
}
// slowSetField 把设置操作交回 reflux 现有实现, 保证语义完全一致。
func (v rfx) slowSetField(seg string, val any) (err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = stripSetFailed(e)
} else {
err = fmt.Errorf("%v", r)
}
}
}()
if !v.writable {
return NewErrCannotSetPath(seg)
}
v.slow().Set(seg, val)
return nil
}
// stripSetFailed 剥掉 reflux 自己加的一层 ErrSetFailed 包装。
//
// reflux 的 Set 失败时 panic 的是 NewErrSetFailed(seg, inner), 我们 recover 之后
// 外层还会再包一次完整路径, 不剥就会得到
// "set failed at path 'Items.10': set failed at path '10': index out of range"
// 这种双层信息, 与现状不一致。
func stripSetFailed(err error) error {
type multi interface{ Unwrap() []error }
if m, ok := err.(multi); ok {
parts := m.Unwrap()
if len(parts) == 2 && errors.Is(parts[0], ErrSetFailed) {
return parts[1]
}
}
return err
}
// assignField 是所有字段赋值的统一入口, 按"能多快就多快, 但语义必须与 reflux 一致"
// 的原则分三级:
//
// 1. 类型完全一致的常见标量 -> 直接按 *T 写内存, 零分配
// 2. 其它标量目标 -> 用 cast 转换(由 conv_equiv_test 全矩阵对拍保证一致)
// 3. 复合目标(指针/切片/结构体/map/interface) -> **交回 reflux**
//
// 第 3 级不能自己实现: reflux 的 setValue 对这些类型有一整套语义 ——
// 指针字段是"设置指针指向的值"而不是替换指针、[]any 会逐元素转成目标切片类型、
// map 可以填充进 struct。重新实现一遍必然出现行为偏差
// (ExampleNew_withPointer 就是这么发现的)。
func (v rfx) assignField(td *rfxTypeDesc, p unsafe.Pointer, seg string, val any) error {
if storeFast(td, p, val) {
return nil
}
// 类型完全匹配: 直接赋值, 不必绕道
if val != nil {
if rv := reflect.ValueOf(val); rv.IsValid() && rv.Type() == td.rtype {
valueAt(td, p).Set(rv)
return nil
}
}
if td.isScalar() {
return assignReflect(valueAt(td, p), val)
}
return v.slowSetField(seg, val)
}
// storeFast 在"字段类型与传入值类型完全一致"时直接按类型写内存, 零分配、零转换。
// 返回 false 表示走不了快路径, 需要回退。
func storeFast(td *rfxTypeDesc, p unsafe.Pointer, val any) bool {
switch td.fast {
case fkString:
if s, ok := val.(string); ok {
storeString(p, s)
return true
}
case fkInt:
if i, ok := val.(int); ok {
storeInt(p, i)
return true
}
case fkBool:
if b, ok := val.(bool); ok {
storeBool(p, b)
return true
}
case fkFloat64:
if f, ok := val.(float64); ok {
storeFloat64(p, f)
return true
}
}
return false
}
// assignReflect 是通用赋值回退路径, 与 reflux setValue 的转换策略一致:
// 可直接赋值 -> 赋值; 可转换 -> 转换后赋值; 否则用 cast 做智能转换。
func assignReflect(dst reflect.Value, val any) error {
if val == nil {
dst.Set(reflect.Zero(dst.Type()))
return nil
}
// 解开 valuex.Accessor / R
if acc, ok := val.(valuex.Accessor); ok {
if raw := acc.Raw(); raw.IsValid() {
val = raw.Interface()
} else {
dst.Set(reflect.Zero(dst.Type()))
return nil
}
}
rv := reflect.ValueOf(val)
dt := dst.Type()
if rv.Type().AssignableTo(dt) {
dst.Set(rv)
return nil
}
switch dt.Kind() {
case reflect.String:
s, err := cast.ToStringE(val)
if err != nil {
return NewErrCannotAssign(rv.Type(), dt)
}
dst.SetString(s)
return nil
case reflect.Bool:
b, err := cast.ToBoolE(val)
if err != nil {
return NewErrCannotAssign(rv.Type(), dt)
}
dst.SetBool(b)
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := cast.ToInt64E(val)
if err != nil {
return NewErrCannotAssign(rv.Type(), dt)
}
if dst.OverflowInt(i) {
return NewErrCannotAssign(rv.Type(), dt)
}
dst.SetInt(i)
return nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
u, err := cast.ToUint64E(val)
if err != nil {
return NewErrCannotAssign(rv.Type(), dt)
}
if dst.OverflowUint(u) {
return NewErrCannotAssign(rv.Type(), dt)
}
dst.SetUint(u)
return nil
case reflect.Float32, reflect.Float64:
f, err := cast.ToFloat64E(val)
if err != nil {
return NewErrCannotAssign(rv.Type(), dt)
}
dst.SetFloat(f)
return nil
}
if rv.Type().ConvertibleTo(dt) {
dst.Set(rv.Convert(dt))
return nil
}
return NewErrCannotAssign(rv.Type(), dt)
}
// Exists 检查路径是否存在。
func (v *rfx) Exists(p ...string) bool {
if !v.valid() {
return false
}
if len(p) == 0 {
// 必须规范化: 值可能是空指针, 或是装着 Nil 的 interface,
// 这些情况下 Exists() 应该为 false。
_, ok := (*v).normalize()
return ok
}
_, ok := v.walk(newPathIter(p))
return ok
}
// ---------------------------------------------------------------------------
// R 接口: 冷路径 —— 直接委托给 reflux 现有实现, 保证行为完全一致
// ---------------------------------------------------------------------------
// slow 把当前值交给基于 reflect 的冷路径实现 refx。
// 传的是 *T 指针, 所以不会发生深拷贝, 写操作落在同一块内存上。
func (v rfx) slow() R {
if v.td == nil || v.ptr == nil {
return Nil
}
// 直接构造 refx: 手上已经是一个合法的 *T 指针, 不需要再走一遍
// New 的类型校验和可能的深拷贝。
return &refx{value: ptrValueAt(v.td, v.ptr)}
}
func (v *rfx) Scope(p ...string) R {
target := v.Get(p...)
tv, ok := target.(*rfx)
if !ok || !tv.valid() {
return Nil
}
return tv.slow().Scope()
}
func (v *rfx) Append(items ...any) R {
v.slow().Append(items...)
return v
}
func (v *rfx) Delete(p ...string) R {
v.slow().Delete(p...)
return v
}
func (v *rfx) Array() []R {
if !v.valid() {
return nil
}
switch v.td.Kind {
case reflect.Slice:
n := sliceLen(v.ptr)
// 批量分配: 一次开出 n 个 rfx 的连续内存, 再取每个元素的地址,
// 把 n+1 次堆分配压成 2 次。元素游标本来就同生共死, 连续存放也更缓存友好。
buf := make([]rfx, n)
out := make([]R, n)
for i := 0; i < n; i++ {
buf[i] = rfx{
td: v.td.Elem,
ptr: sliceElemAt(v.ptr, i, v.td.elemSize),
writable: true,
}
out[i] = &buf[i]
}
return out
case reflect.Array:
n := v.td.arrayLen
out := make([]R, n)
for i := 0; i < n; i++ {
out[i] = &rfx{
td: v.td.Elem,
ptr: arrayElemAt(v.ptr, i, n, v.td.elemSize),
writable: v.writable,
}
}
return out
}
return nil
}
func (v *rfx) Keys() []string {
if !v.valid() {
return nil
}
cur, ok := (*v).normalize()
if !ok {
return nil
}
switch cur.td.Kind {
case reflect.Struct:
out := make([]string, len(cur.td.fields))
for i := range cur.td.fields {
out[i] = cur.td.fields[i].Name
}
return out
case reflect.Map:
// 不必判 IsNil: MapKeys() 对 nil map 返回空切片而不是 panic,
// 于是这里得到 []string{} —— 与旧实现一致。提前返回 nil 会让
// DeepEqual 和 JSON 序列化(`[]` vs `null`)出现差异。
m := valueAt(cur.td, cur.ptr)
keys := m.MapKeys()
out := make([]string, len(keys))
// 字符串键直接取, 不必绕 fmt.Sprint —— 后者要先把 key 装箱成
// interface 再走一遍格式化, 实测 3 个键就差一倍。
// 非字符串键仍旧交给 fmt.Sprint: k.String() 对非字符串 Value 返回的是
// "<int Value>" 这种占位串, 不是键本身。
if m.Type().Key().Kind() == reflect.String {
for i, k := range keys {
out[i] = k.String()
}
} else {
for i, k := range keys {
out[i] = fmt.Sprint(k.Interface())
}
}
return out
}
return v.slow().Keys()
}
func (v *rfx) MarshalJSON() ([]byte, error) {
if !v.valid() {
return []byte("null"), nil
}
return json.Marshal(valueAt(v.td, v.ptr).Interface())
}
func (v *rfx) UnmarshalJSON(data []byte) error {
if !v.valid() {
return ErrInvalidValue
}
if !v.writable {
return NewErrCannotSetPath("")
}
return json.Unmarshal(data, ptrValueAt(v.td, v.ptr).Interface())
}
func (v *rfx) Lookup(path string) (valuex.Accessor, bool) {
got := v.Get(path)
if got.Exists() {
return got, true
}
return valuex.Nil, false
}
func (v *rfx) MustLookup(path string) valuex.Accessor {
got := v.Get(path)
if got.Exists() {
return got
}
return valuex.Nil
}
// ---------------------------------------------------------------------------
// valuex.Accessor: 类型转换
// ---------------------------------------------------------------------------
func (v *rfx) Raw() reflect.Value {
if !v.valid() {
return reflect.Value{}
}
if v.ptrRoot {
// 与 reflux 对齐: New(&x) 的 Raw() 返回指针本身
return ptrValueAt(v.td, v.ptr)
}
return valueAt(v.td, v.ptr)
}
func (v *rfx) Ptr() any {
if !v.valid() {
return nil
}
return ptrValueAt(v.td, v.ptr).Interface()
}
func (v *rfx) Any() any {
if !v.valid() {
return nil
}
cur, ok := (*v).normalize()
if !ok {
return nil
}
return valueAt(cur.td, cur.ptr).Interface()
}
// ---------------------------------------------------------------------------
// 标量直读快路径
//
// 目标类型与字段类型"内存宽度一致"时, 直接按 *T 读内存, 不经过
// Any() -> interface 装箱 -> cast.ToXxxE 这条链路, 零分配。
//
// 语义必须与 reflux(即 spf13/cast)完全一致, 已实测确认:
// - 整数溢出是**静默截断**, 不报错 (cast.ToInt8E(300) == 44, nil)
// - 负数转无符号才报错 (cast.ToUint8E(-1) -> error)
// 因此整数之间用 Go 原生转换即可对齐; 负数转无符号一律回退到 cast, 保证 panic 信息一致。
//
// 浮点字段读成窄整数(Int8/Int16/Int32)不走快路径 —— Go 里超范围的 float->int
// 转换结果是实现定义的, 与 cast 的直接转换未必一致, 这种边角情况交给 cast。
// ---------------------------------------------------------------------------
// scalarInt64 把整数/bool 类型的字段直接读成 int64。
// ok 为 false 表示当前类型没有快路径, 调用方需要回退到 cast。
func (v rfx) scalarInt64() (int64, bool) {
switch v.td.Kind {
case reflect.Int:
return int64(loadInt(v.ptr)), true
case reflect.Int8:
return int64(loadInt8(v.ptr)), true
case reflect.Int16:
return int64(loadInt16(v.ptr)), true
case reflect.Int32:
return int64(loadInt32(v.ptr)), true
case reflect.Int64:
return loadInt64(v.ptr), true
case reflect.Uint:
return int64(loadUint(v.ptr)), true
case reflect.Uint8:
return int64(loadUint8(v.ptr)), true
case reflect.Uint16:
return int64(loadUint16(v.ptr)), true
case reflect.Uint32:
return int64(loadUint32(v.ptr)), true
case reflect.Bool:
if loadBool(v.ptr) {
return 1, true
}
return 0, true
}
return 0, false
}
// scalarUint64 把整数/bool 类型的字段直接读成 uint64。
// 负数一律返回 ok=false 回退到 cast, 这样 panic 信息与 reflux 完全一致。
func (v rfx) scalarUint64() (uint64, bool) {
switch v.td.Kind {
case reflect.Uint:
return uint64(loadUint(v.ptr)), true
case reflect.Uint8:
return uint64(loadUint8(v.ptr)), true
case reflect.Uint16:
return uint64(loadUint16(v.ptr)), true
case reflect.Uint32:
return uint64(loadUint32(v.ptr)), true
case reflect.Uint64:
return loadUint64(v.ptr), true
// 注意: 这里**故意不处理** reflect.Uintptr。
// spf13/cast 不支持 uintptr, reflux 读 uintptr 字段是 panic 的;
// 快路径若把它算进来就会返回数值, 与现状不一致。
// 由 TestFullInterfaceContract 的 "uintptr 字段读 Uint64" 用例钉住。
case reflect.Bool:
if loadBool(v.ptr) {
return 1, true
}
return 0, true
}
if n, ok := v.scalarInt64(); ok && n >= 0 {
return uint64(n), true
}
return 0, false
}
// scalarFloat64 把数值类型的字段直接读成 float64。
func (v rfx) scalarFloat64() (float64, bool) {
switch v.td.Kind {
case reflect.Float64:
return loadFloat64(v.ptr), true
case reflect.Float32:
return float64(loadFloat32(v.ptr)), true
case reflect.Uint64:
return float64(loadUint64(v.ptr)), true
}
if n, ok := v.scalarInt64(); ok {
return float64(n), true
}
return 0, false
}
// isFloatKind 报告字段本身是不是浮点类型。
func (v rfx) isFloatKind() bool {
return v.td.Kind == reflect.Float32 || v.td.Kind == reflect.Float64
}
// String 对 string 类型有零分配快路径: 直接按 *string 读, 不经过
// interface 装箱和 cast。其余类型与 reflux 行为一致。
func (v *rfx) String() string {
if v.valid() {
// 数字/布尔转字符串也走直读 + strconv, 绕开 interface 装箱。
// 格式必须与 cast.ToStringE 完全一致, 由 TestScalarFastPathMatchesReflux
// 的全矩阵对拍保证。
switch v.td.Kind {
case reflect.String:
return loadString(v.ptr)
case reflect.Bool:
return strconv.FormatBool(loadBool(v.ptr))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, _ := (*v).scalarInt64()
return strconv.FormatInt(n, 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64, reflect.Uintptr:
u, _ := (*v).scalarUint64()
return strconv.FormatUint(u, 10)
case reflect.Float64:
return strconv.FormatFloat(loadFloat64(v.ptr), 'f', -1, 64)
case reflect.Float32:
return strconv.FormatFloat(float64(loadFloat32(v.ptr)), 'f', -1, 32)
}
}
data := v.Any()
if data == nil {
return ""
}
result, err := cast.ToStringE(data)
if err != nil {
if b, e := json.Marshal(data); e == nil {
return string(b)
}
panic(fmt.Sprintf("rfx: failed to convert to string: %v", err))
}
return result
}
func (v *rfx) Bool() bool {
if v.valid() && v.td.Kind == reflect.Bool {
return loadBool(v.ptr)
}
result, err := cast.ToBoolE(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to bool: %v", err))
}
return result
}
func (v *rfx) Int() int {
if v.valid() {
if n, ok := (*v).scalarInt64(); ok {
return int(n)
}
}
result, err := cast.ToIntE(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to int: %v", err))
}
return result
}
func (v *rfx) Int64() int64 {
if v.valid() {
if n, ok := (*v).scalarInt64(); ok {
return n
}
}
result, err := cast.ToInt64E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to int64: %v", err))
}
return result
}
// Int32/Int16/Int8 只在源本身是整数/bool 时走快路径。
// 浮点源交给 cast —— 超范围的 float->窄整数 转换在 Go 里是实现定义的。
func (v *rfx) Int32() int32 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarInt64(); ok {
return int32(n)
}
}
result, err := cast.ToInt32E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to int32: %v", err))
}
return result
}
func (v *rfx) Int16() int16 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarInt64(); ok {
return int16(n)
}
}
result, err := cast.ToInt16E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to int16: %v", err))
}
return result
}
func (v *rfx) Int8() int8 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarInt64(); ok {
return int8(n)
}
}
result, err := cast.ToInt8E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to int8: %v", err))
}
return result
}
func (v *rfx) Uint() uint {
if v.valid() {
if n, ok := (*v).scalarUint64(); ok {
return uint(n)
}
}
result, err := cast.ToUintE(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to uint: %v", err))
}
return result
}
func (v *rfx) Uint64() uint64 {
if v.valid() {
if n, ok := (*v).scalarUint64(); ok {
return n
}
}
result, err := cast.ToUint64E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to uint64: %v", err))
}
return result
}
func (v *rfx) Uint32() uint32 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarUint64(); ok {
return uint32(n)
}
}
result, err := cast.ToUint32E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to uint32: %v", err))
}
return result
}
func (v *rfx) Uint16() uint16 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarUint64(); ok {
return uint16(n)
}
}
result, err := cast.ToUint16E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to uint16: %v", err))
}
return result
}
func (v *rfx) Uint8() uint8 {
if v.valid() && !v.isFloatKind() {
if n, ok := (*v).scalarUint64(); ok {
return uint8(n)
}
}
result, err := cast.ToUint8E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to uint8: %v", err))
}
return result
}
func (v *rfx) Float64() float64 {
if v.valid() {
if f, ok := (*v).scalarFloat64(); ok {
return f
}
}
result, err := cast.ToFloat64E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to float64: %v", err))
}
return result
}
func (v *rfx) Float32() float32 {
if v.valid() {
if f, ok := (*v).scalarFloat64(); ok {
return float32(f)
}
}
result, err := cast.ToFloat32E(v.Any())
if err != nil {
panic(fmt.Sprintf("rfx: failed to convert to float32: %v", err))
}
return result
}
func (v *rfx) StringMapString() map[string]string { return v.slow().StringMapString() }
func (v *rfx) StringMapStringSlice() map[string][]string { return v.slow().StringMapStringSlice() }
func (v *rfx) StringMapBool() map[string]bool { return v.slow().StringMapBool() }
func (v *rfx) StringMapInt() map[string]int { return v.slow().StringMapInt() }
func (v *rfx) StringMapInt64() map[string]int64 { return v.slow().StringMapInt64() }
func (v *rfx) StringMap() map[string]any { return v.slow().StringMap() }
func (v *rfx) Slice() []any { return v.slow().Slice() }
func (v *rfx) BoolSlice() []bool { return v.slow().BoolSlice() }
func (v *rfx) StringSlice() []string { return v.slow().StringSlice() }
func (v *rfx) IntSlice() []int { return v.slow().IntSlice() }