[ruby/prism] Fix support for 'it' implicit local variable

https://github.com/ruby/prism/commit/53bbcfe513
This commit is contained in:
Kevin Newton 2024-05-22 14:31:38 -04:00
Родитель 5613d6e95b
Коммит e575954887
10 изменённых файлов: 292 добавлений и 283 удалений

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

@ -1148,6 +1148,12 @@ module Prism
end
end
# -> { it }
# ^^
def visit_it_local_variable_read_node(node)
builder.ident([:it, srange(node.location)]).updated(:lvar)
end
# -> { it }
# ^^^^^^^^^
def visit_it_parameters_node(node)
@ -1201,14 +1207,7 @@ module Prism
# foo
# ^^^
def visit_local_variable_read_node(node)
name = node.name
# This is just a guess. parser doesn't have support for the implicit
# `it` variable yet, so we'll probably have to visit this once it
# does.
name = :it if name == :"0it"
builder.ident([name, srange(node.location)]).updated(:lvar)
builder.ident([node.name, srange(node.location)]).updated(:lvar)
end
# foo = 1

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

@ -2217,6 +2217,13 @@ module Prism
end
end
# -> { it }
# ^^
def visit_it_local_variable_read_node(node)
bounds(node.location)
on_vcall(on_ident(node.slice))
end
# -> { it }
# ^^^^^^^^^
def visit_it_parameters_node(node)
@ -2312,12 +2319,7 @@ module Prism
# ^^^
def visit_local_variable_read_node(node)
bounds(node.location)
if node.name == :"0it"
on_vcall(on_ident(node.slice))
else
on_var_ref(on_ident(node.slice))
end
on_var_ref(on_ident(node.slice))
end
# foo = 1

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

@ -916,6 +916,12 @@ module Prism
end
end
# -> { it }
# ^^
def visit_it_local_variable_read_node(node)
s(node, :call, nil, :it)
end
# foo(bar: baz)
# ^^^^^^^^
def visit_keyword_hash_node(node)

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

@ -2570,6 +2570,12 @@ nodes:
`foo #{bar} baz`
^^^^^^^^^^^^^^^^
- name: ItLocalVariableReadNode
comment: |
Represents reading from the implicit `it` local variable.
-> { it }
^^
- name: ItParametersNode
comment: |
Represents an implicit set of parameters through the use of the `it` keyword within a block or lambda.
@ -2695,10 +2701,6 @@ nodes:
_1 # name `:_1`
Finally, for the default `it` block parameter, the name is `0it`. This is to distinguish it from an `it` local variable that is explicitly declared.
it # name `:0it`
- name: depth
type: uint32
comment: |

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

@ -546,6 +546,17 @@ typedef struct pm_locals {
pm_local_t *locals;
} pm_locals_t;
/** The flags about scope parameters that can be set. */
typedef uint8_t pm_scope_parameters_t;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NONE = 0x0;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS = 0x1;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS = 0x2;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_BLOCK = 0x4;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x8;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED = 0x10;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NUMBERED_INNER = 0x20;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NUMBERED_FOUND = 0x40;
/**
* This struct represents a node in a linked list of scopes. Some scopes can see
* into their parent scopes, while others cannot.
@ -557,10 +568,19 @@ typedef struct pm_scope {
/** The IDs of the locals in the given scope. */
pm_locals_t locals;
/**
* This is a list of the implicit parameters contained within the block.
* These will be processed after the block is parsed to determine the kind
* of parameters node that should be used and to check if any errors need to
* be added.
*/
pm_node_list_t implicit_parameters;
/**
* This is a bitfield that indicates the parameters that are being used in
* this scope. It is a combination of the PM_SCOPE_PARAMS_* constants. There
* are three different kinds of parameters that can be used in a scope:
* this scope. It is a combination of the PM_SCOPE_PARAMETERS_* constants.
* There are three different kinds of parameters that can be used in a
* scope:
*
* - Ordinary parameters (e.g., def foo(bar); end)
* - Numbered parameters (e.g., def foo; _1; end)
@ -575,15 +595,7 @@ typedef struct pm_scope {
* - def foo(&); end
* - def foo(...); end
*/
uint8_t parameters;
/**
* An integer indicating the number of numbered parameters on this scope.
* This is necessary to determine if child blocks are allowed to use
* numbered parameters, and to pass information to consumers of the AST
* about how many numbered parameters exist.
*/
int8_t numbered_parameters;
pm_scope_parameters_t parameters;
/**
* The current state of constant shareability for this scope. This is
@ -598,21 +610,6 @@ typedef struct pm_scope {
bool closed;
} pm_scope_t;
static const uint8_t PM_SCOPE_PARAMETERS_NONE = 0x0;
static const uint8_t PM_SCOPE_PARAMETERS_ORDINARY = 0x1;
static const uint8_t PM_SCOPE_PARAMETERS_NUMBERED = 0x2;
static const uint8_t PM_SCOPE_PARAMETERS_IT = 0x4;
static const uint8_t PM_SCOPE_PARAMETERS_TYPE_MASK = 0x7;
static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS = 0x8;
static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS = 0x10;
static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_BLOCK = 0x20;
static const uint8_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x40;
static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_INNER = -2;
static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED = -1;
static const int8_t PM_SCOPE_NUMBERED_PARAMETERS_NONE = 0;
/**
* A struct that represents a stack of boolean values.
*/

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

