Skip to content

Commit d6e56aa

Browse files
committed
[GR-23912] [GR-21764] Foreign object changes.
PullRequest: truffleruby/1737
2 parents 0a29d3b + 286671a commit d6e56aa

File tree

11 files changed

+283
-192
lines changed

11 files changed

+283
-192
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ New features:
1212
* `foreign_object.name = value` will now call `Interoplibrary#writeMember("name", value)` instead of `invokeMember("name=", value)`.
1313
* Always show the Ruby core library files in backtraces (#1414).
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).
15+
* Calls to foreign objects with a block argument will now pass the block as the last argument.
16+
* `foreign.name` will now use `invokeMember` if invocable and if not use `readMember`, see `doc/contrib/interop_implicit_api.md` for details.
1517

1618
Bug fixes:
1719

doc/contributor/interop_implicit_api.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ Format: `Ruby code` sends `InteropLibrary message`
2020
- `foreign_object.nil?` sends `isNull(foreign_object)`
2121
- `foreign_object.size` sends `getArraySize(foreign_object)`
2222
- `foreign_object.keys` sends `getMembers(foreign_object)`
23-
- `foreign_object.method_name` sends `invoke_member(foreign_object, method_name)`
24-
- `foreign_object.method_name(*arguments)` sends `invoke_member(foreign_object, method_name, *arguments)`
23+
- `foreign_object.method_name` sends `invokeMember(foreign_object, method_name)` if member is invocable
24+
- `foreign_object.method_name` sends `readMember(foreign_object, method_name)` if member is readable but not invocable
25+
- `foreign_object.method_name` sends `readMember(foreign_object, method_name)` and raises if member is neither invocable nor readable
26+
- `foreign_object.method_name(*arguments)` sends `invokeMember(foreign_object, method_name, *arguments)`
27+
- `foreign_object.method_name(*arguments, &block)` sends `invokeMember(foreign_object, method_name, *arguments, block)`
2528
- `foreign_object.new(*arguments)` sends `instantiate(foreign_object, *arguments)`
2629
- `foreign_object.class` sends `readMember(foreign_object, "class")` when `foreign_object` is a `java.lang.Class`
2730
- `foreign_object.class` sends `getMetaObject(foreign_object)`

spec/truffle/interop/fixtures/classes.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@
1010

1111
module TruffleInteropSpecs
1212

13+
class Logger
14+
attr_reader :log
15+
16+
def initialize
17+
@log = []
18+
end
19+
20+
def <<(*args)
21+
@log << args
22+
end
23+
end
24+
1325
class AsPointerClass
1426
def polyglot_pointer?
1527
true

spec/truffle/interop/special_forms_spec.rb

Lines changed: 140 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@
2424

2525
describe "Interop special forms" do
2626

27-
before :each do
28-
@object = Truffle::Interop.logging_foreign_object
29-
end
30-
3127
after :all do
3228
file = File.expand_path('../../../doc/contributor/interop_implicit_api.md', __dir__)
3329
File.open(file, 'w') do |out|
@@ -49,98 +45,148 @@
4945
doc[form, result]
5046
end
5147

48+
proxy = -> obj {
49+
logger = TruffleInteropSpecs::Logger.new
50+
return Truffle::Interop.proxy_foreign_object(obj, logger), obj, logger
51+
}
52+
5253
# TODO (pitr-ch 23-Mar-2020): test what method has a precedence, special or the invokable-member on the foreign object
5354
# TODO (pitr-ch 23-Mar-2020): test left side operator conversion with asBoolean, asString, etc.
5455

5556
it description['[name]', :readMember, [:name]] do
56-
-> { @object[:foo] }.should raise_error(Polyglot::UnsupportedMessageError)
57-
-> { @object['bar'] }.should raise_error(Polyglot::UnsupportedMessageError)
58-
Truffle::Interop.to_display_string(@object).should include("readMember(foo)")
59-
Truffle::Interop.to_display_string(@object).should include("readMember(bar)")
57+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
58+
-> { pfo[:foo] }.should raise_error(NameError)
59+
-> { pfo['bar'] }.should raise_error(NameError)
60+
l.log.should include(['readMember', 'foo'])
61+
l.log.should include(['readMember', 'bar'])
62+
pm.log.should include([:polyglot_read_member, 'foo'])
63+
pm.log.should include([:polyglot_read_member, 'bar'])
6064
end
6165

