---
id: static
title: Enabling Static Checks
---

This doc will cover how to enable and disable the **static checks** that Sorbet reports. Specifically, we'll look at how to toggle these checks...

- ...within an entire **file**.
- ...for a particular **method**.
- ...for a single **argument** of a method.
- ...at a specific **call site**.

Before we get into the mechanics of how to toggle Sorbet's static checks, let's slap on a quick warning label:

> **Warning**: Think carefully before disabling static checks!

## File-level granularity: strictness levels

When run at the command line, Sorbet roughly works like this:

1.  Read, parse, and analyze every Ruby file in a project.
2.  Generate a list of errors within the project.
3.  Display all errors to the user.

However, in step (3), most kinds of errors are _silenced_ by default, instead of being reported. To opt into _more_ checks, we use `# typed:` **sigils**[^sigil]. A `# typed:` sigil is a comment placed at the top of a Ruby file, indicating to Sorbet which errors to report and which to silence. These are the available sigils, each defining a **strictness level**:

<!-- prettier-ignore-start -->

[^sigil]: Google defines sigil as, "an inscribed or painted symbol considered to
have magical power," and we like to think of types as pretty magical 🙂

<!-- prettier-ignore-end -->

| All errors silenced |  |  |  | All errors reported |
| --- | --- | --- | --- | --- |
| `typed: ignore` | `typed: false` | `typed: true` | `typed: strict` | `typed: strong` |

Each strictness level reports all errors at lower levels, plus new errors:

- At `# typed: ignore`, the file is not even read by Sorbet, and so no errors at all are reported in that file. **Note**: ignoring a file can cause errors to appear in _other_ files, because that other file references something defined in an ignored file. We recommend pushing the entire project to out of `ignore` (at Stripe, `# typed: ignore` is only used for certain autogenerated Ruby files, which have corresponding well-typed [RBI files](rbi.md).)

