Skip to content

Commit aaadee3

Browse files
committed
Kernel#sprintf: fix %c - convert argument to String and Integer properly
1 parent 11cc3ed commit aaadee3

File tree

3 files changed

+113
-8
lines changed

3 files changed

+113
-8
lines changed

spec/ruby/core/kernel/shared/sprintf.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,12 @@ def obj.to_i; 10; end
301301
}.should raise_error(ArgumentError, /%c requires a character/)
302302
end
303303

304+
it "raises TypeError if argument is not String or Integer and cannot be converted to them" do
305+
-> {
306+
@method.call("%c", [])
307+
}.should raise_error(TypeError, /no implicit conversion of Array into Integer/)
308+
end
309+
304310
it "raises TypeError if argument is nil" do
305311
-> {
306312
@method.call("%c", nil)
@@ -315,6 +321,37 @@ def obj.to_str
315321

316322
@method.call("%c", obj).should == "a"
317323
end
324+
325+
it "tries to convert argument to Integer with to_int" do
326+
obj = BasicObject.new
327+
def obj.to_int
328+
90
329+
end
330+
331+
@method.call("%c", obj).should == "Z"
332+
end
333+
334+
it "raises TypeError if converting to String with to_str returns non-String" do
335+
obj = BasicObject.new
336+
def obj.to_str
337+
:foo
338+
end
339+
340+
-> {
341+
@method.call("%c", obj)
342+
}.should raise_error(TypeError, /can't convert BasicObject to String/)
343+
end
344+
345+
it "raises TypeError if converting to Integer with to_int returns non-Integer" do
346+
obj = BasicObject.new
347+
def obj.to_str
348+
:foo
349+
end
350+
351+
-> {
352+
@method.call("%c", obj)
353+
}.should raise_error(TypeError, /can't convert BasicObject to String/)
354+
end
318355
end
319356

320357
describe "p" do
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.core.format.convert;
11+
12+
import com.oracle.truffle.api.dsl.Cached;
13+
import com.oracle.truffle.api.profiles.BranchProfile;
14+
import org.truffleruby.core.format.FormatNode;
15+
import org.truffleruby.core.format.exceptions.NoImplicitConversionException;
16+
import org.truffleruby.core.string.ImmutableRubyString;
17+
import org.truffleruby.core.string.RubyString;
18+
import org.truffleruby.language.control.RaiseException;
19+
import org.truffleruby.language.dispatch.DispatchNode;
20+
21+
import com.oracle.truffle.api.dsl.NodeChild;
22+
import com.oracle.truffle.api.dsl.Specialization;
23+
import org.truffleruby.language.library.RubyStringLibrary;
24+
25+
@NodeChild("value")
26+
public abstract class ToStrNode extends FormatNode {
27+
28+
public abstract Object execute(Object object);
29+
30+
@Specialization
31+
protected RubyString coerceRubyString(RubyString string) {
32+
return string;
33+
}
34+
35+
@Specialization
36+
protected ImmutableRubyString coerceImmutableRubyString(ImmutableRubyString string) {
37+
return string;
38+
}
39+
40+
@Specialization(guards = "isNotRubyString(object)")
41+
protected Object coerceObject(Object object,
42+
@Cached BranchProfile errorProfile,
43+
@Cached DispatchNode toStrNode,
44+
@Cached RubyStringLibrary libString) {
45+
final Object coerced;
46+
47+
try {
48+
coerced = toStrNode.call(object, "to_str");
49+
} catch (RaiseException e) {
50+
errorProfile.enter();
51+
if (e.getException().getLogicalClass() == coreLibrary().noMethodErrorClass) {
52+
throw new NoImplicitConversionException(object, "String");
53+
} else {
54+
throw e;
55+
}
56+
}
57+
58+
if (libString.isRubyString(coerced)) {
59+
return coerced;
60+
} else {
61+
errorProfile.enter();
62+
throw new RaiseException(
63+
getContext(),
64+
coreExceptions().typeErrorBadCoercion(object, "String", "to_str", coerced, this));
65+
}
66+
}
67+
68+
}

src/main/java/org/truffleruby/core/format/format/FormatCharacterNode.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import org.truffleruby.core.cast.ToIntNodeGen;
1919
import org.truffleruby.core.encoding.RubyEncoding;
2020
import org.truffleruby.core.format.FormatNode;
21-
import org.truffleruby.core.format.convert.ToStringNode;
22-
import org.truffleruby.core.format.convert.ToStringNodeGen;
21+
import org.truffleruby.core.format.convert.ToStrNode;
22+
import org.truffleruby.core.format.convert.ToStrNodeGen;
2323
import org.truffleruby.core.format.exceptions.NoImplicitConversionException;
2424
import org.truffleruby.core.string.RubyString;
2525
import org.truffleruby.language.RubyGuards;
@@ -38,7 +38,7 @@ public abstract class FormatCharacterNode extends FormatNode {
3838
private final RubyEncoding encoding;
3939

4040
@Child private ToIntNode toIntegerNode;
41-
@Child private ToStringNode toStringNode;
41+
@Child private ToStrNode toStrNode;
4242
@Child private FromCodePointNode fromCodePointNode;
4343
@Child private CodePointLengthNode codePointLengthNode;
4444
@Child private ForceEncodingNode forceEncodingNode;
@@ -60,7 +60,7 @@ protected TruffleString getCharacter(Object value, RubyStringLibrary strings) {
6060

6161
Object stringArgument;
6262
try {
63-
stringArgument = toStringNode().executeToString(value);
63+
stringArgument = toStrNode().execute(value);
6464
} catch (NoImplicitConversionException e) {
6565
stringArgument = null;
6666
}
@@ -93,13 +93,13 @@ protected TruffleString getCharacter(Object value, RubyStringLibrary strings) {
9393
return character;
9494
}
9595

96-
private ToStringNode toStringNode() {
97-
if (toStringNode == null) {
96+
private ToStrNode toStrNode() {
97+
if (toStrNode == null) {
9898
CompilerDirectives.transferToInterpreterAndInvalidate();
99-
toStringNode = insert(ToStringNodeGen.create(false, "to_str", false, null, null));
99+
toStrNode = insert(ToStrNodeGen.create(null));
100100
}
101101

102-
return toStringNode;
102+
return toStrNode;
103103
}
104104

105105
private ToIntNode toIntegerNode() {

0 commit comments

Comments
 (0)