6266
it description['[index]', :readArrayElement, [:index]] do
63-
-> { @object[0] }.should raise_error(Polyglot::UnsupportedMessageError)
64-
Truffle::Interop.to_display_string(@object).should include("readArrayElement(0)")
67+
pfo, pa, l = proxy[TruffleInteropSpecs::PolyglotArray.new]
68+
-> { pfo[0] }.should raise_error(IndexError)
69+
l.log.should include(['readArrayElement', 0])
70+
pa.log.should include([:polyglot_read_array_element, 0])
6571
end
6672

6773
it description['[name] = value', :writeMember, [:name, :value]] do
68-
-> { (@object[:foo] = 1) }.should raise_error(Polyglot::UnsupportedMessageError)
69-
-> { (@object['bar'] = 2) }.should raise_error(Polyglot::UnsupportedMessageError)
70-
Truffle::Interop.to_display_string(@object).should include("writeMember(foo, 1)")
71-
Truffle::Interop.to_display_string(@object).should include("writeMember(bar, 2)")
74+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
75+
pfo[:foo] = 1
76+
pfo['bar'] = 2
77+
l.log.should include(['writeMember', 'foo', 1])
78+
l.log.should include(['writeMember', 'bar', 2])
79+
pm.log.should include([:polyglot_write_member, "foo", 1])
80+
pm.log.should include([:polyglot_write_member, "bar", 2])
7281
end
7382

7483
it description['[index] = value', :writeArrayElement, [:index, :value]] do
75-
-> { (@object[0] = 1) }.should raise_error(Polyglot::UnsupportedMessageError)
76-
Truffle::Interop.to_display_string(@object).should include("writeArrayElement(0, 1)")
84+
pfo, pa, l = proxy[TruffleInteropSpecs::PolyglotArray.new]
85+
pfo[0] = 1
86+
l.log.should include(['writeArrayElement', 0, 1])
87+
pa.log.should include([:polyglot_write_array_element, 0, 1])
7788
end
7889

7990
it description['.name = value', :writeMember, [:name, :value]] do
80-
pm = TruffleInteropSpecs::PolyglotMember.new
81-
pfo = Truffle::Interop.proxy_foreign_object(pm)
91+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
8292
pfo.foo = :bar
83-
messages = pm.log
84-
messages.should include([:polyglot_write_member, "foo", :bar])
93+
l.log.should include(['writeMember', 'foo', :bar])
94+
pm.log.should include([:polyglot_write_member, "foo", :bar])
8595
end
8696

8797
it description['.name = *arguments', :writeMember, [:name, 'arguments']] do
88-
pm = TruffleInteropSpecs::PolyglotMember.new
89-
pfo = Truffle::Interop.proxy_foreign_object(pm)
98+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
9099
pfo.foo = :bar, :baz
91-
messages = pm.log
92-
messages.should include([:polyglot_write_member, "foo", [:bar, :baz]])
100+
l.log.should include(['writeMember','foo', [:bar, :baz]])
101+
pm.log.should include([:polyglot_write_member, "foo", [:bar, :baz]])
93102
end
94103

95104
it "raises an argument error if an assignment method is called with more than 1 argument" do
96-
pm = TruffleInteropSpecs::PolyglotMember.new
97-
pfo = Truffle::Interop.proxy_foreign_object(pm)
105+
pfo, _, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
106+
l.log.should_not include(['writeMember', :bar, :baz])
98107
-> { pfo.__send__(:foo=, :bar, :baz) }.should raise_error(ArgumentError)
99108
end
100109

101110
it description['.delete(name)', :removeMember, [:name]] do
102-
-> { @object.delete :foo }.should raise_error(Polyglot::UnsupportedMessageError)
103-
Truffle::Interop.to_display_string(@object).should include("removeMember(foo)")
111+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
112+
-> { pfo.delete :foo }.should raise_error(NameError)
113+
l.log.should include(['removeMember', 'foo'])
114+
pm.log.should include([:polyglot_remove_member, 'foo'])
104115
end
105116

