Ruby sorbet: Difference between revisions

From wikinotes
Line 212: Line 212:
</blockquote><!-- Type Aliases -->
</blockquote><!-- Type Aliases -->


== Copying a Type Signature ==
== Dynamically setting a Type Signature ==
<blockquote>
<blockquote>
<source lang="ruby">
<source lang="ruby">

Revision as of 19:02, 20 June 2023

Implements static typing for ruby.
You may also be interested in ruby tapioca to automatically generate rbi files.

Documentation

homepage https://sorbet.org/
github https://github.com/sorbet/sorbet
gem src (no apidocs) https://github.com/sorbet/sorbet/tree/master/gems
sig documentation https://sorbet.org/docs/sigs

Usage

bundle exec srb tc --help 2>&1 | less -Ri           # show help in less
bundle exec srb tc 2>&1 | grep -C5 ClassICareAbout  # check for errors in specific class

You can also use sorbet as an LSP.

srb tc --lsp

Basics

# person.rb

class Person
  def create(name:, age:, object:); end
  def speak; "foo"; end
end
# person.rbi

class Person
  extend T::Sig       # adds 'sig' method
  extend T::Helpers   # adds 'requires_ancestor, abstract!, interface!, sealed!, ...' methods 

  sig { params(name: ::String, age: ::Integer, object: T.untyped).void }
  def create(name:, age:, object:); end

  sig { returns(String) }
  def speak; "foo"; end
end

Common Cases

attr_reader/writer/accessor

sig { returns(String) }
attr_reader :name

include module

require_ancestors https://sorbet.org/docs/requires-ancestor
mix_in_class_methods https://sorbet.org/docs/abstract

instance methods should just work

module AnimalMethods
  sig { returns(T.void) }
  def walk(); end
end

class Cat
  include AnimalMethods
end

class methods

module AnimalMethods
  extend T::Sig
  extend T::Helpers

  # modify to set classmethods on include
  def self.included(base = nil)
    base&.extend(ClassMethods)
  end

  # define classmethods
  module ClassMethods
    sig { returns(String) }
    def speak(); end
  end

  # inform sorbet of classmethods
  mixes_in_class_methods ClassMethods
end

class Cat
  include AnimalMethods
end

rails concerns && included do

procs/lambdas

sig { params(block: T.proc.params(arg0: Integer).void).void }
def foo(&block)

If your proc is designed to be executed in a different context
(for example, proc is set as a class method, but designed to run on a class instance)
you can use T.bind(self, YourClass).

Types

# stdlib types
String
Symbol
Integer
Float
NilClass

# generic types
T::Array[Integer]
T::Set[Integer]
T::Hash[Symbol, String]
T::Enumerable[Integer]
T::Enumerator[Integer]
T::Range[Integer]

# sorbet types
T::Boolean              # true/false
T.untyped               # anything
T.nilable(String)       # Unions: string or nil
T.any(String, Integer)  # Unions: either string or integer
T.noreturn              # if method never returns (exception, exit process, loop forever, ...)

sig { params(...).void }  # ignore return value, not meant to be used

Generics

# a generic method, that takes param of arbitrary type 'T' and returns type 'T'
sig do
  type_parameters(:T)
    .params(var: T.type_parameter(:T))
    .returns(T.type_parameter(:T))
end
def foo(var)
  var
end

Tools

T.type_alias
T.bind(self, YourClass)              # use within proc, to indicate the instance it should be executed in (defaults to current)
T.cast(var, YourClass)
mixes_in_class_methods ClassMethods  # when class included, ClassMethods become static methods on target class
requires_ancestor Kernel             # any class that includes this module, must also include Kernel

Tricks

Type Aliases

You can define re-usable type-sigs with type aliases.

class University
  extend T::Sig

  StudentMap = T.type_alias { T::Hash[Symbol, String] }
  
  sig { params(student_map: StudentMap).returns(Integer) }
  def count_students(student_map)
    student_map.count
  end

  sig { params(student_map: StudentMap).returns(List[String]) }
  def name_students(student_map)
    student_map.keys()
  end
end

Dynamically setting a Type Signature

class Foo
  sig { params(a: Integer).void }
  def bar(a) = nil
end

T::Private::Methods.declare_sig(Foo, nil, :final) { params(a: String).void }
declaration = T::Private::DeclState.current.active_declaration
T::Private::Methods.run_sig(Foo, :bar, Foo.instance_method(:bar), declaration)

# voila! signature installed
T::Utils.signature_for_instance_method(Foo, :bar)