Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support fixed string argument with instance_eval #2285

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/natalie/compiler/pass1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,13 @@ def transform_call_node(node, used:, with_block: false)

instructions = []

if node.name == :instance_eval && !node.block && node&.arguments&.child_nodes&.size == 1 && node.arguments.child_nodes.first.is_a?(Prism::StringNode)
parser = Natalie::Parser.new(node.arguments.child_nodes.first.unescaped, @file.path, locals: [])
block = Prism::BlockNode.new(node.arguments.child_nodes.first.unescaped, nil, node.arguments.location, 0, nil, nil, parser.ast.statements, node.arguments.child_nodes.first.opening_loc, node.arguments.child_nodes.first.closing_loc)
node = Prism::CallNode.new(node.__send__(:source), node.node_id, node.location, node.__send__(:flags), node.receiver, node.call_operator_loc, node.name, node.message_loc, node.opening_loc, nil, node.closing_loc, block)
args = []
end

# block handling
if node.block.is_a?(Prism::BlockNode)
with_block = true
Expand Down
116 changes: 48 additions & 68 deletions spec/core/basicobject/instance_eval_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
end

it "evaluates strings" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
a = BasicObject.new
a.instance_eval('self').equal?(a).should be_true
end
a = BasicObject.new
a.instance_eval('self').equal?(a).should be_true
end

it "raises an ArgumentError when no arguments and no block are given" do
Expand Down Expand Up @@ -74,35 +72,29 @@ def foo
it "binds self to the receiver" do
s = "hola"
(s == s.instance_eval { self }).should be_true
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
o = mock('o')
(o == o.instance_eval("self")).should be_true
end
o = mock('o')
(o == o.instance_eval("self")).should be_true
end

it "executes in the context of the receiver" do
"Ruby-fu".instance_eval { size }.should == 7
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
"hola".instance_eval("size").should == 4
Object.class_eval { "hola".instance_eval("to_s") }.should == "hola"
end
"hola".instance_eval("size").should == 4
Object.class_eval { "hola".instance_eval("to_s") }.should == "hola"
Object.class_eval { "Ruby-fu".instance_eval{ to_s } }.should == "Ruby-fu"
end

ruby_version_is "3.3" do
it "uses the caller location as default location" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
f = Object.new
f = Object.new
NATFIXME 'it uses the caller location as default location', exception: SpecFailedException do
f.instance_eval("[__FILE__, __LINE__]").should == ["(eval at #{__FILE__}:#{__LINE__})", 1]
end
end
end

it "has access to receiver's instance variables" do
BasicObjectSpecs::IVars.new.instance_eval { @secret }.should == 99
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
BasicObjectSpecs::IVars.new.instance_eval("@secret").should == 99
end
BasicObjectSpecs::IVars.new.instance_eval("@secret").should == 99
end

it "raises TypeError for frozen objects when tries to set receiver's instance variables" do
Expand All @@ -118,25 +110,23 @@ def foo
end

it "treats block-local variables as local to the block" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
prc = instance_eval <<-CODE
proc do |x, prc|
if x
n = 2
else
n = 1
prc.call(true, prc)
n
end
prc = instance_eval <<-CODE
proc do |x, prc|
if x
n = 2
else
n = 1
prc.call(true, prc)
n
end
CODE
end
CODE

prc.call(false, prc).should == 1
end
prc.call(false, prc).should == 1
end

it "makes the receiver metaclass the scoped class when used with a string" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
NATFIXME 'it makes the receiver metaclass the scoped class when used with a string', exception: NameError, message: /uninitialized constant \S+::B/ do
obj = Object.new
obj.instance_eval %{
class B; end
Expand All @@ -148,10 +138,10 @@ class B; end

describe "constants lookup when a String given" do
it "looks in the receiver singleton class first" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::CallerScope::Caller.new
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverSingletonClass::CallerScope::Caller.new

NATFIXME 'it looks in the receiver singleton class first', exception: SpecFailedException do
caller.get_constant_with_string(receiver).should == :singleton_class
end
end
Expand All @@ -167,38 +157,36 @@ class B; end

ruby_version_is "3.1" do
it "looks in the receiver class next" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::CallerScope::Caller.new
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverClass::CallerScope::Caller.new

caller.get_constant_with_string(receiver).should == :Receiver
end
caller.get_constant_with_string(receiver).should == :Receiver
end
end

it "looks in the caller class next" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::CallerScope::Caller.new
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerClass::CallerScope::Caller.new

NATFIXME 'it looks in the caller class next', exception: SpecFailedException do
caller.get_constant_with_string(receiver).should == :Caller
end
end

it "looks in the caller outer scopes next" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::CallerScope::Caller.new
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInCallerOuterScopes::CallerScope::Caller.new

NATFIXME 'it looks in the caller outer scopes next', exception: SpecFailedException do
caller.get_constant_with_string(receiver).should == :CallerScope
end
end

it "looks in the receiver class hierarchy next" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::CallerScope::Caller.new
receiver = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::ReceiverScope::Receiver.new
caller = BasicObjectSpecs::InstEval::Constants::ConstantInReceiverParentClass::CallerScope::Caller.new

NATFIXME 'it looks in the receiver class hierarchy next', exception: SpecFailedException do
caller.get_constant_with_string(receiver).should == :ReceiverParent
end
end
Expand All @@ -219,12 +207,10 @@ class B; end

describe "class variables lookup" do
it "gets class variables in the caller class when called with a String" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerScope.new
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerScope.new

caller.get_class_variable_with_string(receiver).should == :value_defined_in_caller_scope
end
caller.get_class_variable_with_string(receiver).should == :value_defined_in_caller_scope
end

it "gets class variables in the block definition scope when called with a block" do
Expand Down Expand Up @@ -255,19 +241,15 @@ class B; end
end

it "does not have access to class variables in the receiver class when called with a String" do
NATFIXME 'Natalie does not support instance_eval with string', exception: SpecFailedException do
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
-> { caller.get_class_variable_with_string(receiver) }.should raise_error(NameError, /uninitialized class variable @@cvar/)
end
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverScope.new
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
-> { caller.get_class_variable_with_string(receiver) }.should raise_error(NameError, /uninitialized class variable @@cvar/)
end

it "does not have access to class variables in the receiver's singleton class when called with a String" do
NATFIXME 'Natalie does not support instance_eval with string', exception: SpecFailedException do
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverWithCVarDefinedInSingletonClass
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
-> { caller.get_class_variable_with_string(receiver) }.should raise_error(NameError, /uninitialized class variable @@cvar/)
end
receiver = BasicObjectSpecs::InstEval::CVar::Get::ReceiverWithCVarDefinedInSingletonClass
caller = BasicObjectSpecs::InstEval::CVar::Get::CallerWithoutCVarScope.new
-> { caller.get_class_variable_with_string(receiver) }.should raise_error(NameError, /uninitialized class variable @@cvar/)
end
end

Expand Down Expand Up @@ -312,12 +294,10 @@ def meth(arg); arg; end
end

it "has access to the caller's local variables" do
NATFIXME 'Natalie does not support instance_eval with string', exception: ArgumentError, message: 'Natalie only supports instance_eval with a block' do
x = nil
x = nil

instance_eval "x = :value"
x.should == :value
end
instance_eval "x = :value"
x.should == :value
end

it "converts string argument with #to_str method" do
Expand Down
Loading