Bug #9907


Abbreviated method assignment with private attr_writer/attr_reader does not work.

Added by jwmittag (Jörg W Mittag) about 8 years ago. Updated over 5 years ago.

Target version:
ruby -v:
ruby 2.2.0dev (2014-06-05 trunk 46357) [x86_64-darwin13]


This looks like a hole in the specification:

private def foo=(*) end
public  def foo; 0  end =  42 += 42
# private method `foo=' called for main:Object (NoMethodError)

private :foo += 42
# private method `foo' called for main:Object (NoMethodError)

There is an exception for private writers in the rule for private message sends, but apparently that exception needs to broadened so that it also works in the case of abbreviated assignments. I'm not entirely sure what this rule would be, but I don't think it would break backwards compatibility, since all situations that would work differently with the changed rule would currently raise a NoMethodError anyway.

The rule should be something like:

  • private methods can only be called without an explicit receiver.
  • An exception is made for method assignments, where the literal receiver self is also allowed in the assignee method expression.
  • This also applies to compound assignments: ω= bar shall always succeed if either or both of foo and foo= are private.

Related issues 2 (0 open2 closed)

Related to Ruby master - Bug #10060: private attr_accessor and NoMethodErrorClosed07/18/2014Actions
Related to Ruby master - Bug #11096: 'private' access control bypassed when ||= is usedClosedActions

Updated by nobu (Nobuyoshi Nakada) about 8 years ago

  • Backport changed from 2.0.0: UNKNOWN, 2.1: UNKNOWN to 2.0.0: REQUIRED, 2.1: REQUIRED

Updated by nobu (Nobuyoshi Nakada) about 8 years ago

  • Description updated (diff)

Updated by nobu (Nobuyoshi Nakada) about 8 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

Applied in changeset r46365.

compile.c, parse.y: private op assign

  • compile.c (iseq_compile_each), parse.y (new_attr_op_assign_gen):
    allow op assign to a private attribute.
    [ruby-core:62949] [Bug #9907]

Updated by nobu (Nobuyoshi Nakada) about 8 years ago

  • Related to Bug #10060: private attr_accessor and NoMethodError added

Updated by nagachika (Tomoyuki Chikanaga) almost 8 years ago

  • Backport changed from 2.0.0: REQUIRED, 2.1: REQUIRED to 2.0.0: REQUIRED, 2.1: DONTNEED

This seems a spec change for me. Any comments?

Updated by headius (Charles Nutter) almost 8 years ago

I'm confused about how private dispatch against self should behave. I expected that at least all self.x calls would be considered private dispatch, but that's not the case.

For this code:

def foo; @a; end
def foo=(a); @a = a; end

puts begin; foo; rescue; 'foo failed'; else; 'foo worked'; end
puts begin;; rescue; ' failed'; else; ' worked'; end
puts begin; = 1; rescue; ' = 1 failed'; else; ' = 1 worked'; end
puts begin; += 1; rescue; ' += 1 failed'; else; ' += 1 worked'; end
puts begin; o = self;; rescue; 'o = self; failed'; else; 'o = self; worked'; end
puts begin; o = self; = 1; rescue; 'o = self; = 1 failed'; else; 'o = self; = 1 worked'; end

I have the following behavior with 2.2.0-preview1:

foo worked failed = 1 worked += 1 worked
o = self; failed
o = self; = 1 failed

On 2.1, the += 1 case failed, which is the purpose of this bug.

However, I don't see why that case should pass and should fail. += must do an implicit call, meaning the "self" exception for private dispatch propagates through. It makes no sense that directly calling would produce an error and implicitly calling would not.

I would also argue that the "o" cases should pass as well, but I'm more concerned about the inconsistent spec change in this bug.

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

I think, += is a kind of assignment, so it should allow even private attributes as a whole.

Updated by headius (Charles Nutter) over 7 years ago

The following two pieces of code should both work, since they both do the same thing. VM-level exceptions hurt understandability, and it should come as a surprise to any Rubyist that can skip visibility checks but can't. += 1 # ok

AND = + 1 # visibility error

The first line essentially expands to the second line. The first line works, the second line doesn't. That makes no sense to me. Also, what if I had " += 1" (working fine after this bug) and later wanted to expand the logic to add some additional operations. It would break, and I wouldn't understand why. = * 5 + 1 # visibility error

I think consistency wins here and should be able to skip visibility checks too.

Updated by Eregon (Benoit Daloze) over 7 years ago

Until now, the ability to call private methods has always been a property verifiable at parse time
(no receiver or "receiver is self and methods ends with =").

I think it is good to remain that way so it is really easy to know whether you may call private method, as a Ruby user.
So I think the "o" cases above should not be able to call private methods.

On the other hand, the current semantics seem less clear and more complex than they could be.
I agree with Charles, the "no explicit receiver or receiver is literally self" rule is simpler, more consistent and avoid problems with special cases such as assignments.

Having a hidden implicit visibility such as in the committed change seems harder for everyone to understand and may only solve a part of the special cases.

Updated by siimliiser (Siim Liiser) over 5 years ago

This issue seems to have resurfaced. The issue is fixed in 2.2, but broken in both 2.3 and 2.4.

Actions #11

Updated by nobu (Nobuyoshi Nakada) over 5 years ago

  • Related to Bug #11096: 'private' access control bypassed when ||= is used added

Updated by nobu (Nobuyoshi Nakada) over 5 years ago

Intentional fix.


Also available in: Atom PDF