Ruby sorbet: Difference between revisions
From wikinotes
No edit summary |
|||
Line 15: | Line 15: | ||
|} | |} | ||
</blockquote><!-- documentation --> | </blockquote><!-- documentation --> | ||
= Locations = | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
| <code>sorbet/config</code> || config | |||
|- | |||
| <code>sorbet/rbi/shims/*.rbi</code> || add your own custom stubs | |||
|- | |||
|} | |||
</blockquote><!-- Locations --> | |||
= Usage = | = Usage = | ||
Line 27: | Line 38: | ||
srb tc --lsp | srb tc --lsp | ||
</source> | </source> | ||
Include <code>sig</code> by requiring <code>sorbet-runtime</code> | |||
</blockquote><!-- Usage --> | </blockquote><!-- Usage --> | ||
= Configuration = | |||
<blockquote> | |||
== Disable sorbet-runtime == | |||
<blockquote> | |||
You may want to temporarily disable sorbet-runtime for an executable, environment etc. | |||
<syntaxhighlight lang="ruby"> | |||
require "sorbet-runtime" | |||
T::Configuration.call_validation_error_handler = nil | |||
</syntaxhighlight> | |||
</blockquote><!-- Disable sorbet-runtime --> | |||
</blockquote><!-- Configuration --> | |||
= Basics = | = Basics = | ||
Line 45: | Line 71: | ||
class Person | class Person | ||
extend T::Sig # adds 'sig' method | extend T::Sig # adds 'sig' method | ||
extend T::Helpers # adds 'requires_ancestor, abstract!, interface!, sealed!, ...' methods | extend T::Helpers # adds 'requires_ancestor, abstract!, interface!, sealed!, ...' methods | ||
sig { params(name: ::String, age: ::Integer, object: T.untyped).void } | sig { params(name: ::String, age: ::Integer, object: T.untyped).void } | ||
Line 198: | Line 224: | ||
StudentMap = T.type_alias { T::Hash[Symbol, String] } | StudentMap = T.type_alias { T::Hash[Symbol, String] } | ||
sig { params(student_map: StudentMap).returns(Integer) } | sig { params(student_map: StudentMap).returns(Integer) } | ||
def count_students(student_map) | def count_students(student_map) |
Revision as of 01:02, 29 July 2023
Implements static typing for ruby.
You may also be interested in ruby tapioca to automatically generate rbi files.
Documentation
homepage https://sorbet.org/ github https://github.com/sorbet/sorbet gem src (no apidocs) https://github.com/sorbet/sorbet/tree/master/gems sig documentation https://sorbet.org/docs/sigs
Locations
sorbet/config
config sorbet/rbi/shims/*.rbi
add your own custom stubs
Usage
bundle exec srb tc --help 2>&1 | less -Ri # show help in less bundle exec srb tc 2>&1 | grep -C5 ClassICareAbout # check for errors in specific classYou can also use sorbet as an LSP.
srb tc --lspInclude
sig
by requiringsorbet-runtime
Configuration
Disable sorbet-runtime
You may want to temporarily disable sorbet-runtime for an executable, environment etc.
require "sorbet-runtime" T::Configuration.call_validation_error_handler = nil
Basics
# person.rb class Person def create(name:, age:, object:); end def speak; "foo"; end end# person.rbi class Person extend T::Sig # adds 'sig' method extend T::Helpers # adds 'requires_ancestor, abstract!, interface!, sealed!, ...' methods sig { params(name: ::String, age: ::Integer, object: T.untyped).void } def create(name:, age:, object:); end sig { returns(String) } def speak; "foo"; end end
Common Cases
attr_reader/writer/accessor
sig { returns(String) } attr_reader :nameinclude module
require_ancestors https://sorbet.org/docs/requires-ancestor mix_in_class_methods https://sorbet.org/docs/abstract instance methods should just work
module AnimalMethods sig { returns(T.void) } def walk(); end end class Cat include AnimalMethods endclass methods
module AnimalMethods extend T::Sig extend T::Helpers # modify to set classmethods on include def self.included(base = nil) base&.extend(ClassMethods) end # define classmethods module ClassMethods sig { returns(String) } def speak(); end end # inform sorbet of classmethods mixes_in_class_methods ClassMethods end class Cat include AnimalMethods endrails concerns && included do
procs/lambdas
sig { params(block: T.proc.params(arg0: Integer).void).void } def foo(&block)If your proc is designed to be executed in a different context
(for example, proc is set as a class method, but designed to run on a class instance)
you can useT.bind(self, YourClass)
.
Types
# stdlib types String Symbol Integer Float NilClass # generic types T::Array[Integer] T::Set[Integer] T::Hash[Symbol, String] T::Enumerable[Integer] T::Enumerator[Integer] T::Range[Integer] # sorbet types T::Boolean # true/false T.untyped # anything T.nilable(String) # Unions: string or nil T.any(String, Integer) # Unions: either string or integer T.noreturn # if method never returns (exception, exit process, loop forever, ...) sig { params(...).void } # ignore return value, not meant to be used
Generics
# a generic method, that takes param of arbitrary type 'T' and returns type 'T' sig do type_parameters(:T) .params(var: T.type_parameter(:T)) .returns(T.type_parameter(:T)) end def foo(var) var end
Tools
T.type_alias T.bind(self, YourClass) # use within proc, to indicate the instance it should be executed in (defaults to current) T.cast(var, YourClass) mixes_in_class_methods ClassMethods # when class included, ClassMethods become static methods on target class requires_ancestor Kernel # any class that includes this module, must also include Kernel
Tricks
Type Aliases
You can define re-usable type-sigs with type aliases.
class University extend T::Sig StudentMap = T.type_alias { T::Hash[Symbol, String] } sig { params(student_map: StudentMap).returns(Integer) } def count_students(student_map) student_map.count end sig { params(student_map: StudentMap).returns(List[String]) } def name_students(student_map) student_map.keys() end endDynamically setting a Type Signature
class Foo sig { params(a: Integer).void } def bar(a) = nil end T::Private::Methods.declare_sig(Foo, nil, :final) { params(a: String).void } declaration = T::Private::DeclState.current.active_declaration T::Private::Methods.run_sig(Foo, :bar, Foo.instance_method(:bar), declaration) # voila! signature installed T::Utils.signature_for_instance_method(Foo, :bar)