Skip to content

Syntax guide

Soutaro Matsumoto edited this page May 4, 2024 · 7 revisions

Magic comment

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

# rbs_inline: enabled

class Foo
end

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

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.

Doc style syntax

Here is the example of doc style syntax.

# @rbs size: Integer -- The size of the section in px
# @rbs title: String -- Title of the section
# @rbs rest: Array[String] -- Type of rest args must be `Array[T]`
# @rbs kwrest: Hash[Symbol, untyped] -- Type of keyword rest args must be `Hash[K, V]`
# @rbs returns Section? -- Returns the new section or `nil`
def section(size, title: "Hello", *rest, **kwrest)
end

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

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

# @rbs yields -- Block is required, but not clear what will be yielded
def foo
  yield 1
  yield 2, "true"
end

# @rbs yields [optional] -- Block is optional
def bar
end

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

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

def to_s #:: String
end

Note

Overloaded methods nor generic methods cannot be defined with doc-style syntax.

You can escape local variable names conflicting to keywords, like returns, yields, skip, with !.

# @rbs !returns: bool
# @rbs !yields: String
# @rbs !skip: untyped
def foo(returns, yields, skip)
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