Created
June 4, 2018 13:08
-
-
Save JoshCheek/83c408348b5c597b73622a81b90c6e70 to your computer and use it in GitHub Desktop.
A Ruby runtime type checker based off Stripe's "Sorbet" (https://tinyurl.com/y7tzjutf)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# An implementation to make this code example work: https://tinyurl.com/y7tzjutf | |
class Sig | |
def initialize(param_types) | |
@param_types = param_types | |
end | |
def returns(return_type) | |
@return_type = return_type | |
self | |
end | |
def named(name) | |
@name = name | |
self | |
end | |
def with_params(parameters) | |
@parameters = parameters | |
return self if @param_types.keys.sort == parameters.map(&:last).sort | |
raise "Declared type (#{@param_types.inspect}) does not match signature (#{parameters.inspect})" | |
end | |
def check_args!(args, block) | |
# this is only really valid for required (aka ordinal) args, but good enough for the example | |
@parameters.map(&:last).zip(args) do |name, val| | |
expected = @param_types[name] | |
actual = val.class | |
next if expected == actual | |
raise "Expected #{name.inspect} to have type #{expected.inspect}, but got #{actual.inspect}" | |
end | |
nil | |
end | |
def check_return!(returned) | |
return returned unless defined? @return_type | |
klass = returned.class | |
return returned if @return_type == klass | |
raise TypeError, "Return type mismatch! Expected #{@return_type.inspect}, got #{klass.inspect}" | |
end | |
end | |
class Module | |
def sig(**param_types) | |
@next_sig = Sig.new(param_types) | |
end | |
def method_added(name) | |
return unless @next_sig | |
original = instance_method name | |
sig = @next_sig.named(name).with_params(original.parameters) | |
@next_sig = nil | |
define_method name do |*args, &block| | |
sig.check_args! args, block | |
returned = original.bind(self).call(*args, &block) | |
sig.check_return! returned | |
end | |
end | |
end | |
# # # # # # # # # # # # # # # # | |
class BoxedInt | |
sig val: Integer | |
def initialize(val) | |
@val = val | |
end | |
sig(num: Integer).returns(BoxedInt) | |
def + num | |
BoxedInt.new @val + num | |
end | |
sig(num: Integer).returns(BoxedInt) | |
def - num | |
BoxedInt.new @val - num | |
end | |
sig.returns(String) | |
def inspect | |
"BoxedInt.new(#{@val.inspect})" | |
end | |
end | |
n = BoxedInt.new 10 | |
n + 2 # => BoxedInt.new(12) | |
n - 3 # => BoxedInt.new(7) | |
n + 5 - 3 # => BoxedInt.new(12) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment