Open JoshCheek opened 6 years ago
Note that this refactoring did not have the issue:
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
$!.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 + num2 # ~> TypeError: Sig doesn't match the method for BoxedInt#+\n declared: [:num]\n observed: [:num2]\n
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 # =>
n - 3 # =>
n + 5 - 3 # =>
# ~> TypeError
# ~> Sig doesn't match the method for BoxedInt#+
# ~> declared: [:num]
# ~> observed: [:num2]
# ~>
# ~> /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20180607-91903-1lu6wgo/program.rb:97:in `<class:BoxedInt>'
# ~> /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20180607-91903-1lu6wgo/program.rb:90:in `<main>'
While playing with it, I changed the arg to
BoxedInt#-
to be namednum2
, the backtrace says it comes from sib/the_matrix.rbI don't really understand what's going on here, maybe a bug.