Skip to content

Commit 37c06a4

Browse files
committed
Fix #arity and #inspect for Symbol#to_proc Procs
PullRequest: truffleruby/558
2 parents 1500988 + 329bff6 commit 37c06a4

File tree

8 files changed

+69
-36
lines changed

8 files changed

+69
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Compatibility:
1515
* Change to a new system for handling Ruby objects in C extensions which
1616
greatly increases compatibility with MRI.
1717
* Implemented `BigDecimal#to_r` (#1521).
18+
* `Symbol#to_proc` now returns `-1` like on MRI (#1462).
1819

1920
# 1.0 RC 11, 15 January 2019
2021

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,24 @@
3232
describe "for a proc created with UnboundMethod#to_proc" do
3333
it "returns a description including '(lambda)' and optionally including file and line number" do
3434
def hello; end
35-
3635
method("hello").to_proc.send(@method).should =~ /^#<Proc:([^ ]*?)(@([^ ]*)\/to_s\.rb:22)? \(lambda\)>$/
3736
end
3837

3938
it "has an ASCII-8BIT encoding" do
4039
def hello; end
41-
4240
method("hello").to_proc.send(@method).encoding.should == Encoding::ASCII_8BIT
4341
end
4442
end
43+
44+
describe "for a proc created with Symbol#to_proc" do
45+
it "returns a description including '(&:symbol)'" do
46+
proc = :foobar.to_proc
47+
proc.send(@method).should =~ /^#<Proc:0x\h+\(&:foobar\)>$/
48+
end
49+
50+
it "has an ASCII-8BIT encoding" do
51+
proc = :foobar.to_proc
52+
proc.send(@method).encoding.should == Encoding::ASCII_8BIT
53+
end
54+
end
4555
end

spec/ruby/core/symbol/to_proc_spec.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@
1212
:to_s.to_proc.call(obj).should == "Received #to_s"
1313
end
1414

15+
it "produces a proc with arity -1" do
16+
pr = :to_s.to_proc
17+
pr.arity.should == -1
18+
end
19+
1520
it "raises an ArgumentError when calling #call on the Proc without receiver" do
16-
lambda { :object_id.to_proc.call }.should raise_error(ArgumentError)
21+
lambda { :object_id.to_proc.call }.should raise_error(ArgumentError, "no receiver given")
1722
end
1823

1924
it "produces a proc that always returns [[:rest]] for #parameters" do
2025
pr = :to_s.to_proc
2126
pr.parameters.should == [[:rest]]
2227
end
23-
end
2428

25-
describe "Symbol#to_proc" do
26-
before :all do
27-
@klass = Class.new do
29+
it "passes along the block passed to Proc#call" do
30+
klass = Class.new do
2831
def m
2932
yield
3033
end
@@ -33,9 +36,6 @@ def to_proc
3336
:m.to_proc.call(self) { :value }
3437
end
3538
end
36-
end
37-
38-
it "passes along the block passed to Proc#call" do
39-
@klass.new.to_proc.should == :value
39+
klass.new.to_proc.should == :value
4040
end
4141
end

src/main/java/org/truffleruby/core/proc/ProcNodes.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import org.truffleruby.builtins.CoreClass;
1515
import org.truffleruby.builtins.CoreMethod;
1616
import org.truffleruby.builtins.CoreMethodArrayArgumentsNode;
17+
import org.truffleruby.builtins.Primitive;
18+
import org.truffleruby.builtins.PrimitiveArrayArgumentsNode;
1719
import org.truffleruby.builtins.UnaryCoreMethodNode;
1820
import org.truffleruby.core.binding.BindingNodes;
1921
import org.truffleruby.core.rope.CodeRange;
2022
import org.truffleruby.core.string.StringNodes;
23+
import org.truffleruby.core.symbol.SymbolNodes;
2124
import org.truffleruby.language.NotProvided;
2225
import org.truffleruby.language.Visibility;
2326
import org.truffleruby.language.arguments.ArgumentDescriptorUtils;
@@ -280,4 +283,18 @@ public Object sourceLocation(DynamicObject proc) {
280283

281284
}
282285

286+
@Primitive(name = "proc_symbol_to_proc_symbol")
287+
public abstract static class ProcSymbolToProcSymbolNode extends PrimitiveArrayArgumentsNode {
288+
289+
@Specialization
290+
public DynamicObject symbolToProcSymbol(DynamicObject proc) {
291+
if (Layouts.PROC.getSharedMethodInfo(proc).getArity() == SymbolNodes.ToProcNode.ARITY) {
292+
return getSymbol(Layouts.PROC.getSharedMethodInfo(proc).getName());
293+
} else {
294+
return nil();
295+
}
296+
}
297+
298+
}
299+
283300
}

src/main/java/org/truffleruby/core/symbol/SymbolNodes.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.truffleruby.core.proc.ProcType;
2929
import org.truffleruby.core.string.StringNodes;
3030
import org.truffleruby.language.RubyRootNode;
31-
import org.truffleruby.language.SourceIndexLength;
3231
import org.truffleruby.language.Visibility;
3332
import org.truffleruby.language.arguments.ReadCallerFrameNode;
3433
import org.truffleruby.language.arguments.RubyArguments;
@@ -39,9 +38,6 @@
3938
import org.truffleruby.language.methods.SharedMethodInfo;
4039
import org.truffleruby.language.methods.SymbolProcNode;
4140
import org.truffleruby.parser.ArgumentDescriptor;
42-
import org.truffleruby.parser.Translator;
43-
44-
import java.util.Arrays;
4541

4642
@CoreClass("Symbol")
4743
public abstract class SymbolNodes {
@@ -99,6 +95,8 @@ protected boolean isPreInitializing() {
9995
@CoreMethod(names = "to_proc")
10096
public abstract static class ToProcNode extends CoreMethodArrayArgumentsNode {
10197

98+
public static final Arity ARITY = new Arity(0, 0, true);
99+
102100
@Child private ReadCallerFrameNode readCallerFrame = ReadCallerFrameNode.create();
103101

104102
@Specialization(guards = { "cachedSymbol == symbol", "getDeclarationContext(frame) == cachedDeclarationContext" }, limit = "getCacheLimit()")
@@ -120,12 +118,11 @@ public DynamicObject toProcUncached(VirtualFrame frame, DynamicObject symbol) {
120118
protected DynamicObject createProc(DeclarationContext declarationContext, InternalMethod method, DynamicObject symbol) {
121119
final SourceSection sourceSection = getContext().getCallStack().getCallerFrameIgnoringSend()
122120
.getCallNode().getEncapsulatingSourceSection();
123-
final SourceIndexLength sourceIndexLength = new SourceIndexLength(sourceSection);
124121

125122
final SharedMethodInfo sharedMethodInfo = new SharedMethodInfo(
126123
sourceSection,
127124
method.getLexicalScope(),
128-
Arity.AT_LEAST_ONE,
125+
ARITY,
129126
null,
130127
Layouts.SYMBOL.getString(symbol),
131128
0,
@@ -138,15 +135,17 @@ protected DynamicObject createProc(DeclarationContext declarationContext, Intern
138135
// binding as this simplifies the logic elsewhere in the runtime.
139136
final MaterializedFrame declarationFrame = Truffle.getRuntime().createMaterializedFrame(args, coreLibrary().getEmptyDescriptor());
140137
final RubyRootNode rootNode = new RubyRootNode(getContext(), sourceSection, new FrameDescriptor(nil()), sharedMethodInfo,
141-
Translator.sequence(sourceIndexLength, Arrays.asList(Translator.createCheckArityNode(Arity.AT_LEAST_ONE), new SymbolProcNode(Layouts.SYMBOL.getString(symbol)))));
138+
new SymbolProcNode(Layouts.SYMBOL.getString(symbol)));
142139

143140
final CallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
144141

145142
return ProcOperations.createRubyProc(
146143
coreLibrary().getProcFactory(),
147144
ProcType.PROC,
148145
sharedMethodInfo,
149-
callTarget, callTarget, declarationFrame,
146+
callTarget,
147+
callTarget,
148+
declarationFrame,
150149
method,
151150
null,
152151
null,
@@ -168,6 +167,7 @@ protected DeclarationContext getDeclarationContext(VirtualFrame frame) {
168167
protected FrameDescriptor getDescriptor(VirtualFrame frame) {
169168
return frame.getFrameDescriptor();
170169
}
170+
171171
}
172172

173173
@CoreMethod(names = "to_s")

src/main/java/org/truffleruby/language/methods/Arity.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ public class Arity {
2121
public static final String[] NO_KEYWORDS = new String[]{};
2222
public static final Arity NO_ARGUMENTS = new Arity(0, 0, false);
2323
public static final Arity ONE_REQUIRED = new Arity(1, 0, false);
24-
public static final Arity AT_LEAST_ONE = new Arity(1, 0, true);
2524

2625
private final int preRequired;
2726
private final int optional;

src/main/java/org/truffleruby/language/methods/SymbolProcNode.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212
import com.oracle.truffle.api.CompilerDirectives;
1313
import com.oracle.truffle.api.frame.VirtualFrame;
1414
import com.oracle.truffle.api.object.DynamicObject;
15+
import com.oracle.truffle.api.profiles.BranchProfile;
16+
1517
import org.truffleruby.core.array.ArrayUtils;
1618
import org.truffleruby.language.RubyNode;
1719
import org.truffleruby.language.arguments.RubyArguments;
20+
import org.truffleruby.language.control.RaiseException;
1821
import org.truffleruby.language.dispatch.CallDispatchHeadNode;
1922

2023
public class SymbolProcNode extends RubyNode {
2124

2225
private final String symbol;
26+
private final BranchProfile noReceiverProfile = BranchProfile.create();
2327

2428
@Child private CallDispatchHeadNode callNode;
2529

@@ -29,15 +33,17 @@ public SymbolProcNode(String symbol) {
2933

3034
@Override
3135
public Object execute(VirtualFrame frame) {
32-
final Object receiver = RubyArguments.getArgument(frame, 0);
36+
// Not using CheckArityNode as the message is different and arity is reported as -1
37+
final int given = RubyArguments.getArgumentsCount(frame);
38+
if (given == 0) {
39+
noReceiverProfile.enter();
40+
throw new RaiseException(getContext(), coreExceptions().argumentError("no receiver given", this));
41+
}
3342

43+
final Object receiver = RubyArguments.getArgument(frame, 0);
44+
final Object[] arguments = ArrayUtils.extractRange(RubyArguments.getArguments(frame), 1, given);
3445
final DynamicObject block = RubyArguments.getBlock(frame);
3546

36-
final Object[] arguments = ArrayUtils.extractRange(
37-
RubyArguments.getArguments(frame),
38-
1,
39-
RubyArguments.getArgumentsCount(frame));
40-
4147
return getCallNode().dispatch(frame, receiver, symbol, block, arguments);
4248
}
4349

src/main/ruby/core/proc.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,18 @@ def f.binding
8686
end
8787

8888
def to_s
89-
sl = source_location
90-
return super if sl.nil?
91-
file, line = sl
92-
93-
l = ' (lambda)' if lambda?
94-
if file and line
95-
"#<#{self.class}:0x#{self.object_id.to_s(16)}@#{file}:#{line}#{l}>".force_encoding('ASCII-8BIT')
96-
else
97-
"#<#{self.class}:0x#{self.object_id.to_s(16)}#{l}>".force_encoding('ASCII-8BIT')
89+
base = super()
90+
file, line = source_location
91+
92+
suffix = ''.b
93+
if sym = Truffle.invoke_primitive(:proc_symbol_to_proc_symbol, self)
94+
suffix << "(&#{sym.inspect})"
95+
elsif file and line
96+
suffix << "@#{file}:#{line}"
9897
end
98+
suffix << ' (lambda)' if lambda?
99+
base.b.insert(-2, suffix)
99100
end
100-
101101
alias_method :inspect, :to_s
102102

103103
def to_proc

0 commit comments

Comments
 (0)