Skip to content

Commit 01644d3

Browse files
committed
[GR-15774] [GR-8022] Block args to block fix.
PullRequest: truffleruby/835
2 parents b0722e0 + 483a917 commit 01644d3

File tree

18 files changed

+252
-104
lines changed

18 files changed

+252
-104
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Bug fixes:
1212
* Return a `FFI::Function` correctly for functions returning a callback.
1313
* Convert to intuitive Ruby exceptions when INVOKE fails (#1690).
1414
* Implemented `FFI::Pointer#clear` (#1687).
15+
* Procs will now yield to the block in their declaration context even when called with a block argument (#1657).
1516

1617
Compatibility
1718

spec/ruby/core/proc/new_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@ def some_method
190190

191191
prc.call.should == "hello"
192192
end
193+
194+
it "uses the implicit block from an enclosing method when called inside a block" do
195+
def some_method
196+
proc do |&block|
197+
Proc.new
198+
end.call { "failing" }
199+
end
200+
prc = some_method { "hello" }
201+
202+
prc.call.should == "hello"
203+
end
193204
end
194205

195206
ruby_version_is "2.7" do

spec/ruby/core/proc/shared/call_arguments.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,26 @@
44
lambda {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2
55
proc {|&b| b.send(@method)}.send(@method) {1 + 1}.should == 2
66
end
7+
8+
it "yields to the block given at declaration and not to the block argument" do
9+
proc_creator = Object.new
10+
def proc_creator.create
11+
Proc.new do |&b|
12+
yield
13+
end
14+
end
15+
a_proc = proc_creator.create { 7 }
16+
a_proc.send(@method) { 3 }.should == 7
17+
end
18+
19+
it "can call its block argument declared with a block argument" do
20+
proc_creator = Object.new
21+
def proc_creator.create(method_name)
22+
Proc.new do |&b|
23+
yield + b.send(method_name)
24+
end
25+
end
26+
a_proc = proc_creator.create(@method) { 7 }
27+
a_proc.call { 3 }.should == 10
28+
end
729
end

spec/ruby/language/yield_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
it "ignores assignment to the explicit block argument and calls the passed block" do
2020
@y.ze { 42 }.should == 42
2121
end
22+
23+
it "does not pass a named block to the block being yielded to" do
24+
@y.z() { |&block| block == nil }.should == true
25+
end
2226
end
2327

2428
describe "taking a single argument" do

spec/tags/core/kernel/block_given_tags.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/main/java/org/truffleruby/core/binding/BindingNodes.java

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
import org.truffleruby.language.arguments.ReadCallerFrameNode;
3333
import org.truffleruby.language.arguments.RubyArguments;
3434
import org.truffleruby.language.control.RaiseException;
35+
import org.truffleruby.language.locals.FindDeclarationVariableNodes;
3536
import org.truffleruby.language.locals.ReadFrameSlotNode;
3637
import org.truffleruby.language.locals.ReadFrameSlotNodeGen;
3738
import org.truffleruby.language.locals.WriteFrameSlotNode;
3839
import org.truffleruby.language.locals.WriteFrameSlotNodeGen;
40+
import org.truffleruby.language.locals.FindDeclarationVariableNodes.FrameSlotAndDepth;
3941
import org.truffleruby.language.objects.AllocateObjectNode;
4042

4143
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
@@ -125,40 +127,6 @@ public static void insertAncestorFrame(RubyContext context, DynamicObject bindin
125127
newFrame(binding, newFrameDescriptor(context));
126128
}
127129

128-
protected static class FrameSlotAndDepth {
129-
private final FrameSlot slot;
130-
private final int depth;
131-
132-
public FrameSlotAndDepth(FrameSlot slot, int depth) {
133-
this.slot = slot;
134-
this.depth = depth;
135-
}
136-
137-
public FrameSlot getSlot() {
138-
return slot;
139-
}
140-
}
141-
142-
public static FrameSlotAndDepth findFrameSlotOrNull(String identifier, MaterializedFrame frame) {
143-
int depth = 0;
144-
while (frame != null) {
145-
final FrameSlot frameSlot = frame.getFrameDescriptor().findFrameSlot(identifier);
146-
if (frameSlot != null) {
147-
return new FrameSlotAndDepth(frameSlot, depth);
148-
}
149-
150-
frame = RubyArguments.getDeclarationFrame(frame);
151-
depth++;
152-
}
153-
return null;
154-
}
155-
156-
public static FrameSlotAndDepth findFrameSlot(String identifier, FrameDescriptor frameDescriptor) {
157-
final FrameSlot frameSlot = frameDescriptor.findFrameSlot(identifier);
158-
assert frameSlot != null;
159-
return new FrameSlotAndDepth(frameSlot, 0);
160-
}
161-
162130
public static boolean hiddenVariable(String name) {
163131
assert !name.isEmpty();
164132
return name.startsWith("$") || name.charAt(0) == TranslatorEnvironment.TEMP_PREFIX;
@@ -179,7 +147,7 @@ public DynamicObject dup(DynamicObject binding) {
179147
}
180148
}
181149

182-
@ImportStatic(BindingNodes.class)
150+
@ImportStatic({ BindingNodes.class, FindDeclarationVariableNodes.class })
183151
@CoreMethod(names = "local_variable_defined?", required = 1)
184152
@NodeChild(value = "binding", type = RubyNode.class)
185153
@NodeChild(value = "name", type = RubyNode.class)
@@ -205,7 +173,7 @@ public boolean localVariableDefinedCached(DynamicObject binding, String name,
205173
@TruffleBoundary
206174
@Specialization(guards = "!hiddenVariable(name)")
207175
public boolean localVariableDefinedUncached(DynamicObject binding, String name) {
208-
return findFrameSlotOrNull(name, getFrame(binding)) != null;
176+
return FindDeclarationVariableNodes.findFrameSlotOrNull(name, getFrame(binding)) != null;
209177
}
210178

211179
@TruffleBoundary
@@ -231,31 +199,15 @@ public RubyNode coerceToString(RubyNode name) {
231199
return NameToJavaStringNodeGen.create(name);
232200
}
233201

234-
@Specialization(guards = {
235-
"name == cachedName",
236-
"!hiddenVariable(cachedName)",
237-
"cachedFrameSlot != null",
238-
"getFrameDescriptor(binding) == descriptor"
239-
}, limit = "getCacheLimit()")
240-
public Object localVariableGetCached(DynamicObject binding, String name,
241-
@Cached("name") String cachedName,
242-
@Cached("getFrameDescriptor(binding)") FrameDescriptor descriptor,
243-
@Cached("findFrameSlotOrNull(name, getFrame(binding))") FrameSlotAndDepth cachedFrameSlot,
244-
@Cached("createReadNode(cachedFrameSlot)") ReadFrameSlotNode readLocalVariableNode) {
245-
final MaterializedFrame frame = RubyArguments.getDeclarationFrame(getFrame(binding), cachedFrameSlot.depth);
246-
return readLocalVariableNode.executeRead(frame);
247-
}
248-
249-
@TruffleBoundary
250202
@Specialization(guards = "!hiddenVariable(name)")
251-
public Object localVariableGetUncached(DynamicObject binding, String name) {
203+
public Object localVariableGetUncached(DynamicObject binding, String name,
204+
@Cached("create(null)") FindDeclarationVariableNodes.FindAndReadDeclarationVariableNode readNode) {
252205
MaterializedFrame frame = getFrame(binding);
253-
FrameSlotAndDepth frameSlot = findFrameSlotOrNull(name, frame);
254-
if (frameSlot != null) {
255-
return RubyArguments.getDeclarationFrame(frame, frameSlot.depth).getValue(frameSlot.slot);
256-
} else {
206+
Object result = readNode.execute(frame, name);
207+
if (result == null) {
257208
throw new RaiseException(getContext(), coreExceptions().nameErrorLocalVariableNotDefined(name, binding, this));
258209
}
210+
return result;
259211
}
260212

261213
@TruffleBoundary
@@ -264,21 +216,13 @@ public Object localVariableGetLastLine(DynamicObject binding, String name) {
264216
throw new RaiseException(getContext(), coreExceptions().nameError("Bad local variable name", binding, name, this));
265217
}
266218

267-
protected ReadFrameSlotNode createReadNode(FrameSlotAndDepth frameSlot) {
268-
if (frameSlot == null) {
269-
return null;
270-
} else {
271-
return ReadFrameSlotNodeGen.create(frameSlot.slot);
272-
}
273-
}
274-
275219
protected int getCacheLimit() {
276220
return getContext().getOptions().BINDING_LOCAL_VARIABLE_CACHE;
277221
}
278222

279223
}
280224

281-
@ImportStatic(BindingNodes.class)
225+
@ImportStatic({ BindingNodes.class, FindDeclarationVariableNodes.class })
282226
@CoreMethod(names = "local_variable_set", required = 2)
283227
@NodeChild(value = "binding", type = RubyNode.class)
284228
@NodeChild(value = "name", type = RubyNode.class)
@@ -326,7 +270,7 @@ public Object localVariableSetNewCached(DynamicObject binding, String name, Obje
326270
@Specialization(guards = "!hiddenVariable(name)")
327271
public Object localVariableSetUncached(DynamicObject binding, String name, Object value) {
328272
MaterializedFrame frame = getFrame(binding);
329-
final FrameSlotAndDepth frameSlot = findFrameSlotOrNull(name, frame);
273+
final FrameSlotAndDepth frameSlot = FindDeclarationVariableNodes.findFrameSlotOrNull(name, frame);
330274
final FrameSlot slot;
331275
if (frameSlot != null) {
332276
frame = RubyArguments.getDeclarationFrame(frame, frameSlot.depth);

src/main/java/org/truffleruby/core/inlined/CoreMethods.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import org.truffleruby.language.RubyNode;
1919
import org.truffleruby.language.dispatch.RubyCallNode;
2020
import org.truffleruby.language.dispatch.RubyCallNodeParameters;
21+
import org.truffleruby.language.literal.NilLiteralNode;
2122
import org.truffleruby.language.methods.BlockDefinitionNode;
2223
import org.truffleruby.language.methods.InternalMethod;
24+
import org.truffleruby.parser.TranslatorEnvironment;
2325

2426
/**
2527
* We inline basic operations as it makes little sense to compile them in isolation without the
@@ -138,7 +140,7 @@ private InternalMethod getMethod(DynamicObject module, String name) {
138140
return method;
139141
}
140142

141-
public RubyNode createCallNode(RubyCallNodeParameters callParameters) {
143+
public RubyNode createCallNode(RubyCallNodeParameters callParameters, TranslatorEnvironment environment) {
142144
if (!context.getOptions().BASICOPS_INLINE ||
143145
callParameters.isSplatted() || callParameters.isSafeNavigation()) {
144146
return new RubyCallNode(callParameters);
@@ -171,7 +173,7 @@ public RubyNode createCallNode(RubyCallNodeParameters callParameters) {
171173
break;
172174
case "block_given?":
173175
if (callParameters.isIgnoreVisibility()) {
174-
return InlinedBlockGivenNodeGen.create(context, callParameters, self);
176+
return InlinedBlockGivenNodeGen.create(callParameters, environment, self);
175177
}
176178
break;
177179
case "nil?":

src/main/java/org/truffleruby/core/inlined/InlinedBlockGivenNode.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
package org.truffleruby.core.inlined;
1111

1212
import org.truffleruby.RubyContext;
13-
import org.truffleruby.language.arguments.RubyArguments;
13+
import org.truffleruby.language.RubyNode;
1414
import org.truffleruby.language.dispatch.RubyCallNodeParameters;
1515
import org.truffleruby.language.methods.LookupMethodNode;
16+
import org.truffleruby.parser.TranslatorEnvironment;
1617

1718
import com.oracle.truffle.api.dsl.Cached;
1819
import com.oracle.truffle.api.dsl.Specialization;
@@ -22,16 +23,19 @@ public abstract class InlinedBlockGivenNode extends UnaryInlinedOperationNode {
2223

2324
protected static final String METHOD = "block_given?";
2425

25-
public InlinedBlockGivenNode(RubyContext context, RubyCallNodeParameters callNodeParameters) {
26+
@Child protected RubyNode readMethodBlockNode;
27+
28+
public InlinedBlockGivenNode(RubyCallNodeParameters callNodeParameters, TranslatorEnvironment environment) {
2629
super(callNodeParameters);
30+
this.readMethodBlockNode = environment.findLocalVarOrNilNode(TranslatorEnvironment.METHOD_BLOCK_NAME, null);
2731
}
2832

2933
@Specialization(guards = {
3034
"lookupNode.lookup(frame, self, METHOD) == coreMethods().BLOCK_GIVEN",
3135
}, assumptions = "assumptions", limit = "1")
3236
boolean blockGiven(VirtualFrame frame, Object self,
3337
@Cached("createIgnoreVisibility()") LookupMethodNode lookupNode) {
34-
return RubyArguments.getBlock(frame) != null;
38+
return readMethodBlockNode.execute(frame) != nil();
3539
}
3640

3741
@Specialization

src/main/java/org/truffleruby/core/kernel/KernelNodes.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109
import org.truffleruby.language.globals.ReadGlobalVariableNodeGen;
110110
import org.truffleruby.language.loader.CodeLoader;
111111
import org.truffleruby.language.loader.RequireNode;
112+
import org.truffleruby.language.locals.ReadFrameSlotNode;
113+
import org.truffleruby.language.locals.ReadFrameSlotNodeGen;
114+
import org.truffleruby.language.locals.FindDeclarationVariableNodes.FindAndReadDeclarationVariableNode;
112115
import org.truffleruby.language.methods.DeclarationContext;
113116
import org.truffleruby.language.methods.InternalMethod;
114117
import org.truffleruby.language.methods.LookupMethodNode;
@@ -137,6 +140,7 @@
137140
import org.truffleruby.language.objects.shared.SharedObjects;
138141
import org.truffleruby.parser.ParserContext;
139142
import org.truffleruby.parser.RubySource;
143+
import org.truffleruby.parser.TranslatorEnvironment;
140144

141145
import java.io.File;
142146
import java.io.PrintStream;
@@ -283,15 +287,15 @@ protected SourceSection getCallerSourceSection() {
283287
@CoreMethod(names = "block_given?", isModuleFunction = true)
284288
public abstract static class BlockGivenNode extends CoreMethodArrayArgumentsNode {
285289

286-
@Child ReadCallerFrameNode callerFrameNode = new ReadCallerFrameNode(CallerFrameAccess.ARGUMENTS);
290+
@Child ReadCallerFrameNode callerFrameNode = new ReadCallerFrameNode(CallerFrameAccess.MATERIALIZE);
287291

288292
@Specialization
289293
public boolean blockGiven(VirtualFrame frame,
294+
@Cached("create(nil())") FindAndReadDeclarationVariableNode readNode,
290295
@Cached("createBinaryProfile()") ConditionProfile blockProfile) {
291-
Frame callerFrame = callerFrameNode.execute(frame);
292-
return blockProfile.profile(RubyArguments.getBlock(callerFrame) != null);
296+
MaterializedFrame callerFrame = callerFrameNode.execute(frame).materialize();
297+
return blockProfile.profile(readNode.execute(callerFrame, TranslatorEnvironment.METHOD_BLOCK_NAME) != nil());
293298
}
294-
295299
}
296300

297301
@CoreMethod(names = "__callee__", isModuleFunction = true)
@@ -995,11 +999,12 @@ public abstract static class LambdaNode extends CoreMethodArrayArgumentsNode {
995999

9961000
@TruffleBoundary
9971001
@Specialization
998-
public DynamicObject lambda(NotProvided block) {
999-
final Frame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend(0).getFrame(FrameAccess.READ_ONLY);
1000-
final DynamicObject parentBlock = RubyArguments.getBlock(parentFrame);
1002+
public DynamicObject lambda(NotProvided block,
1003+
@Cached("create(nil())") FindAndReadDeclarationVariableNode readNode) {
1004+
final MaterializedFrame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend(0).getFrame(FrameAccess.MATERIALIZE).materialize();
1005+
DynamicObject parentBlock = (DynamicObject) readNode.execute(parentFrame, TranslatorEnvironment.METHOD_BLOCK_NAME);
10011006

1002-
if (parentBlock == null) {
1007+
if (parentBlock == nil()) {
10031008
throw new RaiseException(getContext(), coreExceptions().argumentError("tried to create Proc object without a block", this));
10041009
} else {
10051010
warnProcWithoutBlock();

0 commit comments

Comments
 (0)