[ruby/yarp] Disallow numbered parameters in multiple scopes

https://github.com/ruby/yarp/commit/5fd4d3b89a
This commit is contained in:
Kevin Newton 2023-09-13 11:39:09 -04:00 коммит произвёл git
Родитель a4b4ebc7c1
Коммит b5084877c0
5 изменённых файлов: 56 добавлений и 4 удалений

Просмотреть файл

@ -1206,11 +1206,18 @@ module YARP
]
end
def test_double_scope_numbered_parameters
source = "-> { _1 + -> { _2 } }"
errors = [["Numbered parameter is already used in outer scope", 15..17]]
assert_errors expression(source), source, errors, compare_ripper: false
end
private
def assert_errors(expected, source, errors)
def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby")
# Ripper behaves differently on JRuby/TruffleRuby, so only check this on CRuby
assert_nil Ripper.sexp_raw(source) if RUBY_ENGINE == "ruby"
assert_nil Ripper.sexp_raw(source) if compare_ripper
result = YARP.parse(source)
node = result.value.statements.body.last

Просмотреть файл

@ -193,6 +193,7 @@ static const char* const diagnostic_messages[YP_DIAGNOSTIC_ID_LEN] = {
[YP_ERR_PARAMETER_NO_DEFAULT] = "Expected a default value for the parameter",
[YP_ERR_PARAMETER_NO_DEFAULT_KW] = "Expected a default value for the keyword parameter",
[YP_ERR_PARAMETER_NUMBERED_RESERVED] = "Token reserved for a numbered parameter",
[YP_ERR_NUMBERED_PARAMETER_OUTER_SCOPE] = "Numbered parameter is already used in outer scope",
[YP_ERR_PARAMETER_ORDER] = "Unexpected parameter order",
[YP_ERR_PARAMETER_SPLAT_MULTI] = "Unexpected multiple `*` splat parameters",
[YP_ERR_PARAMETER_STAR] = "Unexpected parameter `*`",

Просмотреть файл

@ -151,6 +151,7 @@ typedef enum {
YP_ERR_NOT_EXPRESSION,
YP_ERR_NUMBER_LITERAL_UNDERSCORE,
YP_ERR_NUMBERED_PARAMETER_NOT_ALLOWED,
YP_ERR_NUMBERED_PARAMETER_OUTER_SCOPE,
YP_ERR_OPERATOR_MULTI_ASSIGN,
YP_ERR_OPERATOR_WRITE_BLOCK,
YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI,

Просмотреть файл

@ -288,6 +288,11 @@ typedef struct yp_scope {
// This is necessary to determine whether or not numbered parameters are
// allowed.
bool explicit_params;
// A boolean indicating whether or not this scope has numbered parameters.
// This is necessary to determine if child blocks are allowed to use
// numbered parameters.
bool numbered_params;
} yp_scope_t;
// This struct represents the overall parser. It contains a reference to the

Просмотреть файл

@ -4720,10 +4720,16 @@ yp_parser_scope_push(yp_parser_t *parser, bool closed) {
yp_scope_t *scope = (yp_scope_t *) malloc(sizeof(yp_scope_t));
if (scope == NULL) return false;
*scope = (yp_scope_t) { .previous = parser->current_scope, .closed = closed, .explicit_params = false };
yp_constant_id_list_init(&scope->locals);
*scope = (yp_scope_t) {
.previous = parser->current_scope,
.closed = closed,
.explicit_params = false,
.numbered_params = false
};
yp_constant_id_list_init(&scope->locals);
parser->current_scope = scope;
return true;
}
@ -10109,6 +10115,24 @@ parse_alias_argument(yp_parser_t *parser, bool first) {
}
}
// Return true if any of the visible scopes to the current context are using
// numbered parameters.
static bool
outer_scope_using_numbered_params_p(yp_parser_t *parser) {
yp_scope_t *scope = parser->current_scope;
// We can bail early here if we've already performed this lookup since it
// will be set on the current scope.
if (scope->numbered_params) return false;
// Otherwise we need to walk up the list of scopes.
for (scope = scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
if (scope->numbered_params) return true;
}
return false;
}
// Parse an identifier into either a local variable read or a call.
static yp_node_t *
parse_variable_call(yp_parser_t *parser) {
@ -10127,7 +10151,18 @@ parse_variable_call(yp_parser_t *parser) {
// node but add an error.
if (parser->current_scope->explicit_params) {
yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_NUMBERED_PARAMETER_NOT_ALLOWED);
} else if (outer_scope_using_numbered_params_p(parser)) {
yp_diagnostic_list_append(&parser->error_list, parser->previous.start, parser->previous.end, YP_ERR_NUMBERED_PARAMETER_OUTER_SCOPE);
} else {
// Indicate that this scope is using numbered params so that
// child scopes cannot.
parser->current_scope->numbered_params = true;
// When you use a numbered parameter, it implies the existence
// of all of the locals that exist before it. For example,
// referencing _2 means that _1 must exist. Therefore here we
// loop through all of the possibilities and add them into the
// constant pool.
uint8_t number = parser->previous.start[1];
uint8_t current = '1';
uint8_t *value;
@ -10139,6 +10174,9 @@ parse_variable_call(yp_parser_t *parser) {
yp_parser_local_add_owned(parser, value, 2);
}
// Now we can add the actual token that is being used. For
// this one we can add a shared version since it is directly
// referenced in the source.
yp_parser_local_add_token(parser, &parser->previous);
return (yp_node_t *) yp_local_variable_read_node_create(parser, &parser->previous, 0);
}