Skip to content

Commit

Permalink
Merge pull request #137 in G/truffleruby from is-a-foreign to master
Browse files Browse the repository at this point in the history
* commit 'f7e37516f63fbc20e65e433d968d450e8412919c':
  Tidy up guards
  Combine specs
  Run some foreign is_a? in native mode
  Fixes to is_a?
  Native guard
  Foreign is_a?
  Truffle::Interop.java_instanceof?
  • Loading branch information
chrisseaton committed Jun 14, 2018
2 parents fbb4838 + f7e3751 commit 5d7724a
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# 1.0 RC 3

New features:

* `is_a?` can be called on foreign objects.

Bug fixes:

* It is no longer needed to have `ruby` in `$PATH` to run the post-install hook.
Expand Down
20 changes: 15 additions & 5 deletions doc/contributor/interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ looks at the underlying Java object.
`object.to_str` will try to `UNBOX` the object and return it if it's a `String`,
or will raise `NoMethodError` if it isn't.

`java_object.is_a?(java_class)` does `java_object instanceof java_class`, using
the host object instance, rather than any runtime interop wrapper.

`object.is_a?(java_class)` does `object instanceof java_class`, using the
runtime object instance.

`foreign_object.is_a?(ruby_class)` returns `false`.

`foreign_object.is_a?(foreign_class)` raises a `TypeError`.

`object.respond_to?(:to_a)`, `respond_to?(:to_ary)` and `respond_to?(:size)`
sends `HAS_SIZE`.

Expand All @@ -426,14 +436,12 @@ sends `HAS_SIZE`.

`object.respond_to?(:class)` calls `Truffle::Interop.java_class?(object)`.

`object.respond_to?(name)` for other names returns `false`.

`object.respond_to?(:inspect)` is `true`.

`object.respond_to?(:to_s)` is `true`.
`object.respond_to?(:inspect)`, `:to_s`, `:is_a?`, is `true`.

`object.respond_to?(:to_str)` is `true` if the object `UNBOXes` to a `String`.

`object.respond_to?(name)` for other names returns `false`.