106117
it description['.delete(index)', :removeArrayElement, [:index]] do
107-
-> { @object.delete 14 }.should raise_error(Polyglot::UnsupportedMessageError)
108-
Truffle::Interop.to_display_string(@object).should include("removeArrayElement(14)")
118+
pfo, pa, l = proxy[TruffleInteropSpecs::PolyglotArray.new]
119+
-> { pfo.delete 14 }.should raise_error(IndexError)
120+
l.log.should include(['removeArrayElement', 14])
121+
pa.log.should include([:polyglot_remove_array_element, 14])
109122
end
110123

111124
it description['.call(*arguments)', :execute, ['*arguments']] do
112-
-> { @object.call(1, 2, 3) }.should raise_error(Polyglot::UnsupportedMessageError)
113-
Truffle::Interop.to_display_string(@object).should include("execute(1, 2, 3)")
125+
pfo, _, l = proxy[-> *x { x }]
126+
pfo.call(1, 2, 3)
127+
l.log.should include(['execute', 1, 2, 3])
114128
end
115129

116130
it description['.nil?', :isNull] do
117-
@object.nil?
118-
Truffle::Interop.to_display_string(@object).should include("isNull()")
131+
pfo, _, l = proxy[Object.new]
132+
pfo.nil?
133+
l.log.should include(['isNull'])
119134
end
120135

121136
it description['.size', :getArraySize] do
122-
-> { @object.size }.should raise_error(Polyglot::UnsupportedMessageError)
123-
Truffle::Interop.to_display_string(@object).should include("getArraySize()")
137+
pfo, _, l = proxy[Object.new]
138+
-> { pfo.size }.should raise_error(Polyglot::UnsupportedMessageError)
139+
l.log.should include(['getArraySize'])
124140
end
125141

126142
it description['.keys', :getMembers] do
127-
-> { @object.keys }.should raise_error(Polyglot::UnsupportedMessageError)
128-
Truffle::Interop.to_display_string(@object).should include("getMembers(false)")
143+
pfo, _, l = proxy[Object.new]
144+
pfo.keys
145+
l.log.should include(['getMembers', false])
129146
end
130147

131-
it description['.method_name', :invoke_member, ['method_name']] do
132-
-> { @object.foo }.should raise_error(Polyglot::UnsupportedMessageError)
133-
Truffle::Interop.to_display_string(@object).should include("invokeMember(foo)")
148+
it description['.method_name', :invokeMember, ['method_name'], 'if member is invocable'] do
149+
pfo, _, l = proxy[Class.new { def foo; 3; end }.new]
150+
pfo.foo
151+
l.log.should include(["isMemberInvocable", "foo"])
152+
l.log.should include(["invokeMember", "foo"])
134153
end
135154

136-
it description['.method_name(*arguments)', :invoke_member, ['method_name', '*arguments']] do
137-
-> { @object.bar(1, 2, 3) }.should raise_error(Polyglot::UnsupportedMessageError)
138-
Truffle::Interop.to_display_string(@object).should include("invokeMember(bar, 1, 2, 3)")
155+
it description['.method_name', :readMember, ['method_name'], 'if member is readable but not invocable'] do
156+
pfo, _, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
157+
pfo.foo = :bar
158+
pfo.foo
159+
l.log.should include(["isMemberInvocable", "foo"])
160+
l.log.should include(["readMember", "foo"])
161+
end
162+
163+
it description['.method_name', :readMember, ['method_name'], 'and raises if member is neither invocable nor readable'] do
164+
pfo, _, l = proxy[Object.new]
165+
-> { pfo.foo }.should raise_error(NameError)
166+
l.log.should include(["isMemberInvocable", "foo"])
167+
l.log.should include(["readMember", "foo"])
168+
end
169+
170+
it description['.method_name(*arguments)', :invokeMember, ['method_name', '*arguments']] do
171+
pfo, _, l = proxy[Object.new]
172+
-> { pfo.bar(1, 2, 3) }.should raise_error(NoMethodError)
173+
l.log.should include(["invokeMember", "bar", 1, 2, 3])
174+
end
175+
176+
it description['.method_name(*arguments, &block)', :invokeMember, ['method_name', '*arguments, block']] do
177+
pfo, pm, l = proxy[TruffleInteropSpecs::PolyglotMember.new]
178+
block = Proc.new { }
179+
pfo.foo = -> *_ { 1 }
180+
pfo.foo(1, 2, 3, &block)
181+
l.log.should include(["invokeMember", "foo", 1, 2, 3, block])
182+
messages = pm.log
183+
messages.should include([:polyglot_invoke_member, "foo", 1, 2, 3, block])
139184
end
140185

