Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange error where exception backtrace shows that it came from SiB #132

Open
JoshCheek opened this issue Jun 7, 2018 · 1 comment
Open

Comments

@JoshCheek
Copy link
Owner

While playing with it, I changed the arg to BoxedInt#- to be named num2, the backtrace says it comes from sib/the_matrix.rb

I don't really understand what's going on here, maybe a bug.

class Sig
  def initialize
    @known = Hash.new { |h, mod| h[mod] = {} }
    @current = nil
  end
  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
    @known[mod][method.name] = {receives: param_types, returns: return_type}
    declared_names = param_types.keys.sort
    observed_names = method.parameters.map(&:last).sort
    declared_names == observed_names or raise TypeError, <<~MSG
      Sig doesn't match the method for #{mod}##{method.name}
        declared: #{declared_names.inspect}
        observed: #{observed_names.inspect}
    MSG
    @current = nil
    self
  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

  def get_sig(obj, method_name)
    @known.key?(obj.class) && @known[obj.class][method_name]
  end
end

Sig::INSTANCE = Sig.new

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|
  next erroring = false if erroring
  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 + num
    BoxedInt.new @val + num
  end

  sig(num: Integer).returns(BoxedInt)
  def - num2
    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  # => 

# !> /Users/josh/.gem/ruby/2.5.0/gems/seeing_is_believing-3.6.0/lib/seeing_is_believing/the_matrix.rb:78:in `define_method': Sig doesn't match the method for BoxedInt#message (TypeError)
# !>   declared: [:num]
# !>   observed: []
# !> \tfrom /Users/josh/.gem/ruby/2.5.0/gems/seeing_is_believing-3.6.0/lib/seeing_is_believing/the_matrix.rb:78:in `block (2 levels) in <top (required)>'
# !> \tfrom /Users/josh/.gem/ruby/2.5.0/gems/seeing_is_believing-3.6.0/lib/seeing_is_believing/the_matrix.rb:78:in `class_eval'
# !> \tfrom /Users/josh/.gem/ruby/2.5.0/gems/seeing_is_believing-3.6.0/lib/seeing_is_believing/the_matrix.rb:78:in `block in <top (required)>'
# !> /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20180607-91489-n76wa7/program.rb:94:in `<class:BoxedInt>': Sig doesn't match the method for BoxedInt#- (TypeError)
# !>   declared: [:num]
# !>   observed: [:num2]
# !> \tfrom /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20180607-91489-n76wa7/program.rb:82:in `<main>'
@JoshCheek
Copy link
Owner Author

JoshCheek commented Jun 7, 2018

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>'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant