Bump Prism to v1.5.2

[Backport #21187]
This commit is contained in:
Takashi Kokubun 2025-12-08 14:56:06 -08:00
parent d0b778cd19
commit d282e76fb6
27 changed files with 216 additions and 56 deletions

View File

@ -7,17 +7,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa
Kernel.prepend(
Module.new {
def warn(*msgs, uplevel: nil, category: nil) # :nodoc:
uplevel =
case uplevel
when nil
1
when Integer
uplevel + 1
else
uplevel.to_int + 1
end
super(*msgs, uplevel: uplevel)
case uplevel
when nil
super(*msgs)
when Integer
super(*msgs, uplevel: uplevel + 1)
else
super(*msgs, uplevel: uplevel.to_int + 1)
end
end
}
)
@ -25,17 +22,14 @@ if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.pa
Object.prepend(
Module.new {
def warn(*msgs, uplevel: nil, category: nil) # :nodoc:
uplevel =
case uplevel
when nil
1
when Integer
uplevel + 1
else
uplevel.to_int + 1
end
super(*msgs, uplevel: uplevel)
case uplevel
when nil
super(*msgs)
when Integer
super(*msgs, uplevel: uplevel + 1)
else
super(*msgs, uplevel: uplevel.to_int + 1)
end
end
}
)

View File

@ -2,7 +2,7 @@
Gem::Specification.new do |spec|
spec.name = "prism"
spec.version = "1.5.1"
spec.version = "1.5.2"
spec.authors = ["Shopify"]
spec.email = ["ruby@shopify.com"]

View File

@ -19,6 +19,13 @@ module Prism
# whitequark/parser gem's syntax tree. It inherits from the base parser for
# the parser gem, and overrides the parse* methods to parse with prism and
# then translate.
#
# Note that this version of the parser always parses using the latest
# version of Ruby syntax supported by Prism. If you want specific version
# support, use one of the version-specific subclasses, such as
# `Prism::Translation::Parser34`. If you want to parse using the same
# version of Ruby syntax as the currently running version of Ruby, use
# `Prism::Translation::ParserCurrent`.
class Parser < ::Parser::Base
Diagnostic = ::Parser::Diagnostic # :nodoc:
private_constant :Diagnostic
@ -77,7 +84,7 @@ module Prism
end
def version # :nodoc:
34
35
end
# The default encoding for Ruby files is UTF-8.

View File

@ -152,7 +152,7 @@ module Prism
# ^^
# ```
def visit_back_reference_read_node(node)
s(node, :back_ref, node.name.name.delete_prefix("$").to_sym)
s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym)
end
# ```

View File

