Overriding the hash and creating [] private operators - can not use || = more

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 . However, only set works, and set_maybe fails:

 [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 suggested that self[:b] ||= <x> is just syntactic sugar for self[:b] || self[:b] = <x> self[:b] || self[:b] = <x> , but I think this is not because it works.

What bothers me, why am I getting this error. I am doing this from within the class, so why am I getting a private method error?

+6
source share
3 answers

Handling private methods is currently a bit messy.

The original rule:

private methods can be called without an explicit receiver.

This is a nice, simple, easy to understand rule. This is also a static rule, that is, it can be checked without running the code, in fact it is even a syntax rule, it does not even need complex static analysis, it can be checked in the parser.

However, it was soon noticed that this rule made it impossible to call private setters, since setters cannot be called without an explicit recipient ( foo = bar is a local variable parameter that does not call the setter). Ergo, the rule has been expanded:

Private methods

can only be called without an explicit receiver, if the method call is not an assignment method call, in which case the method can also be called with an explicit receiver, if this explicit receiver is a 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 the 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 during parsing.

Two years ago, I filed an error stating that abbreviated assignment assignments do not work, that is:

 self.foo += bar # NoMethodError 

This error was fixed by re-expanding the rule for calling private methods (and now the rule is already becoming so complex that I am not going to write it down).

However, there are still many cases that are not covered by the existing rules, where methods simply cannot be called syntactically without an explicit receiver and, therefore, cannot be private:

 self[foo] !self self + foo 

and etc.

Some of them are fixed, some are not. The problem is that the rule has now become so complex that it is difficult to implement correctly. There were suggestions for changing the rule like this:

private methods can only be called without an explicit receiver or explicit receiver, which is a literal pseudo-variable self .

This is a good, simple, easy-to-understand rule that can be checked statically during parsing and does not have any of the difficult exceptions and corner cases that we currently have. However, it has not yet been implemented by AFAIK.

+3
source

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 that the problem is this: calls 2.2.0

 0010 opt_aref <callinfo!mid:[], argc:1, FCALL|ARGS_SIMPLE> ... 0013 branchif 25 ... 0020 opt_aset <callinfo!mid:[]=, argc:2, FCALL|ARGS_SIMPLE> 

In principle, evaluate [] , see if it is false, and if so, call []= . But 2.3.0 does not use the FCALL flag when calling [] :

 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 the call with an implicit receiver ( foo() ); without FCALL , the call is an explicit receiver ( self.foo() or bar.foo() ), which is prohibited by private methods.

Now, why does 2.3.0 do this ... I donโ€™t know.

+3
source

I suggested that self[:b] ||= <x> is just syntactic sugar for self[:b] || self[:b] = <x> self[:b] || self[:b] = <x>

Yes it is. But this does not mean that he rewrites the code in place, like a preprocessor. It extends inside the main Ruby code, and I think this is not subject to a rule that allows explicit self using a private method. Perhaps you can consider this feature again.

+2
source

All Articles