Repository: ruby/prism
Pull request #2265 (2024-01-24 20:14:12)

Add level to warnings and errors to categorize them
* Fixes https://github.com/ruby/prism/issues/2082

eregon: > This generally looks good. My only feedback is could you change this to be an integer? I'd like to reuse this field for error level as well.

That makes a lot of sense, and then also makes it clear this new field is useful for both warnings and errors alike.
eregon: Done.
I also modified `templates/javascript/src/deserialize.js.erb`.
That doesn't seem tested in CI BTW because it was not failing on the commit before even though I forgot to update it.
(OTOH Loader.java was failing CI before being edited as deserialization failed without the changes)
eregon: Ah one more thing to do related to this is to remove the suppress warnings field in serialization.
What do you want to do for the verbose option to `Prism.parse`? It's no longer necessary but maybe we should keep it for compatibility?

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 6c794cf0..b18fd293 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,7 +4,3 @@ updates:
     directory: '/'
     schedule:
       interval: 'weekly'
-  - package-ecosystem: 'maven'
-    directory: '/java-wasm'
-    schedule:
-      interval: 'weekly'
diff --git a/bin/parse b/bin/parse
index 349b6083..26d30e28 100755
--- a/bin/parse
+++ b/bin/parse
@@ -8,6 +8,7 @@
 
 $:.unshift(File.expand_path("../lib", __dir__))
 require "prism"
+require "pp"
 
 if ARGV[0] == "-e"
   result = Prism.parse(ARGV[1])
@@ -36,6 +37,6 @@ else
   parts.each_with_index do |(key, value), index|
     puts if index > 0
     puts "#{key}:"
-    puts value.inspect
+    pp value
   end
 end
diff --git a/docs/serialization.md b/docs/serialization.md
index 804be8a6..1b3f3d15 100644
--- a/docs/serialization.md
+++ b/docs/serialization.md
@@ -51,12 +51,21 @@ The comment type is one of:
 | location | the location of the key of the magic comment |
 | location | the location of the value of the magic comment |
 
-### diagnostic
+### error
 
 | # bytes | field |
 | --- | --- |
-| string | diagnostic message (ASCII-only characters) |
-| location | the location in the source this diagnostic applies to |
+| string | error message (ASCII-only characters) |
+| location | the location in the source this error applies to |
+| `1` | the level of the error, see pm_diagnostic_level_t for the values |
+
+## warning
+
+| # bytes | field |
+| --- | --- |
+| string | warning message (ASCII-only characters) |
+| location | the location in the source this warning applies to |
+| `1` | the level of the warning, see pm_diagnostic_level_t for the values |
 
 ## Structure
 
@@ -82,9 +91,9 @@ The header is structured like the following table:
 | magic comment* | magic comments |
 | location? | the optional location of the `__END__` keyword and its contents |
 | varuint | number of errors |
-| diagnostic* | errors |
+| error* | errors |
 | varuint | number of warnings |
-| diagnostic* | warnings |
+| warning* | warnings |
 | `4` | content pool offset |
 | varuint | content pool size |
 
diff --git a/ext/prism/extension.c b/ext/prism/extension.c
index 416e682f..72be59bc 100644
--- a/ext/prism/extension.c
+++ b/ext/prism/extension.c
@@ -368,6 +368,19 @@ parser_magic_comments(pm_parser_t *parser, VALUE source) {
     return magic_comments;
 }
 
+static VALUE get_diagnostic_level_symbol(uint8_t level) {
+    switch (level) {
+        case 0:
+            return ID2SYM(rb_intern("error_default"));
+        case 1:
+            return ID2SYM(rb_intern("warning_verbose_not_nil"));
+        case 2:
+            return ID2SYM(rb_intern("warning_verbose_true"));
+        default:
+            rb_raise(rb_eRuntimeError, "Unknown level: %" PRIu8, level);
+    }
+}
+
 /**
  * Extract out the data location from the parser into a Location instance if one
  * exists.
@@ -404,10 +417,11 @@ parser_errors(pm_parser_t *parser, rb_encoding *encoding, VALUE source) {
 
         VALUE error_argv[] = {
             rb_enc_str_new_cstr(error->message, encoding),
-            rb_class_new_instance(3, location_argv, rb_cPrismLocation)
+            rb_class_new_instance(3, location_argv, rb_cPrismLocation),
+            get_diagnostic_level_symbol(error->level)
         };
 
-        rb_ary_push(errors, rb_class_new_instance(2, error_argv, rb_cPrismParseError));
+        rb_ary_push(errors, rb_class_new_instance(3, error_argv, rb_cPrismParseError));
     }
 
     return errors;
@@ -430,10 +444,11 @@ parser_warnings(pm_parser_t *parser, rb_encoding *encoding, VALUE source) {
 
         VALUE warning_argv[] = {
             rb_enc_str_new_cstr(warning->message, encoding),
-            rb_class_new_instance(3, location_argv, rb_cPrismLocation)
+            rb_class_new_instance(3, location_argv, rb_cPrismLocation),
+            get_diagnostic_level_symbol(warning->level)
         };
 
-        rb_ary_push(warnings, rb_class_new_instance(2, warning_argv, rb_cPrismParseWarning));
+        rb_ary_push(warnings, rb_class_new_instance(3, warning_argv, rb_cPrismParseWarning));
     }
 
     return warnings;
diff --git a/include/prism/diagnostic.h b/include/prism/diagnostic.h
index fb411023..19395176 100644
--- a/include/prism/diagnostic.h
+++ b/include/prism/diagnostic.h
@@ -14,6 +14,18 @@
 #include <stdlib.h>
 #include <assert.h>
 
+/**
+ * This enum represents the level of diagnostics generated during parsing.
+ */
+typedef enum {
+    /** The default level for errors. */
+    PM_ERROR_DEFAULT = 0,
+    /** For warnings which should be emitted if $VERBOSE != nil. */
+    PM_WARNING_VERBOSE_NOT_NIL = 1,
+    /** For warnings which should be emitted if $VERBOSE == true. */
+    PM_WARNING_VERBOSE_TRUE = 2
+} pm_diagnostic_level_t;
+
 /**
  * This struct represents a diagnostic generated during parsing.
  *
@@ -35,6 +47,9 @@ typedef struct {
      * diagnostic is freed.
      */
     bool owned;
