Sorbet

Sorbet

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

›Experimental Features

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

Requiring Ancestors

This feature is experimental and might be changed or removed without notice. To enable it pass the --enable-experimental-requires-ancestor option to Sorbet or add it to your sorbet/config.

It’s not uncommon in Ruby to define helper modules that depends on other modules. For example, let’s take the following helper which provides say_error method:

# typed: true

module MyHelper
  def say_error(message)
    raise "InternalError: #{message}" # error: Method `raise` does not exist on `MyHelper`
  end
end

class MyClass
  include MyHelper

  def do_something(x)
    say_error("some error") unless x
    # ...
  end
end

If we run Sorbet on this example, we will get a type-checking error saying that the method raise does not exist on MyHelper since this method is defined on Kernel. Thanks to this error, Sorbet is protecting us against some edge-cases where we would try to include the MyHelper module in a class that does not include Kernel:

class MyBaseClass < BasicObject
  include MyHelper

  def do_something(x)
    say_error("some error") unless x
    # ...
  end
end

MyBaseClass.new.do_something(false) # runtime-error: in `say_error': undefined method `raise' for #<MyBaseClass> (NoMethodError)

This example would raise an error at runtime because the method raise is undefined for instances of MyBaseClass as it doesn’t include Kernel.

Requiring Ancestors

Sorbet provides the requires_ancestor method as a way to ensure that classes or modules including MyHelper will also include Kernel.

Let’s change our base example to use requires_ancestor:

module MyHelper
  extend T::Helpers

  requires_ancestor { Kernel }

  def say_error(message)
    raise "InternalError: #{message}"
  end
end

This way we specify that any module including MyHelper must also include Kernel and Sorbet will display an error if it’s not the case:

class MyBaseClass < BasicObject # error: `MyBaseClass` must include `Kernel` (required by `MyHelper`)
  include MyHelper
end

requires_ancestor also works to require that a specific class must be inherited:

module MyHelper
  extend T::Helpers

  requires_ancestor { Object }

  def class_name
    self.class.name
  end
end

class MyBaseClass < BasicObject # error: `MyBaseClass` must inherit `Object` (required by `MyHelper`)
  include MyHelper
end

Note that requirements are transitive:

class MyBaseClass2 < MyBaseClass # error: `MyBaseClass2` must inherit `Object` (required by `MyHelper`)
  include MyHelper
end

requires_ancestor can be used to require more than one ancestor:

module Test
  module TestAssertions
    def assert_equal(x, y)
      x == y
    end
  end

  class TestBase
    def test; end
  end

  class TestCase < TestBase
    include TestAssertions
  end
end

module MyLogger
  def log_test_failed; end
end

module MyTestHelper
  extend T::Helpers

  requires_ancestor { Test::TestAssertions }
  requires_ancestor { MyLogger }

  def assert_not_equal(x, y)
    if assert_equal(x, y)
      true
    else
      log_test_failed
      false
    end
  end
end

class MyValidTest < Test::TestCase
  include MyTestHelper
  include MyLogger
end

class MyBrokenTest < Test::TestBase # error: `MyBrokenTest` must include `Test::TestAssertions` (required by `MyTestHelper`)
                                    # error: `MyBrokenTest` must include `MyLogger` (required by `MyTestHelper`)
  include MyTestHelper
end

requires_ancestor can also be used to require a singleton class as an ancestor:

module MyHelper
  extend T::Helpers

  requires_ancestor { T.class_of(MyBaseClass) }

  def helper
    my_singleton_method
  end
end

class MyBaseClass
  class << self
    include MyHelper

    def my_singleton_method; end
  end
end
← OverloadsRBS Comments →
  • Requiring Ancestors

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