Scope() 原来是 tv.slow().Scope(), 返回的是 *refx —— 基于 reflect 的慢实现。
功能完全正常, 只是它之后的每一次 Get/Set 都退回逐次反射, 这次重写的收益
在 Scope 之后全部消失。
Scope("A") 之后再 Get 144.6ns / 7 allocs -> 48.7ns / 2 allocs 2.97x
对照: 不经 Scope 直接 Get 59.0ns / 2 allocs
改为自己走 walk 定位、克隆、再包成 *rfx。克隆复用 cloneForValueInput,
与 New(值) 同一套逻辑: 含引用成分才递归深拷贝, 纯值类型逐字节复制。
这类问题很容易在重构里悄悄失守 —— 交回慢实现不会有任何功能异常, 只是慢 3 倍,
测试不看动态类型就发现不了。所以 TestScopeReturnsFastImpl 直接断言返回的是
*rfx, 并验证过有效: 改回 &refx{} 后该用例失败。
顺带确认了其余 slow() 调用点都不会把慢实现泄漏给调用方:
Append/Delete 返回 v 自身, Keys/StringMap*/Slice* 返回的是具体 Go 类型。
另加 Scope 的语义测试(含引用子树 / 纯值子树 / map 子树 / 无参数整体克隆,
都断言原数据未被改写)与 78 个对拍用例。
1134 lines
31 KiB
Go
1134 lines
31 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 沿路径前进一段。
|
|
//
|
|
// 要求 v 已经 normalize 过, 且 Kind 不是 Map —— 两件事都由 walk 保证:
|
|
// map 会被 walk 拦下来改走 walkReflect, 不会到这里。
|
|
func (v rfx) step(seg string) (rfx, bool) {
|
|
cur := v
|
|
|
|
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
|
|
|
|
default:
|
|
// 含 Map —— walk 已经把它拦到 walkReflect 去了, 走不到这里
|
|
return rfx{}, false
|
|
}
|
|
}
|
|
|
|
// walk 沿整条路径前进, 全程不分配。
|
|
func (v rfx) walk(it pathIter) (rfx, bool) {
|
|
cur := v
|
|
for {
|
|
seg, more := it.next()
|
|
if !more {
|
|
break
|
|
}
|
|
norm, ok := cur.normalize()
|
|
if !ok {
|
|
return rfx{}, false
|
|
}
|
|
|
|
// 命中 map: 用 reflect 连续走完**这一段 map**, 只在离开时装箱一次。
|
|
//
|
|
// 两头都要顾: 逐跳走 step 的话每跳都要 boxCopy(map 元素不可寻址,
|
|
// 而 rfx 的表示需要一个地址), 连续多层 map 会累积多次堆拷贝; 但如果
|
|
// 一路 reflect 走到底, map 后面的 struct 跳就丢了偏移量快路径,
|
|
// 退回 FieldByName。所以只在 map 段内用 reflect, 一离开就切回去。
|
|
if norm.td.Kind == reflect.Map {
|
|
rv, pending, exhausted := walkMapRun(valueAt(norm.td, norm.ptr), seg, &it)
|
|
if exhausted {
|
|
return fromReflect(rv, false)
|
|
}
|
|
// 路径还没走完但已经不是 map 了: 装箱一次, 回到快路径继续。
|
|
// pending 是离开 map 段之后的第一跳, walkMapRun 没有消费它。
|
|
cur, ok = fromReflect(rv, false)
|
|
if !ok {
|
|
return rfx{}, false
|
|
}
|
|
if cur, ok = cur.step(pending); !ok {
|
|
return rfx{}, false
|
|
}
|
|
continue
|
|
}
|
|
|
|
cur, ok = norm.step(seg)
|
|
if !ok {
|
|
return rfx{}, false
|
|
}
|
|
}
|
|
return cur.normalize()
|
|
}
|
|
|
|
// walkMapRun 用 reflect 连续走完一段 map: 从 seg 这一跳开始, 只要落点仍是
|
|
// map 就继续吃下一段, 一旦不是 map(或路径走完)就返回。
|
|
//
|
|
// exhausted 为 false 时表示路径还没走完但已经离开 map 段, 此时 pending 是
|
|
// 尚未消费的下一跳; 调用方应当把返回值装箱一次, 切回基于偏移量的快路径 ——
|
|
// map 后面如果跟着 struct, 那些跳仍然值得走快路径。
|
|
//
|
|
// 注意 pending 是**返回**给调用方的, 而不是通过 it 指针回退。
|
|
// 写回(*it = save)会让逃逸分析认为 it 指向的内容可能逃逸, 进而把 Get 的
|
|
// 可变参数切片也判定为逃逸 —— 那会波及所有路径, 连纯 struct 的零分配快路径
|
|
// 都跟着退化成 1 次分配。
|
|
func walkMapRun(v reflect.Value, seg string, it *pathIter) (_ reflect.Value, pending string, exhausted bool) {
|
|
for {
|
|
if !v.IsValid() || v.Kind() != reflect.Map {
|
|
return reflect.Value{}, "", true
|
|
}
|
|
v = derefWithAccessor(tryMapFieldValue(v, seg))
|
|
if !v.IsValid() {
|
|
return reflect.Value{}, "", true
|
|
}
|
|
|
|
next, more := it.next()
|
|
if !more {
|
|
return v, "", true
|
|
}
|
|
if v.Kind() != reflect.Map {
|
|
return v, next, false
|
|
}
|
|
seg = next
|
|
}
|
|
}
|
|
|
|
// 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)}
|
|
}
|
|
|
|
// Scope 返回指定路径的深度克隆,在克隆上的修改不影响原数据。
|
|
//
|
|
// 注意返回的必须是 *rfx 而不是 refx: Scope 的结果通常还要继续 Get/Set,
|
|
// 交回慢实现的话后续操作就全部退回逐次反射了 —— 实测那样比直接 Get 慢 2.5 倍。
|
|
func (v *rfx) Scope(p ...string) R {
|
|
if !v.valid() {
|
|
return Nil
|
|
}
|
|
target, ok := v.walk(newPathIter(p))
|
|
if !ok {
|
|
return Nil
|
|
}
|
|
// cloneForValueInput 与 New(值) 同一套逻辑: 含引用成分才递归深拷贝,
|
|
// 纯值类型逐字节复制就已经独立了。两者都返回指向副本的指针。
|
|
cloned := cloneForValueInput(valueAt(target.td, target.ptr))
|
|
if !cloned.IsValid() {
|
|
return Nil
|
|
}
|
|
return newRfx(cloned)
|
|
}
|
|
|
|
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() }
|