Skip to content

Latest commit

 

History

History
936 lines (671 loc) · 24.2 KB

error-reference.md

File metadata and controls

936 lines (671 loc) · 24.2 KB
id title sidebar_label
error-reference
Sorbet Error Reference
Error Reference
<style> #missing-doc-for-error-code-box.is-hidden { display: none; } </style>

Heads up: There aren't any docs yet for this error code. If you have suggestions for what would have helped you solve this problem, click the "Edit" button above to contribute! Otherwise, try using the search above to find an answer.

Note: This list is not exhaustive! Some errors are very context dependent and other error codes are not common enough to know how to generally suggest help. Contributions to this list are welcome!

This is one of three docs aimed at helping answer common questions about Sorbet:

  1. Troubleshooting
  2. Frequently Asked Questions
  3. Sorbet Error Reference (this doc)

This page contains tips and tricks for common errors from srb.

1001

Sorbet has crashed. Please report an issue!

1004

Sorbet couldn't find a file.

2001

There was a Ruby syntax error. Sorbet was unable to parse the source code. If you encounter this error but your code is accepted by Ruby itself, this is a bug in our parser; please report an issue to us so we can address it.

The only intentional break with Ruby compatibility is that method names that are keywords have some limitations with multi-line code, as explained in #1993, and should not be reported.

4002

Sorbet requires that every include references a constant literal. For example, this is an error, even in # typed: false files:

module A; end
module B; end

def x
  rand.round == 0 ? A : B
end

class Main
  include x  # error: `include` must be passed a constant literal
end

Non-constant literals make it hard to impossible to determine the complete inheritance hierarchy in a codebase. Sorbet must know the complete inheritance hierarchy of a codebase in order to check that a variable is a valid instance of a type.

It is possible to silence this error, but it should be done with utmost caution, as Sorbet will fail in strange ways and make far less accurate predictions about a codebase. To silence this error, use T.unsafe:

module A; end
module B; end

def x
  rand.round == 0 ? A : B
end

class Main
  T.unsafe(self).include x
end

4010

There are multiple definitions for a method. Having multiple definitions for a method is problematic, because it can make a codebase's behavior dependent on the order in which files are loaded.

The only way to silence this error currently is to mark the offending file as # typed: false.

4011

There are multiple definitions for the same type member within a given class or module.

class Main
  extend T::Generic

  Elem = type_member
  Elem = type_member # error: Duplicate type member
end

You can fix this by removing the second definition of the type member:

class Main
  extend T::Generic

  Elem = type_member # ok
end

4015

This error usually comes when a class or module is dynamically defined and stored into a constant, like this:

A = ...
A::B = 1

where ... is some expression which computes a class or module. Sorbet can't know statically what this ... code does (and for example even if could assume that it's defining a class, Sorbet can't know what methods or constants it has). Therefore, Sorbet does not support this pattern.

5002

This means that the typechecker has been unable to resolve a reference to a constant (e.g., a Ruby class). Most commonly, this indicates that there's a typo.

First, try confirming whether the code runs successfully. Does the code raise an "uninitialized constant" error when run? If so, Sorbet caught a bug! Try finding out why that constant is actually uninitialized.

If it isn't a typo, then there are a few other things to look at. If it looks like the constant is related to a gem, maybe one of these helps:

  • Is it coming from a gem? Sorbet does not look through the gem's source code. Instead, there must be an *.rbi file for this gem. Try finding the *.rbi corresponding to this gem, and searching through it for the constant.

    For more information, see RBI files. If you are at Stripe, please instead see http://go/types/rbi.

  • If the gem was recently updated, its *.rbi might need to be regenerated. Each RBI file has a line at the top which can be copy / pasted to re-generate the file when the underlying gem has changed.

  • When deleting constants, sometimes they are still referenced from an autogenerated *.rbi file. If that's the case, consider deleting the constant or regenerating the file.

Another thing it could be: Sorbet explicitly does not support resolving constants through ancestors (both mixins or superclasses).

