From 24743af7d521c1ca6be95b4b41a6e3bd448a13d6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Jul 2026 16:50:09 -0700 Subject: [PATCH] Support task and function names shadowing type identifiers SystemVerilog allows a declaration in an inner scope to use the same name as a type identifier from an outer scope. This also applies to task and function names. The lexer reports such names as `TYPE_IDENTIFIER` before the new task or function has been installed, which made constructs such as: typedef int T; module test; function int T(input int value); return value; endfunction task T; endtask endmodule fail in the task and function declaration grammar. A class method with the same name as the class itself hits the same problem because the class name is visible as a type identifier in the class scope. The task grammar can accept `identifier_name` directly, because a task has no return type and the token after `task` and the optional lifetime is always the task name. Function declarations have a local return-type/name ambiguity. After `function T` the parser does not know yet whether `T` is the function name with no explicit return type, or whether a following identifier will make `T` the explicit return type as in `function T f`. Parse the optional function return type and function name together. This allows a `TYPE_IDENTIFIER` token to be interpreted as the function name when no separate function name follows, while still parsing typed forms and `void` return types correctly. Signed-off-by: Lars-Peter Clausen --- parse.y | 82 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/parse.y b/parse.y index 0803658e8..b2979edc7 100644 --- a/parse.y +++ b/parse.y @@ -1017,7 +1017,9 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %type forward_type forward_type_without_enum %type data_type_or_implicit_plus_id_base +%type data_type_or_implicit_plus_id %type data_type_or_implicit_plus_id_dim +%type data_type_or_implicit_or_void_plus_id %type partial_port_name_dim %type partial_port_type_plus_id_dim %type partial_port_typedef_plus_id_dim @@ -1808,53 +1810,53 @@ for_step_opt definitions in the func_body to take on the scope of the function instead of the module. */ function_declaration /* IEEE1800-2005: A.2.6 */ - : K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER ';' + : K_function lifetime_opt data_type_or_implicit_or_void_plus_id ';' { assert(current_function == 0); - current_function = pform_push_function_scope(@1, $4, $2); + current_function = pform_push_function_scope(@1, $3.id, $2); } tf_item_list_opt statement_or_null_list_opt K_endfunction - { current_function->set_ports($7); - current_function->set_return($3); - current_function_set_statement($8? @8 : @4, $8); - pform_set_this_class(@4, current_function); + { current_function->set_ports($6); + current_function->set_return($3.type); + current_function_set_statement($7 ? @7 : @3, $7); + pform_set_this_class(@3, current_function); pform_pop_scope(); current_function = 0; } label_opt { // Last step: check any closing name. - check_end_label(@11, "function", $4, $11); - delete[]$4; + check_end_label(@10, "function", $3.id, $10); + delete[]$3.id; } - | K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER + | K_function lifetime_opt data_type_or_implicit_or_void_plus_id { assert(current_function == 0); - current_function = pform_push_function_scope(@1, $4, $2); + current_function = pform_push_function_scope(@1, $3.id, $2); } '(' tf_port_list_opt ')' ';' block_item_decls_opt statement_or_null_list_opt K_endfunction - { current_function->set_ports($7); - current_function->set_return($3); - current_function_set_statement($11? @11 : @4, $11); - pform_set_this_class(@4, current_function); + { current_function->set_ports($6); + current_function->set_return($3.type); + current_function_set_statement($10 ? @10 : @3, $10); + pform_set_this_class(@3, current_function); pform_pop_scope(); current_function = 0; - if ($7 == 0) { - pform_requires_sv(@4, "Functions with no ports"); + if ($6 == 0) { + pform_requires_sv(@3, "Functions with no ports"); } } label_opt { // Last step: check any closing name. - check_end_label(@14, "function", $4, $14); - delete[]$4; + check_end_label(@13, "function", $3.id, $13); + delete[]$3.id; } /* Detect and recover from some errors. */ - | K_function lifetime_opt data_type_or_implicit_or_void IDENTIFIER error K_endfunction + | K_function lifetime_opt data_type_or_implicit_or_void_plus_id error K_endfunction { /* */ if (current_function) { pform_pop_scope(); @@ -1866,8 +1868,8 @@ function_declaration /* IEEE1800-2005: A.2.6 */ } label_opt { // Last step: check any closing name. - check_end_label(@8, "function", $4, $8); - delete[]$4; + check_end_label(@7, "function", $3.id, $7); + delete[]$3.id; } ; @@ -2662,7 +2664,11 @@ streaming_concatenation /* IEEE1800-2005: A.8.1 */ task_declaration /* IEEE1800-2005: A.2.7 */ - : K_task lifetime_opt IDENTIFIER ';' + /* Tasks do not have a return type, so the leading identifier is always the + task name. Use identifier_name so a name tokenized as TYPE_IDENTIFIER can + still declare a task that shadows a visible type name. */ + + : K_task lifetime_opt identifier_name ';' { assert(current_task == 0); current_task = pform_push_task_scope(@1, $3, $2); } @@ -2689,7 +2695,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ delete[]$3; } - | K_task lifetime_opt IDENTIFIER '(' + | K_task lifetime_opt identifier_name '(' { assert(current_task == 0); current_task = pform_push_task_scope(@1, $3, $2); } @@ -2718,7 +2724,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ delete[]$3; } - | K_task lifetime_opt IDENTIFIER error K_endtask + | K_task lifetime_opt identifier_name error K_endtask { if (current_task) { pform_pop_scope(); @@ -2765,6 +2771,20 @@ data_type_or_implicit_plus_id_base } ; + // Function declarations have the same type/name ambiguity as other + // declarations. For `function T;`, T can be the function name even when the + // lexer returns TYPE_IDENTIFIER, while `function T f;` still uses T as the + // explicit return type. Unlike variable/net declarations, function names do + // not allow unpacked dimensions. +data_type_or_implicit_plus_id + : TYPE_IDENTIFIER + { set_type_id_range($$, nullptr, $1.text, @1, nullptr); + } + | data_type_or_implicit_plus_id_base + { $$ = $1; + } + ; + data_type_or_implicit_plus_id_dim : TYPE_IDENTIFIER dimensions_opt { set_type_id_range($$, nullptr, $1.text, @1, $2); @@ -2775,6 +2795,20 @@ data_type_or_implicit_plus_id_dim } ; + // `void` is only an explicit return type. The following identifier is still a + // function name and may be tokenized as TYPE_IDENTIFIER when it shadows a + // visible type name. +data_type_or_implicit_or_void_plus_id + : data_type_or_implicit_plus_id + { $$ = $1; + } + | K_void identifier_name + { void_type_t*tmp = new void_type_t; + FILE_NAME(tmp, @1); + set_type_id_range($$, tmp, $2, @2, nullptr); + } + ; + // Partial ANSI port declarations such as `input a, integer b` can redeclare // the data type without repeating the direction. Keep this narrower than // data_type_or_implicit_plus_id_dim so a bare identifier after a comma is