This is just part of Ruby's ambiguity resolution.
In Ruby, methods and variables live in different namespaces, therefore there can be methods and variables (or constants) with the same name. This means that, when using them, there needs to be some way to distinguish them. In general, that's not a problem: messages have receivers, variables don't. Messages have arguments, variables don't. Variables are assigned to, messages aren't.
The only problem is when you have no receiver, no argument and no assignment. Then, Ruby cannot tell the difference between a receiverless message send without arguments and a variable. So, it has to make up some arbitrary rules, and those rules are basically:
- for an ambiguous token starting with a lowercase letter, prefer to interpret it as a message send, unless you positively know it is a variable (i.e. the parser (not(!) the interpreter) has seen an assignment before)
- for an ambiguous token starting with an uppercase letter, prefer to interpret it as a constant
Note that for a message send with arguments (even if the argument list is empty), there is no ambiguity, which is why your third example works.
test()
: obviously a message send, no ambiguity here
test
: might be a message send or a variable; resolution rules say it is a message send
Test()
: obviously a message send, no ambiguity here
self.Test
: also obviously a message send, no ambiguity here
Test
: might be a message send or a constant; resolution rules say it is a constant
Note that those rules are a little bit subtle, for example here:
if false
foo = 'This will never get executed'
end
foo # still this will get interpreted as a variable
The rules say that whether an ambiguous token gets interpreted as a variable or a message send is determined by the parser and not the interpreter. So, because the parser has seen foo = whatever
, it tags foo
as a variable, even though the code will never get executed and foo
will evaluate to nil
as all uninitialized variables in Ruby do.
TL;DR summary: you're SOL.
What you could do is override const_missing
to translate into a message send. Something like this:
class DemoClass
def test; puts "output from test" end
def Test; puts "output from Test" end
def run
puts "Calling 'test'"
test()
puts "Calling 'test'"
test
puts "Calling 'Test()'"
Test()
puts "Calling 'Test'"
Test
end
def self.const_missing(const)
send const.downcase
end
end
demo = DemoClass.new
demo.run
Except this obviously won't work, since const_missing
is defined on DemoClass
and thus, when const_missing
is run, self
is DemoClass
which means that it tries to call DemoClass.test
when it should be calling DemoClass#test
via demo.test
.
I don't know how to easily solve this.