+
+    /** The level of the diagnostic, see pm_diagnostic_level_t for possible values. */
+    uint8_t level;
 } pm_diagnostic_t;
 
 /**
diff --git a/java-wasm/pom.xml b/java-wasm/pom.xml
index 4d339033..89c504a1 100644
--- a/java-wasm/pom.xml
+++ b/java-wasm/pom.xml
@@ -22,7 +22,7 @@
     <dependency>
       <groupId>com.dylibso.chicory</groupId>
       <artifactId>runtime</artifactId>
-      <version>0.0.6</version>
+      <version>0.0.5</version>
     </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
diff --git a/java/org/prism/ParseResult.java b/java/org/prism/ParseResult.java
index 6dadbe89..641ff543 100644
--- a/java/org/prism/ParseResult.java
+++ b/java/org/prism/ParseResult.java
@@ -13,23 +13,38 @@ public final class ParseResult {
         }
     }
 
+    public enum DiagnosticLevel {
+        /** The default level for errors. */
+        ERROR_DEFAULT,
+        /** For warnings which should be emitted if $VERBOSE != nil. */
+        WARNING_VERBOSE_NOT_NIL,
+        /** For warnings which should be emitted if $VERBOSE == true. */
+        WARNING_VERBOSE_TRUE
+    }
+
+    public static DiagnosticLevel[] DIAGNOSTIC_LEVELS = DiagnosticLevel.values();
+
     public static final class Error {
         public final String message;
         public final Nodes.Location location;
+        public final DiagnosticLevel level;
 
-        public Error(String message, Nodes.Location location) {
+        public Error(String message, Nodes.Location location, DiagnosticLevel level) {
             this.message = message;
             this.location = location;
+            this.level = level;
         }
     }
 
     public static final class Warning {
         public final String message;
         public final Nodes.Location location;
+        public final DiagnosticLevel level;
 
-        public Warning(String message, Nodes.Location location) {
+        public Warning(String message, Nodes.Location location, DiagnosticLevel level) {
             this.message = message;
             this.location = location;
+            this.level = level;
         }
     }
 
diff --git a/lib/prism/debug.rb b/lib/prism/debug.rb
index f4c0bcf9..24a27d07 100644
--- a/lib/prism/debug.rb
+++ b/lib/prism/debug.rb
@@ -59,7 +59,21 @@ module Prism
         stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
 
         while (iseq = stack.pop)
-          locals << iseq.local_table
+          names = [*iseq.local_table]
+          names.map!.with_index do |name, index|
+            # When an anonymous local variable is present in the iseq's local
+            # table, it is represented as the stack offset from the top.
+            # However, when these are dumped to binary and read back in, they
+            # are replaced with the symbol :#arg_rest. To consistently handle
+            # this, we replace them here with their index.
+            if name == :"#arg_rest"
+              names.length - index + 1
+            else
+              name
+            end
+          end
+
+          locals << names
           iseq.each_child { |child| stack << child }
         end
 
@@ -127,7 +141,7 @@ module Prism
             sorted << AnonymousLocal if params.keywords.any?
 
             if params.keyword_rest.is_a?(ForwardingParameterNode)
-              sorted.push(:*, :**, :&, :"...")
+              sorted.push(:*, :&, :"...")
             elsif params.keyword_rest.is_a?(KeywordRestParameterNode)
               sorted << params.keyword_rest.name if params.keyword_rest.name
             end
diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb
index 556c6640..27e03fe2 100644
--- a/lib/prism/parse_result.rb
+++ b/lib/prism/parse_result.rb
@@ -312,10 +312,14 @@ module Prism
     # A Location object representing the location of this error in the source.
     attr_reader :location
 
+    # The level of this error
+    attr_reader :level
+
     # Create a new error object with the given message and location.
-    def initialize(message, location)
+    def initialize(message, location, level)
       @message = message
       @location = location
+      @level = level
     end
 
     # Implement the hash pattern matching interface for ParseError.
@@ -325,7 +329,7 @@ module Prism
 
     # Returns a string representation of this error.
     def inspect
-      "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect}>"
+      "#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level}>"
     end
   end
 
@@ -337,20 +341,24 @@ module Prism
     # A Location object representing the location of this warning in the source.
     attr_reader :location
 
+    # The level of this warning
+    attr_reader :level
+
     # Create a new warning object with the given message and location.
-    def initialize(message, location)
+    def initialize(message, location, level)
       @message = message
       @location = location
+      @level = level
     end
 
     # Implement the hash pattern matching interface for ParseWarning.
     def deconstruct_keys(keys)
-      { message: message, location: location }
+      { message: message, location: location, verbose_only: verbose_only }
     end
 
     # Returns a string representation of this warning.
     def inspect
-      "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect}>"
+      "#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect} @level=#{@level}>"
     end
   end
 
diff --git a/src/diagnostic.c b/src/diagnostic.c
index ed47a1cd..112ee4dd 100644
--- a/src/diagnostic.c
+++ b/src/diagnostic.c
@@ -1,5 +1,14 @@
 #include "prism/diagnostic.h"
 
+/** This struct holds the data for each diagnostic. */
+struct diagnostic_data {
+    /** The message associated with the diagnostic. */
+    const char* message;
+
+    /** The level associated with the diagnostic. */
+    pm_diagnostic_level_t level;
+};
+
 /**
  * ## Message composition
  *
@@ -49,238 +58,254 @@
  *   - e.g., "INVALID_NUMBER_DECIMAL" is better than "DECIMAL_INVALID_NUMBER".
  *   - When in doubt, look for similar patterns and name them so that they are grouped when lexically
  *     sorted. See PM_ERR_ARGUMENT_NO_FORWARDING_* for an example.
+ *
+ * ## Level
+ *
+ * See pm_diagnostic_level_t for the available levels.
+ * For warning, they are either:
+ * PM_WARNING_VERBOSE_NOT_NIL if they appear for `ruby -c -e 'code'`,
+ * or PM_WARNING_VERBOSE_TRUE if they appear only with -w: `ruby -w -c -e 'code'`.
  */
-static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
-    [PM_ERR_ALIAS_ARGUMENT]                     = "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable",
-    [PM_ERR_AMPAMPEQ_MULTI_ASSIGN]              = "unexpected `&&=` in a multiple assignment",
-    [PM_ERR_ARGUMENT_AFTER_BLOCK]               = "unexpected argument after a block argument",
-    [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = "unexpected argument after `...`",
-    [PM_ERR_ARGUMENT_BARE_HASH]                 = "unexpected bare hash argument",
-    [PM_ERR_ARGUMENT_BLOCK_FORWARDING]          = "both a block argument and a forwarding argument; only one block is allowed",
-    [PM_ERR_ARGUMENT_BLOCK_MULTI]               = "multiple block arguments; only one block is allowed",
-    [PM_ERR_ARGUMENT_FORMAL_CLASS]              = "invalid formal argument; formal argument cannot be a class variable",
-    [PM_ERR_ARGUMENT_FORMAL_CONSTANT]           = "invalid formal argument; formal argument cannot be a constant",
-    [PM_ERR_ARGUMENT_FORMAL_GLOBAL]             = "invalid formal argument; formal argument cannot be a global variable",
-    [PM_ERR_ARGUMENT_FORMAL_IVAR]               = "invalid formal argument; formal argument cannot be an instance variable",
-    [PM_ERR_ARGUMENT_FORWARDING_UNBOUND]        = "unexpected `...` in an non-parenthesized call",
-    [PM_ERR_ARGUMENT_IN]                        = "unexpected `in` keyword in arguments",
-    [PM_ERR_ARGUMENT_NO_FORWARDING_AMP]         = "unexpected `&` when the parent method is not forwarding",
-    [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES]    = "unexpected `...` when the parent method is not forwarding",
-    [PM_ERR_ARGUMENT_NO_FORWARDING_STAR]        = "unexpected `*` when the parent method is not forwarding",
-    [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT]   = "unexpected `*` splat argument after a `**` keyword splat argument",
-    [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT]         = "unexpected `*` splat argument after a `*` splat argument",
-    [PM_ERR_ARGUMENT_TERM_PAREN]                = "expected a `)` to close the arguments",
-    [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK]          = "unexpected `{` after a method call without parenthesis",
-    [PM_ERR_ARRAY_ELEMENT]                      = "expected an element for the array",
-    [PM_ERR_ARRAY_EXPRESSION]                   = "expected an expression for the array element",
-    [PM_ERR_ARRAY_EXPRESSION_AFTER_STAR]        = "expected an expression after `*` in the array",
-    [PM_ERR_ARRAY_SEPARATOR]                    = "expected a `,` separator for the array elements",
-    [PM_ERR_ARRAY_TERM]                         = "expected a `]` to close the array",
-    [PM_ERR_BEGIN_LONELY_ELSE]                  = "unexpected `else` in `begin` block; a `rescue` clause must precede `else`",
-    [PM_ERR_BEGIN_TERM]                         = "expected an `end` to close the `begin` statement",
-    [PM_ERR_BEGIN_UPCASE_BRACE]                 = "expected a `{` after `BEGIN`",
-    [PM_ERR_BEGIN_UPCASE_TERM]                  = "expected a `}` to close the `BEGIN` statement",
-    [PM_ERR_BEGIN_UPCASE_TOPLEVEL]              = "BEGIN is permitted only at toplevel",
-    [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE]         = "expected a local variable name in the block parameters",
-    [PM_ERR_BLOCK_PARAM_PIPE_TERM]              = "expected the block parameters to end with `|`",
-    [PM_ERR_BLOCK_TERM_BRACE]                   = "expected a block beginning with `{` to end with `}`",
-    [PM_ERR_BLOCK_TERM_END]                     = "expected a block beginning with `do` to end with `end`",
-    [PM_ERR_CANNOT_PARSE_EXPRESSION]            = "cannot parse the expression",
-    [PM_ERR_CANNOT_PARSE_STRING_PART]           = "cannot parse the string part",
-    [PM_ERR_CASE_EXPRESSION_AFTER_CASE]         = "expected an expression after `case`",
-    [PM_ERR_CASE_EXPRESSION_AFTER_WHEN]         = "expected an expression after `when`",
-    [PM_ERR_CASE_MATCH_MISSING_PREDICATE]       = "expected a predicate for a case matching statement",
-    [PM_ERR_CASE_MISSING_CONDITIONS]            = "expected a `when` or `in` clause after `case`",
-    [PM_ERR_CASE_TERM]                          = "expected an `end` to close the `case` statement",
-    [PM_ERR_CLASS_IN_METHOD]                    = "unexpected class definition in a method definition",
-    [PM_ERR_CLASS_NAME]                         = "expected a constant name after `class`",
-    [PM_ERR_CLASS_SUPERCLASS]                   = "expected a superclass after `<`",
-    [PM_ERR_CLASS_TERM]                         = "expected an `end` to close the `class` statement",
-    [PM_ERR_CLASS_UNEXPECTED_END]               = "unexpected `end`, expecting ';' or '\\n'",
-    [PM_ERR_CONDITIONAL_ELSIF_PREDICATE]        = "expected a predicate expression for the `elsif` statement",
-    [PM_ERR_CONDITIONAL_IF_PREDICATE]           = "expected a predicate expression for the `if` statement",
-    [PM_ERR_CONDITIONAL_PREDICATE_TERM]         = "expected `then` or `;` or '\\n'",
-    [PM_ERR_CONDITIONAL_TERM]                   = "expected an `end` to close the conditional clause",
-    [PM_ERR_CONDITIONAL_TERM_ELSE]              = "expected an `end` to close the `else` clause",
-    [PM_ERR_CONDITIONAL_UNLESS_PREDICATE]       = "expected a predicate expression for the `unless` statement",
-    [PM_ERR_CONDITIONAL_UNTIL_PREDICATE]        = "expected a predicate expression for the `until` statement",
-    [PM_ERR_CONDITIONAL_WHILE_PREDICATE]        = "expected a predicate expression for the `while` statement",
-    [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = "expected a constant after the `::` operator",
-    [PM_ERR_DEF_ENDLESS]                        = "could not parse the endless method body",
-    [PM_ERR_DEF_ENDLESS_SETTER]                 = "invalid method name; a setter method cannot be defined in an endless method definition",
-    [PM_ERR_DEF_NAME]                           = "expected a method name",
-    [PM_ERR_DEF_NAME_AFTER_RECEIVER]            = "expected a method name after the receiver",
-    [PM_ERR_DEF_PARAMS_TERM]                    = "expected a delimiter to close the parameters",
-    [PM_ERR_DEF_PARAMS_TERM_PAREN]              = "expected a `)` to close the parameters",
-    [PM_ERR_DEF_RECEIVER]                       = "expected a receiver for the method definition",
-    [PM_ERR_DEF_RECEIVER_TERM]                  = "expected a `.` or `::` after the receiver in a method definition",
-    [PM_ERR_DEF_TERM]                           = "expected an `end` to close the `def` statement",
-    [PM_ERR_DEFINED_EXPRESSION]                 = "expected an expression after `defined?`",
-    [PM_ERR_EMBDOC_TERM]                        = "could not find a terminator for the embedded document",
-    [PM_ERR_EMBEXPR_END]                        = "expected a `}` to close the embedded expression",
-    [PM_ERR_EMBVAR_INVALID]                     = "invalid embedded variable",
-    [PM_ERR_END_UPCASE_BRACE]                   = "expected a `{` after `END`",
-    [PM_ERR_END_UPCASE_TERM]                    = "expected a `}` to close the `END` statement",
-    [PM_ERR_ESCAPE_INVALID_CONTROL]             = "invalid control escape sequence",
-    [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT]      = "invalid control escape sequence; control cannot be repeated",
-    [PM_ERR_ESCAPE_INVALID_HEXADECIMAL]         = "invalid hexadecimal escape sequence",
-    [PM_ERR_ESCAPE_INVALID_META]                = "invalid meta escape sequence",
-    [PM_ERR_ESCAPE_INVALID_META_REPEAT]         = "invalid meta escape sequence; meta cannot be repeated",
-    [PM_ERR_ESCAPE_INVALID_UNICODE]             = "invalid Unicode escape sequence",
-    [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS]    = "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags",
-    [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL]     = "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal",
-    [PM_ERR_ESCAPE_INVALID_UNICODE_LONG]        = "invalid Unicode escape sequence; maximum length is 6 digits",
-    [PM_ERR_ESCAPE_INVALID_UNICODE_TERM]        = "invalid Unicode escape sequence; needs closing `}`",
-    [PM_ERR_EXPECT_ARGUMENT]                    = "expected an argument",
-    [PM_ERR_EXPECT_EOL_AFTER_STATEMENT]         = "expected a newline or semicolon after the statement",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ]   = "expected an expression after `&&=`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = "expected an expression after `||=`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA]      = "expected an expression after `,`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL]      = "expected an expression after `=`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS]  = "expected an expression after `<<`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN]     = "expected an expression after `(`",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR]   = "expected an expression after the operator",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT]      = "expected an expression after `*` splat in an argument",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = "expected an expression after `**` in a hash",
-    [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR]       = "expected an expression after `*`",
-    [PM_ERR_EXPECT_IDENT_REQ_PARAMETER]         = "expected an identifier for the required parameter",
-    [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER]        = "expected a `(` to start a required parameter",
-    [PM_ERR_EXPECT_RBRACKET]                    = "expected a matching `]`",
-    [PM_ERR_EXPECT_RPAREN]                      = "expected a matching `)`",
-    [PM_ERR_EXPECT_RPAREN_AFTER_MULTI]          = "expected a `)` after multiple assignment",
-    [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER]        = "expected a `)` to end a required parameter",
-    [PM_ERR_EXPECT_STRING_CONTENT]              = "expected string content after opening string delimiter",
-    [PM_ERR_EXPECT_WHEN_DELIMITER]              = "expected a delimiter after the predicates of a `when` clause",
-    [PM_ERR_EXPRESSION_BARE_HASH]               = "unexpected bare hash in expression",
-    [PM_ERR_FOR_COLLECTION]                     = "expected a collection after the `in` in a `for` statement",
-    [PM_ERR_FOR_INDEX]                          = "expected an index after `for`",
-    [PM_ERR_FOR_IN]                             = "expected an `in` after the index in a `for` statement",
-    [PM_ERR_FOR_TERM]                           = "expected an `end` to close the `for` loop",
-    [PM_ERR_HASH_EXPRESSION_AFTER_LABEL]        = "expected an expression after the label in a hash",
-    [PM_ERR_HASH_KEY]                           = "expected a key in the hash literal",
-    [PM_ERR_HASH_ROCKET]                        = "expected a `=>` between the hash key and value",
-    [PM_ERR_HASH_TERM]                          = "expected a `}` to close the hash literal",
-    [PM_ERR_HASH_VALUE]                         = "expected a value in the hash literal",
-    [PM_ERR_HEREDOC_TERM]                       = "could not find a terminator for the heredoc",
-    [PM_ERR_INCOMPLETE_QUESTION_MARK]           = "incomplete expression at `?`",
-    [PM_ERR_INCOMPLETE_VARIABLE_CLASS]          = "incomplete class variable",
-    [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE]       = "incomplete instance variable",
-    [PM_ERR_INVALID_ENCODING_MAGIC_COMMENT]     = "unknown or invalid encoding in the magic comment",
-    [PM_ERR_INVALID_FLOAT_EXPONENT]             = "invalid exponent",
-    [PM_ERR_INVALID_NUMBER_BINARY]              = "invalid binary number",
-    [PM_ERR_INVALID_NUMBER_DECIMAL]             = "invalid decimal number",
-    [PM_ERR_INVALID_NUMBER_HEXADECIMAL]         = "invalid hexadecimal number",
-    [PM_ERR_INVALID_NUMBER_OCTAL]               = "invalid octal number",
-    [PM_ERR_INVALID_NUMBER_UNDERSCORE]          = "invalid underscore placement in number",
-    [PM_ERR_INVALID_PERCENT]                    = "invalid `%` token", // TODO WHAT?
-    [PM_ERR_INVALID_TOKEN]                      = "invalid token", // TODO WHAT?
-    [PM_ERR_INVALID_VARIABLE_GLOBAL]            = "invalid global variable",
-    [PM_ERR_IT_NOT_ALLOWED]                     = "`it` is not allowed when an ordinary parameter is defined",
-    [PM_ERR_LAMBDA_OPEN]                        = "expected a `do` keyword or a `{` to open the lambda block",
-    [PM_ERR_LAMBDA_TERM_BRACE]                  = "expected a lambda block beginning with `{` to end with `}`",
-    [PM_ERR_LAMBDA_TERM_END]                    = "expected a lambda block beginning with `do` to end with `end`",
-    [PM_ERR_LIST_I_LOWER_ELEMENT]               = "expected a symbol in a `%i` list",
-    [PM_ERR_LIST_I_LOWER_TERM]                  = "expected a closing delimiter for the `%i` list",
-    [PM_ERR_LIST_I_UPPER_ELEMENT]               = "expected a symbol in a `%I` list",
-    [PM_ERR_LIST_I_UPPER_TERM]                  = "expected a closing delimiter for the `%I` list",
-    [PM_ERR_LIST_W_LOWER_ELEMENT]               = "expected a string in a `%w` list",
-    [PM_ERR_LIST_W_LOWER_TERM]                  = "expected a closing delimiter for the `%w` list",
-    [PM_ERR_LIST_W_UPPER_ELEMENT]               = "expected a string in a `%W` list",
-    [PM_ERR_LIST_W_UPPER_TERM]                  = "expected a closing delimiter for the `%W` list",
-    [PM_ERR_MALLOC_FAILED]                      = "failed to allocate memory",
-    [PM_ERR_MIXED_ENCODING]                     = "UTF-8 mixed within %s source",
-    [PM_ERR_MODULE_IN_METHOD]                   = "unexpected module definition in a method definition",
-    [PM_ERR_MODULE_NAME]                        = "expected a constant name after `module`",
-    [PM_ERR_MODULE_TERM]                        = "expected an `end` to close the `module` statement",
-    [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS]          = "multiple splats in multiple assignment",
-    [PM_ERR_NOT_EXPRESSION]                     = "expected an expression after `not`",
-    [PM_ERR_NO_LOCAL_VARIABLE]                  = "%.*s: no such local variable",
-    [PM_ERR_NUMBER_LITERAL_UNDERSCORE]          = "number literal ending with a `_`",
-    [PM_ERR_NUMBERED_PARAMETER_NOT_ALLOWED]     = "numbered parameters are not allowed when an ordinary parameter is defined",
-    [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE]     = "numbered parameter is already used in outer scope",
-    [PM_ERR_OPERATOR_MULTI_ASSIGN]              = "unexpected operator for a multiple assignment",
-    [PM_ERR_OPERATOR_WRITE_ARGUMENTS]           = "unexpected operator after a call with arguments",
-    [PM_ERR_OPERATOR_WRITE_BLOCK]               = "unexpected operator after a call with a block",
-    [PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI]        = "unexpected multiple `**` splat parameters",
-    [PM_ERR_PARAMETER_BLOCK_MULTI]              = "multiple block parameters; only one block is allowed",
-    [PM_ERR_PARAMETER_CIRCULAR]                 = "parameter default value references itself",
-    [PM_ERR_PARAMETER_METHOD_NAME]              = "unexpected name for a parameter",
-    [PM_ERR_PARAMETER_NAME_REPEAT]              = "repeated parameter name",
-    [PM_ERR_PARAMETER_NO_DEFAULT]               = "expected a default value for the parameter",
-    [PM_ERR_PARAMETER_NO_DEFAULT_KW]            = "expected a default value for the keyword parameter",
-    [PM_ERR_PARAMETER_NUMBERED_RESERVED]        = "%.2s is reserved for numbered parameters",
-    [PM_ERR_PARAMETER_ORDER]                    = "unexpected parameter order",
-    [PM_ERR_PARAMETER_SPLAT_MULTI]              = "unexpected multiple `*` splat parameters",
-    [PM_ERR_PARAMETER_STAR]                     = "unexpected parameter `*`",
-    [PM_ERR_PARAMETER_UNEXPECTED_FWD]           = "unexpected `...` in parameters",
-    [PM_ERR_PARAMETER_WILD_LOOSE_COMMA]         = "unexpected `,` in parameters",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET]   = "expected a pattern expression after the `[` operator",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA]     = "expected a pattern expression after `,`",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET]   = "expected a pattern expression after `=>`",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_IN]        = "expected a pattern expression after the `in` keyword",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY]       = "expected a pattern expression after the key",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN]     = "expected a pattern expression after the `(` operator",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN]       = "expected a pattern expression after the `^` pin operator",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE]      = "expected a pattern expression after the `|` operator",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE]     = "expected a pattern expression after the range operator",
-    [PM_ERR_PATTERN_EXPRESSION_AFTER_REST]      = "unexpected pattern expression after the `**` expression",
-    [PM_ERR_PATTERN_HASH_KEY]                   = "expected a key in the hash pattern",
-    [PM_ERR_PATTERN_HASH_KEY_LABEL]             = "expected a label as the key in the hash pattern", // TODO // THIS // AND // ABOVE // IS WEIRD
-    [PM_ERR_PATTERN_IDENT_AFTER_HROCKET]        = "expected an identifier after the `=>` operator",
-    [PM_ERR_PATTERN_LABEL_AFTER_COMMA]          = "expected a label after the `,` in the hash pattern",
-    [PM_ERR_PATTERN_REST]                       = "unexpected rest pattern",
-    [PM_ERR_PATTERN_TERM_BRACE]                 = "expected a `}` to close the pattern expression",
-    [PM_ERR_PATTERN_TERM_BRACKET]               = "expected a `]` to close the pattern expression",
-    [PM_ERR_PATTERN_TERM_PAREN]                 = "expected a `)` to close the pattern expression",
-    [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN]            = "unexpected `||=` in a multiple assignment",
-    [PM_ERR_REGEXP_TERM]                        = "expected a closing delimiter for the regular expression",
-    [PM_ERR_RESCUE_EXPRESSION]                  = "expected a rescued expression",
-    [PM_ERR_RESCUE_MODIFIER_VALUE]              = "expected a value after the `rescue` modifier",
-    [PM_ERR_RESCUE_TERM]                        = "expected a closing delimiter for the `rescue` clause",
-    [PM_ERR_RESCUE_VARIABLE]                    = "expected an exception variable after `=>` in a rescue statement",
-    [PM_ERR_RETURN_INVALID]                     = "invalid `return` in a class or module body",
-    [PM_ERR_STATEMENT_ALIAS]                    = "unexpected an `alias` at a non-statement position",
-    [PM_ERR_STATEMENT_POSTEXE_END]              = "unexpected an `END` at a non-statement position",
-    [PM_ERR_STATEMENT_PREEXE_BEGIN]             = "unexpected a `BEGIN` at a non-statement position",
-    [PM_ERR_STATEMENT_UNDEF]                    = "unexpected an `undef` at a non-statement position",
-    [PM_ERR_STRING_CONCATENATION]               = "expected a string for concatenation",
-    [PM_ERR_STRING_INTERPOLATED_TERM]           = "expected a closing delimiter for the interpolated string",
-    [PM_ERR_STRING_LITERAL_TERM]                = "expected a closing delimiter for the string literal",
-    [PM_ERR_SYMBOL_INVALID]                     = "invalid symbol", // TODO expected symbol? prism.c ~9719
-    [PM_ERR_SYMBOL_TERM_DYNAMIC]                = "expected a closing delimiter for the dynamic symbol",
-    [PM_ERR_SYMBOL_TERM_INTERPOLATED]           = "expected a closing delimiter for the interpolated symbol",
-    [PM_ERR_TERNARY_COLON]                      = "expected a `:` after the true expression of a ternary operator",
-    [PM_ERR_TERNARY_EXPRESSION_FALSE]           = "expected an expression after `:` in the ternary operator",
-    [PM_ERR_TERNARY_EXPRESSION_TRUE]            = "expected an expression after `?` in the ternary operator",
-    [PM_ERR_UNDEF_ARGUMENT]                     = "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument",
-    [PM_ERR_UNARY_RECEIVER_BANG]                = "expected a receiver for unary `!`",
-    [PM_ERR_UNARY_RECEIVER_MINUS]               = "expected a receiver for unary `-`",
-    [PM_ERR_UNARY_RECEIVER_PLUS]                = "expected a receiver for unary `+`",
-    [PM_ERR_UNARY_RECEIVER_TILDE]               = "expected a receiver for unary `~`",
-    [PM_ERR_UNTIL_TERM]                         = "expected an `end` to close the `until` statement",
-    [PM_ERR_VOID_EXPRESSION]                    = "unexpected void value expression",
-    [PM_ERR_WHILE_TERM]                         = "expected an `end` to close the `while` statement",
-    [PM_ERR_WRITE_TARGET_IN_METHOD]             = "dynamic constant assignment",
-    [PM_ERR_WRITE_TARGET_READONLY]              = "immutable variable as a write target",
-    [PM_ERR_WRITE_TARGET_UNEXPECTED]            = "unexpected write target",
-    [PM_ERR_XSTRING_TERM]                       = "expected a closing delimiter for the `%x` or backtick string",
-    [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS]    = "ambiguous first argument; put parentheses or a space even after `-` operator",
-    [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS]     = "ambiguous first argument; put parentheses or a space even after `+` operator",
-    [PM_WARN_AMBIGUOUS_PREFIX_STAR]             = "ambiguous `*` has been interpreted as an argument prefix",
-    [PM_WARN_AMBIGUOUS_SLASH]                   = "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator",
-    [PM_WARN_END_IN_METHOD]                     = "END in method; use at_exit",
+static struct diagnostic_data const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
+    // Errors
+    [PM_ERR_ALIAS_ARGUMENT]                     = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_DEFAULT },
+    [PM_ERR_AMPAMPEQ_MULTI_ASSIGN]              = { "unexpected `&&=` in a multiple assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_AFTER_BLOCK]               = { "unexpected argument after a block argument", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_BARE_HASH]                 = { "unexpected bare hash argument", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_BLOCK_FORWARDING]          = { "both a block argument and a forwarding argument; only one block is allowed", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_BLOCK_MULTI]               = { "multiple block arguments; only one block is allowed", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_FORMAL_CLASS]              = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_FORMAL_CONSTANT]           = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_FORMAL_GLOBAL]             = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_FORMAL_IVAR]               = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_FORWARDING_UNBOUND]        = { "unexpected `...` in an non-parenthesized call", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_IN]                        = { "unexpected `in` keyword in arguments", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_NO_FORWARDING_AMP]         = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES]    = { "unexpected `...` when the parent method is not forwarding", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_NO_FORWARDING_STAR]        = { "unexpected `*` when the parent method is not forwarding", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT]   = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT]         = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_TERM_PAREN]                = { "expected a `)` to close the arguments", PM_ERROR_DEFAULT },
+    [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK]          = { "unexpected `{` after a method call without parenthesis", PM_ERROR_DEFAULT },
+    [PM_ERR_ARRAY_ELEMENT]                      = { "expected an element for the array", PM_ERROR_DEFAULT },
+    [PM_ERR_ARRAY_EXPRESSION]                   = { "expected an expression for the array element", PM_ERROR_DEFAULT },
+    [PM_ERR_ARRAY_EXPRESSION_AFTER_STAR]        = { "expected an expression after `*` in the array", PM_ERROR_DEFAULT },
+    [PM_ERR_ARRAY_SEPARATOR]                    = { "expected a `,` separator for the array elements", PM_ERROR_DEFAULT },
+    [PM_ERR_ARRAY_TERM]                         = { "expected a `]` to close the array", PM_ERROR_DEFAULT },
+    [PM_ERR_BEGIN_LONELY_ELSE]                  = { "unexpected `else` in `begin` block; a `rescue` clause must precede `else`", PM_ERROR_DEFAULT },
+    [PM_ERR_BEGIN_TERM]                         = { "expected an `end` to close the `begin` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_BEGIN_UPCASE_BRACE]                 = { "expected a `{` after `BEGIN`", PM_ERROR_DEFAULT },
+    [PM_ERR_BEGIN_UPCASE_TERM]                  = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_BEGIN_UPCASE_TOPLEVEL]              = { "BEGIN is permitted only at toplevel", PM_ERROR_DEFAULT },
+    [PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE]         = { "expected a local variable name in the block parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_BLOCK_PARAM_PIPE_TERM]              = { "expected the block parameters to end with `|`", PM_ERROR_DEFAULT },
+    [PM_ERR_BLOCK_TERM_BRACE]                   = { "expected a block beginning with `{` to end with `}`", PM_ERROR_DEFAULT },
+    [PM_ERR_BLOCK_TERM_END]                     = { "expected a block beginning with `do` to end with `end`", PM_ERROR_DEFAULT },
+    [PM_ERR_CANNOT_PARSE_EXPRESSION]            = { "cannot parse the expression", PM_ERROR_DEFAULT },
+    [PM_ERR_CANNOT_PARSE_STRING_PART]           = { "cannot parse the string part", PM_ERROR_DEFAULT },
+    [PM_ERR_CASE_EXPRESSION_AFTER_CASE]         = { "expected an expression after `case`", PM_ERROR_DEFAULT },
+    [PM_ERR_CASE_EXPRESSION_AFTER_WHEN]         = { "expected an expression after `when`", PM_ERROR_DEFAULT },
+    [PM_ERR_CASE_MATCH_MISSING_PREDICATE]       = { "expected a predicate for a case matching statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CASE_MISSING_CONDITIONS]            = { "expected a `when` or `in` clause after `case`", PM_ERROR_DEFAULT },
+    [PM_ERR_CASE_TERM]                          = { "expected an `end` to close the `case` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CLASS_IN_METHOD]                    = { "unexpected class definition in a method definition", PM_ERROR_DEFAULT },
+    [PM_ERR_CLASS_NAME]                         = { "expected a constant name after `class`", PM_ERROR_DEFAULT },
+    [PM_ERR_CLASS_SUPERCLASS]                   = { "expected a superclass after `<`", PM_ERROR_DEFAULT },
+    [PM_ERR_CLASS_TERM]                         = { "expected an `end` to close the `class` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CLASS_UNEXPECTED_END]               = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_ELSIF_PREDICATE]        = { "expected a predicate expression for the `elsif` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_IF_PREDICATE]           = { "expected a predicate expression for the `if` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_PREDICATE_TERM]         = { "expected `then` or `;` or '\\n'", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_TERM]                   = { "expected an `end` to close the conditional clause", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_TERM_ELSE]              = { "expected an `end` to close the `else` clause", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_UNLESS_PREDICATE]       = { "expected a predicate expression for the `unless` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_UNTIL_PREDICATE]        = { "expected a predicate expression for the `until` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CONDITIONAL_WHILE_PREDICATE]        = { "expected a predicate expression for the `while` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_ENDLESS]                        = { "could not parse the endless method body", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_ENDLESS_SETTER]                 = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_NAME]                           = { "expected a method name", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_NAME_AFTER_RECEIVER]            = { "expected a method name after the receiver", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_PARAMS_TERM]                    = { "expected a delimiter to close the parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_PARAMS_TERM_PAREN]              = { "expected a `)` to close the parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_RECEIVER]                       = { "expected a receiver for the method definition", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_RECEIVER_TERM]                  = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_DEFAULT },
+    [PM_ERR_DEF_TERM]                           = { "expected an `end` to close the `def` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_DEFINED_EXPRESSION]                 = { "expected an expression after `defined?`", PM_ERROR_DEFAULT },
+    [PM_ERR_EMBDOC_TERM]                        = { "could not find a terminator for the embedded document", PM_ERROR_DEFAULT },
+    [PM_ERR_EMBEXPR_END]                        = { "expected a `}` to close the embedded expression", PM_ERROR_DEFAULT },
+    [PM_ERR_EMBVAR_INVALID]                     = { "invalid embedded variable", PM_ERROR_DEFAULT },
+    [PM_ERR_END_UPCASE_BRACE]                   = { "expected a `{` after `END`", PM_ERROR_DEFAULT },
+    [PM_ERR_END_UPCASE_TERM]                    = { "expected a `}` to close the `END` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_CONTROL]             = { "invalid control escape sequence", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT]      = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_HEXADECIMAL]         = { "invalid hexadecimal escape sequence", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_META]                = { "invalid meta escape sequence", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_META_REPEAT]         = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_UNICODE]             = { "invalid Unicode escape sequence", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS]    = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL]     = { "invalid Unicode escape sequence; multiple codepoints are not allowed in a character literal", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_UNICODE_LONG]        = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_DEFAULT },
+    [PM_ERR_ESCAPE_INVALID_UNICODE_TERM]        = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_ARGUMENT]                    = { "expected an argument", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EOL_AFTER_STATEMENT]         = { "expected a newline or semicolon after the statement", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ]   = { "expected an expression after `&&=`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA]      = { "expected an expression after `,`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL]      = { "expected an expression after `=`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS]  = { "expected an expression after `<<`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN]     = { "expected an expression after `(`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR]   = { "expected an expression after the operator", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT]      = { "expected an expression after `*` splat in an argument", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR]       = { "expected an expression after `*`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_IDENT_REQ_PARAMETER]         = { "expected an identifier for the required parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER]        = { "expected a `(` to start a required parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_RBRACKET]                    = { "expected a matching `]`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_RPAREN]                      = { "expected a matching `)`", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_RPAREN_AFTER_MULTI]          = { "expected a `)` after multiple assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_RPAREN_REQ_PARAMETER]        = { "expected a `)` to end a required parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_STRING_CONTENT]              = { "expected string content after opening string delimiter", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPECT_WHEN_DELIMITER]              = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_DEFAULT },
+    [PM_ERR_EXPRESSION_BARE_HASH]               = { "unexpected bare hash in expression", PM_ERROR_DEFAULT },
+    [PM_ERR_FOR_COLLECTION]                     = { "expected a collection after the `in` in a `for` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_FOR_INDEX]                          = { "expected an index after `for`", PM_ERROR_DEFAULT },
+    [PM_ERR_FOR_IN]                             = { "expected an `in` after the index in a `for` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_FOR_TERM]                           = { "expected an `end` to close the `for` loop", PM_ERROR_DEFAULT },
+    [PM_ERR_HASH_EXPRESSION_AFTER_LABEL]        = { "expected an expression after the label in a hash", PM_ERROR_DEFAULT },
+    [PM_ERR_HASH_KEY]                           = { "expected a key in the hash literal", PM_ERROR_DEFAULT },
+    [PM_ERR_HASH_ROCKET]                        = { "expected a `=>` between the hash key and value", PM_ERROR_DEFAULT },
+    [PM_ERR_HASH_TERM]                          = { "expected a `}` to close the hash literal", PM_ERROR_DEFAULT },
+    [PM_ERR_HASH_VALUE]                         = { "expected a value in the hash literal", PM_ERROR_DEFAULT },
+    [PM_ERR_HEREDOC_TERM]                       = { "could not find a terminator for the heredoc", PM_ERROR_DEFAULT },
+    [PM_ERR_INCOMPLETE_QUESTION_MARK]           = { "incomplete expression at `?`", PM_ERROR_DEFAULT },
+    [PM_ERR_INCOMPLETE_VARIABLE_CLASS]          = { "incomplete class variable", PM_ERROR_DEFAULT },
+    [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE]       = { "incomplete instance variable", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_ENCODING_MAGIC_COMMENT]     = { "unknown or invalid encoding in the magic comment", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_FLOAT_EXPONENT]             = { "invalid exponent", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_NUMBER_BINARY]              = { "invalid binary number", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_NUMBER_DECIMAL]             = { "invalid decimal number", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_NUMBER_HEXADECIMAL]         = { "invalid hexadecimal number", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_NUMBER_OCTAL]               = { "invalid octal number", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_NUMBER_UNDERSCORE]          = { "invalid underscore placement in number", PM_ERROR_DEFAULT },
+    [PM_ERR_INVALID_PERCENT]                    = { "invalid `%` token", PM_ERROR_DEFAULT }, // TODO WHAT?
+    [PM_ERR_INVALID_TOKEN]                      = { "invalid token", PM_ERROR_DEFAULT }, // TODO WHAT?
+    [PM_ERR_INVALID_VARIABLE_GLOBAL]            = { "invalid global variable", PM_ERROR_DEFAULT },
+    [PM_ERR_IT_NOT_ALLOWED]                     = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_DEFAULT },
+    [PM_ERR_LAMBDA_OPEN]                        = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_DEFAULT },
+    [PM_ERR_LAMBDA_TERM_BRACE]                  = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_DEFAULT },
+    [PM_ERR_LAMBDA_TERM_END]                    = { "expected a lambda block beginning with `do` to end with `end`", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_I_LOWER_ELEMENT]               = { "expected a symbol in a `%i` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_I_LOWER_TERM]                  = { "expected a closing delimiter for the `%i` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_I_UPPER_ELEMENT]               = { "expected a symbol in a `%I` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_I_UPPER_TERM]                  = { "expected a closing delimiter for the `%I` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_W_LOWER_ELEMENT]               = { "expected a string in a `%w` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_W_LOWER_TERM]                  = { "expected a closing delimiter for the `%w` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_W_UPPER_ELEMENT]               = { "expected a string in a `%W` list", PM_ERROR_DEFAULT },
+    [PM_ERR_LIST_W_UPPER_TERM]                  = { "expected a closing delimiter for the `%W` list", PM_ERROR_DEFAULT },
+    [PM_ERR_MALLOC_FAILED]                      = { "failed to allocate memory", PM_ERROR_DEFAULT },
+    [PM_ERR_MIXED_ENCODING]                     = { "UTF-8 mixed within %s source", PM_ERROR_DEFAULT },
+    [PM_ERR_MODULE_IN_METHOD]                   = { "unexpected module definition in a method definition", PM_ERROR_DEFAULT },
+    [PM_ERR_MODULE_NAME]                        = { "expected a constant name after `module`", PM_ERROR_DEFAULT },
+    [PM_ERR_MODULE_TERM]                        = { "expected an `end` to close the `module` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS]          = { "multiple splats in multiple assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_NOT_EXPRESSION]                     = { "expected an expression after `not`", PM_ERROR_DEFAULT },
+    [PM_ERR_NO_LOCAL_VARIABLE]                  = { "%.*s: no such local variable", PM_ERROR_DEFAULT },
+    [PM_ERR_NUMBER_LITERAL_UNDERSCORE]          = { "number literal ending with a `_`", PM_ERROR_DEFAULT },
+    [PM_ERR_NUMBERED_PARAMETER_NOT_ALLOWED]     = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_DEFAULT },
+    [PM_ERR_NUMBERED_PARAMETER_OUTER_SCOPE]     = { "numbered parameter is already used in outer scope", PM_ERROR_DEFAULT },
+    [PM_ERR_OPERATOR_MULTI_ASSIGN]              = { "unexpected operator for a multiple assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_OPERATOR_WRITE_ARGUMENTS]           = { "unexpected operator after a call with arguments", PM_ERROR_DEFAULT },
+    [PM_ERR_OPERATOR_WRITE_BLOCK]               = { "unexpected operator after a call with a block", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI]        = { "unexpected multiple `**` splat parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_BLOCK_MULTI]              = { "multiple block parameters; only one block is allowed", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_CIRCULAR]                 = { "parameter default value references itself", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_METHOD_NAME]              = { "unexpected name for a parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_NAME_REPEAT]              = { "repeated parameter name", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_NO_DEFAULT]               = { "expected a default value for the parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_NO_DEFAULT_KW]            = { "expected a default value for the keyword parameter", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_NUMBERED_RESERVED]        = { "%.2s is reserved for numbered parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_ORDER]                    = { "unexpected parameter order", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_SPLAT_MULTI]              = { "unexpected multiple `*` splat parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_STAR]                     = { "unexpected parameter `*`", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_UNEXPECTED_FWD]           = { "unexpected `...` in parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_PARAMETER_WILD_LOOSE_COMMA]         = { "unexpected `,` in parameters", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET]   = { "expected a pattern expression after the `[` operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA]     = { "expected a pattern expression after `,`", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET]   = { "expected a pattern expression after `=>`", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_IN]        = { "expected a pattern expression after the `in` keyword", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_KEY]       = { "expected a pattern expression after the key", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN]     = { "expected a pattern expression after the `(` operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_PIN]       = { "expected a pattern expression after the `^` pin operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE]      = { "expected a pattern expression after the `|` operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE]     = { "expected a pattern expression after the range operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_EXPRESSION_AFTER_REST]      = { "unexpected pattern expression after the `**` expression", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_HASH_KEY]                   = { "expected a key in the hash pattern", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_HASH_KEY_LABEL]             = { "expected a label as the key in the hash pattern", PM_ERROR_DEFAULT }, // TODO // THIS // AND // ABOVE // IS WEIRD
+    [PM_ERR_PATTERN_IDENT_AFTER_HROCKET]        = { "expected an identifier after the `=>` operator", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_LABEL_AFTER_COMMA]          = { "expected a label after the `,` in the hash pattern", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_REST]                       = { "unexpected rest pattern", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_TERM_BRACE]                 = { "expected a `}` to close the pattern expression", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_TERM_BRACKET]               = { "expected a `]` to close the pattern expression", PM_ERROR_DEFAULT },
+    [PM_ERR_PATTERN_TERM_PAREN]                 = { "expected a `)` to close the pattern expression", PM_ERROR_DEFAULT },
+    [PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN]            = { "unexpected `||=` in a multiple assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_REGEXP_TERM]                        = { "expected a closing delimiter for the regular expression", PM_ERROR_DEFAULT },
+    [PM_ERR_RESCUE_EXPRESSION]                  = { "expected a rescued expression", PM_ERROR_DEFAULT },
+    [PM_ERR_RESCUE_MODIFIER_VALUE]              = { "expected a value after the `rescue` modifier", PM_ERROR_DEFAULT },
+    [PM_ERR_RESCUE_TERM]                        = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_DEFAULT },
+    [PM_ERR_RESCUE_VARIABLE]                    = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_DEFAULT },
+    [PM_ERR_RETURN_INVALID]                     = { "invalid `return` in a class or module body", PM_ERROR_DEFAULT },
+    [PM_ERR_STATEMENT_ALIAS]                    = { "unexpected an `alias` at a non-statement position", PM_ERROR_DEFAULT },
+    [PM_ERR_STATEMENT_POSTEXE_END]              = { "unexpected an `END` at a non-statement position", PM_ERROR_DEFAULT },
+    [PM_ERR_STATEMENT_PREEXE_BEGIN]             = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_DEFAULT },
+    [PM_ERR_STATEMENT_UNDEF]                    = { "unexpected an `undef` at a non-statement position", PM_ERROR_DEFAULT },
+    [PM_ERR_STRING_CONCATENATION]               = { "expected a string for concatenation", PM_ERROR_DEFAULT },
+    [PM_ERR_STRING_INTERPOLATED_TERM]           = { "expected a closing delimiter for the interpolated string", PM_ERROR_DEFAULT },
+    [PM_ERR_STRING_LITERAL_TERM]                = { "expected a closing delimiter for the string literal", PM_ERROR_DEFAULT },
+    [PM_ERR_SYMBOL_INVALID]                     = { "invalid symbol", PM_ERROR_DEFAULT }, // TODO expected symbol? prism.c ~9719
+    [PM_ERR_SYMBOL_TERM_DYNAMIC]                = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_DEFAULT },
+    [PM_ERR_SYMBOL_TERM_INTERPOLATED]           = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_DEFAULT },
+    [PM_ERR_TERNARY_COLON]                      = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_DEFAULT },
+    [PM_ERR_TERNARY_EXPRESSION_FALSE]           = { "expected an expression after `:` in the ternary operator", PM_ERROR_DEFAULT },
+    [PM_ERR_TERNARY_EXPRESSION_TRUE]            = { "expected an expression after `?` in the ternary operator", PM_ERROR_DEFAULT },
+    [PM_ERR_UNDEF_ARGUMENT]                     = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_DEFAULT },
+    [PM_ERR_UNARY_RECEIVER_BANG]                = { "expected a receiver for unary `!`", PM_ERROR_DEFAULT },
+    [PM_ERR_UNARY_RECEIVER_MINUS]               = { "expected a receiver for unary `-`", PM_ERROR_DEFAULT },
+    [PM_ERR_UNARY_RECEIVER_PLUS]                = { "expected a receiver for unary `+`", PM_ERROR_DEFAULT },
+    [PM_ERR_UNARY_RECEIVER_TILDE]               = { "expected a receiver for unary `~`", PM_ERROR_DEFAULT },
+    [PM_ERR_UNTIL_TERM]                         = { "expected an `end` to close the `until` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_VOID_EXPRESSION]                    = { "unexpected void value expression", PM_ERROR_DEFAULT },
+    [PM_ERR_WHILE_TERM]                         = { "expected an `end` to close the `while` statement", PM_ERROR_DEFAULT },
+    [PM_ERR_WRITE_TARGET_IN_METHOD]             = { "dynamic constant assignment", PM_ERROR_DEFAULT },
+    [PM_ERR_WRITE_TARGET_READONLY]              = { "immutable variable as a write target", PM_ERROR_DEFAULT },
+    [PM_ERR_WRITE_TARGET_UNEXPECTED]            = { "unexpected write target", PM_ERROR_DEFAULT },
+    [PM_ERR_XSTRING_TERM]                       = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_DEFAULT },
+    // Warnings
+    [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS]    = { "ambiguous first argument; put parentheses or a space even after `-` operator", PM_WARNING_VERBOSE_TRUE },
+    [PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS]     = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_VERBOSE_TRUE },
+    [PM_WARN_AMBIGUOUS_PREFIX_STAR]             = { "ambiguous `*` has been interpreted as an argument prefix", PM_WARNING_VERBOSE_TRUE },
+    [PM_WARN_AMBIGUOUS_SLASH]                   = { "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", PM_WARNING_VERBOSE_TRUE },
+    [PM_WARN_END_IN_METHOD]                     = { "END in method; use at_exit", PM_WARNING_VERBOSE_NOT_NIL },
 };
 
 static const char*
 pm_diagnostic_message(pm_diagnostic_id_t diag_id) {
     assert(diag_id < PM_DIAGNOSTIC_ID_LEN);
 
-    const char *message = diagnostic_messages[diag_id];
+    const char *message = diagnostic_messages[diag_id].message;
     assert(message);
 
     return message;
 }
 
