Repository: ruby/prism
Pull request #1764 (2023-11-03 13:42:33)

Split comment
We were previously holding a type field on Comment to tell what kind of comment it was. Instead, let's just use actual classes for this. This makes it nicer to work with when you call `Prism.parse_comments` because you can do `Prism.parse_comments(source).grep(Prism::InlineComment)`


Patch:
diff --git a/ext/prism/extension.c b/ext/prism/extension.c
index 839a5cda..1f6e199d 100644
--- a/ext/prism/extension.c
+++ b/ext/prism/extension.c
@@ -10,6 +10,9 @@ VALUE rb_cPrismToken;
 VALUE rb_cPrismLocation;
 
 VALUE rb_cPrismComment;
+VALUE rb_cPrismInlineComment;
+VALUE rb_cPrismEmbDocComment;
+VALUE rb_cPrismDATAComment;
 VALUE rb_cPrismMagicComment;
 VALUE rb_cPrismParseError;
 VALUE rb_cPrismParseWarning;
@@ -320,21 +323,18 @@ parser_comments(pm_parser_t *parser, VALUE source) {
         VALUE type;
         switch (comment->type) {
             case PM_COMMENT_INLINE:
-                type = ID2SYM(rb_intern("inline"));
+                type = rb_cPrismInlineComment;
                 break;
             case PM_COMMENT_EMBDOC:
-                type = ID2SYM(rb_intern("embdoc"));
+                type = rb_cPrismEmbDocComment;
                 break;
             case PM_COMMENT___END__:
-                type = ID2SYM(rb_intern("__END__"));
-                break;
-            default:
-                type = ID2SYM(rb_intern("inline"));
+                type = rb_cPrismDATAComment;
                 break;
         }
 
-        VALUE comment_argv[] = { type, rb_class_new_instance(3, location_argv, rb_cPrismLocation) };
-        rb_ary_push(comments, rb_class_new_instance(2, comment_argv, rb_cPrismComment));
+        VALUE comment_argv[] = { rb_class_new_instance(3, location_argv, rb_cPrismLocation) };
+        rb_ary_push(comments, rb_class_new_instance(1, comment_argv, type));
     }
 
     return comments;
