[ruby/prism] Reject class/module defs in method params/rescue/ensure/else

Fix https://github.com/ruby/prism/pull/1936

https://github.com/ruby/prism/commit/232e77a003
This commit is contained in:
TSUYUSATO Kitsune 2023-11-29 10:32:26 +09:00 committed by git
parent fcabe2df39
commit a908cef53f
6 changed files with 174 additions and 71 deletions

View File

@ -90,7 +90,7 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
[PM_ERR_CASE_MATCH_MISSING_PREDICATE] = "expected a predicate for a case matching statement",
[PM_ERR_CASE_MISSING_CONDITIONS] = "expected a `when` or `in` clause after `case`",
[PM_ERR_CASE_TERM] = "expected an `end` to close the `case` statement",
[PM_ERR_CLASS_IN_METHOD] = "unexpected class definition in a method body",
[PM_ERR_CLASS_IN_METHOD] = "unexpected class definition in a method definition",
[PM_ERR_CLASS_NAME] = "expected a constant name after `class`",
[PM_ERR_CLASS_SUPERCLASS] = "expected a superclass after `<`",
[PM_ERR_CLASS_TERM] = "expected an `end` to close the `class` statement",
@ -185,7 +185,7 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
[PM_ERR_LIST_W_UPPER_ELEMENT] = "expected a string in a `%W` list",
[PM_ERR_LIST_W_UPPER_TERM] = "expected a closing delimiter for the `%W` list",
[PM_ERR_MALLOC_FAILED] = "failed to allocate memory",
[PM_ERR_MODULE_IN_METHOD] = "unexpected module definition in a method body",
[PM_ERR_MODULE_IN_METHOD] = "unexpected module definition in a method definition",
[PM_ERR_MODULE_NAME] = "expected a constant name after `module`",
[PM_ERR_MODULE_TERM] = "expected an `end` to close the `module` statement",
[PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = "multiple splats in multiple assignment",

View File

@ -297,6 +297,9 @@ typedef enum {
/** an ensure statement */
PM_CONTEXT_ENSURE,
/** an ensure statement within a method definition */
PM_CONTEXT_ENSURE_DEF,
/** a for loop */
PM_CONTEXT_FOR,
@ -333,9 +336,15 @@ typedef enum {
/** a rescue else statement */
PM_CONTEXT_RESCUE_ELSE,
/** a rescue else statement within a method definition */
PM_CONTEXT_RESCUE_ELSE_DEF,
/** a rescue statement */
PM_CONTEXT_RESCUE,
/** a rescue statement within a method definition */
PM_CONTEXT_RESCUE_DEF,
/** a singleton class definition */
PM_CONTEXT_SCLASS,

View File

@ -6603,6 +6603,7 @@ context_terminator(pm_context_t context, pm_token_t *token) {
case PM_CONTEXT_ELSE:
case PM_CONTEXT_FOR:
case PM_CONTEXT_ENSURE:
case PM_CONTEXT_ENSURE_DEF:
return token->type == PM_TOKEN_KEYWORD_END;
case PM_CONTEXT_FOR_INDEX:
return token->type == PM_TOKEN_KEYWORD_IN;
@ -6623,8 +6624,10 @@ context_terminator(pm_context_t context, pm_token_t *token) {
return token->type == PM_TOKEN_PARENTHESIS_RIGHT;
case PM_CONTEXT_BEGIN:
case PM_CONTEXT_RESCUE:
case PM_CONTEXT_RESCUE_DEF:
return token->type == PM_TOKEN_KEYWORD_ENSURE || token->type == PM_TOKEN_KEYWORD_RESCUE || token->type == PM_TOKEN_KEYWORD_ELSE || token->type == PM_TOKEN_KEYWORD_END;
case PM_CONTEXT_RESCUE_ELSE:
case PM_CONTEXT_RESCUE_ELSE_DEF:
return token->type == PM_TOKEN_KEYWORD_ENSURE || token->type == PM_TOKEN_KEYWORD_END;
case PM_CONTEXT_LAMBDA_BRACES:
return token->type == PM_TOKEN_BRACE_RIGHT;
@ -6690,6 +6693,10 @@ context_def_p(pm_parser_t *parser) {
while (context_node != NULL) {
switch (context_node->context) {
case PM_CONTEXT_DEF:
case PM_CONTEXT_DEF_PARAMS:
case PM_CONTEXT_ENSURE_DEF:
case PM_CONTEXT_RESCUE_DEF:
case PM_CONTEXT_RESCUE_ELSE_DEF:
return true;
case PM_CONTEXT_CLASS:
case PM_CONTEXT_MODULE:
@ -11837,7 +11844,7 @@ parse_parameters(
* nodes pointing to each other from the top.
*/
static inline void
parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node) {
parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, bool def_p) {
pm_rescue_node_t *current = NULL;
while (accept1(parser, PM_TOKEN_KEYWORD_RESCUE)) {
@ -11900,7 +11907,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node) {
if (!match3(parser, PM_TOKEN_KEYWORD_ELSE, PM_TOKEN_KEYWORD_ENSURE, PM_TOKEN_KEYWORD_END)) {
pm_accepts_block_stack_push(parser, true);
pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_RESCUE);
pm_statements_node_t *statements = parse_statements(parser, def_p ? PM_CONTEXT_RESCUE_DEF : PM_CONTEXT_RESCUE);
if (statements) {
pm_rescue_node_statements_set(rescue, statements);
}
@ -11936,7 +11943,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node) {
pm_statements_node_t *else_statements = NULL;
if (!match2(parser, PM_TOKEN_KEYWORD_END, PM_TOKEN_KEYWORD_ENSURE)) {
pm_accepts_block_stack_push(parser, true);
else_statements = parse_statements(parser, PM_CONTEXT_RESCUE_ELSE);
else_statements = parse_statements(parser, def_p ? PM_CONTEXT_RESCUE_ELSE_DEF : PM_CONTEXT_RESCUE_ELSE);
pm_accepts_block_stack_pop(parser);
accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON);
}
@ -11952,7 +11959,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node) {
pm_statements_node_t *ensure_statements = NULL;
if (!match1(parser, PM_TOKEN_KEYWORD_END)) {
pm_accepts_block_stack_push(parser, true);
ensure_statements = parse_statements(parser, PM_CONTEXT_ENSURE);
ensure_statements = parse_statements(parser, def_p ? PM_CONTEXT_ENSURE_DEF : PM_CONTEXT_ENSURE);
pm_accepts_block_stack_pop(parser);
accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON);
}
@ -11970,10 +11977,10 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node) {
}
static inline pm_begin_node_t *
parse_rescues_as_begin(pm_parser_t *parser, pm_statements_node_t *statements) {
parse_rescues_as_begin(pm_parser_t *parser, pm_statements_node_t *statements, bool def_p) {
pm_token_t no_begin_token = not_provided(parser);
pm_begin_node_t *begin_node = pm_begin_node_create(parser, &no_begin_token, statements);
parse_rescues(parser, begin_node);
parse_rescues(parser, begin_node, def_p);
// All nodes within a begin node are optional, so we look
// for the earliest possible node that we can use to set
@ -12078,7 +12085,7 @@ parse_block(pm_parser_t *parser) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE));
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements);
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements, false);
}
}
@ -14547,7 +14554,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
}
pm_begin_node_t *begin_node = pm_begin_node_create(parser, &begin_keyword, begin_statements);
parse_rescues(parser, begin_node);
parse_rescues(parser, begin_node, false);
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_BEGIN_TERM);
begin_node->base.location.end = parser->previous.end;
@ -14665,7 +14672,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE));
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements);
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements, false);
}
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM);
@ -14717,7 +14724,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE));
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements);
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements, false);
}
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM);
@ -14744,6 +14751,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
pm_token_t operator = not_provided(parser);
pm_token_t name = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = def_keyword.end, .end = def_keyword.end };
// This context is necessary for lexing `...` in a bare params correctly.
// It must be pushed before lexing the first param, so it is here.
context_push(parser, PM_CONTEXT_DEF_PARAMS);
parser_lex(parser);
pm_constant_id_t old_param_name = parser->current_param_name;
@ -14844,7 +14853,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
break;
}
case PM_TOKEN_PARENTHESIS_LEFT: {
// The current context is `PM_CONTEXT_DEF_PARAMS`, however the inner expression
// of this parenthesis should not be processed under this context.
// Thus, the context is popped here.
context_pop(parser);
parser_lex(parser);
pm_token_t lparen = parser->previous;
pm_node_t *expression = parse_value_expression(parser, PM_BINDING_POWER_STATEMENT, PM_ERR_DEF_RECEIVER);
@ -14859,6 +14873,9 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
pm_parser_scope_push(parser, true);
parser->current_param_name = 0;
// To push `PM_CONTEXT_DEF_PARAMS` again is for the same reason as described the above.
context_push(parser, PM_CONTEXT_DEF_PARAMS);
name = parse_method_definition_name(parser);
break;
}
@ -14967,7 +14984,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE));
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements);
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements, true);
}
pm_accepts_block_stack_pop(parser);
@ -15222,7 +15239,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE));
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements);
statements = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) statements, false);
}
pm_constant_id_list_t locals = parser->current_scope->locals;
@ -15893,7 +15910,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) {
assert(body == NULL || PM_NODE_TYPE_P(body, PM_STATEMENTS_NODE));
body = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) body);
body = (pm_node_t *) parse_rescues_as_begin(parser, (pm_statements_node_t *) body, false);
}
expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END);

