Skip to content

Data Struct support

Soutaro Matsumoto edited this page Jul 25, 2024 · 2 revisions

Note

Data/Struct support will be available with rbs-inline-0.6.

RBS::Inline defines classes for Data.define and Struct.new calls.

It detects assignments of the result of the calls to constants.

Account = Data.define(
  :id,   #: Integer
  :email #: String
)

Group = Struct.new(
  :accounts #: Array[Account]
)

It generates RBS type definitions for the assignments.

class Account < Data
  attr_reader id(): Integer

  attr_reader email(): String

  def initialize: (Integer id, String email) -> void
                | (id: Integer, email: String) -> void
end

class Group < Strcut[untyped]
  attr_accessor accounts(): Array[Account]

  def initialize: (?Array[Account] accounts) -> void
                | (?accounts: Array[Account]) -> void
end

Notes on Struct.new

keyword_init: keyword

keyword_init: options are detected, but the value must be literals.

  • keyword_init: true: The positional parameters version initializer is not generated.
  • keyword_init: false: The keyword parameters version initializers is not generated.

Specifying something other than true/false literals generates both versions.

Annotations

Struct.new calls detects two special annotations:

# @rbs %a{rbs-inline:new-args=required}
# @rbs %a{rbs-inline:readonly-attributes=true}
Group = Struct.new(
  :accounts #: Array[Account]
)

The rbs-inline:new-args=required generates .new methods with required parameters, while they are implemented as optional parameters.

The rbs-inline:readonly-attributes=true generates the attributes with attr_reader syntax, instead of attr_accessor syntax. This helps making the Struct objects treated as readonly.

Adding methods to Data and Struct

They accepts blocks to define additional methods, but RBS::Inline doesn't support the syntax. If you want to add custom methods, you have to use the class syntax after assigning the value to constants:

Account = Data.define(
  :id,   #: Integer
  :email #: String
)

class Account
  # @rbs (String) -> void
  def send_email(body)
    # Do something
  end
end

Using with Steep

Steep complains with the Data definition:

  • :id is a Symbol, not an Integer
  • Assignment to the constant may be a type error

A workaround is using with __skip__ annotation (or you can use steep:ignore comments.)

Account = __skip__ = Data.define(
  :id,   #: Integer
  :email #: String
)

class Account
  # @rbs (String) -> void
  def send_email(body)
    # Do something
  end
end

Clone this wiki locally