Sorbet

Sorbet

  • Get started
  • Docs
  • Try
  • Community
  • GitHub
  • Blog

›Type System

Getting Started

  • Overview
  • Adopting Sorbet
  • Tracking Adoption
  • Quick Reference
  • Visual Studio Code
  • TypeScript ↔ Sorbet

Static & Runtime

  • Gradual Type Checking
  • Enabling Static Checks
  • Enabling Runtime Checks
  • RBI Files
  • CLI Quickstart
  • CLI Reference
  • Runtime Configuration

Troubleshooting

  • Troubleshooting
  • Why type annotations?
  • FAQ
  • Error Reference
  • Unsupported Ruby Features

Type System

  • sig
  • Type Annotations (non-sig)
  • T.let, T.cast, T.must, T.bind
  • Class Types (Integer, String)
  • Arrays & Hashes
  • Nilable Types (T.nilable)
  • Union Types (T.any)
  • Flow-Sensitivity (is_a?, nil?)
  • T.type_alias
  • Exhaustiveness (T.absurd)
  • T::Struct
  • T::Enum
  • T.untyped
  • Blocks, Procs, & Lambdas
  • Abstract Classes & Interfaces
  • Final Methods & Classes
  • Override Checking
  • Sealed Classes
  • T.class_of
  • T.self_type
  • T.noreturn
  • T.anything
  • T.attached_class
  • Intersection Types (T.all)
  • Generics
  • T::NonForcingConstants
  • Banning untyped

Editor Features

  • Language Server (LSP)
  • Server Status
  • LSP & Typed Level
  • Go to Definition
  • Hover
  • Autocompletion
  • Find All References
  • Code Actions
  • Outline & Document Symbols
  • Documentation Comments
  • Suggesting sigs
  • Highlighting untyped
  • sorbet: URIs

Experimental Features

  • Tuples
  • Shapes
  • Overloads
  • Requiring Ancestors
  • RBS Comments
Edit

Class Types

Note: Class types are used to describe values that are instances of a class—these are the most commonly used types. To instead learn about types for class objects themselves, see T.class_of.

Every Ruby class and module doubles as a type in Sorbet. Class types supersede the notion some other languages have of “primitive” types. For example, "abc" is an instance of the String class, and so "abc" has type String. The same goes for many other values in Ruby:

TypeExample value
String"abc"
Symbol:abc
Integer42
Float3.14
NilClassnil

To reiterate: a class type means "any value which is an instance of this class". If x.is_a?(SomeClass) would return true when run, then x has type SomeClass.

We can mention class types directly in a method signature:

sig {returns(Integer)}
def age
  25
end

sig {params(x: Float).returns(String)}
def float_to_string(x)
  x.to_s
end

Booleans

One gotcha is that false is an instance of FalseClass, and true is an instance of TrueClass—there is no Boolean class in Ruby. So to represent the type of booleans in Sorbet, the sorbet-runtime uses type aliases and union types to define a convenient name for "either true or false": T::Boolean.

extend T::Sig

sig {params(new_value: T::Boolean).void}
def set_flag(new_value)
  @flag = new_value
  puts "Set value to #{new_value}"
end

set_flag(true)
set_flag(false)

nil

Note that the class (and type) of nil is NilClass.

There’s a lot to say about nil, so it gets its own doc.

User-defined class types

Everything we’ve seen so far has used classes built into Ruby, but it works the exact same for any classes we define ourselves:

extend T::Sig

class MyClass; end

sig {returns(MyClass)}
def foo
  MyClass.new
end

Inheritance

Ruby is object-oriented, so an instance of a child class is also an instance of the child class’s superclass. To make this more explicit, let’s look at an example:

extend T::Sig

# Set up an inheritance relationship between three classes
class GrandParentClass; end
class ParentClass < GrandParentClass; end
class ChildClass < ParentClass; end

# Takes ParentClass or lower, not GrandParentClass
sig {params(x: ParentClass).void}
def foo(x); end

foo(GrandParentClass.new)  # error
foo(ParentClass.new)       # ok
foo(ChildClass.new)        # ok

Object vs BasicObject

Another note about inheritance in Ruby concerns the distinction between Object and BasicObject. Object is what classes subclass by default, unless they explicitly subclass from BasicObject. Object subclasses from BasicObject. Again, let’s make this clear with an example:

# Some helper methods to play with
sig {params(x: Object).void}
def takes_object(x); end
sig {params(x: BasicObject).void}
def takes_basic_object(x); end

# The one error is because an instance of BasicObject is not an instance of Object
takes_object(Object.new)              # ok
takes_object(BasicObject.new)         # error
takes_basic_object(Object.new)        # ok
takes_basic_object(BasicObject.new)   # ok

# Some classes to play around with
class ObjectChild; end
class BasicObjectChild < BasicObject; end

# The class that explicitly subclasses `BasicObject`
# can't be given to `takes_object`.
takes_object(ObjectChild.new)              # ok
takes_object(BasicObjectChild.new)         # error

Modules

Modules can be used as “class types” in exactly the same way as classes can. For a module, the meaning is not “an instance of this class” but "an instance of a class which includes this module". This is compatible with how x.is_a?(SomeModule) works in Ruby. Here’s an example:

extend T::Sig

module MyModule
  def some_method
    puts 'inside MyModule'
  end
end

class MyClass
  include MyModule
end

sig {params(x: MyModule).void}
def foo(x)
  x.some_method
end

foo(MyClass.new)  # ok; MyClass mixes in MyModule
← T.let, T.cast, T.must, T.bindArrays & Hashes →
  • Booleans
  • nil
  • User-defined class types
  • Inheritance
  • Object vs BasicObject
  • Modules

Get started · Docs · Try · Community · Blog · Twitter