From ae0c7faa79029ebe615487d23c8ee1ca44ce4a36 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 24 Jun 2024 11:32:58 -0700 Subject: [PATCH] Handle hash and splat nodes in defined? This supports the nodes in both in the parse.y and prism compilers. Fixes [Bug #20043] Co-authored-by: Kevin Newton --- compile.c | 30 ++++++++++---- prism_compile.c | 84 ++++++++++++++++++++++++++++++++++----- test/ruby/test_defined.rb | 35 ++++++++++++++++ 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/compile.c b/compile.c index fee2fafc8d..62b5bbc490 100644 --- a/compile.c +++ b/compile.c @@ -5859,17 +5859,22 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, expr_type = DEFINED_FALSE; break; + case NODE_HASH: case NODE_LIST:{ - const NODE *vals = node; + const NODE *vals = (nd_type(node) == NODE_HASH) ? RNODE_HASH(node)->nd_head : node; - do { - defined_expr0(iseq, ret, RNODE_LIST(vals)->nd_head, lfinish, Qfalse, false); + if (vals) { + do { + if (RNODE_LIST(vals)->nd_head) { + defined_expr0(iseq, ret, RNODE_LIST(vals)->nd_head, lfinish, Qfalse, false); - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(line); - } - ADD_INSNL(ret, line_node, branchunless, lfinish[1]); - } while ((vals = RNODE_LIST(vals)->nd_next) != NULL); + if (!lfinish[1]) { + lfinish[1] = NEW_LABEL(line); + } + ADD_INSNL(ret, line_node, branchunless, lfinish[1]); + } + } while ((vals = RNODE_LIST(vals)->nd_next) != NULL); + } } /* fall through */ case NODE_STR: @@ -5889,6 +5894,15 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, expr_type = DEFINED_EXPR; break; + case NODE_SPLAT: + defined_expr0(iseq, ret, RNODE_LIST(node)->nd_head, lfinish, Qfalse, false); + if (!lfinish[1]) { + lfinish[1] = NEW_LABEL(line); + } + ADD_INSNL(ret, line_node, branchunless, lfinish[1]); + expr_type = DEFINED_EXPR; + break; + /* variables */ case NODE_LVAR: case NODE_DVAR: diff --git a/prism_compile.c b/prism_compile.c index 3aeb9d0e8e..08e2c4cc85 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -3443,19 +3443,83 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_c dtype = DEFINED_FALSE; break; case PM_ARRAY_NODE: { - const pm_array_node_t *cast = (const pm_array_node_t *) node; + const pm_array_node_t *cast = (const pm_array_node_t *) node; - if (!PM_NODE_FLAG_P(cast, PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT)) { - for (size_t index = 0; index < cast->elements.size; index++) { - pm_compile_defined_expr0(iseq, cast->elements.nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); + if (cast->elements.size > 0 && !lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } - if (!lfinish[1]) { - lfinish[1] = NEW_LABEL(location.line); - } + for (size_t index = 0; index < cast->elements.size; index++) { + pm_compile_defined_expr0(iseq, cast->elements.nodes[index], node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + } - PUSH_INSNL(ret, location, branchunless, lfinish[1]); + dtype = DEFINED_EXPR; + break; + } + case PM_HASH_NODE: + case PM_KEYWORD_HASH_NODE: { + const pm_node_list_t *elements; + + if (PM_NODE_TYPE_P(node, PM_HASH_NODE)) { + elements = &((const pm_hash_node_t *) node)->elements; + } + else { + elements = &((const pm_keyword_hash_node_t *) node)->elements; + } + + if (elements->size > 0 && !lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } + + for (size_t index = 0; index < elements->size; index++) { + const pm_node_t *element = elements->nodes[index]; + + switch (PM_NODE_TYPE(element)) { + case PM_ASSOC_NODE: { + const pm_assoc_node_t *assoc = (const pm_assoc_node_t *) element; + + pm_compile_defined_expr0(iseq, assoc->key, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + + pm_compile_defined_expr0(iseq, assoc->value, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + + break; } - } + case PM_ASSOC_SPLAT_NODE: { + const pm_assoc_splat_node_t *assoc_splat = (const pm_assoc_splat_node_t *) element; + + pm_compile_defined_expr0(iseq, assoc_splat->value, node_location, ret, popped, scope_node, true, lfinish, false); + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + + break; + } + default: + rb_bug("unexpected node type in hash node: %s", pm_node_type_to_str(PM_NODE_TYPE(element))); + break; + } + } + + dtype = DEFINED_EXPR; + break; + } + case PM_SPLAT_NODE: { + const pm_splat_node_t *cast = (const pm_splat_node_t *) node; + pm_compile_defined_expr0(iseq, cast->expression, node_location, ret, popped, scope_node, in_condition, lfinish, false); + + if (!lfinish[1]) { + lfinish[1] = NEW_LABEL(location.line); + } + + PUSH_INSNL(ret, location, branchunless, lfinish[1]); + dtype = DEFINED_EXPR; + break; + } + case PM_IMPLICIT_NODE: { + const pm_implicit_node_t *cast = (const pm_implicit_node_t *) node; + pm_compile_defined_expr0(iseq, cast->value, node_location, ret, popped, scope_node, in_condition, lfinish, explicit_receiver); + return; } case PM_AND_NODE: case PM_BEGIN_NODE: @@ -3467,7 +3531,6 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_c case PM_DEFINED_NODE: case PM_FLOAT_NODE: case PM_FOR_NODE: - case PM_HASH_NODE: case PM_IF_NODE: case PM_IMAGINARY_NODE: case PM_INTEGER_NODE: @@ -3475,7 +3538,6 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, const pm_line_c case PM_INTERPOLATED_STRING_NODE: case PM_INTERPOLATED_SYMBOL_NODE: case PM_INTERPOLATED_X_STRING_NODE: - case PM_KEYWORD_HASH_NODE: case PM_LAMBDA_NODE: case PM_MATCH_PREDICATE_NODE: case PM_MATCH_REQUIRED_NODE: diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index 0505bdada6..3a8065d959 100644 --- a/test/ruby/test_defined.rb +++ b/test/ruby/test_defined.rb @@ -139,6 +139,41 @@ class TestDefined < Test::Unit::TestCase assert_equal("assignment", eval('defined?(A::B &&= 1)')) end + def test_defined_splat + assert_nil(defined?([*a])) + assert_nil(defined?(itself(*a))) + assert_equal("expression", defined?([*itself])) + assert_equal("method", defined?(itself(*itself))) + end + + def test_defined_hash + assert_nil(defined?({a: a})) + assert_nil(defined?({a => 1})) + assert_nil(defined?({a => a})) + assert_nil(defined?({**a})) + assert_nil(defined?(itself(a: a))) + assert_nil(defined?(itself(a => 1))) + assert_nil(defined?(itself(a => a))) + assert_nil(defined?(itself(**a))) + assert_nil(defined?(itself({a: a}))) + assert_nil(defined?(itself({a => 1}))) + assert_nil(defined?(itself({a => a}))) + assert_nil(defined?(itself({**a}))) + + assert_equal("expression", defined?({a: itself})) + assert_equal("expression", defined?({itself => 1})) + assert_equal("expression", defined?({itself => itself})) + assert_equal("expression", defined?({**itself})) + assert_equal("method", defined?(itself(a: itself))) + assert_equal("method", defined?(itself(itself => 1))) + assert_equal("method", defined?(itself(itself => itself))) + assert_equal("method", defined?(itself(**itself))) + assert_equal("method", defined?(itself({a: itself}))) + assert_equal("method", defined?(itself({itself => 1}))) + assert_equal("method", defined?(itself({itself => itself}))) + assert_equal("method", defined?(itself({**itself}))) + end + def test_defined_literal assert_equal("nil", defined?(nil)) assert_equal("true", defined?(true))