Skip to content

Commit 7e09a3b

Browse files
committed
Eliminate support for anon Data or Data with ivars
This commit removes support for the following features: * Dumping an anonymous Data. This is unsupported for the same reasons as dumping anonymous modules or classes: there's no way to reference the proper Data class, and defining a new anonymous Data for every such element would not round-trip properly. * Dumping a Data with instance variables. Instance variables on a Data can only be set during the initialize chain when first constructed, and such a chain is custom for each such Data class. As there is no way to modify a Data after constructed and no way to accurately invoke the custom initialize method, all Data with instance variables are rejected. Data is also no longer registered while loading, since it's not possible to have a self-referential Data instance without a custom initialize that sets an instance variable to self. The combination of these removed features simplifies Data dumping and loading and eliminates all allocate+initialize hacks.
1 parent 2547051 commit 7e09a3b

File tree

7 files changed

+23
-140
lines changed

7 files changed

+23
-140
lines changed

lib/psych/visitors/to_ruby.rb

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -197,30 +197,11 @@ def visit_Psych_Nodes_Mapping o
197197
s
198198
end
199199

200-
when /^!ruby\/data(-with-ivars)?(?::(.*))?$/
201-
data = register(o, resolve_class($2).allocate) if $2
202-
members = {}
203-
204-
if $1 # data-with-ivars
205-
ivars = {}
206-
o.children.each_slice(2) do |type, vars|
207-
case accept(type)
208-
when 'members'
209-
revive_data_members(members, vars)
210-
data ||= allocate_anon_data(o, members)
211-
when 'ivars'
212-
revive_hash(ivars, vars)
213-
end
214-
end
215-
ivars.each do |ivar, v|
216-
data.instance_variable_set ivar, v
217-
end
218-
else
219-
revive_data_members(members, o)
220-
end
221-
data ||= allocate_anon_data(o, members)
222-
data.__send__ :initialize, **members
223-
data
200+
when /^!ruby\/data?(?::(.*))?$/
201+
data_class = resolve_class($1) if $1
202+
members = revive_data_members(o)
203+
data = (data_class || Data.define(*members.keys)).new(**members)
204+
register(o, data) # replace temp with actual
224205

225206
when /^!ruby\/object:?(.*)?$/
226207
name = $1 || 'Object'
@@ -370,7 +351,8 @@ def allocate_anon_data node, members
370351
register(node, klass.allocate)
371352
end
372353

373-
def revive_data_members hash, o
354+
def revive_data_members o
355+
hash = {}
374356
o.children.each_slice(2) do |k,v|
375357
name = accept(k)
376358
value = accept(v)

lib/psych/visitors/yaml_tree.rb

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -163,41 +163,16 @@ def visit_Object o
163163
alias :visit_Delegator :visit_Object
164164

165165
def visit_Data o
166-
ivars = o.instance_variables
167-
if ivars.empty?
168-
tag = ['!ruby/data', o.class.name].compact.join(':')
169-
register o, @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
170-
o.members.each do |member|
171-
@emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
172-
accept o.send member
173-
end
174-
@emitter.end_mapping
166+
raise TypeError, "can't dump Data with ivars: #{o}" unless o.instance_variables.empty?
167+
raise TypeError, "can't dump anonymous Data: #{o}" unless o.class.name
175168

176-
else
177-
tag = ['!ruby/data-with-ivars', o.class.name].compact.join(':')
178-
node = @emitter.start_mapping(nil, tag, false, Psych::Nodes::Mapping::BLOCK)
179-
register(o, node)
180-
181-
# Dump the members
182-
accept 'members'
183-
@emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
184-
o.members.each do |member|
185-
@emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
186-
accept o.send member
187-
end
188-
@emitter.end_mapping
189-
190-
# Dump the ivars
191-
accept 'ivars'
192-
@emitter.start_mapping nil, nil, true, Nodes::Mapping::BLOCK
193-
ivars.each do |ivar|
194-
accept ivar.to_s
195-
accept o.instance_variable_get ivar
196-
end
197-
@emitter.end_mapping
198-
199-
@emitter.end_mapping
169+
tag = ['!ruby/data', o.class.name].compact.join(':')
170+
register o, @emitter.start_mapping(nil, tag, false, Nodes::Mapping::BLOCK)
171+
o.members.each do |member|
172+
@emitter.scalar member.to_s, nil, nil, true, false, Nodes::Scalar::ANY
173+
accept o.send member
200174
end
175+
@emitter.end_mapping
201176
end unless RUBY_VERSION < "3.2"
202177

203178
def visit_Struct o

test/psych/test_data.rb

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,35 @@
11
# frozen_string_literal: true
22
require_relative 'helper'
33

