| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "verilog/analysis/symbol_table.h" | |
| #include <iostream> | |
| #include <memory> | |
| #include <sstream> | |
| #include <stack> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/str_join.h" | |
| #include "common/strings/display_utils.h" | |
| #include "common/text/concrete_syntax_leaf.h" | |
| #include "common/text/concrete_syntax_tree.h" | |
| #include "common/text/token_info.h" | |
| #include "common/text/tree_context_visitor.h" | |
| #include "common/text/tree_utils.h" | |
| #include "common/text/visitors.h" | |
| #include "common/util/enum_flags.h" | |
| #include "common/util/logging.h" | |
| #include "common/util/spacer.h" | |
| #include "common/util/tree_operations.h" | |
| #include "common/util/value_saver.h" | |
| #include "verilog/CST/class.h" | |
| #include "verilog/CST/declaration.h" | |
| #include "verilog/CST/functions.h" | |
| #include "verilog/CST/macro.h" | |
| #include "verilog/CST/module.h" | |
| #include "verilog/CST/net.h" | |
| #include "verilog/CST/package.h" | |
| #include "verilog/CST/parameters.h" | |
| #include "verilog/CST/port.h" | |
| #include "verilog/CST/seq_block.h" | |
| #include "verilog/CST/statement.h" | |
| #include "verilog/CST/tasks.h" | |
| #include "verilog/CST/type.h" | |
| #include "verilog/CST/verilog_nonterminals.h" | |
| #include "verilog/analysis/verilog_project.h" | |
| #include "verilog/parser/verilog_parser.h" | |
| #include "verilog/parser/verilog_token_enum.h" | |
| namespace verilog { | |
| using verible::AutoTruncate; | |
| using verible::StringSpanOfSymbol; | |
| using verible::SyntaxTreeLeaf; | |
| using verible::SyntaxTreeNode; | |
| using verible::TokenInfo; | |
| using verible::TreeContextVisitor; | |
| using verible::ValueSaver; | |
| // Returns string_view of `text` with outermost double-quotes removed. | |
| // If `text` is not wrapped in quotes, return it as-is. | |
| static absl::string_view StripOuterQuotes(absl::string_view text) { | |
| return absl::StripSuffix(absl::StripPrefix(text, "\""), "\""); | |
| } | |
| static const verible::EnumNameMap<SymbolMetaType>& SymbolMetaTypeNames() { | |
| static const verible::EnumNameMap<SymbolMetaType> kSymbolMetaTypeNames({ | |
| // short-hand annotation for identifier reference type | |
| {"<root>", SymbolMetaType::kRoot}, | |
| {"class", SymbolMetaType::kClass}, | |
| {"module", SymbolMetaType::kModule}, | |
| {"package", SymbolMetaType::kPackage}, | |
| {"parameter", SymbolMetaType::kParameter}, | |
| {"typedef", SymbolMetaType::kTypeAlias}, | |
| {"data/net/var/instance", SymbolMetaType::kDataNetVariableInstance}, | |
| {"function", SymbolMetaType::kFunction}, | |
| {"task", SymbolMetaType::kTask}, | |
| {"struct", SymbolMetaType::kStruct}, | |
| {"enum", SymbolMetaType::kEnumType}, | |
| {"<enum constant>", SymbolMetaType::kEnumConstant}, | |
| {"interface", SymbolMetaType::kInterface}, | |
| {"<unspecified>", SymbolMetaType::kUnspecified}, | |
| {"<callable>", SymbolMetaType::kCallable}, | |
| }); | |
| return kSymbolMetaTypeNames; | |
| } | |
| std::ostream& operator<<(std::ostream& stream, SymbolMetaType symbol_type) { | |
| return SymbolMetaTypeNames().Unparse(symbol_type, stream); | |
| } | |
| static absl::string_view SymbolMetaTypeAsString(SymbolMetaType type) { | |
| return SymbolMetaTypeNames().EnumName(type); | |
| } | |
| // Root SymbolTableNode has no key, but we identify it as "$root" | |
| static constexpr absl::string_view kRoot("$root"); | |
| std::ostream& SymbolTableNodeFullPath(std::ostream& stream, | |
| const SymbolTableNode& node) { | |
| if (node.Parent() != nullptr) { | |
| SymbolTableNodeFullPath(stream, *node.Parent()) << "::" << *node.Key(); | |
| } else { | |
| stream << kRoot; | |
| } | |
| return stream; | |
| } | |
| static std::string ContextFullPath(const SymbolTableNode& context) { | |
| std::ostringstream stream; | |
| SymbolTableNodeFullPath(stream, context); | |
| return stream.str(); | |
| } | |
| std::ostream& ReferenceNodeFullPath(std::ostream& stream, | |
| const ReferenceComponentNode& node) { | |
| if (node.Parent() != nullptr) { | |
| ReferenceNodeFullPath(stream, *node.Parent()); // recursive | |
| } | |
| return node.Value().PrintPathComponent(stream); | |
| } | |
| static std::string ReferenceNodeFullPathString( | |
| const ReferenceComponentNode& node) { | |
| std::ostringstream stream; | |
| ReferenceNodeFullPath(stream, node); | |
| return stream.str(); | |
| } | |
| static std::ostream& operator<<(std::ostream& stream, | |
| const ReferenceComponentNode& ref_node) { | |
| PrintTree( | |
| ref_node, &stream, | |
| [](std::ostream& s, const ReferenceComponent& ref_comp) -> std::ostream& { | |
| return s << ref_comp; | |
| }); | |
| return stream; | |
| } | |
| // Validates iterator/pointer stability when appending new child. | |
| // Detects unwanted reallocation. | |
| static ReferenceComponentNode* CheckedNewChildReferenceNode( | |
| ReferenceComponentNode* parent, const ReferenceComponent& component) { | |
| auto& siblings = parent->Children(); | |
| if (!siblings.empty()) { | |
| CHECK_LT(siblings.size(), siblings.capacity()) | |
| << "\nReallocation would invalidate pointers to reference nodes at:\n" | |
| << *parent << "\nWhile attempting to add child:\n" | |
| << component << "\nFix: pre-allocate child nodes."; | |
| } | |
| // Otherwise, this first node had no prior siblings, so no need to check. | |
| siblings.emplace_back(component); // copy | |
| return &siblings.back(); | |
| } | |
| static absl::Status DiagnoseMemberSymbolResolutionFailure( | |
| absl::string_view name, const SymbolTableNode& context) { | |
| const absl::string_view context_name = | |
| context.Parent() == nullptr ? kRoot : *context.Key(); | |
| return absl::NotFoundError( | |
| absl::StrCat("No member symbol \"", name, "\" in parent scope (", | |
| SymbolMetaTypeAsString(context.Value().metatype), ") ", | |
| 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, | |
| VerilogProject* project) | |
| : source_(&source), | |
| token_context_(MakeTokenContext()), | |
| symbol_table_(symbol_table), | |
| current_scope_(&symbol_table_->MutableRoot()) {} | |
| std::vector<absl::Status> TakeDiagnostics() { | |
| return std::move(diagnostics_); | |
| } | |
| private: // methods | |
| void Visit(const SyntaxTreeNode& node) final { | |
| const auto tag = static_cast<NodeEnum>(node.Tag().tag); | |
| VLOG(1) << __FUNCTION__ << " [node]: " << tag; | |
| switch (tag) { | |
| case NodeEnum::kModuleDeclaration: | |
| DeclareModule(node); | |
| break; | |
| case NodeEnum::kGenerateIfClause: | |
| DeclareGenerateIf(node); | |
| break; | |
| case NodeEnum::kGenerateElseClause: | |
| DeclareGenerateElse(node); | |
| break; | |
| case NodeEnum::kPackageDeclaration: | |
| DeclarePackage(node); | |
| break; | |
| case NodeEnum::kClassDeclaration: | |
| DeclareClass(node); | |
| break; | |
| case NodeEnum::kFunctionPrototype: // fall-through | |
| case NodeEnum::kFunctionDeclaration: | |
| DeclareFunction(node); | |
| break; | |
| case NodeEnum::kFunctionHeader: | |
| SetupFunctionHeader(node); | |
| break; | |
| case NodeEnum::kClassConstructorPrototype: | |
| DeclareConstructor(node); | |
| break; | |
| case NodeEnum::kTaskPrototype: // fall-through | |
| case NodeEnum::kTaskDeclaration: | |
| DeclareTask(node); | |
| break; | |
| // No special handling needed for kTaskHeader | |
| case NodeEnum::kPortList: | |
| DeclarePorts(node); | |
| break; | |
| case NodeEnum::kPortItem: // fall-through | |
| // for function/task parameters | |
| case NodeEnum::kPortDeclaration: // fall-through | |
| case NodeEnum::kNetDeclaration: // fall-through | |
| case NodeEnum::kStructUnionMember: // fall-through | |
| case NodeEnum::kTypeDeclaration: // fall-through | |
| case NodeEnum::kDataDeclaration: | |
| DeclareData(node); | |
| break; | |
| case NodeEnum::kParamDeclaration: | |
| DeclareParameter(node); | |
| break; | |
| case NodeEnum::kTypeInfo: // fall-through | |
| case NodeEnum::kDataType: | |
| DescendDataType(node); | |
| break; | |
| case NodeEnum::kReferenceCallBase: | |
| DescendReferenceExpression(node); | |
| break; | |
| case NodeEnum::kActualParameterList: | |
| DescendActualParameterList(node); | |
| break; | |
| case NodeEnum::kPortActualList: | |
| DescendPortActualList(node); | |
| break; | |
| case NodeEnum::kArgumentList: | |
| DescendCallArgumentList(node); | |
| break; | |
| case NodeEnum::kGateInstanceRegisterVariableList: { | |
| // TODO: reserve() to guarantee pointer/iterator stability in VectorTree | |
| Descend(node); | |
| break; | |
| } | |
| case NodeEnum::kNetVariable: | |
| DeclareNet(node); | |
| break; | |
| case NodeEnum::kRegisterVariable: | |
| DeclareRegister(node); | |
| break; | |
| case NodeEnum::kGateInstance: | |
| DeclareInstance(node); | |
| break; | |
| case NodeEnum::kVariableDeclarationAssignment: | |
| DeclareVariable(node); | |
| break; | |
| case NodeEnum::kQualifiedId: | |
| HandleQualifiedId(node); | |
| break; | |
| case NodeEnum::kPreprocessorInclude: | |
| EnterIncludeFile(node); | |
| break; | |
| case NodeEnum::kExtendsList: | |
| DescendExtends(node); | |
| break; | |
| case NodeEnum::kStructType: | |
| DescendStructType(node); | |
| break; | |
| case NodeEnum::kEnumType: | |
| DescendEnumType(node); | |
| break; | |
| case NodeEnum::kLPValue: | |
| HandlePossibleImplicitDeclaration(node); | |
| break; | |
| case NodeEnum::kBindDirective: | |
| // TODO(#1241) Not handled right now. | |
| // TODO(#1255) Not handled right now. | |
| break; | |
| default: | |
| Descend(node); | |
| break; | |
| } | |
| VLOG(1) << "end of " << __FUNCTION__ << " [node]: " << tag; | |
| } | |
| // This overload enters 'scope' for the duration of the call. | |
| // New declared symbols will belong to that scope. | |
| void Descend(const SyntaxTreeNode& node, SymbolTableNode* scope) { | |
| const ValueSaver<SymbolTableNode*> save_scope(¤t_scope_, scope); | |
| Descend(node); | |
| } | |
| void Descend(const SyntaxTreeNode& node) { | |
| TreeContextVisitor::Visit(node); // maintains syntax tree Context() stack. | |
| } | |
| // RAII-class balance the Builder::references_builders_ stack. | |
| // The work of moving collecting references into the current scope is done in | |
| // the destructor. | |
| class CaptureDependentReference { | |
| public: | |
| explicit CaptureDependentReference(Builder* builder) | |
| : builder_(builder), | |
| saved_branch_point_(builder_->reference_branch_point_) { | |
| // Push stack space to capture references. | |
| builder_->reference_builders_.emplace(/* DependentReferences */); | |
| // Reset the branch point to start new named parameter/port chains | |
| // from the same context. | |
| builder_->reference_branch_point_ = nullptr; | |
| } | |
| ~CaptureDependentReference() { | |
| // This completes the capture of a chain of dependent references. | |
| // Ref() can be empty if the subtree doesn't reference any identifiers. | |
| // Empty refs are non-actionable and must be excluded. | |
| DependentReferences& ref(Ref()); | |
| if (!ref.Empty()) { | |
| builder_->current_scope_->Value().local_references_to_bind.emplace_back( | |
| std::move(ref)); | |
| } | |
| builder_->reference_builders_.pop(); | |
| builder_->reference_branch_point_ = saved_branch_point_; // restore | |
| } | |
| // Returns the chain of dependent references that were built. | |
| DependentReferences& Ref() const { | |
| return builder_->reference_builders_.top(); | |
| } | |
| private: | |
| Builder* builder_; | |
| ReferenceComponentNode* saved_branch_point_; | |
| }; | |
| void DescendReferenceExpression(const SyntaxTreeNode& reference) { | |
| // capture expressions referenced from the current scope | |
| const CaptureDependentReference capture(this); | |
| // subexpressions' references will be collected before this one | |
| Descend(reference); // no scope change | |
| } | |
| void DescendExtends(const SyntaxTreeNode& extends) { | |
| VLOG(2) << __FUNCTION__ << " from: " << CurrentScopeFullPath(); | |
| { | |
| // At this point we are already inside the scope of the class declaration, | |
| // however, the base classes should be resolved starting from the scope | |
| // that *contains* this class declaration. | |
| const ValueSaver<SymbolTableNode*> save(¤t_scope_, | |
| current_scope_->Parent()); | |
| // capture the one base class type referenced by 'extends' | |
| const CaptureDependentReference capture(this); | |
| Descend(extends); | |
| } | |
| // Link this new type reference as the base type of the current class being | |
| // declared. | |
| const DependentReferences& recent_ref = | |
| current_scope_->Parent()->Value().local_references_to_bind.back(); | |
| const ReferenceComponentNode* base_type_ref = | |
| recent_ref.LastTypeComponent(); | |
| SymbolInfo& current_declared_class_info = current_scope_->Value(); | |
| current_declared_class_info.parent_type.user_defined_type = base_type_ref; | |
| } | |
| // Traverse a subtree for a data type and collects type references | |
| // originating from the current context. | |
| // If the context is such that this type is used in a declaration, | |
| // then capture that type information to be used later. | |
| // | |
| // The state/stack management here is intended to accommodate type references | |
| // of arbitrary complexity. | |
| // A generalized type could look like: | |
| // "A#(.B(1))::C#(.D(E#(.F(0))))::G" | |
| // This should produce the following reference trees: | |
| // A -+- ::B | |
| // | | |
| // \- ::C -+- ::D | |
| // | | |
| // \- ::G | |
| // E -+- ::F | |
| // | |
| void DescendDataType(const SyntaxTreeNode& data_type_node) { | |
| VLOG(1) << __FUNCTION__ << ": " << StringSpanOfSymbol(data_type_node); | |
| const CaptureDependentReference capture(this); | |
| { | |
| // Inform that named parameter identifiers will yield parallel children | |
| // from this reference branch point. Start this out as nullptr, and set | |
| // it once an unqualified identifier is encountered that starts a | |
| // reference tree. | |
| const ValueSaver<ReferenceComponentNode*> set_branch( | |
| &reference_branch_point_, nullptr); | |
| Descend(data_type_node); | |
| // declaration_type_info_ will be restored after this closes. | |
| } | |
| if (declaration_type_info_ != nullptr) { | |
| // 'declaration_type_info_' holds the declared type we want to capture. | |
| if (verible::GetLeftmostLeaf(data_type_node) != nullptr) { | |
| declaration_type_info_->syntax_origin = &data_type_node; | |
| // Otherwise, if the type subtree contains no leaves (e.g. implicit or | |
| // void), then do not assign a syntax origin. | |
| } | |
| const DependentReferences& type_ref(capture.Ref()); | |
| if (!type_ref.Empty()) { | |
| // then some user-defined type was referenced | |
| declaration_type_info_->user_defined_type = | |
| type_ref.LastTypeComponent(); | |
| } | |
| VLOG(2) << "declared type: " << *declaration_type_info_; | |
| } | |
| // In all cases, a type is being referenced from the current scope, so add | |
| // it to the list of references to resolve (done by 'capture'). | |
| VLOG(1) << "end of " << __FUNCTION__; | |
| } | |
| void DescendActualParameterList(const SyntaxTreeNode& node) { | |
| if (reference_branch_point_ != nullptr) { | |
| // Pre-allocate siblings to guarantee pointer/iterator stability. | |
| // FindAll* will also catch actual port connections inside preprocessing | |
| // conditionals. | |
| const size_t num_params = FindAllNamedParams(node).size(); | |
| // +1 to accommodate the slot needed for a nested type reference | |
| // e.g. for "B" in "A#(.X(), .Y(), ...)::B" | |
| reference_branch_point_->Children().reserve(num_params + 1); | |
| } | |
| Descend(node); | |
| } | |
| void DescendPortActualList(const SyntaxTreeNode& node) { | |
| if (reference_branch_point_ != nullptr) { | |
| // Pre-allocate siblings to guarantee pointer/iterator stability. | |
| // FindAll* will also catch actual port connections inside preprocessing | |
| // conditionals. | |
| const size_t num_ports = FindAllActualNamedPort(node).size(); | |
| reference_branch_point_->Children().reserve(num_ports); | |
| } | |
| Descend(node); | |
| } | |
| void DescendCallArgumentList(const SyntaxTreeNode& node) { | |
| if (reference_branch_point_ != nullptr) { | |
| // Pre-allocate siblings to guarantee pointer/iterator stability. | |
| // FindAll* will also catch call arguments inside preprocessing | |
| // conditionals. | |
| const size_t num_args = FindAllNamedParams(node).size(); | |
| reference_branch_point_->Children().reserve(num_args); | |
| } | |
| Descend(node); | |
| } | |
| void DescendStructType(const SyntaxTreeNode& struct_type) { | |
| CHECK(struct_type.MatchesTag(NodeEnum::kStructType)); | |
| // Structs do not inherently have names, so they are all anonymous. | |
| // Type declarations (typedefs) create named alias elsewhere. | |
| const absl::string_view anon_name = | |
| current_scope_->Value().CreateAnonymousScope("struct"); | |
| SymbolTableNode* new_struct = DeclareScopedElementAndDescend( | |
| struct_type, anon_name, SymbolMetaType::kStruct); | |
| // Create a self-reference to this struct type so that it can be linked | |
| // for declarations that use this type. | |
| const ReferenceComponent anon_type_ref{ | |
| .identifier = anon_name, | |
| .ref_type = ReferenceType::kImmediate, | |
| .required_metatype = SymbolMetaType::kStruct, | |
| // pre-resolve this symbol immediately | |
| .resolved_symbol = new_struct, | |
| }; | |
| const CaptureDependentReference capture(this); | |
| capture.Ref().PushReferenceComponent(anon_type_ref); | |
| if (declaration_type_info_ != nullptr) { | |
| declaration_type_info_->user_defined_type = capture.Ref().LastLeaf(); | |
| } | |
| } | |
| 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<DeclarationTypeInfo*> 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 | |
| // | |
| // Consider using something different than kTypeAlias here | |
| // (which is technically an alias for a type and not for a data/variable) | |
| // e.g. kConstantAlias or even kGenericAlias. | |
| 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<DeclarationTypeInfo*> 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; | |
| VLOG(2) << "current context: " << CurrentScopeFullPath(); | |
| if (Context().DirectParentIs(NodeEnum::kParamType)) { | |
| // This identifier declares a value parameter. | |
| EmplaceTypedElementInCurrentScope(leaf, text, SymbolMetaType::kParameter); | |
| return; | |
| } | |
| if (Context().DirectParentIs(NodeEnum::kTypeAssignment)) { | |
| // This identifier declares a type parameter. | |
| EmplaceElementInCurrentScope(leaf, text, SymbolMetaType::kParameter); | |
| return; | |
| } | |
| 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 | |
| // private internals members. | |
| return; | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kUnqualifiedId, NodeEnum::kFunctionHeader})) { | |
| // We deferred adding a declared function to the current scope until this | |
| // point (from DeclareFunction()). | |
| // Note that this excludes the out-of-line definition case, | |
| // which is handled in DescendThroughOutOfLineDefinition(). | |
| const SyntaxTreeNode* decl_syntax = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTagAnyOf( | |
| {NodeEnum::kFunctionDeclaration, NodeEnum::kFunctionPrototype}); | |
| }); | |
| if (decl_syntax == nullptr) return; | |
| SymbolTableNode* declared_function = &EmplaceTypedElementInCurrentScope( | |
| *decl_syntax, text, SymbolMetaType::kFunction); | |
| // After this point, we've registered the new function with its return | |
| // type, so we can switch context over to the newly declared function | |
| // for its port interface and definition internals. | |
| current_scope_ = declared_function; | |
| return; | |
| } | |
| if (Context().DirectParentIs(NodeEnum::kClassConstructorPrototype)) { | |
| // This is a constructor or its prototype. | |
| // From verilog.y, the "new" token is directly under this prototype node, | |
| // and the full constructor definition also contains its prototype. | |
| const SyntaxTreeNode* decl_syntax = &Context().top(); | |
| SymbolTableNode* declared_function = &EmplaceTypedElementInCurrentScope( | |
| *decl_syntax, text, SymbolMetaType::kFunction); | |
| current_scope_ = declared_function; | |
| return; | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kUnqualifiedId, NodeEnum::kTaskHeader})) { | |
| // We deferred adding a declared task to the current scope until this | |
| // point (from DeclareFunction()). | |
| // Note that this excludes the out-of-line definition case, | |
| // which is handled in DescendThroughOutOfLineDefinition(). | |
| const SyntaxTreeNode* decl_syntax = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTagAnyOf( | |
| {NodeEnum::kTaskDeclaration, NodeEnum::kTaskPrototype}); | |
| }); | |
| if (decl_syntax == nullptr) return; | |
| SymbolTableNode* declared_task = EmplaceElementInCurrentScope( | |
| *decl_syntax, text, SymbolMetaType::kTask); | |
| // After this point, we've registered the new task, | |
| // so we can switch context over to the newly declared function | |
| // for its port interface and definition internals. | |
| current_scope_ = declared_task; | |
| return; | |
| } | |
| if (Context().DirectParentsAre({NodeEnum::kDataTypeImplicitIdDimensions, | |
| NodeEnum::kStructUnionMember})) { | |
| // This is a struct/union member. Add it to the enclosing scope. | |
| // e.g. "foo" in "struct { int foo; }" | |
| EmplaceTypedElementInCurrentScope( | |
| leaf, text, SymbolMetaType::kDataNetVariableInstance); | |
| return; | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kVariableDeclarationAssignment, | |
| NodeEnum::kVariableDeclarationAssignmentList, | |
| NodeEnum::kStructUnionMember})) { | |
| // This is part of a declaration covered by kVariableDeclarationAssignment | |
| // already, so do not interpret this as a reference. | |
| // e.g. "z" in "struct { int y, z; }" | |
| 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; | |
| if (Context().DirectParentIs(NodeEnum::kTypeDeclaration)) { | |
| // This identifier declares a type alias (typedef). | |
| EmplaceTypedElementInCurrentScope(leaf, text, SymbolMetaType::kTypeAlias); | |
| return; | |
| } | |
| // Capture only referencing identifiers, omit declarative identifiers. | |
| // This is set up when traversing references, e.g. types, expressions. | |
| // All of the code below takes effect inside a CaptureDependentReferences | |
| // RAII block. | |
| if (reference_builders_.empty()) return; | |
| // Building a reference, possible part of a chain or qualified | |
| // reference. | |
| DependentReferences& ref(reference_builders_.top()); | |
| const ReferenceComponent new_ref{ | |
| .identifier = text, | |
| .ref_type = InferReferenceType(), | |
| .required_metatype = InferMetaType(), | |
| }; | |
| // For instances' named ports, and types' named parameters, | |
| // add references as siblings of the same parent. | |
| // (Recall that instances form self-references). | |
| if (Context().DirectParentIsOneOf( | |
| {NodeEnum::kActualNamedPort, NodeEnum::kParamByName})) { | |
| CheckedNewChildReferenceNode(ABSL_DIE_IF_NULL(reference_branch_point_), | |
| new_ref); | |
| 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 | |
| // reference node as the point from which named parameter references | |
| // get added as siblings. | |
| // e.g. "A#(.B(...), .C(...))" would result in a reference tree: | |
| // A -+- ::B | |
| // | | |
| // \- ::C | |
| reference_branch_point_ = ref.PushReferenceComponent(new_ref); | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| const auto tag = leaf.Tag().tag; | |
| VLOG(1) << __FUNCTION__ << " [leaf]: " << VerboseToken(leaf.get()); | |
| switch (tag) { | |
| case verilog_tokentype::TK_new: // constructor name | |
| case verilog_tokentype::SymbolIdentifier: | |
| HandleIdentifier(leaf); | |
| break; | |
| case verilog_tokentype::TK_SCOPE_RES: // "::" | |
| case '.': | |
| last_hierarchy_operator_ = &leaf.get(); | |
| break; | |
| default: | |
| // TODO(hzeller): use verilog::IsIdentifierLike() ? | |
| // Using that would result in some mis-classifications. | |
| break; | |
| } | |
| VLOG(1) << "end " << __FUNCTION__ << " [leaf]:" << VerboseToken(leaf.get()); | |
| } | |
| // Distinguish between '.' and "::" hierarchy in reference components. | |
| ReferenceType InferReferenceType() const { | |
| CHECK(!reference_builders_.empty()) | |
| << "Not currently in a reference context."; | |
| const DependentReferences& ref(reference_builders_.top()); | |
| if (ref.Empty() || last_hierarchy_operator_ == nullptr) { | |
| // The root component is always treated as unqualified. | |
| // Out-of-line definitions' base/outer references must be resolved | |
| // immediately. | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kQualifiedId, // out-of-line | |
| NodeEnum::kFunctionHeader})) { | |
| return ReferenceType::kImmediate; | |
| } | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kQualifiedId, // out-of-line | |
| NodeEnum::kTaskHeader})) { | |
| return ReferenceType::kImmediate; | |
| } | |
| return ReferenceType::kUnqualified; | |
| } | |
| if (Context().DirectParentIs(NodeEnum::kParamByName)) { | |
| // Even though named parameters are referenced with ".PARAM", | |
| // they are branched off of a base reference that already points | |
| // to the type whose scope should be used, so no additional typeof() | |
| // indirection is needed. | |
| return ReferenceType::kDirectMember; | |
| } | |
| return ABSL_DIE_IF_NULL(last_hierarchy_operator_)->token_enum() == '.' | |
| ? ReferenceType::kMemberOfTypeOfParent | |
| : ReferenceType::kDirectMember; | |
| } | |
| bool QualifiedIdComponentInLastPosition() const { | |
| const SyntaxTreeNode* qualified_id = | |
| Context().NearestParentWithTag(NodeEnum::kQualifiedId); | |
| const SyntaxTreeNode* unqualified_id = | |
| Context().NearestParentWithTag(NodeEnum::kUnqualifiedId); | |
| return ABSL_DIE_IF_NULL(qualified_id)->children().back().get() == | |
| unqualified_id; | |
| } | |
| // Does the context necessitate that the symbol being referenced have a | |
| // particular metatype? | |
| SymbolMetaType InferMetaType() const { | |
| const DependentReferences& ref(reference_builders_.top()); | |
| // Out-of-line definitions' base/outer references must be resolved | |
| // immediately to a class. | |
| // Member references (inner) is a function or task, depending on header | |
| // type. | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kQualifiedId, // out-of-line | |
| NodeEnum::kFunctionHeader})) { | |
| return ref.Empty() ? SymbolMetaType::kClass : SymbolMetaType::kFunction; | |
| } | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kQualifiedId, // out-of-line | |
| NodeEnum::kTaskHeader})) { | |
| return ref.Empty() ? SymbolMetaType::kClass : SymbolMetaType::kTask; | |
| } | |
| // TODO: import references bases must be resolved as | |
| // SymbolMetaType::kPackage. | |
| if (Context().DirectParentIs(NodeEnum::kActualNamedPort)) { | |
| return SymbolMetaType::kDataNetVariableInstance; | |
| } | |
| // module type or class parameters by name | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kParamByName, NodeEnum::kActualParameterByNameList})) { | |
| return SymbolMetaType::kParameter; | |
| } | |
| // function call arguments by name | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kParamByName, NodeEnum::kArgumentList})) { | |
| return SymbolMetaType::kDataNetVariableInstance; | |
| } | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kLocalRoot, | |
| NodeEnum::kFunctionCall})) { | |
| // bare call like "function_name(...)" | |
| return SymbolMetaType::kCallable; | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kUnqualifiedId, NodeEnum::kQualifiedId, | |
| NodeEnum::kLocalRoot, NodeEnum::kFunctionCall})) { | |
| // qualified call like "pkg_or_class::function_name(...)" | |
| // Only the last component needs to be callable. | |
| if (QualifiedIdComponentInLastPosition()) { | |
| return SymbolMetaType::kCallable; | |
| } | |
| // TODO(fangism): could require parents to be kPackage or kClass | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kUnqualifiedId, NodeEnum::kMethodCallExtension})) { | |
| // method call like "obj.method_name(...)" | |
| return SymbolMetaType::kCallable; | |
| // TODO(fangism): check that method is non-static | |
| } | |
| if (Context().DirectParentsAre( | |
| {NodeEnum::kUnqualifiedId, NodeEnum::kExtendsList})) { | |
| // e.g. "base" in "class derived extends base;" | |
| return SymbolMetaType::kClass; | |
| } | |
| if (Context().DirectParentsAre({NodeEnum::kUnqualifiedId, | |
| NodeEnum::kQualifiedId, | |
| NodeEnum::kExtendsList})) { | |
| // base class is a qualified type like "pkg_or_class::class_name" | |
| // Only the last component needs to be a type. | |
| if (QualifiedIdComponentInLastPosition()) { | |
| return SymbolMetaType::kClass; | |
| } | |
| // TODO(fangism): could require parents to be kPackage or kClass | |
| } | |
| // Default: no specific metatype. | |
| return SymbolMetaType::kUnspecified; | |
| } | |
| // Creates a named element in the current scope. | |
| // Suitable for SystemVerilog language elements: functions, tasks, packages, | |
| // classes, modules, etc... | |
| SymbolTableNode* EmplaceElementInCurrentScope(const verible::Symbol& element, | |
| absl::string_view name, | |
| SymbolMetaType metatype) { | |
| const auto p = current_scope_->TryEmplace( | |
| name, SymbolInfo{metatype, source_, &element}); | |
| if (!p.second) { | |
| DiagnoseSymbolAlreadyExists(name, p.first->first); | |
| } | |
| return &p.first->second; // scope of the new (or pre-existing symbol) | |
| } | |
| // Creates a named typed element in the current scope. | |
| // Suitable for SystemVerilog language elements: nets, parameter, variables, | |
| // instances, functions (using their return types). | |
| SymbolTableNode& EmplaceTypedElementInCurrentScope( | |
| const verible::Symbol& element, absl::string_view name, | |
| SymbolMetaType metatype) { | |
| VLOG(1) << __FUNCTION__ << ": " << name << " in " << CurrentScopeFullPath(); | |
| VLOG(1) << " type info: " << *ABSL_DIE_IF_NULL(declaration_type_info_); | |
| VLOG(1) << " 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 | |
| }); | |
| if (!p.second) { | |
| DiagnoseSymbolAlreadyExists(name, p.first->first); | |
| } | |
| VLOG(1) << "end of " << __FUNCTION__ << ": " << name; | |
| return p.first->second; // scope of the new (or pre-existing symbol) | |
| } | |
| // Creates a named element in the current scope, and traverses its subtree | |
| // inside the new element's scope. | |
| // Returns the new scope. | |
| SymbolTableNode* DeclareScopedElementAndDescend(const SyntaxTreeNode& element, | |
| absl::string_view name, | |
| SymbolMetaType type) { | |
| SymbolTableNode* enter_scope = | |
| EmplaceElementInCurrentScope(element, name, type); | |
| Descend(element, enter_scope); | |
| return enter_scope; | |
| } | |
| void DeclareModule(const SyntaxTreeNode& module) { | |
| const SyntaxTreeLeaf* module_name = GetModuleName(module); | |
| if (!module_name) return; | |
| DeclareScopedElementAndDescend(module, module_name->get().text(), | |
| SymbolMetaType::kModule); | |
| } | |
| absl::string_view GetScopeNameFromGenerateBody(const SyntaxTreeNode& body) { | |
| if (body.MatchesTag(NodeEnum::kGenerateBlock)) { | |
| const SyntaxTreeNode* gen_block = GetGenerateBlockBegin(body); | |
| const TokenInfo* label = | |
| gen_block ? GetBeginLabelTokenInfo(*gen_block) : nullptr; | |
| if (label != nullptr) { | |
| // TODO: Check for a matching end-label here, and if its name matches | |
| // the begin label, then immediately create a resolved reference because | |
| // it only makes sense for it resolve to this begin. | |
| // Otherwise, do nothing with the end label. | |
| return label->text(); | |
| } | |
| } | |
| return current_scope_->Value().CreateAnonymousScope("generate"); | |
| } | |
| void DeclareGenerateIf(const SyntaxTreeNode& generate_if) { | |
| const SyntaxTreeNode* body(GetIfClauseGenerateBody(generate_if)); | |
| if (body) { | |
| DeclareScopedElementAndDescend(generate_if, | |
| GetScopeNameFromGenerateBody(*body), | |
| SymbolMetaType::kGenerate); | |
| } | |
| } | |
| void DeclareGenerateElse(const SyntaxTreeNode& generate_else) { | |
| const SyntaxTreeNode* body(GetElseClauseGenerateBody(generate_else)); | |
| if (!body) return; | |
| if (body->MatchesTag(NodeEnum::kConditionalGenerateConstruct)) { | |
| // else-if chained. Flatten the else block by not creating a new scope | |
| // and let the if-clause inside create a scope directly under the current | |
| // scope. | |
| Descend(*body); | |
| } else { | |
| DeclareScopedElementAndDescend(generate_else, | |
| GetScopeNameFromGenerateBody(*body), | |
| SymbolMetaType::kGenerate); | |
| } | |
| } | |
| void DeclarePackage(const SyntaxTreeNode& package) { | |
| const auto* token = GetPackageNameToken(package); | |
| if (!token) return; | |
| DeclareScopedElementAndDescend(package, token->text(), | |
| SymbolMetaType::kPackage); | |
| } | |
| void DeclareClass(const SyntaxTreeNode& class_node) { | |
| const SyntaxTreeLeaf* class_name = GetClassName(class_node); | |
| if (!class_name) return; | |
| DeclareScopedElementAndDescend(class_node, class_name->get().text(), | |
| SymbolMetaType::kClass); | |
| } | |
| void DeclareTask(const SyntaxTreeNode& task_node) { | |
| const ValueSaver<SymbolTableNode*> reserve_for_task_decl( | |
| ¤t_scope_); // no scope change yet | |
| Descend(task_node); | |
| } | |
| void DeclareFunction(const SyntaxTreeNode& function_node) { | |
| // Reserve a slot for the function's scope on the stack, but do not set it | |
| // until we add it in HandleIdentifier(). This deferral allows us to | |
| // evaluate the return type of the declared function as a reference in the | |
| // current context. | |
| const ValueSaver<SymbolTableNode*> reserve_for_function_decl( | |
| ¤t_scope_); // no scope change yet | |
| Descend(function_node); | |
| } | |
| void DeclareConstructor(const SyntaxTreeNode& constructor_node) { | |
| // Reserve a slot for the constructor's scope on the stack, but do not set | |
| // it until we add it in HandleIdentifier(). The effective return type of | |
| // the constructor is the class type. | |
| const ValueSaver<SymbolTableNode*> reserve_for_function_decl( | |
| ¤t_scope_); // no scope change yet | |
| const SyntaxTreeLeaf* new_keyword = | |
| GetConstructorPrototypeNewKeyword(constructor_node); | |
| // Create a self-reference to this class. | |
| const ReferenceComponent class_type_ref{ | |
| .identifier = new_keyword->get().text(), // "new" | |
| .ref_type = ReferenceType::kImmediate, | |
| .required_metatype = SymbolMetaType::kClass, | |
| // pre-resolve this symbol to the enclosing class immediately | |
| .resolved_symbol = current_scope_, // the current class | |
| }; | |
| // Build-up a reference to the constructor, rooted at the class node. | |
| const CaptureDependentReference capture(this); | |
| capture.Ref().PushReferenceComponent(class_type_ref); | |
| DeclarationTypeInfo decl_type_info{ | |
| // There is no actual source text that references the type here. | |
| // We arbitrarily designate the 'new' keyword as the reference point. | |
| .syntax_origin = new_keyword, | |
| .user_defined_type = capture.Ref().LastLeaf(), | |
| }; | |
| const ValueSaver<DeclarationTypeInfo*> function_return_type( | |
| &declaration_type_info_, &decl_type_info); | |
| Descend(constructor_node); | |
| } | |
| void DeclarePorts(const SyntaxTreeNode& port_list) { | |
| // For out-of-line function declarations, do not re-declare ports that | |
| // already came from the method prototype. | |
| // We designate the prototype as the source-of-truth because in Verilog, | |
| // port *names* are part of the public interface (allowing calling with | |
| // named parameter assignments, unlike C++ function calls). | |
| // LRM 8.24: "The out-of-block method declaration shall match the prototype | |
| // declaration exactly, with the following exceptions..." | |
| { | |
| const SyntaxTreeNode* function_header = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTag(NodeEnum::kFunctionHeader); | |
| }); | |
| if (function_header != nullptr) { | |
| const SyntaxTreeNode& id = verible::SymbolCastToNode( | |
| *ABSL_DIE_IF_NULL(GetFunctionHeaderId(*function_header))); | |
| if (id.MatchesTag(NodeEnum::kQualifiedId)) { | |
| // For now, ignore the out-of-line port declarations. | |
| // TODO: Diagnose port type/name mismatches between prototypes' and | |
| // out-of-line headers' ports. | |
| return; | |
| } | |
| } | |
| } | |
| { | |
| const SyntaxTreeNode* task_header = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTag(NodeEnum::kTaskHeader); | |
| }); | |
| if (task_header != nullptr) { | |
| const SyntaxTreeNode& id = verible::SymbolCastToNode( | |
| *ABSL_DIE_IF_NULL(GetTaskHeaderId(*task_header))); | |
| if (id.MatchesTag(NodeEnum::kQualifiedId)) { | |
| // For now, ignore the out-of-line port declarations. | |
| // TODO: Diagnose port type/name mismatches between prototypes' and | |
| // out-of-line headers' ports. | |
| return; | |
| } | |
| } | |
| } | |
| // In all other cases, declare ports normally at the declaration site. | |
| Descend(port_list); | |
| } | |
| // Capture the declared function's return type. | |
| void SetupFunctionHeader(const SyntaxTreeNode& function_header) { | |
| DeclarationTypeInfo decl_type_info; | |
| const ValueSaver<DeclarationTypeInfo*> function_return_type( | |
| &declaration_type_info_, &decl_type_info); | |
| Descend(function_header); | |
| // decl_type_info will be safely copied away in HandleIdentifier(). | |
| } | |
| // TODO: functions and tasks, which could appear as out-of-line definitions. | |
| void DeclareParameter(const SyntaxTreeNode& param_decl_node) { | |
| CHECK(param_decl_node.MatchesTag(NodeEnum::kParamDeclaration)); | |
| DeclarationTypeInfo decl_type_info; | |
| // Set declaration_type_info_ to capture any user-defined type used to | |
| // declare data/variables/instances. | |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, | |
| &decl_type_info); | |
| Descend(param_decl_node); | |
| } | |
| // Declares one or more variables/instances/nets. | |
| void DeclareData(const SyntaxTreeNode& data_decl_node) { | |
| VLOG(1) << __FUNCTION__; | |
| DeclarationTypeInfo decl_type_info; | |
| // Set declaration_type_info_ to capture any user-defined type used to | |
| // declare data/variables/instances. | |
| const ValueSaver<DeclarationTypeInfo*> save_type(&declaration_type_info_, | |
| &decl_type_info); | |
| Descend(data_decl_node); | |
| VLOG(1) << "end of " << __FUNCTION__; | |
| } | |
| // Declare one (of potentially multiple) instances in a single declaration | |
| // statement. | |
| void DeclareInstance(const SyntaxTreeNode& instance) { | |
| const verible::TokenInfo* instance_name_token = | |
| GetModuleInstanceNameTokenInfoFromGateInstance(instance); | |
| if (!instance_name_token) return; | |
| const absl::string_view instance_name(instance_name_token->text()); | |
| const SymbolTableNode& new_instance(EmplaceTypedElementInCurrentScope( | |
| instance, instance_name, SymbolMetaType::kDataNetVariableInstance)); | |
| // Also create a DependentReferences chain starting with this named instance | |
| // so that named port references are direct children of this reference root. | |
| // This is a self-reference. | |
| const CaptureDependentReference capture(this); | |
| capture.Ref().PushReferenceComponent(ReferenceComponent{ | |
| .identifier = instance_name, | |
| .ref_type = ReferenceType::kUnqualified, | |
| .required_metatype = SymbolMetaType::kDataNetVariableInstance, | |
| // Start with its type already resolved to the node we just declared. | |
| .resolved_symbol = &new_instance, | |
| }); | |
| // Inform that named port identifiers will yield parallel children from | |
| // this reference branch point. | |
| const ValueSaver<ReferenceComponentNode*> set_branch( | |
| &reference_branch_point_, capture.Ref().components.get()); | |
| // No change of scope, but named ports will be resolved with respect to the | |
| // decl_type_info's scope later. | |
| Descend(instance); // visit parameter/port connections, etc. | |
| } | |
| void DeclareNet(const SyntaxTreeNode& net_variable) { | |
| const SyntaxTreeLeaf* net_variable_name = | |
| GetNameLeafOfNetVariable(net_variable); | |
| if (!net_variable_name) return; | |
| const absl::string_view net_name(net_variable_name->get().text()); | |
| EmplaceTypedElementInCurrentScope(net_variable, net_name, | |
| SymbolMetaType::kDataNetVariableInstance); | |
| Descend(net_variable); | |
| } | |
| void DeclareRegister(const SyntaxTreeNode& reg_variable) { | |
| const SyntaxTreeLeaf* register_variable_name = | |
| GetNameLeafOfRegisterVariable(reg_variable); | |
| if (!register_variable_name) return; | |
| const absl::string_view net_name(register_variable_name->get().text()); | |
| EmplaceTypedElementInCurrentScope(reg_variable, net_name, | |
| SymbolMetaType::kDataNetVariableInstance); | |
| Descend(reg_variable); | |
| } | |
| void DeclareVariable(const SyntaxTreeNode& variable) { | |
| const SyntaxTreeLeaf* unqualified_id = | |
| GetUnqualifiedIdFromVariableDeclarationAssignment(variable); | |
| if (unqualified_id) { | |
| const absl::string_view var_name(unqualified_id->get().text()); | |
| EmplaceTypedElementInCurrentScope( | |
| variable, var_name, SymbolMetaType::kDataNetVariableInstance); | |
| } | |
| Descend(variable); | |
| } | |
| void DiagnoseSymbolAlreadyExists(absl::string_view name, | |
| absl::string_view previous) { | |
| std::ostringstream here_print; | |
| here_print << source_->GetTextStructure()->GetRangeForText(name); | |
| std::ostringstream previous_print; | |
| previous_print << source_->GetTextStructure()->GetRangeForText(previous); | |
| // TODO(hzeller): output in some structured form easy to use downstream. | |
| diagnostics_.push_back(absl::AlreadyExistsError(absl::StrCat( | |
| source_->ReferencedPath(), ":", here_print.str(), " Symbol \"", name, | |
| "\" is already defined in the ", CurrentScopeFullPath(), " scope at ", | |
| previous_print.str()))); | |
| } | |
| absl::StatusOr<SymbolTableNode*> LookupOrInjectOutOfLineDefinition( | |
| const SyntaxTreeNode& qualified_id, SymbolMetaType metatype, | |
| const SyntaxTreeNode* definition_syntax) { | |
| // e.g. "function int class_c::func(...); ... endfunction" | |
| // Use a DependentReference object to establish a self-reference. | |
| CaptureDependentReference capture(this); | |
| Descend(qualified_id); | |
| DependentReferences& ref(capture.Ref()); | |
| // Expecting only two-level reference "outer::inner". | |
| CHECK_EQ(ABSL_DIE_IF_NULL(ref.components)->Children().size(), 1); | |
| // Must resolve base, instead of deferring to resolve phase. | |
| // Do not inject the outer_scope (class name) into the current scope. | |
| // Reject injections into non-classes. | |
| const auto outer_scope_or_status = | |
| ref.ResolveOnlyBaseLocally(current_scope_); | |
| if (!outer_scope_or_status.ok()) { | |
| return outer_scope_or_status.status(); | |
| } | |
| SymbolTableNode* outer_scope = ABSL_DIE_IF_NULL(*outer_scope_or_status); | |
| // Lookup inner symbol in outer_scope, but also allow injection of the | |
| // inner symbol name into the outer_scope (with diagnostic). | |
| ReferenceComponent& inner_ref = ref.components->Children().front().Value(); | |
| const absl::string_view inner_key = inner_ref.identifier; | |
| const auto p = outer_scope->TryEmplace( | |
| inner_key, SymbolInfo{metatype, source_, definition_syntax}); | |
| SymbolTableNode* inner_symbol = &p.first->second; | |
| if (p.second) { | |
| // If injection succeeded, then the outer_scope did not already contain a | |
| // forward declaration of the inner symbol to be defined. | |
| // Diagnose this non-fatally, but continue. | |
| diagnostics_.push_back( | |
| DiagnoseMemberSymbolResolutionFailure(inner_key, *outer_scope)); | |
| } else { | |
| // Use pre-existing symbol table entry created from the prototype. | |
| // Check that out-of-line and prototype symbol metatypes match. | |
| const SymbolMetaType original_metatype = inner_symbol->Value().metatype; | |
| if (original_metatype != metatype) { | |
| return absl::AlreadyExistsError( | |
| absl::StrCat(SymbolMetaTypeAsString(original_metatype), " ", | |
| ContextFullPath(*inner_symbol), | |
| " cannot be redefined out-of-line as a ", | |
| SymbolMetaTypeAsString(metatype))); | |
| } | |
| } | |
| // Resolve this self-reference immediately. | |
| inner_ref.resolved_symbol = inner_symbol; | |
| return inner_symbol; // mutable for purpose of constructing definition | |
| } | |
| void DescendThroughOutOfLineDefinition(const SyntaxTreeNode& qualified_id, | |
| SymbolMetaType type, | |
| const SyntaxTreeNode* decl_syntax) { | |
| const auto inner_symbol_or_status = | |
| LookupOrInjectOutOfLineDefinition(qualified_id, type, decl_syntax); | |
| // Change the current scope (which was set up on the stack by | |
| // kFunctionDeclaration or kTaskDeclaration) for the rest of the | |
| // definition. | |
| if (inner_symbol_or_status.ok()) { | |
| current_scope_ = *inner_symbol_or_status; | |
| Descend(qualified_id); | |
| } else { | |
| // On failure, skip the entire definition because there is no place | |
| // to add its local symbols. | |
| diagnostics_.push_back(inner_symbol_or_status.status()); | |
| } | |
| } | |
| void HandleQualifiedId(const SyntaxTreeNode& qualified_id) { | |
| switch (static_cast<NodeEnum>(Context().top().Tag().tag)) { | |
| case NodeEnum::kFunctionHeader: { | |
| const SyntaxTreeNode* decl_syntax = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTagAnyOf({NodeEnum::kFunctionDeclaration, | |
| NodeEnum::kFunctionPrototype}); | |
| }); | |
| DescendThroughOutOfLineDefinition(qualified_id, | |
| SymbolMetaType::kFunction, | |
| ABSL_DIE_IF_NULL(decl_syntax)); | |
| break; | |
| } | |
| case NodeEnum::kTaskHeader: { | |
| const SyntaxTreeNode* decl_syntax = | |
| Context().NearestParentMatching([](const SyntaxTreeNode& node) { | |
| return node.MatchesTagAnyOf( | |
| {NodeEnum::kTaskDeclaration, NodeEnum::kTaskPrototype}); | |
| }); | |
| DescendThroughOutOfLineDefinition(qualified_id, SymbolMetaType::kTask, | |
| ABSL_DIE_IF_NULL(decl_syntax)); | |
| break; | |
| } | |
| default: | |
| // Treat this as a reference, not an out-of-line definition. | |
| Descend(qualified_id); | |
| break; | |
| } | |
| } | |
| void EnterIncludeFile(const SyntaxTreeNode& preprocessor_include) { | |
| const SyntaxTreeLeaf* included_filename = | |
| GetFileFromPreprocessorInclude(preprocessor_include); | |
| if (included_filename == nullptr) return; | |
| const absl::string_view filename_text = included_filename->get().text(); | |
| // Remove the double quotes from the filename. | |
| const absl::string_view filename_unquoted = StripOuterQuotes(filename_text); | |
| VLOG(1) << "got: `include \"" << filename_unquoted << "\""; | |
| // Opening included file requires a VerilogProject. | |
| // Open this file (could be first time, or previously opened). | |
| VerilogProject* project = symbol_table_->project_; | |
| if (project == nullptr) return; // Without project, ignore. | |
| const auto status_or_file = project->OpenIncludedFile(filename_unquoted); | |
| if (!status_or_file.ok()) { | |
| diagnostics_.push_back(status_or_file.status()); | |
| // Errors can be retrieved later. | |
| return; | |
| } | |
| VerilogSourceFile* const included_file = *status_or_file; | |
| if (included_file == nullptr) return; | |
| VLOG(1) << "opened include file: " << included_file->ResolvedPath(); | |
| const auto parse_status = included_file->Parse(); | |
| if (!parse_status.ok()) { | |
| diagnostics_.push_back(parse_status); | |
| // For now, don't bother attempting to parse a partial syntax tree. | |
| // This would be best handled in the future with actual preprocessing. | |
| return; | |
| } | |
| // Depending on application, one may wish to avoid re-processing the same | |
| // included file. If desired, add logic to return early here. | |
| { // Traverse included file's syntax tree. | |
| const ValueSaver<const VerilogSourceFile*> includer(&source_, | |
| included_file); | |
| const ValueSaver<TokenInfo::Context> save_context_text( | |
| &token_context_, MakeTokenContext()); | |
| included_file->GetTextStructure()->SyntaxTree()->Accept(this); | |
| } | |
| } | |
| std::string CurrentScopeFullPath() const { | |
| return ContextFullPath(*current_scope_); | |
| } | |
| verible::TokenWithContext VerboseToken(const TokenInfo& token) const { | |
| return verible::TokenWithContext{token, token_context_}; | |
| } | |
| TokenInfo::Context MakeTokenContext() const { | |
| return TokenInfo::Context( | |
| source_->GetTextStructure()->Contents(), | |
| [](std::ostream& stream, int e) { stream << verilog_symbol_name(e); }); | |
| } | |
| private: // data | |
| // Points to the source file that is the origin of symbols. | |
| // This changes when opening preprocess-included files. | |
| // TODO(fangism): maintain a vector/stack of these for richer diagnostics | |
| const VerilogSourceFile* source_; | |
| // For human-readable debugging. | |
| // This should be constructed using MakeTokenContext(), after setting | |
| // 'source_'. | |
| TokenInfo::Context token_context_; | |
| // The symbol table to build, never nullptr. | |
| SymbolTable* const symbol_table_; | |
| // The remaining fields are mutable state: | |
| // This is the current scope where encountered definitions register their | |
| // symbols, never nullptr. | |
| // There is no need to maintain a stack because SymbolTableNodes already link | |
| // to their parents. | |
| SymbolTableNode* current_scope_; | |
| // Stack of references. | |
| // A stack is needed to support nested type references like "A#(B(#(C)))", | |
| // and nested expressions like "f(g(h))" | |
| std::stack<DependentReferences> reference_builders_; | |
| // When creating branched references, like with instances' named ports, | |
| // set this to the nearest branch point. | |
| // This will signal to the reference builder that parallel children | |
| // are to be added, as opposed to deeper descendants. | |
| ReferenceComponentNode* reference_branch_point_ = nullptr; | |
| // For a data/instance/variable declaration statement, this is the declared | |
| // type (could be primitive or named-user-defined). | |
| // For functions, this is the return type. | |
| // For constructors, this is the class type. | |
| // Set this type before traversing declared instances and variables to capture | |
| // the type of the declaration. Unset this to prevent type capture. | |
| // Such declarations cannot nest, so a stack is not needed. | |
| DeclarationTypeInfo* declaration_type_info_ = nullptr; | |
| // Update to either "::" or '.'. | |
| const TokenInfo* last_hierarchy_operator_ = nullptr; | |
| // Collection of findings that might be considered compiler/tool errors in a | |
| // real toolchain. For example: attempt to redefine symbol. | |
| std::vector<absl::Status> diagnostics_; | |
| }; | |
| void ReferenceComponent::VerifySymbolTableRoot( | |
| const SymbolTableNode* root) const { | |
| if (resolved_symbol != nullptr) { | |
| CHECK_EQ(resolved_symbol->Root(), root) | |
| << "Resolved symbols must point to a node in the same SymbolTable."; | |
| } | |
| } | |
| absl::Status ReferenceComponent::MatchesMetatype( | |
| SymbolMetaType found_metatype) const { | |
| switch (required_metatype) { | |
| case SymbolMetaType::kUnspecified: | |
| return absl::OkStatus(); | |
| case SymbolMetaType::kCallable: | |
| if (found_metatype == SymbolMetaType::kFunction || | |
| found_metatype == SymbolMetaType::kTask) { | |
| return absl::OkStatus(); | |
| } | |
| break; | |
| case SymbolMetaType::kClass: | |
| if (found_metatype == SymbolMetaType::kClass || | |
| found_metatype == SymbolMetaType::kTypeAlias) { | |
| // Where a class is expected, a typedef could be accepted. | |
| return absl::OkStatus(); | |
| } | |
| break; | |
| default: | |
| if (required_metatype == found_metatype) return absl::OkStatus(); | |
| break; | |
| } | |
| // Otherwise, mismatched metatype. | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Expecting reference \"", identifier, "\" to resolve to a ", | |
| SymbolMetaTypeAsString(required_metatype), ", but found a ", | |
| SymbolMetaTypeAsString(found_metatype), ".")); | |
| } | |
| absl::Status ReferenceComponent::ResolveSymbol( | |
| const SymbolTableNode& resolved) { | |
| // Verify metatype match. | |
| if (auto metatype_match_status = MatchesMetatype(resolved.Value().metatype); | |
| !metatype_match_status.ok()) { | |
| VLOG(2) << metatype_match_status.message(); | |
| return metatype_match_status; | |
| } | |
| VLOG(2) << " resolved: " << ContextFullPath(resolved); | |
| resolved_symbol = &resolved; | |
| return absl::OkStatus(); | |
| } | |
| const ReferenceComponentNode* DependentReferences::LastLeaf() const { | |
| if (components == nullptr) return nullptr; | |
| const ReferenceComponentNode* node = components.get(); | |
| while (!is_leaf(*node)) node = &node->Children().front(); | |
| return node; | |
| } | |
| // RefType can be ReferenceComponentNode or const ReferenceComponentNode. | |
| template <typename RefType> | |
| static RefType* ReferenceLastTypeComponent(RefType* node) { | |
| // This references a type that may be nested and have name parameters. | |
| // From A#(.B())::C#(.D()), we want C as the desired type component. | |
| while (!is_leaf(*node)) { | |
| // There should be at most one non-parameter at each branch point, | |
| // so stop at the first one found. | |
| // In the above example, this would find "C" among {"B", "C"} inside "A". | |
| auto& branches(node->Children()); | |
| const auto found = std::find_if( | |
| branches.begin(), branches.end(), [](const ReferenceComponentNode& n) { | |
| return n.Value().required_metatype != SymbolMetaType::kParameter; | |
| }); | |
| // If there are only parameters referenced, then this code is the last | |
| // component we want. | |
| if (found == branches.end()) return node; | |
| // Otherwise, continue searching along the non-parameter branch. | |
| node = &*found; | |
| } | |
| return node; | |
| } | |
| const ReferenceComponentNode* DependentReferences::LastTypeComponent() const { | |
| // This references a type that may be nested and have name parameters. | |
| // From A#(.B())::C#(.D()), we want C as the desired type component. | |
| if (components == nullptr) return nullptr; | |
| const ReferenceComponentNode* node = components.get(); | |
| return ReferenceLastTypeComponent(node); | |
| } | |
| ReferenceComponentNode* DependentReferences::LastTypeComponent() { | |
| if (components == nullptr) return nullptr; | |
| ReferenceComponentNode* node = components.get(); | |
| return ReferenceLastTypeComponent(node); | |
| } | |
| ReferenceComponentNode* DependentReferences::PushReferenceComponent( | |
| const ReferenceComponent& component) { | |
| VLOG(3) << __FUNCTION__ << ", id: " << component.identifier; | |
| ReferenceComponentNode* new_child; | |
| if (Empty()) { | |
| components = std::make_unique<ReferenceComponentNode>(component); // copy | |
| new_child = components.get(); | |
| } else { | |
| // Find the last node from which references can be grown. | |
| // Exclude type named parameters. | |
| ReferenceComponentNode* node = LastTypeComponent(); | |
| new_child = CheckedNewChildReferenceNode(node, component); | |
| } | |
| VLOG(3) << "end of " << __FUNCTION__ << ":\n" << *this; | |
| return new_child; | |
| } | |
| void DependentReferences::VerifySymbolTableRoot( | |
| const SymbolTableNode* root) const { | |
| if (components != nullptr) { | |
| ApplyPreOrder(*components, [=](const ReferenceComponent& component) { | |
| component.VerifySymbolTableRoot(root); | |
| }); | |
| } | |
| } | |
| std::ostream& operator<<(std::ostream& stream, | |
| const DependentReferences& dep_refs) { | |
| if (dep_refs.components == nullptr) return stream << "(empty-ref)"; | |
| return stream << *dep_refs.components; | |
| } | |
| // Follow type aliases through canonical type. | |
| static const SymbolTableNode* CanonicalizeTypeForMemberLookup( | |
| const SymbolTableNode& context) { | |
| VLOG(2) << __FUNCTION__; | |
| const SymbolTableNode* current_context = &context; | |
| do { | |
| VLOG(2) << " -> " << ContextFullPath(*current_context); | |
| if (current_context->Value().metatype != SymbolMetaType::kTypeAlias) break; | |
| const ReferenceComponentNode* ref_type = | |
| current_context->Value().declared_type.user_defined_type; | |
| if (ref_type == nullptr) { | |
| // Could be a primitive type. | |
| return nullptr; | |
| } | |
| current_context = ref_type->Value().resolved_symbol; | |
| // TODO: We haven't guaranteed that typedefs have been resolved in order, | |
| // so these will need to be resolved on-demand in the future. | |
| } while (current_context != nullptr); | |
| // TODO: the return value currently does not distinguish between a failed | |
| // resolution of a dependent symbol and a primitive referenced type; | |
| // both yield a nullptr. | |
| return current_context; | |
| } | |
| // Search through base class's scopes for a symbol. | |
| static const SymbolTableNode* LookupSymbolThroughInheritedScopes( | |
| const SymbolTableNode& context, absl::string_view symbol) { | |
| const SymbolTableNode* current_context = &context; | |
| do { | |
| // Look directly in current scope. | |
| const auto found = current_context->Find(symbol); | |
| if (found != current_context->end()) { | |
| return &found->second; | |
| } | |
| // TODO: lookup imported namespaces and symbols | |
| // Point to next inherited scope. | |
| const auto* base_type = | |
| current_context->Value().parent_type.user_defined_type; | |
| if (base_type == nullptr) break; | |
| const SymbolTableNode* resolved_base = base_type->Value().resolved_symbol; | |
| // TODO: attempt to resolve on-demand because resolve ordering is not | |
| // guaranteed. | |
| if (resolved_base == nullptr) return nullptr; | |
| // base type could be a typedef, so canonicalize | |
| current_context = CanonicalizeTypeForMemberLookup(*resolved_base); | |
| } while (current_context != nullptr); | |
| return nullptr; // resolution failed | |
| } | |
| // Search up-scope, stopping at the first symbol found in the nearest scope. | |
| static const SymbolTableNode* LookupSymbolUpwards( | |
| const SymbolTableNode& context, absl::string_view symbol) { | |
| const SymbolTableNode* current_context = &context; | |
| do { | |
| const SymbolTableNode* found = | |
| LookupSymbolThroughInheritedScopes(*current_context, symbol); | |
| if (found != nullptr) return found; | |
| // Point to next enclosing scope. | |
| current_context = current_context->Parent(); | |
| } while (current_context != nullptr); | |
| return nullptr; // resolution failed | |
| } | |
| static absl::Status DiagnoseUnqualifiedSymbolResolutionFailure( | |
| absl::string_view name, const SymbolTableNode& context) { | |
| return absl::NotFoundError(absl::StrCat("Unable to resolve symbol \"", name, | |
| "\" from context ", | |
| ContextFullPath(context), ".")); | |
| } | |
| static void ResolveReferenceComponentNodeLocal(ReferenceComponentNode* node, | |
| const SymbolTableNode& context) { | |
| ReferenceComponent& component(node->Value()); | |
| VLOG(2) << __FUNCTION__ << ": " << component; | |
| // If already resolved, skip. | |
| if (component.resolved_symbol != nullptr) return; // already bound | |
| const absl::string_view key(component.identifier); | |
| CHECK(node->Parent() == nullptr); // is root | |
| // root node: lookup this symbol from its context upward | |
| CHECK_EQ(component.ref_type, ReferenceType::kUnqualified); | |
| // Only try to resolve using the same scope in which the reference appeared, | |
| // local, without upward search. | |
| const auto found = context.Find(key); | |
| if (found != context.end()) { | |
| component.resolved_symbol = &found->second; | |
| } | |
| } | |
| static void ResolveUnqualifiedName(ReferenceComponent* component, | |
| const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) { | |
| VLOG(2) << __FUNCTION__ << ": " << component; | |
| const absl::string_view key(component->identifier); | |
| // Find the first symbol whose name matches, without regard to its metatype. | |
| const SymbolTableNode* resolved = LookupSymbolUpwards(context, key); | |
| if (resolved == nullptr) { | |
| diagnostics->emplace_back( | |
| DiagnoseUnqualifiedSymbolResolutionFailure(key, context)); | |
| return; | |
| } | |
| const auto resolve_status = component->ResolveSymbol(*resolved); | |
| if (!resolve_status.ok()) { | |
| diagnostics->push_back(resolve_status); | |
| } | |
| VLOG(2) << "end of " << __FUNCTION__; | |
| } | |
| // Search this scope directly for a symbol, without any upward/inheritance | |
| // lookups. | |
| static void ResolveImmediateMember(ReferenceComponent* component, | |
| const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) { | |
| VLOG(2) << __FUNCTION__ << ": " << component; | |
| const absl::string_view key(component->identifier); | |
| const auto found = context.Find(key); | |
| if (found == context.end()) { | |
| diagnostics->emplace_back( | |
| DiagnoseMemberSymbolResolutionFailure(key, context)); | |
| return; | |
| } | |
| const SymbolTableNode& found_symbol = found->second; | |
| const auto resolve_status = component->ResolveSymbol(found_symbol); | |
| if (!resolve_status.ok()) { | |
| diagnostics->push_back(resolve_status); | |
| } | |
| VLOG(2) << "end of " << __FUNCTION__; | |
| } | |
| static void ResolveDirectMember(ReferenceComponent* component, | |
| const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) { | |
| VLOG(2) << __FUNCTION__ << ": " << component; | |
| // Canonicalize context if it an alias. | |
| const SymbolTableNode* canonical_context = | |
| CanonicalizeTypeForMemberLookup(context); | |
| if (canonical_context == nullptr) { | |
| // TODO: diagnostic could be improved by following each typedef indirection. | |
| diagnostics->push_back(absl::InvalidArgumentError( | |
| absl::StrCat("Canonical type of ", ContextFullPath(context), | |
| " does not have any members."))); | |
| return; | |
| } | |
| const absl::string_view key(component->identifier); | |
| const auto* found = | |
| LookupSymbolThroughInheritedScopes(*canonical_context, key); | |
| if (found == nullptr) { | |
| diagnostics->emplace_back( | |
| DiagnoseMemberSymbolResolutionFailure(key, *canonical_context)); | |
| return; | |
| } | |
| const SymbolTableNode& found_symbol = *found; | |
| const auto resolve_status = component->ResolveSymbol(found_symbol); | |
| if (!resolve_status.ok()) { | |
| diagnostics->push_back(resolve_status); | |
| } | |
| VLOG(2) << "end of " << __FUNCTION__; | |
| } | |
| // This is the primary function that resolves references. | |
| // Dependent (parent) nodes must already be resolved before attempting to | |
| // resolve children references (guaranteed by calling this in a pre-order | |
| // traversal). | |
| static void ResolveReferenceComponentNode( | |
| ReferenceComponentNode* node, const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) { | |
| ReferenceComponent& component(node->Value()); | |
| VLOG(2) << __FUNCTION__ << ": " << component; | |
| if (component.resolved_symbol != nullptr) return; // already bound | |
| switch (component.ref_type) { | |
| case ReferenceType::kUnqualified: { | |
| // root node: lookup this symbol from its context upward | |
| CHECK(node->Parent() == nullptr); | |
| ResolveUnqualifiedName(&component, context, diagnostics); | |
| break; | |
| } | |
| case ReferenceType::kImmediate: { | |
| ResolveImmediateMember(&component, context, diagnostics); | |
| break; | |
| } | |
| case ReferenceType::kDirectMember: { | |
| // Use parent's scope (if resolved successfully) to resolve this node. | |
| const ReferenceComponent& parent_component(node->Parent()->Value()); | |
| const SymbolTableNode* parent_scope = parent_component.resolved_symbol; | |
| if (parent_scope == nullptr) return; // leave this subtree unresolved | |
| ResolveDirectMember(&component, *parent_scope, diagnostics); | |
| break; | |
| } | |
| case ReferenceType::kMemberOfTypeOfParent: { | |
| // Use parent's type's scope (if resolved successfully) to resolve this | |
| // node. Get the type of the object from the parent component. | |
| const ReferenceComponent& parent_component(node->Parent()->Value()); | |
| const SymbolTableNode* parent_scope = parent_component.resolved_symbol; | |
| if (parent_scope == nullptr) return; // leave this subtree unresolved | |
| const DeclarationTypeInfo& type_info = | |
| parent_scope->Value().declared_type; | |
| // Primitive types do not have members. | |
| if (type_info.user_defined_type == nullptr) { | |
| diagnostics->push_back(absl::InvalidArgumentError( | |
| absl::StrCat("Type of parent reference ", | |
| ReferenceNodeFullPathString(*node->Parent()), " (", | |
| verible::StringSpanOfSymbol(*type_info.syntax_origin), | |
| ") does not have any members."))); | |
| return; | |
| } | |
| // This referenced object's scope is not a parent of this node, and | |
| // thus, not guaranteed to have been resolved first. | |
| // TODO(fangism): resolve on-demand | |
| const SymbolTableNode* type_scope = | |
| type_info.user_defined_type->Value().resolved_symbol; | |
| if (type_scope == nullptr) return; | |
| ResolveDirectMember(&component, *type_scope, diagnostics); | |
| break; | |
| } | |
| } | |
| VLOG(2) << "end of " << __FUNCTION__; | |
| } | |
| ReferenceComponentMap ReferenceComponentNodeMapView( | |
| const ReferenceComponentNode& node) { | |
| ReferenceComponentMap map_view; | |
| for (const auto& child : node.Children()) { | |
| map_view.emplace(std::make_pair(child.Value().identifier, &child)); | |
| } | |
| return map_view; | |
| } | |
| void DependentReferences::Resolve( | |
| const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) const { | |
| VLOG(1) << __FUNCTION__; | |
| if (components == nullptr) return; | |
| // References are arranged in dependency trees. | |
| // Parent node references must be resolved before children nodes, | |
| // hence a pre-order traversal. | |
| ApplyPreOrder(*components, | |
| [&context, diagnostics](ReferenceComponentNode& node) { | |
| ResolveReferenceComponentNode(&node, context, diagnostics); | |
| // TODO: minor optimization, when resolution for a node fails, | |
| // skip checking that node's subtree; early terminate. | |
| }); | |
| VLOG(1) << "end of " << __FUNCTION__; | |
| } | |
| void DependentReferences::ResolveLocally(const SymbolTableNode& context) const { | |
| if (components == nullptr) return; | |
| // Only attempt to resolve the reference root, and none of its subtrees. | |
| ResolveReferenceComponentNodeLocal(components.get(), context); | |
| } | |
| absl::StatusOr<SymbolTableNode*> DependentReferences::ResolveOnlyBaseLocally( | |
| SymbolTableNode* context) { | |
| // Similar lookup to ResolveReferenceComponentNodeLocal() but allows | |
| // mutability of 'context' for injecting out-of-line definitions. | |
| ReferenceComponent& base(ABSL_DIE_IF_NULL(components)->Value()); | |
| CHECK(base.ref_type == ReferenceType::kUnqualified || | |
| base.ref_type == ReferenceType::kImmediate) | |
| << "Inconsistent reference type: " << base.ref_type; | |
| const absl::string_view key(base.identifier); | |
| const auto found = context->Find(key); | |
| if (found == context->end()) { | |
| return DiagnoseMemberSymbolResolutionFailure(key, *context); | |
| } | |
| SymbolTableNode& resolved = found->second; | |
| // If metatype doesn't match what is expected, then fail. | |
| const auto resolve_status = base.ResolveSymbol(resolved); | |
| if (!resolve_status.ok()) { | |
| return resolve_status; | |
| } | |
| return &resolved; | |
| } | |
| std::ostream& operator<<(std::ostream& stream, ReferenceType ref_type) { | |
| static const verible::EnumNameMap<ReferenceType> kReferenceTypeNames({ | |
| // short-hand annotation for identifier reference type | |
| {"@", ReferenceType::kUnqualified}, | |
| {"!", ReferenceType::kImmediate}, | |
| {"::", ReferenceType::kDirectMember}, | |
| {".", ReferenceType::kMemberOfTypeOfParent}, | |
| }); | |
| return kReferenceTypeNames.Unparse(ref_type, stream); | |
| } | |
| std::ostream& ReferenceComponent::PrintPathComponent( | |
| std::ostream& stream) const { | |
| stream << ref_type << identifier; | |
| if (required_metatype != SymbolMetaType::kUnspecified) { | |
| stream << '[' << required_metatype << ']'; | |
| } | |
| return stream; | |
| } | |
| std::ostream& ReferenceComponent::PrintVerbose(std::ostream& stream) const { | |
| PrintPathComponent(stream) << " -> "; | |
| if (resolved_symbol == nullptr) { | |
| return stream << "<unresolved>"; | |
| } | |
| return stream << ContextFullPath(*resolved_symbol); | |
| } | |
| std::ostream& operator<<(std::ostream& stream, | |
| const ReferenceComponent& component) { | |
| return component.PrintVerbose(stream); | |
| } | |
| void DeclarationTypeInfo::VerifySymbolTableRoot( | |
| const SymbolTableNode* root) const { | |
| if (user_defined_type != nullptr) { | |
| ApplyPreOrder(*user_defined_type, [=](const ReferenceComponent& component) { | |
| component.VerifySymbolTableRoot(root); | |
| }); | |
| } | |
| } | |
| absl::string_view SymbolInfo::CreateAnonymousScope(absl::string_view base) { | |
| const size_t n = anonymous_scope_names.size(); | |
| anonymous_scope_names.emplace_back(std::make_unique<const std::string>( | |
| // Starting with a non-alpha character guarantees it cannot collide with | |
| // any user-given identifier. | |
| absl::StrCat("%", "anon-", base, "-", n))); | |
| return *anonymous_scope_names.back(); | |
| } | |
| std::ostream& operator<<(std::ostream& stream, | |
| const DeclarationTypeInfo& decl_type_info) { | |
| stream << "type-info { "; | |
| stream << "source: "; | |
| if (decl_type_info.syntax_origin != nullptr) { | |
| stream << "\"" | |
| << AutoTruncate{.text = StringSpanOfSymbol( | |
| *decl_type_info.syntax_origin), | |
| .max_chars = 25} | |
| << "\""; | |
| } else { | |
| stream << "(unknown)"; | |
| } | |
| stream << ", type ref: "; | |
| if (decl_type_info.user_defined_type != nullptr) { | |
| stream << *decl_type_info.user_defined_type; | |
| } else { | |
| stream << "(primitive)"; | |
| } | |
| if (decl_type_info.implicit) { | |
| stream << ", implicit"; | |
| } | |
| return stream << " }"; | |
| } | |
| void SymbolInfo::VerifySymbolTableRoot(const SymbolTableNode* root) const { | |
| declared_type.VerifySymbolTableRoot(root); | |
| for (const auto& local_ref : local_references_to_bind) { | |
| local_ref.VerifySymbolTableRoot(root); | |
| } | |
| } | |
| void SymbolInfo::Resolve(const SymbolTableNode& context, | |
| std::vector<absl::Status>* diagnostics) { | |
| for (auto& local_ref : local_references_to_bind) { | |
| local_ref.Resolve(context, diagnostics); | |
| } | |
| } | |
| void SymbolInfo::ResolveLocally(const SymbolTableNode& context) { | |
| for (auto& local_ref : local_references_to_bind) { | |
| local_ref.ResolveLocally(context); | |
| } | |
| } | |
| std::ostream& SymbolInfo::PrintDefinition(std::ostream& stream, | |
| size_t indent) const { | |
| // print everything except local_references_to_bind | |
| const verible::Spacer wrap(indent); | |
| stream << wrap << "metatype: " << metatype << std::endl; | |
| if (file_origin != nullptr) { | |
| stream << wrap << "file: " << file_origin->ResolvedPath() << std::endl; | |
| } | |
| // declared_type only makes sense for elements with potentially user-defined | |
| // types, and not for language element declarations like modules and classes. | |
| if (metatype == SymbolMetaType::kDataNetVariableInstance) { | |
| stream << wrap << declared_type << std::endl; | |
| } | |
| return stream; | |
| } | |
| std::ostream& SymbolInfo::PrintReferences(std::ostream& stream, | |
| size_t indent) const { | |
| // only print local_references_to_bind | |
| // TODO: support indentation | |
| std::string newline_wrap(indent + 1, ' '); | |
| newline_wrap.front() = '\n'; | |
| stream << "refs:"; | |
| // When there's at most 1 reference, print more compactly. | |
| if (local_references_to_bind.size() > 1) | |
| stream << newline_wrap; | |
| else | |
| stream << ' '; | |
| stream << absl::StrJoin(local_references_to_bind, newline_wrap, | |
| absl::StreamFormatter()); | |
| if (local_references_to_bind.size() > 1) stream << newline_wrap; | |
| return stream; | |
| } | |
| SymbolInfo::references_map_view_type | |
| SymbolInfo::LocalReferencesMapViewForTesting() const { | |
| references_map_view_type map_view; | |
| for (const auto& local_ref : local_references_to_bind) { | |
| CHECK(!local_ref.Empty()) << "Never add empty DependentReferences."; | |
| map_view[local_ref.components->Value().identifier].emplace(&local_ref); | |
| } | |
| return map_view; | |
| } | |
| void SymbolTable::CheckIntegrity() const { | |
| const SymbolTableNode* root = &symbol_table_root_; | |
| symbol_table_root_.ApplyPreOrder( | |
| [=](const SymbolInfo& s) { s.VerifySymbolTableRoot(root); }); | |
| } | |
| void SymbolTable::Resolve(std::vector<absl::Status>* diagnostics) { | |
| symbol_table_root_.ApplyPreOrder( | |
| [=](SymbolTableNode& node) { node.Value().Resolve(node, diagnostics); }); | |
| } | |
| void SymbolTable::ResolveLocallyOnly() { | |
| symbol_table_root_.ApplyPreOrder( | |
| [=](SymbolTableNode& node) { node.Value().ResolveLocally(node); }); | |
| } | |
| std::ostream& SymbolTable::PrintSymbolDefinitions(std::ostream& stream) const { | |
| return symbol_table_root_.PrintTree( | |
| stream, | |
| [](std::ostream& s, const SymbolInfo& sym, | |
| size_t indent) -> std::ostream& { | |
| return sym.PrintDefinition(s << std::endl, indent + 4 /* wrap */) | |
| << verible::Spacer(indent); | |
| }); | |
| } | |
| std::ostream& SymbolTable::PrintSymbolReferences(std::ostream& stream) const { | |
| return symbol_table_root_.PrintTree(stream, | |
| [](std::ostream& s, const SymbolInfo& sym, | |
| size_t indent) -> std::ostream& { | |
| return sym.PrintReferences( | |
| s, indent + 4 /* wrap */); | |
| }); | |
| } | |
| static void ParseFileAndBuildSymbolTable( | |
| VerilogSourceFile* source, SymbolTable* symbol_table, | |
| VerilogProject* project, std::vector<absl::Status>* diagnostics) { | |
| const auto parse_status = source->Parse(); | |
| if (!parse_status.ok()) diagnostics->push_back(parse_status); | |
| // Continue, in case syntax-error recovery left a partial syntax tree. | |
| // Amend symbol table by analyzing this translation unit. | |
| const std::vector<absl::Status> statuses = | |
| BuildSymbolTable(*source, symbol_table, project); | |
| // Forward diagnostics. | |
| diagnostics->insert(diagnostics->end(), statuses.begin(), statuses.end()); | |
| } | |
| void SymbolTable::Build(std::vector<absl::Status>* diagnostics) { | |
| for (auto& translation_unit : *project_) { | |
| ParseFileAndBuildSymbolTable(translation_unit.second.get(), this, project_, | |
| diagnostics); | |
| } | |
| } | |
| void SymbolTable::BuildSingleTranslationUnit( | |
| absl::string_view referenced_file_name, | |
| std::vector<absl::Status>* diagnostics) { | |
| const auto translation_unit_or_status = | |
| project_->OpenTranslationUnit(referenced_file_name); | |
| if (!translation_unit_or_status.ok()) { | |
| diagnostics->push_back(translation_unit_or_status.status()); | |
| return; | |
| } | |
| VerilogSourceFile* translation_unit = *translation_unit_or_status; | |
| ParseFileAndBuildSymbolTable(translation_unit, this, project_, diagnostics); | |
| } | |
| std::vector<absl::Status> BuildSymbolTable(const VerilogSourceFile& source, | |
| SymbolTable* symbol_table, | |
| VerilogProject* project) { | |
| VLOG(1) << __FUNCTION__; | |
| const auto* text_structure = source.GetTextStructure(); | |
| if (text_structure == nullptr) return std::vector<absl::Status>(); | |
| const auto& syntax_tree = text_structure->SyntaxTree(); | |
| if (syntax_tree == nullptr) return std::vector<absl::Status>(); | |
| SymbolTable::Builder builder(source, symbol_table, project); | |
| syntax_tree->Accept(&builder); | |
| return builder.TakeDiagnostics(); // move | |
| } | |
| } // namespace verilog |