Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix for retrieving UnboundMethod parameters #2232

Merged
merged 5 commits into from

3 participants

Yorick Peterse Dirkjan Bussink Ryo Onodera
Yorick Peterse

This pull request patches UnboundMethod#parameters (and the code it depends on) so that it returns the correct argument types and names when dealing with method definitions that contain local variables. Thanks to @dbussink for helping me out with these changes!

YorickPeterse added some commits
Yorick Peterse YorickPeterse Fix for retrieving UnboundMethod parameters.
Before this commit the code tasked with retrieving method parameters would
produce incorrect results. For example, take the following method definition:

    def example(number, &block)
      another_number = 20
    end

    method(:example).parameters

This would lead to the following output:

    [[:req, :number], [:block, :block], [:block, :another_number]]

This was caused due to everything that remains (after processing other argument
types) being assigned as block arguments.

This patch fixes this issue by keeping track of the index of the block argument
and comparing this to the list of local variables similar to how splat
arguments are handled.

Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
215c42d
Yorick Peterse YorickPeterse Tests for correctly retrieving block arguments.
Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
fdd376e
Yorick Peterse YorickPeterse Cleaned up code for retrieving method parameters.
The use of Array#map would require the use of Array#compact to get rid of
NilClass values in the parameters collection. An easier (and probably a bit
faster way) of doing this is to simply use Array#each and push values into
another Array.

Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
c8a2535
Yorick Peterse

Sidenote: I have seriously no idea why the tests fail of a sudden. It seems that mspec somehow ignores the tag for the failing test.

Ryo Onodera ryoqun commented on the diff
kernel/common/method.rb
((9 lines not shown))
- p += 1 if @executable.splat
-
- @executable.local_names.each_with_index.map do |name, i|
- if i < m
- [:req, name]
- elsif i < o
- [:opt, name]
- elsif @executable.splat == i
- [:rest, name]
- elsif i < p
- [:req, name]
- else
- [:block, name]
- end
- end
+ return @executable.respond_to?(:parameters) ? @executable.parameters : []
Ryo Onodera Collaborator
ryoqun added a note

Thanks for contributing! I think this is suspicious for the sudden CI failure. I'm not absolutely sure, but probably, I think @executable can be some thing other than CompiledCode? How about changing this empty array with some thing like [:foo, :bar] to check the empty array of actual returned value really came from here in CI? If I'm wrong, I'll test your pull request locally.

Yorick Peterse Owner

The weird thing is that even with a clean working directory (before I committed these changes) the particular test would fail. This would indicate that something else is affecting it, but I have no idea why that would be the case all of a sudden.

Dirkjan Bussink Owner

I think you are still confused on tags work. If you run ./bin/mspec path/to/spec/file.rb directly, it will ignore tags. That means it will run all the specs, even the specs that are known to fail. The CI process does consider the tags, so you don't see the failure then. You won't see the failure either when running 'rake', since that just runs the CI task.

For you now, you can safely ignore this failing spec, since that's for a totally different issue, not related to the problem this pull request tries to fix.

Yorick Peterse Owner

Well that's the thing, the failures do occur both on Travis and when running rake.

Dirkjan Bussink Owner

Ah ok, then yeah, they do seem related to the changes here then. I don't see those currently for master or here locally, so there must be something different for you then indeed.

Yorick Peterse Owner

The odd thing is that when I reset my changes they still failed, which, according to Travis should not be possible. I'll take a closer look at this this evening.

Ryo Onodera Collaborator
ryoqun added a note

As I guessed, @executable can be BlockEnvironment::AsMethod along side CompiledCode. I fixed that at my branch based on your branch with this commit.

Could you rebase --interactive this, while unifying it into your refactoring commit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
kernel/common/compiled_code.rb
((5 lines not shown))
+ #
+ # @param [Fixnum] position
+ #
+ def block=(position)
+ if position
+ add_metadata(:block_index, position)
+ end
+ end
+
+ ##
+ # @return [Fixnum|NilClass]
+ #
+ def block
+ return get_metadata(:block_index)
+ end
+
Ryo Onodera Collaborator
ryoqun added a note

Is there any specific reason to use block instead of block_index?

