Interpretation as a local variable overrides method name?
In Ruby, because methods can be called without an explicit receiver and without parentheses, there is a syntactic ambiguity between a local variable reference and a receiverless argumentless method call:
foo
could either mean "call method foo
on self
with no arguments" or "dereference local variable foo
".
If there exists a local variable foo
in scope, this is always interpreted as a local variable dereference, never as a method call.
So, what does it mean for a local variable to "be in scope"? This is determined syntactically at parse time, not semantically at runtime. This is very important! Local variables are defined at parse time: if an assignment to a local variable is seen by the parser, the local variable is in scope from that point on. It is, however, only initialized at runtime, there is no compile time evaluation of code going on:
if false
foo = 42 # from this point on, the local variable foo is in scope
end
foo # evaluates to nil, since it is declared but not initialized
Why does it make sense for local variables to "shadow" methods and not the way around? Well, if methods did shadow local variables, there would no longer be a way to dereference those local variables. However, if local variables shadow methods, then there is still a way to call those methods: remember, the ambiguity only exists for receiverless argumentless methods calls, if you add an explicit receiver or an explicit argument list, you can still call the method:
def bar; 'Hello from method' end; public :bar
bar # => 'Hello from method'
bar = 'You will never see this' if false
bar # => nil
bar = 'Hello from local variable'
bar # => 'Hello from local variable'
bar() # => 'Hello from method'
self.bar # => 'Hello from method'
Overriding a Local Variable name in Java Bytecode using the ASM library
You'll need to write an adapter (a subclass of ClassVisitor
) and chain it with reader
. For instance,
ClassReader reader = new ClassReader(new FileInputStream(new File("TheClass")));
ClassWriter writer = new ClassWriter(reader, 0);
TraceClassVisitor printer = new TraceClassVisitor(writer,
new PrintWriter(System.getProperty("java.io.tmpdir")
+ File.separator + name + ".log"));
ClassAdapter adapter = new ClassAdapter(printer);
reader.accept(adapter, 0);
byte[] b = writer.toByteArray();
With it you'll get byte[]
, which you can save into file, or load into Class
with a ClassLoader
.
(TraceClassVisitor
is just another ClassVisitor
that I chain it also to get a human-readable log in your temp directory.)
The adapter could look like the following. The method you'll want to override is visitLocalVariable
:
public class ClassAdapter extends ClassVisitor {
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM5, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new MethodAdapter(mv);
}
}
public class MethodAdapter extends MethodVisitor {
public MethodAdapter(MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
@Override
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {
// Put your rename logic here
if (name.equals("c"))
name = "e";
else if (name.equals("d"))
name = "f";
super.visitLocalVariable(name, desc, signature, start, end, index);
}
}
Strange local variable behavior in Ruby
Ruby determines the lifetime of local variables while it parses the code, so even if params = 1
assignment wouldn't be reached, params
will be interpreted as local variable (and set to nil
by default) in this scope.
Here's the link to documentation:
http://docs.ruby-lang.org/en/2.1.0/syntax/assignment_rdoc.html#label-Local+Variables+and+Methods
Getting local variable names defined inside a method from outside the method
You can (re)-parse the method and inspect the S-EXPR tree. See below for a proof of concept. You can get hold of the file where the method is defined using Method#source_location
and then read that file. There is surely room for improvement to my code but should get you started. It is a fully functional piece of code and only requires the ruby parser gem (https://github.com/whitequark/parser).
require 'parser/current'
node = Parser::CurrentRuby.parse(DATA.read) # DATA is everything that comes after __END__
def find_definition(node, name)
return node if definition_node?(node, name)
if node.respond_to?(:children)
node.children.find do |child|
find_definition(child, name)
end
end
end
def definition_node?(node, name)
return false if !node.respond_to?(:type)
node.type == :def && node.children.first == name
end
def collect_lvasgn(node)
result = []
return result if !node.respond_to?(:children)
node.children.each do |child|
if child.respond_to?(:type) && child.type == :lvasgn
result << child.children.first
else
result += collect_lvasgn(child)
end
end
result
end
definition = find_definition(node, :foo)
puts collect_lvasgn(definition)
__END__
def foo
var = 100
arr = [1,2]
if something
this = 3 * var
end
end
def bar
var = 200
arr = [3, 4]
end
Do you mind telling us WHY you want to find the variables?
Defining a method that checks the existence of a variable
In ruby, the keyword def
acts a scope gate, so you lose access to local variables that are defined outside of the method definition. Alternatively, you can define methods using a block, allowing you access to variables defined outside of the method definition.
a = '123'
define_method 'a?' do
defined? a
end
For more information on this, I highly recommend reading through the first section of Metaprogramming Ruby
Unexecuted code overrides local variable
Try this:
class Foo
attr_reader :bar
def initialize
p "instance methods defined in Foo: #{self.methods(false)}"
@bar = "abc"
p "defined? @bar: #{defined? @bar}"
p "bar: #{bar}"
p "defined? bar: #{defined? bar}"
if false
bar = "123"
end
p "defined? bar, 2nd time: #{defined? bar}"
p "bar.nil? = #{bar.nil?}"
p "self.bar = #{self.bar}"
p "instance methods defined in Foo: #{self.class.instance_methods(false)}"
end
end
Foo.new
"instance methods defined in Foo: [:bar]"
"defined? @bar: instance-variable"
"bar: abc"
"defined? bar: method"
"defined? bar, 2nd time: local-variable"
"bar.nil? = true"
"self.bar = abc"
"instance methods defined in Foo: [:bar]"
The lines:
"defined? @bar: instance-variable"
"defined? bar: method"
show that @bar
is an instance variable and bar
is an instance method, namely the getter method for @bar
created by attr_reader :bar
. Before
if false
bar = "123"
end
is evaluated, Ruby peers into the if
clause. There she sees bar = "123"
. If invoked, this would assign the value "123"
to an uninitialized local variable bar
.
bar=
cannot be an instance method (e.g., a setter for @bar
) because any method whose name ends with an equals sign must be invoked on an explicit receiver. (It works that way to allow coders to use local variables that have the same names as instance variables, minus the leading @
.)
What is an "explicit" receiver? If Foo
had a public instance method buz
, you could write:
foo = Foo.new
foo.buz
foo
is an explicit receiver for the method buz
. To invoke buz
from within one of Foo
's instance methods, you could use an explicit receiver:
self.buz
or just write:
buz
in which case self
is the implicit receiver.
As bar=
can only be written with an explicit receiver, we would have write:
attr_writer :bar
...
self.bar = "123"
to invoke @bar
's setter.
Where were we? Ah, we just concluded that:
if false
bar = "123"
end
would assign a value to the local variable bar
if the if
clause were executed, regardless of whether there exists a method Foo#bar=
.
Because false
is, well, false
, the contents of the if
clause are not executed, so the value of bar
is not changed from nil
.
The important thing is that the local variable bar
and the instance variable @bar
are just as different from each other as are night
and @day
. We can easily show that as follows:
a = 'cat'
@a = 'dog'
a #=> "cat"
a = 'pig'
@a #=> "dog"
How do I override a method in C#?
Method overloading does not work for local functions. The code is using local functions and they follow regular rules for defining variables in a scope - you can't redefine it if parent scope already contains something with the same name (see Variable scope confusion in C#). Note that only name of the local method is taken into account for that check unlike class level methods that use both name and parameters to find matching function ("method overloading").
You can either
- name local functions differently. This is likely help best approach if exercise you are trying to is about local functions. "Calculate" is pretty meaningless name and likely cause confusion. Something like "ModifyTotal" would be reasonable name for second function.
- in some cases you can use same variable and assign lambda to it. That would work if methods have the same signature. I'd not try to go that route in the case shown in the question as parameters are different and
ref
/out
parameters are tricky to get right (Cannot use ref or out parameter in lambda expressions). - avoid local methods altogether to allow parameter overloading to work by moving methods out to class level. Note that you can't capture local variable in class-level methods - since methods in the question don't capture local variable moving them at class level will make main method shorter and easier to follow.
- inline the local methods, especially if they are called once.
Related Topics
How to Have Rspec Test for My Default Scope
How to Find Each Instance of a Class in Ruby
Creating Permutations from a Multi-Dimensional Array in Ruby
Converting from Xml Name-Values into Simple Hash
Check If an Array Is Subset of Another Array in Ruby
Sinatra Clears Session on Post
How to Create a Hash in Ruby That Compares Strings, Ignoring Case
To_Specs': Could Not Find Chef (>= 0) Amongst [] (Gem::Loaderror)
Calling Sinatra from Within Sinatra
Ruby Minitest: Suite- or Class- Level Setup
Detect If Application Was Started as Http Server or Not (Rake Task, Rconsole etc)
How to Set Private Instance Variable Used Within a Method Test