ruby Sorbet接口要求实现签名类型等于该类型的祖先而不是后代

3pvhb19x  于 2023-06-22  发布在  Ruby
关注(0)|答案(1)|浏览(88)

下面的示例代码会导致srb错误,并带有注解:参数的类型必须是与超方法上的参数类型相同的超类型。

editor.rb:29: Block parameter _block of type T.proc.params(arg0: T.class_of(Admin::PostAuthAction::PostActionContext)).returns(Admin::PostAuthAction::PostActionContext) not compatible with type of abstract method Lifecycle::PostAuthAction#sync https://srb.help/5035
    29 |    def sync(&_block)
            ^^^^^^^^^^^^^^^^^
    editor.rb:11: The super method parameter _block was declared here with type T.proc.params(arg0: T.class_of(T::Struct)).returns(T::Struct)
    11 |    def sync(&_block); end
            ^^^^^^^^^^^^^^^^^
  Note:
    A parameter's type must be a supertype of the same parameter's type on the super method.
# typed: strict

module Lifecycle
  module PostAuthAction
    extend T::Sig
    extend T::Helpers

    interface!

    sig do
      abstract.params(
        _block: T.proc.params(arg0: T.class_of(T::Struct)).returns(T::Struct)
      ).void
    end
    def sync(&_block); end
  end
end

module Admin
  class PostAuthAction
    include Lifecycle::PostAuthAction
    extend T::Sig

    class PostActionContext < T::Struct
      const :user, Object
    end

    PostActionContextCallback = T.type_alias do
      T.proc.params(arg0: T.class_of(PostActionContext)).returns(PostActionContext)
    end

  
    sig { override.params(_block: PostActionContextCallback).void }
    def sync(&_block)
      context = yield(PostActionContext)
    end
  end
end

我的期望是接口应该定义上限,其中接口的签名期望一个接受类T::Struct并返回T::Struct示例的块。
该实现提供了T::Struct的子类,并导致了此键入错误。相反,接口定义了继承的下限,我只能提供T::Struct的祖先,而不是后代。
怎么了?
如果我添加泛型,我就能够完成预期的LSP(Liskov替换)。下面是上面代码的一个plaground,以及一个使用泛型的解决方案

xn1cxnb4

xn1cxnb41#

Sorbet对原始实现的抱怨是正确的:子类不能重写一个方法来接收比父类更具体的类型。
要了解为什么这是一个问题,请看下面的示例:

class Food; end
class Grass < Food; end
class Meat < Food; end

module Animal
  extend T::Sig
  extend T::Helpers

  interface!

  sig do
    abstract.params(food: Food).void
  end
  def eat(food); end
  end
end

class Elephant
  include Animal

  # Sorbet doesn't allow this, which shows that my modeling is wrong:
  # I can't make `Animal` eat `Food` if then I want some animals to
  # be herbivores!
  sig do
    override.params(food: Grass).void
  end
  def eat(food); end
end

# Now, let's suppose that I ignore the type-checking Sorbet does:

sig {params(animal: Animal, food: Food}
def feed(animal, food)
  animal.eat(food) # Here, I could be giving meat to a non-meat-eater!
end

feed(Elephant.new, Meat.new)

关键是遵循Robustness principle:“保守你所发送的,自由你所接受的”。当处理冰糕签名时,这意味着:

  • 您可以将params声明为类型,或者比父签名中的类型更一般(父)。
  • 您可以将returnsonly 声明为类型,或者比父签名中的类型更具体的类型(子)。

相关问题