Last active
June 7, 2018 12:39
-
-
Save JoshCheek/7155818c8a60a6db589018d0bd3e6b0a to your computer and use it in GitHub Desktop.
A second implementation of the Sorbet API, this time with TracePoint
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
class Sig | |
INSTANCE = new | |
def declared(mod, param_types) | |
@current = [mod, param_types] | |
self | |
end | |
def returns(return_type) | |
@current << return_type if @current | |
self | |
end | |
def observed(method) | |
return unless @current | |
mod, param_types, return_type = @current | |
@current = nil | |
known[mod][method.name] = {receives: param_types, returns: return_type} | |
declared = param_types.keys.sort | |
observed = method.parameters.map(&:last).sort | |
declared == observed or raise TypeError, <<~MSG | |
Sig doesn't match the method for #{mod}##{method.name} | |
declared: #{declared.inspect} | |
observed: #{observed.inspect} | |
MSG | |
end | |
def check_call(binding, obj, method_name) | |
return unless sig = get_sig(obj, method_name) | |
sig[:receives].each do |name, type| | |
value = binding.local_variable_get name | |
next if value.kind_of? type | |
raise TypeError, "#{name} was #{value.inspect}, but expected a #{type.inspect}" | |
end | |
end | |
def check_return(obj, method_name, value) | |
return unless sig = get_sig(obj, method_name) | |
return if value.kind_of? sig[:returns] | |
raise TypeError, "#{method_name} returned #{value.inspect}, but expected a #{sig[:returns].inspect}" | |
end | |
private | |
def known | |
@known ||= Hash.new { |h, mod| h[mod] = {} } | |
end | |
def get_sig(obj, method_name) | |
known.key?(obj.class) && known[obj.class][method_name] | |
end | |
end | |
class Module | |
def sig(**types) | |
Sig::INSTANCE.declared(self, types) | |
end | |
def method_added(name) | |
Sig::INSTANCE.observed instance_method(name) | |
rescue TypeError | |
$!.set_backtrace caller.drop(1) | |
raise | |
end | |
end | |
erroring = false # need this b/c, tp will still call return (w/ return_value set to nil) | |
TracePoint.trace :call, :return do |tp| | |
if erroring | |
erroring = false | |
next | |
end | |
begin | |
case tp.event | |
when :call | |
Sig::INSTANCE.check_call tp.binding, tp.self, tp.method_id | |
when :return | |
Sig::INSTANCE.check_return tp.self, tp.method_id, tp.return_value | |
end | |
rescue TypeError # block rescue added in 2.5, but that's new enough that I won't use it | |
$!.set_backtrace caller.drop(1) | |
erroring = true | |
raise | |
end | |
end | |
############################################### | |
class BoxedInt | |
sig(val: Integer).returns(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 2 | |
n + 10 # => BoxedInt.new(12) | |
n - 3 # => BoxedInt.new(-1) | |
n + 5 - 3 # => BoxedInt.new(4) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment