summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Patterson <[email protected]>2023-12-05 14:07:50 -0800
committerAaron Patterson <[email protected]>2023-12-07 14:07:09 -0800
commit9d696aa20461d94c2d32e1e474bd036ade20c94d (patch)
tree9cf6a89ad9e87a3b01c94e776990f75b26759d90
parentf76881c9af4256d984c0cd59bc1f6929134eded0 (diff)
Support method calls inside `defined?`
This commit supports all kinds of method calls (including methods with parameters) inside `defined?` calls.
-rw-r--r--prism_compile.c217
-rw-r--r--test/ruby/test_compile_prism.rb27
2 files changed, 181 insertions, 63 deletions
diff --git a/prism_compile.c b/prism_compile.c
index 24a102cd6b..d4ee1b4be3 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -1465,12 +1465,29 @@ pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_node_
}
}
+static void pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node);
+
void
-pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition, LABEL **lfinish)
+pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition, LABEL **lfinish, bool explicit_receiver)
{
// in_condition is the same as compile.c's needstr
enum defined_type dtype = DEFINED_NOT_DEFINED;
switch (PM_NODE_TYPE(node)) {
+ case PM_ARGUMENTS_NODE: {
+ const pm_arguments_node_t *cast = (pm_arguments_node_t *) node;
+ const pm_node_list_t *arguments = &cast->arguments;
+ for (size_t idx = 0; idx < arguments->size; idx++) {
+ const pm_node_t *argument = arguments->nodes[idx];
+ pm_compile_defined_expr0(iseq, argument, ret, src, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, explicit_receiver);
+
+ if (!lfinish[1]) {
+ lfinish[1] = NEW_LABEL(lineno);
+ }
+ ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
+ }
+ dtype = DEFINED_TRUE;
+ break;
+ }
case PM_NIL_NODE:
dtype = DEFINED_NIL;
break;
@@ -1494,16 +1511,16 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co
dtype = DEFINED_FALSE;
break;
case PM_ARRAY_NODE: {
- pm_array_node_t *array_node = (pm_array_node_t *) node;
- if (!(array_node->base.flags & PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT)) {
- for (size_t index = 0; index < array_node->elements.size; index++) {
- pm_compile_defined_expr0(iseq, array_node->elements.nodes[index], ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish);
- if (!lfinish[1]) {
- lfinish[1] = NEW_LABEL(lineno);
+ pm_array_node_t *array_node = (pm_array_node_t *) node;
+ if (!(array_node->base.flags & PM_ARRAY_NODE_FLAGS_CONTAINS_SPLAT)) {
+ for (size_t index = 0; index < array_node->elements.size; index++) {
+ pm_compile_defined_expr0(iseq, array_node->elements.nodes[index], ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish, false);
+ if (!lfinish[1]) {
+ lfinish[1] = NEW_LABEL(lineno);
+ }
+ ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
}
- ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
- }
- }
+ }
}
case PM_AND_NODE:
case PM_FLOAT_NODE:
@@ -1586,10 +1603,11 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co
if (!lfinish[1]) {
lfinish[1] = NEW_LABEL(lineno);
}
- pm_compile_defined_expr0(iseq, constant_path_node->parent, ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish);
+ pm_compile_defined_expr0(iseq, constant_path_node->parent, ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish, false);
ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
PM_COMPILE(constant_path_node->parent);
- } else {
+ }
+ else {
ADD_INSN1(ret, &dummy_line_node, putobject, rb_cObject);
}
ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_CONST_FROM),
@@ -1597,6 +1615,51 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co
return;
}
+ case PM_CALL_NODE: {
+ pm_call_node_t *call_node = ((pm_call_node_t *)node);
+ ID method_id = pm_constant_id_lookup(scope_node, call_node->name);
+
+ if (call_node->receiver || call_node->arguments) {
+ if (!lfinish[1]) {
+ lfinish[1] = NEW_LABEL(lineno);
+ }
+ if (!lfinish[2]) {
+ lfinish[2] = NEW_LABEL(lineno);
+ }
+ }
+
+ if (call_node->arguments) {
+ pm_compile_defined_expr0(iseq, (const pm_node_t *)call_node->arguments, ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish, false);
+ ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
+ }
+
+ if (call_node->receiver) {
+ pm_compile_defined_expr0(iseq, call_node->receiver, ret, src, popped, scope_node, dummy_line_node, lineno, true, lfinish, true);
+ if (PM_NODE_TYPE_P(call_node->receiver, PM_CALL_NODE)) {
+ ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[2]);
+ pm_compile_call(iseq, (const pm_call_node_t *)call_node->receiver, ret, src, popped, scope_node);
+ }
+ else {
+ ADD_INSNL(ret, &dummy_line_node, branchunless, lfinish[1]);
+ PM_COMPILE(call_node->receiver);
+ }
+
+ if (explicit_receiver) {
+ PM_DUP;
+ }
+
+ ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_METHOD), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD));
+ }
+ else {
+ ADD_INSN(ret, &dummy_line_node, putself);
+ if (explicit_receiver) {
+ PM_DUP;
+ }
+ ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_FUNC), rb_id2sym(method_id), PUSH_VAL(DEFINED_METHOD));
+ }
+ return;
+ }
+
case PM_YIELD_NODE:
PM_PUTNIL;
ADD_INSN3(ret, &dummy_line_node, defined, INT2FIX(DEFINED_YIELD), 0,
@@ -1646,26 +1709,20 @@ pm_compile_defined_expr0(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *co
#undef PUSH_VAL
}
-void
-pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition)
+static void
+pm_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition, LABEL **lfinish, bool explicit_receiver)
{
- LABEL *lfinish[2];
- LINK_ELEMENT *last = ret->last;
+ LINK_ELEMENT *lcur = ret->last;
- lfinish[0] = NEW_LABEL(lineno);
- lfinish[1] = 0;
-
- if (!popped) {
- pm_compile_defined_expr0(iseq, node, ret, src, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish);
- }
+ pm_compile_defined_expr0(iseq, node, ret, src, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, false);
if (lfinish[1]) {
- struct rb_iseq_new_with_callback_callback_func *ifunc =
- rb_iseq_new_with_callback_new_callback(build_defined_rescue_iseq, NULL);
-
LABEL *lstart = NEW_LABEL(lineno);
LABEL *lend = NEW_LABEL(lineno);
+ struct rb_iseq_new_with_callback_callback_func *ifunc =
+ rb_iseq_new_with_callback_new_callback(build_defined_rescue_iseq, NULL);
+
const rb_iseq_t *rescue = new_child_iseq_with_callback(iseq, ifunc,
rb_str_concat(rb_str_new2("defined guard in "),
ISEQ_BODY(iseq)->location.label),
@@ -1674,17 +1731,85 @@ pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *con
lstart->rescued = LABEL_RESCUE_BEG;
lend->rescued = LABEL_RESCUE_END;
+ APPEND_LABEL(ret, lcur, lstart);
+ ADD_LABEL(ret, lend);
+ ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]);
+ }
+}
+
+void
+pm_compile_defined_expr(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node, NODE dummy_line_node, int lineno, bool in_condition)
+{
+ LABEL *lfinish[3];
+ LINK_ELEMENT *last = ret->last;
+
+ lfinish[0] = NEW_LABEL(lineno);
+ lfinish[1] = 0;
+ lfinish[2] = 0;
+
+ if (!popped) {
+ pm_defined_expr(iseq, node, ret, src, popped, scope_node, dummy_line_node, lineno, in_condition, lfinish, false);
+ }
+
+ if (lfinish[1]) {
ELEM_INSERT_NEXT(last, &new_insn_body(iseq, &dummy_line_node, BIN(putnil), 0)->link);
ADD_INSN(ret, &dummy_line_node, swap);
+ if (lfinish[2]) {
+ ADD_LABEL(ret, lfinish[2]);
+ }
ADD_INSN(ret, &dummy_line_node, pop);
ADD_LABEL(ret, lfinish[1]);
- APPEND_LABEL(ret, last, lstart);
- ADD_LABEL(ret, lend);
- ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]);
+
}
ADD_LABEL(ret, lfinish[0]);
}
+static void
+pm_compile_call(rb_iseq_t *iseq, const pm_call_node_t *call_node, LINK_ANCHOR *const ret, const uint8_t *src, bool popped, pm_scope_node_t *scope_node)
+{
+ pm_parser_t *parser = scope_node->parser;
+ pm_newline_list_t newline_list = parser->newline_list;
+ int lineno = (int)pm_newline_list_line_column(&newline_list, ((pm_node_t *)call_node)->location.start).line;
+ NODE dummy_line_node = generate_dummy_line_node(lineno, lineno);
+
+ ID method_id = pm_constant_id_lookup(scope_node, call_node->name);
+ int flags = 0;
+ struct rb_callinfo_kwarg *kw_arg = NULL;
+
+ int orig_argc = pm_setup_args(call_node->arguments, &flags, &kw_arg, iseq, ret, src, popped, scope_node, dummy_line_node, parser);
+
+ const rb_iseq_t *block_iseq = NULL;
+ if (call_node->block != NULL && PM_NODE_TYPE_P(call_node->block, PM_BLOCK_NODE)) {
+ // Scope associated with the block
+ pm_scope_node_t next_scope_node;
+ pm_scope_node_init(call_node->block, &next_scope_node, scope_node, parser);
+
+ block_iseq = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno);
+ ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq;
+ }
+ else {
+ if (((pm_node_t *)call_node)->flags & PM_CALL_NODE_FLAGS_VARIABLE_CALL) {
+ flags |= VM_CALL_VCALL;
+ }
+
+ if (call_node->block != NULL) {
+ PM_COMPILE_NOT_POPPED(call_node->block);
+ flags |= VM_CALL_ARGS_BLOCKARG;
+ }
+
+ if (!flags) {
+ flags |= VM_CALL_ARGS_SIMPLE;
+ }
+ }
+
+ if (call_node->receiver == NULL) {
+ flags |= VM_CALL_FCALL;
+ }
+
+ ADD_SEND_R(ret, &dummy_line_node, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg);
+ PM_POP_IF_POPPED;
+}
+
/*
* Compiles a prism node into instruction sequences
*
@@ -2024,47 +2149,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
case PM_CALL_NODE: {
pm_call_node_t *call_node = (pm_call_node_t *) node;
- ID method_id = pm_constant_id_lookup(scope_node, call_node->name);
- int flags = 0;
- struct rb_callinfo_kwarg *kw_arg = NULL;
if (call_node->receiver == NULL) {
PM_PUTSELF;
} else {
PM_COMPILE_NOT_POPPED(call_node->receiver);
}
- int orig_argc = pm_setup_args(call_node->arguments, &flags, &kw_arg, iseq, ret, src, popped, scope_node, dummy_line_node, parser);
-
- const rb_iseq_t *block_iseq = NULL;
- if (call_node->block != NULL && PM_NODE_TYPE_P(call_node->block, PM_BLOCK_NODE)) {
- // Scope associated with the block
- pm_scope_node_t next_scope_node;
- pm_scope_node_init(call_node->block, &next_scope_node, scope_node, parser);
-
- block_iseq = NEW_CHILD_ISEQ(next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno);
- ISEQ_COMPILE_DATA(iseq)->current_block = block_iseq;
- }
- else {
- if (node->flags & PM_CALL_NODE_FLAGS_VARIABLE_CALL) {
- flags |= VM_CALL_VCALL;
- }
-
- if (call_node->block != NULL) {
- PM_COMPILE_NOT_POPPED(call_node->block);
- flags |= VM_CALL_ARGS_BLOCKARG;
- }
-
- if (!flags) {
- flags |= VM_CALL_ARGS_SIMPLE;
- }
- }
-
- if (call_node->receiver == NULL) {
- flags |= VM_CALL_FCALL;
- }
-
- ADD_SEND_R(ret, &dummy_line_node, method_id, INT2FIX(orig_argc), block_iseq, INT2FIX(flags), kw_arg);
- PM_POP_IF_POPPED;
+ pm_compile_call(iseq, call_node, ret, src, popped, scope_node);
return;
}
case PM_CALL_AND_WRITE_NODE: {
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index 50bb61c99d..3d7e16a59a 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -181,6 +181,33 @@ module Prism
assert_prism_eval("defined?(())")
assert_prism_eval("defined?(('1'))")
+
+ # method chain starting with self that's truthy
+ assert_prism_eval("defined?(self.itself.itself.itself)")
+
+ # method chain starting with self that's false (exception swallowed)
+ assert_prism_eval("defined?(self.itself.itself.neat)")
+
+ # single self with method, truthy
+ assert_prism_eval("defined?(self.itself)")
+
+ # single self with method, false
+ assert_prism_eval("defined?(self.neat!)")
+
+ # method chain implicit self that's truthy
+ assert_prism_eval("defined?(itself.itself.itself)")
+
+ # method chain implicit self that's false
+ assert_prism_eval("defined?(itself.neat.itself)")
+
+ ## single method implicit self that's truthy
+ assert_prism_eval("defined?(itself)")
+
+ ## single method implicit self that's false
+ assert_prism_eval("defined?(neatneat)")
+
+ assert_prism_eval("defined?(a(itself))")
+ assert_prism_eval("defined?(itself(itself))")
end
def test_GlobalVariableReadNode