- At `# typed: false`, only errors related to syntax, constant resolution and correctness of `sig`s are reported. Fixing these errors is the baseline for adopting Sorbet in a new codebase, and provides value even before adding type annotations. `# typed: false` is the **default** for files without sigils.

  (**Note**: Sorbet will still parse and store [method signatures](sigs.md) even in `# typed: false` files, for use whenever it sees a call to that method in `# typed: true` or higher files. `# typed: false` only prevents that method's body from being typechecked, which means that signatures in `# typed: false` files are simply assumed to hold.)

- At `# typed: true`, things that would normally be called "type errors" are reported. This includes calling a non-existent method, calling a method with mismatched argument counts, using variables inconsistently with their types, etc.

- At `# typed: strict`, Sorbet no longer implicitly marks things as being [dynamically typed](untyped.md). At this level all methods must have [sigs](sigs.md), and all constants and instance variables must have [explicitly annotated types](type-annotations.md). This is analogous to TypeScript's `noImplicitAny` flag.

<a class="anchor" aria-hidden="true" id="typed-strong"></a>

- At `# typed: strong`, Sorbet no longer allows usages of [`T.untyped`](untyped.md) values. This effectively means that Sorbet knew the type statically for all values within a file. Currently, this sigil is considered a beta feature, as it has certain known limitations. See [`# typed: strong`](strong.md) for more information.

To recap: adding one of these comments to the top of a Ruby file controls which errors Sorbet reports or silences in that file. The strictness level only affects which errors are reported.

> **Note**: Method signatures in `# typed: false` files are _still parsed and used_ by Sorbet if that method is called in other files. Specifically, adding a signature in a `# typed: false` file might introduce new type errors if it's called from a `# typed: true` file.

### Upgrading a file from `ignore` to any other sigil

After changing the sigil of an `ignore`d file, you must run `srb rbi hidden-definitions`.

The last time hidden definitions were generated, the file was `ignore`d, sorbet didn't load it, so anything in it was treated as "hidden" and defined in `sorbet/rbi/hidden-definitions/hidden.rbi`. Now that sorbet is allowed to load it, those definitions must be removed from `hidden.rbi`.

If you don't regenerate `hidden.rbi`, you are likely to encounter [error 4010](https://srb.help/4010) (two definitions of the same method).

## Method-level granularity: `sig`

After enabling `# typed: true` in some files, we can opt individual methods into even more checks by adding signatures (or `sig`s) to them. For example, Sorbet reports no errors in this file:

```ruby
# typed: true

def log_env(env, key)
  puts "LOG: #{key} => #{env[key]}"
end

log_env({timeout_len: 2000}, 'timeout_len')
```

It would be nice to be warned about our call to `log_env`, because we passed a Hash with `Symbol` keys but tried to ask about a `String` key. To opt into this check, we can add a signature to `log_env`:

```ruby
# typed: true

# (1) add this to get access to sig method
extend T::Sig

# (2) add a signature
sig { params(env: T::Hash[Symbol, Integer], key: Symbol).void }
def log_env(env, key)
  puts "LOG: #{key} => #{env[key]}"
end

log_env({timeout_len: 2000}, 'timeout_len') # => Expected `Symbol` but found `String("timeout_len")`
```

In this example, we add a line like `sig { ... }` above the `def log_env` line. This is a Sorbet method signature---it declares the parameter and return types of a method. By adding the `sig` to `log_env`, we opted this method into additional checks. Now Sorbet reports this:

```plaintext
Expected `Symbol` but found `String("timeout_len")` for argument `key`
```

## Argument-level granularity: `T.untyped`

In our previous example, the `sig` we added was a bit too restrictive for the `env` parameter:

```ruby
T::Hash[Symbol, Integer]
                ^^^^^^^
```

For example, it's possible that we don't care about what's stored in the `env`, only that we access things in the `env` with `Symbol` keys. Right now though, an `env` of `{user: 'jez'}` is a type error. In this case, we may want to _opt out_ of some static checks on this specific argument, without opting out the method entirely. In this case, we can use [`T.untyped`](untyped.md):

```ruby
# typed: true
extend T::Sig

sig { params(env: T::Hash[Symbol, T.untyped], key: Symbol).void }
def log_env(env, key)
  puts "LOG: #{key} => #{env[key]}"
end

log_env({timeout_len: 2000, user: 'jez'}, :user)  # ok
```

`T.untyped` is a type that effectively makes a region of code act like it was written in a dynamically typed language with no static checks. By using `T.untyped` in specific arguments within a `sig`, we can silence most (but not all) errors relating to that argument.

> **Warning**: Be careful about opting out of static checks with `T.untyped`! Usually, we can rewrite our code to avoid silencing errors. For example, we could have refactored this code to use [Shape types](shapes.md), `Struct`s, or best of all: [Typed Structs](tstruct.md).

## Call-site granularity: `T.unsafe`

Using sigils and method signatures are the primary ways we opt _into_ static checks, and using `T.untyped` we can opt a specific argument _out of_ static checks.

One last way we can opt out of static checks is with `T.unsafe`. `T.unsafe` is a method (not a type) that returns its input unchanged and marks the result as `T.untyped`. Like how `T.untyped` in a signature lets us opt an argument out of type checks, `T.unsafe` lets us opt out a local variable or method call.

This is frequently necessary when using Ruby's various "metaprogramming" features:

```ruby
class A
  define_method(:foo) { puts 'In A#foo' }
end

a = A.new
a.foo             # => Method `foo` does not exist on `A`
T.unsafe(a).foo   # ok
```

The call to `T.unsafe` marks `a` as `T.untyped`, which causes Sorbet to silence the error about the method `foo` as missing. Note: sometimes what looks like a local variable is actually a method call on `self` in Ruby:

```ruby
define_singleton_method(:foo) { puts 'A.foo'; true }

if foo                 # => Method `foo` does not exist on `T.class_of(A)`
  puts 'succeeded'
end
```

In this case the tendency is to want to wrap the call to `foo` in `T.unsafe` (for example: `T.unsafe(foo)`) but in fact what we need to wrap is the method's **receiver** (the thing the method is being called on). When there is no explicit receiver, it's `self` in Ruby:

```ruby
define_singleton_method(:foo) { puts 'A.foo'; true }

if T.unsafe(self).foo  # ok
  puts 'succeeded'
end
```

The call to `T.unsafe(self)` evaluates to `self`, but forces Sorbet to think it has type `T.untyped`, which permits calling any method. Using `T.unsafe` we can limit untyped code to a specific call site, and make explicit where we're relying on dynamic behaviors.

## What's next?

- [Enabling Runtime Checks](runtime.md)

  The runtime component of Sorbet supports the static component. Learn how it works and how to best take advantage of it.

- [Gradual Type Checking](gradual.md)

  If you haven't already read this, learn what makes Sorbet different from other static type systems.