@ -60,6 +60,7 @@ errors:
- CONDITIONAL_WHILE_PREDICATE
- CONSTANT_PATH_COLON_COLON_CONSTANT
- DEF_ENDLESS
- DEF_ENDLESS_PARAMETERS
- DEF_ENDLESS_SETTER
- DEF_NAME
- DEF_PARAMS_TERM
@ -1800,7 +1801,7 @@ nodes:
Represents the predicate of the case statement. This can be either `nil` or any [non-void expressions](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#non-void-expression).
case true; when false; end
^^^^
^^^^
- name: conditions
type: node[]
kind: WhenNode

View File

@ -1,7 +1,7 @@
#ifndef PRISM_EXT_NODE_H
#define PRISM_EXT_NODE_H
#define EXPECTED_PRISM_VERSION "1.5.1"
#define EXPECTED_PRISM_VERSION "1.5.2"
#include <ruby.h>
#include <ruby/encoding.h>

View File

@ -2622,10 +2622,11 @@ pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argument
// There are certain flags that we want to use internally but don't want to
// expose because they are not relevant beyond parsing. Therefore we'll define
// them here and not define them in config.yml/a header file.
static const pm_node_flags_t PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY = 0x4;
static const pm_node_flags_t PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY = 0x40;
static const pm_node_flags_t PM_CALL_NODE_FLAGS_COMPARISON = 0x80;
static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = 0x100;
static const pm_node_flags_t PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY = (1 << 2);
static const pm_node_flags_t PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY = ((PM_CALL_NODE_FLAGS_LAST - 1) << 1);
static const pm_node_flags_t PM_CALL_NODE_FLAGS_COMPARISON = ((PM_CALL_NODE_FLAGS_LAST - 1) << 2);
static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = ((PM_CALL_NODE_FLAGS_LAST - 1) << 3);
/**
* Allocate and initialize a new CallNode node. This sets everything to NULL or
@ -5279,6 +5280,12 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_
switch (PM_NODE_TYPE(part)) {
case PM_STRING_NODE:
// If inner string is not frozen, it stops being a static literal. We should *not* clear other flags,
// because concatenating two frozen strings (`'foo' 'bar'`) is still frozen. This holds true for
// as long as this interpolation only consists of other string literals.
if (!PM_NODE_FLAG_P(part, PM_STRING_FLAGS_FROZEN)) {
pm_node_flag_unset((pm_node_t *) node, PM_NODE_FLAG_STATIC_LITERAL);
}
part->flags = (pm_node_flags_t) ((part->flags | PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN) & ~PM_STRING_FLAGS_MUTABLE);
break;
case PM_INTERPOLATED_STRING_NODE:
@ -14443,6 +14450,17 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for
if (accepted_newline) {
pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
}
// If this is a command call and an argument takes a block,
// there can be no further arguments. For example,
// `foo(bar 1 do end, 2)` should be rejected.
if (PM_NODE_TYPE_P(argument, PM_CALL_NODE)) {
pm_call_node_t *call = (pm_call_node_t *) argument;
if (call->opening_loc.start == NULL && call->arguments != NULL && call->block != NULL) {
pm_parser_err_previous(parser, PM_ERR_INVALID_COMMA);
break;
}
}
} else {
// If there is no comma at the end of the argument list then we're
// done parsing arguments and can break out of this loop.
@ -14594,6 +14612,18 @@ update_parameter_state(pm_parser_t *parser, pm_token_t *token, pm_parameters_ord
return true;
}
/**
* Ensures that after parsing a parameter, the next token is not `=`.
* Some parameters like `def(* = 1)` cannot become optional. When no parens
* are present like in `def * = 1`, this creates ambiguity with endless method definitions.
*/
static inline void
refute_optional_parameter(pm_parser_t *parser) {
if (match1(parser, PM_TOKEN_EQUAL)) {
pm_parser_err_previous(parser, PM_ERR_DEF_ENDLESS_PARAMETERS);
}
}
/**
* Parse a list of parameters on a method definition.
*/
@ -14646,6 +14676,10 @@ parse_parameters(
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK;
}
if (!uses_parentheses) {
refute_optional_parameter(parser);
}
pm_block_parameter_node_t *param = pm_block_parameter_node_create(parser, &name, &operator);
if (repeated) {
pm_node_flag_set_repeated_parameter((pm_node_t *)param);
@ -14667,6 +14701,10 @@ parse_parameters(
bool succeeded = update_parameter_state(parser, &parser->current, &order);
parser_lex(parser);
if (!uses_parentheses) {
refute_optional_parameter(parser);
}
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL;
pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous);
@ -14848,6 +14886,10 @@ parse_parameters(
context_pop(parser);
pm_parameters_node_keywords_append(params, param);
if (!uses_parentheses) {
refute_optional_parameter(parser);
}
// If parsing the value of the parameter resulted in error recovery,
// then we can put a missing node in its place and stop parsing the
// parameters entirely now.
@ -14879,6 +14921,10 @@ parse_parameters(
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS;
}
if (!uses_parentheses) {
refute_optional_parameter(parser);
}
pm_node_t *param = (pm_node_t *) pm_rest_parameter_node_create(parser, &operator, &name);
if (repeated) {
pm_node_flag_set_repeated_parameter(param);
@ -14927,6 +14973,10 @@ parse_parameters(
}
}
if (!uses_parentheses) {
refute_optional_parameter(parser);
}
if (params->keyword_rest == NULL) {
pm_parameters_node_keyword_rest_set(params, param);
} else {
@ -18491,20 +18541,28 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
return (pm_node_t *) node;
}
case PM_TOKEN_CHARACTER_LITERAL: {
parser_lex(parser);
pm_token_t opening = parser->previous;
opening.type = PM_TOKEN_STRING_BEGIN;
opening.end = opening.start + 1;
pm_token_t content = parser->previous;
content.type = PM_TOKEN_STRING_CONTENT;
content.start = content.start + 1;
pm_token_t closing = not_provided(parser);
pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(parser, &opening, &content, &closing);
pm_node_t *node = (pm_node_t *) pm_string_node_create_current_string(
parser,
&(pm_token_t) {
.type = PM_TOKEN_STRING_BEGIN,
.start = parser->current.start,
.end = parser->current.start + 1
},
&(pm_token_t) {
.type = PM_TOKEN_STRING_CONTENT,
.start = parser->current.start + 1,
.end = parser->current.end
},
&closing
);
pm_node_flag_set(node, parse_unescaped_encoding(parser));
// Skip past the character literal here, since now we have handled
// parser->explicit_encoding correctly.
parser_lex(parser);
// Characters can be followed by strings in which case they are
// automatically concatenated.
if (match1(parser, PM_TOKEN_STRING_BEGIN)) {
@ -20901,7 +20959,7 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding
bool permitted = true;
if (previous_binding_power != PM_BINDING_POWER_STATEMENT && match1(parser, PM_TOKEN_USTAR)) permitted = false;
pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id, (uint16_t) (depth + 1));
pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MODIFIER, diag_id, (uint16_t) (depth + 1));
if (!permitted) pm_parser_err_node(parser, value, PM_ERR_UNEXPECTED_MULTI_WRITE);
parse_assignment_value_local(parser, value);
@ -22498,9 +22556,10 @@ parse_program(pm_parser_t *parser) {
statements = wrap_statements(parser, statements);
} else {
flush_block_exits(parser, previous_block_exits);
pm_node_list_free(&current_block_exits);
}
pm_node_list_free(&current_block_exits);
// If this is an empty file, then we're still going to parse all of the
// statements in order to gather up all of the comments and such. Here we'll
// correct the location information.

View File

@ -212,6 +212,8 @@ typedef enum pm_<%= flag.human %> {
/** <%= value.comment %> */
PM_<%= flag.human.upcase %>_<%= value.name %> = <%= 1 << (index + Prism::Template::COMMON_FLAGS_COUNT) %>,
<%- end -%>
PM_<%= flag.human.upcase %>_LAST,
} pm_<%= flag.human %>_t;
<%- end -%>

View File

@ -14,7 +14,7 @@ module Prism
# The patch version of prism that we are expecting to find in the serialized
# strings.
PATCH_VERSION = 1
PATCH_VERSION = 2
# Deserialize the dumped output from a request to parse or parse_file.
#

View File

@ -144,6 +144,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
[PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_ENDLESS_PARAMETERS] = { "could not parse the endless method parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX },

View File

@ -1,5 +1,7 @@
#include "prism/util/pm_string.h"
static const uint8_t empty_source[] = "";
/**
* Returns the size of the pm_string_t struct. This is necessary to allocate the
* correct amount of memory in the FFI backend.
@ -133,8 +135,7 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
// the source to a constant empty string and return.
if (file_size == 0) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = empty_source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
@ -182,8 +183,7 @@ pm_string_mapped_init(pm_string_t *string, const char *filepath) {
if (size == 0) {
close(fd);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = empty_source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
@ -225,8 +225,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) {
// the source to a constant empty string and return.
if (file_size == 0) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = empty_source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
@ -278,8 +277,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) {
size_t size = (size_t) sb.st_size;
if (size == 0) {
close(fd);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = empty_source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}

View File

@ -19,11 +19,11 @@
/**
* The patch version of the Prism library as an int.
*/
#define PRISM_VERSION_PATCH 1
#define PRISM_VERSION_PATCH 2
/**
* The version of the Prism library as a constant string.
*/
#define PRISM_VERSION "1.5.1"
#define PRISM_VERSION "1.5.2"
#endif

View File

@ -1,3 +1,10 @@
[a b]
^ unexpected local variable or method; expected a `,` separator for the array elements
[
a b do
^ unexpected local variable or method; expected a `,` separator for the array elements
end,
]

View File

@ -0,0 +1,6 @@
1 if foo = bar baz
^~~ unexpected local variable or method, expecting end-of-input
1 and foo = bar baz
^~~ unexpected local variable or method, expecting end-of-input

View File

@ -0,0 +1,24 @@
foo(bar 1 do end, 2)
^ invalid comma
^ unexpected integer; expected a `)` to close the arguments
^ unexpected integer, expecting end-of-input
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it
foo(bar 1 do end,)
^ invalid comma
foo(1, bar 2 do end)
^ unexpected integer; expected a `)` to close the arguments
^ unexpected integer, expecting end-of-input
^~ unexpected 'do', expecting end-of-input
^~ unexpected 'do', ignoring it
^~~ unexpected 'end', ignoring it
^ unexpected ')', ignoring it
foo(1, bar 2)
^ unexpected integer; expected a `)` to close the arguments
^ unexpected integer, expecting end-of-input
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it

View File

@ -0,0 +1,6 @@
def foo(*bar = nil); end
^ unexpected '='; expected a `)` to close the parameters
^ unexpected ')', expecting end-of-input
^ unexpected ')', ignoring it
^~~ unexpected 'end', ignoring it

View File

@ -0,0 +1,24 @@
def f x: = 1
^~ could not parse the endless method parameters
def f ... = 1
^~~ could not parse the endless method parameters
def f * = 1
^ could not parse the endless method parameters
def f ** = 1
^~ could not parse the endless method parameters
def f & = 1
^ could not parse the endless method parameters
def f *a = 1
^ could not parse the endless method parameters
def f **a = 1
^ could not parse the endless method parameters
def f &a = 1
^ could not parse the endless method parameters

View File

@ -0,0 +1,2 @@
# encoding: Windows-31J
p ?\u3042""

View File

@ -0,0 +1,3 @@
foo(bar baz do end)
foo(bar baz, bat)

View File

@ -0,0 +1,5 @@
# frozen_string_literal: false
'foo' 'bar'
'foo' 'bar' "baz#{bat}"

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
'foo' 'bar'
'foo' 'bar' "baz#{bat}"

View File

@ -27,6 +27,8 @@ module Prism
# Leaving these out until they are supported by parse.y.
except << "leading_logical.txt"
except << "endless_methods_command_call.txt"
# https://bugs.ruby-lang.org/issues/21168#note-5
except << "command_method_call_2.txt"
Fixture.each(except: except) do |fixture|
define_method(fixture.test_name) { assert_valid_syntax(fixture.read) }

View File

@ -48,6 +48,9 @@ module Prism
# https://bugs.ruby-lang.org/issues/17398#note-12
except << "endless_methods_command_call.txt"
# https://bugs.ruby-lang.org/issues/21168#note-5
except << "command_method_call_2.txt"
Fixture.each(except: except) do |fixture|
define_method(fixture.test_name) { assert_lex(fixture) }
end

View File

@ -33,7 +33,8 @@ module Prism
# Leaving these out until they are supported by parse.y.
"leading_logical.txt",
"endless_methods_command_call.txt"
"endless_methods_command_call.txt",
"command_method_call_2.txt"
]
Fixture.each(except: except) do |fixture|

View File

@ -70,6 +70,9 @@ module Prism
# Ruby >= 3.5 specific syntax
"endless_methods_command_call.txt",
# https://bugs.ruby-lang.org/issues/21168#note-5
"command_method_call_2.txt",
]
# These files contain code that is being parsed incorrectly by the parser

View File

@ -33,6 +33,9 @@ module Prism
# https://bugs.ruby-lang.org/issues/17398#note-12
"endless_methods_command_call.txt",
# https://bugs.ruby-lang.org/issues/21168#note-5
"command_method_call_2.txt",
]
# Skip these tests that we haven't implemented yet.

View File

@ -16,6 +16,7 @@ end
module Prism
class RubyParserTest < TestCase
todos = [
"character_literal.txt",
"encoding_euc_jp.txt",
"regex_char_width.txt",
"seattlerb/masgn_colon3.txt",
@ -78,6 +79,9 @@ module Prism
# Ruby >= 3.5 specific syntax
"endless_methods_command_call.txt",
# https://bugs.ruby-lang.org/issues/21168#note-5
"command_method_call_2.txt",
]
Fixture.each(except: failures) do |fixture|