diff --git a/lib/yarp.rb b/lib/yarp.rb index b81c988177..c1043c3232 100644 --- a/lib/yarp.rb +++ b/lib/yarp.rb @@ -440,10 +440,6 @@ module YARP # arguments. We get rid of that here. names = names.grep_v(Integer) - # TODO: We don't support numbered local variables yet, so we get rid - # of those here. - names = names.grep_v(/^_\d$/) - # For some reason, CRuby occasionally pushes this special local # variable when there are splat arguments. We get rid of that here. names = names.grep_v(:"#arg_rest") diff --git a/test/yarp/snapshots/unparser/corpus/literal/block.txt b/test/yarp/snapshots/unparser/corpus/literal/block.txt index bf3b680639..85f6f8fd2d 100644 --- a/test/yarp/snapshots/unparser/corpus/literal/block.txt +++ b/test/yarp/snapshots/unparser/corpus/literal/block.txt @@ -1337,39 +1337,25 @@ ├── closing_loc: ∅ ├── block: │ @ BlockNode (location: (724...737)) - │ ├── locals: [] + │ ├── locals: [:_1, :_2] │ ├── parameters: ∅ │ ├── body: │ │ @ StatementsNode (location: (728...735)) │ │ └── body: (length: 1) │ │ └── @ CallNode (location: (728...735)) │ │ ├── receiver: - │ │ │ @ CallNode (location: (728...730)) - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── message_loc: (728...730) = "_1" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ ├── block: ∅ - │ │ │ ├── flags: variable_call - │ │ │ └── name: "_1" + │ │ │ @ LocalVariableReadNode (location: (728...730)) + │ │ │ ├── name: :_1 + │ │ │ └── depth: 0 │ │ ├── call_operator_loc: ∅ │ │ ├── message_loc: (731...732) = "+" │ │ ├── opening_loc: ∅ │ │ ├── arguments: │ │ │ @ ArgumentsNode (location: (733...735)) │ │ │ └── arguments: (length: 1) - │ │ │ └── @ CallNode (location: (733...735)) - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── message_loc: (733...735) = "_2" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ ├── block: ∅ - │ │ │ ├── flags: variable_call - │ │ │ └── name: "_2" + │ │ │ └── @ LocalVariableReadNode (location: (733...735)) + │ │ │ ├── name: :_2 + │ │ │ └── depth: 0 │ │ ├── closing_loc: ∅ │ │ ├── block: ∅ │ │ ├── flags: ∅ diff --git a/test/yarp/snapshots/unparser/corpus/literal/since/27.txt b/test/yarp/snapshots/unparser/corpus/literal/since/27.txt index 05bc6fda5d..78ecbff556 100644 --- a/test/yarp/snapshots/unparser/corpus/literal/since/27.txt +++ b/test/yarp/snapshots/unparser/corpus/literal/since/27.txt @@ -4,7 +4,7 @@ @ StatementsNode (location: (0...22)) └── body: (length: 2) ├── @ LambdaNode (location: (0...16)) - │ ├── locals: [] + │ ├── locals: [:_1, :_2] │ ├── operator_loc: (0...2) = "->" │ ├── opening_loc: (3...4) = "{" │ ├── closing_loc: (15...16) = "}" @@ -14,32 +14,18 @@ │ └── body: (length: 1) │ └── @ CallNode (location: (7...14)) │ ├── receiver: - │ │ @ CallNode (location: (7...9)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (7...9) = "_1" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_1" + │ │ @ LocalVariableReadNode (location: (7...9)) + │ │ ├── name: :_1 + │ │ └── depth: 0 │ ├── call_operator_loc: ∅ │ ├── message_loc: (10...11) = "+" │ ├── opening_loc: ∅ │ ├── arguments: │ │ @ ArgumentsNode (location: (12...14)) │ │ └── arguments: (length: 1) - │ │ └── @ CallNode (location: (12...14)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (12...14) = "_2" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_2" + │ │ └── @ LocalVariableReadNode (location: (12...14)) + │ │ ├── name: :_2 + │ │ └── depth: 0 │ ├── closing_loc: ∅ │ ├── block: ∅ │ ├── flags: ∅ diff --git a/test/yarp/snapshots/whitequark/numbered_args_after_27.txt b/test/yarp/snapshots/whitequark/numbered_args_after_27.txt index c1cbe3c75f..97e39435d3 100644 --- a/test/yarp/snapshots/whitequark/numbered_args_after_27.txt +++ b/test/yarp/snapshots/whitequark/numbered_args_after_27.txt @@ -4,7 +4,7 @@ @ StatementsNode (location: (0...65)) └── body: (length: 4) ├── @ LambdaNode (location: (0...17)) - │ ├── locals: [] + │ ├── locals: [:_1, :_2, :_3, :_4, :_5, :_6, :_7, :_8, :_9] │ ├── operator_loc: (0...2) = "->" │ ├── opening_loc: (3...5) = "do" │ ├── closing_loc: (14...17) = "end" @@ -14,38 +14,24 @@ │ └── body: (length: 1) │ └── @ CallNode (location: (6...13)) │ ├── receiver: - │ │ @ CallNode (location: (6...8)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (6...8) = "_1" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_1" + │ │ @ LocalVariableReadNode (location: (6...8)) + │ │ ├── name: :_1 + │ │ └── depth: 0 │ ├── call_operator_loc: ∅ │ ├── message_loc: (9...10) = "+" │ ├── opening_loc: ∅ │ ├── arguments: │ │ @ ArgumentsNode (location: (11...13)) │ │ └── arguments: (length: 1) - │ │ └── @ CallNode (location: (11...13)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (11...13) = "_9" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_9" + │ │ └── @ LocalVariableReadNode (location: (11...13)) + │ │ ├── name: :_9 + │ │ └── depth: 0 │ ├── closing_loc: ∅ │ ├── block: ∅ │ ├── flags: ∅ │ └── name: "+" ├── @ LambdaNode (location: (19...32)) - │ ├── locals: [] + │ ├── locals: [:_1, :_2, :_3, :_4, :_5, :_6, :_7, :_8, :_9] │ ├── operator_loc: (19...21) = "->" │ ├── opening_loc: (22...23) = "{" │ ├── closing_loc: (31...32) = "}" @@ -55,32 +41,18 @@ │ └── body: (length: 1) │ └── @ CallNode (location: (24...31)) │ ├── receiver: - │ │ @ CallNode (location: (24...26)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (24...26) = "_1" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_1" + │ │ @ LocalVariableReadNode (location: (24...26)) + │ │ ├── name: :_1 + │ │ └── depth: 0 │ ├── call_operator_loc: ∅ │ ├── message_loc: (27...28) = "+" │ ├── opening_loc: ∅ │ ├── arguments: │ │ @ ArgumentsNode (location: (29...31)) │ │ └── arguments: (length: 1) - │ │ └── @ CallNode (location: (29...31)) - │ │ ├── receiver: ∅ - │ │ ├── call_operator_loc: ∅ - │ │ ├── message_loc: (29...31) = "_9" - │ │ ├── opening_loc: ∅ - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: ∅ - │ │ ├── block: ∅ - │ │ ├── flags: variable_call - │ │ └── name: "_9" + │ │ └── @ LocalVariableReadNode (location: (29...31)) + │ │ ├── name: :_9 + │ │ └── depth: 0 │ ├── closing_loc: ∅ │ ├── block: ∅ │ ├── flags: ∅ @@ -94,39 +66,25 @@ │ ├── closing_loc: ∅ │ ├── block: │ │ @ BlockNode (location: (36...50)) - │ │ ├── locals: [] + │ │ ├── locals: [:_1, :_2, :_3, :_4, :_5, :_6, :_7, :_8, :_9] │ │ ├── parameters: ∅ │ │ ├── body: │ │ │ @ StatementsNode (location: (39...46)) │ │ │ └── body: (length: 1) │ │ │ └── @ CallNode (location: (39...46)) │ │ │ ├── receiver: - │ │ │ │ @ CallNode (location: (39...41)) - │ │ │ │ ├── receiver: ∅ - │ │ │ │ ├── call_operator_loc: ∅ - │ │ │ │ ├── message_loc: (39...41) = "_1" - │ │ │ │ ├── opening_loc: ∅ - │ │ │ │ ├── arguments: ∅ - │ │ │ │ ├── closing_loc: ∅ - │ │ │ │ ├── block: ∅ - │ │ │ │ ├── flags: variable_call - │ │ │ │ └── name: "_1" + │ │ │ │ @ LocalVariableReadNode (location: (39...41)) + │ │ │ │ ├── name: :_1 + │ │ │ │ └── depth: 0 │ │ │ ├── call_operator_loc: ∅ │ │ │ ├── message_loc: (42...43) = "+" │ │ │ ├── opening_loc: ∅ │ │ │ ├── arguments: │ │ │ │ @ ArgumentsNode (location: (44...46)) │ │ │ │ └── arguments: (length: 1) - │ │ │ │ └── @ CallNode (location: (44...46)) - │ │ │ │ ├── receiver: ∅ - │ │ │ │ ├── call_operator_loc: ∅ - │ │ │ │ ├── message_loc: (44...46) = "_9" - │ │ │ │ ├── opening_loc: ∅ - │ │ │ │ ├── arguments: ∅ - │ │ │ │ ├── closing_loc: ∅ - │ │ │ │ ├── block: ∅ - │ │ │ │ ├── flags: variable_call - │ │ │ │ └── name: "_9" + │ │ │ │ └── @ LocalVariableReadNode (location: (44...46)) + │ │ │ │ ├── name: :_9 + │ │ │ │ └── depth: 0 │ │ │ ├── closing_loc: ∅ │ │ │ ├── block: ∅ │ │ │ ├── flags: ∅ @@ -144,39 +102,25 @@ ├── closing_loc: ∅ ├── block: │ @ BlockNode (location: (54...65)) - │ ├── locals: [] + │ ├── locals: [:_1, :_2, :_3, :_4, :_5, :_6, :_7, :_8, :_9] │ ├── parameters: ∅ │ ├── body: │ │ @ StatementsNode (location: (56...63)) │ │ └── body: (length: 1) │ │ └── @ CallNode (location: (56...63)) │ │ ├── receiver: - │ │ │ @ CallNode (location: (56...58)) - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── message_loc: (56...58) = "_1" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ ├── block: ∅ - │ │ │ ├── flags: variable_call - │ │ │ └── name: "_1" + │ │ │ @ LocalVariableReadNode (location: (56...58)) + │ │ │ ├── name: :_1 + │ │ │ └── depth: 0 │ │ ├── call_operator_loc: ∅ │ │ ├── message_loc: (59...60) = "+" │ │ ├── opening_loc: ∅ │ │ ├── arguments: │ │ │ @ ArgumentsNode (location: (61...63)) │ │ │ └── arguments: (length: 1) - │ │ │ └── @ CallNode (location: (61...63)) - │ │ │ ├── receiver: ∅ - │ │ │ ├── call_operator_loc: ∅ - │ │ │ ├── message_loc: (61...63) = "_9" - │ │ │ ├── opening_loc: ∅ - │ │ │ ├── arguments: ∅ - │ │ │ ├── closing_loc: ∅ - │ │ │ ├── block: ∅ - │ │ │ ├── flags: variable_call - │ │ │ └── name: "_9" + │ │ │ └── @ LocalVariableReadNode (location: (61...63)) + │ │ │ ├── name: :_9 + │ │ │ └── depth: 0 │ │ ├── closing_loc: ∅ │ │ ├── block: ∅ │ │ ├── flags: ∅ diff --git a/test/yarp/snapshots/whitequark/ruby_bug_15789.txt b/test/yarp/snapshots/whitequark/ruby_bug_15789.txt index d2ff89848a..d0069b5650 100644 --- a/test/yarp/snapshots/whitequark/ruby_bug_15789.txt +++ b/test/yarp/snapshots/whitequark/ruby_bug_15789.txt @@ -28,7 +28,7 @@ │ │ │ │ │ ├── operator_loc: (7...8) = "=" │ │ │ │ │ └── value: │ │ │ │ │ @ LambdaNode (location: (9...15)) - │ │ │ │ │ ├── locals: [] + │ │ │ │ │ ├── locals: [:_1] │ │ │ │ │ ├── operator_loc: (9...11) = "->" │ │ │ │ │ ├── opening_loc: (11...12) = "{" │ │ │ │ │ ├── closing_loc: (14...15) = "}" @@ -36,16 +36,9 @@ │ │ │ │ │ └── body: │ │ │ │ │ @ StatementsNode (location: (12...14)) │ │ │ │ │ └── body: (length: 1) - │ │ │ │ │ └── @ CallNode (location: (12...14)) - │ │ │ │ │ ├── receiver: ∅ - │ │ │ │ │ ├── call_operator_loc: ∅ - │ │ │ │ │ ├── message_loc: (12...14) = "_1" - │ │ │ │ │ ├── opening_loc: ∅ - │ │ │ │ │ ├── arguments: ∅ - │ │ │ │ │ ├── closing_loc: ∅ - │ │ │ │ │ ├── block: ∅ - │ │ │ │ │ ├── flags: variable_call - │ │ │ │ │ └── name: "_1" + │ │ │ │ │ └── @ LocalVariableReadNode (location: (12...14)) + │ │ │ │ │ ├── name: :_1 + │ │ │ │ │ └── depth: 0 │ │ │ │ ├── rest: ∅ │ │ │ │ ├── posts: (length: 0) │ │ │ │ ├── keywords: (length: 0) @@ -91,7 +84,7 @@ │ │ │ │ ├── name_loc: (27...29) = "a:" │ │ │ │ └── value: │ │ │ │ @ LambdaNode (location: (30...36)) - │ │ │ │ ├── locals: [] + │ │ │ │ ├── locals: [:_1] │ │ │ │ ├── operator_loc: (30...32) = "->" │ │ │ │ ├── opening_loc: (32...33) = "{" │ │ │ │ ├── closing_loc: (35...36) = "}" @@ -99,16 +92,9 @@ │ │ │ │ └── body: │ │ │ │ @ StatementsNode (location: (33...35)) │ │ │ │ └── body: (length: 1) - │ │ │ │ └── @ CallNode (location: (33...35)) - │ │ │ │ ├── receiver: ∅ - │ │ │ │ ├── call_operator_loc: ∅ - │ │ │ │ ├── message_loc: (33...35) = "_1" - │ │ │ │ ├── opening_loc: ∅ - │ │ │ │ ├── arguments: ∅ - │ │ │ │ ├── closing_loc: ∅ - │ │ │ │ ├── block: ∅ - │ │ │ │ ├── flags: variable_call - │ │ │ │ └── name: "_1" + │ │ │ │ └── @ LocalVariableReadNode (location: (33...35)) + │ │ │ │ ├── name: :_1 + │ │ │ │ └── depth: 0 │ │ │ ├── keyword_rest: ∅ │ │ │ └── block: ∅ │ │ ├── locals: (length: 0) diff --git a/yarp/diagnostic.c b/yarp/diagnostic.c index 7fd658d53d..55e16e6c10 100644 --- a/yarp/diagnostic.c +++ b/yarp/diagnostic.c @@ -184,8 +184,9 @@ static const char* const diagnostic_messages[YP_DIAGNOSTIC_ID_LEN] = { [YP_ERR_MULTI_ASSIGN_MULTI_SPLATS] = "Multiple splats in multiple assignment", [YP_ERR_NOT_EXPRESSION] = "Expected an expression after `not`", [YP_ERR_NUMBER_LITERAL_UNDERSCORE] = "Number literal ending with a `_`", - [YP_ERR_OPERATOR_WRITE_BLOCK] = "Unexpected operator after a call with a block", + [YP_ERR_NUMBERED_PARAMETER_NOT_ALLOWED] = "Numbered parameters are not allowed alongside explicit parameters", [YP_ERR_OPERATOR_MULTI_ASSIGN] = "Unexpected operator for a multiple assignment", + [YP_ERR_OPERATOR_WRITE_BLOCK] = "Unexpected operator after a call with a block", [YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = "Unexpected multiple `**` splat parameters", [YP_ERR_PARAMETER_BLOCK_MULTI] = "Multiple block parameters; only one block is allowed", [YP_ERR_PARAMETER_NAME_REPEAT] = "Repeated parameter name", diff --git a/yarp/diagnostic.h b/yarp/diagnostic.h index 9a8975f061..07b5790072 100644 --- a/yarp/diagnostic.h +++ b/yarp/diagnostic.h @@ -150,6 +150,7 @@ typedef enum { YP_ERR_MULTI_ASSIGN_MULTI_SPLATS, YP_ERR_NOT_EXPRESSION, YP_ERR_NUMBER_LITERAL_UNDERSCORE, + YP_ERR_NUMBERED_PARAMETER_NOT_ALLOWED, YP_ERR_OPERATOR_MULTI_ASSIGN, YP_ERR_OPERATOR_WRITE_BLOCK, YP_ERR_PARAMETER_ASSOC_SPLAT_MULTI, diff --git a/yarp/util/yp_constant_pool.c b/yarp/util/yp_constant_pool.c index 8be96138a1..80d14fb8f2 100644 --- a/yarp/util/yp_constant_pool.c +++ b/yarp/util/yp_constant_pool.c @@ -107,10 +107,10 @@ yp_constant_pool_init(yp_constant_pool_t *pool, size_t capacity) { } // Insert a constant into a constant pool and return its index in the pool. -static size_t -yp_constant_pool_insert(yp_constant_pool_t *pool, const uint8_t *start, size_t length) { +static inline yp_constant_id_t +yp_constant_pool_insert(yp_constant_pool_t *pool, const uint8_t *start, size_t length, bool owned) { if (pool->size >= (pool->capacity / 4 * 3)) { - if (!yp_constant_pool_resize(pool)) return pool->capacity; + if (!yp_constant_pool_resize(pool)) return 0; } size_t hash = yp_constant_pool_hash(start, length); @@ -122,7 +122,24 @@ yp_constant_pool_insert(yp_constant_pool_t *pool, const uint8_t *start, size_t l // same as the content we are trying to insert. If it is, then we can // return the id of the existing constant. if ((constant->length == length) && memcmp(constant->start, start, length) == 0) { - return index; + // Since we have found a match, we need to check if this is + // attempting to insert a shared or an owned constant. We want to + // prefer shared constants since they don't require allocations. + if (owned) { + // If we're attempting to insert an owned constant and we have + // an existing constant, then either way we don't want the given + // memory. Either it's duplicated with the existing constant or + // it's not necessary because we have a shared version. + free((void *) start); + } else if (constant->owned) { + // If we're attempting to insert a shared constant and the + // existing constant is owned, then we can free the owned + // constant and replace it with the shared constant. + free((void *) constant->start); + constant->start = start; + } + + return constant->id; } index = (index + 1) % pool->capacity; @@ -131,22 +148,22 @@ yp_constant_pool_insert(yp_constant_pool_t *pool, const uint8_t *start, size_t l pool->size++; assert(pool->size < ((size_t) (1 << 31))); - pool->constants[index] = (yp_constant_t) { + *constant = (yp_constant_t) { .id = (unsigned int) (pool->size & 0x7FFFFFFF), + .owned = owned & 0x1, .start = start, .length = length, .hash = hash }; - return index; + return constant->id; } // Insert a constant into a constant pool. Returns the id of the constant, or 0 // if any potential calls to resize fail. yp_constant_id_t yp_constant_pool_insert_shared(yp_constant_pool_t *pool, const uint8_t *start, size_t length) { - size_t index = yp_constant_pool_insert(pool, start, length); - return index == pool->capacity ? 0 : ((yp_constant_id_t) pool->constants[index].id); + return yp_constant_pool_insert(pool, start, length, false); } // Insert a constant into a constant pool from memory that is now owned by the @@ -154,12 +171,7 @@ yp_constant_pool_insert_shared(yp_constant_pool_t *pool, const uint8_t *start, s // resize fail. yp_constant_id_t yp_constant_pool_insert_owned(yp_constant_pool_t *pool, const uint8_t *start, size_t length) { - size_t index = yp_constant_pool_insert(pool, start, length); - if (index == pool->capacity) return 0; - - yp_constant_t *constant = &pool->constants[index]; - constant->owned = true; - return ((yp_constant_id_t) constant->id); + return yp_constant_pool_insert(pool, start, length, true); } // Free the memory associated with a constant pool. diff --git a/yarp/yarp.c b/yarp/yarp.c index fb7c400311..36ca8dc726 100644 --- a/yarp/yarp.c +++ b/yarp/yarp.c @@ -10110,6 +10110,30 @@ parse_variable_call(yp_parser_t *parser) { return (yp_node_t *) yp_local_variable_read_node_create(parser, &parser->previous, (uint32_t) depth); } + if (!parser->current_scope->closed && 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 (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 { + uint8_t number = parser->previous.start[1]; + uint8_t current = '1'; + uint8_t *value; + + while (current < number) { + value = malloc(2); + value[0] = '_'; + value[1] = current++; + yp_parser_local_add_owned(parser, value, 2); + } + + yp_parser_local_add_token(parser, &parser->previous); + return (yp_node_t *) yp_local_variable_read_node_create(parser, &parser->previous, 0); + } + } + flags |= YP_CALL_NODE_FLAGS_VARIABLE_CALL; }