@@ -933,6 +933,9 @@ Init_prism(void) {
     rb_cPrismToken = rb_define_class_under(rb_cPrism, "Token", rb_cObject);
     rb_cPrismLocation = rb_define_class_under(rb_cPrism, "Location", rb_cObject);
     rb_cPrismComment = rb_define_class_under(rb_cPrism, "Comment", rb_cObject);
+    rb_cPrismInlineComment = rb_define_class_under(rb_cPrism, "InlineComment", rb_cPrismComment);
+    rb_cPrismEmbDocComment = rb_define_class_under(rb_cPrism, "EmbDocComment", rb_cPrismComment);
+    rb_cPrismDATAComment = rb_define_class_under(rb_cPrism, "DATAComment", rb_cPrismComment);
     rb_cPrismMagicComment = rb_define_class_under(rb_cPrism, "MagicComment", rb_cObject);
     rb_cPrismParseError = rb_define_class_under(rb_cPrism, "ParseError", rb_cObject);
     rb_cPrismParseWarning = rb_define_class_under(rb_cPrism, "ParseWarning", rb_cObject);
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index c7f6cdcd..170a529b 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -189,45 +189,58 @@ module Prism
     end
   end
 
-  # This represents a comment that was encountered during parsing.
+  # This represents a comment that was encountered during parsing. It is the
+  # base class for all comment types.
   class Comment
-    # These are the three types that comments can be from a source file.
-    #
-    # :inline comments are the most common. They correspond to comments in the
-    # source file like this one that start with #.
-    #
-    # :embdoc comments are comments that are surrounded by =begin and =end.
-    #
-    # :__END__ comments are comments that are after the __END__ keyword in a
-    # source file.
-    TYPES = [:inline, :embdoc, :__END__]
-
-    # The type of comment that this comment is.
-    attr_reader :type
-
     # The location of this comment in the source.
     attr_reader :location
 
-    # Create a new comment object with the given type and location.
-    def initialize(type, location)
-      @type = type
+    # Create a new comment object with the given location.
+    def initialize(location)
       @location = location
     end
 
     # Implement the hash pattern matching interface for Comment.
     def deconstruct_keys(keys)
-      { type: type, location: location }
+      { location: location }
     end
 
-    # Returns true if the comment happens on the same line as other code and
+    # This can only be true for inline comments.
+    def trailing?
+      false
+    end
+  end
+
+  # InlineComment objects are the most common. They correspond to comments in
+  # the source file like this one that start with #.
+  class InlineComment < Comment
+    # Returns true if this comment happens on the same line as other code and
     # false if the comment is by itself.
     def trailing?
-      type == :inline && !location.start_line_slice.strip.empty?
+      !location.start_line_slice.strip.empty?
     end
 
     # Returns a string representation of this comment.
     def inspect
-      "#<Prism::Comment @type=#{@type.inspect} @location=#{@location.inspect}>"
+      "#<Prism::InlineComment @location=#{location.inspect}>"
+    end
+  end
+
+  # EmbDocComment objects correspond to comments that are surrounded by =begin
+  # and =end.
+  class EmbDocComment < Comment
+    # Returns a string representation of this comment.
+    def inspect
+      "#<Prism::EmbDocComment @location=#{location.inspect}>"
+    end
+  end
+
+  # DATAComment objects correspond to comments that are after the __END__
+  # keyword in a source file.
+  class DATAComment < Comment
+    # Returns a string representation of this comment.
+    def inspect
+      "#<Prism::DATAComment @location=#{location.inspect}>"
     end
   end
 
diff --git a/rakelib/check_annotations.rake b/rakelib/check_annotations.rake
index a933f31f..fe729fd8 100644
--- a/rakelib/check_annotations.rake
+++ b/rakelib/check_annotations.rake
@@ -1,20 +1,15 @@
 # frozen_string_literal: true
 
 desc "Check that RBS and RBI generated files are valid"
-task :check_annotations do
+task check_annotations: :compile do
   require "prism"
   require "rbs"
   require "rbs/cli"
 
   # Run `rbs` against the generated file, which checks for valid syntax and any missing constants
   puts "Checking RBS annotations"
-  # output = Bundler.with_original_env { `rbs -I sig/prism.rbs validate` }
-  begin
-    cli = RBS::CLI.new(stdout: STDOUT, stderr: STDERR)
-    cli.run(["-I", "sig", "validate"])
-  rescue => e
-    abort(e.message)
-  end
+  cli = RBS::CLI.new(stdout: STDOUT, stderr: STDERR)
+  cli.run(["-I", "sig", "validate"])
 
   # For RBI files, we just use Prism itself to check for valid syntax since they use Ruby compliant syntax
   puts "Checking RBI annotations"
diff --git a/rakelib/check_manifest.rake b/rakelib/check_manifest.rake
index ce039e6b..46aa5c0d 100644
--- a/rakelib/check_manifest.rake
+++ b/rakelib/check_manifest.rake
@@ -1,5 +1,5 @@
 desc "Perform a sanity check on the gemspec file list"
-task :check_manifest => [:templates] do
+task check_manifest: :templates do
   raw_gemspec = Bundler.load_gemspec("prism.gemspec")
 
   ignore_directories = %w[
diff --git a/rbi/prism_static.rbi b/rbi/prism_static.rbi
index 3860f0ab..88c91739 100644
--- a/rbi/prism_static.rbi
+++ b/rbi/prism_static.rbi
@@ -48,6 +48,20 @@ module Prism
   class Comment
     sig { returns(Location) }
     def location; end
+
+    sig { returns(T::Boolean) }
+    def trailing?; end
+  end
+
+  class InlineComment < Comment
+    sig { override.returns(T::Boolean) }
+    def trailing?; end
+  end
+
+  class EmbDocComment < Comment
+  end
+
+  class DATAComment < Comment
   end
 
   class Location
diff --git a/sig/prism_static.rbs b/sig/prism_static.rbs
index 1497ca46..d8740bc7 100644
--- a/sig/prism_static.rbs
+++ b/sig/prism_static.rbs
@@ -25,6 +25,17 @@ module Prism
 
   class Comment
     def location: () -> Location
+    def trailing?: () -> bool
+  end
+
+  class InlineComment < Comment
+    def trailing?: () -> bool
+  end
+
+  class EmbDocComment < Comment
+  end
+
+  class DATAComment < Comment
   end
 
   class Location
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index ef8ebbba..32bc7411 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -86,7 +86,13 @@ module Prism
       end
 
       def load_comments
-        load_varint.times.map { Comment.new(Comment::TYPES.fetch(load_varint), load_location) }
+        load_varint.times.map do
+          case load_varint
+          when 0 then InlineComment.new(load_location)
+          when 1 then EmbDocComment.new(load_location)
+          when 2 then DATAComment.new(load_location)
+          end
+        end
       end
 
       def load_metadata


Test patch:
diff --git a/test/prism/comments_test.rb b/test/prism/comments_test.rb
index dc43fb6a..d1440945 100644
--- a/test/prism/comments_test.rb
+++ b/test/prism/comments_test.rb
@@ -10,7 +10,7 @@ module Prism
 
       assert_comment(
         source,
-        :inline,
+        InlineComment,
         start_offset: 0,
         end_offset: 9,
         start_line: 1,
@@ -29,7 +29,7 @@ module Prism
 
       assert_comment(
         source,
-        :inline,
+        InlineComment,
         start_offset: 10,
         end_offset: 21,
         start_line: 2,
@@ -47,7 +47,7 @@ module Prism
 
       assert_comment(
         source,
-        :__END__,
+        DATAComment,
         start_offset: 0,
         end_offset: 16,
         start_line: 1,
@@ -62,7 +62,7 @@ module Prism
 
       assert_comment(
         source,
-        :__END__,
+        DATAComment,
         start_offset: 0,
         end_offset: 18,
         start_line: 1,
@@ -81,7 +81,7 @@ module Prism
 
       assert_comment(
         source,
-        :embdoc,
+        EmbDocComment,
         start_offset: 0,
         end_offset: 20,
         start_line: 1,
@@ -99,7 +99,7 @@ module Prism
 
       assert_comment(
         source,
-        :embdoc,
+        EmbDocComment,
         start_offset: 0,
         end_offset: 24,
         start_line: 1,
@@ -138,7 +138,7 @@ module Prism
     def assert_comment(source, type, start_offset:, end_offset:, start_line:, end_line:, start_column:, end_column:)
       result = Prism.parse(source)
       assert result.errors.empty?, result.errors.map(&:message).join("\n")
-      assert_equal type, result.comments.first.type
+      assert_kind_of type, result.comments.first
 
       location = result.comments.first.location
       assert_equal start_offset, location.start_offset, -> { "Expected start_offset to be #{start_offset}" }