Pine Script v6:UDT 数组切片复制后 push() 为何越界?
结论
按文档语义,suffix.copy() 应创建独立的数组容器,后续调用 push() 不应仅因为它来自切片而发生越界。现有材料不足以确定这次报错的内部原因,也不能据此认定是 UDT 或 switch 的缺陷。
可以先用一个避开切片的方案:创建新数组,逐项放入需要保留的后缀元素,再替换 self.things。这个方案保留对 Thing 对象的引用,也处理了删除全部元素的情况。下面的代码尚未在 TradingView 中运行验证。
slice() 与 copy() 有什么区别?
array.slice() 创建一个关联原数组指定区间的窗口,array.copy() 则创建新的数组容器。对于 array<Thing>,复制后的元素仍引用原来的 Thing 对象,因此两个数组都可能观察到对象字段的修改。单纯追加或删除副本中的元素,不应改变原数组结构。TradingView 数组文档
切片范围有效时,下面的写法符合文档描述:
suffix = self.things.slice(t.pos + 1, self.things.size())
self.things := suffix.copy()
不能仅用“切片共享原数组”解释复制后 push() 的异常,也没有必要为了追加元素而深复制所有 Thing 对象。
为什么删除 switch 后错误会消失?
Pine 编译器会删除不影响脚本输出的代码。如果删除读取 passed 的 switch 后,测试调用不再影响绘图、日志或其他输出,相关计算可能被一起移除。这可以解释“删除后不再报错”的现象,但仍是根据编译机制做出的推断,尚不能确认当前复现中具体发生了哪些优化。TradingView 编译优化说明
排查时可以保留可观察输出,例如绘制数组大小。相比单纯删除 switch,这样更容易判断数组逻辑是否仍在执行。
如何改为显式复制后缀?
以下示例恢复了 Pine 所需的缩进,并用新数组保存后缀,不再创建 prefix 或 suffix 切片:
//@version=6
indicator("Repro - explicit suffix copy")
type Thing
int pos = -1
bool output = false
type Detector
array<Thing> things = na
method detect(Detector self, Thing t) =>
if na(self.things)
self.things := array.new<Thing>()
self.things.push(t)
if t.output
int first = t.pos + 1
int count = self.things.size()
if first < 0 or first > count
runtime.error("Invalid prefix length")
array<Thing> kept = array.new<Thing>()
if first < count
for i = first to count - 1
kept.push(self.things.get(i))
self.things := kept
true
var Detector detector = Detector.new()
Thing thing = switch bar_index
0 => Thing.new(pos=-1, output=false)
1 => Thing.new(pos=0, output=true)
2 => Thing.new(pos=0, output=true)
3 => Thing.new(pos=-1, output=false)
=> na
bool passed = true
if not na(thing)
passed := detector.detect(thing)
result_colour = switch
passed => color.green
=> color.red
bgcolor(result_colour)
plot(detector.things.size(), title="things size")
这里的 first 是追加当前对象后,需要保留的第一个索引:
first == 0:保留全部元素。0 < first < count:只复制后缀。first == count:保留空数组,不进入复制循环。- 超出上述范围:显式报错,避免掩盖无效的
t.pos。
按代码逻辑推算,bar 0、1、2、3 处理结束后的数组大小应依次为 1、1、1、2。这是推算结果,尚未经实测。
原复现还需要检查哪些地方?
原复现中的 prefix 没有被使用,而且在 t.pos == -1 时会构造 slice(0, 0)。文档要求切片起始索引小于结束索引,空区间需要单独处理,不能直接当作正常切片使用。如果业务需要前缀,应在区间非空时再创建它。TradingView 切片边界说明
原来的 switch bar_index 只覆盖 0 到 3,继续运行时需要处理没有匹配项的情况。示例通过 => na 和 if not na(thing) 避免对不存在的 Thing 访问字段。
错误堆栈显示 array.insert(),而源码调用的是 push()。仅凭名称差异,无法确定内部实现或错误根因。修正空区间并保留数组输出后,如果原来的 slice().copy() 路径仍稳定报错,可以对照上述显式复制版本,向 TradingView 提交最小复现,进一步确认是否为编译器或运行时问题。
备注:内容仅供参考。
