diff --git a/verilog/analysis/symbol_table.cc b/verilog/analysis/symbol_table.cc index 13d17547d..0500771db 100644 --- a/verilog/analysis/symbol_table.cc +++ b/verilog/analysis/symbol_table.cc @@ -224,6 +224,7 @@ class SymbolTable::Builder : public TreeContextVisitor { case NodeEnum::kPortList: DeclarePorts(node); break; + case NodeEnum::kModulePortDeclaration: case NodeEnum::kPortItem: // fall-through // for function/task parameters case NodeEnum::kPortDeclaration: // fall-through @@ -588,6 +589,15 @@ class SymbolTable::Builder : public TreeContextVisitor { } } + // stores the direction of the port in the current declaration type info + void HandleDirection(const SyntaxTreeLeaf& leaf) { + if (!declaration_type_info_) return; + if (Context().DirectParentIs(NodeEnum::kModulePortDeclaration) || + Context().DirectParentIs(NodeEnum::kPortDeclaration)) { + declaration_type_info_->direction = leaf.get().text(); + } + } + void HandleIdentifier(const SyntaxTreeLeaf& leaf) { const absl::string_view text = leaf.get().text(); VLOG(2) << __FUNCTION__ << ": " << text; @@ -602,14 +612,35 @@ class SymbolTable::Builder : public TreeContextVisitor { EmplaceElementInCurrentScope(leaf, text, SymbolMetaType::kParameter); return; } + // If identifier is within ModulePortDeclaration, add port identifier to the + // scope + if (Context().DirectParentsAre( + {NodeEnum::kUnqualifiedId, NodeEnum::kModulePortDeclaration}) || + Context().DirectParentsAre( + {NodeEnum::kUnqualifiedId, NodeEnum::kIdentifierUnpackedDimensions, + NodeEnum::kIdentifierList, NodeEnum::kModulePortDeclaration}) || + Context().DirectParentsAre({NodeEnum::kIdentifierUnpackedDimensions, + NodeEnum::kIdentifierList, + NodeEnum::kModulePortDeclaration}) || + Context().DirectParentsAre({NodeEnum::kIdentifierUnpackedDimensions, + NodeEnum::kIdentifierUnpackedDimensionsList, + NodeEnum::kModulePortDeclaration}) || + Context().DirectParentsAre({NodeEnum::kPortIdentifier, + NodeEnum::kPortIdentifierList, + NodeEnum::kModulePortDeclaration})) { + EmplacePortIdentifierInCurrentScope( + leaf, text, SymbolMetaType::kDataNetVariableInstance); + // TODO(fangism): Add attributes to distinguish public ports from + // private internals members. + return; + } + // If identifier is within PortDeclaration/Port, add a typed element if (Context().DirectParentsAre( {NodeEnum::kUnqualifiedId, NodeEnum::kPortDeclaration}) || Context().DirectParentsAre( {NodeEnum::kUnqualifiedId, NodeEnum::kDataTypeImplicitBasicIdDimensions, NodeEnum::kPortItem})) { - // This identifier declares a (non-parameter) port (of a module, - // function, task). EmplaceTypedElementInCurrentScope( leaf, text, SymbolMetaType::kDataNetVariableInstance); // TODO(fangism): Add attributes to distinguish public ports from @@ -783,6 +814,12 @@ class SymbolTable::Builder : public TreeContextVisitor { case '.': last_hierarchy_operator_ = &leaf.get(); break; + case verilog_tokentype::TK_input: + case verilog_tokentype::TK_output: + case verilog_tokentype::TK_inout: + case verilog_tokentype::TK_ref: + HandleDirection(leaf); + break; default: // TODO(hzeller): use verilog::IsIdentifierLike() ? @@ -970,12 +1007,158 @@ class SymbolTable::Builder : public TreeContextVisitor { SymbolTableNode* EmplaceElementInCurrentScope(const verible::Symbol& element, absl::string_view name, SymbolMetaType metatype) { - const auto p = current_scope_->TryEmplace( + const auto [kv, did_emplace] = current_scope_->TryEmplace( name, SymbolInfo{metatype, source_, &element}); - if (!p.second) { - DiagnoseSymbolAlreadyExists(name, p.first->second); + if (!did_emplace) { + if (kv->second.Value().is_port_identifier) { + kv->second.Value().supplement_definitions.push_back(name); + } else { + DiagnoseSymbolAlreadyExists(name, kv->second); + } + } + return &kv->second; // scope of the new (or pre-existing symbol) + } + + // checks whether a given tag belongs to one of the listed tags + bool IsTagMatching(int tag, std::initializer_list tags) { + return std::find(tags.begin(), tags.end(), tag) != tags.end(); + } + + // checks if the current first leaf has conflicting information with the + // second symbol + bool IsTypeLeafConflicting(const SyntaxTreeLeaf* first, + const verible::Symbol* second) { + if (!first || !second) return false; + if (IsTagMatching(second->Tag().tag, + {static_cast(NodeEnum::kPackedDimensions), + static_cast(NodeEnum::kUnpackedDimensions)})) { + return false; + } + if (second->Kind() == verible::SymbolKind::kLeaf) { + const SyntaxTreeLeaf* second_leaf = + verible::down_cast(second); + // conflict if there are multiple direction specifications + const std::initializer_list directiontags = { + verilog_tokentype::TK_input, verilog_tokentype::TK_output, + verilog_tokentype::TK_inout, verilog_tokentype::TK_ref}; + + const bool is_first_direction = + IsTagMatching(first->Tag().tag, directiontags); + const bool is_second_direction = + IsTagMatching(second_leaf->Tag().tag, directiontags); + + if (is_first_direction && is_second_direction) return true; + + // conflict if there are multiple sign specifications + const std::initializer_list signtags = { + verilog_tokentype::TK_signed, verilog_tokentype::TK_unsigned}; + const bool is_first_sign = IsTagMatching(first->Tag().tag, signtags); + const bool is_second_sign = + IsTagMatching(second_leaf->Tag().tag, signtags); + + if (is_first_sign && is_second_sign) return true; + + // since dimensions are not handled here and + // there are two different leaves that are not direction or sign + // then we assume it is a different type on both sides (hence the + // conflict) + if (!(is_first_direction || is_second_direction || is_first_sign || + is_second_sign)) { + return true; + } } - return &p.first->second; // scope of the new (or pre-existing symbol) + if (second->Kind() == verible::SymbolKind::kNode) { + const SyntaxTreeNode* second_node = + verible::down_cast(second); + for (const auto& child : second_node->children()) { + if (IsTypeLeafConflicting(first, child.get())) return true; + } + } + return false; + } + + // checks if two nodes have conflicting information + bool DoesConflictingNodeExist(const SyntaxTreeNode* node, + const verible::Symbol* context) { + if (context && context->Kind() == verible::SymbolKind::kNode) { + const SyntaxTreeNode* second_node = + verible::down_cast(context); + if ((node->Tag().tag == second_node->Tag().tag) && + !verible::EqualTreesByEnumString(node, second_node)) { + return true; + } + for (const auto& child : second_node->children()) { + if (DoesConflictingNodeExist(node, child.get())) return true; + } + } + return false; + } + + // checks if two kDataTypes have conflicting information, used + // in multiline definitions of nodes + bool IsDataTypeNodeConflicting(const verible::Symbol* first, + const verible::Symbol* second) { + // if type was not specified for symbol (e.g. implicit) in any case, return + // true + if (!first || !second) return false; + // if the left expression is a leaf, do final checks against the right + // expression + if (first->Kind() == verible::SymbolKind::kLeaf) { + const SyntaxTreeLeaf* leaf = + verible::down_cast(first); + return IsTypeLeafConflicting(leaf, second); + } + // if the left expression is a node, iterate over its children and check + // compatibility + if (first->Kind() == verible::SymbolKind::kNode) { + const SyntaxTreeNode* node = + verible::down_cast(first); + if (IsTagMatching(node->Tag().tag, + {static_cast(NodeEnum::kPackedDimensions), + static_cast(NodeEnum::kUnpackedDimensions)})) { + if (DoesConflictingNodeExist(node, second)) return true; + return false; + } + for (const auto& child : node->children()) { + // run method recursively for each child + if (IsDataTypeNodeConflicting(child.get(), second)) return true; + } + } + return false; + } + + // Checks potential multiline declaration of port + // against correctness + void CheckMultilinePortDeclarationCorrectness(SymbolTableNode* existing_node, + absl::string_view name) { + DeclarationTypeInfo& new_decl_info = + *ABSL_DIE_IF_NULL(declaration_type_info_); + DeclarationTypeInfo& old_decl_info = existing_node->Value().declared_type; + // TODO (glatosinski): currently direction is kept separately from + // kDataTypes, that is why it is handled separately. We may want to + // include it in kDataType to have a full type information in one place + // Also, according to some entries (e.g. net_variable) it is possible + // to have both delay and strength, we may want to have separate fields + // for them in MakeDataType (currently we have delay_or_strength) + if (!new_decl_info.direction.empty() && !old_decl_info.direction.empty()) { + DiagnoseSymbolAlreadyExists(name, *existing_node); + return; + } + if (IsDataTypeNodeConflicting(old_decl_info.syntax_origin, + new_decl_info.syntax_origin)) { + DiagnoseSymbolAlreadyExists(name, *existing_node); + return; + } + for (const auto& type_specification : old_decl_info.type_specifications) { + if (IsDataTypeNodeConflicting(type_specification, + new_decl_info.syntax_origin)) { + DiagnoseSymbolAlreadyExists(name, *existing_node); + return; + } + } + existing_node->Value().supplement_definitions.push_back(name); + existing_node->Value().declared_type.type_specifications.push_back( + declaration_type_info_->syntax_origin); } // Creates a named typed element in the current scope. @@ -987,14 +1170,42 @@ class SymbolTable::Builder : public TreeContextVisitor { VLOG(2) << __FUNCTION__ << ": " << name << " in " << CurrentScopeFullPath(); VLOG(3) << " type info: " << *ABSL_DIE_IF_NULL(declaration_type_info_); VLOG(3) << " full text: " << AutoTruncate{StringSpanOfSymbol(element), 40}; + const auto [kv, passed] = current_scope_->TryEmplace( + name, SymbolInfo{ + metatype, source_, &element, + // associate this instance with its declared type + *ABSL_DIE_IF_NULL(declaration_type_info_), // copy + }); + if (!passed) { + if (kv->second.Value().is_port_identifier) { + CheckMultilinePortDeclarationCorrectness(&kv->second, name); + } else { + DiagnoseSymbolAlreadyExists(name, kv->second); + } + } + VLOG(2) << "end of " << __FUNCTION__ << ": " << name; + return kv->second; // scope of the new (or pre-existing symbol) + } + + // Creates a port identifier element in the current scope. + // Suitable for SystemVerilog module port declarations, where + // there are multiple lines defining the symbol. + SymbolTableNode& EmplacePortIdentifierInCurrentScope( + const verible::Symbol& element, absl::string_view name, + SymbolMetaType metatype) { + VLOG(2) << __FUNCTION__ << ": " << name << " in " << CurrentScopeFullPath(); + VLOG(3) << " type info: " << *ABSL_DIE_IF_NULL(declaration_type_info_); + VLOG(3) << " full text: " << AutoTruncate{StringSpanOfSymbol(element), 40}; const auto p = current_scope_->TryEmplace( name, SymbolInfo{ metatype, source_, &element, // associate this instance with its declared type *ABSL_DIE_IF_NULL(declaration_type_info_), // copy }); + p.first->second.Value().is_port_identifier = true; if (!p.second) { - DiagnoseSymbolAlreadyExists(name, p.first->second); + // the symbol was already defined, add it to supplement_definitions + CheckMultilinePortDeclarationCorrectness(&p.first->second, name); } VLOG(2) << "end of " << __FUNCTION__ << ": " << name; return p.first->second; // scope of the new (or pre-existing symbol) @@ -1197,6 +1408,7 @@ class SymbolTable::Builder : public TreeContextVisitor { // declare data/variables/instances. const ValueSaver save_type(&declaration_type_info_, &decl_type_info); + // reset port direction Descend(data_decl_node); VLOG(2) << "end of " << __FUNCTION__; } diff --git a/verilog/analysis/symbol_table.h b/verilog/analysis/symbol_table.h index ac6a71a37..bdc9d848e 100644 --- a/verilog/analysis/symbol_table.h +++ b/verilog/analysis/symbol_table.h @@ -231,6 +231,13 @@ struct DeclarationTypeInfo { // which can be recovered by StringSpanOfSymbol(const verible::Symbol&). const verible::Symbol* syntax_origin = nullptr; + // holds optional string_view describing direction of the port + absl::string_view direction = ""; + + // holds additional type specifications, used mostly in multiline definitions + // of ports + std::vector type_specifications; + // Pointer to the reference node that represents a user-defined type, if // applicable. // For built-in and primitive types, this is left as nullptr. @@ -280,6 +287,15 @@ struct SymbolInfo { // Reminder: Parts of the syntax tree may originate from included files. const verible::Symbol* syntax_origin = nullptr; + // vector to additional definition entries, e.g. for port definitions + // TODO (glatosinski): I guess we should include more information here rather + // than just string_view pointing to the symbol, or add string_view pointing + // to the symbol in the Symbol class + std::vector supplement_definitions; + + // bool telling if the given symbol is a port identifier + bool is_port_identifier = false; + // What is the type associated with this symbol? // Only applicable to typed elements: variables, nets, instances, // typedefs, etc. @@ -330,7 +346,7 @@ struct SymbolInfo { : metatype(metatype), file_origin(file_origin), syntax_origin(syntax_origin), - declared_type(declared_type) {} + declared_type(std::move(declared_type)) {} // move-only SymbolInfo(const SymbolInfo&) = delete; diff --git a/verilog/analysis/symbol_table_test.cc b/verilog/analysis/symbol_table_test.cc index c238cf238..b6c14f2f9 100644 --- a/verilog/analysis/symbol_table_test.cc +++ b/verilog/analysis/symbol_table_test.cc @@ -9010,6 +9010,219 @@ TEST(BuildSymbolTableTest, IncludedTwiceFromDifferentFiles) { EXPECT_EMPTY_STATUSES(resolve_diagnostics); } +TEST(BuildSymbolTableTest, ModulePortDeclarationMultiline) { + TestVerilogSourceFile src("foobar.sv", + "module a; endmodule\n" + "module m(mport);\n" + " input mport;\n" + " wire mport;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + EXPECT_EMPTY_STATUSES(build_diagnostics); +} + +TEST(BuildSymbolTableTest, ModulePortDeclarationDirectionRedefinition) { + TestVerilogSourceFile src( + "foobar.sv", + "module m(mport);\n" + " input mport;\n" + " output mport;\n" // direction is already declared + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + const SymbolTableNode& root_symbol(symbol_table.Root()); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_node, root_symbol, "m"); + EXPECT_EQ(module_node_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_node_info.file_origin, &src); + EXPECT_EQ(module_node_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + ASSIGN_MUST_HAVE_UNIQUE(err_status, build_diagnostics); + EXPECT_EQ(err_status.code(), absl::StatusCode::kAlreadyExists); + EXPECT_THAT(err_status.message(), + HasSubstr("\"mport\" is already defined in the $root::m scope")); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); // nothing to resolve + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + } +} + +TEST(BuildSymbolTableTest, ModulePortDeclarationTypeRedefinition) { + TestVerilogSourceFile src("foobar.sv", + "module a; endmodule\n" + "module m(mport);\n" + " input mport;\n" + " wire mport;\n" + " logic mport;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + const SymbolTableNode& root_symbol(symbol_table.Root()); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_node, root_symbol, "m"); + EXPECT_EQ(module_node_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_node_info.file_origin, &src); + EXPECT_EQ(module_node_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + ASSIGN_MUST_HAVE_UNIQUE(err_status, build_diagnostics); + EXPECT_EQ(err_status.code(), absl::StatusCode::kAlreadyExists); + EXPECT_THAT(err_status.message(), + HasSubstr("\"mport\" is already defined in the $root::m scope")); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); // nothing to resolve + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + } +} + +TEST(BuildSymbolTableTest, ModulePortDeclarationTypeMultilineWithDimensions) { + TestVerilogSourceFile src("foobar.sv", + "module m(mport);\n" + " input [10:0] mport;\n" + " reg [10:0] mport;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + EXPECT_EMPTY_STATUSES(build_diagnostics); +} + +TEST(BuildSymbolTableTest, + ModulePortDeclarationTypeMultilineWithMismatchingDimensions) { + TestVerilogSourceFile src("foobar.sv", + "module m(mport);\n" + " input [10:0] mport;\n" + " reg [8:0] mport;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + const SymbolTableNode& root_symbol(symbol_table.Root()); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_node, root_symbol, "m"); + EXPECT_EQ(module_node_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_node_info.file_origin, &src); + EXPECT_EQ(module_node_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + ASSIGN_MUST_HAVE_UNIQUE(err_status, build_diagnostics); + EXPECT_EQ(err_status.code(), absl::StatusCode::kAlreadyExists); + EXPECT_THAT(err_status.message(), + HasSubstr("\"mport\" is already defined in the $root::m scope")); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); // nothing to resolve + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + } +} + +TEST(BuildSymbolTableTest, + ModulePortDeclarationTypeMultilineCorrectSignPlacements) { + TestVerilogSourceFile src("foobar.sv", + "module m(a, b, c, d);\n" + " input signed [10:0] a;\n" + " output unsigned [10:0] b;\n" + " input [10:0] c;\n" + " output [10:0] d;\n" + " wire [10:0] a;\n" + " logic [10:0] b;\n" + " logic unsigned [10:0] c;\n" + " wire signed [10:0] d;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + EXPECT_EMPTY_STATUSES(build_diagnostics); +} + +TEST(BuildSymbolTableTest, + ModulePortDeclarationTypeMultilineWithMismatchingSigns) { + TestVerilogSourceFile src("foobar.sv", + "module m(mport);\n" + " input unsigned [10:0] mport;\n" + " reg signed [10:0] mport;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + const SymbolTableNode& root_symbol(symbol_table.Root()); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_node, root_symbol, "m"); + EXPECT_EQ(module_node_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_node_info.file_origin, &src); + EXPECT_EQ(module_node_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + ASSIGN_MUST_HAVE_UNIQUE(err_status, build_diagnostics); + EXPECT_EQ(err_status.code(), absl::StatusCode::kAlreadyExists); + EXPECT_THAT(err_status.message(), + HasSubstr("\"mport\" is already defined in the $root::m scope")); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); // nothing to resolve + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + } +} + +TEST(BuildSymbolTableTest, ModulePortDeclarationTypeMultilineWithPortList) { + TestVerilogSourceFile src("foobar.sv", + "module m(a, b, c);\n" + " input a, b;\n" + " output b, c;\n" + "endmodule\n"); + const auto status = src.Parse(); + ASSERT_TRUE(status.ok()) << status.message(); + SymbolTable symbol_table(nullptr); + const SymbolTableNode& root_symbol(symbol_table.Root()); + + const auto build_diagnostics = BuildSymbolTable(src, &symbol_table); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_node, root_symbol, "m"); + EXPECT_EQ(module_node_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_node_info.file_origin, &src); + EXPECT_EQ(module_node_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + ASSIGN_MUST_HAVE_UNIQUE(err_status, build_diagnostics); + EXPECT_EQ(err_status.code(), absl::StatusCode::kAlreadyExists); + EXPECT_THAT(err_status.message(), + HasSubstr("\"b\" is already defined in the $root::m scope")); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); // nothing to resolve + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + } +} + struct FileListTestCase { absl::string_view contents; std::vector expected_files; diff --git a/verilog/parser/verilog.y b/verilog/parser/verilog.y index e8eb0325a..5a2cef8ae 100644 --- a/verilog/parser/verilog.y +++ b/verilog/parser/verilog.y @@ -5549,11 +5549,17 @@ module_parameter_port_list_item_last { $$ = MakeTaggedNode(N::kFormalParameterList, $1); } ; +// TODO (glatosinski) MakeDataType is introduced here to mark +// all tokens responsible for data creation for a given net +// declaration. +// Additional information, such as dimensions should be possibly +// included too, for type comparison purposes (for multiline port +// declaration) net_declaration : net_type net_variable_or_decl_assigns ';' - { $$ = MakeTaggedNode(N::kNetDeclaration, $1, nullptr, $2, $3); } + { $$ = MakeTaggedNode(N::kNetDeclaration, MakeDataType($1), nullptr, $2, $3); } | net_type data_type_or_implicit net_variable_or_decl_assigns ';' - { $$ = MakeTaggedNode(N::kNetDeclaration, $1, $2, $3, $4); } + { $$ = MakeTaggedNode(N::kNetDeclaration, MakeDataType($1), $2, $3, $4); } /* TODO(fangism): support drive_strength and charge_strength */ // : net_type data_type_or_implicit delay3_opt net_variable_list ';' // : net_type data_type_or_implicit delay3 net_variable_list ';' @@ -5566,11 +5572,11 @@ net_declaration // trailing_assign_opt ',' net_decl_assigns ';' // | net_type data_type_or_implicit drive_strength net_decl_assigns ';' | TK_trireg charge_strength_opt decl_dimensions_opt delay3_opt list_of_identifiers ';' - { $$ = MakeTaggedNode(N::kNetDeclaration, $1, $2, + { $$ = MakeTaggedNode(N::kNetDeclaration, MakeDataType(nullptr, $1, $4, nullptr), $2, MakePackedDimensionsNode($3), - $4, $5, $6); } + $5, $6); } | net_type delay3 net_variable_or_decl_assigns ';' - { $$ = MakeTaggedNode(N::kNetDeclaration, $1, nullptr, nullptr, $2, $3, $4); } + { $$ = MakeTaggedNode(N::kNetDeclaration, MakeDataType(nullptr, $1, $2, nullptr), nullptr, nullptr, $3, $4); } /* TODO(fangism): net_type_identifer [ delay_control ] list_of_net_decl_assignments */ /* TODO(fangism): TK_interconnect ... */ ; @@ -5582,36 +5588,36 @@ module_port_declaration */ : port_direction signed_unsigned_opt qualified_id decl_dimensions_opt list_of_identifiers_unpacked_dimensions ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, - MakePackedDimensionsNode($4), + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, MakeDataType($2, $3, + MakePackedDimensionsNode($4)), $5, $6); } | port_direction signed_unsigned_opt unqualified_id decl_dimensions_opt list_of_identifiers_unpacked_dimensions ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, - MakePackedDimensionsNode($4), + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, MakeDataType($2, $3, + MakePackedDimensionsNode($4)), $5, $6); } | port_direction signed_unsigned_opt decl_dimensions delay3_opt list_of_identifiers_unpacked_dimensions ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, - MakePackedDimensionsNode($3), $4, + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, + MakeDataType($2, nullptr, $4, MakePackedDimensionsNode($3)), $5, $6);} /* implicit type */ | port_direction signed_unsigned_opt delay3 list_of_identifiers_unpacked_dimensions ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, $4, $5); } + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, MakeDataType($2, nullptr, $3, nullptr), $4, $5); } /* implicit type */ | port_direction signed_unsigned_opt list_of_module_item_identifiers ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, $4);} + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, MakeDataType($2, nullptr, nullptr), $3, $4);} /* implicit type */ | port_direction port_net_type signed_unsigned_opt decl_dimensions_opt list_of_identifiers_unpacked_dimensions ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, - MakePackedDimensionsNode($4), + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, + MakeDataType($3, ForwardChildren($2), MakePackedDimensionsNode($4)), $5, $6); } | dir var_type signed_unsigned_opt decl_dimensions_opt list_of_port_identifiers ';' - { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, $2, $3, - MakePackedDimensionsNode($4), + { $$ = MakeTaggedNode(N::kModulePortDeclaration, $1, + MakeDataType($3, ForwardChildren($2), MakePackedDimensionsNode($4)), $5, $6); } ; diff --git a/verilog/tools/ls/symbol-table-handler.cc b/verilog/tools/ls/symbol-table-handler.cc index 7bb81f179..1f9f3d881 100644 --- a/verilog/tools/ls/symbol-table-handler.cc +++ b/verilog/tools/ls/symbol-table-handler.cc @@ -197,6 +197,14 @@ const SymbolTableNode *SymbolTableHandler::ScanSymbolTreeForDefinition( } // TODO (glatosinski): reduce searched scope by utilizing information from // syntax tree? + if (context->Key() && verible::IsSubRange(*context->Key(), symbol)) { + return context; + } + for (const auto &sdef : context->Value().supplement_definitions) { + if (verible::IsSubRange(sdef, symbol)) { + return context; + } + } for (const auto &ref : context->Value().local_references_to_bind) { if (ref.Empty()) continue; const SymbolTableNode *resolved = @@ -284,10 +292,16 @@ std::vector SymbolTableHandler::FindDefinitionLocation( const SymbolTableNode *node = ScanSymbolTreeForDefinition(&root, symbol); // Symbol not found if (!node) return {}; - std::optional location = + std::vector locations; + const std::optional location = GetLocationFromSymbolName(*node->Key(), node->Value().file_origin); if (!location) return {}; - return {*location}; + locations.push_back(*location); + for (const auto &sdef : node->Value().supplement_definitions) { + const auto loc = GetLocationFromSymbolName(sdef, node->Value().file_origin); + if (loc) locations.push_back(*loc); + } + return locations; } const verible::Symbol *SymbolTableHandler::FindDefinitionSymbol( @@ -305,7 +319,7 @@ std::vector SymbolTableHandler::FindReferencesLocations( const verible::lsp::ReferenceParams ¶ms, const verilog::BufferTrackerContainer &parsed_buffers) { Prepare(); - absl::string_view symbol = + const absl::string_view symbol = GetTokenAtTextDocumentPosition(params, parsed_buffers); const SymbolTableNode &root = symbol_table_->Root(); const SymbolTableNode *node = ScanSymbolTreeForDefinition(&root, symbol); @@ -322,8 +336,8 @@ void SymbolTableHandler::CollectReferencesReferenceComponents( const SymbolTableNode *definition_node, std::vector *references) { if (ref->Value().resolved_symbol == definition_node) { - std::optional loc = GetLocationFromSymbolName( - ref->Value().identifier, ref_origin->Value().file_origin); + const auto loc = GetLocationFromSymbolName(ref->Value().identifier, + ref_origin->Value().file_origin); if (loc) references->push_back(*loc); } for (const auto &childref : ref->Children()) { diff --git a/verilog/tools/ls/verilog-language-server_test.cc b/verilog/tools/ls/verilog-language-server_test.cc index 39a77bd26..7f8b230f6 100644 --- a/verilog/tools/ls/verilog-language-server_test.cc +++ b/verilog/tools/ls/verilog-language-server_test.cc @@ -488,7 +488,7 @@ TEST_F(VerilogLanguageServerTest, RangeFormattingTest) { 0}}; for (const auto ¶ms : formatting_params) { - std::string request = FormattingRequest("file://fmt.sv", params); + const std::string request = FormattingRequest("file://fmt.sv", params); ASSERT_OK(SendRequest(request)); const json response = json::parse(GetResponse()); @@ -616,6 +616,27 @@ std::string ReferencesRequest(absl::string_view file, int id, int line, line, character); } +void CheckDefinitionEntry(const json &entry, verible::LineColumn start, + verible::LineColumn end, + const std::string &file_uri) { + ASSERT_EQ(entry["range"]["start"]["line"], start.line); + ASSERT_EQ(entry["range"]["start"]["character"], start.column); + ASSERT_EQ(entry["range"]["end"]["line"], end.line); + ASSERT_EQ(entry["range"]["end"]["character"], end.column); + ASSERT_EQ(entry["uri"], file_uri); +} + +// Performs assertions on textDocument/definition responses where single +// definition is expected +void CheckDefinitionResponseSingleDefinition(const json &response, int id, + verible::LineColumn start, + verible::LineColumn end, + const std::string &file_uri) { + ASSERT_EQ(response["id"], id); + ASSERT_EQ(response["result"].size(), 1); + CheckDefinitionEntry(response["result"][0], start, end, file_uri); +} + // Performs simple textDocument/definition request with no VerilogProject set TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestNoProjectTest) { std::string definition_request = DefinitionRequest("file://b.sv", 2, 3, 18); @@ -644,18 +665,15 @@ TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestTest) { GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_a_uri, 2, 2, 16); + const std::string definition_request = + DefinitionRequest(module_a_uri, 2, 2, 16); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); - ASSERT_EQ(response["id"], 2); - ASSERT_EQ(response["result"].size(), 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 1, .column = 9}, + {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request when there are two symbols of the same @@ -689,27 +707,20 @@ TEST_F(VerilogLanguageServerSymbolTableTest, ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_b_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_b_uri); // find definition for "var1" variable in a.sv file - definition_request = DefinitionRequest(module_a_uri, 3, 2, 16); + const std::string definition_request2 = + DefinitionRequest(module_a_uri, 3, 2, 16); - ASSERT_OK(SendRequest(definition_request)); + ASSERT_OK(SendRequest(definition_request2)); json response_a = json::parse(GetResponse()); - ASSERT_EQ(response_a["id"], 3); - ASSERT_EQ(response_a["result"].size(), 1); - ASSERT_EQ(response_a["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_a["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_a["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_a["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_a["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_a, 3, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request where we want definition of a symbol @@ -738,18 +749,15 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in b.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 4, 14); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 4, 14); ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request where we want definition of a symbol @@ -775,18 +783,15 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in b.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 4, 14); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 4, 14); ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request where we want definition of a symbol @@ -827,30 +832,23 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable of a module in b.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 4, 14); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 4, 14); ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); // perform double check ASSERT_OK(SendRequest(definition_request)); response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request where we want definition of a symbol @@ -886,18 +884,15 @@ endmodule GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_a_uri, 2, 2, 16); + const std::string definition_request = + DefinitionRequest(module_a_uri, 2, 2, 16); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); - ASSERT_EQ(response["id"], 2); - ASSERT_EQ(response["result"].size(), 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 1, .column = 9}, + {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition request where we want definition of a symbol @@ -932,7 +927,8 @@ endmodule GetResponse(); // find definition for "var1" variable of a module in b.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 4, 15); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 4, 15); ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); @@ -961,7 +957,7 @@ TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestUnsupportedURI) { GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest( + const std::string definition_request = DefinitionRequest( absl::StrReplaceAll(module_a_uri, {{"file://", "https://"}}), 2, 2, 16); ASSERT_OK(SendRequest(definition_request)); @@ -990,18 +986,15 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_a_uri, 2, 1, 10); + const std::string definition_request = + DefinitionRequest(module_a_uri, 2, 1, 10); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); - ASSERT_EQ(response["id"], 2); - ASSERT_EQ(response["result"].size(), 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 1, .column = 9}, + {.line = 1, .column = 13}, + module_a_uri); } // Check textDocument/definition when the cursor points at nothing @@ -1023,7 +1016,8 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_a_uri, 2, 1, 0); + const std::string definition_request = + DefinitionRequest(module_a_uri, 2, 1, 0); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); @@ -1051,7 +1045,8 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 3, 2); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 3, 2); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); @@ -1075,7 +1070,8 @@ TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestNoFileList) { GetResponse(); // find definition for "var1" variable in a.sv file - std::string definition_request = DefinitionRequest(module_a_uri, 2, 2, 16); + const std::string definition_request = + DefinitionRequest(module_a_uri, 2, 2, 16); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); @@ -1107,18 +1103,15 @@ TEST_F(VerilogLanguageServerSymbolTableTest, GetResponse(); // find definition for "var1" variable in b.sv file - std::string definition_request = DefinitionRequest(module_b_uri, 2, 4, 14); + const std::string definition_request = + DefinitionRequest(module_b_uri, 2, 4, 14); ASSERT_OK(SendRequest(definition_request)); json response_b = json::parse(GetResponse()); - ASSERT_EQ(response_b["id"], 2); - ASSERT_EQ(response_b["result"].size(), 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["start"]["character"], 9); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["line"], 1); - ASSERT_EQ(response_b["result"][0]["range"]["end"]["character"], 13); - ASSERT_EQ(response_b["result"][0]["uri"], module_a_uri); + CheckDefinitionResponseSingleDefinition( + response_b, 2, {.line = 1, .column = 9}, {.line = 1, .column = 13}, + module_a_uri); } TEST_F(VerilogLanguageServerSymbolTableTest, MultipleDefinitionsOfSameSymbol) { @@ -1160,21 +1153,18 @@ endmodule GetResponse(); // find definition for "bar" type - std::string definition_request = DefinitionRequest(module_foo_uri, 2, 1, 3); + const std::string definition_request = + DefinitionRequest(module_foo_uri, 2, 1, 3); ASSERT_OK(SendRequest(definition_request)); json response = json::parse(GetResponse()); - ASSERT_EQ(response["id"], 2); - ASSERT_EQ(response["result"].size(), 1); - ASSERT_EQ(response["result"][0]["range"]["start"]["line"], 0); - ASSERT_EQ(response["result"][0]["range"]["start"]["character"], 7); - ASSERT_EQ(response["result"][0]["range"]["end"]["line"], 0); - ASSERT_EQ(response["result"][0]["range"]["end"]["character"], 10); - ASSERT_EQ(response["result"][0]["uri"], module_bar_1_uri); + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 0, .column = 7}, + {.line = 0, .column = 10}, + module_bar_1_uri); } -// Sample of badly styled modle +// Sample of badly styled module constexpr static absl::string_view badly_styled_module = "module my_module(input logic in, output logic out);\n\tassign out = in; " "\nendmodule"; @@ -1459,6 +1449,351 @@ TEST_F(VerilogLanguageServerSymbolTableTest, CheckReferenceUnknownSymbol) { ASSERT_EQ(response_b["result"].size(), 0); } +// Checks the definition request for module type in different module +TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestModule) { + static constexpr absl::string_view // + instmodule( + R"(module InstModule ( + o, + i +); + output [31:0] o; + input i; + wire [31:0] o = {32{i}}; +endmodule + +module ExampInst ( + o, + i +); + + output o; + input i; + + InstModule instName ( /*AUTOINST*/); + +endmodule +)"); + const verible::file::testing::ScopedTestFile module_instmodule( + root_dir, instmodule, "instmodule.sv"); + + const std::string module_instmodule_uri = + PathToLSPUri(module_instmodule.filename()); + const std::string foo_open_request = + DidOpenRequest(module_instmodule_uri, instmodule); + ASSERT_OK(SendRequest(foo_open_request)); + + GetResponse(); + + // find definition for "InstModule" + const std::string definition_request = + DefinitionRequest(module_instmodule_uri, 2, 17, 3); + + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 0, .column = 7}, + {.line = 0, .column = 17}, + module_instmodule_uri); +} + +// Checks the go-to definition when pointing to the definition of the symbol +TEST_F(VerilogLanguageServerSymbolTableTest, DefinitionRequestSelf) { + static constexpr absl::string_view // + instmodule( + R"(module InstModule ( + o, + i +); + output [31:0] o; + input i; + wire [31:0] o = {32{i}}; +endmodule + +module ExampInst ( + o, + i +); + + output o; + input i; + + InstModule instName ( /*AUTOINST*/); + +endmodule +)"); + const verible::file::testing::ScopedTestFile module_instmodule( + root_dir, instmodule, "instmodule.sv"); + + const std::string module_instmodule_uri = + PathToLSPUri(module_instmodule.filename()); + const std::string foo_open_request = + DidOpenRequest(module_instmodule_uri, instmodule); + ASSERT_OK(SendRequest(foo_open_request)); + + GetResponse(); + + // find definition for "InstModule" + const std::string definition_request = + DefinitionRequest(module_instmodule_uri, 2, 0, 8); + + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + + CheckDefinitionResponseSingleDefinition(response, 2, {.line = 0, .column = 7}, + {.line = 0, .column = 17}, + module_instmodule_uri); +} + +// Checks the definition request for module port +// This check verifies ports with types defined inside port list +TEST_F(VerilogLanguageServerSymbolTableTest, + DefinitionRequestPortTypesInsideList) { + static constexpr absl::string_view // + instmodule( + R"(module InstModule ( + output logic [31:0] o, + input logic i +); + wire [31:0] o = {32{i}}; +endmodule +)"); + const verible::file::testing::ScopedTestFile module_instmodule( + root_dir, instmodule, "instmodule.sv"); + + const std::string module_instmodule_uri = + PathToLSPUri(module_instmodule.filename()); + const std::string foo_open_request = + DidOpenRequest(module_instmodule_uri, instmodule); + ASSERT_OK(SendRequest(foo_open_request)); + + GetResponse(); + + // find definition for "i" + std::string definition_request = + DefinitionRequest(module_instmodule_uri, 2, 4, 22); + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 2, {.line = 2, .column = 16}, {.line = 2, .column = 17}, + module_instmodule_uri); +} + +// Checks the definition request for module port +// This check verifies ports with types defined outside port list +TEST_F(VerilogLanguageServerSymbolTableTest, + DefinitionRequestPortTypesOutsideList) { + static constexpr absl::string_view // + instmodule( + R"(module InstModule ( + o, + i +); + output logic [31:0] o; + input logic i; + wire [31:0] o = {32{i}}; +endmodule +)"); + const verible::file::testing::ScopedTestFile module_instmodule( + root_dir, instmodule, "instmodule.sv"); + + const std::string module_instmodule_uri = + PathToLSPUri(module_instmodule.filename()); + const std::string foo_open_request = + DidOpenRequest(module_instmodule_uri, instmodule); + ASSERT_OK(SendRequest(foo_open_request)); + + GetResponse(); + + // find definition for "bar" type + const std::string definition_request = + DefinitionRequest(module_instmodule_uri, 2, 6, 22); + + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 2, {.line = 5, .column = 14}, {.line = 5, .column = 15}, + module_instmodule_uri); +} + +// Checks jumps to different variants of kModulePortDeclaration +// * port with implicit type +// * kPortIdentifier (reg with assignment) +// * port with dimensions +// * simple port +TEST_F(VerilogLanguageServerSymbolTableTest, + DefinitionRequestPortPortIdentifierVariant) { + static constexpr absl::string_view // + port_identifier( + R"(module port_identifier(a, rst, clk, out); + input logic [15:0] a; + input rst; + input logic clk; + output reg [15:0] out = 0; + + always @(posedge clk) begin + if (! rst) begin + out = 0; + end + else begin + out = out + a; + end + end +endmodule)"); + const verible::file::testing::ScopedTestFile module_port_identifier( + root_dir, port_identifier, "port_identifier.sv"); + + const std::string module_port_identifier_uri = + PathToLSPUri(module_port_identifier.filename()); + const std::string foo_open_request = + DidOpenRequest(module_port_identifier_uri, port_identifier); + ASSERT_OK(SendRequest(foo_open_request)); + + GetResponse(); + + // find definition for "a" + std::string definition_request = + DefinitionRequest(module_port_identifier_uri, 2, 11, 24); + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 2, {.line = 1, .column = 23}, {.line = 1, .column = 24}, + module_port_identifier_uri); + + // find definition for "clk" + definition_request = DefinitionRequest(module_port_identifier_uri, 3, 6, 22); + ASSERT_OK(SendRequest(definition_request)); + response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 3, {.line = 3, .column = 16}, {.line = 3, .column = 19}, + module_port_identifier_uri); + + // find definition for "rst" + definition_request = DefinitionRequest(module_port_identifier_uri, 4, 6, 22); + ASSERT_OK(SendRequest(definition_request)); + response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 4, {.line = 3, .column = 16}, {.line = 3, .column = 19}, + module_port_identifier_uri); + + // find first definition for "out" + definition_request = DefinitionRequest(module_port_identifier_uri, 5, 8, 13); + ASSERT_OK(SendRequest(definition_request)); + response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 5, {.line = 4, .column = 22}, {.line = 4, .column = 25}, + module_port_identifier_uri); + + // find second definition for "out" + definition_request = DefinitionRequest(module_port_identifier_uri, 6, 11, 18); + ASSERT_OK(SendRequest(definition_request)); + response = json::parse(GetResponse()); + CheckDefinitionResponseSingleDefinition( + response, 6, {.line = 4, .column = 22}, {.line = 4, .column = 25}, + module_port_identifier_uri); +} + +// Verifies the work of the go-to definition request when the +// definition of the symbol is split into multiple lines, +// e.g. for port module declarations. +TEST_F(VerilogLanguageServerSymbolTableTest, MultilinePortDefinitions) { + static constexpr absl::string_view // + port_identifier( + R"(module port_identifier(i, o, trigger); + input trigger; + input i; + output o; + + reg [31:0] i; + wire [31:0] o; + + always @(posedge clock) + assign o = i; +endmodule +)"); + const verible::file::testing::ScopedTestFile module_port_identifier( + root_dir, port_identifier, "port_identifier.sv"); + + const std::string module_port_identifier_uri = + PathToLSPUri(module_port_identifier.filename()); + const std::string foo_open_request = + DidOpenRequest(module_port_identifier_uri, port_identifier); + ASSERT_OK(SendRequest(foo_open_request)); + + json diagnostics = json::parse(GetResponse()); + ASSERT_EQ(diagnostics["method"], "textDocument/publishDiagnostics"); + ASSERT_EQ(diagnostics["params"]["uri"], module_port_identifier_uri); + ASSERT_EQ(diagnostics["params"]["diagnostics"].size(), 0); + + // find definition for "i" + std::string definition_request = + DefinitionRequest(module_port_identifier_uri, 2, 9, 15); + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + + ASSERT_EQ(response["id"], 2); + ASSERT_EQ(response["result"].size(), 2); + + std::sort( + response["result"].begin(), response["result"].end(), + [](const json &a, const json &b) -> bool { return a.dump() < b.dump(); }); + + CheckDefinitionEntry(response["result"][0], {.line = 5, .column = 13}, + {.line = 5, .column = 14}, module_port_identifier_uri); + CheckDefinitionEntry(response["result"][1], {.line = 2, .column = 8}, + {.line = 2, .column = 9}, module_port_identifier_uri); +} + +// Verifies the work of the go-to definition request when +// definition of the symbol later in the definition list is requested +TEST_F(VerilogLanguageServerSymbolTableTest, MultilinePortDefinitionsWithList) { + static constexpr absl::string_view // + port_identifier( + R"(module port_identifier(a, b, o, trigger); + input trigger; + input a, b; + output o; + + reg [31:0] a, b; + wire [31:0] o; + + always @(posedge clock) + assign o = a + b; +endmodule +)"); + const verible::file::testing::ScopedTestFile module_port_identifier( + root_dir, port_identifier, "port_identifier.sv"); + + const std::string module_port_identifier_uri = + PathToLSPUri(module_port_identifier.filename()); + const std::string foo_open_request = + DidOpenRequest(module_port_identifier_uri, port_identifier); + ASSERT_OK(SendRequest(foo_open_request)); + + json diagnostics = json::parse(GetResponse()); + ASSERT_EQ(diagnostics["method"], "textDocument/publishDiagnostics"); + ASSERT_EQ(diagnostics["params"]["uri"], module_port_identifier_uri); + ASSERT_EQ(diagnostics["params"]["diagnostics"].size(), 0); + + // find definition for "i" + std::string definition_request = + DefinitionRequest(module_port_identifier_uri, 2, 5, 16); + ASSERT_OK(SendRequest(definition_request)); + json response = json::parse(GetResponse()); + + ASSERT_EQ(response["id"], 2); + ASSERT_EQ(response["result"].size(), 2); + + std::sort( + response["result"].begin(), response["result"].end(), + [](const json &a, const json &b) -> bool { return a.dump() < b.dump(); }); + + CheckDefinitionEntry(response["result"][0], {.line = 2, .column = 11}, + {.line = 2, .column = 12}, module_port_identifier_uri); + CheckDefinitionEntry(response["result"][1], {.line = 5, .column = 16}, + {.line = 5, .column = 17}, module_port_identifier_uri); +} + // Tests correctness of Language Server shutdown request TEST_F(VerilogLanguageServerTest, ShutdownTest) { const absl::string_view shutdown_request =