Skip to content

Syntax guide

Soutaro Matsumoto edited this page Aug 29, 2024 · 14 revisions

Magic comment

# rbs_inline: enabled magic comment is required to generate RBS from Ruby code.

# rbs_inline: enabled

class Foo
end

You can also use # rbs_inline: disabled magic comment for --opt-out options.

Defining classes

The class syntax in Ruby is translated to class definition in RBS. Note that the class names and super class names must be constant syntax in Ruby.

class Foo
end

Generic classes can be defined with # @rbs generic annotation.

# @rbs generic A -- Type of `a`
# @rbs generic unchecked in B < String -- Type of `b`
class Foo
end

The class definition generates the following RBS definition:

class Foo[A, unchecked in B < String]
end

The constant super class is supported. Generics is also supported by #[ annotation.

class Foo < String
end

class Bar < Array #[String]
end

# @rbs inherits annotation allows specifying super class with RBS syntax, even if the super class syntax in Ruby is something unsupported.

# @rbs inherits Hash[String, Integer]
class Foo < Hash
end

# @rbs inherits Struct[String | Integer]
class Bar < Struct.new(:size, :name, keyword_init: true)
end

Defining modules

The module syntax in Ruby is translated to a module definition in RBS. Note that the module name must be constant syntax in Ruby.

module Foo
end

Generics is supported with # @rbs generic annotation, like class definition.

Module self type constraints can be defined with # @rbs module-self annotation.

# @rbs module-self BasicObject
module Kernel
end

Blocks defining classes and modules

For method calls with blocks that defines modules/classes implicitly, we introduce @rbs class and @rbs module syntax.

module Foo
  extend ActiveSupport::Concern

  # @rbs module ClassMethods
  class_methods do
    # @rbs () -> Integer
    def foo = 123
  end
end

The @rbs module and @rbs class annotations accepts RBS syntax for opening modules/classes. end is omitted.

Mixins

Mixin method calls with constant syntax -- include, prepend, and extend -- are translated to mixin syntax in RBS.

class Foo
  include Foo

  extend Enumerable #[Integer, void]
end

The #[ syntax allows mixing generic modules.

Method definitions

We have two syntaxes for type of methods.

#: syntax

This is more primitive syntax. It allows writing RBS method type embedded in Ruby code.

#: () -> String
def to_s
end

#: (?Regexp?) -> Enumerator[String, void]
#: (?Regexp?) { (String) -> void } -> void
def each_person(pattern = nil, &block)
end

Having multiple #: syntax generates method definition with overloads.

# @rbs syntax

You can also write method types after # @rbs keyword.

# @rbs () -> String
def to_s
end

# @rbs (?Regexp?) -> Enumerator[String, void]
#    | (?Regexp?) { (String) -> void } -> void
def each_person(pattern = nil, &block)
end

Note that the @rbs METHOD-TYPE syntax allows writing method overloadings with |s.

Doc style syntax

Here is the example of doc style syntax.

# @rbs size: Integer -- The size of the section in px
# @rbs optional: Integer -- Type of the optional parameter
# @rbs title: String -- Title of the section
# @rbs content: String -- Type of the optional keyword parameter
# @rbs *rest: String -- Type of the rest args
# @rbs **kwrest: untyped -- Type of the keyword rest args
# @rbs return: Section? -- Returns the new section or `nil`
def section(size, optional=123, title:, content: "Hello!", *rest, **kwrest)
end

You can write down type of parameters like # @rbs var: TYPE -- some comments. The @rbs return: TYPE defines the return type.

The block types can be defined with # @rbs &block: annotations.

# @rbs &block: (?) -> untyped -- Block is required, but not clear what will be yielded
def foo(&block)
  yield 1
  yield 2, "true"
end

# @rbs &block: ? (?) -> untyped -- Block is optional
def bar(&block)
end

# @rbs &: (String) -> void -- It yields String
def baz
end

The variable names of *, **, and & syntax are optional, as the omission is allowed in Ruby too.

# @rbs *: untyped
# @rbs **: String
# @rbs &: ? (String) -> bool
def foo(*, **) = nil

We also have #: syntax at method definition which allows defining method return type.

def to_s #: String
end

The parse now handles parameter type declarations even with keywords. The only exception is return type, and the return keyword should be escaped as !return for the type of the parameter.

# @rbs yields: String
# @rbs skip: untyped
# @rbs !return: bool
def foo(return:, yields:, skip:) #: void
end

Overriding methods

# @rbs override annotation tells the method is overriding the super class definition.

# @rbs override
def foo(x, y)
end

This generates def foo: .... We need this annotation to avoid having parameters types and the return type untyped, while we don't want to copy and paste the super method definition.

Singleton methods

def self.foo syntax inside a class/module definition is supported.

Other receivers nor singleton class definition syntax <<self are not supported.

RBS annotations

# @rbs %a{} syntax allows giving annotations to methods.

# @rbs %a{pure} %a{another:annotation}
def to_s #: String
end

Attributes

The standard attribute definition methods are supported -- attr_reader, attr_writer, and attr_accessor.

class Foo
  attr_reader :name #: String
  attr_reader :size, :count #: Integer
end

Attribute names defined with symbol literal are supported. One attribute method call can have several names, but all of them will have the same type.

Instance variables

You can use # @rbs @var: Type annotation.

class Foo
  # @rbs @name: String -- Instance variable
  # @rbs self.@all_names: Array[String] -- Instance variable of the class
end

Constant types

Add #: syntax to declare constant of a specific type. The gem infers some simple types for literals.

VERSION = "1.2.3"

Foo = [1,2,3].sample #: Integer

Alias

alias syntax in Ruby code is detected and alias declaration will be generated in RBS.

class Foo
  def ==(other) #: bool
  end

  alias eql? ==
end

Skipping constructs

You may want to # @rbs skip annotation to skip generating RBS definition.

# @rbs skip
class HiddenClass
end

class Foo
  # @rbs skip
  def hidden_method
  end
end

Embedding RBS elements directly

We have # @rbs! annotation for something not covered by the annotations.

class Person
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :id, :integer, default: 0
  attribute :name, :string, default: ''

  # @rbs!
  #   attr_accessor id (): Integer
  #
  #   attr_accessor name (): String
end

Deprecated syntaxes

0.5.0

  • # @rbs returns T is deprecated. Use # @rbs return: T.
  • # @rbs yields: T is deprecated. Use # @rbs &block: T syntax.
  • #:: is deprecated. Use #: syntax.