Skip to content

Commit 5c80912

Browse files
committed
[GR-18163] Implement foreign.to_f and foreign.to_i (#2038).
PullRequest: truffleruby/1756
2 parents c71d881 + 25e4f21 commit 5c80912

File tree

5 files changed

+111
-0
lines changed

5 files changed

+111
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ New features:
1414
* The Java stacktrace is now shown when sending SIGQUIT to the process, also on TruffleRuby Native, see [Debugging](doc/user/debugging.md) for details (#2041).
1515
* Calls to foreign objects with a block argument will now pass the block as the last argument.
1616
* `foreign.name` will now use `invokeMember` if invocable and if not use `readMember`, see `doc/contrib/interop_implicit_api.md` for details.
17+
* `foreign.to_f` and `foreign.to_i` will now attempt to convert to Ruby `Float` and `Integer` (#2038).
1718

1819
Bug fixes:
1920

doc/contributor/interop_implicit_api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ Format: `Ruby code` sends `InteropLibrary message`
3535
- `foreign_object.to_str` raises `NoMethodError` otherwise
3636
- `foreign_object.to_a` converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`
3737
- `foreign_object.to_ary` converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`
38+
- `foreign_object.to_f` tries to converts to a Ruby `Float` using `asDouble()` and `(double) asLong()` or raises `TypeError`
39+
- `foreign_object.to_i` tries to converts to a Ruby `Integer` using `asInt()` and `asLong()` or raises `TypeError`
3840

3941
Use `.respond_to?` for calling `InteropLibrary` predicates:
4042
- `foreign_object.respond_to?(:inspect)` is always true
4143
- `foreign_object.respond_to?(:to_s)` is always true
4244
- `foreign_object.respond_to?(:to_str)` sends `isString(foreign_object)`
4345
- `foreign_object.respond_to?(:to_a)` sends `hasArrayElements(foreign_object)`
4446
- `foreign_object.respond_to?(:to_ary)` sends `hasArrayElements(foreign_object)`
47+
- `foreign_object.respond_to?(:to_f)` sends `fitsInDouble()` and `fitsInLong()`
48+
- `foreign_object.respond_to?(:to_i)` sends `fitsInInt()` and `fitsInLong()`
4549
- `foreign_object.respond_to?(:size)` sends `hasArrayElements(foreign_object)`
4650
- `foreign_object.respond_to?(:keys)` sends `hasMembers(foreign_object)`
4751
- `foreign_object.respond_to?(:call)` sends `isExecutable(foreign_object)`

spec/truffle/interop/special_forms_spec.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,42 @@
244244
l.log.should include(["hasArrayElements"])
245245
end
246246

247+
it doc['.to_f', 'tries to converts to a Ruby `Float` using `asDouble()` and `(double) asLong()` or raises `TypeError`'] do
248+
pfo, _, l = proxy[42]
249+
pfo.to_f.should.eql?(42.0)
250+
l.log.should include(["fitsInDouble"])
251+
l.log.should include(["asDouble"])
252+
253+
does_not_fit_perfectly_in_double = (1 << 62) + 1
254+
pfo, _, l = proxy[does_not_fit_perfectly_in_double]
255+
pfo.to_f.should.eql?(does_not_fit_perfectly_in_double.to_f)
256+
l.log.should include(["fitsInDouble"])
257+
l.log.should include(["fitsInLong"])
258+
l.log.should include(["asLong"])
259+
260+
pfo, _, l = proxy[Object.new]
261+
-> { pfo.to_f }.should raise_error(TypeError, "can't convert foreign object to Float")
262+
l.log.should include(["fitsInDouble"])
263+
l.log.should include(["fitsInLong"])
264+
end
265+
266+
it doc['.to_i', 'tries to converts to a Ruby `Integer` using `asInt()` and `asLong()` or raises `TypeError`'] do
267+
pfo, _, l = proxy[42]
268+
pfo.to_i.should.eql?(42)
269+
l.log.should include(["fitsInInt"])
270+
l.log.should include(["asInt"])
271+
272+
pfo, _, l = proxy[1 << 42]
273+
pfo.to_i.should.eql?(1 << 42)
274+
l.log.should include(["fitsInLong"])
275+
l.log.should include(["asLong"])
276+
277+
pfo, _, l = proxy[Object.new]
278+
-> { pfo.to_i }.should raise_error(TypeError, "can't convert foreign object to Integer")
279+
l.log.should include(["fitsInInt"])
280+
l.log.should include(["fitsInLong"])
281+
end
282+
247283
output << "\nUse `.respond_to?` for calling `InteropLibrary` predicates:\n"
248284

249285
it doc['.respond_to?(:inspect)', "is always true"] do
@@ -272,6 +308,20 @@
272308
l.log.should include(["hasArrayElements"])
273309
end
274310

311+
it doc['.respond_to?(:to_f)', 'sends `fitsInDouble()` and `fitsInLong()`'] do
312+
pfo, _, l = proxy[Object.new]
313+
pfo.respond_to?(:to_f)
314+
l.log.should include(["fitsInDouble"])
315+
l.log.should include(["fitsInLong"])
316+
end
317+
318+
it doc['.respond_to?(:to_i)', 'sends `fitsInInt()` and `fitsInLong()`'] do
319+
pfo, _, l = proxy[Object.new]
320+
pfo.respond_to?(:to_i)
321+
l.log.should include(["fitsInInt"])
322+
l.log.should include(["fitsInLong"])
323+
end
324+
275325
it description['.respond_to?(:size)', :hasArrayElements] do
276326
pfo, _, l = proxy[Object.new]
277327
pfo.respond_to?(:size)

src/main/java/org/truffleruby/interop/OutgoingForeignCallNode.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import com.oracle.truffle.api.dsl.Specialization;
2727
import com.oracle.truffle.api.interop.InteropException;
2828
import com.oracle.truffle.api.interop.InteropLibrary;
29+
import com.oracle.truffle.api.interop.UnsupportedMessageException;
2930
import com.oracle.truffle.api.library.CachedLibrary;
31+
import com.oracle.truffle.api.profiles.BranchProfile;
3032
import com.oracle.truffle.api.profiles.ConditionProfile;
3133

3234
@GenerateUncached
@@ -46,6 +48,8 @@ public abstract class OutgoingForeignCallNode extends RubyBaseNode {
4648
protected final static String NEW = "new";
4749
protected final static String TO_A = "to_a";
4850
protected final static String TO_ARY = "to_ary";
51+
protected final static String TO_I = "to_i";
52+
protected final static String TO_F = "to_f";
4953
protected final static String RESPOND_TO = "respond_to?";
5054
protected final static String SEND = "__send__";
5155
protected final static String NIL = "nil?";
@@ -206,6 +210,52 @@ protected Object deleteMember(Object receiver, String name, Object[] args,
206210
return dispatchNode.call(context.getCoreLibrary().truffleInteropModule, "remove_member", receiver, args[0]);
207211
}
208212

213+
@Specialization(guards = { "name == cachedName", "cachedName.equals(TO_F)", "args.length == 0" }, limit = "1")
214+
protected double toF(Object receiver, String name, Object[] args,
215+
@Cached(value = "name", allowUncached = true) @Shared("name") String cachedName,
216+
@CachedLibrary("receiver") InteropLibrary interop,
217+
@Cached TranslateInteropExceptionNode translateInteropException,
218+
@CachedContext(RubyLanguage.class) RubyContext context,
219+
@Cached BranchProfile errorProfile) {
220+
try {
221+
if (interop.fitsInDouble(receiver)) {
222+
return interop.asDouble(receiver);
223+
} else if (interop.fitsInLong(receiver)) {
224+
return /* (double) */ interop.asLong(receiver);
225+
} else {
226+
errorProfile.enter();
227+
throw new RaiseException(
228+
context,
229+
context.getCoreExceptions().typeError("can't convert foreign object to Float", this));
230+
}
231+
} catch (UnsupportedMessageException e) {
232+
throw translateInteropException.execute(e);
233+
}
234+
}
235+
236+
@Specialization(guards = { "name == cachedName", "cachedName.equals(TO_I)", "args.length == 0" }, limit = "1")
237+
protected Object toI(Object receiver, String name, Object[] args,
238+
@Cached(value = "name", allowUncached = true) @Shared("name") String cachedName,
239+
@CachedLibrary("receiver") InteropLibrary interop,
240+
@Cached TranslateInteropExceptionNode translateInteropException,
241+
@CachedContext(RubyLanguage.class) RubyContext context,
242+
@Cached BranchProfile errorProfile) {
243+
try {
244+
if (interop.fitsInInt(receiver)) {
245+
return interop.asInt(receiver);
246+
} else if (interop.fitsInLong(receiver)) {
247+
return interop.asLong(receiver);
248+
} else {
249+
errorProfile.enter();
250+
throw new RaiseException(
251+
context,
252+
context.getCoreExceptions().typeError("can't convert foreign object to Integer", this));
253+
}
254+
} catch (UnsupportedMessageException e) {
255+
throw translateInteropException.execute(e);
256+
}
257+
}
258+
209259
protected static boolean canHaveBadArguments(String cachedName) {
210260
return cachedName.equals(INDEX_READ) || cachedName.equals(INDEX_WRITE) || cachedName.equals(SEND) ||
211261
cachedName.equals(NIL) || cachedName.equals(EQUAL) || cachedName.equals(OBJECT_ID) ||
@@ -240,6 +290,8 @@ protected static int expectedArity(String name) {
240290
case KEYS:
241291
case INSPECT:
242292
case CLASS:
293+
case TO_F:
294+
case TO_I:
243295
case TO_S:
244296
case TO_STR:
245297
case NIL:

src/main/ruby/truffleruby/core/truffle/interop.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ def self.foreign_respond_to?(object, name)
359359
case name.to_sym
360360
when :to_a, :to_ary
361361
Truffle::Interop.has_array_elements?(object)
362+
when :to_f
363+
Truffle::Interop.fits_in_double?(object) || Truffle::Interop.fits_in_long?(object)
364+
when :to_i
365+
Truffle::Interop.fits_in_int?(object) || Truffle::Interop.fits_in_long?(object)
362366
when :new
363367
Truffle::Interop.instantiable?(object)
364368
when :size

0 commit comments

Comments
 (0)