View File

@ -428,7 +428,7 @@ module Prism
)
assert_errors expected, "def foo;module A;end;end", [
["unexpected module definition in a method body", 8..14]
["unexpected module definition in a method definition", 8..14]
]
end
@ -467,7 +467,7 @@ module Prism
Location()
)
assert_errors expected, <<~RUBY, [["unexpected module definition in a method body", 21..27]]
assert_errors expected, <<~RUBY, [["unexpected module definition in a method definition", 21..27]]
def foo
bar do
module Foo;end
@ -476,6 +476,20 @@ module Prism
RUBY
end
def test_module_definition_in_method_defs
source = <<~RUBY
def foo(bar = module A;end);end
def foo;rescue;module A;end;end
def foo;ensure;module A;end;end
RUBY
message = "unexpected module definition in a method definition"
assert_errors expression(source), source, [
[message, 14..20],
[message, 47..53],
[message, 79..85],
]
end
def test_class_definition_in_method_body
expected = DefNode(
:foo,
@ -504,7 +518,21 @@ module Prism
)
assert_errors expected, "def foo;class A;end;end", [
["unexpected class definition in a method body", 8..13]
["unexpected class definition in a method definition", 8..13]
]
end
def test_class_definition_in_method_defs
source = <<~RUBY
def foo(bar = class A;end);end
def foo;rescue;class A;end;end
def foo;ensure;class A;end;end
RUBY
message = "unexpected class definition in a method definition"
assert_errors expression(source), source, [
[message, 14..19],
[message, 46..51],
[message, 77..82],
]
end