4-
class PsychDataWithIvar < Data.define(:foo)
5-
attr_reader :bar
6-
def initialize(**)
7-
@bar = 'hello'
8-
super
9-
end
10-
end unless RUBY_VERSION < "3.2"
4+
PsychData = Data.define(:foo) unless RUBY_VERSION < "3.2"
115

126
module Psych
137
class TestData < TestCase
14-
class SelfReferentialData < Data.define(:foo)
15-
attr_accessor :ref
16-
def initialize(foo:)
17-
@ref = self
18-
super
19-
end
20-
end unless RUBY_VERSION < "3.2"
21-
228
def setup
239
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
2410
end
2511

2612
# TODO: move to another test?
2713
def test_dump_data
28-
assert_equal <<~eoyml, Psych.dump(PsychDataWithIvar["bar"])
29-
--- !ruby/data-with-ivars:PsychDataWithIvar
30-
members:
31-
foo: bar
32-
ivars:
33-
"@bar": hello
14+
assert_equal <<~eoyml, Psych.dump(PsychData["bar"])
15+
--- !ruby/data:PsychData
16+
foo: bar
3417
eoyml
3518
end
3619

37-
def test_self_referential_data
38-
circular = SelfReferentialData.new("foo")
39-
40-
loaded = Psych.unsafe_load(Psych.dump(circular))
41-
assert_instance_of(SelfReferentialData, loaded.ref)
42-
43-
assert_equal(circular, loaded)
44-
assert_same(loaded, loaded.ref)
45-
end
46-
4720
def test_roundtrip
48-
thing = PsychDataWithIvar.new("bar")
21+
thing = PsychData.new("bar")
4922
data = Psych.unsafe_load(Psych.dump(thing))
5023

51-
assert_equal "hello", data.bar
5224
assert_equal "bar", data.foo
5325
end
5426

5527
def test_load
5628
obj = Psych.unsafe_load(<<~eoyml)
57-
--- !ruby/data-with-ivars:PsychDataWithIvar
58-
members:
59-
foo: bar
60-
ivars:
61-
"@bar": hello
29+
--- !ruby/data:PsychData
30+
foo: bar
6231
eoyml
6332

64-
assert_equal "hello", obj.bar
6533
assert_equal "bar", obj.foo
6634
end
6735
end

test/psych/test_object_references.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ def test_struct_has_references
3131
assert_reference_trip Struct.new(:foo).new(1)
3232
end
3333

34-
def test_data_has_references
35-
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
36-
assert_reference_trip Data.define(:foo).new(1)
37-
end
38-
3934
def assert_reference_trip obj
4035
yml = Psych.dump([obj, obj])
4136
assert_match(/\*-?\d+/, yml)

test/psych/test_safe_load.rb

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -124,28 +124,6 @@ def test_data_depends_on_sym
124124
end
125125
end
126126

127-
def test_anon_data
128-
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
129-
assert Psych.safe_load(<<-eoyml, permitted_classes: [Data, Symbol])
130-
--- !ruby/data
131-
foo: bar
132-
eoyml
133-
134-
assert_raise(Psych::DisallowedClass) do
135-
Psych.safe_load(<<-eoyml, permitted_classes: [Data])
136-
--- !ruby/data
137-
foo: bar
138-
eoyml
139-
end
140-
141-
assert_raise(Psych::DisallowedClass) do
142-
Psych.safe_load(<<-eoyml, permitted_classes: [Symbol])
143-
--- !ruby/data
144-
foo: bar
145-
eoyml
146-
end
147-
end
148-
149127
def test_safe_load_default_fallback
150128
assert_nil Psych.safe_load("")
151129
end

test/psych/test_serialize_subclasses.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,11 @@ def test_struct_subclass
3838

3939
class DataSubclass < Data.define(:foo)
4040
def initialize(foo:)
41-
@bar = "hello #{foo}"
4241
super(foo: foo)
4342
end
4443

4544
def == other
46-
super(other) && @bar == other.instance_eval{ @bar }
45+
super(other)
4746
end
4847
end unless RUBY_VERSION < "3.2"
4948

test/psych/visitors/test_yaml_tree.rb

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,6 @@ def test_data
8080
assert_cycle D.new('bar')
8181
end
8282

83-
def test_data_anon
84-
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
85-
d = Data.define(:foo).new('bar')
86-
obj = Psych.unsafe_load(Psych.dump(d))
87-
assert_equal d.foo, obj.foo
88-
end
89-
90-
def test_data_override_method
91-
omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2"
92-
d = Data.define(:method).new('override')
93-
obj = Psych.unsafe_load(Psych.dump(d))
94-
assert_equal d.method, obj.method
95-
end
96-
9783
def test_exception
9884
ex = Exception.new 'foo'
9985
loaded = Psych.unsafe_load(Psych.dump(ex))

0 commit comments

Comments
 (0)