From ace4faee372e0e5895d1abf074a3e9ccc12f5222 Mon Sep 17 00:00:00 2001 From: Lukasz Dalek Date: Tue, 13 Apr 2021 13:04:05 +0200 Subject: [PATCH] Squashed commits form PRs #675, #745 and #746 Signed-off-by: Lukasz Dalek --- verilog/analysis/symbol_table.cc | 122 ++++++++++++ verilog/analysis/symbol_table.h | 7 + verilog/analysis/symbol_table_test.cc | 244 +++++++++++++++++++++++ verilog/analysis/verilog_project.cc | 17 ++ verilog/analysis/verilog_project.h | 34 +++- verilog/analysis/verilog_project_test.cc | 30 +++ 6 files changed, 451 insertions(+), 3 deletions(-) diff --git a/verilog/analysis/symbol_table.cc b/verilog/analysis/symbol_table.cc index bc1158e874..cfe933b7b8 100644 --- a/verilog/analysis/symbol_table.cc +++ b/verilog/analysis/symbol_table.cc @@ -78,6 +78,8 @@ static const verible::EnumNameMap kSymbolMetaTypeNames({ {"function", SymbolMetaType::kFunction}, {"task", SymbolMetaType::kTask}, {"struct", SymbolMetaType::kStruct}, + {"enum", SymbolMetaType::kEnumType}, + {"", SymbolMetaType::kEnumConstant}, {"interface", SymbolMetaType::kInterface}, {"", SymbolMetaType::kUnspecified}, {"", SymbolMetaType::kCallable}, @@ -159,6 +161,9 @@ static absl::Status DiagnoseMemberSymbolResolutionFailure( context_name, ".")); } +static const SymbolTableNode* LookupSymbolUpwards( + const SymbolTableNode& context, absl::string_view symbol); + class SymbolTable::Builder : public TreeContextVisitor { public: Builder(const VerilogSourceFile& source, SymbolTable* symbol_table, @@ -261,6 +266,12 @@ class SymbolTable::Builder : public TreeContextVisitor { case NodeEnum::kStructType: DescendStructType(node); break; + case NodeEnum::kEnumType: + DescendEnumType(node); + break; + case NodeEnum::kLPValue: + HandlePossibleImplicitDeclaration(node); + break; default: Descend(node); break; @@ -455,6 +466,83 @@ class SymbolTable::Builder : public TreeContextVisitor { } } + void DescendEnumType(const SyntaxTreeNode& enum_type) { + CHECK(enum_type.MatchesTag(NodeEnum::kEnumType)); + const absl::string_view anon_name = + current_scope_->Value().CreateAnonymousScope("enum"); + SymbolTableNode& new_enum = DeclareScopedElementAndDescend( + enum_type, anon_name, SymbolMetaType::kEnumType); + + const ReferenceComponent anon_type_ref{ + .identifier = anon_name, + .ref_type = ReferenceType::kImmediate, + .required_metatype = SymbolMetaType::kEnumType, + // pre-resolve this symbol immediately + .resolved_symbol = &new_enum, + }; + + const CaptureDependentReference capture(this); + capture.Ref().PushReferenceComponent(anon_type_ref); + + if (declaration_type_info_ != nullptr) { + declaration_type_info_->user_defined_type = capture.Ref().LastLeaf(); + } + + // Iterate over enumeration constants + for (const auto& itr : new_enum) { + const auto enum_constant_name = itr.first; + const auto& symbol = itr.second; + const auto& syntax_origin = + *ABSL_DIE_IF_NULL(symbol.Value().syntax_origin); + + const ReferenceComponent itr_ref{ + .identifier = enum_constant_name, + .ref_type = ReferenceType::kImmediate, + .required_metatype = SymbolMetaType::kEnumConstant, + // pre-resolve this symbol immediately + .resolved_symbol = &symbol, + }; + + // CaptureDependentReference class doesn't support + // copy constructor + const CaptureDependentReference cap(this); + cap.Ref().PushReferenceComponent(anon_type_ref); + cap.Ref().PushReferenceComponent(itr_ref); + + // Create default DeclarationTypeInfo + DeclarationTypeInfo decl_type_info; + const ValueSaver save_type(&declaration_type_info_, + &decl_type_info); + declaration_type_info_->syntax_origin = &syntax_origin; + declaration_type_info_->user_defined_type = cap.Ref().LastLeaf(); + + // Constants should be visible in current scope so we create + // variable instances with references to enum constants + EmplaceTypedElementInCurrentScope(syntax_origin, enum_constant_name, + SymbolMetaType::kTypeAlias); + } + } + + void HandlePossibleImplicitDeclaration(const SyntaxTreeNode& node) { + VLOG(2) << __FUNCTION__; + + // Only left-hand side of continuous assignment statements are allowed to + // implicitly declare nets (LRM 6.10: Implicit declarations). + if (Context().DirectParentsAre( + {NodeEnum::kNetVariableAssignment, NodeEnum::kAssignmentList, + NodeEnum::kContinuousAssignmentStatement})) { + CHECK(node.MatchesTag(NodeEnum::kLPValue)); + + DeclarationTypeInfo decl_type_info; + const ValueSaver save_type(&declaration_type_info_, + &decl_type_info); + declaration_type_info_->implicit = true; + Descend(node); + } else { + Descend(node); + } + } + void HandleIdentifier(const SyntaxTreeLeaf& leaf) { const absl::string_view text = leaf.get().text(); VLOG(2) << __FUNCTION__ << ": " << text; @@ -546,6 +634,13 @@ class SymbolTable::Builder : public TreeContextVisitor { return; } + if (Context().DirectParentsAre( + {NodeEnum::kEnumName, NodeEnum::kEnumNameList})) { + EmplaceTypedElementInCurrentScope(leaf, text, + SymbolMetaType::kEnumConstant); + return; + } + // In DeclareInstance(), we already planted a self-reference that is // resolved to the instance being declared. if (Context().DirectParentIs(NodeEnum::kGateInstance)) return; @@ -582,6 +677,29 @@ class SymbolTable::Builder : public TreeContextVisitor { return; } + // Handle possible implicit declarations here + if (declaration_type_info_ != nullptr && declaration_type_info_->implicit) { + const SymbolTableNode* resolved = + LookupSymbolUpwards(*ABSL_DIE_IF_NULL(current_scope_), text); + if (resolved == nullptr) { + // No explicit declaration found, declare here + SymbolTableNode& implicit_declaration = + EmplaceTypedElementInCurrentScope( + leaf, text, SymbolMetaType::kDataNetVariableInstance); + + const ReferenceComponent implicit_ref{ + .identifier = text, + .ref_type = InferReferenceType(), + .required_metatype = InferMetaType(), + // pre-resolve + .resolved_symbol = &implicit_declaration, + }; + + ref.PushReferenceComponent(implicit_ref); + return; + } + } + // For all other cases, grow the reference chain deeper. // For type references, which may contained named parameters, // when encountering the first unqualified reference, establish its @@ -1697,6 +1815,10 @@ std::ostream& operator<<(std::ostream& stream, stream << "(primitive)"; } + if (decl_type_info.implicit) { + stream << ", implicit"; + } + return stream << " }"; } diff --git a/verilog/analysis/symbol_table.h b/verilog/analysis/symbol_table.h index a339dd17f8..fa7defc07b 100644 --- a/verilog/analysis/symbol_table.h +++ b/verilog/analysis/symbol_table.h @@ -54,6 +54,8 @@ enum class SymbolMetaType { kFunction, kTask, kStruct, + kEnumType, + kEnumConstant, kInterface, // The following enums represent classes/groups of the above types, @@ -248,6 +250,11 @@ struct DeclarationTypeInfo { // advance, and only ever moving ReferenceComponents, never copying them. const ReferenceComponentNode* user_defined_type = nullptr; + // Indicates that this is implicit declaration. + // FIXME(ldk): Check if this could be replaced by user_defined_type pointing + // to default implicit type. + bool implicit = false; + public: DeclarationTypeInfo() = default; diff --git a/verilog/analysis/symbol_table_test.cc b/verilog/analysis/symbol_table_test.cc index 9bc78f3738..9f7d83b93a 100644 --- a/verilog/analysis/symbol_table_test.cc +++ b/verilog/analysis/symbol_table_test.cc @@ -2183,6 +2183,138 @@ TEST(BuildSymbolTableTest, ModuleDeclarationLocalsDependOnParameter) { } } +TEST(BuildSymbolTableTest, ModuleSingleImplicitDeclaration) { + TestVerilogSourceFile src("foo.sv", + "module m;" + "assign a = 1'b0;" + "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); + EXPECT_EMPTY_STATUSES(build_diagnostics); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_m, root_symbol, "m"); + EXPECT_EQ(module_m_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_m_info.file_origin, &src); + EXPECT_EQ(module_m_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + MUST_ASSIGN_LOOKUP_SYMBOL(a_variable, module_m, "a"); + EXPECT_EQ(a_variable_info.metatype, SymbolMetaType::kDataNetVariableInstance); + const ReferenceComponentNode* a_type_ref = + a_variable_info.declared_type.user_defined_type; + EXPECT_EQ(a_type_ref, nullptr); // implicit type is primitive type + + EXPECT_EQ(module_m_info.local_references_to_bind.size(), 1); + const auto ref_map(module_m_info.LocalReferencesMapViewForTesting()); + + ASSIGN_MUST_FIND(a_refs, ref_map, "a"); + ASSERT_EQ(a_refs.size(), 1); // all references to "N" parameter + for (const auto& a_ref : a_refs) { + const ReferenceComponent& a_ref_comp(a_ref->components->Value()); + EXPECT_EQ(a_ref_comp.ref_type, ReferenceType::kUnqualified); + EXPECT_EQ(a_ref_comp.required_metatype, SymbolMetaType::kUnspecified); + EXPECT_EQ(a_ref_comp.identifier, "a"); + EXPECT_EQ(a_ref_comp.resolved_symbol, &a_variable); // pre-resolved + } + + { + std::vector resolve_diagnostics; + // Resolve mustn't break anything + symbol_table.Resolve(&resolve_diagnostics); + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + + // All references to "a" resolved. + for (const auto& a_ref : a_refs) { + const ReferenceComponent& a_ref_comp(a_ref->components->Value()); + EXPECT_EQ(a_ref_comp.resolved_symbol, + &a_variable); // resolved successfully + } + } +} + +TEST(BuildSymbolTableTest, ModuleReferenceToImplicitDeclaration) { + TestVerilogSourceFile src("foo.sv", + "module m;" + "assign a = 1'b0;" + "assign a = 1'b1;" + "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); + EXPECT_EMPTY_STATUSES(build_diagnostics); + + MUST_ASSIGN_LOOKUP_SYMBOL(module_m, root_symbol, "m"); + EXPECT_EQ(module_m_info.metatype, SymbolMetaType::kModule); + EXPECT_EQ(module_m_info.file_origin, &src); + EXPECT_EQ(module_m_info.declared_type.syntax_origin, + nullptr); // there is no module meta-type + + MUST_ASSIGN_LOOKUP_SYMBOL(a_variable, module_m, "a"); + EXPECT_EQ(a_variable_info.metatype, SymbolMetaType::kDataNetVariableInstance); + const ReferenceComponentNode* a_type_ref = + a_variable_info.declared_type.user_defined_type; + EXPECT_EQ(a_type_ref, nullptr); // implicit type is primitive type + + EXPECT_EQ(module_m_info.local_references_to_bind.size(), 2); + const auto ref_map(module_m_info.LocalReferencesMapViewForTesting()); + + ASSIGN_MUST_FIND(a_refs, ref_map, "a"); + ASSERT_EQ(a_refs.size(), 2); // all references to "N" parameter + { + const auto& a_ref = *a_refs.begin(); + const ReferenceComponent& a_ref_comp(a_ref->components->Value()); + EXPECT_EQ(a_ref_comp.ref_type, ReferenceType::kUnqualified); + EXPECT_EQ(a_ref_comp.required_metatype, SymbolMetaType::kUnspecified); + EXPECT_EQ(a_ref_comp.identifier, "a"); + EXPECT_EQ(a_ref_comp.resolved_symbol, &a_variable); // pre-resolved + } + { + const auto& a_ref = *std::next(a_refs.begin()); + const ReferenceComponent& a_ref_comp(a_ref->components->Value()); + EXPECT_EQ(a_ref_comp.ref_type, ReferenceType::kUnqualified); + EXPECT_EQ(a_ref_comp.required_metatype, SymbolMetaType::kUnspecified); + EXPECT_EQ(a_ref_comp.identifier, "a"); + EXPECT_EQ(a_ref_comp.resolved_symbol, nullptr); // pre-resolved + } + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + + // All references to "a" resolved. + for (const auto& a_ref : a_refs) { + const ReferenceComponent& a_ref_comp(a_ref->components->Value()); + EXPECT_EQ(a_ref_comp.resolved_symbol, + &a_variable); // resolved successfully + } + } +} + +TEST(BuildSymbolTableTest, ModuleImplicitRedeclared) { + TestVerilogSourceFile src("foo.sv", + "module m;" + "assign a = 1'b0;" + "wire a;" + "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_EQ(build_diagnostics.size(), 1); + EXPECT_FALSE(build_diagnostics.front().ok()); + EXPECT_EQ(build_diagnostics.front().message(), + "Symbol \"a\" is already defined in the $root::m scope."); +} + TEST(BuildSymbolTableTest, ClassDeclarationSingle) { TestVerilogSourceFile src("foobar.sv", "class ccc;\nendclass\n"); const auto status = src.Parse(); @@ -6132,6 +6264,118 @@ TEST(BuildSymbolTableTest, AnonymousStructTypeNestedMemberReference) { } } +TEST(BuildSymbolTableTest, AnonymousEnumTypeData) { + TestVerilogSourceFile src("simple_enum.sv", + "enum {\n" + " idle, busy\n" + "} data;\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); + EXPECT_EMPTY_STATUSES(build_diagnostics); + + // Expect one anonymous enum definition and reference. + EXPECT_EQ(root_symbol.Value().anonymous_scope_names.size(), 1); + + // Expect four symbols (enum, data, idle, busy) + ASSERT_EQ(root_symbol.Children().size(), 4); + + // Find the symbol that is a enum (anon) + const auto found = std::find_if( + root_symbol.Children().begin(), root_symbol.Children().end(), + [](const SymbolTableNode::key_value_type& p) { + return p.first != "data" && p.first != "idle" && p.first != "busy"; + }); + ASSERT_NE(found, root_symbol.Children().end()); + const SymbolTableNode& anon_enum(found->second); + const SymbolInfo& anon_enum_info(anon_enum.Value()); + EXPECT_EQ(anon_enum_info.metatype, SymbolMetaType::kEnumType); + EXPECT_TRUE(anon_enum_info.local_references_to_bind.empty()); + + // Enum has two members. + EXPECT_EQ(anon_enum.Children().size(), 2); + + MUST_ASSIGN_LOOKUP_SYMBOL(idle, anon_enum, "idle"); + EXPECT_EQ(idle_info.metatype, SymbolMetaType::kEnumConstant); + EXPECT_EQ(idle_info.file_origin, &src); + EXPECT_EQ(idle_info.declared_type.user_defined_type, nullptr); + + MUST_ASSIGN_LOOKUP_SYMBOL(busy, anon_enum, "busy"); + EXPECT_EQ(busy_info.metatype, SymbolMetaType::kEnumConstant); + EXPECT_EQ(busy_info.file_origin, &src); + EXPECT_EQ(busy_info.declared_type.user_defined_type, nullptr); + + // Find idle symbol + const auto found_enum_idle = + std::find_if(root_symbol.Children().begin(), root_symbol.Children().end(), + [](const SymbolTableNode::key_value_type& p) { + return p.first == "idle"; + }); + ASSERT_NE(found_enum_idle, root_symbol.Children().end()); + const SymbolTableNode& enum_idle(found_enum_idle->second); + const SymbolInfo& enum_idle_info(enum_idle.Value()); + EXPECT_EQ(enum_idle_info.metatype, SymbolMetaType::kTypeAlias); + EXPECT_TRUE(enum_idle_info.local_references_to_bind.empty()); + + // Find busy symbol + const auto found_enum_busy = + std::find_if(root_symbol.Children().begin(), root_symbol.Children().end(), + [](const SymbolTableNode::key_value_type& p) { + return p.first == "busy"; + }); + ASSERT_NE(found_enum_busy, root_symbol.Children().end()); + const SymbolTableNode& enum_busy(found_enum_busy->second); + const SymbolInfo& enum_busy_info(enum_busy.Value()); + EXPECT_EQ(enum_busy_info.metatype, SymbolMetaType::kTypeAlias); + EXPECT_TRUE(enum_busy_info.local_references_to_bind.empty()); + + // Three references: data and two enum constants + ASSERT_EQ(root_symbol.Value().local_references_to_bind.size(), 3); + + // Expect them to bind immediately. + + const ReferenceComponent& anon_enum_ref_comp( + root_symbol.Value().local_references_to_bind[2].LastLeaf()->Value()); + EXPECT_EQ(anon_enum_ref_comp.ref_type, ReferenceType::kImmediate); + EXPECT_EQ(anon_enum_ref_comp.required_metatype, SymbolMetaType::kEnumType); + EXPECT_EQ(anon_enum_ref_comp.resolved_symbol, &anon_enum); + + const ReferenceComponent& enum_idle_ref_comp( + root_symbol.Value().local_references_to_bind[0].LastLeaf()->Value()); + EXPECT_EQ(enum_idle_ref_comp.ref_type, ReferenceType::kImmediate); + EXPECT_EQ(enum_idle_ref_comp.required_metatype, + SymbolMetaType::kEnumConstant); + EXPECT_EQ(enum_idle_ref_comp.resolved_symbol, &busy); + + const ReferenceComponent& enum_busy_ref_comp( + root_symbol.Value().local_references_to_bind[1].LastLeaf()->Value()); + EXPECT_EQ(enum_busy_ref_comp.ref_type, ReferenceType::kImmediate); + EXPECT_EQ(enum_busy_ref_comp.required_metatype, + SymbolMetaType::kEnumConstant); + EXPECT_EQ(enum_busy_ref_comp.resolved_symbol, &idle); + + // "data"'s type is the (internal) anonymous enum type reference. + MUST_ASSIGN_LOOKUP_SYMBOL(data, root_symbol, "data"); + EXPECT_EQ(data_info.metatype, SymbolMetaType::kDataNetVariableInstance); + EXPECT_EQ(data_info.file_origin, &src); + + const DependentReferences& anon_enum_ref( + root_symbol.Value().local_references_to_bind[2]); + EXPECT_EQ(data_info.declared_type.user_defined_type, + anon_enum_ref.LastLeaf()); + + { + std::vector resolve_diagnostics; + symbol_table.Resolve(&resolve_diagnostics); + EXPECT_EMPTY_STATUSES(resolve_diagnostics); + + EXPECT_EQ(anon_enum_ref_comp.resolved_symbol, &anon_enum); // unchanged + } +} + TEST(BuildSymbolTableTest, TypedefPrimitive) { TestVerilogSourceFile src("typedef.sv", "typedef int number;\n" diff --git a/verilog/analysis/verilog_project.cc b/verilog/analysis/verilog_project.cc index d3458ecdb3..950f493894 100644 --- a/verilog/analysis/verilog_project.cc +++ b/verilog/analysis/verilog_project.cc @@ -91,6 +91,23 @@ absl::Status InMemoryVerilogSourceFile::Open() { return status_; } +absl::Status ParsedVerilogSourceFile::Open() { + state_ = State::kOpened; + status_ = absl::OkStatus(); + return status_; +} + +absl::Status ParsedVerilogSourceFile::Parse() { + state_ = State::kParsed; + status_ = absl::OkStatus(); + return status_; +} + +const verible::TextStructureView* ParsedVerilogSourceFile::GetTextStructure() + const { + return text_structure_; +} + absl::StatusOr VerilogProject::OpenFile( absl::string_view referenced_filename, absl::string_view resolved_filename, absl::string_view corpus) { diff --git a/verilog/analysis/verilog_project.h b/verilog/analysis/verilog_project.h index b2e0360944..eb7fc3ee1a 100644 --- a/verilog/analysis/verilog_project.h +++ b/verilog/analysis/verilog_project.h @@ -60,12 +60,12 @@ class VerilogSourceFile { // Attempts to lex and parse the file (without preprocessing). // Will Open() if the file is not already opened. // Depending on context, not all files are suitable for standalone parsing. - absl::Status Parse(); + virtual absl::Status Parse(); // After Open(), the underlying text structure contains at least the file's // contents. After Parse(), it may contain other analyzed structural forms. // Before Open(), this returns nullptr. - const verible::TextStructureView* GetTextStructure() const; + virtual const verible::TextStructureView* GetTextStructure() const; // Returns the first non-Ok status if there is one, else OkStatus(). absl::Status Status() const { return status_; } @@ -149,7 +149,7 @@ std::ostream& operator<<(std::ostream&, const VerilogSourceFile&); // An in-memory source file that doesn't require file-system access, // nor create temporary files. -class InMemoryVerilogSourceFile : public VerilogSourceFile { +class InMemoryVerilogSourceFile final : public VerilogSourceFile { public: // filename can be fake, it is not used to open any file. InMemoryVerilogSourceFile(absl::string_view filename, @@ -165,6 +165,34 @@ class InMemoryVerilogSourceFile : public VerilogSourceFile { const absl::string_view contents_for_open_; }; +// Source file that was already parsed and got its own TextStructure. +// Doesn't require file-system access, nor create temporary files. +class ParsedVerilogSourceFile final : public VerilogSourceFile { + public: + // filename can be fake, it is not used to open any file. + // text_structure is a pointer to a TextStructureView object of + // already parsed file. Current implementation does _not_ make a + // copy of it and expects it will be available for the lifetime of + // object of this class. + ParsedVerilogSourceFile(absl::string_view filename, + const verible::TextStructureView* text_structure, + absl::string_view corpus = "") + : VerilogSourceFile(filename, filename, corpus), + text_structure_(text_structure) {} + + // Do nothing (file contents already loaded) + absl::Status Open() override; + + // Do nothing (contents already parsed) + absl::Status Parse() override; + + // Return TextStructureView provided previously in constructor + const verible::TextStructureView* GetTextStructure() const override; + + private: + const verible::TextStructureView* text_structure_; +}; + // VerilogProject represents a set of files as a cohesive unit of compilation. // Files can include top-level translation units and preprocessor included // files. This is responsible for owning string memory that corresponds diff --git a/verilog/analysis/verilog_project_test.cc b/verilog/analysis/verilog_project_test.cc index a41ad243d9..7fd37a841d 100644 --- a/verilog/analysis/verilog_project_test.cc +++ b/verilog/analysis/verilog_project_test.cc @@ -197,6 +197,36 @@ TEST(InMemoryVerilogSourceFileTest, ParseInvalidFile) { EXPECT_EQ(&text_structure->SyntaxTree(), tree); } +TEST(ParsedVerilogSourceFileTest, ParseValidFile) { + constexpr absl::string_view text("localparam int p = 1;\n"); + std::unique_ptr analyzed_structure = + absl::make_unique(text, "internal"); + absl::Status status = analyzed_structure->Analyze(); + EXPECT_TRUE(status.ok()); + const TextStructureView& input_text_structure = analyzed_structure->Data(); + + ParsedVerilogSourceFile file("internal", &input_text_structure); + // Parse automatically opens. + EXPECT_TRUE(file.Parse().ok()); + EXPECT_TRUE(file.Status().ok()); + const TextStructureView* text_structure = + ABSL_DIE_IF_NULL(file.GetTextStructure()); + EXPECT_EQ(&input_text_structure, text_structure); + const absl::string_view owned_string_range(text_structure->Contents()); + EXPECT_EQ(owned_string_range, text); + const auto* tokens = &text_structure->TokenStream(); + EXPECT_NE(tokens, nullptr); + const auto* tree = &text_structure->SyntaxTree(); + EXPECT_NE(tree, nullptr); + + // Re-parsing doesn't change anything + EXPECT_TRUE(file.Parse().ok()); + EXPECT_TRUE(file.Status().ok()); + EXPECT_EQ(file.GetTextStructure(), text_structure); + EXPECT_EQ(&text_structure->TokenStream(), tokens); + EXPECT_EQ(&text_structure->SyntaxTree(), tree); +} + TEST(VerilogProjectTest, Initialization) { const auto tempdir = ::testing::TempDir(); VerilogProject project(tempdir, {tempdir});