View File

@ -182,3 +182,5 @@ def foo(...)
end
def foo(bar = (def baz(bar) = bar; 1)) = 2
def (class Foo; end).foo(bar = 1) = 2

View File

@ -1,8 +1,8 @@
@ ProgramNode (location: (1,0)-(184,42))
@ ProgramNode (location: (1,0)-(186,37))
├── locals: [:a, :c, :foo]
└── statements:
@ StatementsNode (location: (1,0)-(184,42))
└── body: (length: 69)
@ StatementsNode (location: (1,0)-(186,37))
└── body: (length: 70)
├── @ DefNode (location: (1,0)-(2,3))
│ ├── name: :foo
│ ├── name_loc: (1,4)-(1,7) = "foo"
@ -1888,69 +1888,116 @@
│ ├── rparen_loc: (180,11)-(180,12) = ")"
│ ├── equal_loc: ∅
│ └── end_keyword_loc: (182,0)-(182,3) = "end"
└── @ DefNode (location: (184,0)-(184,42))
├── @ DefNode (location: (184,0)-(184,42))
│ ├── name: :foo
│ ├── name_loc: (184,4)-(184,7) = "foo"
│ ├── receiver: ∅
│ ├── parameters:
│ │ @ ParametersNode (location: (184,8)-(184,37))
│ │ ├── requireds: (length: 0)
│ │ ├── optionals: (length: 1)
│ │ │ └── @ OptionalParameterNode (location: (184,8)-(184,37))
│ │ │ ├── name: :bar
│ │ │ ├── name_loc: (184,8)-(184,11) = "bar"
│ │ │ ├── operator_loc: (184,12)-(184,13) = "="
│ │ │ └── value:
│ │ │ @ ParenthesesNode (location: (184,14)-(184,37))
│ │ │ ├── body:
│ │ │ │ @ StatementsNode (location: (184,15)-(184,36))
│ │ │ │ └── body: (length: 2)
│ │ │ │ ├── @ DefNode (location: (184,15)-(184,33))
│ │ │ │ │ ├── name: :baz
│ │ │ │ │ ├── name_loc: (184,19)-(184,22) = "baz"
│ │ │ │ │ ├── receiver: ∅
│ │ │ │ │ ├── parameters:
│ │ │ │ │ │ @ ParametersNode (location: (184,23)-(184,26))
│ │ │ │ │ │ ├── requireds: (length: 1)
│ │ │ │ │ │ │ └── @ RequiredParameterNode (location: (184,23)-(184,26))
│ │ │ │ │ │ │ └── name: :bar
│ │ │ │ │ │ ├── optionals: (length: 0)
│ │ │ │ │ │ ├── rest: ∅
│ │ │ │ │ │ ├── posts: (length: 0)
│ │ │ │ │ │ ├── keywords: (length: 0)
│ │ │ │ │ │ ├── keyword_rest: ∅
│ │ │ │ │ │ └── block: ∅
│ │ │ │ │ ├── body:
│ │ │ │ │ │ @ StatementsNode (location: (184,30)-(184,33))
│ │ │ │ │ │ └── body: (length: 1)
│ │ │ │ │ │ └── @ LocalVariableReadNode (location: (184,30)-(184,33))
│ │ │ │ │ │ ├── name: :bar
│ │ │ │ │ │ └── depth: 0
│ │ │ │ │ ├── locals: [:bar]
│ │ │ │ │ ├── def_keyword_loc: (184,15)-(184,18) = "def"
│ │ │ │ │ ├── operator_loc: ∅
│ │ │ │ │ ├── lparen_loc: (184,22)-(184,23) = "("
│ │ │ │ │ ├── rparen_loc: (184,26)-(184,27) = ")"
│ │ │ │ │ ├── equal_loc: (184,28)-(184,29) = "="
│ │ │ │ │ └── end_keyword_loc: ∅
│ │ │ │ └── @ IntegerNode (location: (184,35)-(184,36))
│ │ │ │ └── flags: decimal
│ │ │ ├── opening_loc: (184,14)-(184,15) = "("
│ │ │ └── closing_loc: (184,36)-(184,37) = ")"
│ │ ├── rest: ∅
│ │ ├── posts: (length: 0)
│ │ ├── keywords: (length: 0)
│ │ ├── keyword_rest: ∅
│ │ └── block: ∅
│ ├── body:
│ │ @ StatementsNode (location: (184,41)-(184,42))
│ │ └── body: (length: 1)
│ │ └── @ IntegerNode (location: (184,41)-(184,42))
│ │ └── flags: decimal
│ ├── locals: [:bar]
│ ├── def_keyword_loc: (184,0)-(184,3) = "def"
│ ├── operator_loc: ∅
│ ├── lparen_loc: (184,7)-(184,8) = "("
│ ├── rparen_loc: (184,37)-(184,38) = ")"
│ ├── equal_loc: (184,39)-(184,40) = "="
│ └── end_keyword_loc: ∅
└── @ DefNode (location: (186,0)-(186,37))
├── name: :foo
├── name_loc: (184,4)-(184,7) = "foo"
├── receiver: ∅
├── name_loc: (186,21)-(186,24) = "foo"
├── receiver:
│ @ ParenthesesNode (location: (186,4)-(186,20))
│ ├── body:
│ │ @ ClassNode (location: (186,5)-(186,19))
│ │ ├── locals: []
│ │ ├── class_keyword_loc: (186,5)-(186,10) = "class"
│ │ ├── constant_path:
│ │ │ @ ConstantReadNode (location: (186,11)-(186,14))
│ │ │ └── name: :Foo
│ │ ├── inheritance_operator_loc: ∅
│ │ ├── superclass: ∅
│ │ ├── body: ∅
│ │ ├── end_keyword_loc: (186,16)-(186,19) = "end"
│ │ └── name: :Foo
│ ├── opening_loc: (186,4)-(186,5) = "("
│ └── closing_loc: (186,19)-(186,20) = ")"
├── parameters:
│ @ ParametersNode (location: (184,8)-(184,37))
│ @ ParametersNode (location: (186,25)-(186,32))
│ ├── requireds: (length: 0)
│ ├── optionals: (length: 1)
│ │ └── @ OptionalParameterNode (location: (184,8)-(184,37))
│ │ └── @ OptionalParameterNode (location: (186,25)-(186,32))
│ │ ├── name: :bar
│ │ ├── name_loc: (184,8)-(184,11) = "bar"
│ │ ├── operator_loc: (184,12)-(184,13) = "="
│ │ ├── name_loc: (186,25)-(186,28) = "bar"
│ │ ├── operator_loc: (186,29)-(186,30) = "="
│ │ └── value:
│ │ @ ParenthesesNode (location: (184,14)-(184,37))
│ │ ├── body:
│ │ │ @ StatementsNode (location: (184,15)-(184,36))
│ │ │ └── body: (length: 2)
│ │ │ ├── @ DefNode (location: (184,15)-(184,33))
│ │ │ │ ├── name: :baz
│ │ │ │ ├── name_loc: (184,19)-(184,22) = "baz"
│ │ │ │ ├── receiver: ∅
│ │ │ │ ├── parameters:
│ │ │ │ │ @ ParametersNode (location: (184,23)-(184,26))
│ │ │ │ │ ├── requireds: (length: 1)
│ │ │ │ │ │ └── @ RequiredParameterNode (location: (184,23)-(184,26))
│ │ │ │ │ │ └── name: :bar
│ │ │ │ │ ├── optionals: (length: 0)
│ │ │ │ │ ├── rest: ∅
│ │ │ │ │ ├── posts: (length: 0)
│ │ │ │ │ ├── keywords: (length: 0)
│ │ │ │ │ ├── keyword_rest: ∅
│ │ │ │ │ └── block: ∅
│ │ │ │ ├── body:
│ │ │ │ │ @ StatementsNode (location: (184,30)-(184,33))
│ │ │ │ │ └── body: (length: 1)
│ │ │ │ │ └── @ LocalVariableReadNode (location: (184,30)-(184,33))
│ │ │ │ │ ├── name: :bar
│ │ │ │ │ └── depth: 0
│ │ │ │ ├── locals: [:bar]
│ │ │ │ ├── def_keyword_loc: (184,15)-(184,18) = "def"
│ │ │ │ ├── operator_loc: ∅
│ │ │ │ ├── lparen_loc: (184,22)-(184,23) = "("
│ │ │ │ ├── rparen_loc: (184,26)-(184,27) = ")"
│ │ │ │ ├── equal_loc: (184,28)-(184,29) = "="
│ │ │ │ └── end_keyword_loc: ∅
│ │ │ └── @ IntegerNode (location: (184,35)-(184,36))
│ │ │ └── flags: decimal
│ │ ├── opening_loc: (184,14)-(184,15) = "("
│ │ └── closing_loc: (184,36)-(184,37) = ")"
│ │ @ IntegerNode (location: (186,31)-(186,32))
│ │ └── flags: decimal
│ ├── rest: ∅
│ ├── posts: (length: 0)
│ ├── keywords: (length: 0)
│ ├── keyword_rest: ∅
│ └── block: ∅
├── body:
│ @ StatementsNode (location: (184,41)-(184,42))
│ @ StatementsNode (location: (186,36)-(186,37))
│ └── body: (length: 1)
│ └── @ IntegerNode (location: (184,41)-(184,42))
│ └── @ IntegerNode (location: (186,36)-(186,37))
│ └── flags: decimal
├── locals: [:bar]
├── def_keyword_loc: (184,0)-(184,3) = "def"
├── operator_loc: ∅
├── lparen_loc: (184,7)-(184,8) = "("
├── rparen_loc: (184,37)-(184,38) = ")"
├── equal_loc: (184,39)-(184,40) = "="
├── def_keyword_loc: (186,0)-(186,3) = "def"
├── operator_loc: (186,20)-(186,21) = "."
├── lparen_loc: (186,24)-(186,25) = "("
├── rparen_loc: (186,32)-(186,33) = ")"
├── equal_loc: (186,34)-(186,35) = "="
└── end_keyword_loc: ∅