@ -708,7 +708,7 @@ pm_parser_scope_push(pm_parser_t *parser, bool closed) {
.previous = parser->current_scope,
.locals = { 0 },
.parameters = PM_SCOPE_PARAMETERS_NONE,
.numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_NONE,
.implicit_parameters = { 0 },
.shareable_constant = (closed || parser->current_scope == NULL) ? PM_SCOPE_SHAREABLE_CONSTANT_NONE : parser->current_scope->shareable_constant,
.closed = closed
};
@ -4322,7 +4322,7 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) {
const uint8_t *point = memchr(start, '.', length);
assert(point && "should have a decimal point");
uint8_t *digits = malloc(length - 1);
uint8_t *digits = malloc(length);
if (digits == NULL) {
fputs("[pm_float_node_rational_create] Failed to allocate memory", stderr);
abort();
@ -4333,7 +4333,7 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) {
pm_integer_parse(&node->numerator, PM_INTEGER_BASE_DEFAULT, digits, digits + length - 1);
digits[0] = '1';
memset(digits + 1, '0', (size_t) (end - point - 1));
if (end - point > 1) memset(digits + 1, '0', (size_t) (end - point - 1));
pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + (end - point));
free(digits);
@ -5497,6 +5497,23 @@ pm_interpolated_xstring_node_closing_set(pm_interpolated_x_string_node_t *node,
node->base.location.end = closing->end;
}
/**
* Create a local variable read that is reading the implicit 'it' variable.
*/
static pm_it_local_variable_read_node_t *
pm_it_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) {
pm_it_local_variable_read_node_t *node = PM_ALLOC_NODE(parser, pm_it_local_variable_read_node_t);
*node = (pm_it_local_variable_read_node_t) {
{
.type = PM_IT_LOCAL_VARIABLE_READ_NODE,
.location = PM_LOCATION_TOKEN_VALUE(name)
}
};
return node;
}
/**
* Allocate and initialize a new ItParametersNode node.
*/
@ -5809,28 +5826,6 @@ pm_token_is_it(const uint8_t *start, const uint8_t *end) {
return (end - start == 2) && (start[0] == 'i') && (start[1] == 't');
}
/**
* Returns true if the given node is `it` default parameter.
*/
static inline bool
pm_node_is_it(pm_parser_t *parser, pm_node_t *node) {
// Check if it's a local variable reference
if (node->type != PM_CALL_NODE) {
return false;
}
// Check if it's a variable call
pm_call_node_t *call_node = (pm_call_node_t *) node;
if (!PM_NODE_FLAG_P(call_node, PM_CALL_NODE_FLAGS_VARIABLE_CALL)) {
return false;
}
// Check if it's called `it`
pm_constant_id_t id = ((pm_call_node_t *)node)->name;
pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, id);
return pm_token_is_it(constant->start, constant->start + constant->length);
}
/**
* Returns true if the given bounds comprise a numbered parameter (i.e., they
* are of the form /^_\d$/).
@ -7951,51 +7946,6 @@ pm_parser_local_add_constant(pm_parser_t *parser, const char *start, size_t leng
return constant_id;
}
/**
* Create a local variable read that is reading the implicit 'it' variable.
*/
static pm_local_variable_read_node_t *
pm_local_variable_read_node_create_it(pm_parser_t *parser, const pm_token_t *name) {
if (parser->current_scope->parameters & PM_SCOPE_PARAMETERS_ORDINARY) {
pm_parser_err_token(parser, name, PM_ERR_IT_NOT_ALLOWED_ORDINARY);
return NULL;
}
if (parser->current_scope->parameters & PM_SCOPE_PARAMETERS_NUMBERED) {
pm_parser_err_token(parser, name, PM_ERR_IT_NOT_ALLOWED_NUMBERED);
return NULL;
}
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_IT;
pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3);
pm_parser_local_add(parser, name_id, name->start, name->end, 0);
return pm_local_variable_read_node_create_constant_id(parser, name, name_id, 0, false);
}
/**
* Convert a `it` variable call node to a node for `it` default parameter.
*/
static pm_node_t *
pm_node_check_it(pm_parser_t *parser, pm_node_t *node) {
if (
(parser->version != PM_OPTIONS_VERSION_CRUBY_3_3) &&
!parser->current_scope->closed &&
(parser->current_scope->numbered_parameters != PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED) &&
pm_node_is_it(parser, node)
) {
pm_local_variable_read_node_t *read = pm_local_variable_read_node_create_it(parser, &parser->previous);
if (read != NULL) {
pm_node_destroy(parser, node);
node = (pm_node_t *) read;
}
}
return node;
}
/**
* Add a parameter name to the current scope and check whether the name of the
* parameter is unique or not.
@ -8031,6 +7981,7 @@ pm_parser_scope_pop(pm_parser_t *parser) {
pm_scope_t *scope = parser->current_scope;
parser->current_scope = scope->previous;
pm_locals_free(&scope->locals);
pm_node_list_free(&scope->implicit_parameters);
xfree(scope);
}
@ -13187,6 +13138,30 @@ parse_unwriteable_target(pm_parser_t *parser, pm_node_t *target) {
return (pm_node_t *) result;
}
/**
* When an implicit local variable is written to or targeted, it becomes a
* regular, named local variable. This function removes it from the list of
* implicit parameters when that happens.
*/
static void
parse_target_implicit_parameter(pm_parser_t *parser, pm_node_t *node) {
pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters;
for (size_t index = 0; index < implicit_parameters->size; index++) {
if (implicit_parameters->nodes[index] == node) {
// If the node is not the last one in the list, we need to shift the
// remaining nodes down to fill the gap. This is extremely unlikely
// to happen.
if (index != implicit_parameters->size - 1) {
memcpy(&implicit_parameters->nodes[index], &implicit_parameters->nodes[index + 1], (implicit_parameters->size - index - 1) * sizeof(pm_node_t *));
}
implicit_parameters->size--;
break;
}
}
}
/**
* Convert the given node into a valid target node.
*/
@ -13237,7 +13212,10 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple) {
target->type = PM_GLOBAL_VARIABLE_TARGET_NODE;
return target;
case PM_LOCAL_VARIABLE_READ_NODE: {
pm_refute_numbered_parameter(parser, target->location.start, target->location.end);
if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) {
PM_PARSER_ERR_FORMAT(parser, target->location.start, target->location.end, PM_ERR_PARAMETER_NUMBERED_RESERVED, target->location.start);
parse_target_implicit_parameter(parser, target);
}
const pm_local_variable_read_node_t *cast = (const pm_local_variable_read_node_t *) target;
uint32_t name = cast->name;
@ -13249,6 +13227,15 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple) {
return target;
}
case PM_IT_LOCAL_VARIABLE_READ_NODE: {
pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2);
pm_node_t *node = (pm_node_t *) pm_local_variable_target_node_create(parser, &target->location, name, 0);
parse_target_implicit_parameter(parser, target);
pm_node_destroy(parser, target);
return node;
}
case PM_INSTANCE_VARIABLE_READ_NODE:
assert(sizeof(pm_instance_variable_target_node_t) == sizeof(pm_instance_variable_read_node_t));
target->type = PM_INSTANCE_VARIABLE_TARGET_NODE;
@ -13410,8 +13397,9 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
pm_scope_t *scope = pm_parser_scope_find(parser, depth);
if (pm_token_is_numbered_parameter(target->location.start, target->location.end)) {
pm_diagnostic_id_t diag_id = scope->parameters > PM_SCOPE_NUMBERED_PARAMETERS_NONE ? PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED : PM_ERR_PARAMETER_NUMBERED_RESERVED;
pm_diagnostic_id_t diag_id = (scope->parameters & PM_SCOPE_PARAMETERS_NUMBERED_FOUND) ? PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED : PM_ERR_PARAMETER_NUMBERED_RESERVED;
PM_PARSER_ERR_FORMAT(parser, target->location.start, target->location.end, diag_id, target->location.start);
parse_target_implicit_parameter(parser, target);
}
pm_locals_unread(&scope->locals, name);
@ -13419,6 +13407,15 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod
return (pm_node_t *) pm_local_variable_write_node_create(parser, name, depth, value, &name_loc, operator);
}
case PM_IT_LOCAL_VARIABLE_READ_NODE: {
pm_constant_id_t name = pm_parser_local_add_constant(parser, "it", 2);
pm_node_t *node = (pm_node_t *) pm_local_variable_write_node_create(parser, name, 0, value, &target->location, operator);
parse_target_implicit_parameter(parser, target);
pm_node_destroy(parser, target);
return node;
}
case PM_INSTANCE_VARIABLE_READ_NODE: {
pm_node_t *write_node = (pm_node_t *) pm_instance_variable_write_node_create(parser, (pm_instance_variable_read_node_t *) target, operator, value);
pm_node_destroy(parser, target);
@ -14860,6 +14857,28 @@ parse_block_parameters(
return block_parameters;
}
/**
* Return true if any of the visible scopes to the current context are using
* numbered parameters.
*/
static bool
outer_scope_using_numbered_parameters_p(pm_parser_t *parser) {
for (pm_scope_t *scope = parser->current_scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
if (scope->parameters & PM_SCOPE_PARAMETERS_NUMBERED_FOUND) return true;
}
return false;
}
/**
* These are the names of the various numbered parameters. We have them here so
* that when we insert them into the constant pool we can use a constant string
* and not have to allocate.
*/
static const char * const pm_numbered_parameter_names[] = {
"_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9"
};
/**
* Return the node that should be used in the parameters field of a block-like
* (block or lambda) node, depending on the kind of parameters that were
@ -14867,31 +14886,79 @@ parse_block_parameters(
*/
static pm_node_t *
parse_blocklike_parameters(pm_parser_t *parser, pm_node_t *parameters, const pm_token_t *opening, const pm_token_t *closing) {
uint8_t masked = parser->current_scope->parameters & PM_SCOPE_PARAMETERS_TYPE_MASK;
pm_node_list_t *implicit_parameters = &parser->current_scope->implicit_parameters;
if (masked == PM_SCOPE_PARAMETERS_NONE) {
assert(parameters == NULL);
return NULL;
} else if (masked == PM_SCOPE_PARAMETERS_ORDINARY) {
assert(parameters != NULL);
return parameters;
} else if (masked == PM_SCOPE_PARAMETERS_NUMBERED) {
assert(parameters == NULL);
// If we have ordinary parameters, then we will return them as the set of
// parameters.
if (parameters != NULL) {
// If we also have implicit parameters, then this is an error.
if (implicit_parameters->size > 0) {
pm_node_t *node = implicit_parameters->nodes[0];
int8_t maximum = parser->current_scope->numbered_parameters;
if (maximum > 0) {
const pm_location_t location = { .start = opening->start, .end = closing->end };
return (pm_node_t *) pm_numbered_parameters_node_create(parser, &location, (uint8_t) maximum);
if (PM_NODE_TYPE_P(node, PM_LOCAL_VARIABLE_READ_NODE)) {
pm_parser_err_node(parser, node, PM_ERR_NUMBERED_PARAMETER_ORDINARY);
} else if (PM_NODE_TYPE_P(node, PM_IT_LOCAL_VARIABLE_READ_NODE)) {
pm_parser_err_node(parser, node, PM_ERR_IT_NOT_ALLOWED_ORDINARY);
} else {
assert(false && "unreachable");
}
}
return NULL;
} else if (masked == PM_SCOPE_PARAMETERS_IT) {
assert(parameters == NULL);
return (pm_node_t *) pm_it_parameters_node_create(parser, opening, closing);
} else {
assert(false && "unreachable");
return parameters;
}
// If we don't have any implicit parameters, then the set of parameters is
// NULL.
if (implicit_parameters->size == 0) {
return NULL;
}
// If we don't have ordinary parameters, then we now must validate our set
// of implicit parameters. We can only have numbered parameters or it, but
// they cannot be mixed.
uint8_t numbered_parameter = 0;
bool it_parameter = false;
for (size_t index = 0; index < implicit_parameters->size; index++) {
pm_node_t *node = implicit_parameters->nodes[index];
if (PM_NODE_TYPE_P(node, PM_LOCAL_VARIABLE_READ_NODE)) {
if (it_parameter) {
pm_parser_err_node(parser, node, PM_ERR_NUMBERED_PARAMETER_IT);
} else if (outer_scope_using_numbered_parameters_p(parser)) {
pm_parser_err_node(parser, node, PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK);
} else if (parser->current_scope->parameters & PM_SCOPE_PARAMETERS_NUMBERED_INNER) {
pm_parser_err_node(parser, node, PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK);
} else if (pm_token_is_numbered_parameter(node->location.start, node->location.end)) {
numbered_parameter = MAX(numbered_parameter, (uint8_t) (node->location.start[1] - '0'));
} else {
assert(false && "unreachable");
}
} else if (PM_NODE_TYPE_P(node, PM_IT_LOCAL_VARIABLE_READ_NODE)) {
if (numbered_parameter > 0) {
pm_parser_err_node(parser, node, PM_ERR_IT_NOT_ALLOWED_NUMBERED);
} else {
it_parameter = true;
}
}
}
if (numbered_parameter > 0) {
// Go through the parent scopes and mark them as being disallowed from
// using numbered parameters because this inner scope is using them.
for (pm_scope_t *scope = parser->current_scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED_INNER;
}
const pm_location_t location = { .start = opening->start, .end = closing->end };
return (pm_node_t *) pm_numbered_parameters_node_create(parser, &location, numbered_parameter);
}
if (it_parameter) {
return (pm_node_t *) pm_it_parameters_node_create(parser, opening, closing);
}
return NULL;
}
/**
@ -14908,9 +14975,6 @@ parse_block(pm_parser_t *parser) {
pm_block_parameters_node_t *block_parameters = NULL;
if (accept1(parser, PM_TOKEN_PIPE)) {
assert(parser->current_scope->parameters == PM_SCOPE_PARAMETERS_NONE);
parser->current_scope->parameters = PM_SCOPE_PARAMETERS_ORDINARY;
pm_token_t block_parameters_opening = parser->previous;
if (match1(parser, PM_TOKEN_PIPE)) {
block_parameters = pm_block_parameters_node_create(parser, NULL, &block_parameters_opening);
@ -15408,7 +15472,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context) {
#define PM_CASE_WRITABLE PM_CLASS_VARIABLE_READ_NODE: case PM_CONSTANT_PATH_NODE: \
case PM_CONSTANT_READ_NODE: case PM_GLOBAL_VARIABLE_READ_NODE: case PM_LOCAL_VARIABLE_READ_NODE: \
case PM_INSTANCE_VARIABLE_READ_NODE: case PM_MULTI_TARGET_NODE: case PM_BACK_REFERENCE_READ_NODE: \
case PM_NUMBERED_REFERENCE_READ_NODE
case PM_NUMBERED_REFERENCE_READ_NODE: case PM_IT_LOCAL_VARIABLE_READ_NODE
// Assert here that the flags are the same so that we can safely switch the type
// of the node without having to move the flags.
@ -15827,92 +15891,44 @@ parse_alias_argument(pm_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_parameters_p(pm_parser_t *parser) {
for (pm_scope_t *scope = parser->current_scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
if (scope->numbered_parameters > 0) return true;
}
return false;
}
/**
* These are the names of the various numbered parameters. We have them here so
* that when we insert them into the constant pool we can use a constant string
* and not have to allocate.
*/
static const char * const pm_numbered_parameter_names[] = {
"_1", "_2", "_3", "_4", "_5", "_6", "_7", "_8", "_9"
};
/**
* Parse an identifier into either a local variable read. If the local variable
* is not found, it returns NULL instead.
*/
static pm_local_variable_read_node_t *
static pm_node_t *
parse_variable(pm_parser_t *parser) {
pm_constant_id_t name_id = pm_parser_constant_id_token(parser, &parser->previous);
int depth;
if ((depth = pm_parser_local_depth(parser, &parser->previous)) != -1) {
return pm_local_variable_read_node_create(parser, &parser->previous, (uint32_t) depth);
if ((depth = pm_parser_local_depth_constant_id(parser, name_id)) != -1) {
return (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, (uint32_t) depth, false);
}
pm_scope_t *current_scope = parser->current_scope;
if (!current_scope->closed && current_scope->numbered_parameters != PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED && pm_token_is_numbered_parameter(parser->previous.start, parser->previous.end)) {
// Now that we know we have a numbered parameter, we need to check
// if it's allowed in this context. If it is, then we will create a
// local variable read. If it's not, then we'll create a normal call
// node but add an error.
if (current_scope->parameters & PM_SCOPE_PARAMETERS_ORDINARY) {
pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_ORDINARY);
} else if (current_scope->parameters & PM_SCOPE_PARAMETERS_IT) {
pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_IT);
} else if (outer_scope_using_numbered_parameters_p(parser)) {
pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK);
} else if (current_scope->numbered_parameters == PM_SCOPE_NUMBERED_PARAMETERS_INNER) {
pm_parser_err_previous(parser, PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK);
} else {
// Indicate that this scope is using numbered params so that child
// scopes cannot. We subtract the value for the character '0' to get
// the actual integer value of the number (only _1 through _9 are
// valid).
int8_t numbered_parameters = (int8_t) (parser->previous.start[1] - '0');
if (!current_scope->closed && !(current_scope->parameters & PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED)) {
if (pm_token_is_numbered_parameter(parser->previous.start, parser->previous.end)) {
// 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 maximum = (uint8_t) (parser->previous.start[1] - '0');
for (uint8_t number = 1; number <= maximum; number++) {
pm_parser_local_add_constant(parser, pm_numbered_parameter_names[number - 1], 2);
}
// If we're about to match an =, then this is an invalid use of
// numbered parameters. We'll create all of the necessary
// infrastructure around it, but not actually mark the scope as
// using numbered parameters so that we can get the right error
// message.
if (!match1(parser, PM_TOKEN_EQUAL)) {
current_scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED;
if (numbered_parameters > current_scope->numbered_parameters) {
current_scope->numbered_parameters = numbered_parameters;
}
// Go through the parent scopes and mark them as being
// disallowed from using numbered parameters because this inner
// scope is using them.
for (pm_scope_t *scope = current_scope->previous; scope != NULL && !scope->closed; scope = scope->previous) {
scope->numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_INNER;
}
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_NUMBERED_FOUND;
}
// 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.
for (int8_t numbered_param = 1; numbered_param <= numbered_parameters - 1; numbered_param++) {
pm_parser_local_add_constant(parser, pm_numbered_parameter_names[numbered_param - 1], 2);
}
pm_node_t *node = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false);
pm_node_list_append(&current_scope->implicit_parameters, node);
// Finally we can create the local variable read node.
pm_constant_id_t name_id = pm_parser_local_add_constant(parser, pm_numbered_parameter_names[numbered_parameters - 1], 2);
return pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false);
return node;
} else if ((parser->version != PM_OPTIONS_VERSION_CRUBY_3_3) && pm_token_is_it(parser->previous.start, parser->previous.end)) {
pm_node_t *node = (pm_node_t *) pm_it_local_variable_read_node_create(parser, &parser->previous);
pm_node_list_append(&current_scope->implicit_parameters, node);
return node;
}
}
@ -15927,8 +15943,8 @@ parse_variable_call(pm_parser_t *parser) {
pm_node_flags_t flags = 0;
if (!match1(parser, PM_TOKEN_PARENTHESIS_LEFT) && (parser->previous.end[-1] != '!') && (parser->previous.end[-1] != '?')) {
pm_local_variable_read_node_t *node = parse_variable(parser);
if (node != NULL) return (pm_node_t *) node;
pm_node_t *node = parse_variable(parser);
if (node != NULL) return node;
flags |= PM_CALL_NODE_FLAGS_VARIABLE_CALL;
}
@ -16616,19 +16632,8 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm
pm_node_t *variable = (pm_node_t *) parse_variable(parser);
if (variable == NULL) {
if (
(parser->version != PM_OPTIONS_VERSION_CRUBY_3_3) &&
!parser->current_scope->closed &&
(parser->current_scope->numbered_parameters != PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED) &&
pm_token_is_it(parser->previous.start, parser->previous.end)
) {
pm_local_variable_read_node_t *read = pm_local_variable_read_node_create_it(parser, &parser->previous);
if (read == NULL) read = pm_local_variable_read_node_create(parser, &parser->previous, 0);
variable = (pm_node_t *) read;
} else {
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE);
variable = (pm_node_t *) pm_local_variable_read_node_missing_create(parser, &parser->previous, 0);
}
PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE);
variable = (pm_node_t *) pm_local_variable_read_node_missing_create(parser, &parser->previous, 0);
}
return (pm_node_t *) pm_pinned_variable_node_create(parser, &operator, variable);
@ -17378,8 +17383,13 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
break;
}
if (pm_array_node_size(array) != 0) {
expect1(parser, PM_TOKEN_COMMA, PM_ERR_ARRAY_SEPARATOR);
// Ensure that we have a comma between elements in the array.
if ((pm_array_node_size(array) != 0) && !accept1(parser, PM_TOKEN_COMMA)) {
const uint8_t *location = parser->previous.end;
PM_PARSER_ERR_FORMAT(parser, location, location, PM_ERR_ARRAY_SEPARATOR, pm_token_type_human(parser->current.type));
parser->previous.start = location;
parser->previous.type = PM_TOKEN_MISSING;
}
// If we have a right bracket immediately following a comma,
@ -17807,8 +17817,28 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
) {
pm_arguments_t arguments = { 0 };
parse_arguments_list(parser, &arguments, true, accepts_command_call);
pm_call_node_t *fcall = pm_call_node_fcall_create(parser, &identifier, &arguments);
if (PM_NODE_TYPE_P(node, PM_IT_LOCAL_VARIABLE_READ_NODE)) {
// If we're about to convert an 'it' implicit local
// variable read into a method call, we need to remove
// it from the list of implicit local variables.
parse_target_implicit_parameter(parser, node);
} else {
// Otherwise, we're about to convert a regular local
// variable read into a method call, in which case we
// need to indicate that this was not a read for the
// purposes of warnings.
assert(PM_NODE_TYPE_P(node, PM_LOCAL_VARIABLE_READ_NODE));
if (pm_token_is_numbered_parameter(identifier.start, identifier.end)) {
parse_target_implicit_parameter(parser, node);
} else {
pm_local_variable_read_node_t *cast = (pm_local_variable_read_node_t *) node;
pm_locals_unread(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name);
}
}
pm_node_destroy(parser, node);
return (pm_node_t *) fcall;
}
@ -17816,31 +17846,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
if ((binding_power == PM_BINDING_POWER_STATEMENT) && match1(parser, PM_TOKEN_COMMA)) {
node = parse_targets_validate(parser, node, PM_BINDING_POWER_INDEX);
} else {
// Check if `it` is not going to be assigned.
switch (parser->current.type) {
case PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL:
case PM_TOKEN_AMPERSAND_EQUAL:
case PM_TOKEN_CARET_EQUAL:
case PM_TOKEN_EQUAL:
case PM_TOKEN_GREATER_GREATER_EQUAL:
case PM_TOKEN_LESS_LESS_EQUAL:
case PM_TOKEN_MINUS_EQUAL:
case PM_TOKEN_PARENTHESIS_RIGHT:
case PM_TOKEN_PERCENT_EQUAL:
case PM_TOKEN_PIPE_EQUAL:
case PM_TOKEN_PIPE_PIPE_EQUAL:
case PM_TOKEN_PLUS_EQUAL:
case PM_TOKEN_SLASH_EQUAL:
case PM_TOKEN_STAR_EQUAL:
case PM_TOKEN_STAR_STAR_EQUAL:
break;
default:
// Once we know it's neither a method call nor an
// assignment, we can finally create `it` default
// parameter.
node = pm_node_check_it(parser, node);
}
}
return node;
@ -19702,9 +19707,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
switch (parser->current.type) {
case PM_TOKEN_PARENTHESIS_LEFT: {
assert(parser->current_scope->parameters == PM_SCOPE_PARAMETERS_NONE);
parser->current_scope->parameters = PM_SCOPE_PARAMETERS_ORDINARY;
pm_token_t opening = parser->current;
parser_lex(parser);
@ -19721,9 +19723,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b
break;
}
case PM_CASE_PARAMETER: {
assert(parser->current_scope->parameters == PM_SCOPE_PARAMETERS_NONE);
parser->current_scope->parameters = PM_SCOPE_PARAMETERS_ORDINARY;
pm_accepts_block_stack_push(parser, false);
pm_token_t opening = not_provided(parser);
block_parameters = parse_block_parameters(parser, false, &opening, true);
@ -21258,7 +21257,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm
// Scopes given from the outside are not allowed to have numbered
// parameters.
parser->current_scope->numbered_parameters = PM_SCOPE_NUMBERED_PARAMETERS_DISALLOWED;
parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED;
for (size_t local_index = 0; local_index < scope->locals_count; local_index++) {
const pm_string_t *local = pm_options_scope_local_get(scope, local_index);

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

@ -112,7 +112,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
[PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_EXPRESSION_AFTER_STAR] = { "expected an expression after `*` in the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_SEPARATOR] = { "expected a `,` separator for the array elements", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_SEPARATOR] = { "unexpected %s; expected a `,` separator for the array elements", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_TERM] = { "expected a `]` to close the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_LONELY_ELSE] = { "unexpected `else` in `begin` block; else without rescue is useless", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_SYNTAX },
@ -245,7 +245,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
[PM_ERR_INVALID_VARIABLE_GLOBAL_3_3] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_VARIABLE_GLOBAL] = { "'%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_YIELD] = { "Invalid yield", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "`it` is not allowed when an numbered parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "`it` is not allowed when a numbered parameter is already used", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_IT_NOT_ALLOWED_ORDINARY] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX },
@ -269,7 +269,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
[PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK] = { "numbered parameter is already used in inner block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when an 'it' parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when 'it' is already used", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_ORDINARY] = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK] = { "numbered parameter is already used in outer block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_OPERATOR_MULTI_ASSIGN] = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_SYNTAX },

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

@ -352,7 +352,7 @@ pm_token_type_human(pm_token_type_t token_type) {
case PM_TOKEN_USTAR:
return "*";
case PM_TOKEN_USTAR_STAR:
return "'**'";
return "**";
case PM_TOKEN_WORDS_SEP:
return "string separator";
case PM_TOKEN___END__:

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

@ -1356,14 +1356,14 @@ module Prism
if RUBY_VERSION >= "3.0"
def test_writing_numbered_parameter
assert_errors expression("-> { _1 = 0 }"), "-> { _1 = 0 }", [
["_1 is reserved for numbered parameters", 5..7]
assert_error_messages "-> { _1 = 0 }", [
"_1 is reserved for numbered parameters"
]
end
def test_targeting_numbered_parameter
assert_errors expression("-> { _1, = 0 }"), "-> { _1, = 0 }", [
["_1 is reserved for numbered parameters", 5..7]
assert_error_messages "-> { _1, = 0 }", [
"_1 is reserved for numbered parameters"
]
end

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

@ -175,14 +175,6 @@ module Prism
assert_location(CallNode, "foo bar baz")
assert_location(CallNode, "foo bar('baz')")
assert_location(CallNode, "-> { it }", 5...7, version: "3.3.0") do |node|
node.body.body.first
end
assert_location(LocalVariableReadNode, "-> { it }", 5...7, version: "3.4.0") do |node|
node.body.body.first
end
end
def test_CallAndWriteNode
@ -543,6 +535,24 @@ module Prism
assert_location(InterpolatedXStringNode, '`foo #{bar} baz`')
end
def test_ItLocalVariableReadNode
assert_location(ItLocalVariableReadNode, "-> { it }", 5...7) do |node|
node.body.body.first
end
assert_location(ItLocalVariableReadNode, "foo { it }", 6...8) do |node|
node.block.body.body.first
end
assert_location(CallNode, "-> { it }", 5...7, version: "3.3.0") do |node|
node.body.body.first
end
assert_location(ItLocalVariableReadNode, "-> { it }", 5...7, version: "3.4.0") do |node|
node.body.body.first
end
end
def test_ItParametersNode
assert_location(ItParametersNode, "-> { it }", &:parameters)
end
@ -583,12 +593,6 @@ module Prism
def test_LocalVariableReadNode
assert_location(LocalVariableReadNode, "foo = 1; foo", 9...12)
assert_location(LocalVariableReadNode, "-> { it }", 5...7) do |node|
node.body.body.first
end
assert_location(LocalVariableReadNode, "foo { it }", 6...8) do |node|
node.block.body.body.first
end
end
def test_LocalVariableTargetNode