diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index d66e553fa3..2b57cca86d 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -664,13 +664,37 @@ module Prism # defined?(a) # ^^^^^^^^^^^ def visit_defined_node(node) - builder.keyword_cmd( - :defined?, - token(node.keyword_loc), - token(node.lparen_loc), - [visit(node.value)], - token(node.rparen_loc) - ) + # Very weird circumstances here where something like: + # + # defined? + # (1) + # + # gets parsed in Ruby as having only the `1` expression but in parser + # it gets parsed as having a begin. In this case we need to synthesize + # that begin to match parser's behavior. + if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n") + builder.keyword_cmd( + :defined?, + token(node.keyword_loc), + nil, + [ + builder.begin( + token(node.lparen_loc), + visit(node.value), + token(node.rparen_loc) + ) + ], + nil + ) + else + builder.keyword_cmd( + :defined?, + token(node.keyword_loc), + token(node.lparen_loc), + [visit(node.value)], + token(node.rparen_loc) + ) + end end # if foo then bar else baz end diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index dce96e01ab..95f366ac91 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1615,8 +1615,23 @@ module Prism # defined?(a) # ^^^^^^^^^^^ def visit_defined_node(node) + expression = visit(node.value) + + # Very weird circumstances here where something like: + # + # defined? + # (1) + # + # gets parsed in Ruby as having only the `1` expression but in Ripper it + # gets parsed as having a parentheses node. In this case we need to + # synthesize that node to match Ripper's behavior. + if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n") + bounds(node.lparen_loc.join(node.rparen_loc)) + expression = on_paren(on_stmts_add(on_stmts_new, expression)) + end + bounds(node.location) - on_defined(visit(node.value)) + on_defined(expression) end # if foo then bar else baz end diff --git a/prism/prism.c b/prism/prism.c index 6968bb215c..6fb40e3550 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19557,18 +19557,27 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_token_t lparen; pm_token_t rparen; pm_node_t *expression; + context_push(parser, PM_CONTEXT_DEFINED); + bool newline = accept1(parser, PM_TOKEN_NEWLINE); if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { lparen = parser->previous; - expression = parse_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_DEFINED_EXPRESSION, (uint16_t) (depth + 1)); - if (parser->recovering) { + if (newline && accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { + expression = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, NULL, &parser->previous); + lparen = not_provided(parser); rparen = not_provided(parser); } else { - accept1(parser, PM_TOKEN_NEWLINE); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); - rparen = parser->previous; + expression = parse_expression(parser, PM_BINDING_POWER_COMPOSITION, true, false, PM_ERR_DEFINED_EXPRESSION, (uint16_t) (depth + 1)); + + if (parser->recovering) { + rparen = not_provided(parser); + } else { + accept1(parser, PM_TOKEN_NEWLINE); + expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); + rparen = parser->previous; + } } } else { lparen = not_provided(parser); diff --git a/test/prism/errors/defined_empty.txt b/test/prism/errors/defined_empty.txt new file mode 100644 index 0000000000..4d7ea76413 --- /dev/null +++ b/test/prism/errors/defined_empty.txt @@ -0,0 +1,3 @@ +defined?() + ^ expected an expression after `defined?` + diff --git a/test/prism/fixtures/defined.txt b/test/prism/fixtures/defined.txt index 247fa94e3a..09fc0a29e7 100644 --- a/test/prism/fixtures/defined.txt +++ b/test/prism/fixtures/defined.txt @@ -8,3 +8,12 @@ defined? 1 defined?("foo" ) + +defined? +1 + +defined? +(1) + +defined? +()