Test code:
class PrivHash < Hash
def set(key, val)
self[key] = val
end
def set_maybe(key, val)
self[key] ||= val
end
private
def []= key, value
end
def [] key
super
end
end
With this code, I expect both set
and set_maybe
to work. However, only set
works and set_maybe
fails with:
[30] pry(#<TranslationInfo>):1> ph.set_maybe(:a, 1)
NoMethodError: private method `[]' called for {:a=>2}:#Class:0x007f99c5924c38>::PrivHash
from (pry):56:in `set_maybe'
I assumed that self[:b] ||= <x>
is just syntactic sugar for self[:b] || self[:b] = <x>
, but I guess it isn't because this works.
What bugs me is why I am getting this error.. I am executing this from within the class so why am I getting private method error?
Handling of private methods is a bit of a mess, currently.
The original rule was:
private methods can only be called without an explicit receiver.
This is a nice, simple, easily understandable rule. It is also a static rule, i.e. it can be checked without running the code, in fact, it is even a syntactic rule, it doesn't even need sophisticated static analysis, it can be checked in the parser.
However, it was soon noticed that this rule makes it impossible to call private setters, since setters can't be called without an explicit receiver (foo = bar
is a setting a local variable, not calling a setter). Ergo, the rule was extended:
private methods can only be called without an explicit receiver, unless the method call is an assignment method call, in which case the method can also be called with an explicit receiver as long as that explicit receiver is the literal pseudo-variable
self
.
This allows you to call private setters with an explicit receiver of the literal value self
:
self.foo = bar
but not a dynamic value of self
baz = self
baz.foo = bar # NoMethodError: private method `foo=' called
This still preserves the property that private method calls can be detected at parse time.
Two years ago, I filed a bug about abbreviated method assignments not working, i.e.:
self.foo += bar # NoMethodError
That bug was fixed by again extending the rule for private method calls (and now the rule is already getting so complex that I'm not going to spell it out).
However, there are still a lot of cases left that are not covered by the existing rules, where methods simply cannot syntactically be called without an explicit receiver and thus cannot be private:
self[foo]
!self
self + foo
etc.
Some of those have been fixed, some haven't. The problem is that the rule has now gotten so complex that it is hard to implement correctly. There have been proposals to change the rule to something like this:
private methods can only be called without an explicit receiver or an explicit receiver which is the literal pseudo-variable
self
.
That is a nice, simple, easily understandable rule, can be statically checked at parse time, and has none of the complex exceptions and corner cases we have currently. It is, however, not yet implemented AFAIK.
I tried to decompile it.
code = <<CODE
class PrivHash < Hash
def set(key, val)
self[key] = val
end
def set_maybe(key, val)
self[key] ||= val
end
private
def []= key, value
end
def [] key
super
end
end
CODE
disasm = RubyVM::InstructionSequence.compile(code).disasm
File.write("#{RUBY_VERSION}.txt", disasm)
Based on the results, I conclude the issue is this: 2.2.0 calls
0010 opt_aref <callinfo!mid:[], argc:1, FCALL|ARGS_SIMPLE>
...
0013 branchif 25
...
0020 opt_aset <callinfo!mid:[]=, argc:2, FCALL|ARGS_SIMPLE>
Basically, evaluate []
, see if it's falsy, and if so, call []=
. But 2.3.0 doesn't use FCALL
flag on the []
call:
0010 opt_aref <callinfo!mid:[], argc:1, ARGS_SIMPLE>, <callcache>
...
0014 branchif 27
...
0021 opt_aset <callinfo!mid:[]=, argc:2, FCALL|ARGS_SIMPLE>, <callcache>
The FCALL
flag identifies a call with an implicit receiver (foo()
); without FCALL
, the call is to an explicit receiver (self.foo()
or bar.foo()
), which is prohibited by private
methods.
Now, why 2.3.0 does this... No idea.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With