lutaml-model 0.8.95 → 0.8.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5933a3971f4f39e30541f504636641cd059e7a97751ba67a710203dbfdcc45a
4
- data.tar.gz: c23b70227e80d99427b9d4125a09e33d9751112bc72a5e6abaca73d6a3897c60
3
+ metadata.gz: 2dcefe56e003b241812971d37fc9d76d6b6945cb5a138a9fa15f9ce2aa4fbd7d
4
+ data.tar.gz: 835dfecf768169c9fbc6ab772c2e11dbe593f3f5e9deb7016b09c8088706117c
5
5
  SHA512:
6
- metadata.gz: 473b9970246c24172d6040a9c5062fad06d0fea413a5e04956ac98c0990324f2543668436c9259ad2a30b8647555d1e00102f6346afd3018814d68c2841ca8e5
7
- data.tar.gz: f48b568bdc715a7649a262ca5757474f56ecc52d775121be99f18f067570d48d5401ff29cd7141ff768e9ea0a3e0aef5996d32129b8907c386cfd3c72e9f5370
6
+ metadata.gz: 7efe4b0bc24f20b9d48e37c33b5e23cf23af7397900a7d788912275a4b396318d916c721b21bc6373de53e99f3f4f0dc29e459c94bba2e2baaf8c4da67232c14
7
+ data.tar.gz: 59d5412393469b1904c4c6e29bc0ff58b0c6333b5e3710e531d9e7a8ae465e7762993288f04e6147a12a204a8fb581059a24491d9dc90d7f309e78b1bb39b97a
@@ -0,0 +1,69 @@
1
+ = Grammar-backed string formats
2
+
3
+ A serialization format whose input is a *grammar*, not a data structure:
4
+ identifier strings, query languages, DSLs — text that is parsed, not
5
+ decoded. lutaml-model supports registering these as first-class formats
6
+ through the same `FormatRegistry` extension point as xml/json/yaml; the
7
+ parse side is implemented by a grammar engine (the reference
8
+ implementation is https://github.com/parsanol/parsanol-ruby[Parsanol]'s
9
+ `Parsanol::PARG::Lutaml.register`, which consumes a compiled PARG
10
+ artifact plus its bindings).
11
+
12
+ == The contract
13
+
14
+ A grammar-backed format needs three things:
15
+
16
+ * a *parse* — text → attribute hash (the grammar engine's job)
17
+ * *bindings* — capture names → model attributes (the artifact's
18
+ bindings section)
19
+ * a *model class* — a regular `Serializable` with attributes; no
20
+ hand-written `from_h`/`to_h` anywhere
21
+
22
+ == Reference implementation (Parsanol)
23
+
24
+ [source,ruby]
25
+ ----
26
+ require "parsanol/parg/lutaml"
27
+
28
+ Parsanol::PARG::Lutaml.register(
29
+ IsoIdentifier,
30
+ format_name: :pubid_iso,
31
+ artifact: "artifacts/iso.json", # compiled grammar + bindings + tests
32
+ entry: "identifier",
33
+ )
34
+
35
+ IsoIdentifier.from_pubid_iso("ISO/IEC 12345-1:2020")
36
+ # => #<IsoIdentifier number: "12345-1", year: 2020, ...>
37
+ ----
38
+
39
+ What `register` does:
40
+
41
+ . loads the checksummed artifact (parse failures raise the engine's
42
+ error type — register it via `error_types:` so users can rescue it);
43
+ . registers the format through `FormatRegistry.register` with an
44
+ adapter whose `parse` is artifact-parse-then-bind;
45
+ . defines `from_<format>` on the model class.
46
+
47
+ The artifact is the parser of record: it carries the grammar, the
48
+ bindings, and the conformance tests (the grammar's own test block is
49
+ baked in and can be re-run by consumers), and its checksum pins the
50
+ pinned version of the language.
51
+
52
+ == Writing the grammar side
53
+
54
+ Grammars are authored in PARG (`.parg` — ABNF-flavoured surface, PEG
55
+ semantics, one rule per line) and compiled with the `parsanol` CLI:
56
+
57
+ ----
58
+ parsanol compile grammars/iso.parg --tables tables
59
+ ----
60
+
61
+ The compile-time lint rejects left recursion and shadowed
62
+ alternatives; the embedded test block runs at compile time, so a
63
+ grammar that stops accepting its documented inputs fails the build
64
+ rather than the consumer.
65
+
66
+ == See also
67
+
68
+ * parsanol-ruby `docs/pg-language-guide.md` — the PARG language
69
+ * `Parsanol::PARG::Lutaml.register` — the bridge (reference implementation)
@@ -26,6 +26,7 @@ Task-oriented guides for accomplishing specific goals with Lutaml::Model.
26
26
  == Advanced mapping
27
27
 
28
28
  * link:../advanced-mapping[Advanced Attribute Mapping] - Same-name dispatch on attribute values (`when_attribute`), grouped declarations, custom methods, delegation
29
+ * link:../grammar-backed-formats[Grammar-Backed String Formats] - Register a grammar engine as a serialization format (identifier strings, DSLs)
29
30
 
30
31
  == Performance
31
32
 
@@ -47,6 +48,7 @@ See link:../references/rdf-namespaces[RDF Namespaces] for namespace classes,
47
48
  * link:../value-transformations[Value Transformations] - Transform values during serialization
48
49
  * link:../missing-values-handling[Missing Values Handling] - Handle nil/empty/omitted
49
50
  * link:../advanced-mapping[Advanced Mapping] - Custom methods and delegation
51
+ * link:../grammar-backed-formats[Grammar-Backed String Formats] - Grammar engines as serialization formats
50
52
  * link:../schema-generation[Schema Generation] - Generate schemas from models
51
53
  * link:../creating-xsd[Creating XSD Schemas] - Complete reference for XSD generation from LutaML models
52
54
  * link:../schema-import[Schema Import] - Import schemas to create models
@@ -8,6 +8,24 @@ module Lutaml
8
8
  # Extracted from serialize.rb to improve code organization.
9
9
  # Provides methods for defining and validating model attributes.
10
10
  module AttributeDefinition
11
+ # Whether the per-class accessors and the state-defaults seed are
12
+ # compiled from Ruby source with a String class_eval (the fast
13
+ # path). Opal has no runtime compiler unless opal-parser is
14
+ # bundled, so there every such method is defined from a block
15
+ # with define_method instead.
16
+ # Core dispatch for the block-form methods, bound per call so an
17
+ # attribute or method a model defines with one of these names
18
+ # (send, __send__, instance_variable_get, const_get, ...) is never invoked;
19
+ # the compiled form reads ivars and calls methods directly.
20
+ IVAR_GET = ::Kernel.instance_method(:instance_variable_get)
21
+ IVAR_SET = ::Kernel.instance_method(:instance_variable_set)
22
+ SEND = ::BasicObject.instance_method(:__send__)
23
+ CONST_GET = ::Module.instance_method(:const_get)
24
+
25
+ def self.source_compilation?
26
+ !Lutaml::Model.opal?
27
+ end
28
+
11
29
  # Define attribute methods on the model class
12
30
  #
13
31
  # @param attr [Attribute] The attribute to define methods for
@@ -44,6 +62,26 @@ module Lutaml
44
62
  return
45
63
  end
46
64
 
65
+ unless AttributeDefinition.source_compilation?
66
+ seeds = attrs.map do |name, attr|
67
+ sentinel = if attr.collection?
68
+ Lutaml::Model::Serialize::LAZY_EMPTY_COLLECTION
69
+ else
70
+ Lutaml::Model::UninitializedClass.instance
71
+ end
72
+ [:"@#{name}", sentinel]
73
+ end
74
+ # Raw readers for every attribute the register sees, as the
75
+ # compiled form below does (#933).
76
+ attrs.each_key { |name| define_raw_state_reader(name) }
77
+ define_method(method_name) do
78
+ # Returns the last seeded value, as the compiled form's final
79
+ # assignment does.
80
+ seeds.reduce(nil) { |_, (ivar, sentinel)| IVAR_SET.bind_call(self, ivar, sentinel) }
81
+ end
82
+ return
83
+ end
84
+
47
85
  lines = attrs.map do |name, attr|
48
86
  if attr.collection?
49
87
  "@#{name} = Lutaml::Model::Serialize::LAZY_EMPTY_COLLECTION"
@@ -82,6 +120,14 @@ module Lutaml
82
120
  RUBY
83
121
  end
84
122
 
123
+ # Block-form raw-state reader (no String eval), defined on self.
124
+ def define_raw_state_reader(name)
125
+ ivar = :"@#{name}"
126
+ define_method(:"__lutaml_raw_#{name}") do
127
+ IVAR_GET.bind_call(self, ivar)
128
+ end
129
+ end
130
+
85
131
  # Historical getter shape for punctuation-named attributes and
86
132
  # any name the compiled form cannot express.
87
133
  def define_reflective_attribute_methods(name, attr)
@@ -145,11 +191,16 @@ module Lutaml
145
191
  # the state-defaults compilation) so late declarations get it
146
192
  # too -- XMI's extension loader declares attributes after
147
193
  # first parse.
148
- if name.to_s.match?(PLAIN_NAME)
149
- # class_eval on self: a bare `model` receiver resolves
150
- # through the host class's own namespace (omml's Base
151
- # answers a Store model) and defined the reader on the
152
- # wrong object (#933).
194
+ # Without a source compiler, compile_state_defaults! defines
195
+ # its raw readers from blocks; define one for every name here
196
+ # too (define_method takes any name, not only plain ones).
197
+ # Both forms define on self: a bare `model` receiver resolves
198
+ # through the host class's own namespace (omml's Base answers
199
+ # a Store model) and defined the reader on the wrong object
200
+ # (#933).
201
+ if !AttributeDefinition.source_compilation?
202
+ define_raw_state_reader(name)
203
+ elsif name.to_s.match?(PLAIN_NAME)
153
204
  class_eval(<<~RUBY, __FILE__, __LINE__ + 1) # rubocop:disable Style/DocumentDynamicEvalDefinition
154
205
  def __lutaml_raw_#{name}
155
206
  @#{name}
@@ -263,7 +314,9 @@ module Lutaml
263
314
  # Array on EVERY read — the largest per-call allocation source
264
315
  # on instance-heavy parses (TODO.max-perf/06). The optional-arg
265
316
  # form allocates nothing, and the read is a direct @ivar.
266
- if attr.collection?
317
+ if !AttributeDefinition.source_compilation?
318
+ define_block_attribute_getter(name, attr)
319
+ elsif attr.collection?
267
320
  # class_eval interpolates, e.g.:
268
321
  # def items(arg = Lutaml::Model::Serialize::NO_ARG)
269
322
  # if arg.equal?(Lutaml::Model::Serialize::NO_ARG)
@@ -319,7 +372,9 @@ module Lutaml
319
372
  end
320
373
  define_method(attr_reader_method) { attr } unless reader_defined
321
374
 
322
- if attr.collection?
375
+ if !AttributeDefinition.source_compilation?
376
+ define_block_attribute_setters(name, attr)
377
+ elsif attr.collection?
323
378
  # class_eval interpolates, e.g.:
324
379
  # def items=(value)
325
380
  # value_set_for(:items)
@@ -379,6 +434,77 @@ module Lutaml
379
434
  end
380
435
  end
381
436
 
437
+ # Block form of the compiled getter in define_regular_attribute_methods,
438
+ # for runtimes without a source compiler (see source_compilation?).
439
+ def define_block_attribute_getter(name, attr)
440
+ name = name.to_sym # the compiled form passes :name literals
441
+ ivar = :"@#{name}"
442
+ if attr.collection?
443
+ # The compiled getter resolves Array in the model class's scope.
444
+ scope = self
445
+ define_method(name) do |arg = Lutaml::Model::Serialize::NO_ARG|
446
+ if arg.equal?(Lutaml::Model::Serialize::NO_ARG)
447
+ materialize_lazy_collection(name)
448
+ else
449
+ current = IVAR_GET.bind_call(self, ivar) || []
450
+ new_value = current.is_a?(CONST_GET.bind_call(scope, :Array)) ? current + [arg] : arg
451
+ IVAR_SET.bind_call(self, ivar, new_value)
452
+ record_mutation(name, arg)
453
+ arg
454
+ end
455
+ end
456
+ else
457
+ define_method(name) do |arg = Lutaml::Model::Serialize::NO_ARG|
458
+ if arg.equal?(Lutaml::Model::Serialize::NO_ARG)
459
+ IVAR_GET.bind_call(self, ivar)
460
+ else
461
+ public_send(:"#{name}=", arg)
462
+ arg
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+ # Block form of the compiled setters in define_regular_attribute_methods.
469
+ # The Attribute is read through the same hidden accessor the
470
+ # compiled form calls, not captured: a setter redefined over an
471
+ # enum shorthand keeps casting with the accessor's Attribute.
472
+ def define_block_attribute_setters(name, attr)
473
+ name = name.to_sym # the compiled form passes :name literals
474
+ ivar = :"@#{name}"
475
+ handle = :"__attribute_definition_#{name}"
476
+ if attr.collection?
477
+ assign = lambda do |model, value|
478
+ current = IVAR_GET.bind_call(model, ivar)
479
+ unless current.equal?(Lutaml::Model::Serialize::LAZY_EMPTY_COLLECTION) &&
480
+ (value.nil? || Lutaml::Model::Utils.uninitialized?(value))
481
+ IVAR_SET.bind_call(model, ivar, value)
482
+ end
483
+ SEND.bind_call(model, :record_mutation_collection, name, value)
484
+ end
485
+ define_method(:"#{name}=") do |value|
486
+ value_set_for(name)
487
+ assign.call(self, SEND.bind_call(self, handle).cast_value(value, lutaml_register))
488
+ end
489
+ define_method(:"__assign_parsed_#{name}=") do |value|
490
+ value_set_for(name)
491
+ assign.call(self, value)
492
+ end
493
+ else
494
+ define_method(:"#{name}=") do |value|
495
+ value_set_for(name)
496
+ value = SEND.bind_call(self, handle).cast_value(value, lutaml_register)
497
+ IVAR_SET.bind_call(self, ivar, value)
498
+ record_mutation(name, value)
499
+ end
500
+ define_method(:"__assign_parsed_#{name}=") do |value|
501
+ value_set_for(name)
502
+ IVAR_SET.bind_call(self, ivar, value)
503
+ record_mutation(name, value)
504
+ end
505
+ end
506
+ end
507
+
382
508
  # Define an attribute for the model
383
509
  #
384
510
  # @param name [Symbol] The attribute name
@@ -95,7 +95,12 @@ module Lutaml
95
95
  options.key?(:mappings) || options.key?(:adapter) ||
96
96
  options.key?(:_adapter_override) ||
97
97
  options.key?(:indent) || options.key?(:xml_declaration) ||
98
- options.key?(:declaration) || options.key?(:doctype)
98
+ options.key?(:declaration) || options.key?(:doctype) ||
99
+ # prefix requests ride the interpretive writer's use_prefix
100
+ # option (planner Tier-1) — the plan emission has no
101
+ # option-aware spelling. The instance-level option prep
102
+ # rewrites :prefix to :use_prefix before this gate.
103
+ options.key?(:prefix) || options.key?(:use_prefix)
99
104
 
100
105
  adapter_name = Lutaml::Model::Config.adapter_for(:xml)
101
106
  adapter_name &&= adapter_name.name
@@ -103,9 +108,14 @@ module Lutaml
103
108
 
104
109
  register = Lutaml::Model::Config.default_register
105
110
  plan = Lutaml::Xml::Leptris::PlanCompiler.compile(self, register)
106
- return nil unless plan && !plan[:namespaced] &&
111
+ return nil unless plan &&
107
112
  Lutaml::Xml::Leptris::PlanSerializer.serializable?(plan)
108
113
 
114
+ # Namespaced plans ride the plan serializer too: the DOM
115
+ # emission spells prefixed names and hoists declarations
116
+ # (the #847 xmlns drop is fixed in PlanSerializer); any
117
+ # unspellable shape answers nil there and falls back
118
+ # interpretively.
109
119
  Lutaml::Xml::Leptris::PlanSerializer.call(instance, plan)
110
120
  end
111
121
 
@@ -128,6 +138,7 @@ module Lutaml
128
138
  register = Lutaml::Model::Config.default_register
129
139
  plan = Lutaml::Xml::Leptris::PlanCompiler.compile(self, register)
130
140
  return nil unless plan
141
+ return nil unless Lutaml::Xml::Leptris::PlanCompiler.engine_walk_safe?(plan)
131
142
 
132
143
  root = ::Leptris::XML.parse(data.to_s).root
133
144
  return nil if root.nil?
@@ -172,12 +172,30 @@ module Lutaml
172
172
  #
173
173
  # @param register [Symbol, nil] The register context
174
174
  def ensure_imports!(register = nil)
175
- ensure_model_imports!(register)
176
- ensure_choice_imports!(register)
177
- ensure_restrict_attributes!(register)
178
- # Hook for format-specific mapping import resolution.
179
- # XML overrides this to call mappings[:xml]&.ensure_mappings_imported!(register)
180
- ensure_format_mapping_imports!(register)
175
+ # Bounded re-entry for the resolution chain. Each leg's
176
+ # record_import_attempt tail calls attributes(), which calls
177
+ # back into ensure_imports!; nested passes converge imports
178
+ # whose targets only become resolvable after an earlier leg
179
+ # (so they must not be dropped), but with a permanently
180
+ # pending import the tail re-entered unboundedly and crashed
181
+ # with SystemStackError (#938). Two levels allow one nested
182
+ # convergence pass; deeper re-entry is suppressed per
183
+ # register and the counter unwinds on error.
184
+ register_id = extract_register_id(register)
185
+ (@ensure_imports_depth ||= {})[register_id] ||= 0
186
+ return if @ensure_imports_depth[register_id] >= 2
187
+
188
+ @ensure_imports_depth[register_id] += 1
189
+ begin
190
+ ensure_model_imports!(register)
191
+ ensure_choice_imports!(register)
192
+ ensure_restrict_attributes!(register)
193
+ # Hook for format-specific mapping import resolution.
194
+ # XML overrides this to call mappings[:xml]&.ensure_mappings_imported!(register)
195
+ ensure_format_mapping_imports!(register)
196
+ ensure
197
+ @ensure_imports_depth[register_id] -= 1
198
+ end
181
199
  end
182
200
 
183
201
  # Hook for format-specific mapping import resolution.
@@ -357,7 +375,7 @@ module Lutaml
357
375
  # chain's frames per level pushed that recursion past the
358
376
  # stack limit — models without pending deferred imports skip
359
377
  # it entirely.
360
- ensure_imports!(register_id) if finalized? && deferred_imports_pending?
378
+ ensure_imports!(register_id) if finalized? && deferred_imports_pending?(register_id)
361
379
  instance = allocate
362
380
  instance.finalize_deserialization(register_id)
363
381
  instance
@@ -162,11 +162,39 @@ module Lutaml
162
162
  # @param register_id [Symbol, nil] The register context
163
163
  # Whether deferred (symbol-form) imports are still pending —
164
164
  # gates the allocation-time ensure chain (#933).
165
- def deferred_imports_pending?
166
- importable_models.any? { |_method, models| models.any? } ||
167
- importable_choices.any? do |_choice, imports|
168
- imports.any? { |_method, models| models.any? }
165
+ # The attribute-set fingerprint at the moment an import
166
+ # resolution attempt ran — deferred_imports_pending? compares
167
+ # against it to skip re-resolution on stable sets.
168
+ def record_import_attempt(register_id)
169
+ # Raw stores only — the lazy attributes() re-enters
170
+ # ensure_imports! and, when @models_imported was just set to
171
+ # false (unresolved imports), cycles through this very
172
+ # method forever (the uniword register_in SystemStackError).
173
+ register_attrs = (@register_records ||= {})[register_id]
174
+ (@import_attempt_fingerprints ||= {})[register_id] =
175
+ @attributes.size + @choice_attributes.to_a.size +
176
+ (register_attrs ? register_attrs[:attributes].to_a.size : 0)
177
+ end
178
+
179
+ def deferred_imports_pending?(register_id = nil)
180
+ register_id ||= Lutaml::Model::Config.default_register
181
+ has_importables = importable_models.any? { |_m, models| models.any? } ||
182
+ importable_choices.any? do |_c, imports|
183
+ imports.any? { |_m, models| models.any? }
169
184
  end || restrict_attributes.any?
185
+ return false unless has_importables
186
+
187
+ # Unresolvable imports stay pending forever; re-running the
188
+ # ensure chain (and its state-defaults invalidation) on every
189
+ # allocation thrashed import-heavy models quadratically.
190
+ # Resolution re-arms only when the attribute set CHANGED
191
+ # since the last attempt — a late declaration after a failed
192
+ # resolution re-attempts; a stable set skips.
193
+ attempted = (@import_attempt_fingerprints ||= {})[register_id]
194
+ return true if attempted.nil?
195
+
196
+ current = attributes(register_id).size + @choice_attributes.to_a.size
197
+ current != attempted
170
198
  end
171
199
 
172
200
  def ensure_model_imports!(register_id = nil)
@@ -200,6 +228,7 @@ module Lutaml
200
228
  end
201
229
 
202
230
  @models_imported[register_id] = all_resolved
231
+ record_import_attempt(register_id)
203
232
  end
204
233
 
205
234
  # Ensure all choice imports are resolved for a specific register
@@ -239,6 +268,7 @@ module Lutaml
239
268
  end
240
269
 
241
270
  @choices_imported[register_id] = true if all_resolved
271
+ record_import_attempt(register_id)
242
272
  # Choice imports can add attributes without passing through
243
273
  # define_attribute_methods — recompile state defaults so the
244
274
  # seeding and raw readers cover them (#933). Only when an
@@ -268,6 +298,11 @@ module Lutaml
268
298
  # Uses TracePoint to detect when a class definition is complete,
269
299
  # allowing deferred import resolution.
270
300
  def setup_trace_point
301
+ # Opal has no TracePoint. Symbol imports still resolve lazily
302
+ # on first use (ensure_imports!), so there the class is marked
303
+ # finalized at once instead of at the end of its body.
304
+ return @finalized = true if Lutaml::Model.opal?
305
+
271
306
  @trace ||= TracePoint.new(:end) do |_tp|
272
307
  if include?(Lutaml::Model::Serialize)
273
308
  @finalized = true
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.95"
5
+ VERSION = "0.8.97"
6
6
  end
7
7
  end
data/lib/lutaml/model.rb CHANGED
@@ -294,3 +294,10 @@ require "#{__dir__}/xml/format"
294
294
  # Builder must be prepended AFTER XML so its initialize runs first
295
295
  # (Builder -> XML InstanceMethods -> Serialize)
296
296
  Lutaml::Model::Serialize.prepend(Lutaml::Model::Serialize::Builder)
297
+
298
+ # Opal does not propagate a module prepended into an already-included
299
+ # module to classes that included it earlier (see lib/lutaml/xml/format.rb).
300
+ # Serializable included Serialize at boot, so Builder goes onto it directly.
301
+ if Lutaml::Model.opal?
302
+ Lutaml::Model::Serializable.prepend(Lutaml::Model::Serialize::Builder)
303
+ end
@@ -243,9 +243,11 @@ Lutaml::Model::Serialize.prepend(
243
243
  #
244
244
  # Anonymous model classes (Class.new { include Serialize }) extend
245
245
  # Serialize::ClassMethods rather than inheriting from Serializable, so the
246
- # ModelImportExt override (root?) must land on ClassMethods too. The other
247
- # two (FormatConversion, InstanceMethods) are already prepended unconditionally
248
- # above, so Opal's no-double-prepend rule means we don't repeat them here.
246
+ # ModelImportExt override (root?) must land on ClassMethods too.
247
+ #
248
+ # On Opal (and TruffleRuby, below) the ClassMethods.prepend(FormatConversion)
249
+ # above does not reach Serializable's singleton chain, which extended
250
+ # ClassMethods before it ran, so it is prepended onto that singleton directly.
249
251
  #
250
252
  # TruffleRuby (34.0.1) has the same propagation gap, on both the include
251
253
  # side and the extend/singleton side — a module prepended into an
@@ -273,13 +275,9 @@ if Lutaml::Model.opal? || RUBY_ENGINE == "truffleruby"
273
275
  Lutaml::Xml::Serialization::ModelImportExt,
274
276
  )
275
277
 
276
- # TruffleRuby only: Opal's no-double-prepend rule makes the unconditional
277
- # ClassMethods.prepend(FormatConversion) above sufficient there.
278
- if RUBY_ENGINE == "truffleruby"
279
- Lutaml::Model::Serializable.singleton_class.prepend(
280
- Lutaml::Xml::Serialization::FormatConversion,
281
- )
282
- end
278
+ Lutaml::Model::Serializable.singleton_class.prepend(
279
+ Lutaml::Xml::Serialization::FormatConversion,
280
+ )
283
281
  end
284
282
 
285
283
  # Register XML-specific attribute override warning names