blob: 3ee0ab47bf1348de654673b3057a78f55368c5d5 [file] [log] [blame]
// 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(&current_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(&current_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(
&current_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(
&current_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(
&current_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