Skip to content

Commit f32117b

Browse files
committedMar 9, 2025·
[GR-20446] Add missing specs for Data
PullRequest: truffleruby/4491
2 parents fa1a18a + a07ec4d commit f32117b

14 files changed

+389
-9
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#deconstruct" do
6+
it "returns a hash of attributes" do
7+
klass = Data.define(:x, :y)
8+
d = klass.new(1, 2)
9+
d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
10+
end
11+
12+
it "requires one argument" do
13+
klass = Data.define(:x, :y)
14+
d = klass.new(1, 2)
15+
16+
-> {
17+
d.deconstruct_keys
18+
}.should raise_error(ArgumentError, /wrong number of arguments \(given 0, expected 1\)/)
19+
end
20+
21+
it "returns only specified keys" do
22+
klass = Data.define(:x, :y)
23+
d = klass.new(1, 2)
24+
25+
d.deconstruct_keys([:x, :y]).should == {x: 1, y: 2}
26+
d.deconstruct_keys([:x] ).should == {x: 1}
27+
d.deconstruct_keys([] ).should == {}
28+
end
29+
30+
it "accepts string attribute names" do
31+
klass = Data.define(:x, :y)
32+
d = klass.new(1, 2)
33+
d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
34+
end
35+
36+
it "accepts argument position number as well but returns them as keys" do
37+
klass = Data.define(:x, :y)
38+
d = klass.new(1, 2)
39+
40+
d.deconstruct_keys([0, 1]).should == {0 => 1, 1 => 2}
41+
d.deconstruct_keys([0] ).should == {0 => 1}
42+
d.deconstruct_keys([-1] ).should == {-1 => 2}
43+
end
44+
45+
it "ignores incorrect position numbers" do
46+
klass = Data.define(:x, :y)
47+
d = klass.new(1, 2)
48+
49+
d.deconstruct_keys([0, 3]).should == {0 => 1}
50+
end
51+
52+
it "support mixing attribute names and argument position numbers" do
53+
klass = Data.define(:x, :y)
54+
d = klass.new(1, 2)
55+
56+
d.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1}
57+
end
58+
59+
it "returns an empty hash when there are more keys than attributes" do
60+
klass = Data.define(:x, :y)
61+
d = klass.new(1, 2)
62+
d.deconstruct_keys([:x, :y, :x]).should == {}
63+
end
64+
65+
it "returns at first not existing attribute name" do
66+
klass = Data.define(:x, :y)
67+
d = klass.new(1, 2)
68+
69+
d.deconstruct_keys([:a, :x]).should == {}
70+
d.deconstruct_keys([:x, :a]).should == {x: 1}
71+
end
72+
73+
it "returns at first not existing argument position number" do
74+
klass = Data.define(:x, :y)
75+
d = klass.new(1, 2)
76+
77+
d.deconstruct_keys([3, 0]).should == {}
78+
d.deconstruct_keys([0, 3]).should == {0 => 1}
79+
end
80+
81+
it "accepts nil argument and return all the attributes" do
82+
klass = Data.define(:x, :y)
83+
d = klass.new(1, 2)
84+
85+
d.deconstruct_keys(nil).should == {x: 1, y: 2}
86+
end
87+
88+
it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do
89+
klass = Data.define(:x, :y)
90+
d = klass.new(1, 2)
91+
92+
-> {
93+
d.deconstruct_keys([0, []])
94+
}.should raise_error(TypeError, "no implicit conversion of Array into Integer")
95+
end
96+
97+
it "raise TypeError if passed anything except nil or array" do
98+
klass = Data.define(:x, :y)
99+
d = klass.new(1, 2)
100+
101+
-> { d.deconstruct_keys('x') }.should raise_error(TypeError, /expected Array or nil/)
102+
-> { d.deconstruct_keys(1) }.should raise_error(TypeError, /expected Array or nil/)
103+
-> { d.deconstruct_keys(:x) }.should raise_error(TypeError, /expected Array or nil/)
104+
-> { d.deconstruct_keys({}) }.should raise_error(TypeError, /expected Array or nil/)
105+
end
106+
end
107+
end
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#deconstruct" do
6+
it "returns an array of attribute values" do
7+
DataSpecs::Measure.new(42, "km").deconstruct.should == [42, "km"]
8+
end
9+
end
10+
end

