Skip to content

Commit 9d34b00

Browse files
committed
[GR-38544] Support foreign strings fully and treat java.lang.String and TruffleString as foreign strings
PullRequest: truffleruby/3399
2 parents 4693f7b + 26f5dee commit 9d34b00

33 files changed

+896
-559
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
New features:
44

5+
* Foreign strings now have all methods of Ruby `String`. They are treated as `#frozen?` UTF-8 Ruby Strings.
56

67
Bug fixes:
78

@@ -15,6 +16,9 @@ Performance:
1516

1617
Changes:
1718

19+
* No more conversion between Java Strings and Ruby Strings at the interop boundary.
20+
* Removed `Truffle::Interop.{import_without_conversion,export_without_conversion}` (use `Polyglot.{import,export}` instead).
21+
* Removed `Truffle::Interop.members_without_conversion` (use `Truffle::Interop.members` instead).
1822

1923
# 22.2.0
2024

doc/contributor/interop.md

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ details.
1818
* [How to explicitly send messages from Ruby](#how-to-explicitly-send-messages-from-ruby)
1919
* [How to send messages using idiomatic Ruby](#how-to-send-messages-using-idiomatic-ruby)
2020
* [What messages are sent for Ruby syntax on foreign objects](#what-messages-are-sent-for-ruby-syntax-on-foreign-objects)
21-
* [String conversion](#string-conversion)
21+
* [Conversion of primitive values](#conversion-of-primitive-values)
2222
* [Import and export](#import-and-export)
2323
* [Interop eval](#interop-eval)
2424
* [Java interop](#java-interop)
@@ -119,39 +119,16 @@ runtime object instance.
119119
the foreign object, including allowing the special-forms listed above (see
120120
[notes on method resolution](#notes-on-method-resolution)).
121121

122-
## String conversion
123-
124-
Ruby strings and symbols are unboxable to Java strings.
122+
## Conversion of primitive values
125123

126124
A call from Ruby to a foreign language using `NEW`, `EXECUTE`, `INVOKE`, `READ`,
127-
`WRITE`, or `UNBOX`, that has Ruby strings or symbols as arguments, will convert
128-
each Ruby string or symbol argument to a Java string. You can avoid this
125+
`WRITE`, or `UNBOX`, that returns a `byte`, `short` or `float` will convert the
126+
returned value to respectively `int`, `int` or `double`. You can avoid this
129127
conversion for `EXECUTE` using `Truffle::Interop.execute_without_conversion`,
130128
for `READ` using `Truffle::Interop.read_without_conversion`, and for `UNBOX`
131129
using `Truffle::Interop.unbox_without_conversion`.
132130

133-
`Truffle::Interop.members` converts Java string member names to Ruby strings, so it
134-
also has a `Truffle::Interop.members_without_conversion` equivalent.
135-
136-
A call from Ruby to a foreign language using `NEW`, `EXECUTE`, `INVOKE`, `READ`,
137-
`WRITE`, or `UNBOX`, that returns a Java string will convert the returned string
138-
to a Ruby string.
139-
140-
A call from a foreign language to Ruby using `NEW`, `EXECUTE`, `INVOKE`, or
141-
`WRITE`, that has Java strings as arguments, will convert each Java string
142-
argument to a Ruby string.
143-
144-
A call from a foreign language to Ruby `NEW`, `EXECUTE`, `INVOKE` or `READ`
145-
that returns a Ruby string will not convert it to a Java string, as this would
146-
break our C extension support which uses these messages to get Ruby objects
147-
and expects to be able to mutate them and so on
148-
(compare with `Truffle::Interop.execute_without_conversion`).
149-
150-
Export and import also converts strings, and also has `_without_conversion`
151-
counterparts.
152-
153-
Boxed foreign strings (foreign objects that respond positively to `IS_BOXED` and
154-
`UNBOX` to a Java string) unbox on `to_s`, `to_str` and `inspect`.
131+
`Polyglot.import` also converts `byte/short/float`.
155132

156133
## Import and export
157134

doc/contributor/interop_implicit_api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ Format: `Ruby code` sends `InteropLibrary message`
3131
- `foreign_object.method_name(*arguments, &block)` sends `invokeMember(foreign_object, method_name, *arguments, block)`
3232
- `foreign_object.new(*arguments)` sends `instantiate(foreign_object, *arguments)`
3333
- `foreign_object.inspect` returns a Ruby-style `#inspect` string showing members, array elements, etc
34-
- `foreign_object.to_s` sends `asString(foreign_object)` when `isString(foreign_object)` is true
34+
- `foreign_object.to_s` sends `asTruffleString(foreign_object)` when `isString(foreign_object)` is true
3535
- `foreign_object.to_s` sends `toDisplayString(foreign_object)` otherwise
36-
- `foreign_object.to_str` sends `asString(foreign_object)` when `isString(foreign_object)` is true
36+
- `foreign_object.to_str` sends `asTruffleString(foreign_object)` when `isString(foreign_object)` is true
3737
- `foreign_object.to_str` raises `NameError` otherwise
3838
- `foreign_object.to_a` converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`
3939
- `foreign_object.to_ary` converts to a Ruby `Array` with `Truffle::Interop.to_array(foreign_object)`

doc/user/polyglot.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ The JVM configuration automatically has access to other languages.
2020
* [Using Ruby objects from a foreign language](#using-ruby-objects-from-a-foreign-language)
2121
* [Using foreign objects from Ruby](#using-foreign-objects-from-ruby)
2222
* [Accessing Java objects](#accessing-java-objects)
23-
* [Strings](#strings)
2423
* [Threading and interop](#threading-and-interop)
2524
* [Embedded configuration](#embedded-configuration)
2625

@@ -223,10 +222,6 @@ boolean isNull()
223222

224223
The [JRuby migration guide](jruby-migration.md) includes some more examples.
225224

226-
## Strings
227-
228-
Ruby strings and symbols are converted to Java strings when they are passed to foreign languages, and Java strings are converted to Ruby strings when they are passed into Ruby.
229-
230225
## Threading and Interop
231226

232227
Ruby is designed to be a multi-threaded language and much of the ecosystem expects threads to be available.

spec/truffle/interop/boxed_spec.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@
99
require_relative '../../ruby/spec_helper'
1010

1111
describe "Truffle::Interop.boxed?" do
12+
it "returns true for numbers" do
13+
Truffle::Interop.boxed?(1).should be_true
14+
Truffle::Interop.boxed?(1.2).should be_true
15+
Truffle::Interop.boxed?(Truffle::Debug.foreign_boxed_value(2)).should be_true
16+
Truffle::Interop.boxed?(Truffle::Debug.foreign_boxed_value(2.3)).should be_true
17+
end
1218

13-
it "returns true for strings" do
14-
Truffle::Interop.boxed?('test').should be_true
19+
it "returns false for strings" do
20+
Truffle::Interop.boxed?('test').should be_false
1521
end
1622

17-
it "returns true for symbols" do
18-
Truffle::Interop.boxed?(:test).should be_true
23+
it "returns false for symbols" do
24+
Truffle::Interop.boxed?(:test).should be_false
1925
end
2026

2127
it "returns false for other objects" do
2228
Truffle::Interop.boxed?(Object.new).should be_false
2329
end
24-
2530
end

spec/truffle/interop/export_spec.rb

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,59 +13,49 @@
1313
it "exports an object" do
1414
object = Object.new
1515
Truffle::Interop.export :exports_an_object, object
16-
Truffle::Interop.import(:exports_an_object).should == object
16+
Truffle::Interop.import(:exports_an_object).should.equal? object
1717
end
1818

1919
it "exports a primitive boolean" do
2020
Truffle::Interop.export :exports_a_primitive_number, true
21-
Truffle::Interop.import(:exports_a_primitive_number).should be_true
21+
Truffle::Interop.import(:exports_a_primitive_number).should.equal? true
2222
end
2323

2424
it "exports a primitive number" do
2525
Truffle::Interop.export :exports_a_primitive_number, 14
26-
Truffle::Interop.import(:exports_a_primitive_number).should == 14
26+
Truffle::Interop.import(:exports_a_primitive_number).should.equal? 14
2727
end
2828

2929
it "exports a string" do
30-
Truffle::Interop.export :exports_a_string, 'hello'
31-
(Truffle::Interop.import(:exports_a_string) == 'hello').should be_true
30+
ruby_string = 'hello'
31+
Truffle::Interop.export :exports_a_string, ruby_string
32+
Truffle::Interop.import(:exports_a_string).should.equal?(ruby_string)
3233
end
3334

34-
it "exports a symbol, getting back a string" do
35+
it "exports a symbol" do
3536
Truffle::Interop.export :exports_a_symbol, :hello
36-
(Truffle::Interop.import(:exports_a_symbol) == 'hello').should be_true
37+
Truffle::Interop.import(:exports_a_symbol).should.equal? :hello
3738
end
3839

3940
it "exports a foreign object" do
4041
foreign_object = Truffle::Debug.foreign_object
4142
Truffle::Interop.export :exports_a_foreign_object, foreign_object
42-
Truffle::Interop.import(:exports_a_foreign_object).equal?(foreign_object).should be_true
43+
Truffle::Interop.import(:exports_a_foreign_object).should.equal?(foreign_object)
4344
end
4445

4546
it "exports a Java string" do
46-
Truffle::Interop.export :exports_a_java_string, Truffle::Interop.to_java_string('hello')
47-
Truffle::Interop.import(:exports_a_java_string).should == 'hello'
47+
java_string = Truffle::Interop.to_java_string('hello')
48+
Truffle::Interop.export :exports_a_java_string, java_string
49+
Truffle::Interop.import(:exports_a_java_string).should.equal?(java_string)
50+
java_string.should == 'hello'
4851
end
4952

50-
it "converts to Java when exporting a string" do
51-
Truffle::Interop.export :exports_a_string_with_conversion, 'hello'
52-
imported = Truffle::Interop.import_without_conversion(:exports_a_string_with_conversion)
53-
Truffle::Interop.java_string?(imported).should be_true
54-
Truffle::Interop.from_java_string(imported).should == 'hello'
55-
end
56-
57-
it "can export a string without conversion to Java" do
58-
Truffle::Interop.export_without_conversion :exports_a_string_without_conversion, 'hello'
59-
imported = Truffle::Interop.import_without_conversion(:exports_a_string_without_conversion)
60-
Truffle::Interop.java_string?(imported).should be_false
61-
imported.should == 'hello'
62-
end
63-
64-
it "can import a string without conversion from Java" do
65-
Truffle::Interop.export :imports_a_string_without_conversion, 'hello'
66-
imported = Truffle::Interop.import_without_conversion(:imports_a_string_without_conversion)
67-
Truffle::Interop.java_string?(imported).should be_true
68-
Truffle::Interop.from_java_string(imported).should == 'hello'
53+
it "does not convert to Java when exporting a Ruby string" do
54+
ruby_string = 'hello'
55+
Truffle::Interop.export :exports_a_string_with_conversion, ruby_string
56+
imported = Truffle::Interop.import(:exports_a_string_with_conversion)
57+
Truffle::Interop.should_not.java_string?(imported)
58+
imported.should.equal?(ruby_string)
6959
end
7060

7161
it "can be used with a string name" do
@@ -85,7 +75,7 @@
8575

8676
it "returns the original exported value" do
8777
string = 'hello'
88-
Truffle::Interop.export(:returns_original_value, string).equal?(string).should be_true
78+
Truffle::Interop.export(:returns_original_value, string).should.equal?(string)
8979
end
9080
end
9181
end

spec/truffle/interop/foreign_string_spec.rb

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

spec/truffle/interop/java_string_spec.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@
1010

1111
describe "Java strings" do
1212

13-
it "is String" do
13+
it "is Interop.string?" do
1414
Truffle::Interop.string?(Truffle::Interop.to_java_string('test')).should be_true
1515
end
1616

17-
it "return the same object if attempted to be unboxed" do
18-
unboxed = Truffle::Interop.unbox_without_conversion(Truffle::Interop.to_java_string('test'))
19-
Truffle::Interop.java_string?(unboxed).should be_true
20-
Truffle::Interop.from_java_string(unboxed).should == 'test'
17+
it "is Interop.java_string?" do
18+
Truffle::Interop.java_string?(Truffle::Interop.to_java_string('test')).should be_true
2119
end
2220

2321
it "are converted to Ruby automatically on the LHS of string concatenation" do

spec/truffle/interop/members_spec.rb

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@
1616
keys.should be_an_instance_of(Array)
1717
end
1818

19-
it "returns an array of Ruby strings by default" do
20-
keys = Truffle::Interop.members({'a' => 1, 'b' => 2, 'c' => 3})
21-
keys[0].should be_an_instance_of(String)
22-
end
19+
it "returns an array of interop strings" do
20+
keys = Truffle::Interop.members(Object.new)
21+
keys.each do |key|
22+
Truffle::Interop.should.string? key
23+
String.should === key # actually Ruby Strings
24+
end
2325

24-
it "returns an array of Java strings if you don't use conversion" do
25-
keys = Truffle::Interop.members_without_conversion({'a' => 1, 'b' => 2, 'c' => 3})
26-
key = keys[0]
27-
Truffle::Interop.java_string?(key).should be_true
26+
members = Truffle::Interop.members(Truffle::Debug.foreign_object_with_members)
27+
members.should == ["a", "b", "c", "method1", "method2"]
28+
members.each do |key|
29+
Truffle::Interop.should.string? key
30+
Truffle::Interop.should.java_string? key # actually Java Strings
31+
end
2832
end
2933

3034
it "returns an array of public methods for an array" do

spec/truffle/interop/node_library_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,11 @@ def get_scopes()
7676

7777
scope["c"].should == :c
7878
scope["c"] = :d
79-
scope["c"].should == "d"
79+
scope["c"].should == :d
8080

8181
scope["a"].should == :a
8282
scope["a"] = :b
83-
scope["a"].should == "b"
83+
scope["a"].should == :b
8484

8585
-> { scope["missing"] = "missing" }.should raise_error(NameError)
8686
end

0 commit comments

Comments
 (0)