14

According to the documentation for modules and classes, calling super (without arguments or parentheses) calls the parent method with the same arguments:

When used without any arguments super uses the arguments given to the subclass method.

Assigning a new value to the "argument variable" seems to alter this behavior:

class MyClass
  def foo(arg)
    puts "MyClass#foo(#{arg.inspect})"
  end
end

class MySubclass < MyClass
  def foo(arg)
    puts "MySubclass#foo(#{arg.inspect})"
    super
    arg = 'new value'
    super
  end
end

MySubclass.new.foo('inital value')

Output:

MySubclass#foo("inital value")
MyClass#foo("inital value")
MyClass#foo("new value")       # <- not the argument given to MySubclass#foo

Is this expected?

Update

This seems to be the expected behavior for positional and keyword arguments, but it doesn't work for block arguments:

class MyClass
  def foo(&block)
    puts "MyClass#foo { #{block.call.inspect} }"
  end
end

class MySubclass < MyClass
  def foo(&block)
    puts "MySubclass#foo { #{block.call.inspect} }"
    super
    block = Proc.new { 'new value' }
    super
  end
end

MySubclass.new.foo { 'initial value' }

Output:

MySubclass#foo { "initial value" }
MyClass#foo { "initial value" }
MyClass#foo { "initial value" }
Community
  • 1
  • 1
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • But.. you changed the value of the variable `arg`,,, that's why result got changed. Nothing _magic_ :) Documentation not looks like _wrong_. – Arup Rakshit Jan 16 '15 at 10:01
  • Read [keyword](http://ruby-doc.org/docs/keywords/1.9/) doco. It is more clear. – Arup Rakshit Jan 16 '15 at 10:02
  • I'm not a ruby expert, but it sounds logic to me. You did change the argument value so what is passed to parent method is the pointer to the variable which itself has no change but has a new content. (Feel free to correct me if I'm wrong) – Tensibai Jan 16 '15 at 10:04
  • 2
    It looks strange to me. `arg` before assignment is one local variable, and the assigned `arg` is another local variable with the same name. Unless calling the super method is done through explicit reference to the variable names, it does not make sense. – sawa Jan 16 '15 at 10:06
  • 2
    @ArupRakshit sure, I assign a new value to a local variable. But isn't `super` supposed to call the method using the same arguments? – Stefan Jan 16 '15 at 10:06
  • I am curious what happens if you receive the arguments without explicit names, like `def foo(_)` or `def foo(*)`. – sawa Jan 16 '15 at 10:09
  • 1
    @sawa you can assign to `_` which results in the same behavior. `*` on the other hand doesn't create any local variables. – Stefan Jan 16 '15 at 10:26
  • @Stefan Right. So it looks like the easy to think about it, which confirms to what Arup wrote, is that argument-less `super` is not evaluated separately, but is simply replaced by the exact expression as the method was called, and then the whole method is evaluated. I feel this is similar to how `include`-ing a module works. – sawa Jan 16 '15 at 10:29

3 Answers3

11

Lets take one example from the Ruby core:

Keyword2

class Base
  def single(a) a end
  def double(a, b) [a,b] end
  def array(*a) a end
  def optional(a = 0) a end
  def keyword(**a) a end
end

class Keyword2 < Base
  def keyword(foo: "keyword2")
    foo = "changed1"
    x = super
    foo = "changed2"
    y = super
    [x, y]
  end
end

Now, see the test case :-

def test_keyword2
  assert_equal([{foo: "changed1"}, {foo: "changed2"}], Keyword2.new.keyword)
end

Above example exactly mathes the keyword documentation.

Called with no arguments and no empty argument list, super calls the appropriate method with the same arguments, and the same code block, as those used to call the current method. Called with an argument list or arguments, it calls the appropriate methods with exactly the specified arguments (including none, in the case of an empty argument list indicated by empty parentheses).

same arguments means it is saying the current values of argument variables.test_super.rb files contains all the varieties of stuffs we can do with super in Ruby.

No, it work with block too (taken from core) :

a = Class.new do
  def foo
    yield
  end
end

b = Class.new(a) do
  def foo
    super{
      "b"
    }
  end
end

b.new.foo{"c"} # => "b"

But, have no idea why the below is giving "c"? This is actually the updated question of the OP:

c = Class.new do
  def foo(&block)
    block.call
  end
end

d = Class.new(c) do
  def foo(&block)
     block = -> { "b" }
    super
  end
end

d.new.foo{"c"} # => "c"
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • So it looks like `super` without arguments is not evaluating on its own what method and arguments to evaluate, but `super` is simply replaced with literally the same expression as it was called. – sawa Jan 16 '15 at 10:14
  • 3
    If you don't want that behaviour just call `super()` that'll call without arguments. – Sigurd Jan 16 '15 at 10:18
  • @Sigurd Humm.. The OP thought, values of argument variable Ruby cached, but Ruby doesn't. What it cached is the variable names, not their initial values. – Arup Rakshit Jan 16 '15 at 10:20
  • This is more about variable scope I guess. I did not expect `super` to evaluate the local variables every time it is called. – Stefan Jan 16 '15 at 10:32
  • 1
    Interestingly, this doesn't work for block arguments! (see update above) – Stefan Jan 16 '15 at 11:25
  • @Stefan See the update.. But I don't know. How your example differs from the one from core. Need to check. – Arup Rakshit Jan 16 '15 at 11:41
  • @ArupRakshit in your example, `super` is called with an explicit argument. – Stefan Jan 16 '15 at 11:59
4

It seems to be the expected behavior, based on the RubySpec anyway.

module RestArgsWithSuper
  class A
    def a(*args)
      args
    end
  end

  class B < A
    def a(*args)
      args << "foo"

      super
    end
  end
end

(language/fixtures/super.rb).

It's then expected that the arguments are modified:

it "passes along modified rest args when they weren't originally empty" do
  Super::RestArgsWithSuper::B.new.a("bar").should == ["bar", "foo"]
end

(language/super_spec.rb)

Jiří Pospíšil
  • 14,296
  • 2
  • 41
  • 52
0

It's the expected behaviour. Technically, arg is the same argument, it just points to another value.

This answer might explain it better: https://stackoverflow.com/a/1872159/163640

Community
  • 1
  • 1
eugen
  • 8,916
  • 11
  • 57
  • 65