‎spec/ruby/core/data/eql_spec.rb

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#eql?" do
6+
it "returns true if the other is the same object" do
7+
a = DataSpecs::Measure.new(42, "km")
8+
a.should.eql?(a)
9+
end
10+
11+
it "returns true if the other has all the same fields" do
12+
a = DataSpecs::Measure.new(42, "km")
13+
b = DataSpecs::Measure.new(42, "km")
14+
a.should.eql?(b)
15+
end
16+
17+
it "returns false if the other is a different object or has different fields" do
18+
a = DataSpecs::Measure.new(42, "km")
19+
b = DataSpecs::Measure.new(42, "mi")
20+
a.should_not.eql?(b)
21+
end
22+
23+
it "returns false if other is of a different class" do
24+
a = DataSpecs::Measure.new(42, "km")
25+
klass = Data.define(*DataSpecs::Measure.members)
26+
b = klass.new(42, "km")
27+
a.should_not.eql?(b)
28+
end
29+
30+
it "returns false if any corresponding elements are not equal with #eql?" do
31+
a = DataSpecs::Measure.new(42, "km")
32+
b = DataSpecs::Measure.new(42.0, "mi")
33+
a.should_not.eql?(b)
34+
end
35+
36+
context "recursive structure" do
37+
it "returns true the other is the same object" do
38+
a = DataSpecs::Measure.allocate
39+
a.send(:initialize, amount: 42, unit: a)
40+
41+
a.should.eql?(a)
42+
end
43+
44+
it "returns true if the other has all the same fields" do
45+
a = DataSpecs::Measure.allocate
46+
a.send(:initialize, amount: 42, unit: a)
47+
48+
b = DataSpecs::Measure.allocate
49+
b.send(:initialize, amount: 42, unit: b)
50+
51+
a.should.eql?(b)
52+
end
53+
54+
it "returns false if any corresponding elements are not equal with #eql?" do
55+
a = DataSpecs::Measure.allocate
56+
a.send(:initialize, amount: a, unit: "km")
57+
58+
b = DataSpecs::Measure.allocate
59+
b.send(:initialize, amount: b, unit: "mi")
60+
61+
a.should_not.eql?(b)
62+
end
63+
end
64+
end
65+
end
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#==" do
6+
it "returns true if the other is the same object" do
7+
a = DataSpecs::Measure.new(42, "km")
8+
a.should == a
9+
end
10+
11+
it "returns true if the other has all the same fields" do
12+
a = DataSpecs::Measure.new(42, "km")
13+
b = DataSpecs::Measure.new(42, "km")
14+
a.should == b
15+
end
16+
17+
it "returns false if the other is a different object or has different fields" do
18+
a = DataSpecs::Measure.new(42, "km")
19+
b = DataSpecs::Measure.new(42, "mi")
20+
a.should_not == b
21+
end
22+
23+
it "returns false if other is of a different class" do
24+
a = DataSpecs::Measure.new(42, "km")
25+
klass = Data.define(*DataSpecs::Measure.members)
26+
b = klass.new(42, "km")
27+
a.should_not == b
28+
end
29+
30+
it "returns false if any corresponding elements are not equal with #==" do
31+
a = DataSpecs::Measure.new(42, "km")
32+
b = DataSpecs::Measure.new(42.0, "mi")
33+
a.should_not == b
34+
end
35+
36+
context "recursive structure" do
37+
it "returns true the other is the same object" do
38+
a = DataSpecs::Measure.allocate
39+
a.send(:initialize, amount: 42, unit: a)
40+
41+
a.should == a
42+
end
43+
44+
it "returns true if the other has all the same fields" do
45+
a = DataSpecs::Measure.allocate
46+
a.send(:initialize, amount: 42, unit: a)
47+
48+
b = DataSpecs::Measure.allocate
49+
b.send(:initialize, amount: 42, unit: b)
50+
51+
a.should == b
52+
end
53+
54+
it "returns false if any corresponding elements are not equal with #==" do
55+
a = DataSpecs::Measure.allocate
56+
a.send(:initialize, amount: a, unit: "km")
57+
58+
b = DataSpecs::Measure.allocate
59+
b.send(:initialize, amount: b, unit: "mi")
60+
61+
a.should_not == b
62+
end
63+
end
64+
end
65+
end

‎spec/ruby/core/data/fixtures/classes.rb

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ module DataSpecs
22
guard -> { ruby_version_is "3.2" and Data.respond_to?(:define) } do
33
Measure = Data.define(:amount, :unit)
44

