Proper type checking can be challenging in dynamically typed languages like Ruby. Nonetheless, I wound up going down this road as a side effect of my work on Solargraph. The current version (0.39.7 at time of writing) now ships with a type checking tool. While it's still a work in progress, it's mature enough that I use it regularly in my own projects. In this post, I'll discuss the history behind it and provide a brief introduction to how it works.
I started Solargaph several years ago out of an interest in improving the tooling ecosystem around Ruby. At the time, I was developing an Eclipse plugin for Gamefic, another Ruby project of mine. The plugin quickly grew from a domain-specific application to a general-purpose Ruby tool. After the release of Visual Studio Code and the introduction of the Language Server Protocol, I decided to turn Solargraph into an editor-agnostic language server.
Most of my early work focused on capabilities like autocompletion and inline documentation. My early approach was to provide basic mapping of class and method definitions augmented by YARD annotation. Over time, the mapping evolved to provide type inference through static analysis.
As the static analysis improved, it seemed like a natural progression to add type checking as a diagnostics feature.
The type checker is somewhat similar to Sorbet, with the key difference being that Solargraph uses YARD comments for annotation. Type safety isn't enforced at run-time. My approach has been to treat type discrepancies as code smells. You can use the checker to report problems in your editor or add it to your CI/CD workflow with varying degrees of strictness.
Code smells that the type checker can detect include the following:
- Invalid method call arity (e.g., not enough or too many arguments)
- Undefined or unknown types
- Unresolved method or variable names
- Incomplete documentation (e.g., methods without
@returntags that disagree with types inferred from code
@paramtags that disagree with types inferred from method arguments
The type checker can run at four levels:
strong. See the type checker documentation for more information about the rules enforced at each level.
There are two primary ways to use Solargraph's type checker: the command-line tool and the language server's diagnostics capability.
You can run the type checker from the command line in the root directory of your project. We'll use a directory containing the following
class Example # @return [String] def foo "foo" end # @return [InvalidClassName] def bar "bar" end # @return [Integer] def baz "baz" end end Example.new.foo.downcase Example.new.foo.uppercase
Run the type checker:
$ solargraph typecheck ./example/example.rb:8 - Unresolved return type InvalidClassName for Example#bar
normal level enforces the bare minimum of type safety, so the only error it finds is the unrecognized
InvalidClassName class. At the
typed level or higher, it'll catch the type discrepancy in
$ solargraph typecheck --level typed ./example/example.rb:8 - Unresolved return type InvalidClassName for Example#bar ./example/example.rb:13 - Declared return type Integer does not match inferred type String for Example#baz 2 problems found.
strict level, it will also catch the unknown
$ solargraph typecheck --level strict ./example/example.rb:8 - Unresolved return type InvalidClassName for Example#bar ./example/example.rb:13 - Declared return type Integer does not match inferred type String for Example#baz ./example/example.rb:19 - Unresolved call to uppercase 3 problems found.
If you use the Solargraph extension for VS Code, you can configure your editor to use the type checker for diagnostics by adding it to your project's
reporters: - typecheck
You can also specify a level:
reporters: - typecheck:strict
Type errors will appear in the Problems console.
This feature should also work in any other language client that supports diagnostics.
Check the following links to learn more about Solargraph and try it for yourself.
Claim your page on DEV before someone else does
Level up every day