141186
it description['.new(*arguments)', :instantiate, ['*arguments']] do
142-
-> { @object.new }.should raise_error(Polyglot::UnsupportedMessageError)
143-
Truffle::Interop.to_display_string(@object).should include("instantiate()")
187+
pfo, _, l = proxy[Object.new]
188+
-> { pfo.new }.should raise_error(Polyglot::UnsupportedMessageError)
189+
l.log.should include(["instantiate"])
144190
end
145191

146192
guard -> { !TruffleRuby.native? } do
@@ -150,93 +196,108 @@
150196
end
151197

152198
it description['.class', :getMetaObject] do
153-
@object.class.should == Truffle::Interop::Foreign
154-
Truffle::Interop.to_display_string(@object).should include("hasMetaObject()")
199+
pfo, _, l = proxy[Truffle::Debug.foreign_object]
200+
# For Truffle::Debug.foreign_object, hasMetaObject() is false, so then .class returns Truffle::Interop::Foreign
201+
pfo.class.should == Truffle::Interop::Foreign
202+
l.log.should include(["hasMetaObject"])
155203
end
156204

157205
it doc['.inspect', 'returns a Ruby-style `#inspect` string showing members, array elements, etc'] do
158206
# More detailed specs in spec/truffle/interop/foreign_inspect_to_s_spec.rb
159-
@object.inspect.should =~ /\A#<Foreign:0x\h+>\z/
207+
Truffle::Debug.foreign_object.inspect.should =~ /\A#<Foreign:0x\h+>\z/
160208
end
161209

162210
it description['.to_s', :asString, [], 'when `isString(foreign_object)` is true'] do
163-
@object = Truffle::Interop.logging_foreign_object("asString contents")
164-
@object.to_s.should == "asString contents"
165-
Truffle::Interop.to_display_string(@object).should include("asString()")
211+
pfo, _, l = proxy['asString contents']
212+
pfo.to_s.should == "asString contents"
213+
l.log.should include(["asString"])
166214
end
167215

168216
it description['.to_s', :toDisplayString, [], 'otherwise'] do
169-
@object.to_s.should include("toDisplayString(")
217+
pfo, _, l = proxy[Object.new]
218+
pfo.to_s
219+
l.log.should include(["toDisplayString", true])
170220
end
171221

172222
it description['.to_str', :asString, [], 'when `isString(foreign_object)` is true'] do
173-
@object = Truffle::Interop.logging_foreign_object("asString contents")
174-
@object.to_str.should == "asString contents"
175-
Truffle::Interop.to_display_string(@object).should include("isString()")
176-
Truffle::Interop.to_display_string(@object).should include("asString()")
223+
pfo, _, l = proxy["asString contents"]
224+
pfo.to_str.should == "asString contents"
225+
l.log.should include(["isString"])
226+
l.log.should include(["asString"])
177227
end
178228

179229
it doc['.to_str', 'raises `NoMethodError` otherwise'] do
180-
-> { @object.to_str }.should raise_error(NoMethodError)
230+
pfo, _, l = proxy[Object.new]
231+
-> { pfo.to_str }.should raise_error(NoMethodError)
232+
l.log.should include(["isString"])
181233
end
182234

