如何用Sorbet为DelegateClass的子类添加类型注解?
为DelegateClass继承的类添加Sorbet类型注解的解决方案
问题背景
需要为一个继承自DelegateClass(SomeClass)的Ruby类添加Sorbet类型注解。Kernel#DelegateClass接收一个类作为参数,返回新类,该类会将方法委托给传入类的实例。
示例代码如下:
class ExtArray < DelegateClass(Array) def find_by_some_complex_condition find { |element| some_complex_condition(element) } end end
直接使用上述写法会触发Sorbet错误:不允许父类为非常量。尝试用别名定义委托类:
DelegatedArray = DelegateClass(Array) class ExtArray < DelegatedArray def find_by_some_complex_condition find { |element| some_complex_condition(element) } end end
同样无法通过检查,因为Sorbet不允许父类为类别名。需要找到符合Sorbet要求的写法,允许移除DelegateClass但必须保留委托功能。
环境信息:
- Ruby 3.0.5
- Sorbet 0.5.10751
- Tapioca 0.11.4
- macOS Ventura 13.3.1
可行解决方案
方法1:手动实现方法委托
放弃DelegateClass,手动将需要的方法委托给内部实例,同时添加完整的类型注解:
# typed: true class ExtArray extend T::Sig sig { params(array: Array[T.untyped]).void } def initialize(array) @array = array end # 委托find方法 sig { params(block: T.proc.params(element: T.untyped).returns(T::Boolean)).returns(T.untyped) } def find(&block) @array.find(&block) end sig { returns(T.untyped) } def find_by_some_complex_condition find { |element| some_complex_condition(element) } end # 按需添加更多委托方法,比如length sig { returns(Integer) } def length @array.length end end def some_complex_condition(element) rand < 0.5 end
这种方式类型清晰,Sorbet能完全识别,但需要手动维护所有需要委托的方法。
方法2:使用Sorbet内置的T::Delegator
Sorbet提供T::Delegator模块,可实现类型安全的自动委托,替代Ruby标准库的DelegateClass:
# typed: true class ExtArray extend T::Sig include T::Delegator sig { params(delegate: Array[T.untyped]).void } def initialize(delegate) @delegate = delegate end sig { returns(T.untyped) } def find_by_some_complex_condition find { |element| some_complex_condition(element) } end end def some_complex_condition(element) rand < 0.5 end
T::Delegator会自动将未定义的方法委托给@delegate实例,Sorbet能正确识别这些委托方法的类型。只需确保初始化方法中设置@delegate变量即可。
方法3:添加类型存根文件
若要保留DelegateClass的写法,可通过Tapioca生成或手动创建类型存根。比如创建sig/delegate.rbi文件:
class DelegatedArray < Array end
之后在代码中使用:
# typed: true DelegatedArray = DelegateClass(Array) class ExtArray < DelegatedArray extend T::Sig sig { returns(T.untyped) } def find_by_some_complex_condition find { |element| some_complex_condition(element) } end end
这种方式需要维护存根文件,适合需保留原有代码结构的场景。
内容的提问来源于stack exchange,提问作者nekketsuuu
相关产品推荐
相关产品推荐