5+
class MeasureWithOverriddenName < Measure
6+
def self.name
7+
"A"
8+
end
9+
end
10+
511
class DataSubclass < Data; end
612
end
713
end

‎spec/ruby/core/data/hash_spec.rb

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'fixtures/classes'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#hash" do
6+
it "returns the same integer for objects with the same content" do
7+
a = DataSpecs::Measure.new(42, "km")
8+
b = DataSpecs::Measure.new(42, "km")
9+
a.hash.should == b.hash
10+
a.hash.should be_an_instance_of(Integer)
11+
end
12+
13+
it "returns different hashes for objects with different values" do
14+
a = DataSpecs::Measure.new(42, "km")
15+
b = DataSpecs::Measure.new(42, "ml")
16+
a.hash.should_not == b.hash
17+
18+
a = DataSpecs::Measure.new(42, "km")
19+
b = DataSpecs::Measure.new(13, "km")
20+
a.hash.should_not == b.hash
21+
end
22+
23+
it "returns different hashes for different classes" do
24+
Data.define(:x).new(1).hash.should != Data.define(:x).new(1).hash
25+
end
26+
end
27+
end

‎spec/ruby/core/data/inspect_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'shared/inspect'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#inspect" do
6+
it_behaves_like :data_inspect, :inspect
7+
end
8+
end

‎spec/ruby/core/data/shared/inspect.rb

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require_relative '../fixtures/classes'
2+
3+
describe :data_inspect, shared: true do
4+
it "returns a string representation showing members and values" do
5+
a = DataSpecs::Measure.new(42, "km")
6+
a.send(@method).should == '#<data DataSpecs::Measure amount=42, unit="km">'
7+
end
8+
9+
it "returns a string representation without the class name for anonymous structs" do
10+
Data.define(:a).new("").send(@method).should == '#<data a="">'
11+
end
12+
13+
it "returns a string representation without the class name for structs nested in anonymous classes" do
14+
c = Class.new
15+
c.class_eval <<~DOC
16+
Foo = Data.define(:a)
17+
DOC
18+
19+
c::Foo.new("").send(@method).should == '#<data a="">'
20+
end
21+
22+
it "returns a string representation without the class name for structs nested in anonymous modules" do
23+
m = Module.new
24+
m.class_eval <<~DOC
25+
Foo = Data.define(:a)
26+
DOC
27+
28+
m::Foo.new("").send(@method).should == '#<data a="">'
29+
end
30+
31+
it "does not call #name method" do
32+
struct = DataSpecs::MeasureWithOverriddenName.new(42, "km")
33+
struct.send(@method).should == '#<data DataSpecs::MeasureWithOverriddenName amount=42, unit="km">'
34+
end
35+
36+
it "does not call #name method when struct is anonymous" do
37+
klass = Class.new(DataSpecs::Measure) do
38+
def self.name
39+
"A"
40+
end
41+
end
42+
struct = klass.new(42, "km")
43+
struct.send(@method).should == '#<data amount=42, unit="km">'
44+
end
45+
46+
context "recursive structure" do
47+
it "returns string representation with recursive attribute replaced with ..." do
48+
a = DataSpecs::Measure.allocate
49+
a.send(:initialize, amount: 42, unit: a)
50+
51+
a.send(@method).should == "#<data DataSpecs::Measure amount=42, unit=#<data DataSpecs::Measure:...>>"
52+
end
53+
end
54+
end

‎spec/ruby/core/data/to_s_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require_relative '../../spec_helper'
2+
require_relative 'shared/inspect'
3+
4+
ruby_version_is "3.2" do
5+
describe "Data#to_s" do
6+
it_behaves_like :data_inspect, :to_s
7+
end
8+
end

‎spec/ruby/core/struct/deconstruct_keys_spec.rb

+25
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@
4040
s.deconstruct_keys([0, 1, 2]).should == {0 => 10, 1 => 20, 2 => 30}
4141
s.deconstruct_keys([0, 1] ).should == {0 => 10, 1 => 20}
4242
s.deconstruct_keys([0] ).should == {0 => 10}
43+
s.deconstruct_keys([-1] ).should == {-1 => 30}
44+
end
45+
46+
it "support mixing attribute names and argument position numbers" do
47+
struct = Struct.new(:x, :y)
48+
s = struct.new(1, 2)
49+
50+
s.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1}
4351
end
4452

