fix: 补齐 Get[T] 的类型分支,避免静默返回零值

Get[T] 原本只处理 string/int/int64/bool/float64 五种,其余类型掉进 default
分支的 Any().(T) 断言。而断言**不做转换**,导致:

  Get[int32](r, "Age")            // 字段是 int(42) -> 静默返回 0
  Get[[]string](r, "Tags")        // 字段是 []any   -> 静默返回 nil
  Get[map[string]string](r, "M")  // 字段是 map[string]any -> 静默返回 nil

而对应的访问器 r.Get("Age").Int32() 等返回的是正确的转换结果。
实测 8 个场景里 7 个静默给错数据 —— 不报错不 panic,是最难排查的一类问题。

补齐后 T 覆盖 valuex.Accessor 全部转换方法对应的类型(9 个标量 + 4 种切片
+ 6 种 map),每个分支都落到同名访问器上,转换语义完全一致。
五种快路径类型的零分配特性不受影响(新分支同样用 detachPath 切断逃逸)。

新增 TestGenericMirrorsAccessors: 23 个用例逐个比对 Get[T] 与同名访问器,
并额外断言结果不是零值,防止将来有人删掉分支又掉回 default。
This commit is contained in:
2026-08-28 16:50:43 +08:00
parent a6283ac432
commit 9cb3ea857e
3 changed files with 187 additions and 12 deletions
+94
View File
@@ -6,6 +6,7 @@ package reflux
// 丢弃结果的写法会被编译器优化掉, 测出假的 0 分配。
import (
"fmt"
"testing"
)
@@ -160,3 +161,96 @@ func TestGenericAllocs(t *testing.T) {
}
}
}
// ---------------------------------------------------------------------------
// Get[T] 必须是 R 访问器集合的完整镜像
// ---------------------------------------------------------------------------
type genAllDoc struct {
Age int
Ratio float64
Tags []any
Names []string
Nums []int
Flags []bool
Meta map[string]any
SMeta map[string]string
IMeta map[string]int
I64Map map[string]int64
BMeta map[string]bool
SSMeta map[string][]string
}
func newGenAllDoc() *genAllDoc {
return &genAllDoc{
Age: 42, Ratio: 2.5,
Tags: []any{"a", "b"},
Names: []string{"x", "y"},
Nums: []int{1, 2},
Flags: []bool{true, false},
Meta: map[string]any{"k": "v"},
SMeta: map[string]string{"k": "v"},
IMeta: map[string]int{"k": 1},
I64Map: map[string]int64{"k": 2},
BMeta: map[string]bool{"k": true},
SSMeta: map[string][]string{"k": {"a"}},
}
}
// Get[T] 与同名访问器必须给出相同结果 —— 尤其在"类型不完全匹配、需要转换"时。
//
// 这是补齐类型分支的原因: 少一个分支就会掉进 default 的 Any().(T) 断言,
// 断言不做转换, 于是 Get[int32] 读 int 字段会静默返回 0。
func TestGenericMirrorsAccessors(t *testing.T) {
d := newGenAllDoc()
r := New(d)
cases := []struct {
name string
generic any
accessor any
}{
// 需要转换的标量(字段类型与 T 不一致)
{"int8 <- int", Get[int8](r, "Age"), r.Get("Age").Int8()},
{"int16 <- int", Get[int16](r, "Age"), r.Get("Age").Int16()},
{"int32 <- int", Get[int32](r, "Age"), r.Get("Age").Int32()},
{"uint <- int", Get[uint](r, "Age"), r.Get("Age").Uint()},
{"uint8 <- int", Get[uint8](r, "Age"), r.Get("Age").Uint8()},
{"uint16 <- int", Get[uint16](r, "Age"), r.Get("Age").Uint16()},
{"uint32 <- int", Get[uint32](r, "Age"), r.Get("Age").Uint32()},
{"uint64 <- int", Get[uint64](r, "Age"), r.Get("Age").Uint64()},
{"float32 <- float64", Get[float32](r, "Ratio"), r.Get("Ratio").Float32()},
// 快路径的五种
{"string <- int", Get[string](r, "Age"), r.Get("Age").String()},
{"int <- int", Get[int](r, "Age"), r.Get("Age").Int()},
{"int64 <- int", Get[int64](r, "Age"), r.Get("Age").Int64()},
{"float64 <- float64", Get[float64](r, "Ratio"), r.Get("Ratio").Float64()},
// 切片: 需要逐元素转换
{"[]string <- []any", Get[[]string](r, "Tags"), r.Get("Tags").StringSlice()},
{"[]any <- []string", Get[[]any](r, "Names"), r.Get("Names").Slice()},
{"[]int <- []int", Get[[]int](r, "Nums"), r.Get("Nums").IntSlice()},
{"[]bool <- []bool", Get[[]bool](r, "Flags"), r.Get("Flags").BoolSlice()},
// map: 需要逐值转换
{"map[string]string <- map[string]any", Get[map[string]string](r, "Meta"), r.Get("Meta").StringMapString()},
{"map[string]any <- map[string]string", Get[map[string]any](r, "SMeta"), r.Get("SMeta").StringMap()},
{"map[string]int", Get[map[string]int](r, "IMeta"), r.Get("IMeta").StringMapInt()},
{"map[string]int64", Get[map[string]int64](r, "I64Map"), r.Get("I64Map").StringMapInt64()},
{"map[string]bool", Get[map[string]bool](r, "BMeta"), r.Get("BMeta").StringMapBool()},
{"map[string][]string", Get[map[string][]string](r, "SSMeta"), r.Get("SSMeta").StringMapStringSlice()},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
g := fmt.Sprintf("%v", c.generic)
a := fmt.Sprintf("%v", c.accessor)
if g != a {
t.Fatalf("Get[T] 与访问器不一致\n Get[T] = %s\n 访问器 = %s", g, a)
}
// 顺带守住"没掉进 default 分支"这件事: 上面每一格都是需要转换的场景,
// 掉进 Any().(T) 断言就会得到零值。
if g == "0" || g == "[]" || g == "map[]" {
t.Fatalf("疑似掉进 default 分支返回了零值: %s", g)
}
})
}
}