Skip to content

Commit eb057e7

Browse files
committed
[GR-33155] Integrate foreign objects better with Ruby objects (#2153)
PullRequest: truffleruby/2874
2 parents 7ccb875 + 6e7cd1e commit eb057e7

29 files changed

+1018
-1063
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ New features:
55
* [TRegex](https://github.com/oracle/graal/tree/master/regex) is now used by default, which provides large speedups for matching regular expressions.
66
* Add `Polyglot.languages` to expose the list of available languages.
77
* Add `Polyglot::InnerContext` to eval code in any available language in an inner isolated context (#2169).
8+
* Foreign objects now have a dynamically-generated class based on their interop traits like `ForeignArray` and are better integrated with Ruby objects (#2149).
9+
* Foreign arrays now have all methods of Ruby `Enumerable` and many methods of `Array` (#2149).
810

911
Bug fixes:
1012

doc/contributor/interop_implicit_api.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ Format: `Ruby code` sends `InteropLibrary message`
3232
- `foreign_object.to_s` sends `asString(foreign_object)` when `isString(foreign_object)` is true
3333
- `foreign_object.to_s` sends `toDisplayString(foreign_object)` otherwise
3434
- `foreign_object.to_str` sends `asString(foreign_object)` when `isString(foreign_object)` is true
35-
- `foreign_object.to_str` raises `NoMethodError` otherwise
35+
- `foreign_object.to_str` raises `NameError` 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`
38+
- `foreign_object.to_f` tries to converts to a Ruby `Float` using `asDouble()` and `(double) asLong()` or raises `NameError`
39+
- `foreign_object.to_i` tries to converts to a Ruby `Integer` using `asInt()` and `asLong()` or raises `NameError`
4040
- `foreign_object.equal?(other)` sends `isIdentical(foreign_object, other)`
4141
- `foreign_object.eql?(other)` sends `isIdentical(foreign_object, other)`
4242
- `foreign_object.object_id` sends `identityHashCode(foreign_object)` when `hasIdentity()` is true (which might not be unique)
@@ -52,8 +52,8 @@ Use `.respond_to?` for calling `InteropLibrary` predicates:
5252
- `foreign_object.respond_to?(:to_str)` sends `isString(foreign_object)`
5353
- `foreign_object.respond_to?(:to_a)` sends `hasArrayElements(foreign_object)`
5454
- `foreign_object.respond_to?(:to_ary)` sends `hasArrayElements(foreign_object)`
55-
- `foreign_object.respond_to?(:to_f)` sends `fitsInDouble()` and `fitsInLong()`
56-
- `foreign_object.respond_to?(:to_i)` sends `fitsInInt()` and `fitsInLong()`
55+
- `foreign_object.respond_to?(:to_f)` sends `fitsInDouble()`
56+
- `foreign_object.respond_to?(:to_i)` sends `fitsInLong()`
5757
- `foreign_object.respond_to?(:size)` sends `hasArrayElements(foreign_object)`
5858
- `foreign_object.respond_to?(:keys)` sends `hasMembers(foreign_object)`
5959
- `foreign_object.respond_to?(:call)` sends `isExecutable(foreign_object)`

spec/truffle/interop/fixtures/classes.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111
module TruffleInteropSpecs
1212

1313
class Logger
14-
attr_reader :log
15-
1614
def initialize
1715
@log = []
1816
end
1917

2018
def <<(*args)
21-
@log << args
19+
@log << args unless @log.frozen?
20+
end
21+
22+
def log
23+
# Stop recording so messages caused by spec expectations are not included
24+
@log.freeze
2225
end
2326
end
2427

spec/truffle/interop/foreign_array_spec.rb

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

spec/truffle/interop/foreign_inspect_to_s_spec.rb

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,24 @@
1313
describe "Java null" do
1414
it "gives a similar representation to Ruby" do
1515
foreign = Truffle::Debug.java_null
16-
foreign.inspect.should == "#<Java null>"
17-
foreign.to_s.should == "#<Java null>"
16+
foreign.inspect.should == "#<Polyglot::ForeignNull[Java] null>"
17+
foreign.to_s.should == "#<Polyglot::ForeignNull[Java] null>"
1818
end
1919
end
2020

2121
describe "Java list" do
2222
it "gives a similar representation to Ruby" do
2323
foreign = Truffle::Interop.to_java_list([1, 2, 3])
24-
foreign.inspect.should =~ /\A#<Java java\.util\.Arrays\$ArrayList:0x\h+ \[1, 2, 3\]>\z/
25-
foreign.to_s.should == "#<Java [1, 2, 3]>"
24+
foreign.inspect.should =~ /\A#<Polyglot::ForeignArray\[Java\] java\.util\.Arrays\$ArrayList:0x\h+ \[1, 2, 3\]>\z/
25+
foreign.to_s.should == "#<Polyglot::ForeignArray[Java] [1, 2, 3]>"
2626
end
2727
end
2828

2929
describe "Java array" do
3030
it "gives a similar representation to Ruby" do
3131
foreign = Truffle::Interop.to_java_array([1, 2, 3])
32-
foreign.inspect.should =~ /\A#<Java int\[\]:0x\h+ \[1, 2, 3\]>\z/
33-
foreign.to_s.should == "#<Java [1, 2, 3]>"
32+
foreign.inspect.should =~ /\A#<Polyglot::ForeignArray\[Java\] int\[\]:0x\h+ \[1, 2, 3\]>\z/
33+
foreign.to_s.should == "#<Polyglot::ForeignArray[Java] [1, 2, 3]>"
3434
end
3535
end
3636

@@ -40,24 +40,24 @@
4040
foreign = Truffle::Interop.to_java_map(hash)
4141

4242
hash.to_s.should == '{:a=>1, :b=>2, :c=>3}' # for comparison
43-
foreign.inspect.should =~ /\A#<Java java\.util\.HashMap:0x\h+ {"a"=>1, "b"=>2, "c"=>3}>\z/
44-
foreign.to_s.should == "#<Java {a=1, b=2, c=3}>"
43+
foreign.inspect.should =~ /\A#<Polyglot::ForeignObject\[Java\] java\.util\.HashMap:0x\h+ {"a"=>1, "b"=>2, "c"=>3}>\z/
44+
foreign.to_s.should == "#<Polyglot::ForeignObject[Java] {a=1, b=2, c=3}>"
4545
end
4646
end
4747

4848
describe "Java class" do
4949
it "gives a similar representation to Ruby" do
5050
foreign = Truffle::Interop.java_type("java.math.BigInteger")
51-
foreign.inspect.should == "#<Java class java.math.BigInteger>"
52-
foreign.to_s.should == "#<Java java.math.BigInteger>"
51+
foreign.inspect.should == "#<Polyglot::ForeignInstantiable[Java] class java.math.BigInteger>"
52+
foreign.to_s.should == "#<Polyglot::ForeignInstantiable[Java] java.math.BigInteger>"
5353
end
5454
end
5555

5656
describe "Java object" do
5757
it "gives a similar representation to Ruby" do
5858
foreign = Truffle::Interop.java_type("java.math.BigInteger").new('14')
59-
foreign.inspect.should =~ /\A#<Java java\.math\.BigInteger:0x\h+ .+>\z/
60-
foreign.to_s.should == "#<Java 14>"
59+
foreign.inspect.should =~ /\A#<Polyglot::ForeignObject\[Java\] java\.math\.BigInteger:0x\h+ .+>\z/
60+
foreign.to_s.should == "#<Polyglot::ForeignObject[Java] 14>"
6161
end
6262
end
6363
end
@@ -67,47 +67,47 @@
6767
x = [1, 2, 3]
6868
foreign = Truffle::Debug.foreign_array_from_java(Truffle::Interop.to_java_array(x))
6969
foreign[0] = foreign
70-
foreign.inspect.should =~ /\A#<Foreign:0x\h+ \[\[...\], 2, 3\]>\z/
71-
foreign.to_s.should == "#<Foreign [foreign array]>"
70+
foreign.inspect.should =~ /\A#<Polyglot::ForeignArray:0x\h+ \[\[...\], 2, 3\]>\z/
71+
foreign.to_s.should == "#<Polyglot::ForeignArray [foreign array]>"
7272
end
7373
end
7474

7575
describe "null" do
7676
it "gives a similar representation to Ruby" do
7777
foreign = Truffle::Debug.foreign_null
78-
foreign.inspect.should == "#<Foreign null>"
79-
foreign.to_s.should == "#<Foreign [foreign null]>"
78+
foreign.inspect.should == "#<Polyglot::ForeignNull null>"
79+
foreign.to_s.should == "#<Polyglot::ForeignNull [foreign null]>"
8080
end
8181
end
8282

8383
describe "executable" do
8484
it "gives a similar representation to Ruby" do
8585
foreign = Truffle::Debug.foreign_executable(14)
86-
foreign.inspect.should =~ /\A#<Foreign:0x\h+ proc>\z/
87-
foreign.to_s.should == "#<Foreign [foreign executable]>"
86+
foreign.inspect.should =~ /\A#<Polyglot::ForeignExecutable:0x\h+ proc>\z/
87+
foreign.to_s.should == "#<Polyglot::ForeignExecutable [foreign executable]>"
8888
end
8989
end
9090

9191
describe "pointer" do
9292
it "gives a similar representation to Ruby" do
9393
foreign = Truffle::Debug.foreign_pointer(0x1234)
94-
foreign.inspect.should == "#<Foreign pointer 0x1234>"
95-
foreign.to_s.should == "#<Foreign [foreign pointer]>"
94+
foreign.inspect.should == "#<Polyglot::ForeignPointer 0x1234>"
95+
foreign.to_s.should == "#<Polyglot::ForeignPointer [foreign pointer]>"
9696
end
9797
end
9898

9999
guard -> { !TruffleRuby.native? } do
100100
describe "array" do
101101
it "gives a similar representation to Ruby" do
102102
foreign = Truffle::Debug.foreign_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
103-
foreign.inspect.should =~ /\A#<Foreign:0x\h+ \[1, 2, 3\]>\z/
104-
foreign.to_s.should == "#<Foreign [foreign array]>"
103+
foreign.inspect.should =~ /\A#<Polyglot::ForeignArray:0x\h+ \[1, 2, 3\]>\z/
104+
foreign.to_s.should == "#<Polyglot::ForeignArray [foreign array]>"
105105
end
106106

107107
it "gives a similar representation to Ruby, even if it is also a pointer" do
108108
foreign = Truffle::Debug.foreign_pointer_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
109-
foreign.inspect.should =~ /\A#<Foreign pointer 0x\h+ \[1, 2, 3\]>\z/
110-
foreign.to_s.should == "#<Foreign [foreign pointer array]>"
109+
foreign.inspect.should =~ /\A#<Polyglot::ForeignArrayPointer 0x0 \[1, 2, 3\]>\z/
110+
foreign.to_s.should == "#<Polyglot::ForeignArrayPointer [foreign pointer array]>"
111111
end
112112
end
113113
end
@@ -116,16 +116,16 @@
116116
describe "object without members" do
117117
it "gives a similar representation to Ruby" do
118118
foreign = Truffle::Debug.foreign_object
119-
foreign.inspect.should =~ /\A#<Foreign:0x\h+>\z/
120-
foreign.to_s.should == "#<Foreign [foreign object]>"
119+
foreign.inspect.should =~ /\A#<Polyglot::ForeignObject:0x\h+>\z/
120+
foreign.to_s.should == "#<Polyglot::ForeignObject [foreign object]>"
121121
end
122122
end
123123

124124
describe "object with members" do
125125
it "gives a similar representation to Ruby" do
126126
foreign = Truffle::Debug.foreign_object_from_map(Truffle::Interop.to_java_map({a: 1, b: 2, c: 3}))
127-
foreign.inspect.should =~ /\A#<Foreign:0x\h+ a=1, b=2, c=3>\z/
128-
foreign.to_s.should == "#<Foreign [foreign object with members]>"
127+
foreign.inspect.should =~ /\A#<Polyglot::ForeignObject:0x\h+ a=1, b=2, c=3>\z/
128+
foreign.to_s.should == "#<Polyglot::ForeignObject [foreign object with members]>"
129129
end
130130
end
131131
end
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved. This
2+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
3+
# redistribute it and/or modify it under the terms of the:
4+
#
5+
# Eclipse Public License version 2.0, or
6+
# GNU General Public License version 2, or
7+
# GNU Lesser General Public License version 2.1.
8+
9+
require_relative '../../../ruby/spec_helper'
10+
11+
describe "Polyglot::ForeignArray" do
12+
before :each do
13+
@foreign = Truffle::Interop.to_java_array([1, 2, 3])
14+
@empty = Truffle::Interop.to_java_array([])
15+
end
16+
17+
it "should have class ForeignArray" do
18+
@foreign.inspect.should =~ /\A#<Polyglot::ForeignArray\[Java\] int\[\]:0x\h+ \[1, 2, 3\]>/
19+
@foreign.length.should == 3
20+
end
21+
22+
it "should index array with #[]" do
23+
@foreign[0].should == 1
24+
@foreign[1].should == 2
25+
@foreign[2].should == 3
26+
@foreign[-1].should == 3
27+
@foreign[-3].should == 1
28+
@foreign[3].should == nil
29+
@foreign[-4].should == nil
30+
end
31+
32+
it "should index array with #at" do
33+
@foreign.at(0).should == 1
34+
@foreign.at(1).should == 2
35+
@foreign.at(2).should == 3
36+
@foreign.at(-1).should == 3
37+
@foreign.at(-3).should == 1
38+
@foreign.at(3).should == nil
39+
@foreign.at(-4).should == nil
40+
end
41+
42+
it "should access the first element with #first" do
43+
@foreign.first.should == 1
44+
@empty.first.should == nil
45+
end
46+
47+
it "should access the last element with #last" do
48+
@foreign.last.should == 3
49+
@empty.last.should == nil
50+
end
51+
52+
it "supports #each" do
53+
index = 0
54+
@foreign.each do |val|
55+
val.should == (index += 1)
56+
end
57+
end
58+
59+
it "supports #each_with_index" do
60+
@foreign.each_with_index do |val, index|
61+
val.should == (index + 1)
62+
end
63+
end
64+
65+
it "returns an Enumerator for #each with no block" do
66+
enum = @foreign.each
67+
enum.class.should == Enumerator
68+
enum.each_with_index do |val, index|
69+
@foreign[index].should == val
70+
end
71+
end
72+
73+
it "supports #take" do
74+
slice = @foreign.take(2)
75+
slice[0].should == 1
76+
slice[1].should == 2
77+
slice.at(2).should == nil
78+
end
79+
80+
it "should allow assignment of array element with #[]=" do
81+
@foreign[2] = 10
82+
@foreign[2].should == 10
83+
end
84+
85+
it "should allow the use of #next" do
86+
enum = @foreign.each
87+
enum.class.should == Enumerator
88+
enum.next.should == 1
89+
end
90+
91+
it "supports #count" do
92+
foreign = Truffle::Interop.to_java_array([1, 2, 4, 2])
93+
foreign.count.should == 4
94+
foreign.count(2).should == 2
95+
foreign.count { |x| x % 2 == 0 }.should == 3
96+
end
97+
end
98+
99+
describe "Foreign arrays" do
100+
it "can be printed with #print" do
101+
foreign = Truffle::Interop.to_java_array([1, 2, 3])
102+
-> {
103+
print foreign
104+
}.should output_to_fd(foreign.to_s)
105+
end
106+
107+
it "can be printed with #puts" do
108+
-> {
109+
puts Truffle::Interop.to_java_array([1, 2, 3])
110+
}.should output_to_fd("1\n2\n3\n")
111+
end
112+
113+
it "can be printed with #p" do
114+
foreign = Truffle::Interop.to_java_array([1, 2, 3])
115+
-> {
116+
p foreign
117+
}.should output_to_fd("#{foreign.inspect}\n")
118+
end
119+
end
120+
121+
describe "Foreign arrays that are also pointers" do
122+
it "can be printed with #print" do
123+
foreign = Truffle::Debug.foreign_pointer_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
124+
-> {
125+
print foreign
126+
}.should output_to_fd(foreign.to_s)
127+
end
128+
129+
it "can be printed with #puts" do
130+
-> {
131+
puts Truffle::Debug.foreign_pointer_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
132+
}.should output_to_fd("1\n2\n3\n")
133+
end
134+
135+
it "can be printed with #p" do
136+
foreign = Truffle::Debug.foreign_pointer_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
137+
-> {
138+
p foreign
139+
}.should output_to_fd("#{foreign.inspect}\n")
140+
end
141+
end

0 commit comments

Comments
 (0)