4553
it "returns an empty hash when there are more keys than attributes" do
@@ -57,13 +65,30 @@
5765
s.deconstruct_keys([:x, :a]).should == {x: 1}
5866
end
5967

68+
it "returns at first not existing argument position number" do
69+
struct = Struct.new(:x, :y)
70+
s = struct.new(1, 2)
71+
72+
s.deconstruct_keys([3, 0]).should == {}
73+
s.deconstruct_keys([0, 3]).should == {0 => 1}
74+
end
75+
6076
it "accepts nil argument and return all the attributes" do
6177
struct = Struct.new(:x, :y)
6278
obj = struct.new(1, 2)
6379

6480
obj.deconstruct_keys(nil).should == {x: 1, y: 2}
6581
end
6682

83+
it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do
84+
struct = Struct.new(:x, :y)
85+
s = struct.new(1, 2)
86+
87+
-> {
88+
s.deconstruct_keys([0, []])
89+
}.should raise_error(TypeError, "no implicit conversion of Array into Integer")
90+
end
91+
6792
it "raise TypeError if passed anything except nil or array" do
6893
struct = Struct.new(:x, :y)
6994
s = struct.new(1, 2)

‎spec/tags/core/data/inspect_tags.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fails:Data#inspect recursive structure returns string representation with recursive attribute replaced with ...

‎spec/tags/core/data/to_s_tags.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fails:Data#to_s recursive structure returns string representation with recursive attribute replaced with ...

‎src/main/ruby/truffleruby/core/data.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,18 @@ def deconstruct_keys(keys)
166166
return {} if members_hash.size < keys.size
167167
168168
h = {}
169+
members = Primitive.class(self)::CLASS_MEMBERS
169170
keys.each do |requested_key|
170171
case requested_key
171172
when Symbol
172173
symbolized_key = requested_key
173174
when String
174175
symbolized_key = requested_key.to_sym
176+
else
177+
symbolized_key = members[requested_key]
175178
end
176179
177-
if members_hash.include?(symbolized_key)
180+
if symbolized_key && members_hash.include?(symbolized_key)
178181
h[requested_key] = Primitive.object_hidden_var_get(self, symbolized_key)
179182
else
180183
return h

‎src/main/ruby/truffleruby/core/struct.rb

+8-8
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def [](var)
223223
when String
224224
var = var.to_sym
225225
else
226-
var = check_index_var(var)
226+
var = check_index_var!(var)
227227
end
228228

229229
unless _attrs.include? var.to_sym
@@ -245,15 +245,15 @@ def []=(var, obj)
245245
raise NameError, "no member '#{var}' in struct"
246246
end
247247
else
248-
var = check_index_var(var)
248+
var = check_index_var!(var)
249249
end
250250

251251
Primitive.check_frozen self
252252
Primitive.object_hidden_var_set(self, var, obj)
253253
end
254254

255-
private def check_index_var(var)
256-
var = Integer(var)
255+
private def check_index_var!(var)
256+
var = Truffle::Type.rb_convert_type(var, Integer, :to_int)
257257
a_len = _attrs.length
258258
if var >= a_len
259259
raise IndexError, "offset #{var} too large for struct(size:#{a_len})"
@@ -344,10 +344,10 @@ def deconstruct_keys(keys)
344344
when String
345345
symbolized_key = requested_key.to_sym
346346
else
347-
symbolized_key = check_index_var(requested_key)
347+
symbolized_key = _attrs[requested_key]
348348
end
349349

350-
if _attrs.include?(symbolized_key)
350+
if symbolized_key && _attrs.include?(symbolized_key)
351351
h[requested_key] = Primitive.object_hidden_var_get(self, symbolized_key)
352352
else
353353
return h
@@ -369,14 +369,14 @@ def values_at(*args)
369369

370370
finish_in_bounds = [finish, _attrs.length - 1].min
371371
start.upto(finish_in_bounds) do |index|
372-
name = check_index_var(index)
372+
name = check_index_var!(index)
373373
out << Primitive.object_hidden_var_get(self, name)
374374
end
375375

376376
(finish_in_bounds + 1).upto(finish) { out << nil }
377377
else
378378
index = Primitive.rb_num2int(elem)
379-
name = check_index_var(index)
379+
name = check_index_var!(index)
380380
out << Primitive.object_hidden_var_get(self, name)
381381
end
382382
end

0 commit comments

Comments
 (0)
Please sign in to comment.