+static uint8_t
+pm_diagnostic_level(pm_diagnostic_id_t diag_id) {
+    assert(diag_id < PM_DIAGNOSTIC_ID_LEN);
+
+    return (uint8_t) diagnostic_messages[diag_id].level;
+}
+
 /**
  * Append an error to the given list of diagnostic.
  */
@@ -292,7 +317,8 @@ pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *
     *diagnostic = (pm_diagnostic_t) {
         .location = { start, end },
         .message = pm_diagnostic_message(diag_id),
-        .owned = false
+        .owned = false,
+        .level = pm_diagnostic_level(diag_id)
     };
 
     pm_list_append(list, (pm_list_node_t *) diagnostic);
@@ -335,7 +361,8 @@ pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const ui
     *diagnostic = (pm_diagnostic_t) {
         .location = { start, end },
         .message = message,
-        .owned = true
+        .owned = true,
+        .level = pm_diagnostic_level(diag_id)
     };
 
     pm_list_append(list, (pm_list_node_t *) diagnostic);
diff --git a/src/prism.c b/src/prism.c
index 4d2602cd..19cd1e6d 100644
--- a/src/prism.c
+++ b/src/prism.c
@@ -3999,8 +3999,9 @@ pm_keyword_hash_node_create(pm_parser_t *parser) {
  */
 static void
 pm_keyword_hash_node_elements_append(pm_keyword_hash_node_t *hash, pm_node_t *element) {
-    // If the element being added is not an AssocNode or does not have a symbol
-    // key, then we want to turn the SYMBOL_KEYS flag off.
+    // If the element being added is not an AssocNode or does not have a symbol key, then
+    // we want to turn the STATIC_KEYS flag off.
+    // TODO: Rename the flag to SYMBOL_KEYS instead.
     if (!PM_NODE_TYPE_P(element, PM_ASSOC_NODE) || !PM_NODE_TYPE_P(((pm_assoc_node_t *) element)->key, PM_SYMBOL_NODE)) {
         pm_node_flag_unset((pm_node_t *)hash, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS);
     }
@@ -11020,7 +11021,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
                     return target;
                 }
 
-                if (char_is_identifier_start(parser, call->message_loc.start)) {
+                if (*call->message_loc.start == '_' || parser->encoding->alnum_char(call->message_loc.start, call->message_loc.end - call->message_loc.start)) {
                     // When we get here, we have a method call, because it was
                     // previously marked as a method call but now we have an =. This
                     // looks like:
diff --git a/templates/java/org/prism/Loader.java.erb b/templates/java/org/prism/Loader.java.erb
index 073dd351..c4af3c4c 100644
--- a/templates/java/org/prism/Loader.java.erb
+++ b/templates/java/org/prism/Loader.java.erb
@@ -183,8 +183,9 @@ public class Loader {
             byte[] bytes = loadEmbeddedString();
             String message = new String(bytes, StandardCharsets.US_ASCII);
             Nodes.Location location = loadLocation();
+            ParseResult.DiagnosticLevel level = ParseResult.DIAGNOSTIC_LEVELS[buffer.get()];
 
-            ParseResult.Error error = new ParseResult.Error(message, location);
+            ParseResult.Error error = new ParseResult.Error(message, location, level);
             errors[i] = error;
         }
 
@@ -200,8 +201,9 @@ public class Loader {
             byte[] bytes = loadEmbeddedString();
             String message = new String(bytes, StandardCharsets.US_ASCII);
             Nodes.Location location = loadLocation();
+            ParseResult.DiagnosticLevel level = ParseResult.DIAGNOSTIC_LEVELS[buffer.get()];
 
-            ParseResult.Warning warning = new ParseResult.Warning(message, location);
+            ParseResult.Warning warning = new ParseResult.Warning(message, location, level);
             warnings[i] = warning;
         }
 
diff --git a/templates/javascript/src/deserialize.js.erb b/templates/javascript/src/deserialize.js.erb
index 6ab82e3b..915831ee 100644
--- a/templates/javascript/src/deserialize.js.erb
+++ b/templates/javascript/src/deserialize.js.erb
@@ -119,13 +119,13 @@ class SerializationBuffer {
 /**
  * An error in the source code.
  *
- * @typedef {{ message: string, location: Location }} ParseError
+ * @typedef {{ message: string, location: Location, level: string }} ParseError
  */
 
 /**
  * A warning in the source code.
  *
- * @typedef {{ message: string, location: Location }} ParseWarning
+ * @typedef {{ message: string, location: Location, level: string }} ParseWarning
  */
 
 /**
@@ -184,6 +184,12 @@ export class ParseResult {
   }
 }
 
+const diagnosticLevels = [
+    "error_default",
+    "warning_verbose_not_nil",
+    "warning_verbose_true"
+];
+
 /**
  * Accept two Uint8Arrays, one for the source and one for the serialized format.
  * Return the AST corresponding to the serialized form.
@@ -229,12 +235,14 @@ export function deserialize(source, array) {
 
   const errors = Array.from({ length: buffer.readVarInt() }, () => ({
     message: buffer.readString(buffer.readVarInt()),
-    location: buffer.readLocation()
+    location: buffer.readLocation(),
+    level: diagnosticLevels[buffer.readByte()]
   }));
 
   const warnings = Array.from({ length: buffer.readVarInt() }, () => ({
     message: buffer.readString(buffer.readVarInt()),
-    location: buffer.readLocation()
+    location: buffer.readLocation(),
+    level: diagnosticLevels[buffer.readByte()]
   }));
 
   const constantPoolOffset = buffer.readUint32();
diff --git a/templates/lib/prism/serialize.rb.erb b/templates/lib/prism/serialize.rb.erb
index 0b5300c4..220f52be 100644
--- a/templates/lib/prism/serialize.rb.erb
+++ b/templates/lib/prism/serialize.rb.erb
@@ -96,8 +96,8 @@ module Prism
         comments = load_comments
         magic_comments = load_varuint.times.map { MagicComment.new(load_location, load_location) }
         data_loc = load_optional_location
-        errors = load_varuint.times.map { ParseError.new(load_embedded_string, load_location) }
-        warnings = load_varuint.times.map { ParseWarning.new(load_embedded_string, load_location) }
+        errors = load_varuint.times.map { ParseError.new(load_embedded_string, load_location, load_diagnostic_level) }
+        warnings = load_varuint.times.map { ParseWarning.new(load_embedded_string, load_location, load_diagnostic_level) }
         [comments, magic_comments, data_loc, errors, warnings]
       end
 
@@ -211,9 +211,9 @@ module Prism
 
           constant =
             if start.nobits?(1 << 31)
-              input.byteslice(start, length).force_encoding(@encoding).to_sym
+              input.byteslice(start, length).to_sym
             else
-              serialized.byteslice(start & ((1 << 31) - 1), length).force_encoding(@encoding).to_sym
+              serialized.byteslice(start & ((1 << 31) - 1), length).to_sym
             end
 
           constant_pool[index] = constant
@@ -231,6 +231,20 @@ module Prism
         load_constant(index - 1) if index != 0
       end
 
+      def load_diagnostic_level
+        level = io.getbyte
+        case level
+        when 0
+          :error_default
+        when 1
+          :warning_verbose_not_nil
+        when 2
+          :warning_verbose_true
+        else
+          raise "Unknown level: #{level}"
+        end
+      end
+
       if RUBY_ENGINE == 'ruby'
         def load_node
           type = io.getbyte
diff --git a/templates/src/serialize.c.erb b/templates/src/serialize.c.erb
index e9cdd1e8..deda01f2 100644
--- a/templates/src/serialize.c.erb
+++ b/templates/src/serialize.c.erb
@@ -190,6 +190,8 @@ pm_serialize_diagnostic(pm_parser_t *parser, pm_diagnostic_t *diagnostic, pm_buf
 
     // serialize location
     pm_serialize_location(parser, &diagnostic->location, buffer);
+
+    pm_buffer_append_byte(buffer, diagnostic->level);
 }
 
 static void


Test patch:
diff --git a/java-wasm/src/test/java/org/prism/DummyTest.java b/java-wasm/src/test/java/org/prism/DummyTest.java
index 69c99e8e..092be29d 100644
--- a/java-wasm/src/test/java/org/prism/DummyTest.java
+++ b/java-wasm/src/test/java/org/prism/DummyTest.java
@@ -15,14 +15,14 @@ public class DummyTest {
 
     @Test
     public void test1() {
-        var wasmPrism = Module.builder("prism.wasm").build().instantiate();
-        var memory = wasmPrism.memory();
-        var calloc = wasmPrism.export("calloc");
-        var pmSerializeParse = wasmPrism.export("pm_serialize_parse");
-        var pmBufferInit = wasmPrism.export("pm_buffer_init");
-        var pmBufferSizeof = wasmPrism.export("pm_buffer_sizeof");
-        var pmBufferValue = wasmPrism.export("pm_buffer_value");
-        var pmBufferLength = wasmPrism.export("pm_buffer_length");
+        var wasmPrism = Module.build(new File("src/test/resources/prism.wasm")).instantiate();
+        var memory = wasmPrism.getMemory();
+        var calloc = wasmPrism.getExport("calloc");
+        var pmSerializeParse = wasmPrism.getExport("pm_serialize_parse");
+        var pmBufferInit = wasmPrism.getExport("pm_buffer_init");
+        var pmBufferSizeof = wasmPrism.getExport("pm_buffer_sizeof");
+        var pmBufferValue = wasmPrism.getExport("pm_buffer_value");
+        var pmBufferLength = wasmPrism.getExport("pm_buffer_length");
 
         // The Ruby source code to be processed
         var source = "1 + 1";
@@ -58,14 +58,14 @@ public class DummyTest {
 
     @Test
     public void test2() {
-        var wasmPrism = Module.builder("prism.wasm").build().instantiate();
-        var memory = wasmPrism.memory();
-        var calloc = wasmPrism.export("calloc");
-        var pmSerializeParse = wasmPrism.export("pm_serialize_parse");
-        var pmBufferInit = wasmPrism.export("pm_buffer_init");
-        var pmBufferSizeof = wasmPrism.export("pm_buffer_sizeof");
-        var pmBufferValue = wasmPrism.export("pm_buffer_value");
-        var pmBufferLength = wasmPrism.export("pm_buffer_length");
+        var wasmPrism = Module.build(new File("src/test/resources/prism.wasm")).instantiate();
+        var memory = wasmPrism.getMemory();
+        var calloc = wasmPrism.getExport("calloc");
+        var pmSerializeParse = wasmPrism.getExport("pm_serialize_parse");
+        var pmBufferInit = wasmPrism.getExport("pm_buffer_init");
+        var pmBufferSizeof = wasmPrism.getExport("pm_buffer_sizeof");
+        var pmBufferValue = wasmPrism.getExport("pm_buffer_value");
+        var pmBufferLength = wasmPrism.getExport("pm_buffer_length");
 
         // The Ruby source code to be processed
         var source = "puts \"h\ne\nl\nl\no\n\"";
diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb
index 60322585..b58253ef 100644
--- a/test/prism/errors_test.rb
+++ b/test/prism/errors_test.rb
@@ -1871,6 +1871,16 @@ module Prism
       ]
     end
 
+    def test_warnings_verbosity
+      warning = Prism.parse("def foo; END { }; end").warnings[0]
+      assert_equal "END in method; use at_exit", warning.message
+      assert_equal :warning_verbose_not_nil, warning.level
+
+      warning = Prism.parse("foo /regexp/").warnings[0]
+      assert_equal "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", warning.message
+      assert_equal :warning_verbose_true, warning.level
+    end
+
     def test_statement_operators
       source = <<~RUBY
         alias x y + 1
diff --git a/test/prism/fixtures/emoji_method_calls.txt b/test/prism/fixtures/emoji_method_calls.txt
deleted file mode 100644
index 96d0daba..00000000
--- a/test/prism/fixtures/emoji_method_calls.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-foo.🌊 = 1
diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb
index 1453e7ae..df391ca0 100644
--- a/test/prism/locals_test.rb
+++ b/test/prism/locals_test.rb
@@ -7,7 +7,7 @@
 #
 # There have also been changes made in other versions of Ruby, so we only want
 # to test on the most recent versions.
-return if !defined?(RubyVM::InstructionSequence) || RUBY_VERSION < "3.4.0"
+return if !defined?(RubyVM::InstructionSequence) || RUBY_VERSION < "3.2"
 
 # Omit tests if running on a 32-bit machine because there is a bug with how
 # Ruby is handling large ISeqs on 32-bit machines
@@ -57,6 +57,24 @@ module Prism
     # Dead code eliminated
     invalid << "whitequark/ruby_bug_10653.txt"
 
+    # case :a
+    # in Symbol(*lhs, x, *rhs)
+    # end
+    todos << "seattlerb/case_in.txt"
+
+    # <<~HERE
+    #   #{<<~THERE}
+    #   THERE
+    # HERE
+    todos << "seattlerb/heredoc_nested.txt"
+
+    # Ruby < 3.3.0 fails to parse:
+    #
+    # <<-'  HERE'
+    #  foo
+    #   HERE
+    invalid << "heredocs_leading_whitespace.txt" if RUBY_VERSION < "3.3.0"
+
     base = File.join(__dir__, "fixtures")
     skips = invalid | todos
 
diff --git a/test/prism/parse_test.rb b/test/prism/parse_test.rb
index 26243150..6c5d125c 100644
--- a/test/prism/parse_test.rb
+++ b/test/prism/parse_test.rb
@@ -87,7 +87,7 @@ module Prism
 
     relatives.each do |relative|
       # These fail on TruffleRuby due to a difference in Symbol#inspect: :测试 vs :"测试"
-      next if RUBY_ENGINE == "truffleruby" and %w[emoji_method_calls.txt seattlerb/bug202.txt seattlerb/magic_encoding_comment.txt].include?(relative)
+      next if RUBY_ENGINE == "truffleruby" and %w[seattlerb/bug202.txt seattlerb/magic_encoding_comment.txt].include?(relative)
 
       filepath = File.join(base, relative)
       snapshot = File.expand_path(File.join("snapshots", relative), __dir__)
diff --git a/test/prism/snapshots/emoji_method_calls.txt b/test/prism/snapshots/emoji_method_calls.txt
deleted file mode 100644
index a38d1431..00000000
--- a/test/prism/snapshots/emoji_method_calls.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-@ ProgramNode (location: (1,0)-(1,12))
-├── locals: []
-└── statements:
-    @ StatementsNode (location: (1,0)-(1,12))
-    └── body: (length: 1)
-        └── @ CallNode (location: (1,0)-(1,12))
-            ├── flags: attribute_write
-            ├── receiver:
-            │   @ CallNode (location: (1,0)-(1,3))
-            │   ├── flags: variable_call, ignore_visibility
-            │   ├── receiver: ∅
-            │   ├── call_operator_loc: ∅
-            │   ├── name: :foo
-            │   ├── message_loc: (1,0)-(1,3) = "foo"
-            │   ├── opening_loc: ∅
-            │   ├── arguments: ∅
-            │   ├── closing_loc: ∅
-            │   └── block: ∅
-            ├── call_operator_loc: (1,3)-(1,4) = "."
-            ├── name: :🌊=
-            ├── message_loc: (1,4)-(1,8) = "🌊"
-            ├── opening_loc: ∅
-            ├── arguments:
-            │   @ ArgumentsNode (location: (1,11)-(1,12))
-            │   ├── flags: ∅
-            │   └── arguments: (length: 1)
-            │       └── @ IntegerNode (location: (1,11)-(1,12))
-            │           └── flags: decimal
-            ├── closing_loc: ∅
-            └── block: ∅