`object.__send__(name, *args)` works in the same way as literal method call on
the foreign object, including allowing the special-forms listed above (see
[notes on method resolution](#notes-on-method-resolution)).
Expand Down Expand Up @@ -509,6 +517,8 @@ type is supported for interop.

`Truffle::Interop.java_class?(object)`

`Truffle::Interop.java_instanceof?(object, class)`

`Truffle::Interop.java_string?(object)`

`Truffle::Interop.to_java_string(ruby_string)`
Expand Down
60 changes: 60 additions & 0 deletions spec/truffle/interop/java_instanceof_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. This
# code is released under a tri EPL/GPL/LGPL license. You can use it,
# redistribute it and/or modify it under the terms of the:
#
# Eclipse Public License version 1.0, or
# GNU General Public License version 2, or
# GNU Lesser General Public License version 2.1.

require_relative '../../ruby/spec_helper'

guard -> { !TruffleRuby.native? } do
describe "Truffle::Interop.java_instanceof?" do

it "returns true for a directly matching Java object and class" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
Truffle::Interop.java_instanceof?(big_integer, big_integer_class).should be_true
end

it "returns true for a matching Java object and superclass" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
number_class = Truffle::Interop.java_type("java.lang.Number")
Truffle::Interop.java_instanceof?(big_integer, number_class).should be_true
end

it "returns true for a matching Java object and interface" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
serializable_interface = Truffle::Interop.java_type("java.io.Serializable")
Truffle::Interop.java_instanceof?(big_integer, serializable_interface).should be_true
end

it "returns false for an unrelated Java object and Java class" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
big_decimal_class = Truffle::Interop.java_type("java.math.BigDecimal")
Truffle::Interop.java_instanceof?(big_integer, big_decimal_class).should be_false
end

it "returns false for an unrelated Ruby object and Java class" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
Truffle::Interop.java_instanceof?(Object.new, big_integer_class).should be_false
end

it "handles boxing of primitives like Java does" do
integer_class = Truffle::Interop.java_type("java.lang.Integer")
Truffle::Interop.java_instanceof?(14, integer_class).should be_true
double_class = Truffle::Interop.java_type("java.lang.Double")
Truffle::Interop.java_instanceof?(14.2, double_class).should be_true
end

it "raises a type error if passed something that is not a Java class" do
lambda { Truffle::Interop.java_instanceof?(14, nil) }.should raise_error(TypeError)
lambda { Truffle::Interop.java_instanceof?(14, String) }.should raise_error(TypeError)
lambda { Truffle::Interop.java_instanceof?(14, Truffle::Debug.java_object) }.should raise_error(TypeError)
end

end
end
24 changes: 24 additions & 0 deletions spec/truffle/interop/respond_to_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@

end

describe "for :is_a?" do

it "and a Java class returns true" do
Truffle::Interop.respond_to?(Truffle::Debug.java_class, :is_a?).should be_true
end

it "and a Java object returns true" do
Truffle::Interop.respond_to?(Truffle::Debug.java_object, :is_a?).should be_true
end

it "and a Ruby object returns true" do
Truffle::Interop.respond_to?(Object.new, :is_a?).should be_true
end

describe "via a direct call" do

it "and a Java array returns true" do
Truffle::Interop.java_array(1, 2, 3).respond_to?(:is_a?).should be_true
end

end

end

describe "for :class" do

it "and a Java class returns true" do
Expand Down
61 changes: 61 additions & 0 deletions spec/truffle/interop/special_forms_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,67 @@
Truffle::Debug.foreign_object.inspect.should =~ /#<Truffle::Interop::Foreign@\h+>/
end

it "#inspect returns a useful string" do
Truffle::Debug.foreign_object.inspect.should =~ /#<Truffle::Interop::Foreign@\h+>/
end

describe "#is_a?" do

guard -> { !TruffleRuby.native? } do

it "returns true for a directly matching Java object and class" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
big_integer.is_a?(big_integer_class).should be_true
end

it "returns true for a directly matching Java object and superclass" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
number_class = Truffle::Interop.java_type("java.lang.Number")
big_integer.is_a?(number_class).should be_true
end

it "returns true for a directly matching Java object and interface" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
serializable_interface = Truffle::Interop.java_type("java.io.Serializable")
big_integer.is_a?(serializable_interface).should be_true
end

it "returns false for an unrelated Java object and Java class" do
big_integer_class = Truffle::Interop.java_type("java.math.BigInteger")
big_integer = big_integer_class.new("14")
big_decimal_class = Truffle::Interop.java_type("java.math.BigDecimal")
big_integer.is_a?(big_decimal_class).should be_false
end

it "returns false for a Java object and a Ruby class" do
java_hash = Truffle::Interop.java_type("java.util.HashMap").new
java_hash.is_a?(Hash).should be_false
end

it "raises a type error for a non-Java foreign object and a non-Java foreign class" do
lambda {
Truffle::Debug.foreign_object.is_a?(Truffle::Debug.foreign_object)
}.should raise_error(TypeError, /cannot check if a foreign object is an instance of a foreign class/)
end

it "works with boxed primitives" do
boxed_integer = Truffle::Debug.foreign_boxed_number(14)
boxed_integer.is_a?(Integer).should be_true
boxed_double = Truffle::Debug.foreign_boxed_number(14.2)
boxed_double.is_a?(Float).should be_true
end

end

it "returns false for a non-Java foreign object and a Ruby class" do
Truffle::Debug.foreign_object.is_a?(Hash).should be_false
end

end

it "#respond_to?(:to_a) sends HAS_SIZE" do
@object.respond_to?(:to_a)
@object.log.should include("HAS_SIZE")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0, or
* GNU General Public License version 2, or
* GNU Lesser General Public License version 2.1.
*/
package org.truffleruby.debug;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.CanResolve;
import com.oracle.truffle.api.interop.MessageResolution;
import com.oracle.truffle.api.interop.Resolve;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.Node;
import org.truffleruby.debug.TruffleDebugNodes.ForeignObjectNode.ForeignObject;

@MessageResolution(receiverType = ForeignObject.class)
public class ForeignObjectMessageResolution {

@CanResolve
public abstract static class Check extends Node {

protected static boolean test(TruffleObject receiver) {
return receiver instanceof ForeignObject;
}

}

@Resolve(message = "IS_BOXED")
public static abstract class ForeignIsBoxedNode extends Node {

protected Object access(VirtualFrame frame, ForeignObject number) {
return false;
}

}

}
6 changes: 3 additions & 3 deletions src/main/java/org/truffleruby/debug/TruffleDebugNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -406,13 +406,13 @@ public Object javaObject() {
@CoreMethod(names = "foreign_object", onSingleton = true)
public abstract static class ForeignObjectNode extends CoreMethodArrayArgumentsNode {

private static class ForeignObject implements TruffleObject {
public static class ForeignObject implements TruffleObject {

@Override
public ForeignAccess getForeignAccess() {
throw new UnsupportedOperationException();
return ForeignObjectMessageResolutionForeign.ACCESS;
}

}

@TruffleBoundary
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/org/truffleruby/interop/InteropNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,39 @@ public boolean isJavaString(Object value) {

}

@CoreMethod(names = "java_instanceof?", isModuleFunction = true, required = 2)
public abstract static class InteropJavaInstanceOfNode extends CoreMethodArrayArgumentsNode {

@Specialization(guards = {
"isJavaObject(boxedInstance)",
"isJavaClassOrInterface(boxedJavaClass)"
})
public boolean javaInstanceOfJava(Object boxedInstance, TruffleObject boxedJavaClass) {
final Object hostInstance = getContext().getEnv().asHostObject(boxedInstance);
final Class<?> javaClass = (Class<?>) getContext().getEnv().asHostObject(boxedJavaClass);
return javaClass.isAssignableFrom(hostInstance.getClass());
}

@Specialization(guards = {
"!isJavaObject(instance)",
"isJavaClassOrInterface(boxedJavaClass)"
})
public boolean javaInstanceOfNotJava(Object instance, TruffleObject boxedJavaClass) {
final Class<?> javaClass = (Class<?>) getContext().getEnv().asHostObject(boxedJavaClass);
return javaClass.isInstance(instance);
}

protected boolean isJavaObject(Object object) {
return object instanceof TruffleObject && getContext().getEnv().isHostObject(object);
}

protected boolean isJavaClassOrInterface(TruffleObject object) {
return getContext().getEnv().isHostObject(object)
&& getContext().getEnv().asHostObject(object) instanceof Class<?>;
}

}

@CoreMethod(names = "to_java_string", isModuleFunction = true, required = 1)
public abstract static class InteropToJavaStringNode extends CoreMethodArrayArgumentsNode {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@ protected OutgoingNode createHelperNode(int argsLength) {
|| name.equals("keys")
|| name.equals("class")
|| name.equals("to_s")
|| name.equals("to_str")) {
|| name.equals("to_str")
|| name.equals("is_a?")) {
final int expectedArgsLength;

switch (name) {
case "delete":
case "is_a?":
expectedArgsLength = 1;
break;
case "size":
Expand Down
24 changes: 20 additions & 4 deletions src/main/ruby/core/truffle/interop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,24 @@ def self.special_form(receiver, name, *args)
receiver = Truffle::Interop.unbox_if_needed(receiver)
raise NameError, 'no method to_str' unless receiver.is_a?(String)
receiver
when :is_a?
receiver = Truffle::Interop.unbox_if_needed(receiver)
check_class = args.first
if Truffle::Interop.foreign?(receiver)
if !TruffleRuby.native? && Truffle::Interop.java_class?(check_class)
# Checking against a Java class
Truffle::Interop.java_instanceof?(receiver, check_class)
elsif Truffle::Interop.foreign?(check_class)
# Checking a foreign (not Java) object against a foreign (not Java) class
raise TypeError, 'cannot check if a foreign object is an instance of a foreign class'
else
# Checking a foreign or Java object against a Ruby class
false
end
else
# The receiver unboxed to a Ruby object or a primitive
receiver.is_a?(check_class)
end
else
raise
end
Expand All @@ -185,13 +203,11 @@ def self.respond_to?(object, name)
Truffle::Interop.executable?(object)
when :class
Truffle::Interop.java_class?(object)
when :inspect
true
when :to_s
true
when :to_str
object = Truffle::Interop.unbox_if_needed(object)
!Truffle::Interop.foreign?(object) && object.is_a?(String)
when :inspect, :to_s, :is_a?
true
else
false
end
Expand Down

0 comments on commit 5d7724a

Please sign in to comment.