Concretely, here's an example of code rejected and accepted by Sorbet:

class Parent
  MY_CONST = 91
end

class Child < Parent; end

Child::MY_CONST    # error
Parent::MY_CONST   # ok

Alternatively, if it's much more preferable to access the constant on the child, we can set up an explicit alias:

class Parent
  MY_CONST = 91
end

class Child < Parent
  MY_CONST = Parent::MY_CONST
end

Child::MY_CONST    # ok
Parent::MY_CONST   # ok

5005

A class or instance variable is defined in the wrong context.

# typed: true
class A
  extend T::Sig

  def foo
    @@class_var = T.let(10, Integer)
    @x = T.let(10, Integer)
  end

end

There are two such errors in the above. In the first, @@class_var is declared outside of the class scope. In the second, @x is declared outside of the initialize method.

For how to fix, see Type Annotations.

5006

An instance variable has been redeclared with another type.

# typed: true
class A
  extend T::Sig

  def initialize
    @x = T.let(10, Integer)
    @x = T.let("x", String)
  end
end

5008

A class was defined as the subclass of a type_alias. It also occurs if a type_alias mixin is used in a class.

# typed: true
A = T.type_alias {Integer}
class B < A; end # error: Superclasses and mixins may not be type aliases

module M; end

AliasModule = T.type_alias {M}
class C
  include AliasModule # error: Superclasses and mixins may not be type aliases
end

5011

A class inherits from itself either directly or through an inheritance chain.

class A < A; end

class B < C; end
class C < B; end

5012

A class was changed to inherit from a different superclass.

class A; end
class B; end

class C < A; end
class C < B; end

5013

A class or instance variable declaration used T.cast when it should use T.let.

class A
  @@x = T.cast(10, Integer)
end

For how to fix, see Type Annotations.

5014

Given code like this:

# typed: true
class Parent
  extend T::Generic
  Foo = type_member
end

class Child < Parent
  extend T::Generic
end

We need to change our code to redeclare the type member in the child class too:

# typed: true
class Parent
  extend T::Generic
  Foo = type_member
end

class Child < Parent
  extend T::Generic
  Foo = type_member
end

The same thing holds for type templates.

5023

Some modules require specific functionality in the receiving class to work. For example Enumerable needs a each method in the target class.

Failing example in sorbet.run:

class Example
  include Enumerable
end

To fix this, implement the required abstract methods in your class to provide the required functionality.

Passing example in sorbet.run:

class Example
  include Enumerable

  def each(&blk)
  end
end

5028

In # typed: strict files, Sorbet requires that all constants are annotated with a T.let.

For how to fix, see Type Annotations.

See also: 6002, 7017.

5034

Sorbet does not support creating normal Ruby constant aliases to type aliases. Once a type alias is created, all subsequent aliases must also be type aliases.

Concretely, this is not allowed:

A = T.type_alias {Integer}
B = A # error: Reassigning a type alias is not allowed

while this is:

A = T.type_alias {Integer}
B = T.type_alias {A}

(Why? This is due to design tradeoffs to enforce stronger internal invariants. Basically, Sorbet can emit more reliable warnings when users declare their intent to create a new type alias.)

5036

See 5014. 5036 is the same error as 5014 but slightly modified to allow more common Ruby idioms to pass by in # typed: true (5036 is only reported in # typed: strict).

5041

Sorbet does not allow inheriting from a class which inherits from T::Struct.

class S < T::Struct
  prop :foo, Integer
end

class Bad < S; end # error

This limitation exists because, in order to generate a static type for initialize for a struct, we need to know all of the props that are declared on this struct. By disallowing inheritance of structs, we can know that all of the props declared on this struct were syntactically present in the class body.

One common situation where inheritance may be desired is when a parent struct declares some common props, and children structs declare their own props.

class Parent < T::Struct
  prop :foo, Integer
end

class ChildOne < Parent # error
  prop :bar, String
end