183235
it doc['.to_a', 'converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`'] do
184-
-> { @object.to_a }.should raise_error(RuntimeError)
185-
Truffle::Interop.to_display_string(@object).should include("hasArrayElements()")
236+
pfo, _, l = proxy[Object.new]
237+
-> { pfo.to_a }.should raise_error(RuntimeError)
238+
l.log.should include(["hasArrayElements"])
186239
end
187240

188241
it doc['.to_ary', 'converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`'] do
189-
-> { @object.to_a }.should raise_error(RuntimeError)
190-
Truffle::Interop.to_display_string(@object).should include("hasArrayElements()")
242+
pfo, _, l = proxy[Object.new]
243+
-> { pfo.to_a }.should raise_error(RuntimeError)
244+
l.log.should include(["hasArrayElements"])
191245
end
192246

193247
output << "\nUse `.respond_to?` for calling `InteropLibrary` predicates:\n"
194248

195249
it doc['.respond_to?(:inspect)', "is always true"] do
196-
@object.respond_to?(:inspect).should be_true
250+
Truffle::Debug.foreign_object.respond_to?(:inspect).should be_true
197251
end
198252

199253
it doc['.respond_to?(:to_s)', "is always true"] do
200-
@object.respond_to?(:to_s).should be_true
254+
Truffle::Debug.foreign_object.respond_to?(:to_s).should be_true
201255
end
202256

203257
it description['.respond_to?(:to_str)', :isString] do
204-
@object.respond_to?(:to_str)
205-
Truffle::Interop.to_display_string(@object).should include("isString()")
258+
pfo, _, l = proxy[Object.new]
259+
pfo.respond_to?(:to_str)
260+
l.log.should include(["isString"])
206261
end
207262

208263
it description['.respond_to?(:to_a)', :hasArrayElements] do
209-
@object.respond_to?(:to_a)
210-
Truffle::Interop.to_display_string(@object).should include("hasArrayElements()")
264+
pfo, _, l = proxy[Object.new]
265+
pfo.respond_to?(:to_a)
266+
l.log.should include(["hasArrayElements"])
211267
end
212268

213269
it description['.respond_to?(:to_ary)', :hasArrayElements] do
214-
@object.respond_to?(:to_ary)
215-
Truffle::Interop.to_display_string(@object).should include("hasArrayElements()")
270+
pfo, _, l = proxy[Object.new]
271+
pfo.respond_to?(:to_ary)
272+
l.log.should include(["hasArrayElements"])
216273
end
217274

218275
it description['.respond_to?(:size)', :hasArrayElements] do
219-
@object.respond_to?(:size)
220-
Truffle::Interop.to_display_string(@object).should include("hasArrayElements()")
276+
pfo, _, l = proxy[Object.new]
277+
pfo.respond_to?(:size)
278+
l.log.should include(["hasArrayElements"])
221279
end
222280

223281
it description['.respond_to?(:keys)', :hasMembers] do
224-
@object.respond_to?(:keys)
225-
Truffle::Interop.to_display_string(@object).should include("hasMembers()")
282+
pfo, _, l = proxy[Object.new]
283+
pfo.respond_to?(:keys)
284+
l.log.should include(["hasMembers"])
226285
end
227286

228287
it description['.respond_to?(:call)', :isExecutable] do
229-
@object.respond_to?(:call)
230-
Truffle::Interop.to_display_string(@object).should include("isExecutable()")
288+
pfo, _, l = proxy[Object.new]
289+
pfo.respond_to?(:call)
290+
l.log.should include(["isExecutable"])
231291
end
232292

233293
it description['.respond_to?(:new)', :isInstantiable] do
234-
@object.respond_to?(:new)
235-
Truffle::Interop.to_display_string(@object).should include("isInstantiable()")
294+
pfo, _, l = proxy[Object.new]
295+
pfo.respond_to?(:new)
296+
l.log.should include(["isInstantiable"])
236297
end
237298

238299
it doc['.respond_to?(:is_a?)', "is always true"] do
239-
@object.respond_to?(:is_a?).should be_true
300+
Truffle::Debug.foreign_object.respond_to?(:is_a?).should be_true
240301
end
241302

242303
describe "#is_a?" do

0 commit comments

Comments
 (0)