Sorbet Error Reference
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.
This is one of three docs aimed at helping answer common questions about Sorbet:
This page contains tips and tricks for common errors from srb
.
1001
Sorbet has crashed. Please report an issue!
1003
Sorbet has an internal limitation on how deep a chain of class aliases can be:
A1 = Integer
A2 = A1
# ...
A42 = A41
A43 = A42 # error: Too many alias expansions
It’s meant to guard against cases where Sorbet might get stuck in an infinite loop attempting to dealias these class aliases.
If you encounter this bug in the wild (i.e., not just for a contrived example, but a real-world use case), please share with the Sorbet team. We’d like to see what mode of use triggered this behavior, and either add a test to Sorbet or tweak how Sorbet works to support the use case.
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 the Ruby grammar is that method names that are keywords have some limitations with multi-line code, as explained in Unsupported Ruby Features.
2002
Starting in Ruby 3.0, Ruby uses the _1
, _2
, etc. syntax for block arguments:
xs.map {_1.to_s}
# ^ this is equivalent to:
xs.map { |x| x.to_s }
Because of this, _1
and similar variables are not allowed as normal variable
names. Pick another variable name, for example: _x1
or _arg1
.
2003
If you’re seeing this error code, it means that Sorbet already emitted a parse error for the file, but found some extra information that might help point out the root cause of a syntax error. For example:
class A
def foo
if x
end
end
This Ruby snippet does not parse, but the reason why is confusing. Because Ruby
does not care about indentation, it will try to consune end
keywords eagerly
if there is something available to match. In this example, the first end
matches with if
and the second matches with def
and so Sorbet will report
unexpected token "end of file"
because the class A
definition was not
matched with an end
token.
But given the indentation structure present in the original program, it’s more
likely that the if x
statement is unclosed. Thus, in some cases, Sorbet will
provide extra “Hint:” diagnostics that point out things that might be the root
cause.
It’s important to note that these hints are imperfect—fixing them might not actually fix the real parse error. Instead, they’re provided as a way to help recover from the real parse error.
As with all Sorbet error messages, if one of these hint error messages is confusing or misleading, please report an issue to let us know.
2004
Starting in Ruby 3.0, Ruby uses the _1
, _2
, etc. syntax for block arguments:
xs.map {_1.to_s}
# ^ this is equivalent to:
xs.map { |x| x.to_s }
Because of this, _1
and similar variables are not allowed as normal variable
names. Pick another variable name, for example: _x1
or _arg1
.
3001
Sorbet doesn’t support singleton definitions outside of the class itself:
class << MyClass # error: `class << EXPRESSION` is only supported for `class << self`
def foo
# ...
end
end
The workaround is to move the definition inside the MyClass
class itself:
class MyClass
class << self
def foo
# ...
end
end
end
Sometimes, EXPRESSION
is not a constant literal like in what follows:
class << some_variable
include Foo
end
In this case, it is possible to directly call include
on the the singleton
class of EXPRESSION
, but it should be done with utmost caution, as Sorbet
will not consider the include and provide a less accurate analysis (see also
#4002):
some_variable.singleton_class.include(Foo)
3002
Sorbet is limited to C++ INT_MAX
:
puts 11377327221391349843 + 1 # error: Unsupported integer literal: 11377327221391349843
A possible workaround is to use a string and to_i
:
puts "11377327221391349843".to_i + 1
3003
Sorbet does not support certain Ruby features, like the flip flop operator and
the redo
keyword. If you feel strongly that Sorbet support these features,
please open an issue.
Unfortunately, there is no workaround for this issue other than asking Sorbet to ignore the file (which forces the entire file to be ignored, including any methods and constants defined in it), or rewriting the file to not use the unsupported Ruby feature.
If you do decide to ignore the file entirely, you will likely need to use an RBI file to let Sorbet know about any classes and methods defined in the ignored file.
3004
There was an error parsing a float literal in Sorbet. Specifically, we asked a
C++ library to parse a Ruby float literal string to a float, and it returned a
NaN
value.
If you are seeing this error, it likely represents a bug in Sorbet. Please report an issue at https://github.com/sorbet/sorbet/issues.
3005
Sorbet does not support using operators like &&=
or ||=
when the target of
the operator is a constant literal.
Note that Ruby itself will report a warning for such usage:
A = nil
A ||= 1
# => warning: already initialized constant A
If you absolutely must reassign a constant using the current value of the
constant, rewrite the code to use const_defined?
and const_set
:
A = nil
unless Object.const_defined?(:A) && A
Object.const_set(:A, 1)
end
3007
Using yield
in a method body means that a method implicitly takes a block
argument. In # typed: strict
files, such methods are required to explicitly
name the block argument to make the contract clear:
def foo(&blk)
yield
end
This is required even if the block is only ever used by way of yield
, and
never with something like blk.call
.
Why? The distinguishing factor of # typed: strict
is that every method has
an explicit interface with a signature. It’s equally important to be explicit
about the block argument, if present. For more, see the docs on
strictness levels.
3008
Sorbet does not support the undef
keyword. Sorbet assumes that the set of all
classes, modules, methods, and constants is static throughout the lifetime of a
program. It does not attempt to model the way that a Ruby program might mutate
itself by deleting constants or methods at runtime.
For this reason, the undef
node is not supported.
Currently, this error is only reported at # typed: strict
or higher, though in
the future this might move to lower strictness levels. It does mean that
currently it’s possible to silence this error by downgrading the file containing
the undef
to # typed: true
or lower.
Note that regardless of whether Sorbet reports an error for using undef
, it
has no meaning on what Sorbet considers to be defined or not defined. Sorbet
will not report errors for calls to a method that doesn’t exist because it has
been undefined.
3009
Sorbet does not support certain kinds of complicated block parameter destructuring patterns. In most cases, it is possible to rewrite these to use destructuring assignments inside the block itself:
# ----- BAD -----
xs = [[0, 1, 2, 3], 42]
xs.then do |(x, *args), y|
# ^^^^^ error: Unsupported rest args in destructure
p x # => 0
p args # => [1, 2, 3]
p y # => 42
end
# ----- Use this instead -----
xs.then do |arg0, y|
x, *args = arg0
p x # => 0
p args # => [1, 2, 3]
p y # => 42
end
3010
Methods defined in RBI files are not allowed to have code in their bodies.
The only exception is for instance variable assignments (like @x = ...
), which
are allowed so that RBI files may declare the existence of instance variables
and their types.
3011
There was a Hash literal with duplicated keys.
{my_key: 1, my_key: 2} # error: `my_key` is duplicated
This error can also be caused when trying to write a sig
for a method with
duplicated parameter names:
sig {params(_: String, _: Integer).void} # error: `_` is duplicated
def foo(_, _); end
To write a sig
for this method, rename the method’s parameters to have unique
names:
sig {params(_a: String, _b: Integer).void} # ok
def foo(_a, _b); end
3012
There was an anonymous rest argument defined in the block parameter list.
[1,2,3].each do |*|
T.unsafe(self).p(*)
end
The anonymous rest argument may only refer to method parameters, and uses of it will generate runtime syntax errors if it’s used in the context of a block that defines it. This can be resolved by naming the rest argument parameter in the block:
[1,2,3].each do |*args|
T.unsafe(self).p(*args)
end
3501
Sorbet has special support for understanding Ruby’s attr_reader
,
attr_writer
, and attr_accessor
methods. For this support to work, it must be
able to see the name of the attribute syntactically, which means that the
argument must be a String
or Symbol
literal.
If you are attempting to dynamically compute the name of an attr_*
method, you
must either:
- Downgrade the file to
# typed: false
, or - Hide
attr_*
method call from Sorbet by using something likepublic_send(:attr_reader, name)
(instead ofattr_reader name
)
3502
T::InterfaceWrapper
is deprecated and should not be used.
3503
Ruby has separate syntax for marking instance methods and singleton class methods private:
private def some_instance_method; end
private_class_method def self.some_singleton_class_method; end
Note that the self.
keyword in the method declaration changes the method from
being an instance method to being a class method. In Ruby this self.
prefix is
similar to the static
keyword on method definitions in languages like C++ or
Java.
3504
The signature of an attr_reader
, attr_writer
, or attr_accessor
method
cannot have any T.type_parameter
in it.
These methods get and set instance variables on the underlying class, while the
T.type_parameter
in the signature would only be in method scope.
3505
The module_function
helper declares that an instance method on a module should
be duplicated onto the class’s singleton class.
The module_function
must be given an argument that is syntactically
either:
- a method def
- a
String
orSymbol
literal with the name of a method
This argument must be provided syntactically because Sorbet has special handling
for module_function
, and this special logic must be able to see the exact name
of the method that is being defined via module_function
.
To silence this error, either:
- Refactor the code to use
module_function
with only calls to method definitions or literals, or - Downgrade the file to
# typed: false
or lower, or - Use
send(:module_function, ...)
to hide the call tomodule_function
from Sorbet.
3506
Enums declared with T::Enum
are special. A T::Enum
must have all of its enum
values defined in the enums do
block inside an enum, and it’s not allowed to
have any extra constants defined on the class (i.e., constants that don’t hold
values of the enum).
Why? Consistency, readability, and simplicity of implementation at runtime.
This includes type aliases. If you’d like to define a type alias consisting of a
set of enum values, the type alias must be declared outside of the T::Enum
subclass itself.
Note that enums can still have instance variables and methods defined on them. For example:
class Direction < T::Enum
enums do
Up = new
Down = new
Left = new
Right = new
end
def self.vertical
@vertical ||= [Up, Down].freeze
end
end
3507
The syntax for test_each
looks like this:
test_each([true, false]) do |x|
it 'test' do
end
end
For more examples of valid syntax, see the tests, and another test, with describe.
There are some limitations on how test_each
can be used:
The block given to test_each
must accept at least one argument (except when
using test_each_hash
, it must be able to take exactly two arguments).
The body of the test_each
's block must contain only it
, before
, after
,
and describe
blocks, because of limitations in Sorbet. Sorbet models it
,
before
, and after
blocks by translating them to method definitions under the
hood, and method definitions do not have access to variables outside of their
scope.
Usually this comes up with variable destructuring:
# -- BAD EXAMPLE --
test_each(values) do |value|
x, y = compute_x_y(value)
it 'example 1' do
end
it 'example 2' do
end
end
This can be fixed by worked around by writing the assignment into each it
block directly, or by computing it ahead of time:
test_each(values) do |value|
it 'example 1' do
x, y = compute_x_y(value)
end
it 'example 2' do
x, y = compute_x_y(value)
end
end
new_values = values.map do |value|
x, y = compute_x_y(value)
[value, x, y]
end
test_each(new_values) do |value, x, y|
it 'example 1' do
end
it 'example 2' do
end
end
3508
The argument to the foreign:
attribute on a prop
declaration must be a
lambda function. This prevents the other model class from needing to be loaded
eagerly. Use the autocorrect to fix the error.
3509
The argument to the computed_by
argument on a prop must be a Symbol
literal
(i.e., syntactically, so that Sorbet can analyze it without performing any
inference).
3510
The return type of the initialize
method in Ruby is never used. Under the
hood, a call to new
on a class in Ruby looks something like this:
def self.new(...)
instance = alloc
_discarded = instance.initialize(...)
instance
end
If you’d like to make a custom factory method for your class, define a custom singleton class method.
3511
Accessor methods defined with Struct.new
must not end with =
, because the
generated setter method will then be given an invalid name ending with ==
.
3512
T.nilable(T.untyped)
is just T.untyped
, because nil
is a valid value of
type T.untyped
(along with all other values).
3513
This error code is from an old Sorbet version. It’s equivalent to error 4023:
3514
The has_attached_class!
annotation cannot be given a contravariant :in
annotation because T.attached_class
is only allowed in output positions.
3515
This error indicates that a prop
or const
has already been defined with the
name provided. For example:
class Info < T::Struct
const :name, String
const :age, Integer
prop :age, Float
end
In this case, the :age
prop has been defined twice on lines 3 and 4, and this
error will be raised on the occurrence on line 4.
While these errors exist, the first occurrence of the conflicting name will be used when computing a typed initializer for static typechecking purposes. For the example above, the initializer would look as though it was defined with the following signature:
class Info < T::struct
...
sig {params(name: String, age: Integer).void}
def initialize(name:, age:)
...
end
end
3702
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Package definitions must be formatted like this:
class Opus::Foo < PackageSpec
# ...
end
In this example, Opus::Foo
is the name of the package, and PackageSpec
explicitly declares that this class definition is a package spec.
3703
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Each package must have only one definition. A package definition is the place
where there is a line like class Opus::Foo < PackageSpec
in a __package.rb
file.
3704
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Sorbet found an import
in a __package.rb
file, but the imported constant did
not exist.
3705
TODO This error code is not yet documented.
3706
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
All import
and export
lines in a __package.rb
file must have constant
literals as their argument. Doing arbitrary computation of imports and exports
is not allowed in __package.rb
files.
Also note that all import
declarations must be unique, with no duplicated
imports.
3707
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
__package.rb
files must declare exactly one package.
3709
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
A package cannot import itself. Double check which files and/or packages you intended to modify, as you’ve likely made a typo.
3710
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Even though __package.rb
files use Ruby syntax, they do not allow arbitrary
Ruby code. The fact that they use Ruby syntax is a convenience so that:
- package declarations get syntax highlighting in all Ruby editors
- tooling like Sorbet and RuboCop work on
__package.rb
files out of the box - Sorbet can support things like jump-to-definition inside
__package.rb
files
But despite that, __package.rb
files must be completely statically analyzable,
which means most forms of Ruby expressions are not allowed in these files.
3711
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Package files must be # typed: strict
. If you are in the process of migrating
a codebase to use the packager mode and want to sometimes ignore a
__package.rb
file, use the --ignore=__package.rb
command line flag which
will ignore all files whose name matches __package.rb
exactly, in any folder.
3712
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Package names must not contain underscores. Internally, the packager assumes it
can use the underscore character to join components of a package name together.
For example, internally the package uses names like Opus_Foo
to represent the
package Opus::Foo
. If underscores were allowed in package names,
Opus_Foo_Bar
could represent a package called Opus::Foo_Bar
, Opus_Foo::Bar
or Opus::Foo::Bar
.
3713
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
All code inside a package must live within the namespace declared by it’s
enclosing __package.rb
file. Note that since packages are allowed to nest
inside each other, sometimes you might have attempted to add code in a folder
that you didn’t realize was actually managed by a nested package.
If you’re seeing this error and surprised, double check which folders have
__package.rb
files in them, and the names of the packages declared by them.
3714
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
This error arises when it’s unclear which import actually provides a constant,
which in turn usually happens with nested packages: given a constant A::B::C
and a package that imports both the packages A
and A::B
, it’s difficult to
tell without deep examination which actually exports A::B::C
.
In these cases, it’s best to refactor the packages so they are clearly
delineated: instead of A
and A::B
, it might be best to figure out what
behavior lives in A
but not A::B
and move it to a new package entirely, like
A::X
(and then update the import structure as needed).
This error can also be produced when trying to import a name which is a prefix
of the nested package: for example, importing A
when your package name is
A::B
.
Again, this probably implies you should try to move away from the nested package
structure, and move the contents of A
that aren’t in a nested package into
another less ambiguous package.
3715
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
The export_for_test
directive is used to say, “Make this constant from the
non-test namespace available in the test namespace for the same package.” That
means it only makes sense to export_for_test
constants from the non-test
namespace.
Trying to apply this directive to a constant defined in the Test::
namespace
will result in this error.
If you’re trying to export a constant from the test namespace to be used in
other packages, then just use export
. Otherwise, these lines can be safely
deleted.
3716
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
This error means that you’re exporting a constant redundantly. In Stripe
Packages mode, exporting a constant A::B
will export anything accessible
underneath A::B
. That means that if you try to export A::B
and A::B::C
,
you’ll get this error—the latter export is redundant, as it’s implied by export
A::B
.
To fix this error, simply remove the more specific export.
3717
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Referencing a constant defined in another package that has not been explicitly exported by that package is not allowed.
To fix this error, either change the upstream package to start exporting the constant, or change the code in the current package to depend on only public exports of the upstream package. When changing an upstream package to expose things that were not previously exposed, double check whether it’s intentional that the constant has not been exported.
3718
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
To reference constants defined in other packages, the package must first be imported into the current package.
Fix the error by explicitly import
'ing the upstream package into the
__package.rb
file for the package where this error was reported.
Note that sometimes it is not possible to import another package because doing so would make the dependency graph of packages have a dependency cycle. In these cases, a common solution is to factor the shared functionality to a new package, and import that new package wherever it’s needed. In some situations, there may be simpler ways to restructure the code that don’t involve making a new package.
3720
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
The --stripe-packages
mode draws a distinction between test and non-test code.
For a package called A::B
Some of the constants defined outside of
A::B
but imported intoA::B
are test-only imports (created by writingtest_import
instead ofimport
in a__package.rb
file).To fix this error, if the change is desired, replace
test_import
withimport
for the constant.Some of the constants defined within
A::B
live in the test namespace ofTest::A::B
, meaning that those constants can only be referenced insideT::A::B
, not inside the non-testA::B
namespace.To fix this error, avoid referencing test code from a non-test namespace. This means refactoring where code lives within
A::B
to satisfy the above rule.
3721
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
A package may only export a constant defined within itself.
To re-export a constant defined by another package, make a constant alias to the other constant, and export that:
class A::B
SomeOtherConstant = A::X::SomeOtherConstant
end
# -- a/b/__package.rb --
class A::B < PackageSpec
import A::X
# BAD
export A::X::SomeOtherConstant
# CORRECT
export A::B::SomeOtherConstant
end
Additional signatures of error 3721 include:
- A package exporting a constant only defined in .rbi files. RBI files are shims to enable typechecking in places where Ruby metaprogramming prevents Sorbet from statically interpreting the behavior of a class or module. Generally, these files declare additional methods on classes defined in Ruby source files, and should not define any new constants. However, there are some rare exceptions where these files can define net-new constants. For these cases, we enforce that these constants cannot be exported.
- A package exporting an enum value:
module MyPackage
class A < T::Enum
enums do
Val1 = new
Val2 = new
end
end
end
# -- my_package/__package.rb --
class MyPackage < PackageSpec
export A::Val1 # not allowed, instead the full enum should be exported with `export A`
end
3722
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
Sorbet’s package mode does not currently exporting type aliases. The workaround is to declare the type alias inside a module, and export the containing module instead:
class A::B
BadTypeAlias = T.type_alias {T.any(Integer, String)}
end
class A::B::Types
GoodTypeAlias = T.type_alias {T.any(Integer, String)}
end
class A::B < PackageSpec
export A::B::BadTypeAlias # error
export A::B::Types # OK
# ^ allows referencing `A::B::Types::GoodTypeAlias` in downstream packages
end
3723
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity for more.
The --stripe-packages
mode allows packages to explicitly enumerate which other
packages are allowed to import them by using the visible_to
directive. If a
package uses one or more visible_to
lines, and is imported by a package not
referenced by a visible_to
line, then Sorbet will report an error pointing to
that import.
Often, if you’re running across this error, it means that you’re trying to rely
on an implementation detail that was deliberately made private. However, if
you’re sure that it should be okay to import this package, then you can add an
additional visible_to
directive in order to allow the import you’re trying to
add.
3724
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity and go/strict-dependencies for more.
All packages have a strict_dependencies
level, which controls what
restrictions are applied on imported packages.
class MyPackage < PackageSpec
strict_dependencies 'false'
end
The 4 possible values are:
'false'
: No restrictions are placed on dependencies.'layered'
'layered_dag'
'dag'
3725
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity and go/layers for more.
All packages have a layer
, which is used when checking for layering
violations. See 3726.
class MyPackage < PackageSpec
strict_dependencies 'false'
layer 'library'
end
You can choose the valid layers using the --packager-layers
command line flag.
For example, the following specifies that there are three valid layers: util
,
lib
and app
, ordered lowest to highest.
srb tc --packager-layers util,lib,app
Note that the order matters here. Passing the option as above means that in
layered
(or stricter), packages in the app
layer can import those in util
or lib
, but packages in the util
layer cannot import those in lib
or
app
.
If the flag is passed with no argument, then the default valid layers are
library
and application
.
3726
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity and go/layers for more.
If a package is at strict_dependencies 'layered'
or stricter, all packages it
imports must be in the same or lower layer. For example, given
--packager-layers util,lib,app
, all imports for a package with layer lib
must either also have layer lib
, or have layer util
(but not layer app
).
Note: test_import
s are not checked for layering violations.
3727
This error is specific to Stripe’s custom
--stripe-packages
mode. If you are at Stripe, please see go/modularity and go/strict-dependencies for more.
If a package is at strict_dependencies 'layered'
or stricter, all packages it
imports must also be at strict_dependencies 'layered'
.
If a package is at strict_dependencies 'layered_dag'
or stricter, it cannot be
part of a cycle of dependencies. For example, the following is invalid:
class A < PackageSpec
strict_dependencies 'layered_dag'
import B # error: importing B will put A into a cycle, which is not valid at strict_dependencies level layered_dag
end
class B < PackageSpec
strict_dependencies 'layered'
import A
end
Additionally, if a package is at strict_dependencies 'dag'
, all packages it
imports must also be at strict_dependencies 'dag'
.
Note: test_import
s are not checked for strict dependency violations.
4001
Sorbet parses the syntax of include
and extend
declarations, even in
# typed: false
files. Recall from the
strictness levels docs
that all constants in a Sorbet codebase must resolve, even at # typed: false
.
Parsing include
blocks is required for this, so incorrect usages of include
are reported when encountered.
To fix, ensure that the include
or extend
line is given at least one
argument.
4002
Sorbet requires seeing the complete inheritance hierarchy in a codebase. To do
this, it must be able to statically resolve a class’s superclass and any mixins,
declared with include
or extend
.
To make this possible, Sorbet requires that every superclass, include
, and
extend
references a constant literal. It’s not possible to use an arbitrary
expression (like a method call that produces a class or module) as an ancestor.
This restriction holds even in # typed: false
files.
module A; end
module B; end
def x
rand.round == 0 ? A : B
end
class C
include x # error: `include` must be passed a constant literal
end
(For some intuition why this restriction is in place: Sorbet requires resolving the inheritance hierarchy before it can run inference. Inference is when it assigns types to every expression in the codebase. Therefore inheritance resolution cannot depend on inference, as otherwise there would be a logical cycle in the order Sorbet has to type check a codebase. Similar restrictions appear throughout Sorbet: see Why type annotations? for more examples.)
For module mixins, it is possible to silence this error with T.unsafe
, but it
should be done with utmost caution, as Sorbet will not consider the include
and provide a less accurate analysis:
module A; end
module B; end
def x
rand.round == 0 ? A : B
end
class C
T.unsafe(self).include x
end
Which might create unexpected errors:
c = C.new
c.a # error: Method `a` does not exist on `C`
c.b # error: Method `b` does not exist on `C`
T.let(C, A) # error: Argument does not have asserted type `A`
T.let(C, B) # error: Argument does not have asserted type `B`
There is no such workaround for superclasses.
4003
Sorbet parses the syntax of include
and extend
declarations, even in
# typed: false
files. Recall from the
strictness levels docs
that all constants in a Sorbet codebase must resolve, even at # typed: false
.
Parsing include
blocks is required for this, so incorrect usages of include
are reported when encountered.
To fix, ensure that the include
or extend
line is not given a block.
4006
The super
keyword in Ruby will call the method with the same name on the
nearest ancestor (whether on a mixed-in module or the superclass).
This method only makes sense to call inside the body of a method, not inside a class or file top-level.
4010
When using Sorbet, try to avoid redefining a method. A method is redefined when a method of the same name is defined in the same class (note that Sorbet completely supports overriding methods, where two methods have the same name but one is in a parent class and one in a child class).
If redefining a method is unavoidable, the arity of the new method must match the previous method’s arity exactly. A method’s arity includes how many positional arguments a method has, which keyword arguments it takes, etc.
Determining the arity of the previous method can sometimes be tricky, especially when the previous method was defined dynamically by a DSL. In these cases, the easiest way to determine a method’s arity is to find a place where that method is called, hover over it using Sorbet’s editor integration, and copy the displayed method definition.
It’s worth noting that this error occurs particularly frequently in RBI files, especially autogenerated RBI files. Consider a case like this:
# -- some_gem.rbi --
class SomeGem
def foo(x = nil, y = nil); end
end
# -- autogenerated/some_gem.rbi --
class SomeGem
def foo(*args); end
end
In this case, two RBIs define conflicting definitions for SomeGem#foo
because
the arity of x, y
does not match the arity of *args
. (This frequently
happens because, say, the gem wants to do custom parameter checking so that in
certain cases x
or y
is actually required.) The autogenerated/
RBI file
was generated by using Ruby’s reflection APIs to ask for the arity of the method
as seen by the Ruby VM, while the other RBI was hand-written by the gem
maintainer.
In cases like these, usually the solution is to remove the foo
definition from
autogenerated/some_gem.rbi
, which can usually be accomplished by regenerating
the RBI.
Note: The arity of a method does not include types, only names and kinds of its parameters. For example:
sig {returns(Integer)}
def foo; end
sig {returns(String)}
def foo; end
In this example, both foo
methods have the same arity (they take no arguments,
or are “nullary”), so no error is raised about redefining a method.
But in this case, where their signatures specify different types, the behavior
is unspecified. Sorbet usually takes the types from the “last” signature,
but which signature is “last” is implementation defined when when the two method
definitions happen in two separate files. This includes the case when both a
Ruby source file (*.rb
) and an RBI file (*.rbi
) specify a signature for a
method.
That Sorbet supports method redefinitions, including providing multiple
signatures for a method definition across multiple files, and that this error is
only reported at # typed: true
and above is an accident of history, and
part of the reason why Sorbet strongly discourages using method redefinitions.
One alternative is to mark the original method private
and define a new method
with a new name, instead of redefining the old method. Another alternative is to
use Sorbet’s editor integration to rename the old method, declare
that old method private
, and define a new method with the original name.
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
For more information, see the docs for Generics.
4012
A class
was redefined as a module
or vice versa in two separate locations.
# file_a.rb
class Foo
end
# file_b.rb
module Foo
end
You can fix this error by ensuring that both definitions are declared as
class
es, ensuring both definitions are declared as module
s, or renaming
either definition so they no longer conflict.
4013
The interface!
annotation is reserved for modules. To include abstract methods
in a class, mark the class abstract!
instead.
4014
Sorbet does not support dynamic constant references. All constants must be plain constant literals.
If you are adopting Sorbet in your codebase and get stuck dealing with how to avoid using a particular dynamic constant reference, you might want to ask someone in the Sorbet community whether they have encountered the problem before. Members of the Sorbet community frequently answer questions either on Slack or Stack Overflow.
Why is it this way? Sorbet has a simple architecture which has been chosen to optimize for performance in large codebases. Specifically, Sorbet only knows which method is being called during its inference phase. The inference phase requires that a complete symbol table (listing all classes, all the methods that class owns, and all the methods’ types) has been built already. Thus building this symbol table cannot depend on knowing which methods are defined.
This is also a philosophical belief that Ruby codebases are easier for programmers to understand when constant references are simple.
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.
4016
type_member
and type_template
cannot be used at the top-level of a file.
Instead, they must be used inside a class or module definition.
4019
This error is only reported when running Sorbet with the
--uniquely-defined-behavior
command line flag (formerly called
--stripe-mode
).
A class defines behavior in multiple files when at least two files would need to
be run in order to completely load that class. A class definition that only
serves as a namespace for inner definitions is not considered to have behavior.
For example, in this example module A
has behavior in two files, and thus
produces an error in this mode:
# -- file1.rb --
module A
def foo; end
end
# -- file2.rb --
module A
def bar; end
end
However, in this example, module A
is defined in two files, but the second
definition does not define any behavior (just an empty module body), and thus
does not produce an error:
# -- file1.rb --
module A; end
module A::B
def foo; end
end
# -- file2.rb --
module A; end
module A::B
def bar; end
end
The limitations around what constitutes “defining behavior” is intertwined with
which files would have to be loaded for a class (like A
above) to be fully
loaded. When checking --uniquely-defined-behavior
, it must be possible to
require at most one file to fully load a class. This property is useful to check
in Ruby codebases which use an autoloader, like
Zeitwerk.
What counts as behavior? Basically anything with side effects (any method calls) and any method definitions. It’s easier to describe what doesn’t count: empty class or module definitions, or those class/module definitions which only have constant assignments and/or literals.
4021
The syntax for specifying type bounds when using type_member
and
type_template
has changed. The old syntax looked used a method call with
keyword args:
type_member(fixed: ...)
while the new syntax uses a block that returns a Hash
literal:
type_member {{fixed: ...}}
(Note that any variance annotation like :in
or :out
is still specified as
the first positional argument.)
This new syntax mimics the syntax for T.type_alias
, and shares the same
motivation: generics in Sorbet are completely erased at runtime, so it’s silly
to have to pay the runtime price of computing the runtime types passed to
:fixed
, :upper
, and :lower
. In codebases that heavily autoload constants,
it’s also easy for type_member
definitions to cause constants to be loaded
earlier than they might have been otherwise (potentially introducing load-time
cyclic references).
The first version of Sorbet on RubyGems to support the new syntax is 0.5.9889. It accepts both syntaxes side by side, so you can use it while incrementally migrating your codebase to the new syntax.
This error includes an autocorrect you can run to automatically migrate to the new syntax:
srb tc --isolate-error-code=4021 --autocorrect
4022
Sorbet does not model all Ruby constants using the same internal data structure. Instead, models constants differently depending on how the constant is defined. For example, all three of these constant definitions are treated differently in Sorbet:
class A; end # a class or module constant
B = 1 # a "normal" constant, which Sorbet calls a "static field"
C = type_member # a type member constant, used with generic classes
Sorbet requires that a constant with a given name must have a unique constant “kind” throughout all its appearances in a codebase. For example, even though code like this is valid Ruby code, it’s not allowed in Sorbet:
def method_to_compute_a_Class_object
Class.new
end
MyClass = method_to_compute_a_Class_object()
class MyClass
# ... re-open MyClass to add methods to it ...
def foo; end
end
In the snippet above, Sorbet complains that it can see conflicting definitions
of MyClass
: one as a static field, and one as a class.
Sorbet imposes this limitation for performance—Sorbet does not do method-level type inference until it has a full view of all the constants defined in a program.
At the point where the MyClass = ...
assignment happens, Sorbet must choose
whether to treat MyClass
as a class or module (which allows using it as a
class or interface type, grants it a singleton class which
derives from Module
, lets it own other constants and methods, and more) or
whether to treat it as a “normal” static field constant, which does not allow
any of those things.
To workaround issues like this, if you absolutely must have both a constant assignment and a class definition for a given constant, you can either:
Use
const_set
to hide the constant assignment from Sorbet:def method_to_compute_a_Class_object Class.new end const_set(:MyClass, method_to_compute_a_Class_object()) class MyClass def foo; end end
Factor the code such that the constant assignment is in a file all by itself, and mark that file
# typed: ignore
(possibly also using anRBI
file to declare anything that can’t be factored out of the ignored file but should still be visible to Sorbet).
4023
The has_attached_class!
annotation is only allowed in a Ruby module
, not a
Ruby class
. For more, see the docs: T.attached_class
.
4024
Two keyword arguments to a method definition or block have been given the same name:
def foo(x:, x:)
end
You can resolve this error by giving a different name to one of the conflicting arguments.
5001
Sorbet cannot resolve references to dynamic constants. The common case occurs
when a constant is dynamically referenced through the singleton class of self
:
class MyCachable < Cachable
CACHE_KEY_PREFIX = "my_cachable_"
def cache_key
self.class::CACHE_KEY_PREFIX + identifier
end
end
This code can by made statically analysable by using a singleton method to reference the constant:
class MyCachable < Cachable
def cache_key
self.class.cache_key_prefix + identifier
end
class << self
def cache_key_prefix
"my_cachable_"
end
end
end
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
5003
Sorbet failed to parse a method signature. For more documentation on valid sig
syntax, see Method Signatures.
5004
Sorbet failed to parse a Ruby expression as a valid Sorbet type annotation. Sorbet supports many type annotations—use the sidebar to find relevant docs on the kinds of valid Sorbet type annotations.
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
if RUBY_VERSION < "3.0"
class C < A; end
else
class C < B; end # error: Parent of class C redefined from A to B
end
Sorbet requires that a project have a single, unified inheritance hierarchy. A class cannot sometimes inherit from one class and sometimes inherit from another class depending on code at runtime.
If it’s simply not possible to refactor the codebase so that the given class
always descends from one superclass, the next path forward is to hide all but
one of the superclasses of this class. For example, using a trick like this with
# typed: ignore
files:
# -- main.rb --
class A; end
class B; end
if RUBY_VERSION < "3.0"
require_relative './c_a.rb'
else
require_relative './c_b.rb'
end
class C
# ...
end
# -- c_a.rb --
# typed: ignore
class C < A; end
# -- c_b.rb --
class C < B; end
In this example, Sorbet will think that C
always descends from B
statically,
even though sometimes it may descend from A
.
Another trick to hide the superclass involves using Class.new
to hide the
definition of C
from Sorbet entirely.
Neither of these tricks work when one of the class definitions with a conflicting superclass definition comes from Sorbet’s definitions for standard library classes. For example, this happens when generating an RBI for a newer version of a gem which is also include in Sorbet’s stdlib RBI payload.
In these cases, Sorbet provides the
--suppress-payload-superclass-redefinition-for
command line flag, which
suppresses the superclass redefinition error for that class. For example, if the
class C
above were a class defined in Sorbet’s payload, this would silence the
errors:
srb tc --suppress-payload-superclass-redefinition-for=C
This flag can be repeated once per payload class with a superclass redefinition error.
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.
To instead use T.cast
as a runtime-only type check (that is, neither as a
statically-checked assertion nor as an instance variable declaration), assign
the cast result to an intermediate variable:
class A
x = T.cast(10, Integer)
@@x = x
end
5014
Given code like this:
# typed: true
class Parent
extend T::Generic
X = type_member
end
class Child < Parent
end
We need to change our code to redeclare the type member in the child class too:
# typed: true
class Parent
extend T::Generic
X = type_member
end
class Child < Parent
X = type_member
end
The same thing holds for type templates.
Note that when the ancestor is a module
that has added to a child class using
extend
, the child class will need to use type_template
to redeclare the
module’s type members
# typed: true
module IFoo
extend T::Generic
X = type_member
end
class A
extend T::Generic
extend IFoo
X = type_template
end
The intuition here is similar to how using extend
on an interface requires
implementing its abstract methods as singleton class methods (def self.foo
)
instead of instance methods (def foo
).
For more information, see the docs for Generics.
Why does this apply even to fixed
types? Sorbet requires redeclaring even
fixed
type members and templates. This differs from many other typed,
object-oriented languages that support generic classes. This choice is an
implementation detail which simplifies some logic inside Sorbet, specifically
around responding incrementally to file edits in an editor. It’s not
fundamental to Ruby or the type system semantics Sorbet chooses to implement,
which means that one day we could consider changing Sorbet to support this
(which would require adjusting Sorbet’s algorithm for handling incremental
updates).
For a more technical explanation, see the Sorbet source code.
5015
Variance is a type system concept that controls how generics interact with subtyping.
If a type_member
is declared as covariant (:out
) in a parent class or
module, it must be declared as either covariant or invariant in any children of
that class or module. (A type_member
with no variance annotation is
invariant.)
Similarly if a type_member
is declared as contravariant (:in
) in a parent
class or module, it must be declared as either contravariant or invariant in any
children.
To see why, consider this example:
module Parent
extend T::Generic
X = type_member(:out)
end
module Child
extend T::Generic
include Parent
X = type_member(:in) # error: variance mismatch
end
If Sorbet were to allow this, Sorbet would fail to catch type errors that it
would need to be able to catch. To see why, consider this chain of T.let
statements, all of which have no error:
x1 = Child[T.any(String, Symbol)].new
# `Child::X` is contravariant, so "String <: T.any(String, Symbol)"
# implies "Child[T.any(String, Symbol)] <: Child[String]"
x2 = T.let(x1, Child[String])
# `String` is equivalent to `String`, so Child[String] <: Parent[String]
x3 = T.let(x2, Parent[String])
# `Parent::X` is covariant, so "String <: T.any(String, Integer)"
# implies "Parent[String] <: Parent[T.any(String, Integer)]"
x4 = T.let(x3, Parent[T.any(Integer, String)])
Given the above definitions, with Parent::X
being covariant and Child::X
being contravariant, each subsequent T.let
checks out, with the reasons being
specified.
But that’s a contradiction! We were able to make a conclusion that we know is false. If we had jumped straight from where we started to where we finished, Sorbet would report an error:
y1 = Child[T.any(String, Symbol)].new
T.let(y1, Parent[T.any(Integer, String)])
# ^^ error: Argument does not have asserted type
This is because T.any(Integer, String)
is neither a subtype nor a supertype of
T.any(Symbol, String)
, the types are completely incompatible.
To avoid introducing contradictions like this into the type system, Sorbet requires that the variance on parent and child classes matches.
→ View full example on sorbet.run
5016
Note: more recent versions of Sorbet have eliminated this error–it is now possible to define generic classes with covariant and contravariant type members.
Sorbet does not allow classes to be covariant nor contravariant.
Why? The design of generic classes and interfaces in Sorbet was heavily inspired by the design of C#. This exact question has been answered for C# by Eric Lippert, who worked on the design and implementation of C# for many years.
→ Why isn’t there variance for generic classes?
The answer concludes that the main selling point of having covariant classes is to make immutable generic classes, at the cost of a more complex implementation of generics.
5017
The type_member
and type_template
declarations from a parent class must all
be repeated in the same order in a child class (and before any newly-added type
members belonging only to the child class).
One non-obvious way this error can manifest is via code generation tooling. If a
tool attempts to generate type_member
declarations using runtime reflection,
but does them out of order, that will confuse Sorbet. Ask the owner of the code
generator for help resolving the problem.
5018
A child class defined a normal constant with a name that was already in use as a
type_member
in the parent class. For example:
class Parent
extend T::Generic
X = type_member
end
class Child < Parent
X = 0 # error!
end
In this example, since X
is declared as a type_member
in the parent, it must
also be declared as a type_member
in the child, and cannot be changed to some
other kind of constant in the child.
5019
Abstract methods must not have bodies. For more information, see
Abstract Classes and Interfaces. Despite these methods not having
a body, Sorbet’s runtime support via the sorbet-runtime
gem will
convert these methods to raise. For example:
module IService
extend T::Sig
extend T::Helpers
interface!
sig {abstract.returns(String)}
def port; end
end
class MyService
include IService
# For sake of example, let's "forget" to implement `port`
# (Sorbet would report a static error here)
end
MyService.new.port # raises an exception at runtime:
# example.rb:16:
# The method `port` on `IService` is declared as `abstract`.
# It does not have an implementation.
So you do not need to manually insert a raise
inside the body of an abstract
method.
If you would like to define a method in an abstract class or interface with a
default implementation, use the overridable
annotation on the signature:
class AbstractService
extend T::Sig
# Default port of 8080, but can be overridden in the child class.
sig {overridable.returns(String)}
def port; '8080'; end
end
5020
There are a few constraints around how methods like include
, extend
,
mixes_in_class_methods
(docs), and requires_ancestor
(docs) work.
include
andextend
must be given a constant that Sorbet can statically see resolve to amodule
. If a codebase is using metaprogramming to define constants that are later mixed with these methods, the codebase must either:- Ensure that all relevant
include
andextend
targets are statically defined in*.rb
or*.rbi
files that are not being ignored, or - Hide the
include
orextend
invocations from Sorbet by using# typed: ignore
sigils to ignore the entire file, or - Hide only the individual call from Sorbet statically using something like
send(:include, ...)
.
- Ensure that all relevant
mixes_in_class_methods
andrequires_ancestor
must only be declared in amodule
, not aclass
. Classes are never mixed into other classes or modules, so these methods would have no meaning in a class.
5021
Sorbet requires that classes or modules which define abstract
methods are also
marked as abstract using either abstract!
or interface!
.
For more information, see Abstract Classes and Interfaces.
5022
Sorbet requires that modules marked interface!
must only have abstract
methods. To have a module with some abstract methods and some implemented
methods, use abstract!
in the module instead.
Apart from this error message, there is otherwise essentially no difference
between abstract modules and interface modules. interface!
is provided mostly
as a way for the author of a piece of code to declare intent to the reader more
than anything else.
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
5024
This error usually happens when attempting to define a cyclic type alias:
X = T.type_alias {X} # error: Unable to resolve right hand side of type alias
Note that Sorbet does not support making recursive type aliases. To make a recursively-defined data structure, see Approximating algebraic data types.
5025
Note: This error has been relaxed in newer Sorbet versions. It has been replaced by 5075.
Sorbet does not currently support type aliases in generic classes.
This limitation was crafted early in the development of Sorbet to entirely sidestep problems that can arise in the design of a type system leading to unsoundness. (Sorbet users curious about type system design may wish to read What is Type Projection in Scala, and Why is it Unsound?.)
As a workaround, define type aliases somewhere else. Unfortunately this does mean it is not currently possible to define type aliases that reference type members defined by a generic class.
5026
Various classes in the standard library have been defined as generic classes
retroactively, not via Sorbet’s built-in support for generics. Sorbet’s usual
mechanism for defining custom generic classes is to define the []
method on
the generic class’s singleton class, so that syntax like
MyGenericClass[Integer]
is valid code at runtime.
However for various stdlib generic classes, this syntax is already taken. For example:
# BAD EXAMPLE
Array[Integer]
# => evaluates at runtime to `[Integer]`
# (i.e. a list with one element: the `Integer` class object)
To use these standard library classes in type annotations, prefix the generic
class’s name with T::
, like this:
sig {returns(T::Array[Integer])}
def foo; [0]; end
For more information, see Arrays, Hashes, and Generics in the Standard Library.
5027
This error is opt-in, behind the
--check-out-of-order-constant-references
flag.Sorbet does not check this by default because certain codebases make clever usage of Ruby’s
autoload
mechanism to allow all constants to be referenced before their definitions.
This error fires when a constant is referenced before it is defined.
puts X # error: `X` referenced before it is defined
X = 1
module Foo
A = X
# ^ error: `Foo::X` referenced before it is defined
class X; end
end
Generally, Sorbet is not opinionated about definition-reference ordering. It assumes files are required in the correct order or at the correct times to ensure that definitions are available before they’re referenced.
However, if a constant is defined in a single file, Sorbet can detect when it’s been referenced in that file ahead of its definition (because in the single-file case, it doesn’t matter whether or in what order any require statements happen). There are some limitations:
Load-time scope must be established definitively
Sorbet has to prove definitively that a given constant is accessed out-of-order at load time. It cannot track accesses across function calls or blocks, meaning that the following code, while technically unloadable, will not throw a Sorbet error.
module Foo
def bar(&blk)
yield
end
bar do
A = X # this will not report an error
end
class X; end
Symbols have to be guaranteed to exist only in one file
In the above example, if Foo::X
is also declared in another file, the error
will not fire. In such cases, the other file that defines X
may get required
first, so Sorbet cannot prove that there will be a problem referencing X
in
this file.
Ways to fix the error include:
- Re-ordering the constant access below the declaration.
- In the case of classes, adding an empty pre-declaration before the access.
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, 7027, 7043.
5030
A constant cannot store a reference to itself, like this:
X = X
5031
Constants are not allowed to resolve through type aliases. For example, this is an error:
class A
X = 0
end
AliasToA = T.type_alias {A}
AliasToA::X # error: not allowed
Why? A number of reasons:
Type aliases do not always store references to plain classes. For example, sometimes type aliases store references to types like
T.nilable(A)
. Sorbet doesn’t allow resolving constants through type aliases for the same reason that it doesn’t allow writingT.nilable(A)::X
.The runtime representation of type aliases do not behave like constants at runtime, so
AliasToA::X
would not actually resolve toA::X
at runtime.
If the type alias is merely an alternate name for an existing constant, use a class alias not a type alias:
AliasToA = A
AliasToA::A # allowed
5032
See the docs for error code 5020.
5033
Final methods cannot be overridden by definition. See Final Methods, Classes, and Modules for more information.
The error code 5033 is raised only statically, but it is worth noting that
attempting to the override of a final method statically but still have the
method overridden at runtime will not work—Sorbet’s runtime support for final
methods prevents methods from being overridden at runtime as well. This is
intentional. Final methods cannot even be overridden or redefined by mocks or
stubs in a test suite.
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.)
5035
There are two cases when Sorbet might produce this error code:
A method was marked
override
, but sorbet was unable to find a method in the class’s ancestors that would be overridden. Ensure that the method being overridden exists in the ancestors of the class defining theoverride
method, or removeoverride
from the signature that’s raising the error.If the parent method definitely exists at runtime, it might be hidden in a
# typed: ignore
file, or defined via metaprogramming. To fix, either raise thetyped
sigil of that file aboveignore
, or generate an RBI file that contains signatures for the classes and methods that file defines.Sorbet found the method that this method overrides, but the override method is not valid. An override method must accept at least as many arguments as the parent method, with types at least as wide as the parent’s types, and return a value that is at most as wide as the parent’s return type. This is in keeping with the Liskov substitution principle.
See Override Checking for more.
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
).
5037
Sorbet must be able to statically resolve a method to create an alias to it.
Here, the method is created through a DSL called data_accessor
which defines
methods at runtime through meta-programming:
class Base
def self.data_accessor(key)
define_method(key) do
data[key]
end
end
# ...
end
class Foo < Base
data_accessor :foo
alias_method :bar, :foo # error: Can't make method alias from `bar` to non existing method `foo`
end
One way to make those methods visible statically is to add a declaration for
them in an RBI file. For example, we can write
our definitions as RBI under sorbet/rbi/shims/foo.rbi
:
# sorbet/rbi/shims/foo.rbi
# typed: true
class Foo
def foo; end
end
Sometimes, Sorbet will complain about an alias to a method coming from an
included modules. For example, here bar
is coming from the inclusion of Bar
but Sorbet will complain about the method not existing anyway:
module Bar
def bar; end
end
class Foo
include Bar
alias_method :foo, :bar # error: Can't make method alias from `foo` to non existing method `bar`
end
It’s because Sorbet resolves method aliases before it resolves includes. You can
see an example of this behaviour
here.
To workaround this limitation, we can replace the alias_method
by a real
method definition:
class Foo
include Bar
def foo
bar
end
end
5038
When Sorbet detects that a file is using sig
syntax for methods, it requires
an explicit # typed:
sigil. The default # typed:
sigil in Sorbet is
# typed: false
, so if you would like to add signatures in a method but change
nothing else about how Sorbet checks your codebase, add # typed: false
to the
top of the file.
However, note that Sorbet will not check the method body’s implementation
against the signature at # typed: false
. When you see this error, it’s
strongly recommended that you additionally upgrade the file to at least
# typed: true
.
Regardless of whether the file where this error was reported is # typed: false
or # typed: true
, all other # typed: true
files in the codebase where Sorbet
detects a call to this method will still be checked against this method’s
signature.
To summarize:
# -- a.rb --
# typed: false
class A
extend T::Sig
sig {params(x: Integer).returns(String)}
def int_to_string(x)
x # no static error!
# (this file is `# typed: false`)
end
end
# -- b.rb --
# typed: true
a = A.new
res = a.int_to_string('not an int') # error: Expected `Integer` but got `String`
res + 1 # error: Expected `String` but found `Integer`
For more information on these typedness levels, see Enabling Static Checks.
5039
Sorbet relies on running inference in a method to be able to reveal the type of
an expression with T.reveal_type
. For performance reasons, Sorbet skips
running inference in any file that is # typed: false
, because none of the
other inference-related errors will be reported at that typedness level.
To use T.reveal_type
in a file, upgrade the file to # typed: true
or above.
For more information on typedness levels, see
Enabling Static Checks.
For more information on T.reveal_type
, see Troubleshooting.
5040
Overloading a method (by providing multiple signatures for the method) is only allowed for methods defined in the Ruby standard library. The definitions for the Ruby standard library live inside Sorbet’s textual and binary payload, and cannot be updated by Sorbet end-users except by sending a pull request to Sorbet (see this FAQ entry for more).
Note that even within the type definitions for the Ruby standard library, overloads in Sorbet come with substantial and somewhat fundamental limitations.
When attempting to define methods that would require overloading to properly type them, users are strongly recommended to instead define multiple separate methods. For example, instead of defining a method like this:
# BAD example, will not typecheck
sig {params(idx: Integer, raise_if_not_found: TrueClass).returns(String)}
sig {params(idx: Integer).returns(T.nilable(String))}
def get_element(idx, raise_if_not_found)
# ...
end
This attempts to define a get_element
method that normally returns
T.nilable(String)
when called like get_element(0)
, but will instead always
return String
or raise an exception if called like get_element(0, true)
.
The idea is that these are conceptually two different methods, which is the suggestion for how to refactor the code:
sig {params(idx: Integer).returns(T.nilable(String))}
def get_element(idx)
# ...
end
sig {params(idx).returns(String)}
def get_element_or_raise(idx)
elem = get_element(idx)
case elem
when NilClass then raise "No element found at index #{idx}"
else elem
end
end
We recommend such a refactoring even when attempting to write sigs for methods defined in gems outside the standard library.
If this workaround will not work in your case, the final alternative is to
either omit a signature for the method in question (or define an explicit
signature where all parameters that cannot be typed accurately use T.untyped
).
For more, see Methods with Overloaded Signatures.
5041
See T::Struct: Structs and inheritance for more information.
5042
Sorbet does not allow defining a private
method in an interface!
. Note that
this limitation is lifted if the module
is declared abstract!
not
interface
.
The justification for why this is an error has been lost to the sands of time. It seems reasonable to implement support for this.
https://github.com/sorbet/sorbet/issues/5687
5043
The syntax for declaring type aliases has changed. What used to be written like this:
# BAD
X = T.type_alias(Integer)
must now be written like this, with a block argument instead of a positional argument:
# GOOD
X = T.type_alias {Integer}
The new syntax avoids incurring eagerly evaluating the right-hand side of the type alias, potentially forcing one or more constants to be autoloaded before they would otherwise be required. This improves load-time performance in lazily-loaded environments (usually: development environments) and also prevents certain Sorbet usage patterns from introducing load-time cyclic references.
5044
As background reading, you may first want to read more about variance.
When a type member is declared normally, without any variance annotation, it is
invariant. It can then appear either in the params
list or the returns
of a
method’s signature, but then prevents using subtyping on that type member. For
example:
module IBox
extend T::Generic
Elem = type_member
end
sig {params(x: IBox[Integer]).void}
def example(x)
T.let(x, IBox[T.any(Integer, String)]) # error: Argument does not have asserted type
end
In this example, even though Integer
is a subtype of T.any(Integer, String)
,
IBox[Integer]
is not a subtype of IBox[T.any(Integer, String)]
because
Elem
has not been declared as :out
nor :in
, and is thus invariant.
To allow code like this, we can declare Elem
using :out
, but this comes at
the restriction of only being able to use Elem
in out positions. See
Input and output positions for more
information.
The ways to fix this error include:
- Make the type invariant by removing the
:in
or:out
annotation on the type. (This comes with the normal restrictions on invariant type members.) - Mark the method in question
private
. (This comes with the normal restrictions onprivate
methods.)
If neither of these works, you’ll have to reconsider whether it’s possible to statically type the code in question, and how best to rewrite the code so that it can be typed statically.
Note that
T.attached_class
is actually modeled as a covariant (:out
)type_template
defined automatically on all singleton classes, which means thatT.attached_class
can only be used in:out
positions.
5045
This error regards misuse of user-defined generic classes. This error is
reported even at # typed: true
. Otherwise, the error explanation is the same
as for error code 5046.
5046
Generic classes must be passed all their generic type arguments when being used as types. For example:
T.let([], Array) # error
T.let([], T::Array[Integer]) # ok
Many classes in the standard library are generic classes
(see here), and must be passed type arguments, including
Array
and Hash
. Any user-defined generic classes must similarly be provided
type arguments when used.
For legacy reasons relating to the initial rollout of Sorbet, misuse of generics
defined in the standard library error are only reported at # typed: strict
,
despite being reported at # typed: true
for all user-defined generic classes.
(In an ideal world, it would have always been reported at # typed: true
, and
we might change this in the future.)
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
5049
Parameters given type annotations in a method signature must be provided in the same order they appear in the method definition, even for keyword parameters for which order would not usually be required. This is for readability and consistency.
Also, all optional keyword parameters must come after all required keyword parameters.
5050
Sealed classes (or modules) can only be inherited (or included/extended) in the same file as the parent.
For more information, see Sealed Classes
5051
See Override Checking.
5052
When providing bounds for a type member with lower
and upper
, the lower type
bound must be a subtype of (or equivalent to) the upper
type bound.
Otherwise, it would never be possible to instantiate the generic type because the bounds would never be satisfiable.
5053
For classes that subclass generic classes, the child’s lower and upper bound must still be within the lower and upper bound range specified by the parent class.
This problem can come up from time to time when the parent class uses fixed
when it meant to use upper
. For example:
class IntOrStringBox
extend T::Generic
Elem = type_member {{fixed: T.any(Integer, String)}}
end
class IntBox < IntOrStringBox
Elem = type_member {{fixed: Integer}} # error, incompatible bound
end
In this case we’d like IntBox
to be a subclass of IntOrString
box, but since
we’re using fixed
Sorbet prevents us.
A fix is to use only upper
instead:
class IntOrStringBox
extend T::Generic
Elem = type_member {{upper: T.any(Integer, String)}}
end
class IntBox < IntOrStringBox
Elem = type_member {{upper: Integer}}
end
But this does come at the slight cost that now all type annotations that used to
be using IntOrStringBox
without any generic type arguments must now provide
one:
T.let(IntBox[Integer].new, IntOrStringBox) # error: Generic class without type arguments
The fix is to change all occurrences of IntOrStringBox
without type arguments
to specify IntOrStringBox[T.any(Integer, String)]
.
5054
Use of implementation
has been replaced by override
.
5055
Sorbet does not consider a class “fully resolved” until all of its type members have also been fully resolved. Sorbet cannot fully resolve a type member in a class until all constants mentioned in its right-hand side have been fully resolved. So if any of those right-hand-side constants are themselves generic classes, Sorbet will detect a loop and report this error. Concretely:
class A
extend T::Sig
extend T::Generic
X = type_member {{fixed: B}} # error: A::X is involved in a cycle
end
class B
extend T::Sig
extend T::Generic
X = type_member {{fixed: A}} # error: B::X is involved in a cycle
end
5056
The T.experimental_attached_class
syntax has been stabilized and is now
T.attached_class
. Use the provided autocorrect to migrate.
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.
5058
It’s an error to use T.attached_class
to describe the type of method
parameters. See the
T.attached_class
documentation for a more thorough description of why this is.
5059
The syntax for the second argument to T::NonForcingConstants.non_forcing_is_a?
is much more constrained than what Sorbet allows for when resolving arbitrary
constant literals.
For more information, see T::NonForcingConstants.
5060
When applying a type argument to a generic class, the type argument must be
between any lower
and upper
bound declared by the type_member
(or
type_template
) declaration for that type parameter.
5061
A constant was marked private with Ruby’s private_constant
method, which means
it’s only valid to access that constant directly, with no constant literal
prefix scope:
class A
X = 0
private_constant :X
X # ok
module Inner
X # ok
end
end
A::X # error
If you must access the private constant, you will have to use .const_get
to
both hide the constant access from Sorbet and appease the Ruby VM:
A.const_get(:X)
Note that this technique should be used very sparingly, and many codebases have
lint rules or other coding conventions discouraging or even preventing the use
of const_get
.
5062
Invalid syntax for requires_ancestor
. See
Requiring Ancestors for more information.
5063
Useless requires_ancestor
, because the given class or module is already an
ancestor. See Requiring Ancestors for more information.
5064
Using the requires_ancestor
method, module Bar
has indicated to Sorbet that
it can only work properly if it is explicitly included along module Foo
. In
this example, we see that while module Bar
is included in MyClass
, MyClass
does not include Foo
.
module Foo
def foo; end
end
module Bar
extend T::Helpers
requires_ancestor { Foo }
def bar
foo
end
end
class MyClass # error: `MyClass` must include `Foo` (required by `Bar`)
include Bar
end
The solution is to include Foo
in MyClass
:
class MyClass
include Foo
include Bar
end
Other potential (albeit less common) sources of this error code are classes that are required to have some class as an ancestor:
class Foo
def foo; end
end
module Bar
extend T::Helpers
requires_ancestor { Foo }
def bar
foo
end
end
class MySuperClass
extend T::Helpers
include Bar
abstract!
end
class MyClass < MySuperClass # error: `MyClass` must inherit `Foo` (required by `Bar`)
end
Ensuring MyClass
inherits from Foo
at some point will fix the error:
class MySuperClass < Foo
extend T::Helpers
include Bar
abstract!
end
class MyClass < MySuperClass
end
5065
Unsatisfiable requires_ancestor
. See
Requiring Ancestors for more information.
5067
A class’s superclass (the Parent
in class Child < Parent
) must be statically
resolvable to a class, not a module.
5068
Sorbet requires that class, module or constant definitions be namespaced unambiguously. For example, in this code:
# typed: true
module B
end
module A
class B::C
# ...
end
end
The definition B::C
is ambiguous. In Ruby’s runtime, it resolves to B::C
(and not A::B::C
). However, things are different in the presence of a
pre-declared filler namespace like below:
# typed: true
module B
end
module A
module B; end
class B::C
# ...
end
end
In this case, the definition resolves to A::B::C
in Ruby’s runtime.
By default, Sorbet assumes the presence of filler namespaces while typechecking,
regardless of whether they are explicitly predeclared like in the second
example. This means that in Sorbet’s view, the definition resolves to A::B::C
in either case.
In Stripe’s codebase, this is generally not a problem at runtime, as we use Sorbet’s own autoloader generation to pre-declare filler namespaces, keeping the Ruby runtime’s behavior equivalent to Sorbet. However, the autoloader has some edge cases, which can often cause deviations between Ruby’s runtime and Sorbet. This error helps guard against these issues.
5069
There must be exactly one statement in a method signature (possibly a single chain of methods).
Sorbet used to allow syntax like
sig do
params(x: Integer)
returns(Integer)
end
def foo(x); x; end
where the body of the sig
block was allowed to have multiple methods not
connected by a method call chain.
Use the provided autocorrect to convert the old syntax to the new syntax.
5070
T.nilable(T.untyped)
is just T.untyped
, because nil
is a valid value of
type T.untyped
(along with all other values).
5071
Providing a .bind
when using T.proc
is only valid on a method’s single block
argument (&blk
), not on all arguments. The .bind
is not actually a type
annotation, but instead an instruction to the type inference algorithm about how
to typecheck the body of the block. Type checking for the body of the block
provided to a method call happens after type checking all other arguments,
while type checking for lambda functions and procs passed as positional or
keyword arguments happens before the proc value is checked against the
T.proc
type. For example:
sig do
params(
f: T.proc.returns(Integer),
blk: T.proc.returns(Integer),
)
.void
def example(f, &blk)
end
f = ->() {0} # lambda is type checked here, before running type inference on
# the `example` call below.
example(f) do # call to `example` is typechecked next
1 # block body is typechecked last
end
Since the definition of the lambda f
is typechecked entirely before the call
to example
is typechecked, no .bind
annotation on any of example
's
positional or keyword arguments would actually affect how the lambda is
typechecked.
5072
See type_member
& type_template
in
the generics docs for more.
There are two main cases where this error is reported:
- Using a
type_member
in a scope where only atype_template
is allowed (and vice versa), but within the correct class. - Using either a
type_member
ortype_template
in a completely unrelated class.
Neither is allowed, but in each case the solution is different.
type_member
/ type_template
mismatch
A type_member
is limited in scope to only appear in instance methods of the
given class. A type_template
is limited in scope to only appear in singleton
class methods of the given class. For example:
class Box
extend T::Sig
extend T::Generic
Elem = type_member
sig {returns(Elem)} # error
def self.example
# ...
end
end
This example doesn’t make sense because Elem
in this case is the element type
of our generic Box
type. For example, instances of Box[Integer]
and
Box[String]
hold Integer
's and String
's respectively. Meanwhile, a call to
the method Box.example
wouldn’t have a meaning, because there is no instance
of Box
in this call to Box.example
whose element type should be used.
Sometimes this error comes up when attempting to do something like this:
class AbstractSerializable
extend T::Sig
extend T::Generic
abstract!
SerializeType = type_template
sig {abstract.returns(SerializeType)} # error
def serialize; end
sig do
abstract
.params(raw_input: SerializeType)
.returns(T.attached_class)
end
def self.deserialize(raw_input); end
end
class MyClass < AbstractSerializable
SerializeType = type_template {{fixed: String}}
# ... implement abstract methods ...
end
In this case, the idea is that some subclasses may want to serialize to a
String
, some may want to serialize to an Integer
, some may want to serialize
to a Symbol
, etc. and the child class should get to specify that, by using
fixed
on the type_template
.
Even in these cases, Sorbet does not let the type_template
be referenced from
an instance method. The solution is to instead specify both type_member
and a
type_template
, and have the child class fix both of them:
class AbstractSerializable
# ...
SerializeTypeMember = type_member
SerializeTypeTemplate = type_template
# ...
end
class MyClass < AbstractSerializable
SerializeTypeMember = type_member {{fixed: String}}
SerializeTypeTemplate = type_template {{fixed: String}}
# ...
end
In an unrelated class
In this case, the error usually indicates a more fundamental problem with the design, which means that the way forward is more nuanced.
Consider a class like this:
class Box
extend T::Generic
Elem = type_member
sig { params(val: Elem).void }
def initialize(val); @val = val; end
sig { returns(Elem) }
def val; @val; end
end
We might want to write a function which gets the element out of an arbitrary
Box
:
sig do
type_parameters(:Elem)
.params(box: Box[T.type_parameter(:Elem)])
.returns(Box[T.type_parameter(:Elem)]::Elem)
# ^^^^^^ ❌
end
def example_bad1(box)
box.val
end
This code does not work, because we can’t access arbitrary type members inside a
class. In this case, the fix is easy enough: just
returns(T.type_parameter(:Elem))
:
sig do
type_parameters(:Elem)
.params(box: Box[T.type_parameter(:Elem)])
.returns(T.type_parameter(:Elem)) # ✅
end
def example_good1(box)
box.val
end
Another thing that might cause problems: we want to make the type_member
fixed, and then access it like a type alias:
class ComplicatedBox < Box
Elem = type_member { {fixed: T.any(Integer, String, Float, Symbol)} }
end
sig { params(box: ComplicatedBox).returns(ComplicatedBox::Elem) }
# ^^^^^^^^^^^^^^^^^^^^ ❌
def example_bad2(box)
box.val
end
Type members are not type aliases. If you want something that works like a type
alias, make a type alias, and then use that both in the fixed
annotation of
the type member, and in any signature where you’d like to use that type:
class ComplicatedBox < Box
Type = T.type_alias { T.any(Integer, String, Float, Symbol) }
Elem = type_member { {fixed: Type} } # ✅
end
sig { params(box: ComplicatedBox).returns(ComplicatedBox::Type) }
# ^^^^^^^^^^^^^^^^^^^^ # ✅
def example_good2(box)
box.val
end
For more information on why type members are not type aliases and vice versa, see 5075.
5073
Abstract classes cannot be instantiated by definition. See Abstract Classes and Interfaces for more information.
class Abstract
extend T::Sig
extend T::Helpers
abstract!
sig {abstract.void}
def foo; end
end
Abstract.new # error: Attempt to instantiate abstract class `Abstract`
To fix this error, there are some options:
- If the class which is marked
abstract!
does not actually have anyabstract
methods, simply removeabstract!
from the class definition to fix the error. - If the class does have
abstract
methods, find some concrete subclass to callnew
on instead. If the call tonew
is in a test file, you may wish to make a new, test-only subclass of the abstract class. (Depending on the specifics of the test, it may even be possible to simply define all the abstract methods to simplyraise
, so that other aspects of the parent class can be tested.)
5074
A module marked has_attached_class!
can only be mixed into a class with
extend
, or a module with include
. When mixing a has_attached_class!
module
into another module, both modules must declare has_attached_class!
.
For more information, see the docs for T.attached_class
.
5075
Sorbet does not support type aliases that alias to generic type members (or type templates, which are just type members owned by the singleton class). At the moment, there is no perfect workaround, though in the future Sorbet will support this by allowing generic type members to be bounded by other generic type members. The current best option is simply to copy the contents of the type alias into all the places where you would like to use that type alias.
Here’s why Sorbet does not allow type aliases to alias to generic type members. Consider this motivating example, of a generic box type which can be possibly empty:
class PossiblyEmptyBox
extend T::Generic
class IsEmpty; end
Elem = type_member
MaybeElem = T.type_alias { T.any(IsEmpty, Elem) } # ❌ NOT allowed
# ...
sig { returns(Elem) }
def val!; end
sig { returns(MaybeElem) }
def val; end
end
Our class is generic in Elem
, so that this container can hold an arbitrary
type. But we have a lot of methods that will return either the
Elem
that this box contains, or an instance of some IsEmpty
class if there
is nothing currently in the box. (Presumably: there are a lot of methods where
MaybeElem
is used, and we don’t want to have to repeat ourselves by writing
T.any(IsEmpty, Elem)
all over the place.)
This is not allowed, because the restrictions on where type aliases can be used from are not the same as those that apply to type members:
Type aliases can be used anywhere, regardless of where the type alias is defined.
Type members can only be used inside the enclosing class where they were defined (as if they were declared with
private_constant
) and can only be used inside methods and instance variables of the instance class (or for type templates: methods and instance variables of the singleton class).
If Sorbet were to allow type members to appear in type aliases, that type alias would have to essentially copy all the rules of where that type alias is allowed to be used from the type members inside it.
But at that point, the type alias would be no different from defining a second,
fixed
type member on the same class:
class PossiblyEmptyBox
# ...
Elem = type_member
MaybeElem = type_member {
{fixed: T.any(IsEmpty, Elem)}
# ^ should be allowed in the future
}
sig { returns(Elem) }
def val!; end
sig { returns(MaybeElem) }
def val; end
end
The fixed
annotation makes MaybeElem
exactly like a type alias with the same
scope as Elem
. This solution avoids having to solve the problem of “copying
all the scoping rules of a type member onto a type alias.”
There is only one gotcha: at the moment Sorbet does not support using type
members in fixed
, upper
, or lower
bounds of a type member. We expect to
relax this constraint in the future, which means that at the moment there is no
way to define something that acts like a type alias to a generic type member.
The only way forward is to copy the contents of the type alias into all the places where you’d like to use that type alias, like this:
class PossiblyEmptyBox
# ...
Elem = type_member
sig { returns(Elem) }
def val!; end
# Use `T.any` instead of `MaybeElem`
sig { returns(T.any(IsEmpty, Elem)) }
def val; end
end
5076
T.nilable
expects exactly one argument. A common typo is T.nilable(X, Y)
to
mean T.nilable(T.any(X, Y))
(which would also be the same as
T.any(NilClass, X, Y)
).
See Nilable Types and Union Types for more information.
5077
Sorbet does not allow value literals in type syntax. None of these are allowed:
# ❌NOT SUPPORTED ❌
T.any(:left, :right)
T.any(1, 2, 3)
T.any('foo', 'bar')
There are two options:
Widen the type to whatever type underlies the literal, like
Symbol
orInteger
orString
, and check the values at runtime (not statically in the type system).sig { params(direction: Symbol).void } def move(direction) if ![:left, :right].include?(direction) raise ArgumentError.new("Bad direction: #{direction}") end # ... end
Define a
T::Enum
and use that instead, which requires converting to and from the literal type when calling the method:class Direction < T::Enum enums do Left = new(:left) Right = new(:right) end end sig { params(direction: Direction).void } def move(direction) # ... direction_symbol = direction.serialize end direction_symbol = # ... direction = Direction.deserialize(direction_symbol) move(direction)
5078
This error is functionally equivalent to error 7010. It is reported during the inference stage, while 5078 is reported during the resolver stage.
For detailed information and examples, please refer to the documentation for error 7010.
5079
This error is functionally equivalent to error 7032. It is reported during the inference stage, while 5079 is reported during the resolver stage.
For detailed information and examples, please refer to the documentation for error 7032.
6001
Certain Ruby keywords like break
, next
, and retry
can only be used inside
a Ruby block.
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, 7028, 7043.
6004
In order to statically check exhaustiveness, Sorbet provides
T.absurd
, which lets people opt into exhaustiveness checks.
T.absurd
must be given a variable. If not, like in this example, it reports an
error:
# -- bad example --
sig {returns(T.any(Integer, String))}
def returns_int_or_string; 0; end
case returns_int_or_string
when Integer then puts 'got int'
when String then puts 'got string'
# error! `returns_int_or_string` is not a variable!
else T.absurd(returns_int_or_string)
end
While it looks like returns_int_or_string
is the name of a variable, it’s
actually a method call (Ruby allows method calls to omit parentheses). To fix
this error, store the result of calling returns_int_or_string
in a variable,
and use that variable with the case
and T.absurd
:
sig {returns(T.any(Integer, String))}
def returns_int_or_string; 0; end
# calls returns_int_or_string, stores result in x
x = returns_int_or_string
case x
when Integer then puts 'got int'
when String then puts 'got string'
else T.absurd(x)
end
6005
T.bind
can only be called on self
, syntactically.
To perform type assertions on non-self
things, use T.let
or T.cast
.
See Type Assertions for more.
6006
The program attempted to use T.type_parameter
in a method body, but there was
no such T.type_parameter
in scope.
For more information, see the docs for Generic methods.
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 is not permitted
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
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:
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!
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.
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’sincluded
+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.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 onKernel
in Ruby.Kernel
is included inObject
(which classes default to inheriting from), but not onBasicObject
(which classes can optionally inherit from).The solution is to
include Kernel
in our module:module MyModule include Kernel end
Sorbet will complain about this code:
module MyModule def foo; puts 'hello'; end end
The issue is similar to the above:
puts
is defined onKernel
, 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
7005
Sorbet detected a mismatch between the declared return type for the method and the type of the returned value:
sig { returns(Integer) }
def answer
"42" # error: Expected `Integer` but found `String("42")` for method result type
end
Here we specified in the signature that find
returns an instance of
Configuration
, yet the returned value might be nil
:
sig { params(name: String).returns(Configuration) }
def find(name)
@lookup[name] # error: Expected `Configuration` but found `T.nilable(Configuration)` for method result type
end
A possible solution, if we are certain that name
is in the @lookup
hash,
is to use T.must
when returning the value:
sig { params(name: String).returns(Configuration) }
def find(name)
T.must(@lookup[name])
end
In some cases, we’re already being cautious and perform some checks before returning yet Sorbet still complains about the return type:
sig { params(name: String).returns(Configuration) }
def find(name)
raise ArgumentError, "Configuration #{name} not found" unless @lookup.key?(name)
@lookup[name] # error: Expected `Configuration` but found `T.nilable(Configuration)` for method result type
end
While this code is correct, Sorbet cannot assume the state of @lookup
didn’t
change between the key?
check and the []
read. To fix this, we can take
advantage of flow-typing to make the whole method work without inline type
annotations:
sig { params(name: String).returns(Configuration) }
def find(name)
config = @lookup[name]
raise ArgumentError, "Configuration #{name} not found" unless config
config
end
By using a local variable, we allow Sorbet to assert that config
is never
nilable past the raise
instruction.
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.
7007
Sorbet can statically assert that the value passed into a T.let
does not match
the expected type:
x = T.let(1, String) # error: Argument does not have asserted type `String`
Because of the way default values are desugared by Sorbet, this error also occurs when Sorbet finds a mismatch between the type specified for a parameter in the signature and the default value provided in the method.
In this case, the signature states that category
type is a Category
, yet we
try to use nil
as default value:
sig { params(name: String, category: Category).void }
def publish_item(name, category = nil) # error: Argument does not have asserted type `Category`
# ...
end
If category
value is nil
by default, maybe we should make it so its type is
nilable:
sig { params(name: String, category: T.nilable(Category)).void }
def publish_item(name, category = nil)
# ...
end
7009
This error occurs when a value is used in place of a type. There are many different situations where this can happen; one example is given below:
class Box
extend T::Generic
E = type_member
end
Box[true].new # error: Unexpected bare `TrueClass` value found in type position
More generally, Sorbet draws a distinction between places in a program where Ruby values are allowed, and places where Sorbet type syntax is allowed. For example:
# ----- Arbitrary type syntax allowed -----
T.let(0, T.any(Integer, String))
# ^^^^^^^^^^^^^^^^^^^^^^
sig {returns(T.any(Integer, String))}
# ^^^^^^^^^^^^^^^^^^^^^^
T::Array[T.any(Integer, String)].new
# ^^^^^^^^^^^^^^^^^^^^^^
# ----- Arbitrary type syntax NOT allowed -----
case 0
when T.any(Integer, String)
# ^^^^^^^^^^^^^^^^^^^^^^
end
x.is_a?(T.any(Integer, String))
# ^^^^^^^^^^^^^^^^^^^^^^
Only valid Sorbet types are allowed in type positions. Arbitrary Sorbet types are not allowed in most places where Ruby expects a normal value.
Note: Historically this error message has been one of the more confusing Sorbet errors, and over time we have added special cases to detect common points of confusion. If you’re reading this because you found the error message confusing, please consider sharing your example with the Sorbet team so we can further improve the error message.
7010
Sorbet found a reference to a generic type with the wrong number of type arguments.
Here we defined MyMap
as a generic class expecting two type parameters
KeyType
and ValueType
but we try to instantiate it with only one type
argument:
class MyMap
extend T::Generic
KeyType = type_member
ValueType = type_member
# ...
end
MyMap[String].new # error: Wrong number of type parameters for `MyMap`. Expected: `2`, got: `1`
Unless a type member was fixed
, it is always required to pass the correct
amount of type arguments. T.untyped
can also be used if the type is not
relevant at this point:
MyMap[String, Integer].new
MyMap[String, String].new
MyMap[String, T.untyped].new
7011
Historically, users seeing this error has represented finding a bug in Sorbet (or at least finding a test case for which there could be a better error message). Consider searching for similar bugs at https://github.com/sorbet/sorbet/issues or reporting a new one.
7013
Sorbet detected that an instance variable was reassigned with different types:
class A
extend T::Sig
sig { void }
def initialize
@x = T.let(0, Integer)
end
sig { void }
def foo
@x = 'not an integer' # error: Expected `Integer` but found `String("not an integer")` for field
end
end
If the instance variable can hold both an Integer
and a String
, maybe the
type specified with T.let
should be enlarged:
@x = T.let(0, T.any(Integer, String))
Similarly, Sorbet will reject constants reassigned with different types:
FOO = 42 # error: Expected `String("Hello, world!")` but found `Integer(42)` for field
FOO = "Hello, world!"
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.
7015
Certain forms of T.let
, T.cast
, and T.must
calls are redundant. Follow the
suggestion in the error message to fix—these errors should have an autocorrect
you can apply to automatically fix the error.
7016
Sorbet has special handling for certain methods in the standard library, like
Array#flatten
. This method takes an optional depth
argument, and will only
flatten nested arrays that deep if passed:
[[[[1]]]].flatten(2)
# ERROR
depth = T.let(2, Integer)
[[[[1]]]].flatten(depth)
Unfortunately, Sorbet can only perform this analysis when the depth is static.
If Sorbet cannot see the exact value of the depth and instead only sees a type
of Integer
for the depth argument, it reports an error.
Either:
- Refactor the code so that Sorbet can see the exact depth value, or
- Use
T.unsafe
to hide the method call from Sorbet.
depth = T.let(2, Integer)
# `T.unsafe` disables static type checking for this call site,
# including the flatten depth check
T.unsafe([[[[1]]]]).flatten(depth)
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, 7028, 7043.
7018
At # typed: strong
, Sorbet no longer allows using T.untyped
values. To fix
errors of this class, add type annotations to the code until Sorbet has enough
context to know the static type of a value. Usually this means adding
method signatures or type assertions to declare
types to Sorbet that it couldn’t infer.
Note: this strictness level should be considered a beta feature: the errors
at this level are still being developed. Most Ruby files that actually do
interesting things will have errors in # typed: strong
. As such, an
alternative solution to fixing these errors is simply to downgrade the file to
# typed: strict
or below, which will silence all these T.untyped
errors.
For more information on # typed: strong
, strategies for dealing with errors
that arise from using T.untyped
, and current known limitations, see the docs
for # 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)
# ---------------------------------------------
7020
Note that method-level generics (i.e., those declared by using type_parameters
inside a sig
) are a somewhat unstable feature. If you encounter this error and
believe it to represent a bug in Sorbet, first check the
Sorbet bug tracker to see if a
similar-looking error has already been reported. If no such bug exists, feel
free to open an issue.
7021
The called method declares a block parameter that is not T.nilable
(making the
block argument required), 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).
7022
When run with the --suggest-typed
command line argument, Sorbet will suggest
upgrading or downgrading # typed:
sigils in all files in the project to the
highest level possible that would still have no errors. If the project starts
off with no errors initially, this should have the effect of only upgrading
sigils.
Accept the provided autocorrect suggestion to commit the upgrades (using the
--autocorrect
flag).
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.
7027
In # typed: strict
files, Sorbet requires that all constants are annotated
with a T.let
.
For how to fix, see Type Annotations, or accept the autocorrect suggestion associated with this error.
See also: 5028, 6002, 7017, 7043.
7030
This error is consistently used when the user is trying (implicitly or
explicitly) to call some method on a Sorbet type (e.g. T::Array[Integer]
)
which would actually return a Sorbet-runtime representation of a type.
This error generally occurs when generic types are used in pattern matching:
def get_value(input)
case input
when Integer
input
when T::Array[Integer] # error: Call to method `===` on `T::Array[Integer]` mistakes a type for a value
input.first
end
end
Since generic types are erased at
runtime, this construct would never work when the program executed. Replace the
generic type T::Array[Integer]
by the erased type Array
so the runtime
behavior is correct:
def get_value(input)
case input
when Integer
input
when Array
input.first
end
end
7031
Private methods in Ruby behave somewhat differently from private methods in
other statically typed languages. You may want to read more about
method visibility in Ruby
first. The tl;dr is that private methods can only be called when the receiver is
syntactically self
(or omitted):
class Parent
private def foo; end
def in_parent
foo # OK
self.foo # OK
end
end
class Child < Parent
def in_child
foo # OK
self.foo # OK
end
end
Parent.new.foo # error!
Child.new.foo # error!
This makes private methods in Ruby behave more like protected methods in other object-oriented languages.
To fix this error, either:
- Avoid calling the private method entirely, or
- Update the method definition to not be private, or
- Use
.send(:foo, ...)
to ask Ruby to call the method ignoring visibility checks.
Note: It’s not possible to silence this error using
T.unsafe
. Wrapping the receiver inT.unsafe
will in fact hide the method call from Sorbet statically, but because method visibility is checked syntactically, even usingT.unsafe(self).foo
will cause a call to a private methodfoo
to be rejected by the Ruby VM at runtime.If you must call a private method and also silence any type errors from calling it, you must use
self.send(:foo)
instead.
7032
When passing type arguments to generic classes, to use shape types, use curly brackets around the keys and values of the shape type:
# CORRECT
T::Array[{key: Integer}].new
# BAD
T::Array[key: Integer].new
7033
Note: this error is only reported when Sorbet is passed the
--ruby3-keyword-args
command line flag. For further information about Ruby 3
and keyword args, see the
official blog post.
# typed: true
extend T::Sig
sig {params(x: Integer, y:Integer).void}
def takes_kwargs(x, y:)
end
arghash = {y: 42}
# GOOD EXAMPLE
takes_kwargs(99, **arghash)
# BAD EXAMPLE
takes_kwargs(99, arghash)
# ^^^^^^^ error: Keyword argument hash without `**`
In Ruby 2.7 and earlier, Ruby allowed a positional Hash
argument at the end of
a method call’s list of arguments to implicitly splat into the keyword
parameters of the method. In Ruby 3.0 and later, a positional Hash
argument is
always treated as a positional argument. To avoid this error, prefix the Hash
argument with **
, explicitly requesting to treat it as keyword arguments.
7034
Sorbet detected that the safe navigation operator (&.
) was being used on a
receiver that can never be nil. Replace the offending occurrence of &.
with a
normal method call (.
).
# typed: true
extend T::Sig
sig {params(x: Integer, y: T.nilable(Integer)).void}
def foo(x, y)
puts x&.to_s # error: x can never be nil
puts x.to_s # no error
puts y&.to_s # no error: y may be nil
end
7035
Sorbet can sometimes detect when a method is passed a block despite not accepting a block.
# typed: strict
extend T::Sig
sig {void}
def takes_no_block; end
# BAD EXAMPLE
takes_no_block {} # error: does not take a block
Why only sometimes? Technically all methods in Ruby are allowed to accept blocks. (Unlike positional arguments, if a method does not declare that it accepts a block argument but a caller passes one anyways the Ruby VM does not raise an exception.)
For historical reasons, Sorbet did not require that a sig
mention the blk
parameter if its method used yield
from the beginning of Sorbet adoption. It
later required this, but for reasons of backwards compatibility, it’s only
checked in # typed: strict
or higher files.
Therefore, error 7035 is somewhat special in that it can be reported in
# typed: true
files, but only if the method being passed a block is itself
defined in a # typed: strict
file.
Regardless, to fix this error, either:
- Remove the block from this call site (using the autocorrect), or
- Update the called method’s definition to mention block argument, or
- Drop the strictness level of file containing the called method’s definition
to
# typed: true
or lower (only as a last resort).
7036
Sorbet supports declaring package-private methods. These methods can only be called from within the package where they are declared
To fix this error, either remove the package_private
(or
package_private_class_method
) annotation from the method definition, or
rewrite the code in question to not call the private method.
As a last resort, you can use T.unsafe
to hide the method call from
Sorbet, silencing the error.
7037
See this doc for more information.
In general, Sorbet can’t know that two calls to identical methods return identical things, because in general methods are not pure.
Consider this example
class A < T::Struct
const :foo, T.nilable(Integer)
end
if a.foo && a.foo.even?
# ^^^^^^^^^^^ error
puts a.foo
end
In this example, the call to a.foo.even?
results in an error, even though we
checked that a.foo
was not nil
with the &&
, because Sorbet does not assume
any methods are pure, not even methods defined with the T::Struct
class’s
const
DSL. (There are a number of technical and philosophical reasons why
Sorbet behaves this way, and we do not foresee these reasons changing.)
There is always a simple solution, which is to either factor out the method
call’s result into a variable, or to use Ruby’s conditional method call operator
(&.
):
# -- solution 1 (preferred) --
x = a.foo
if x && x.even?
puts x
end
# -- solution 2 --
if a.foo&.even?
puts a.foo
end
Of the two, the first solution is preferred because not only will the program
type check as written, but Sorbet will know that the x
variable is not nil
throughout the body of the if
statement.
7038
For more information, see the docs on generic methods.
Consider this example:
sig do
type_parameters(:U)
.params(x: T.type_parameter(:U))
.void
end
def example(x)
x.foo # ❌ error!
end
This snippet declares a method example
with a signature that says it can be
passed any input, but then attempts to call a specific method .foo
.
Since this method can be given any type of value, Sorbet rejects the call to
x.foo
.
To allow code like this, use interfaces with intersection types:
# (1) Declare an interface
module IFoo
extend T::Sig
extend T::Helpers
interface!
sig {abstract.void}
def foo; end
end
sig do
type_parameters(:U)
# (2) Use the interface with an intersection type
.params(x: T.all(IFoo, T.type_parameter(:U)))
.void
end
def example(x)
x.foo # ✅ no error
end
Remember that in Sorbet, interfaces must be explicitly implemented in a given class.
Sometimes this error happens due to a call to is_a?
on a type parameter. To
sidestep this error, rewrite the program to use case
:
# ...
def example(x)
if x.is_a?(Integer) # error
# ...
end
case x
when Integer # OK
# ...
end
end
Or if it’s imperative to continue using is_a?
, change the type to
T.all(Kernel, T.type_parameter(:U))
.
7039
For more information, see how to place bounds on type members.
Consider this example:
class A
def only_on_a; end
end
class Box
extend T::Sig
extend T::Generic
Elem = type_member
sig {params(x: Elem).void}
def initialize(x)
x.only_on_a # error, see below for fix
end
end
In this example, we’re trying to to call the method only_on_a
, but we haven’t
specified any type bounds on the Elem
type parameter, which means we can’t
make any assumption about what methods it might have.
If we want to always be able to call the method only_on_a
, we can place a
upper bound on Elem
:
# ...
Elem = type_member {{upper: A}}
# ...
This will guarantee that Elem
is always at least A
, which will let Sorbet
allow the call to x.only_on_a
.
Sometimes this error happens due to a call to is_a?
on a type member. To
sidestep this error, rewrite the program to use case
:
# ...
def example(x)
if x.is_a?(Integer) # error
# ...
end
case x
when Integer # OK
# ...
end
end
Or if it’s imperative to continue using is_a?
, change the type to
T.all(Kernel, Elem)
and/or add an upper bound of Kernel
to the type member.
7040
T.attached_class
is a type annotation that refers to instances of the current
singleton class. For example, a method like this makes sense, because it uses
T.attached_class
on a singleton class method (the self.
prefix):
class A
sig {returns(T.attached_class)}
def self.make
x = T.let(new, T.attached_class)
end
end
Meanwhile, this snippet doesn’t make sense, because foo
is already an instance
method—there is no attached class to speak of for non-singleton classes:
class A
sig {returns(T.attached_class)} # error!
def foo
x = T.let(new, T.attached_class) # error!
end
end
For more information see the T.attached_class
docs.
It may also be interesting to compare and contrast
T.self_type
.
7043
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, 6002, 7017, 7027.
7044
Sorbet has built-in support for the dig
method on Array
and Hash
. In
certain cases, Sorbet can detect that there have been too many arguments
provided to a dig
call. For example:
arr = T::Array[NilClass].new
arr.dig(0, 0)
This tries to get the 0th element of the 0th element of the array, if it exists.
However, Sorbet can know that the 0th element of the first array is
always nil
if it exists, so the second 0th element will never be
accessed, and is thus redundant.
To fix this, either delete the redundant arguments, or use an
Escape Hatch to hide the call to dig
from
Sorbet:
arr = T::Array[NilClass].new
T.unsafe(arr).dig(0, 0)
7045
Sorbet sometimes assumes an expression has a certain type—even when it has no guarantee whether that’s the case—because the assumption will be correct almost all the time and assuming the type means not having to given an explicit type annotation.
This error is reported when those assumptions are wrong. Rather than go back and attempt to invalidate the assumption by redoing work it already did (but this time under the correct assumptions), it reports an error asking the user to provide an explicit type annotation so that no assumption is necessary in the first place. This enables Sorbet to finish type checking quickly on large codebases.
To fix this error, provide an explicit annotation (or simply accept the autocorrect suggestion).
For more information, read Why does Sorbet sometimes need type annotations?.
7046
For a limited number of types, Sorbet checks whether it looks like a call to
==
is out of place. Currently, Sorbet only does these checks when the left
operand of ==
is:
Symbol
String
Sorbet is unable to apply these checks for all types, because ==
can be
overridden in arbitrary ways, including to allow for implicit conversion between
unrelated types. This means that Sorbet will sometimes miss reporting this error
in places where we would like it to, and can’t be changed to report an error
without breaking valid code.
To fix this error, ensure that the left and right operands’ types match before
doing the comparison. For example, try converting String
s to Symbol
s with
to_sym
(or vice versa with to_s
).
7047
This error code is an implementation detail of Sorbet’s “highlight untyped in
editor” mode. It indicates that the given piece of code has type
T.untyped
. Untyped code can be dangerous, because it circumvents
the guarantees of the type system.
This feature is opt-in. See VS Code for instructions on how to turn it on.
7048
This error is like 7003, but for calls using super
.
That is, the difference is that Sorbet checks whether a method with the same name as the enclosing method exists on any ancestor of the current method.
This error is caused by most of the same reasons that cause 7003, so it’s best to read the docs for that code as well.
See also:
- Why is
super
untyped, even when the parent method has asig
? - How can I fix type errors that arise from
super
?
7049
See Methods with Overloaded Signatures for complete docs on how to declare overloaded methods correctly.
7050
Calling T.must
when the argument is T.untyped
, T.anything
, Object
, or
BasicObject
is redundant. This indicates that either:
- a previously-typed argument became untyped, indicating a regression in type safety somewhere, or
- the code does not benefit from
T.must
, because the type before and after the call is the same.
If raising an exception for nil
values is the desired runtime behavior, do so
explicitly:
raise if x == nil
This was separated from 7015 allow it to be autocorrected independently
from invalid usages of T.let
and T.cast
.
7051
When a method’s signature is marked .void
, Sorbet replaces the result value of
the method with a special singleton value, so that callers downstream of the
method cannot depend on whatever value the method happens to return.
(See
returns
& void
: Annotating return types)
One thing to be aware of is that this special singleton value is truthy (because
the only two values that are falsy in the Ruby VM are nil
and false
).
Thus, branching on this special void singleton value is virtually always a bug.
If a method must be able to return anything in its body, while still
branching on the resulting value, use .returns(T.anything)
instead:
sig { returns(T.anything) }
def example
if [true, false].sample
''
else
false
end
end
See the docs for T.anything for more.
This error happens if any component of the type includes void
. For example:
result =
if [true, false].sample
returns_void
else
false
end
T.reveal_type(result) # => T.any(Sorbet::Private::Static::Void, FalseClass)
if result
# ^^^^^^ error
puts
end
This is expected, and the way to fix it is again either to change returns_void
to returns(T.anything)
, or update the if
branch to explicitly produce a
value:
result =
if [true, false].sample
returns_void
true
else
false
end
T.reveal_type(result) # => T::Boolean