class ChildTwo < Parent # error
  prop :quz, Symbol
end

We can restructure the code to use composition instead of inheritance.

class Common < T::Struct
  prop :foo, Integer
end

class ChildOne < T::Struct
  prop :common, Common
  prop :bar, String
end

class ChildTwo < T::Struct
  prop :common, Common
  prop :quz, Symbol
end

5047

A class or module tried to inherit, include, or extend a final class or module.

class Final
  extend T::Helpers
  final!
end

class Bad < Final; end # error

5048

A class or module was declared as final, but a method in the class or module was not explicitly declared as final with a final sig.

class C
  extend T::Helpers
  final!

  def no_sig; end # error

  extend T::Sig

  sig {void}
  def non_final_sig; end # error

  sig(:final) {void}
  def final_sig; end # good
end

5054

Use of implementation has been replaced by override.

5057

Static methods (like self.foo) can never be mixed into another class or module. Both include and extend only mix that module's instance methods onto the target class or module. Classes can inherit static methods from their superclass, but only classes (not modules) can be superclasses.

Thus, a static, abstract method on a module is impossible to implement, and thus is a no-op.

module MyMixin
  sig {abstract.void}
  def foo; end

  sig {abstract.void}
  def self.bar; end # error: Static methods in a module cannot be abstract
end

Some alternatives:

  • Use mixes_in_class_methods, which declares to Sorbet that when a module is included, some other module should be extended into the target class. Full documentation here. This is the preferred option.

  • Separate the interface into two modules. Include one and extend the other in all the places where

  • Change the abstract module to an abstract class, and update all downstream references to inherit from this class instead of including the original module.

6002

In # typed: strict files, Sorbet requires that all instance and class variables are annotated with a T.let.

For how to fix, see Type Annotations.

See also: 5028, 7017.

7001

Sorbet does not allow reassigning a variable to a different type within a loop or block. (Note that we model blocks similarly to loops, as in general they may execute 0, 1, or more times). Due to implementation constraints, Sorbet does not permit this behavior.

A prototypical example of code that might trigger this is code that sets a variable to nil, and then updates it if some value is found inside a loop:

found = nil

list.each do |elem|
  found = elem if want?(elem)
end

In most cases, we can fix this error by declaring the type of the loop variable outside the loop using T.let:

# This is a type annotation that explicitly widens the type:
found = T.let(nil, T.nilable(String))

list.each do |elem|
  found = elem if want?(elem)
end

But my variable does not change its type, it is always a Boolean!

In Ruby, there is no Boolean type. Instead, there are FalseClass and TrueClass types, the union of which defines T::Boolean type as a union type.

When Sorbet encounters a variable declaration like x = true, it infers the type of x as TrueClass. An assignment to x later on in the same block such as x = false would imply that the variable is reassigned to a different type (namely, to FalseClass in this case).

For this reason, a loop such as the following triggers an error:

# Declares `found_valid` with type `FalseClass`
found_valid = false

list.each do |elem|
  # Might change the type of `found_valid` to `TrueClass`
  found_valid = true if valid?(elem) # error: Changing the type of a variable in a loop
end

The fix, again, is to use T.let to widen the type to T::Boolean:

# Declare `found_valid` with type `T::Boolean`
found_valid = T.let(false, T::Boolean)

list.each do |elem|
  # Does not change the type of `found_valid`
  found_valid = true if valid?(elem) # ok
end

5056

The generated annotation in method signatures is deprecated.

For alternatives, see Enabling Runtime Checks which talks about how to change the runtime behavior when method signatures encounter a problem.

7002

This is a standard type mismatch. A method's sig declares one type, but the actual value didn't match. For example:

'str' + :sym  # error: Expected `String` but found `Symbol(:"sym")` for argument `arg0`

Even still, sometimes these errors can be rather confusing. Consider using T.reveal_type to pin down the origin of why Sorbet thinks the types are what it says.

Why does Sorbet think this is nil? I just checked that it's not!