Usually, block is used to mean BlockEnvironment around here. So, I think just reusing of block_index is just fine to avoid confusion (btw, I'm paused to think that why it is needed for CompiedCode to have BlockEnvironment)

Dirkjan Bussink Owner

I think it's a good idea to use block_index to prevent confusion on what it means. The term "block" means a different thing in almost every other context, so it can be confusing if people encounter it later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/compiler/generator.rb
@@ -320,6 +322,7 @@ def package(klass)
code.post_args = @post_args
code.total_args = @total_args
code.splat = @splat_index
+ code.block = @block_index
code.local_count = @local_count
Ryo Onodera Collaborator
ryoqun added a note

oh, this is why you chose the ambiguous name of block? I think splat should be changed to something like splat_index.

Any ideas?, @dbussink

Yorick Peterse Owner

Correct. I didn't want to deviate too much from the existing code, though I agree that both "block" and "splat" aren't accurate names since they contain indexes and not entire argument objects.

Dirkjan Bussink Owner

For now I think we can ignore splat, but I agree that it might be better to change it to splat_index then, but I think we can do that better in a different pull request / change set / commit.

Deleted user
ghost added a note

FWIW Rubinius::MachineCode uses "splat_position". It'd be cool to be consistent across the board.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Deleted user Unknown referenced this pull request from a commit
Robert Gleeson Rubinius::CompiledCode#splat -> Rubinius::CompiledCode#splat_index
Follow up on #2232, rename splat to splat_index.
I noticed that MachineCode has #splat_position. It might be a good idea to
rename that one too.

All tests are green, but I noticed that kernel/common/method.rb calls splat(now
splat_index) on something called @executable, which could blow up if it were not
Rubinius::CompiledCode.

Naming 'splat_index' the same everywhere might avoid that problem, though.
9f7ddcd
Dirkjan Bussink
Owner

@robgleeson Why not use splat_position as well in Rubinius::CompiledCode instead of having to change everything?

Deleted user

@dbussink Ah, I didn't know splat_position was used elsewhere and the preferred choice. Yeah, sounds better to go with splat_position. I'll try to do it, unless someone beats me to it.

ryoqun and others added some commits
Ryo Onodera ryoqun Delegate AsMethod#parameters down to CompiledCode cbee9a2
Yorick Peterse YorickPeterse Use "block_index" for block argument indexes.
Instead of using the name "block", which is rather ambiguous, the name
"block_index" should be used.

Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
c1b7723
Dirkjan Bussink dbussink merged commit 8acde7a into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 24, 2013
  1. Yorick Peterse

    Fix for retrieving UnboundMethod parameters.

    YorickPeterse authored
    Before this commit the code tasked with retrieving method parameters would
    produce incorrect results. For example, take the following method definition:
    
        def example(number, &block)
          another_number = 20
        end
    
        method(:example).parameters
    
    This would lead to the following output:
    
        [[:req, :number], [:block, :block], [:block, :another_number]]
    
    This was caused due to everything that remains (after processing other argument
    types) being assigned as block arguments.
    
    This patch fixes this issue by keeping track of the index of the block argument
    and comparing this to the list of local variables similar to how splat
    arguments are handled.
    
    Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
  2. Yorick Peterse

    Tests for correctly retrieving block arguments.

    YorickPeterse authored
    Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
  3. Yorick Peterse

    Cleaned up code for retrieving method parameters.

    YorickPeterse authored
    The use of Array#map would require the use of Array#compact to get rid of
    NilClass values in the parameters collection. An easier (and probably a bit
    faster way) of doing this is to simply use Array#each and push values into
    another Array.
    
    Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
Commits on Mar 25, 2013
  1. Ryo Onodera Yorick Peterse

    Delegate AsMethod#parameters down to CompiledCode

    ryoqun authored YorickPeterse committed
  2. Yorick Peterse

    Use "block_index" for block argument indexes.

    YorickPeterse authored
    Instead of using the name "block", which is rather ambiguous, the name
    "block_index" should be used.
    
    Signed-off-by: Yorick Peterse <yorickpeterse@gmail.com>
This page is out of date. Refresh to see the latest.
4 kernel/bootstrap/block_environment.rb
View
@@ -52,6 +52,10 @@ def splat
@block_env.compiled_code.splat
end
+ def block_index
+ @block_env.compiled_code.block_index
+ end
+
def file
@block_env.file
end
8 kernel/common/block_environment.rb
View
@@ -84,6 +84,10 @@ def arity
end
end
+ def parameters
+ @compiled_code.parameters
+ end
+
def file
@compiled_code.file
end
@@ -127,6 +131,10 @@ def ==(other)
@block_env == other.block_env
end
+
+ def parameters
+ @block_env.parameters
+ end
end
end
end
39 kernel/common/compiled_code.rb
View
@@ -75,6 +75,7 @@ def equivalent_body?(other)
@required_args == other.required_args and
@total_args == other.total_args and
@splat == other.splat and
+ @block_index == other.block_index and
@literals == other.literals and
@file == other.file and
@local_names == other.local_names
@@ -92,6 +93,24 @@ def local_slot(name)
end
##
+ # Stores the index of the block argument in the metadata storage.
+ #
+ # @param [Fixnum] position
+ #
+ def block_index=(position)
+ if position
+ add_metadata(:block_index, position)
+ end
+ end
+
+ ##
+ # @return [Fixnum|NilClass]
+ #
+ def block_index
+ return get_metadata(:block_index)
+ end
+
+ ##
# Return a human readable interpretation of this method.
#
# @return [String]
@@ -491,24 +510,30 @@ def get_metadata(key)
##
# For Method#parameters
def parameters
+ params = []
+
+ return params unless respond_to?(:local_names)
+
m = required_args - post_args
o = m + total_args - required_args
p = o + post_args
p += 1 if splat
- local_names.each_with_index.map do |name, i|
+ local_names.each_with_index do |name, i|
if i < m
- [:req, name]
+ params << [:req, name]
elsif i < o
- [:opt, name]
+ params << [:opt, name]
elsif splat == i
- [:rest, name]
+ params << [:rest, name]
elsif i < p
- [:req, name]
- else
- [:block, name]
+ params << [:req, name]
+ elsif block_index == i
+ params << [:block, name]
end
end
+
+ return params
end
##
21 kernel/common/method.rb
View
@@ -262,26 +262,7 @@ def source_location
end
def parameters
- return [] unless @executable.respond_to? :local_names
-
- m = @executable.required_args - @executable.post_args
- o = m + @executable.total_args - @executable.required_args
- p = o + @executable.post_args
- p += 1 if @executable.splat
-
- @executable.local_names.each_with_index.map do |name, i|
- if i < m
- [:req, name]
- elsif i < o
- [:opt, name]
- elsif @executable.splat == i
- [:rest, name]
- elsif i < p
- [:req, name]
- else
- [:block, name]
- end
- end
+ return @executable.respond_to?(:parameters) ? @executable.parameters : []
Ryo Onodera Collaborator
ryoqun added a note

Thanks for contributing! I think this is suspicious for the sudden CI failure. I'm not absolutely sure, but probably, I think @executable can be some thing other than CompiledCode? How about changing this empty array with some thing like [:foo, :bar] to check the empty array of actual returned value really came from here in CI? If I'm wrong, I'll test your pull request locally.

Yorick Peterse Owner

The weird thing is that even with a clean working directory (before I committed these changes) the particular test would fail. This would indicate that something else is affecting it, but I have no idea why that would be the case all of a sudden.

Dirkjan Bussink Owner

I think you are still confused on tags work. If you run ./bin/mspec path/to/spec/file.rb directly, it will ignore tags. That means it will run all the specs, even the specs that are known to fail. The CI process does consider the tags, so you don't see the failure then. You won't see the failure either when running 'rake', since that just runs the CI task.

For you now, you can safely ignore this failing spec, since that's for a totally different issue, not related to the problem this pull request tries to fix.

Yorick Peterse Owner

Well that's the thing, the failures do occur both on Travis and when running rake.

Dirkjan Bussink Owner

Ah ok, then yeah, they do seem related to the changes here then. I don't see those currently for master or here locally, so there must be something different for you then indeed.

Yorick Peterse Owner

The odd thing is that when I reset my changes they still failed, which, according to Travis should not be possible. I'll take a closer look at this this evening.

Ryo Onodera Collaborator
ryoqun added a note

As I guessed, @executable can be BlockEnvironment::AsMethod along side CompiledCode. I fixed that at my branch based on your branch with this commit.

Could you rebase --interactive this, while unifying it into your refactoring commit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
end
def owner
7 lib/compiler/ast/definitions.rb
View
@@ -292,12 +292,13 @@ def bytecode(g, recv)
class FormalArguments < Node
attr_accessor :names, :required, :optional, :defaults, :splat
- attr_reader :block_arg
+ attr_reader :block_arg, :block_index
def initialize(line, args, defaults, splat)
@line = line
@defaults = nil
@block_arg = nil
+ @block_index = nil
if defaults
defaults = DefaultArguments.new line, defaults
@@ -324,6 +325,8 @@ def initialize(line, args, defaults, splat)
def block_arg=(node)
@names << node.name
+
+ @block_index = @names.length - 1
@block_arg = node
end
@@ -410,6 +413,7 @@ def initialize(line, required, optional, splat, post, block)
@defaults = nil
@block_arg = nil
@splat_index = nil
+ @block_index = nil
@required = []
names = []
@@ -456,6 +460,7 @@ def initialize(line, required, optional, splat, post, block)
if block
@block_arg = BlockArgument.new line, block
names << block
+ @block_index = names.length - 1
end
@splat = splat
1  lib/compiler/ast/node.rb
View
@@ -76,6 +76,7 @@ def new_generator(g, name, arguments=nil)
meth.post_args = arguments.post_args
meth.total_args = arguments.total_args
meth.splat_index = arguments.splat_index
+ meth.block_index = arguments.block_index
end
meth
5 lib/compiler/generator.rb
View
@@ -266,6 +266,7 @@ def initialize
@splat_index = nil
@local_names = nil
+ @block_index = nil
@local_count = 0
@state = []
@@ -282,7 +283,8 @@ def initialize
attr_accessor :break, :redo, :next, :retry, :file, :name,
:required_args, :post_args, :total_args, :splat_index,
:local_count, :local_names, :primitive, :for_block,
- :current_block, :detected_args, :detected_locals
+ :current_block, :detected_args, :detected_locals,
+ :block_index
def execute(node)
node.bytecode self
@@ -320,6 +322,7 @@ def package(klass)
code.post_args = @post_args
code.total_args = @total_args
code.splat = @splat_index
+ code.block_index = @block_index
code.local_count = @local_count
code.local_names = @local_names.to_tuple if @local_names
15 spec/core/unboundmethod/fixtures/classes.rb
View
@@ -2,10 +2,10 @@ module MethodSpecs
class SourceLocation
- def location # This needs to be on this line
+ def location # This needs to be on this line
:location # for the spec to pass
end
-
+
def redefined
:first
end
@@ -17,12 +17,17 @@ def redefined
def original
end
- alias :aka :original
+ alias :aka :original
end
class Methods
def zero; end
+
+ def zero_with_locals
+ number = 10
+ end
+
def one_req(a); end
def two_req(a, b); end
@@ -30,6 +35,10 @@ def zero_with_block(&block); end
def one_req_with_block(a, &block); end
def two_req_with_block(a, b, &block); end
+ def zero_with_block_and_locals(&block)
+ number = 10
+ end
+
def one_opt(a=nil); end
def one_req_one_opt(a, b=nil); end
def one_req_two_opt(a, b=nil, c=nil); end
14 spec/core/unboundmethod/parameters_spec.rb
View
@@ -7,6 +7,12 @@
MethodSpecs::Methods.instance_method(:zero).parameters.should == []
end
+ it "returns an empty Array when the method expects no arguments but does have local variables" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_locals)
+
+ m.parameters.should == []
+ end
+
it "returns [[:req,:name]] for a method expecting one required argument called 'name'" do
MethodSpecs::Methods.instance_method(:one_req).parameters.should == [[:req,:a]]
end
@@ -21,6 +27,12 @@
m.parameters.should == [[:block,:block]]
end
+ it "returns [[:block,:block]] for a method expecting one block argument with local variables" do
+ m = MethodSpecs::Methods.instance_method(:zero_with_block_and_locals)
+
+ m.parameters.should == [[:block,:block]]
+ end
+
it "returns [[:req,:a],[:block,:b] for a method expecting a required argument ('a') and a block argument ('b')" do
m = MethodSpecs::Methods.instance_method(:one_req_with_block)
m.parameters.should == [[:req,:a], [:block,:block]]
@@ -126,4 +138,4 @@
m.parameters.should == [[:req,:a],[:req,:b],[:opt,:c],[:rest,:d],[:block,:block]]
end
-end
+end
2  spec/custom/helpers/generator.rb
View
@@ -91,7 +91,7 @@ def send_vcall(meth)
attr_accessor :stream, :ip, :redo, :break, :next, :retry,
:name, :file, :line, :primitive, :for_block,
:required_args, :post_args, :total_args, :splat_index,
- :local_count, :local_names
+ :local_count, :local_names, :block_index
def initialize
Something went wrong with that request. Please try again.