From 9c69c426d1632a05f060d9cc59bf410e77b03c34 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Tue, 6 Jan 2026 16:29:07 -0300 Subject: [PATCH] Define `Markup::Heading` as a struct on rubies older than v4.0.0 (#1549) Closes #1535 Reverts part of #1389 We cannot change the constant from a Struct to a class because that breaks Marshal serialization when RDoc is working with a Ruby version different than the latest one. It can only be changed after RDoc's serialization does not use Marshal. Trying to improve serialization is a larger effort, so let's revert to fix the bug for now and we can take our time to plan that afterwards. Co-authored-by: Stan Lo --- lib/rdoc/markup/heading.rb | 49 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb index e07b00bcc0..36f3603de4 100644 --- a/lib/rdoc/markup/heading.rb +++ b/lib/rdoc/markup/heading.rb @@ -2,6 +2,33 @@ module RDoc class Markup + # IMPORTANT! This weird workaround is required to ensure that RDoc can correctly deserializing Marshal data from + # older rubies. Older rubies have `Heading` as a struct, so if we change it to a class, deserialization fails + if RUBY_VERSION.start_with?("4.") + class Heading < Element + #: String + attr_reader :text + + #: Integer + attr_accessor :level + + #: (Integer, String) -> void + def initialize(level, text) + super() + + @level = level + @text = text + end + + #: (Object) -> bool + def ==(other) + other.is_a?(Heading) && other.level == @level && other.text == @text + end + end + else + Heading = Struct.new(:level, :text) + end + # A heading with a level (1-6) and text # # RDoc syntax: @@ -13,13 +40,8 @@ class Markup # # Heading 1 # ## Heading 2 # ### Heading 3 - class Heading < Element - #: String - attr_reader :text - - #: Integer - attr_accessor :level - + # + class Heading # A singleton RDoc::Markup::ToLabel formatter for headings. #: () -> RDoc::Markup::ToLabel def self.to_label @@ -43,19 +65,6 @@ def to_html.handle_regexp_CROSSREF(target) end end - #: (Integer, String) -> void - def initialize(level, text) - super() - - @level = level - @text = text - end - - #: (Object) -> bool - def ==(other) - other.is_a?(Heading) && other.level == @level && other.text == @text - end - # @override #: (untyped) -> void def accept(visitor)