That's a great question, and probably the most common question people have when using Sorbet!

It's answered here: Limitations of flow-sensitivity

7003

This error indicates a call to a method we believe does not exist (a la Ruby's NoMethodError exception). Some steps to debug:

  1. Double check that the code actually runs, either in the REPL, in CI, or with manual tests. If the method doesn't actually exist when run, Sorbet caught a bug!

  2. Even if the method exists when run, Sorbet still might report an error because the method won't always be there. For example, maybe the value is nilable, or we have a union of a handful of different types.

  3. Many times, methods are defined dynamically in Ruby. Sorbet cannot see methods defined with define_method. Sorbet also can't see methods defined using Ruby's included + other.extend(self) pattern. For such dynamically defined methods, Sorbet requires *.rbi files which define the method statically.

    See the RBI docs for how to regenerate the *.rbi files.

  4. Sorbet will complain about this code:

    module MyModule; end
    
    sig {params(x: MyModule).void}
    def foo(x)
      x.nil? # error: Method `nil?` does not exist on `MyModule`
    end

    The nil? method is defined on Kernel in Ruby. Kernel is included in Object (which classes default to inheriting from), but not on BasicObject (which classes can optionally inherit from).

    The solution is to include Kernel in our module:

    module MyModule
      include Kernel
    end
  5. Sorbet will complain about this code:

    module MyModule
      def foo; puts 'hello'; end
    end

    The issue is similar to the above: puts is defined on Kernel, which is not necessarily included in our module. For this situation, there are actually two fixes:

    # Option 1: include Kernel
    module MyModule
      include Kernel
    
      def foo; puts 'hello'; end
    end
    # Option 2: Kernel.puts
    module MyModule
      def foo; Kernel.puts 'hello'; end
    end

7004

This error indicates that a method has been called with incorrect parameters. There are a few cases where this can occur:

  • Too many parameters
  • Not enough parameters
  • Trying to pass parameters that don't exist
  • Missing required parameters
  • Positional parameters used when the method expects named parameters, and vice versa
def foo(x: nil); end

foo(1) # error
foo(y: 1) # error
foo(x: 1) # ok
foo() # ok

def bar(x:); end

bar() # error
bar(1) # error
bar(x: 1) # ok

7006

In Sorbet, it is an error to have provably unreachable code. Because Sorbet is sensitive to control flow in a program, Sorbet can not only track what types each variable has within all the branches of a conditional, but also whether any given branch could be executed at all.

Erroring for dead or unreachable code is generally a way to prevent bugs. People don't usually expect that some branch of code is never taken; usually dead code errors come from simple typos or misunderstandings about how Ruby works. In particular: the only two "falsy" values in Ruby are nil and false.

Note if you intend for code to be dead because you've exhausted all the cases and are trying to raise in the default case, use T.absurd to assert that a case analysis is exhaustive. See Exhaustiveness Checking for more information.

Sometimes, dead code errors can be hard to track down. The best way to pinpoint the cause of a dead code error is to wrap variables or expressions in T.reveal_type(...) to validate the assumptions that a piece of code is making. For more troubleshooting tips, see Troubleshooting.

If for whatever reason it's too hard to track down the cause of a dead code error, it's possible to silence it by making a variable or expression "unanalyzable," aka untyped. (When something is untyped, Sorbet will do very limited flow-sensitivity analysis compared to if Sorbet knows the type. To make something unanalyzable, we can wrap it in T.unsafe(...):

x = false

if x
  puts 'hello!' # error: This code is unreachable
end

if T.unsafe(x)
  puts 'hello!' # ok
end

In this (contrived) example, Sorbet knows statically that x is always false and so our puts within the first if is never reachable. On the other hand, Sorbet allows the second if because we've explicitly made x unanalyzable with T.unsafe(...). T.unsafe is one of a handful of escape hatches built into Sorbet.

7014

Sorbet has a special method called T.reveal_type which can be useful for debugging. T.reveal_type(expr) will report an error in the output of srb tc that shows what the static component of Sorbet thinks the result type of expr is.

Making this an error is nice for two reasons:

  • It makes our internal implementation easier 😅 We don't have some special-case messages and then error messages. The only thing Sorbet prints under normal circumstances are error messages.

  • It serves as a reminder to remove T.reveal_type before committing a change. Since it's a proper error, Sorbet will exit with non-zero status until it's removed.

For more information, see Troubleshooting.

Looking for how to assert that an expression has a certain type? Check out Type Assertions.

7017

In typed: strict files, Sorbet requires that all methods are annotated with a sig. In a # typed: true file Sorbet implicitly assumes that definitions without types are T.untyped, but in a # typed: strict file, Sorbet will no longer make this implicit assumption.

You can still add a sig which declares the arguments or return as T.untyped, so # typed: strict does not outright ban T.untyped. The upside is that usage of T.untyped is more explicit, which makes it easier to drive the number of occurrences down. If you're seeing this warning, there's no time like the present to add proper types to your public-facing API (i.e., your top-level constant and method definitions)!

For how to fix, see Method Signatures.

See also: 5028, 6002.

7018

At typed: strong, Sorbet no longer allows T.untyped as the intermediate result of any method call. This effectively means that Sorbet knew the type statically for 100% of calls within a file. This sigil is rarely used—usually the only files that are # typed: strong are RBI files and files with empty class definitions. Most Ruby files that actually do interesting things will have errors in # typed: strong.

7019

Sorbet does not have great support for splats right now.

In general, when considering taking a variable number of arguments, consider instead taking a single argument that's an array instead of a "rest" arg:

# ----- AVOID THIS ----------------------------
sig {params(xs: Integer).void}
def foo(*xs); end

xs = Array.new(3) {|i| i}
foo(*xs)
# ---------------------------------------------

# ----- Do this instead -----------------------

sig {params(ys: T::Array[Integer]).void}
def bar(ys); end

ys = Array.new(3) {|i| i}
bar(ys)
# ---------------------------------------------

If it is not possible to refactor the code, the current work around is to use T.unsafe:

# ----- WORST CASE ----------------------------
# Prefer the solution described above

sig {params(xs: Integer).void}
def foo(*xs); end

xs = Array.new(3) {|i| i}
T.unsafe(self).foo(xs)
# ---------------------------------------------

7021

The method called declares a block parameter that is not T.nilable, but a block was not passed when it was called.

This can be fixed by either passing a block to the method, or changing the method's signature for the block parameter from T.proc... to T.nilable(T.proc...) (and then changing the method to deal with a nilable block parameter).

7023

This error occurs when a method is passed in a Proc object that Sorbet does not know the arity for statically.

One instance where this can happen is when using method, since the arity of method corresponding to the symbol is unknown. This can be fixed by passing in a block with the correct arity:

# typed: strict

extend T::Sig

sig {params(blk: T.proc.params(arg0: String).void).void}
def foo(&blk)
end

# ----- AVOID THIS ----------------------------
foo(&method(:puts))
# ---------------------------------------------

# ----- Do this instead -----------------------
foo do |arg0|
    method(:puts).call(arg0)
end
# ---------------------------------------------

7024

This error occurs when a generic argument is passed in as a block parameter.

In # typed: strict files, using a parameter from a method that does not have a signature will cause this issue to be reported. Adding a signature to the method will fix the issue.

# typed: strict

extend T::Sig

# ----- This will error -----------------------
def foo(&blk)
    proc(&blk)
end
# ---------------------------------------------

# ----- This will not error -------------------
sig {params(blk: T.untyped).returns(T.untyped)}
def bar(&blk)
    proc(&blk)
end
# ---------------------------------------------

7026

Sorbet detected that it was possible for T.absurd to be reached. This usually means that something that was meant to cover all possible cases of a union type did not cover all the cases.

See Exhaustiveness Checking for more information.

<script src="/js/error-reference.js"></script>