mod connect;
mod r#enum;
mod function;
mod msb;
use crate::analyzer_error::DuplicatedIdentifierKind;
use crate::attribute::IfdefCondition;
use crate::attribute_table;
use crate::namespace::{DefineContext, Namespace};
use crate::scope;
use crate::sv_system_function;
use crate::symbol::{
ConnectTarget, Direction, DocComment, GenericBoundKind, GenericMap, GenericTable,
GenericTables, InstanceProperty, ModportDefault, ModportFunctionMemberProperty,
ModportProperty, ModportVariableMemberProperty, Symbol, SymbolId, SymbolKind, TbComponentKind,
TestProperty, TypeKind,
};
use crate::symbol_path::{
GenericSymbol, GenericSymbolPath, GenericSymbolPathNamespace, SymbolPath, SymbolPathNamespace,
};
use crate::tb_component;
use crate::wavedrom::{self, DocTestTarget};
use crate::{AnalyzerError, HashMap, HashSet, SVec};
use connect::check_connect;
pub(crate) use function::{
find_external_write, function_call_generic_maps, specialized_direct_effect,
};
use log::trace;
use msb::check_msb;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use veryl_parser::resource_table::{self, PathId, StrId};
use veryl_parser::token_collector::TokenCollector;
use veryl_parser::token_range::TokenRange;
use veryl_parser::veryl_grammar_trait::{Expression, ExpressionIdentifier, HierarchicalIdentifier};
use veryl_parser::veryl_token::{Token, TokenSource};
use veryl_parser::veryl_walker::VerylWalker;
#[derive(Clone, Debug)]
pub struct ResolveResult {
pub found: std::rc::Rc<Symbol>,
pub full_path: Vec<SymbolId>,
pub imported: bool,
pub generic_tables: GenericTables,
}
#[derive(Clone, Debug)]
pub struct ResolveError {
pub last_found: Option<Box<Symbol>>,
pub cause: ResolveErrorCause,
}
#[derive(Clone, Debug)]
pub enum ResolveErrorCause {
NotFound(StrId),
Private,
Invisible,
Ambiguous(StrId),
}
impl ResolveError {
pub fn new(last_found: Option<&Rc<Symbol>>, cause: ResolveErrorCause) -> Self {
Self {
last_found: last_found.map(|x| Box::new((**x).clone())),
cause,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Import {
pub path: GenericSymbolPathNamespace,
pub namespace: Namespace,
pub wildcard: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Bind {
pub token: Token,
pub target: GenericSymbolPathNamespace,
pub doc_comment: DocComment,
pub property: InstanceProperty,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Msb {
pub token: Token,
pub path: SymbolPathNamespace,
pub dimension: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Connect {
Statement(ExpressionIdentifier, Expression),
Declaration(HierarchicalIdentifier, Expression),
}
#[derive(Clone, Copy, Debug)]
pub struct PendingWatermark {
import: usize,
bind: usize,
msb: usize,
connect: usize,
}
enum LexicalLookup {
Found {
symbol: SymbolId,
imported: bool,
},
Ambiguous,
NotFound,
}
struct LookupCandidate {
site: (TokenSource, u32, u32),
define_context: DefineContext,
depth: usize,
id: SymbolId,
imported: bool,
can_have_member: bool,
}
fn resolve_tier(candidates: &[LookupCandidate], prefer_container: bool) -> LexicalLookup {
let mut distinct: Vec<&LookupCandidate> = Vec::new();
for candidate in candidates {
if !distinct.iter().any(|x| x.site == candidate.site) {
distinct.push(candidate);
}
}
let ambiguous = distinct.iter().enumerate().any(|(i, ci)| {
distinct
.iter()
.skip(i + 1)
.any(|cj| !ci.define_context.exclusive(&cj.define_context))
});
if ambiguous {
return LexicalLookup::Ambiguous;
}
let rank = |c: &LookupCandidate| (prefer_container && c.can_have_member, c.depth);
let mut best = &candidates[0];
for candidate in &candidates[1..] {
if rank(candidate) >= rank(best) {
best = candidate;
}
}
LexicalLookup::Found {
symbol: best.id,
imported: best.imported,
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SymbolTableFragment {
pub symbols: Vec<Symbol>,
pub imports: Vec<Import>,
pub binds: Vec<Bind>,
pub msbs: Vec<Msb>,
pub connects: Vec<Connect>,
pub reference_functions: Vec<(SymbolId, Vec<GenericSymbolPath>)>,
pub references: Vec<(SymbolId, Vec<Token>)>,
}
#[derive(Clone, Default, Debug)]
pub struct SymbolTable {
name_table: HashMap<StrId, Vec<SymbolId>>,
symbol_table: HashMap<SymbolId, Rc<Symbol>>,
namespace_index: HashMap<SVec<StrId>, Vec<SymbolId>>,
project_local_table: HashMap<StrId, HashMap<StrId, StrId>>,
import_list: Vec<Import>,
bind_list: Vec<Bind>,
msb_list: Vec<Msb>,
connect_list: Vec<Connect>,
reference_func_table: HashMap<SymbolId, Vec<GenericSymbolPath>>,
reference_table: HashMap<SymbolId, Vec<Token>>,
sv_shadows: Vec<Symbol>,
suppress_cache_clear: bool,
skip_generic_args: bool,
}
impl SymbolTable {
pub fn new() -> Self {
let mut ret = Self::default();
let namespace = Namespace::new();
for func in DEFINED_NAMESPACES {
let token = Token::builtin_text(func);
let symbol = Symbol::new(
&token,
SymbolKind::Namespace,
&namespace,
true,
DocComment::default(),
);
let _ = ret.insert(&token, symbol);
}
sv_system_function::insert_symbols(&mut ret, &namespace);
tb_component::insert_symbols(&mut ret, &namespace);
ret
}
pub fn insert(&mut self, token: &Token, symbol: Symbol) -> Option<SymbolId> {
let entry = self
.name_table
.entry(resource_table::canonical_str_id(token.text))
.or_default();
for id in entry.iter() {
let item = self.symbol_table.get(id).unwrap();
let symbol = &symbol.namespace;
let item = &item.namespace;
let same_namespace = symbol.paths == item.paths;
let define_exclusive = symbol.define_context.exclusive(&item.define_context);
let conflict = same_namespace && !define_exclusive;
if conflict {
return None;
}
}
let id = symbol.id;
let scope = symbol.scope;
let ns_paths = symbol.namespace.paths.clone();
entry.push(id);
self.symbol_table.insert(id, Rc::new(symbol));
self.namespace_index.entry(ns_paths).or_default().push(id);
scope::add_local(scope, token.text, id);
Some(id)
}
pub fn insert_sv_member(&mut self, token: &Token, symbol: Symbol) -> Option<SymbolId> {
let ret = self.insert(token, symbol.clone());
if ret.is_none() {
self.sv_shadows.push(symbol);
}
ret
}
pub fn get(&self, id: SymbolId) -> Option<Symbol> {
self.symbol_table.get(&id).map(|x| (**x).clone())
}
pub fn get_rc(&self, id: SymbolId) -> Option<Rc<Symbol>> {
self.symbol_table.get(&id).cloned()
}
pub fn update(&mut self, symbol: Symbol) {
let id = symbol.id;
self.symbol_table.insert(id, Rc::new(symbol));
}
fn match_nested_generic_instance(&self, context: &ResolveContext, found: &Symbol) -> bool {
if let Some(last_found) = context.last_found
&& let (SymbolKind::GenericInstance(_), SymbolKind::GenericInstance(_)) =
(&last_found.kind, &found.kind)
{
let inner = scope::inner_scope(last_found.scope, last_found.token.text);
return inner == found.scope
&& !last_found
.namespace
.define_context
.exclusive(&found.namespace.define_context);
}
false
}
fn trace_type_kind<'a>(
&self,
mut context: ResolveContext<'a>,
kind: &TypeKind,
) -> Result<ResolveContext<'a>, ResolveError> {
if let TypeKind::UserDefined(x) = kind {
let mut path = x.path.clone();
path.resolve_imported(context.scope, &context.define_context, None);
path.unalias(None);
let symbol = self.resolve(
&path.generic_path(),
&path.generic_arguments(),
context.push(),
)?;
let key = (symbol.found.id, ResolvePhase::TypeTrace);
if context.visiting.contains(&key) {
return Ok(context);
}
context.visiting.push(key);
match &symbol.found.kind {
SymbolKind::SystemVerilog => context.sv_member = true,
SymbolKind::TbComponent(x) if matches!(x.kind, TbComponentKind::External(_)) => {
context.sv_member = true
}
SymbolKind::Parameter(x) if !x.is_proto => {
if matches!(x.r#type.kind, TypeKind::Type) {
let value = x.value.as_ref().unwrap();
return self.trace_type_parameter(context, value, &symbol.found);
}
}
SymbolKind::TypeDef(x) => {
if let Some(r#type) = &x.r#type {
context.set_namespace(
symbol.found.scope,
symbol.found.namespace.define_context.clone(),
);
return self.trace_type_kind(context, &r#type.kind);
}
}
SymbolKind::GenericParameter(x) => {
if matches!(x.bound, GenericBoundKind::Type) {
return self.trace_generic_parameter(context.clone(), &symbol.found);
}
}
_ => (),
}
context.set_inner(&symbol.found);
context.last_found_type = Some(symbol.found.id);
context.inner = true;
context.generic_tables = symbol.generic_tables;
} else {
context.set_namespace(scope::ScopeId::default(), DefineContext::default());
context.inner = true;
}
Ok(context)
}
fn trace_type_path<'a>(
&self,
context: ResolveContext<'a>,
path: &GenericSymbolPath,
) -> Result<ResolveContext<'a>, ResolveError> {
let (mut context, found) = self.expand_alias(context, path)?;
match &found.kind {
SymbolKind::GenericInstance(_) => self.trace_generic_instance(context, &found),
SymbolKind::GenericParameter(_) => self.trace_generic_parameter(context, &found),
_ => {
match &found.kind {
SymbolKind::SystemVerilog => context.sv_member = true,
SymbolKind::TbComponent(x)
if matches!(x.kind, TbComponentKind::External(_)) =>
{
context.sv_member = true
}
_ => {}
}
context.set_inner(&found);
context.last_found_type = Some(found.id);
context.inner = true;
Ok(context)
}
}
}
fn expand_alias<'a>(
&self,
mut context: ResolveContext<'a>,
path: &GenericSymbolPath,
) -> Result<(ResolveContext<'a>, Rc<Symbol>), ResolveError> {
let symbol = self.resolve(
&path.generic_path(),
&path.generic_arguments(),
context.push(),
)?;
context.generic_tables = symbol.generic_tables.clone();
if matches!(
symbol.found.kind,
SymbolKind::AliasModule(_)
| SymbolKind::AliasInterface(_)
| SymbolKind::AliasPackage(_)
) {
let key = (symbol.found.id, ResolvePhase::Alias);
if context.visiting.contains(&key) {
return Err(ResolveError::new(
Some(&symbol.found),
ResolveErrorCause::NotFound(symbol.found.token.text),
));
}
context.visiting.push(key);
}
match &symbol.found.kind {
SymbolKind::AliasModule(x) => self.expand_alias(context, &x.target),
SymbolKind::AliasInterface(x) => self.expand_alias(context, &x.target),
SymbolKind::AliasPackage(x) => self.expand_alias(context, &x.target),
_ => Ok((context, Rc::clone(&symbol.found))),
}
}
fn trace_generic_instance<'a>(
&self,
mut context: ResolveContext<'a>,
found: &Symbol,
) -> Result<ResolveContext<'a>, ResolveError> {
if let SymbolKind::GenericInstance(x) = &found.kind {
let base = self.symbol_table.get(&x.base).unwrap();
#[cfg(debug_assertions)]
{
let inst_inner = scope::inner_scope(found.scope, found.token.text);
let base_inner = scope::inner_scope(base.scope, base.token.text);
debug_assert_eq!(
scope::generic_delegation(inst_inner),
Some(base_inner),
"generic instance delegation must match the base redirect"
);
}
let prev_inst = context.inst_scope;
context.set_inner(base);
let base_inner = context.scope;
context.inst_scope = if scope::generic_delegation(prev_inst) == Some(found.scope) {
scope::register_generic_child(prev_inst, found.token.text, base_inner)
} else {
scope::inner_scope(found.scope, found.token.text)
};
context.last_found_type = Some(base.id);
context.inner = true;
}
Ok(context)
}
fn trace_generic_parameter<'a>(
&self,
mut context: ResolveContext<'a>,
found: &Symbol,
) -> Result<ResolveContext<'a>, ResolveError> {
if let SymbolKind::GenericParameter(x) = &found.kind {
if let Some(x) = self.resolve_generic_param_const(context.clone(), found) {
return x;
}
let symbol = match &x.bound {
GenericBoundKind::Type if x.default_value.is_some() => {
let path = x.default_value.as_ref().unwrap().generic_path();
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
&self.resolve(&path, &[], ctxt)?.found
}
GenericBoundKind::Inst(proto) => {
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
&self
.resolve(&proto.generic_path(), &proto.generic_arguments(), ctxt)?
.found
}
GenericBoundKind::Proto(proto) => {
if let Some(x) = proto.get_user_defined() {
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
&self.resolve(&x.path.generic_path(), &[], ctxt)?.found
} else {
found
}
}
_ => found,
};
context.set_inner(symbol);
context.last_found_type = Some(symbol.id);
context.inner = true;
}
Ok(context)
}
fn trace_generic_const<'a>(
&self,
mut context: ResolveContext<'a>,
found: &Symbol,
) -> Result<ResolveContext<'a>, ResolveError> {
if let SymbolKind::GenericConst(x) = &found.kind {
if let Some(x) = self.resolve_generic_param_const(context.clone(), found) {
return x;
}
let symbol = match &x.bound {
GenericBoundKind::Type => {
if let Some(identifier) = x.value.unwrap_identifier() {
let path: GenericSymbolPath = identifier.into();
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
&self.resolve(&path.generic_path(), &[], ctxt)?.found
} else {
found
}
}
GenericBoundKind::Proto(proto) => {
if let Some(x) = proto.get_user_defined() {
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
&self.resolve(&x.path.generic_path(), &[], ctxt)?.found
} else {
found
}
}
_ => found,
};
context.set_inner(symbol);
context.last_found_type = Some(symbol.id);
context.inner = true;
}
Ok(context)
}
fn resolve_generic_param_const<'a>(
&self,
mut context: ResolveContext<'a>,
found: &Symbol,
) -> Option<Result<ResolveContext<'a>, ResolveError>> {
match &found.kind {
SymbolKind::GenericParameter(_) | SymbolKind::GenericConst(_) => {
if let Some(generic_table) = context
.generic_tables
.get(&(found.scope, found.namespace.define_context.clone()))
&& let Some(path) = generic_table.get(&found.token.text)
{
let result = self.resolve(
&path.generic_path(),
&path.generic_arguments(),
context.push(),
);
if let Ok(symbol) = result {
context.set_inner(&symbol.found);
context.last_found_type = Some(symbol.found.id);
context.inner = true;
return Some(Ok(context));
} else {
return result.err().map(Err);
}
}
}
_ => {}
}
None
}
fn trace_type_parameter<'a>(
&self,
mut context: ResolveContext<'a>,
expression: &Expression,
found: &Symbol,
) -> Result<ResolveContext<'a>, ResolveError> {
if let Some(identifier) = expression.unwrap_identifier() {
let mut ctxt = ResolveContext::new(&found.namespace);
ctxt.depth = context.depth + 1;
let symbol = self.resolve(&identifier.into(), &[], ctxt)?;
match &symbol.found.kind {
SymbolKind::Parameter(x) if !x.is_proto => {
if matches!(x.r#type.kind, TypeKind::Type) {
let value = x.value.as_ref().unwrap();
return self.trace_type_parameter(context, value, &symbol.found);
}
}
SymbolKind::TypeDef(x) => {
if let Some(ref r#type) = x.r#type {
return self.trace_type_kind(context, &r#type.kind);
}
}
SymbolKind::GenericParameter(x) => {
if matches!(x.bound, GenericBoundKind::Type)
&& let Some(x) =
self.resolve_generic_param_const(context.clone(), &symbol.found)
{
return x;
}
}
SymbolKind::GenericConst(x) => {
if matches!(x.bound, GenericBoundKind::Type)
&& let Some(x) =
self.resolve_generic_param_const(context.clone(), &symbol.found)
{
return x;
}
}
_ => {}
}
context.set_inner(&symbol.found);
context.last_found_type = Some(symbol.found.id);
context.inner = true;
context.generic_tables = symbol.generic_tables;
}
Ok(context)
}
fn navigate_member<'a>(
&self,
mut context: ResolveContext<'a>,
found: &Rc<Symbol>,
) -> Result<ResolveContext<'a>, ResolveError> {
match &found.kind {
SymbolKind::Variable(x) => {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
SymbolKind::StructMember(x) => {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
SymbolKind::UnionMember(x) => {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
SymbolKind::Parameter(x) if x.is_proto => {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
SymbolKind::Parameter(x) => {
if matches!(x.r#type.kind, TypeKind::Type) {
let value = x.value.as_ref().unwrap();
context = self.trace_type_parameter(context, value, found)?;
} else {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
}
SymbolKind::TypeDef(x) => {
if let Some(ref r#type) = x.r#type {
context = self.trace_type_kind(context, &r#type.kind)?;
}
}
SymbolKind::Port(x) => {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
SymbolKind::ModportVariableMember(_) => {
let path = SymbolPath::new(&[found.token.text]);
context.set_namespace(
scope::parent(found.scope).unwrap_or_default(),
found.namespace.define_context.clone(),
);
let symbol = self.resolve(&path, &[], context.push())?;
if let SymbolKind::Variable(x) = &symbol.found.kind {
context = self.trace_type_kind(context, &x.r#type.kind)?;
}
}
SymbolKind::Module(x) if x.is_proto => (),
SymbolKind::Module(_) | SymbolKind::Interface(_) | SymbolKind::Package(_) => {
context.set_inner(found);
context.inner = true;
}
SymbolKind::AliasModule(x) => {
context = self.trace_type_path(context, &x.target)?;
}
SymbolKind::AliasInterface(x) => {
context = self.trace_type_path(context, &x.target)?;
}
SymbolKind::AliasPackage(x) => {
context = self.trace_type_path(context, &x.target)?;
}
SymbolKind::Enum(_) | SymbolKind::Namespace | SymbolKind::PropNamespace => {
context.set_inner(found);
context.inner = true;
}
SymbolKind::TbComponent(x) => {
context.set_inner(found);
context.inner = true;
if matches!(x.kind, TbComponentKind::External(_)) {
context.sv_member = true;
}
}
SymbolKind::SystemVerilog => {
context.set_inner(found);
context.inner = true;
context.sv_member = true;
}
SymbolKind::Instance(x) => {
let mut type_name = x.type_name.clone();
type_name.resolve_imported(context.scope, &context.define_context, None);
type_name.unalias(None);
context = self.trace_type_path(context, &type_name)?;
}
SymbolKind::GenericInstance(_) => {
context = self.trace_generic_instance(context, found)?;
}
SymbolKind::GenericParameter(_) => {
context = self.trace_generic_parameter(context, found)?;
}
SymbolKind::GenericConst(_) => {
context = self.trace_generic_const(context, found)?;
}
SymbolKind::Function(_)
| SymbolKind::Struct(_)
| SymbolKind::Union(_)
| SymbolKind::Modport(_)
| SymbolKind::ModportFunctionMember(_)
| SymbolKind::EnumMember(_)
| SymbolKind::EnumMemberMangled
| SymbolKind::Block
| SymbolKind::SystemFunction(_)
| SymbolKind::Genvar
| SymbolKind::ClockDomain
| SymbolKind::Test(_)
| SymbolKind::Embed
| SymbolKind::ProjectProperty(_) => (),
}
Ok(context)
}
fn check_access(&self, context: &ResolveContext, found: &Symbol) -> Result<(), ResolveError> {
if !self.is_public(context, found) {
Err(ResolveError::new(context.found, ResolveErrorCause::Private))
} else if !self.is_visible(context, found) {
Err(ResolveError::new(
context.found,
ResolveErrorCause::Invisible,
))
} else {
Ok(())
}
}
fn is_public(&self, context: &ResolveContext, found: &Symbol) -> bool {
match found.kind {
SymbolKind::Module(_)
| SymbolKind::AliasModule(_)
| SymbolKind::Interface(_)
| SymbolKind::AliasInterface(_)
| SymbolKind::Package(_)
| SymbolKind::AliasPackage(_) => !context.other_prj || found.public,
SymbolKind::Namespace => !context.root_prj || found.public,
_ => true,
}
}
fn is_visible(&self, context: &ResolveContext, found: &Symbol) -> bool {
if context.last_found.is_none() || matches!(found.kind, SymbolKind::SystemVerilog) {
return true;
}
let last_found = context.last_found.unwrap();
let last_found_type = context.last_found_type.map(|x| {
let symbol = self.symbol_table.get(&x).unwrap();
symbol.kind.clone()
});
let via_modport = match &last_found.kind {
SymbolKind::Port(x) => matches!(x.direction, Direction::Modport | Direction::Interface),
_ => false,
};
let via_interface_instance = match &last_found.kind {
SymbolKind::Instance(_) => matches!(
last_found_type,
Some(SymbolKind::Interface(_)) | Some(SymbolKind::AliasInterface(_))
),
SymbolKind::GenericParameter(x) => {
matches!(&x.bound, GenericBoundKind::Inst(_))
&& matches!(last_found_type, Some(SymbolKind::Interface(_)))
}
_ => false,
};
let via_interface = match &last_found.kind {
SymbolKind::Interface(_) | SymbolKind::AliasInterface(_) => true,
SymbolKind::GenericInstance(_) => {
matches!(last_found_type, Some(SymbolKind::Interface(ref x)) if !x.is_proto)
}
SymbolKind::GenericParameter(x) => {
matches!(&x.bound, GenericBoundKind::Proto(_))
&& matches!(last_found_type, Some(SymbolKind::Interface(ref x)) if x.is_proto)
}
_ => false,
};
let via_pacakge = match &last_found.kind {
SymbolKind::Package(_) | SymbolKind::AliasPackage(_) => true,
SymbolKind::GenericInstance(_) => {
matches!(last_found_type, Some(SymbolKind::Package(_)))
}
SymbolKind::GenericParameter(_) => {
matches!(last_found_type, Some(SymbolKind::Package(_)))
}
_ => false,
};
let via_enum = match &last_found.kind {
SymbolKind::Enum(_) => true,
SymbolKind::TypeDef(x) => {
x.r#type.is_some() && matches!(last_found_type, Some(SymbolKind::Enum(_)))
}
_ => false,
};
let via_namespace = matches!(last_found.kind, SymbolKind::Namespace);
let via_prop_namespace = matches!(last_found.kind, SymbolKind::PropNamespace);
let via_tb_component = matches!(last_found_type, Some(SymbolKind::TbComponent(_)));
match &found.kind {
SymbolKind::Variable(_)
| SymbolKind::ModportFunctionMember(_)
| SymbolKind::ModportVariableMember(_) => via_modport || via_interface_instance,
SymbolKind::StructMember(_) | SymbolKind::UnionMember(_) => matches!(
last_found.kind,
SymbolKind::Port(_)
| SymbolKind::ModportVariableMember(_)
| SymbolKind::Variable(_)
| SymbolKind::Parameter(_)
| SymbolKind::StructMember(_)
| SymbolKind::UnionMember(_)
| SymbolKind::GenericParameter(_)
),
SymbolKind::Parameter(_)
| SymbolKind::TypeDef(_)
| SymbolKind::Enum(_)
| SymbolKind::Struct(_)
| SymbolKind::Union(_)
| SymbolKind::AliasModule(_)
| SymbolKind::AliasInterface(_)
| SymbolKind::AliasPackage(_) => via_pacakge,
SymbolKind::Function(x) if x.is_proto => via_pacakge,
SymbolKind::Function(x) => {
via_modport
|| via_interface_instance
|| via_pacakge
|| via_tb_component
|| via_namespace && x.is_global()
}
SymbolKind::EnumMember(_) | SymbolKind::EnumMemberMangled => via_enum,
SymbolKind::Modport(_) => via_interface || via_interface_instance,
SymbolKind::GenericInstance(_) => {
via_pacakge || via_namespace
}
SymbolKind::ProjectProperty(_) => via_prop_namespace,
SymbolKind::PropNamespace => !via_namespace,
_ => via_namespace,
}
}
fn lookup_member<'a>(
&'a self,
context: &ResolveContext,
name: StrId,
) -> Option<&'a Rc<Symbol>> {
let mut candidates = scope::locals_get(context.scope, name);
if let Some(last_found) = context.last_found
&& matches!(last_found.kind, SymbolKind::GenericInstance(_))
{
let inner = scope::inner_scope(last_found.scope, last_found.token.text);
candidates.extend(scope::locals_get(inner, name));
}
let mut max_depth = 0;
let mut found = None;
for id in candidates {
let Some(symbol) = self.symbol_table.get(&id) else {
continue;
};
let matched = self.match_nested_generic_instance(context, symbol)
|| (context.scope == symbol.scope
&& !context
.define_context
.exclusive(&symbol.namespace.define_context));
if matched && symbol.namespace.depth() >= max_depth {
found = Some(symbol);
max_depth = symbol.namespace.depth();
}
}
if found.is_some() {
return found;
}
for mixin in scope::mixin_get(context.scope) {
if context.define_context.exclusive(&mixin.define_context) {
continue;
}
for id in scope::locals_get(mixin.source, name) {
let Some(symbol) = self.symbol_table.get(&id) else {
continue;
};
if !symbol
.namespace
.define_context
.exclusive(&mixin.source_define_context)
{
return Some(symbol);
}
}
}
None
}
fn lookup_name(
&self,
scope: scope::ScopeId,
name: StrId,
query_dctx: &DefineContext,
prefer_container: bool,
) -> LexicalLookup {
let candidate = |id: SymbolId, symbol: &Symbol, define_context, imported| LookupCandidate {
site: (symbol.token.source, symbol.token.line, symbol.token.column),
define_context,
depth: symbol.namespace.depth(),
id,
imported,
can_have_member: symbol.kind.can_have_path_member(),
};
let mut fallback: Option<LexicalLookup> = None;
let mut current = Some(scope);
while let Some(scope) = current {
let scope = scope::generic_delegation(scope).unwrap_or(scope);
let mut tier = Vec::new();
for id in scope::locals_get(scope, name) {
if let Some(symbol) = self.symbol_table.get(&id)
&& !query_dctx.exclusive(&symbol.namespace.define_context)
{
let dctx = symbol.namespace.define_context.clone();
tier.push(candidate(id, symbol, dctx, false));
}
}
for binding in scope::imports_get(scope, name) {
if !query_dctx.exclusive(&binding.define_context)
&& let Some(symbol) = self.symbol_table.get(&binding.symbol)
{
tier.push(candidate(
binding.symbol,
symbol,
binding.define_context,
true,
));
}
}
if !tier.is_empty()
&& let Some(result) = self.container_or_stash(
resolve_tier(&tier, prefer_container),
prefer_container,
&mut fallback,
)
{
return result;
}
let mut tier = Vec::new();
for wildcard in scope::wildcards_get(scope) {
if query_dctx.exclusive(&wildcard.define_context) {
continue;
}
for id in scope::locals_get(wildcard.source, name) {
if let Some(symbol) = self.symbol_table.get(&id)
&& !symbol
.namespace
.define_context
.exclusive(&wildcard.source_define_context)
{
let dctx = symbol.namespace.define_context.clone();
tier.push(candidate(id, symbol, dctx, true));
}
}
}
if !tier.is_empty()
&& let Some(result) = self.container_or_stash(
resolve_tier(&tier, prefer_container),
prefer_container,
&mut fallback,
)
{
return result;
}
let mut tier = Vec::new();
for mixin in scope::mixin_get(scope) {
if query_dctx.exclusive(&mixin.define_context) {
continue;
}
for id in scope::locals_get(mixin.source, name) {
if let Some(symbol) = self.symbol_table.get(&id)
&& !symbol
.namespace
.define_context
.exclusive(&mixin.source_define_context)
{
let dctx = symbol.namespace.define_context.clone();
tier.push(candidate(id, symbol, dctx, true));
}
}
}
if !tier.is_empty()
&& let Some(result) = self.container_or_stash(
resolve_tier(&tier, prefer_container),
prefer_container,
&mut fallback,
)
{
return result;
}
current = scope::parent(scope);
}
fallback.unwrap_or(LexicalLookup::NotFound)
}
fn container_or_stash(
&self,
result: LexicalLookup,
prefer_container: bool,
fallback: &mut Option<LexicalLookup>,
) -> Option<LexicalLookup> {
if prefer_container
&& let LexicalLookup::Found { symbol, .. } = &result
&& !self
.symbol_table
.get(symbol)
.is_some_and(|s| s.kind.can_have_path_member())
{
if fallback.is_none() {
*fallback = Some(result);
}
return None;
}
Some(result)
}
fn instantiate_generic(
&self,
context: &mut ResolveContext,
found: &Symbol,
mut arguments: Vec<GenericSymbolPath>,
namespace_generic_map: &NamespaceGenericMap,
entry_scope: scope::ScopeId,
entry_define_context: &DefineContext,
) {
for arg in &mut arguments {
if let Some(map) = namespace_generic_map {
arg.apply_map(map.as_slice());
}
if let Some(prj) = scope::project_of(entry_scope)
&& Some(prj) != scope::project_of(found.scope)
{
self.append_project_path(arg, entry_scope, entry_define_context);
}
}
context.generic_tables.insert(
(
scope::inner_scope(found.scope, found.token.text),
found.namespace.define_context.clone(),
),
found.generic_table(&arguments),
);
}
fn reconstruct_found_namespace(
found: &Rc<Symbol>,
inst_scope: scope::ScopeId,
current_scope: scope::ScopeId,
define_context: &DefineContext,
) -> Rc<Symbol> {
let mixin_target = scope::get_mixin_target_scope(current_scope, found.scope);
let via_generic_instance = if mixin_target.is_some() {
scope::generic_delegation(inst_scope) == mixin_target
} else {
scope::generic_delegation(inst_scope) == Some(found.scope)
};
if via_generic_instance {
let mut found = (**found).clone();
found.namespace = scope::namespace(inst_scope, define_context);
Rc::new(found)
} else if let Some(scope) = mixin_target {
let mut found = (**found).clone();
found.namespace = scope::namespace(scope, define_context);
Rc::new(found)
} else {
Rc::clone(found)
}
}
fn resolve<'a>(
&'a self,
path: &SymbolPath,
generic_arguments: &[Vec<GenericSymbolPath>],
mut context: ResolveContext<'a>,
) -> Result<ResolveResult, ResolveError> {
let entry_scope = context.scope;
let entry_define_context = context.define_context.clone();
let mut path = path.clone();
if let Some(prj) = scope::project_of(entry_scope) {
let path_head = path.0[0];
if let Some(map) = self.project_local_table.get(&prj) {
context.root_prj = false;
if let Some(id) = map.get(&path_head) {
path.0[0] = *id;
}
}
}
trace!("symbol_table: {}resolve '{}'", context.indent(), path);
let namespace_generic_map = if self.skip_generic_args {
None
} else {
self.get_namespace_generic_map(entry_scope, &context.define_context)
};
let visiting_base = context.visiting.len();
for (i, name) in path.as_slice().iter().enumerate() {
let prefer_container = (i + 1) != path.len();
context.found = None;
let generic_argument = if self.skip_generic_args {
None
} else {
generic_arguments.get(i).cloned()
};
if context.sv_member {
let token = Token::new(&name.to_string(), 0, 0, 0, 0, TokenSource::External);
let symbol = Symbol::new(
&token,
SymbolKind::SystemVerilog,
&context.namespace(),
false,
DocComment::default(),
);
return Ok(ResolveResult {
found: std::rc::Rc::new(symbol),
full_path: context.full_path,
imported: context.imported,
generic_tables: context.generic_tables,
});
}
if self
.name_table
.contains_key(&resource_table::canonical_str_id(*name))
{
if context.inner {
if let Some(symbol) = self.lookup_member(&context, *name) {
context.found = Some(symbol);
context.imported = false;
}
} else {
match self.lookup_name(
context.scope,
*name,
&context.define_context,
prefer_container,
) {
LexicalLookup::Found { symbol, imported } => {
context.found = self.symbol_table.get(&symbol);
context.imported = imported;
}
LexicalLookup::Ambiguous => {
trace!("symbol_table: {}ambiguous '{}'", context.indent(), path);
return Err(ResolveError::new(
None,
ResolveErrorCause::Ambiguous(*name),
));
}
LexicalLookup::NotFound => {}
}
}
if let Some(found) = context.found {
self.check_access(&context, found).map_err(|err| {
let kind = if matches!(err.cause, ResolveErrorCause::Private) {
"private"
} else {
"invisible"
};
trace!("symbol_table: {}{} '{}'", context.indent(), kind, path);
err
})?;
if let Some(arguments) = generic_argument {
self.instantiate_generic(
&mut context,
found,
arguments,
&namespace_generic_map,
entry_scope,
&entry_define_context,
);
}
context.last_found = context.found;
context.full_path.push(found.id);
trace!(
"symbol_table: {}- path '{}' : {} @ {}",
context.indent(),
name,
found.kind,
context.namespace(),
);
if (i + 1) < path.len() {
context = self.navigate_member(context, found)?;
context.visiting.truncate(visiting_base);
}
} else {
trace!(
"symbol_table: {}not found '{}' @ {}",
context.indent(),
path,
context.namespace()
);
return Err(ResolveError::new(
context.last_found,
ResolveErrorCause::NotFound(*name),
));
}
} else {
context.set_namespace(
scope::inner_scope(scope::ScopeId::default(), *name),
DefineContext::default(),
);
context.inner = true;
context.other_prj = true;
}
}
if let Some(found) = context.found {
let found = Self::reconstruct_found_namespace(
found,
context.inst_scope,
context.scope,
&context.define_context,
);
trace!(
"symbol_table: {}found '{}' : {}",
context.indent(),
path,
found.kind
);
Ok(ResolveResult {
found,
full_path: context.full_path,
imported: context.imported,
generic_tables: context.generic_tables,
})
} else {
trace!("symbol_table: {}not found '{}'", context.indent(), path);
let cause = ResolveErrorCause::NotFound(
scope::name_path(context.scope).last().copied().unwrap(),
);
Err(ResolveError::new(context.last_found, cause))
}
}
fn resolve_generic_structural<'a>(
&'a self,
path: &GenericSymbolPath,
scope: scope::ScopeId,
define_context: DefineContext,
) -> Option<ResolveResult> {
let mut context = ResolveContext::from_scope(scope, define_context);
let visiting_base = context.visiting.len();
let segments = &path.paths;
for (i, segment) in segments.iter().enumerate() {
context.found = None;
let prefer_container = (i + 1) != segments.len();
let mut name = segment.base();
if i == 0
&& let Some(prj) = scope::project_of(scope)
&& let Some(map) = self.project_local_table.get(&prj)
{
context.root_prj = false;
if let Some(id) = map.get(&name) {
name = *id;
}
}
if context.sv_member {
return None;
}
if !self
.name_table
.contains_key(&resource_table::canonical_str_id(name))
{
context.set_namespace(
scope::inner_scope(scope::ScopeId::default(), name),
DefineContext::default(),
);
context.inner = true;
context.other_prj = true;
continue;
}
let base: &'a Rc<Symbol> = if context.inner {
let member = self.lookup_member(&context, name)?;
context.imported = false;
member
} else {
match self.lookup_name(
context.scope,
name,
&context.define_context,
prefer_container,
) {
LexicalLookup::Found { symbol, imported } => {
context.imported = imported;
self.symbol_table.get(&symbol)?
}
LexicalLookup::Ambiguous | LexicalLookup::NotFound => return None,
}
};
self.check_access(&context, base).ok()?;
let found: &'a Rc<Symbol> = if segment.mangled() == segment.base()
|| matches!(base.kind, SymbolKind::TbComponent(_))
{
base
} else {
let enclosing = if scope::generic_delegation(context.inst_scope) == Some(base.scope)
{
context.inst_scope
} else {
scope::intern_namespace(&base.namespace)
};
let key = generic_instance_key(enclosing, base.id, &segment.arguments);
let inst = GENERIC_INSTANCE_INDEX.with(|f| f.borrow().get(&key).copied())?;
self.symbol_table.get(&inst)?
};
context.found = Some(found);
context.last_found = context.found;
context.full_path.push(found.id);
if (i + 1) < segments.len() {
context = self.navigate_member(context, found).ok()?;
context.visiting.truncate(visiting_base);
}
}
let found = context.found?;
let found = Self::reconstruct_found_namespace(
found,
context.inst_scope,
context.scope,
&context.define_context,
);
Some(ResolveResult {
found,
full_path: context.full_path,
imported: context.imported,
generic_tables: context.generic_tables,
})
}
fn get_namespace_generic_map(
&self,
scope: scope::ScopeId,
define_context: &DefineContext,
) -> Option<Rc<Vec<GenericMap>>> {
if scope::depth(scope) <= 1 {
return None;
}
let key = (scope, define_context.clone());
if let Some(cached) = NS_GENERIC_MAP_CACHE.with(|f| f.borrow().get(&key).cloned()) {
return cached;
}
let namespace = scope::namespace(scope, define_context);
let ret = self
.get_namespace_generic_map_inner(&namespace)
.map(Rc::new);
NS_GENERIC_MAP_CACHE.with(|f| f.borrow_mut().insert(key, ret.clone()));
ret
}
fn get_namespace_generic_map_inner(&self, namespace: &Namespace) -> Option<Vec<GenericMap>> {
let mut namespace = namespace.clone();
namespace.strip_anonymous_path();
let path = namespace.pop().map(|x| SymbolPath::new(&[x]))?;
let context = ResolveContext::new(&namespace);
let param_list = self
.resolve(&path, &[], context)
.map(|x| self.collect_generic_bounds(&x.found))
.ok()?;
if param_list.is_empty() {
return None;
}
let mut maps = Vec::new();
for params in param_list {
let mut map = GenericTable::default();
for (key, bound) in params {
if let GenericBoundKind::Proto(x) = bound {
let TypeKind::UserDefined(x) = x.kind else {
continue;
};
map.insert(key, x.path);
}
}
maps.push(GenericMap { id: None, map });
}
Some(maps)
}
fn collect_generic_bounds(&self, symbol: &Symbol) -> Vec<Vec<(StrId, GenericBoundKind)>> {
fn collect_generic_params(
symbol_table: &SymbolTable,
generic_params: &[SymbolId],
) -> Vec<(StrId, GenericBoundKind)> {
generic_params
.iter()
.map(|param| {
let symbol = symbol_table.get(*param).unwrap();
let SymbolKind::GenericParameter(x) = symbol.kind else {
unreachable!();
};
(symbol.token.text, x.bound)
})
.collect()
}
fn get_parent_symbol(symbol_table: &SymbolTable, symbol: &Symbol) -> Option<Symbol> {
let mut namespace = symbol.namespace.clone();
namespace.strip_anonymous_path();
if let Some(path) = namespace.pop()
&& namespace.depth() >= 1
{
let context = ResolveContext::new(&namespace);
let symbol_path = SymbolPath::new(&[path]);
symbol_table
.resolve(&symbol_path, &[], context)
.map(|x| (*x.found).clone())
.ok()
} else {
None
}
}
match &symbol.kind {
SymbolKind::Module(x) => vec![collect_generic_params(self, &x.generic_parameters)],
SymbolKind::Interface(x) => vec![collect_generic_params(self, &x.generic_parameters)],
SymbolKind::Package(x) if !x.is_proto => {
vec![collect_generic_params(self, &x.generic_parameters)]
}
SymbolKind::GenericInstance(x) => {
let symbol = self.get(x.base).unwrap();
self.collect_generic_bounds(&symbol)
}
SymbolKind::Function(x) if !x.is_proto => {
let mut bounds = if let Some(parent) = get_parent_symbol(self, symbol) {
self.collect_generic_bounds(&parent)
} else {
vec![]
};
bounds.push(collect_generic_params(self, &x.generic_parameters));
bounds
}
SymbolKind::Struct(x) => {
let mut bounds = if let Some(parent) = get_parent_symbol(self, symbol) {
self.collect_generic_bounds(&parent)
} else {
vec![]
};
bounds.push(collect_generic_params(self, &x.generic_parameters));
bounds
}
SymbolKind::Union(x) => {
let mut bounds = if let Some(parent) = get_parent_symbol(self, symbol) {
self.collect_generic_bounds(&parent)
} else {
vec![]
};
bounds.push(collect_generic_params(self, &x.generic_parameters));
bounds
}
SymbolKind::Block => {
if let Some(parent) = get_parent_symbol(self, symbol) {
self.collect_generic_bounds(&parent)
} else {
vec![]
}
}
_ => vec![],
}
}
fn append_project_path(
&self,
path: &mut GenericSymbolPath,
scope: scope::ScopeId,
define_context: &DefineContext,
) {
let namespace = scope::namespace(scope, define_context);
let context = ResolveContext::new(&namespace);
let Ok(symbol) = self.resolve(&path.base_path(0), &[], context) else {
return;
};
if !matches!(
symbol.found.kind,
SymbolKind::Module(_)
| SymbolKind::AliasModule(_)
| SymbolKind::Interface(_)
| SymbolKind::AliasInterface(_)
| SymbolKind::Package(_)
| SymbolKind::AliasPackage(_)
) {
return;
}
if let Some(prj) = scope::project_of(symbol.found.scope)
&& let Some(project_symbol) = self.find_project_symbol(prj)
{
let project_path = GenericSymbol {
base: project_symbol.token,
arguments: vec![],
};
path.paths.insert(0, project_path);
}
}
pub fn get_all(&self) -> Vec<Symbol> {
let mut ret = Vec::new();
for symbol in self.symbol_table.values() {
ret.push((**symbol).clone());
}
ret
}
pub fn dump(&self) -> String {
format!("{self}")
}
pub fn drop(&mut self, file_path: PathId, prj: Option<StrId>) {
fn is_drop_symbol(symbol: &Symbol, file_path: PathId, prj: Option<StrId>) -> bool {
if symbol.token.source != file_path {
return false;
}
if let Some(prj) = prj {
symbol
.namespace
.paths
.first()
.is_some_and(|x| resource_table::canonical_str_id(*x) == prj)
} else {
true
}
}
fn is_drop_token(token: &Token, file_path: PathId, prj: Option<StrId>) -> bool {
token.source == file_path && (prj.is_none() || scope::token_project(token.id) == prj)
}
let prj = prj.map(resource_table::canonical_str_id);
let drop_list: Vec<_> = self
.symbol_table
.iter()
.filter(|x| is_drop_symbol(x.1, file_path, prj))
.map(|x| *x.0)
.collect();
let dropped: Vec<_> = drop_list
.iter()
.filter_map(|id| {
self.symbol_table
.get(id)
.map(|symbol| scope::DroppedSymbol {
scope: symbol.scope,
name: symbol.token.text,
id: *id,
})
})
.collect();
for id in &drop_list {
if let Some(symbol) = self.symbol_table.get(id)
&& let Some(ids) = self.namespace_index.get_mut(&symbol.namespace.paths)
{
ids.retain(|x| x != id);
}
self.symbol_table.remove(id);
self.reference_table.remove(id);
}
for symbols in self.name_table.values_mut() {
symbols.retain(|x| !drop_list.contains(x));
}
for tokens in self.reference_table.values_mut() {
tokens.retain(|x| !is_drop_token(x, file_path, prj));
}
self.sv_shadows.retain(|x| x.token.source != file_path);
scope::drop_symbols(&dropped);
}
pub fn add_reference(&mut self, target: SymbolId, token: &Token) {
if self.symbol_table.contains_key(&target) {
self.reference_table
.entry(target)
.or_default()
.push(token.to_owned());
}
}
pub fn pending_watermark(&self) -> PendingWatermark {
PendingWatermark {
import: self.import_list.len(),
bind: self.bind_list.len(),
msb: self.msb_list.len(),
connect: self.connect_list.len(),
}
}
pub fn export_fragment(
&self,
symbol_window_start: usize,
symbol_window_end: usize,
watermark: &PendingWatermark,
) -> SymbolTableFragment {
let in_window = |id: &SymbolId| id.0 > symbol_window_start && id.0 <= symbol_window_end;
let mut symbols: Vec<Symbol> = self
.symbol_table
.iter()
.filter(|(id, _)| in_window(id))
.map(|(_, symbol)| (**symbol).clone())
.collect();
let sv_member_key = |x: &Symbol| {
(
x.namespace.paths.clone(),
resource_table::canonical_str_id(x.token.text),
)
};
let mut defined: HashSet<_> = symbols
.iter()
.filter(|x| matches!(x.kind, SymbolKind::SystemVerilog))
.map(sv_member_key)
.collect();
symbols.extend(
self.sv_shadows
.iter()
.filter(|x| in_window(&x.id) && defined.insert(sv_member_key(x)))
.cloned(),
);
symbols.sort_unstable_by_key(|x| x.id);
let mut reference_functions: Vec<_> = self
.reference_func_table
.iter()
.filter(|(id, _)| in_window(id))
.map(|(id, x)| (*id, x.clone()))
.collect();
reference_functions.sort_unstable_by_key(|(id, _)| *id);
let mut references: Vec<_> = self
.reference_table
.iter()
.filter(|(id, _)| in_window(id))
.map(|(id, x)| (*id, x.clone()))
.collect();
references.sort_unstable_by_key(|(id, _)| *id);
SymbolTableFragment {
symbols,
imports: self.import_list[watermark.import..].to_vec(),
binds: self.bind_list[watermark.bind..].to_vec(),
msbs: self.msb_list[watermark.msb..].to_vec(),
connects: self.connect_list[watermark.connect..].to_vec(),
reference_functions,
references,
}
}
pub fn restore_fragment(&mut self, fragment: SymbolTableFragment) -> Result<(), Box<Symbol>> {
for mut symbol in fragment.symbols {
symbol.scope = scope::intern_namespace(&symbol.namespace);
let token = symbol.token;
let id = symbol.id;
let scope = symbol.scope;
let owned_scope_kind = scope::scope_kind_of(&symbol.kind);
if self.insert(&token, symbol.clone()).is_none() {
if matches!(symbol.kind, SymbolKind::SystemVerilog) {
continue;
}
return Err(Box::new(symbol));
}
if let Some(kind) = owned_scope_kind {
let owned = scope::intern_child(scope, token.text, kind);
scope::set_kind_owner(owned, kind, id);
}
}
self.import_list.extend(fragment.imports);
self.bind_list.extend(fragment.binds);
self.msb_list.extend(fragment.msbs);
self.connect_list.extend(fragment.connects);
for (id, functions) in fragment.reference_functions {
self.reference_func_table.insert(id, functions);
}
for (id, tokens) in fragment.references {
self.reference_table.entry(id).or_default().extend(tokens);
}
Ok(())
}
pub fn add_generic_instance(&mut self, target: SymbolId, instance: SymbolId) {
if let Some(symbol) = self.symbol_table.get_mut(&target).map(Rc::make_mut)
&& !symbol.generic_instances.contains(&instance)
{
symbol.generic_instances.push(instance);
}
}
pub fn update_generic_instance_affiliation(&mut self, target: SymbolId, instance: &Symbol) {
let Some(target_inst_ids) = self
.symbol_table
.get(&target)
.map(|x| x.generic_instances.clone())
else {
return;
};
for target_inst_id in &target_inst_ids {
if let Some(target_inst_symbol) =
self.symbol_table.get_mut(target_inst_id).map(Rc::make_mut)
&& target_inst_symbol.token.text == instance.token.text
{
let SymbolKind::GenericInstance(target_inst) = &mut target_inst_symbol.kind else {
unreachable!();
};
let SymbolKind::GenericInstance(inst) = &instance.kind else {
unreachable!();
};
if let Some(affiliation_symbol) = inst.affiliation_symbols.first()
&& !target_inst.affiliation_symbols.contains(affiliation_symbol)
{
target_inst.affiliation_symbols.push(*affiliation_symbol);
}
break;
}
}
}
pub fn add_import(&mut self, import: Import) {
self.import_list.push(import);
}
pub fn apply_import(&mut self) {
self.skip_generic_args = true;
let import_list: Vec<_> = std::mem::take(&mut self.import_list);
for import in &import_list {
let context = ResolveContext::new(&import.path.1);
let Ok((symbol, imported)) = self
.resolve(&import.path.0.generic_path(), &[], context)
.map(|x| ((*x.found).clone(), x.imported))
else {
continue;
};
let target_scope = scope::intern_namespace(&import.namespace);
let define_context = import.namespace.define_context.clone();
if import.wildcard {
let wildcard_target = if matches!(symbol.kind, SymbolKind::Enum(_)) {
Some(symbol.clone())
} else {
self.get_package(&symbol, false, imported)
};
if let Some(target_symbol) = wildcard_target {
let target = target_symbol.inner_namespace();
let source_scope = scope::intern_namespace(&target);
scope::add_wildcard(
target_scope,
source_scope,
define_context,
target.define_context.clone(),
import.path.0.clone(),
);
}
} else if !matches!(symbol.kind, SymbolKind::SystemVerilog) {
let mut package_path = import.path.0.clone();
package_path.paths.pop();
scope::add_import(
target_scope,
symbol.token.text,
symbol.id,
define_context,
package_path,
);
}
}
self.skip_generic_args = false;
}
fn get_package(
&self,
symbol: &Symbol,
include_proto: bool,
include_proto_alias: bool,
) -> Option<Symbol> {
match &symbol.kind {
SymbolKind::Package(x) if x.is_proto => {
if include_proto {
return Some(symbol.clone());
}
}
SymbolKind::Package(_) => return Some(symbol.clone()),
SymbolKind::AliasPackage(x) if !x.is_proto || include_proto_alias => {
let context = ResolveContext::new(&symbol.namespace);
if let Ok(symbol) = self.resolve(&x.target.generic_path(), &[], context) {
let include_proto = if x.is_proto { true } else { include_proto };
return self.get_package(&symbol.found, include_proto, false);
}
}
SymbolKind::GenericInstance(x) => {
let symbol = self.get(x.base).unwrap();
return self.get_package(&symbol, false, false);
}
SymbolKind::GenericParameter(x) => {
if let GenericBoundKind::Proto(proto) = &x.bound
&& let Some(x) = proto.get_user_defined()
{
let context = ResolveContext::new(&symbol.namespace);
if let Ok(symbol) = self.resolve(&x.path.generic_path(), &[], context) {
return self.get_package(&symbol.found, true, false);
}
}
}
_ => {}
}
None
}
pub fn resolve_interfaces(&mut self) -> Vec<AnalyzerError> {
let mut errors = Vec::new();
let mut visited = Vec::new();
let interface_list: Vec<_> = self
.symbol_table
.values()
.filter_map(|x| {
if matches!(x.kind, SymbolKind::Interface(_)) {
Some(x.id)
} else {
None
}
})
.collect();
self.skip_generic_args = true;
for id in interface_list {
let if_symbol = self.get(id).unwrap();
self.resolve_interface(&if_symbol, &mut errors, &mut visited);
}
self.skip_generic_args = false;
errors
}
fn resolve_interface(
&mut self,
if_symbol: &Symbol,
errors: &mut Vec<AnalyzerError>,
visited: &mut Vec<SymbolId>,
) {
if visited.contains(&if_symbol.id) {
return;
}
visited.push(if_symbol.id);
let mut members = HashMap::default();
let mut modports = Vec::new();
self.collect_interface_members(if_symbol, true, &mut members, &mut modports);
self.apply_mixin_interfaces(if_symbol, &mut members, &mut modports, errors, visited);
for modport in &modports {
self.link_modport_members(*modport, &members);
}
self.link_modports(if_symbol, &members, &modports, errors);
}
fn collect_interface_members(
&self,
symbol: &Symbol,
is_own_member: bool,
members: &mut HashMap<StrId, SymbolId>,
modports: &mut Vec<SymbolId>,
) {
if let SymbolKind::Interface(x) = &symbol.kind {
for id in &x.members {
let symbol = self.symbol_table.get(id).unwrap();
members.insert(symbol.token.text, symbol.id);
if is_own_member && matches!(symbol.kind, SymbolKind::Modport(_)) {
modports.push(symbol.id);
}
}
}
}
fn apply_mixin_interfaces(
&mut self,
symbol: &Symbol,
members: &mut HashMap<StrId, SymbolId>,
modports: &mut Vec<SymbolId>,
errors: &mut Vec<AnalyzerError>,
visited: &mut Vec<SymbolId>,
) {
if !symbol.kind.has_mixin_sources() {
return;
}
let if_namespace = symbol.inner_namespace();
for mixin_source in symbol.kind.get_mixin_sources() {
let Some(mixin_symbol) = self.resolve_mixin_interface(mixin_source, &if_namespace)
else {
continue;
};
if !self.is_valid_mixin_interface(symbol, &mixin_symbol, members, errors) {
continue;
}
let target_scope = scope::intern_namespace(&if_namespace);
let target_define_context = if_namespace.define_context.clone();
let source_namespace = mixin_symbol.inner_namespace();
let source_scope = scope::intern_namespace(&source_namespace);
scope::add_mixin_source(
target_scope,
source_scope,
target_define_context,
source_namespace.define_context.clone(),
mixin_source.clone(),
);
self.resolve_interface(&mixin_symbol, errors, visited);
self.collect_interface_members(&mixin_symbol, false, members, modports);
}
}
fn resolve_mixin_interface(
&self,
source_path: &GenericSymbolPath,
namespace: &Namespace,
) -> Option<Rc<Symbol>> {
let Ok(symbol) = self.resolve_generic_path(source_path, namespace) else {
return None;
};
if let SymbolKind::AliasInterface(x) = &symbol.found.kind {
self.resolve_mixin_interface(&x.target, namespace)
} else {
Some(symbol.found)
}
}
fn is_valid_mixin_interface(
&self,
if_symbol: &Symbol,
mixin_symbol: &Symbol,
members: &HashMap<StrId, SymbolId>,
errors: &mut Vec<AnalyzerError>,
) -> bool {
if matches!(mixin_symbol.kind, SymbolKind::GenericParameter(_))
|| !mixin_symbol.is_interface(false)
{
errors.push(AnalyzerError::invalid_mixin(
&mixin_symbol.token.to_string(),
"it is not interface",
&mixin_symbol.token.into(),
));
return false;
}
if mixin_symbol.id == if_symbol.id {
errors.push(AnalyzerError::invalid_mixin(
&mixin_symbol.token.to_string(),
"it mixes in itself",
&mixin_symbol.token.into(),
));
return false;
}
if mixin_symbol.kind.has_parameters() {
errors.push(AnalyzerError::invalid_mixin(
&mixin_symbol.token.to_string(),
"it is parameterized",
&mixin_symbol.token.into(),
));
return false;
}
if mixin_symbol.kind.has_mixin_sources() {
errors.push(AnalyzerError::invalid_mixin(
&mixin_symbol.token.to_string(),
"it mixes in other interfaces",
&mixin_symbol.token.into(),
));
return false;
}
let SymbolKind::Interface(mixin_prop) = &mixin_symbol.kind else {
unreachable!();
};
for member in &mixin_prop.members {
let member_symbol = self.symbol_table.get(member).unwrap();
if members.contains_key(&member_symbol.token.text) {
errors.push(AnalyzerError::invalid_mixin(
&mixin_symbol.token.to_string(),
&format!("{} is already defined", member_symbol.token.text),
&mixin_symbol.token.into(),
));
return false;
}
}
true
}
fn link_modport_members(&mut self, modport: SymbolId, members: &HashMap<StrId, SymbolId>) {
let mp_symbol = self.get(modport).unwrap();
let SymbolKind::Modport(mp) = &mp_symbol.kind else {
unreachable!();
};
for mp_member_id in &mp.members {
let mut mp_member = self.get(*mp_member_id).unwrap();
let Some(target_id) = members.get(&mp_member.token.text) else {
continue;
};
match mp_member.kind {
SymbolKind::ModportFunctionMember(x) => {
mp_member.kind =
SymbolKind::ModportFunctionMember(ModportFunctionMemberProperty {
function: *target_id,
generated_from: x.generated_from,
});
self.update(mp_member);
}
SymbolKind::ModportVariableMember(x) => {
mp_member.kind =
SymbolKind::ModportVariableMember(ModportVariableMemberProperty {
variable: *target_id,
direction: x.direction,
generated_from: x.generated_from,
});
self.update(mp_member);
}
_ => unreachable!(),
}
}
}
fn link_modports(
&mut self,
if_symbol: &Symbol,
members: &HashMap<StrId, SymbolId>,
modports: &Vec<SymbolId>,
errors: &mut Vec<AnalyzerError>,
) {
for mp_id in modports {
if let Some(mut mp_symbol) = self.get(*mp_id)
&& let SymbolKind::Modport(mp) = &mp_symbol.kind
{
let mut default_members = self.collect_modport_default_members(&mp_symbol, members);
let mut mp_members = mp.members.clone();
mp_members.append(&mut default_members);
if let Some(last) = mp_members.last()
&& let Some(last_symbol) = self.get(*last)
&& matches!(last_symbol.token.source, TokenSource::Generated(_))
&& let Some(source) = self.guard_source(&last_symbol)
{
let token: TokenRange = (&mp_symbol.token).into();
let member_source: TokenRange = (&source.token).into();
errors.push(AnalyzerError::last_item_with_define_in_modport(
&source.token.to_string(),
&mp_symbol.token.to_string(),
&token,
&member_source,
));
}
mp_symbol.kind = SymbolKind::Modport(ModportProperty {
interface: if_symbol.id,
members: mp_members,
default: mp.default.clone(),
});
self.update(mp_symbol);
}
}
}
fn guard_source(&self, member: &Symbol) -> Option<Symbol> {
collect_guard_sources(member, |id| self.get(id))
.into_iter()
.find(|x| !attribute_table::ifdef_conditions(&x.token).is_empty())
}
fn collect_modport_default_members(
&mut self,
modport_symbol: &Symbol,
members: &HashMap<StrId, SymbolId>,
) -> Vec<SymbolId> {
let symbol_table = self;
let mut ret = Vec::new();
if let SymbolKind::Modport(x) = &modport_symbol.kind
&& let Some(default) = &x.default
{
let default_member_directions =
symbol_table.collect_modport_default_member_target_directions(default, members);
let explicit_members: HashSet<_> = x
.members
.iter()
.map(|x| symbol_table.get(*x).unwrap().token.text)
.collect();
let mut default_members: Vec<_> = members
.iter()
.filter(|(text, id)| {
if explicit_members.contains(text) {
false
} else if matches!(default, ModportDefault::Same(_)) {
true
} else {
symbol_table
.get(**id)
.map(|x| matches!(x.kind, SymbolKind::Variable(_)))
.unwrap_or(false)
}
})
.collect();
default_members.sort_by(|x, y| x.1.cmp(y.1));
let namespace = modport_symbol.inner_namespace();
for (text, id) in default_members {
let target = default_member_directions.get(text);
let direction = match default {
ModportDefault::Input => Some(Direction::Input),
ModportDefault::Output => Some(Direction::Output),
ModportDefault::Same(_) => target.map(|x| x.0),
ModportDefault::Converse(_) => target.map(|x| x.0.converse()),
};
let Some(direction) = direction else {
continue;
};
let generated_from = target.map(|x| x.1);
let source_path = modport_symbol.token.source.get_path().unwrap();
let token = Token::generate(*text, source_path);
scope::insert_token(token.id, source_path, &namespace);
symbol_table.add_reference(*id, &token);
let kind = if matches!(direction, Direction::Import) {
SymbolKind::ModportFunctionMember(ModportFunctionMemberProperty {
function: *id,
generated_from,
})
} else {
SymbolKind::ModportVariableMember(ModportVariableMemberProperty {
direction,
variable: *id,
generated_from,
})
};
let symbol = Symbol::new(&token, kind, &namespace, false, DocComment::default());
if let Some(id) = symbol_table.insert(&token, symbol) {
ret.push(id);
}
}
}
ret
}
fn collect_modport_default_member_target_directions(
&self,
default: &ModportDefault,
members: &HashMap<StrId, SymbolId>,
) -> HashMap<StrId, (Direction, SymbolId)> {
let mut ret = HashMap::default();
match default {
ModportDefault::Same(targets) | ModportDefault::Converse(targets) => {
for target in targets {
let Some(mp_id) = members.get(&target.text) else {
continue;
};
let Some(mp_symbol) = self.get(*mp_id) else {
continue;
};
let SymbolKind::Modport(mp) = mp_symbol.kind else {
continue;
};
for member in &mp.members {
let Some(member_symbol) = self.get(*member) else {
continue;
};
if let SymbolKind::ModportVariableMember(ref member) = member_symbol.kind {
ret.insert(
member_symbol.token.text,
(member.direction, member_symbol.id),
);
} else if matches!(default, ModportDefault::Same(_))
&& matches!(member_symbol.kind, SymbolKind::ModportFunctionMember(_))
{
ret.insert(
member_symbol.token.text,
(Direction::Import, member_symbol.id),
);
}
}
}
}
_ => {}
}
ret
}
pub fn add_bind(&mut self, bind: Bind) {
self.bind_list.push(bind);
}
pub fn apply_bind(&mut self) -> Vec<AnalyzerError> {
let mut errors = Vec::new();
let bind_list: Vec<Bind> = std::mem::take(&mut self.bind_list);
for bind in bind_list {
let Ok(target) = self.resolve_generic_path(&bind.target.0, &bind.target.1) else {
continue;
};
let Some(namespace) = self.resolve_bind_target_namespace(&target.found) else {
continue;
};
let symbol = Symbol::new(
&bind.token,
SymbolKind::Instance(bind.property.clone()),
&namespace,
false,
bind.doc_comment.clone(),
);
if self.insert(&bind.token, symbol).is_some() {
if let TokenSource::File { path, .. } = bind.token.source {
scope::insert_token(bind.token.id, path, &namespace);
for target in bind.property.parameter_connects.values() {
Self::update_connect_target_namespace(target, path, &namespace);
}
for target in bind.property.port_connects.values() {
Self::update_connect_target_namespace(target, path, &namespace);
}
}
} else {
errors.push(AnalyzerError::duplicated_identifier(
&bind.token.to_string(),
DuplicatedIdentifierKind::Normal,
&bind.token.into(),
));
}
}
errors
}
fn add_msb(&mut self, msb: Msb) {
self.msb_list.push(msb);
}
fn get_msb(&mut self) -> Vec<Msb> {
std::mem::take(&mut self.msb_list)
}
fn add_connect(&mut self, connect: Connect) {
self.connect_list.push(connect);
}
fn get_connect(&mut self) -> Vec<Connect> {
std::mem::take(&mut self.connect_list)
}
fn resolve_generic_path(
&self,
path: &GenericSymbolPath,
namespace: &Namespace,
) -> Result<ResolveResult, ResolveError> {
let context = ResolveContext::new(namespace);
self.resolve(&path.generic_path(), &[], context)
}
fn resolve_bind_target_namespace(&mut self, target: &Symbol) -> Option<Namespace> {
match &target.kind {
SymbolKind::Module(_) => Some(target.inner_namespace()),
SymbolKind::AliasModule(x) => {
let Ok(target) = self.resolve_generic_path(&x.target, &target.namespace) else {
return None;
};
self.resolve_bind_target_namespace(&target.found)
}
SymbolKind::Interface(_) => Some(target.inner_namespace()),
SymbolKind::AliasInterface(x) => {
let Ok(target) = self.resolve_generic_path(&x.target, &target.namespace) else {
return None;
};
self.resolve_bind_target_namespace(&target.found)
}
_ => None,
}
}
fn update_connect_target_namespace(
target: &ConnectTarget,
path: PathId,
namespace: &Namespace,
) {
let mut collector = TokenCollector::new(false);
collector.expression(&target.expression);
for token in &collector.tokens {
scope::insert_token(token.id, path, namespace);
}
}
pub fn resolve_user_defined(&self) -> Vec<(SymbolId, SymbolId)> {
let mut resolved = Vec::new();
for symbol in self.symbol_table.values() {
if let Some(x) = symbol.kind.get_type()
&& let TypeKind::UserDefined(x) = &x.kind
{
let context = ResolveContext::new(&symbol.namespace);
if let Ok(type_symbol) =
self.resolve(&x.path.generic_path(), &x.path.generic_arguments(), context)
{
resolved.push((symbol.id, type_symbol.found.id));
}
}
}
resolved
}
pub fn set_user_defined(&mut self, resolved: Vec<(SymbolId, SymbolId)>) {
for (id, type_id) in resolved {
let symbol = Rc::make_mut(self.symbol_table.get_mut(&id).unwrap());
if let Some(x) = symbol.kind.get_type_mut()
&& let TypeKind::UserDefined(x) = &mut x.kind
{
x.symbol = Some(type_id);
}
}
}
pub fn get_function_symbols(&self) -> Vec<Symbol> {
self.symbol_table
.values()
.filter_map(|symbol| {
if matches!(symbol.kind, SymbolKind::Function(_)) {
Some((**symbol).clone())
} else {
None
}
})
.collect()
}
pub fn get_enum_symbols(&self) -> Vec<Symbol> {
self.symbol_table
.values()
.filter_map(|symbol| {
if matches!(symbol.kind, SymbolKind::Enum(_)) {
Some((**symbol).clone())
} else {
None
}
})
.collect()
}
pub fn find_project_symbol(&self, prj: StrId) -> Option<Symbol> {
for symbol in self.symbol_table.values() {
if matches!(symbol.kind, SymbolKind::Namespace) && symbol.token.text == prj {
return Some((**symbol).clone());
}
}
None
}
pub fn add_project_local(&mut self, prj: StrId, from: StrId, to: StrId) {
self.project_local_table
.entry(prj)
.and_modify(|x| {
x.insert(from, to);
})
.or_insert(HashMap::from_iter([(from, to)]));
}
pub fn add_reference_functions(&mut self, id: SymbolId, functions: Vec<GenericSymbolPath>) {
self.reference_func_table.insert(id, functions);
}
pub fn get_reference_functions(&self, id: SymbolId) -> Option<Vec<GenericSymbolPath>> {
self.reference_func_table.get(&id).cloned()
}
pub fn get_references(&self, id: SymbolId) -> Option<Vec<Token>> {
self.reference_table.get(&id).cloned()
}
pub fn clear(&mut self) {
scope::clear();
self.clone_from(&Self::new());
}
fn check_unused_variable(&self) -> Vec<AnalyzerError> {
let mut ret = vec![];
for symbol in self.symbol_table.values() {
if let SymbolKind::Variable(_) = symbol.kind
&& self
.reference_table
.get(&symbol.id)
.is_none_or(|v| v.is_empty())
&& !symbol.allow_unused
{
let name = symbol.token.to_string();
if !name.starts_with('_') {
ret.push(AnalyzerError::unused_variable(
&symbol.token.to_string(),
&symbol.token.into(),
));
}
}
}
ret
}
fn get_tests(&self, project_name: &str) -> Vec<(StrId, TestProperty)> {
self.symbol_table
.values()
.filter(|s| s.namespace.to_string() == project_name)
.filter_map(|symbol| match &symbol.kind {
SymbolKind::Module(x) if x.test.is_some() => {
Some((symbol.token.text, x.test.clone().unwrap()))
}
SymbolKind::Test(x) => Some((symbol.token.text, x.clone())),
_ => None,
})
.collect()
}
fn get_doc_tests(&self, project_name: &str) -> Vec<DocTestTarget> {
self.symbol_table
.values()
.filter(|s| s.namespace.to_string() == project_name)
.filter_map(|symbol| {
if let SymbolKind::Module(ref module_prop) = symbol.kind {
let wavedrom_json = symbol.doc_comment.extract_wavedrom_test()?;
let ports = module_prop
.ports
.iter()
.map(|p| {
let prop = p.property();
let name = veryl_parser::resource_table::get_str_value(p.name())
.unwrap_or_default();
let dir = match prop.direction {
Direction::Input => "input",
Direction::Output => "output",
Direction::Inout => "inout",
_ => "unknown",
};
(name, dir.to_string())
})
.collect();
let path = symbol.token.source.get_path()?;
Some(DocTestTarget {
module_name: symbol.token.text,
wavedrom_json,
ports,
path,
})
} else {
None
}
})
.collect()
}
fn check_wavedrom(&self) -> Vec<AnalyzerError> {
let mut ret = vec![];
for symbol in self.symbol_table.values() {
if let SymbolKind::Module(ref module_prop) = symbol.kind {
let port_names: Vec<String> = module_prop
.ports
.iter()
.map(|p| {
veryl_parser::resource_table::get_str_value(p.name()).unwrap_or_default()
})
.collect();
ret.extend(wavedrom::check_wavedrom(
&symbol.token,
symbol.doc_comment.extract_wavedrom_block(false),
symbol.doc_comment.extract_wavedrom_block(true),
&port_names,
));
}
}
ret
}
}
impl fmt::Display for SymbolTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "SymbolTable [")?;
let mut symbol_width = 0;
let mut namespace_width = 0;
let mut reference_width = 0;
let mut vec: Vec<_> = self.name_table.iter().collect();
vec.sort_by(|x, y| format!("{}", x.0).cmp(&format!("{}", y.0)));
for (k, v) in &vec {
symbol_width = symbol_width.max(format!("{k}").len());
for id in *v {
let symbol = self.symbol_table.get(id).unwrap();
namespace_width = namespace_width.max(format!("{}", symbol.namespace).len());
reference_width = reference_width
.max(format!("{}", self.reference_table.get(id).map_or(0, |v| v.len())).len());
}
}
for (k, v) in &vec {
for id in *v {
let symbol = self.symbol_table.get(id).unwrap();
writeln!(
f,
" {:symbol_width$} @ {:namespace_width$} {{ref: {:reference_width$}}}: {},",
k,
symbol.namespace,
self.reference_table.get(id).map_or(0, |v| v.len()),
symbol.kind,
symbol_width = symbol_width,
namespace_width = namespace_width,
reference_width = reference_width,
)?;
}
}
writeln!(f, "]")?;
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ResolvePhase {
Alias,
TypeTrace,
}
#[derive(Clone)]
struct ResolveContext<'a> {
found: Option<&'a Rc<Symbol>>,
last_found: Option<&'a Rc<Symbol>>,
last_found_type: Option<SymbolId>,
full_path: Vec<SymbolId>,
scope: scope::ScopeId,
inst_scope: scope::ScopeId,
define_context: DefineContext,
generic_tables: GenericTables,
visiting: SVec<(SymbolId, ResolvePhase)>,
inner: bool,
other_prj: bool,
root_prj: bool,
sv_member: bool,
imported: bool,
depth: usize,
}
impl ResolveContext<'_> {
fn from_scope(scope: scope::ScopeId, define_context: DefineContext) -> Self {
Self {
found: None,
last_found: None,
last_found_type: None,
full_path: vec![],
scope,
inst_scope: scope,
define_context,
generic_tables: GenericTables::default(),
visiting: SVec::new(),
inner: false,
other_prj: false,
root_prj: true,
sv_member: false,
imported: false,
depth: 0,
}
}
fn new(namespace: &Namespace) -> Self {
Self::from_scope(
scope::intern_namespace(namespace),
namespace.define_context.clone(),
)
}
fn push(&self) -> Self {
let mut ret = Self::from_scope(self.scope, self.define_context.clone());
ret.generic_tables = self.generic_tables.clone();
ret.visiting = self.visiting.clone();
ret.depth = self.depth + 1;
ret
}
fn set_namespace(&mut self, scope: scope::ScopeId, define_context: DefineContext) {
self.scope = scope;
self.inst_scope = scope;
self.define_context = define_context;
}
fn set_inner(&mut self, symbol: &Symbol) {
let base_inner = scope::inner_scope(symbol.scope, symbol.token.text);
self.inst_scope = if scope::generic_delegation(self.inst_scope) == Some(symbol.scope) {
scope::register_generic_child(self.inst_scope, symbol.token.text, base_inner)
} else {
base_inner
};
self.scope = base_inner;
self.define_context = symbol.namespace.define_context.clone();
}
fn namespace(&self) -> Namespace {
scope::namespace(self.scope, &self.define_context)
}
fn indent(&self) -> String {
" ".repeat(self.depth)
}
}
const DEFINED_NAMESPACES: [&str; 4] = ["$sv", "$std", "$tb", "$comp"];
const SYSTEMVERILOG_KEYWORDS: [&str; 248] = [
"accept_on",
"alias",
"always",
"always_comb",
"always_ff",
"always_latch",
"and",
"assert",
"assign",
"assume",
"automatic",
"before",
"begin",
"bind",
"bins",
"binsof",
"bit",
"break",
"buf",
"bufif0",
"bufif1",
"byte",
"case",
"casex",
"casez",
"cell",
"chandle",
"checker",
"class",
"clocking",
"cmos",
"config",
"const",
"constraint",
"context",
"continue",
"cover",
"covergroup",
"coverpoint",
"cross",
"deassign",
"default",
"defparam",
"design",
"disable",
"dist",
"do",
"edge",
"else",
"end",
"endcase",
"endchecker",
"endclass",
"endclocking",
"endconfig",
"endfunction",
"endgenerate",
"endgroup",
"endinterface",
"endmodule",
"endpackage",
"endprimitive",
"endprogram",
"endproperty",
"endspecify",
"endsequence",
"endtable",
"endtask",
"enum",
"event",
"eventually",
"expect",
"export",
"extends",
"extern",
"final",
"first_match",
"for",
"force",
"foreach",
"forever",
"fork",
"forkjoin",
"function",
"generate",
"genvar",
"global",
"highz0",
"highz1",
"if",
"iff",
"ifnone",
"ignore_bins",
"illegal_bins",
"implements",
"implies",
"import",
"incdir",
"include",
"initial",
"inout",
"input",
"inside",
"instance",
"int",
"integer",
"interconnect",
"interface",
"intersect",
"join",
"join_any",
"join_none",
"large",
"let",
"liblist",
"library",
"local",
"localparam",
"logic",
"longint",
"macromodule",
"matches",
"medium",
"modport",
"module",
"nand",
"negedge",
"nettype",
"new",
"nexttime",
"nmos",
"nor",
"noshowcancelled",
"not",
"notif0",
"notif1",
"null",
"or",
"output",
"package",
"packed",
"parameter",
"pmos",
"posedge",
"primitive",
"priority",
"program",
"property",
"protected",
"pull0",
"pull1",
"pulldown",
"pullup",
"pulsestyle_ondetect",
"pulsestyle_onevent",
"pure",
"rand",
"randc",
"randcase",
"randsequence",
"rcmos",
"real",
"realtime",
"ref",
"reg",
"reject_on",
"release",
"repeat",
"restrict",
"return",
"rnmos",
"rpmos",
"rtran",
"rtranif0",
"rtranif1",
"s_always",
"s_eventually",
"s_nexttime",
"s_until",
"s_until_with",
"scalared",
"sequence",
"shortint",
"shortreal",
"showcancelled",
"signed",
"small",
"soft",
"solve",
"specify",
"specparam",
"static",
"string",
"strong",
"strong0",
"strong1",
"struct",
"super",
"supply0",
"supply1",
"sync_accept_on",
"sync_reject_on",
"table",
"tagged",
"task",
"this",
"throughout",
"time",
"timeprecision",
"timeunit",
"tran",
"tranif0",
"tranif1",
"tri",
"tri0",
"tri1",
"triand",
"trior",
"trireg",
"type",
"typedef",
"union",
"unique",
"unique0",
"unsigned",
"until",
"until_with",
"untyped",
"use",
"uwire",
"var",
"vectored",
"virtual",
"void",
"wait",
"wait_order",
"wand",
"weak",
"weak0",
"weak1",
"while",
"wildcard",
"wire",
"with",
"within",
"wor",
"xnor",
"xor",
];
pub fn is_sv_keyword(s: &str) -> bool {
SYSTEMVERILOG_KEYWORDS.binary_search(&s).is_ok()
}
thread_local!(static SYMBOL_TABLE: RefCell<SymbolTable> = RefCell::new(SymbolTable::new()));
type ResolveCacheKey = (SymbolPath, scope::ScopeId, DefineContext);
thread_local!(static SYMBOL_CACHE: RefCell<HashMap<ResolveCacheKey, Rc<ResolveResult>>> = RefCell::new(HashMap::default()));
type NamespaceGenericMap = Option<Rc<Vec<GenericMap>>>;
thread_local!(static NS_GENERIC_MAP_CACHE: RefCell<HashMap<(scope::ScopeId, DefineContext), NamespaceGenericMap>> = RefCell::new(HashMap::default()));
thread_local!(static SYMBOL_ERR_CACHE: RefCell<HashMap<ResolveCacheKey, ResolveError>> = RefCell::new(HashMap::default()));
type GenericInstanceKey = (scope::ScopeId, SymbolId, Vec<SymbolPath>);
thread_local!(static GENERIC_INSTANCE_INDEX: RefCell<HashMap<GenericInstanceKey, SymbolId>> = RefCell::new(HashMap::default()));
fn generic_instance_key(
enclosing: scope::ScopeId,
base: SymbolId,
arguments: &[GenericSymbolPath],
) -> GenericInstanceKey {
(
enclosing,
base,
arguments.iter().map(|a| a.mangled_path()).collect(),
)
}
pub fn index_generic_instance(
enclosing: scope::ScopeId,
base: SymbolId,
arguments: &[GenericSymbolPath],
instance: SymbolId,
) {
let key = generic_instance_key(enclosing, base, arguments);
GENERIC_INSTANCE_INDEX.with(|f| {
let mut index = f.borrow_mut();
if let Some(prev) = index.get(&key) {
debug_assert_eq!(
*prev, instance,
"structural generic-instance key collides with a distinct instance"
);
}
index.insert(key, instance);
});
}
fn clear_resolve_caches() {
SYMBOL_CACHE.with(|f| f.borrow_mut().clear());
SYMBOL_ERR_CACHE.with(|f| f.borrow_mut().clear());
NS_GENERIC_MAP_CACHE.with(|f| f.borrow_mut().clear());
}
pub fn suppress_cache_clear() {
SYMBOL_TABLE.with(|f| f.borrow_mut().suppress_cache_clear = true);
}
pub fn resume_cache_clear() {
SYMBOL_TABLE.with(|f| f.borrow_mut().suppress_cache_clear = false);
}
fn clear_cache() {
let suppress = SYMBOL_TABLE.with(|f| f.borrow().suppress_cache_clear);
if !suppress {
clear_resolve_caches();
}
}
pub fn insert(token: &Token, symbol: Symbol) -> Option<SymbolId> {
let ret = SYMBOL_TABLE.with(|f| f.borrow_mut().insert(token, symbol));
if ret.is_some() {
clear_resolve_caches();
}
ret
}
pub fn insert_sv_member(token: &Token, symbol: Symbol) {
let ret = SYMBOL_TABLE.with(|f| f.borrow_mut().insert_sv_member(token, symbol));
if ret.is_some() {
clear_resolve_caches();
}
}
pub fn get(id: SymbolId) -> Option<Symbol> {
SYMBOL_TABLE.with(|f| f.borrow().get(id))
}
fn collect_guard_sources(member: &Symbol, get: impl Fn(SymbolId) -> Option<Symbol>) -> Vec<Symbol> {
let mut ret = Vec::new();
let mut visited = Vec::new();
let mut current = Some(member.clone());
while let Some(member) = current {
if visited.contains(&member.id) {
break;
}
visited.push(member.id);
let (target, generated_from) = match member.kind {
SymbolKind::ModportVariableMember(ref x) => (x.variable, x.generated_from),
SymbolKind::ModportFunctionMember(ref x) => (x.function, x.generated_from),
_ => break,
};
ret.push(member);
ret.extend(get(target));
current = generated_from.and_then(&get);
}
ret
}
pub fn modport_member_conditions(member: &Symbol) -> Vec<IfdefCondition> {
let mut ret: Vec<IfdefCondition> = Vec::new();
for source in collect_guard_sources(member, get) {
for condition in attribute_table::ifdef_conditions(&source.token) {
if !ret.contains(&condition) {
ret.push(condition);
}
}
}
ret
}
pub fn get_rc(id: SymbolId) -> Option<Rc<Symbol>> {
SYMBOL_TABLE.with(|f| f.borrow().get_rc(id))
}
pub fn update(symbol: Symbol) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().update(symbol))
}
pub fn resolve<T: Into<SymbolPathNamespace>>(path: T) -> Result<Rc<ResolveResult>, ResolveError> {
let path: SymbolPathNamespace = path.into();
let scope = path.2.unwrap_or_else(|| scope::intern_namespace(&path.1));
let define_context = path.1.define_context.clone();
let key: ResolveCacheKey = (path.0, scope, define_context.clone());
if let Some(x) = SYMBOL_CACHE.with(|f| f.borrow().get(&key).cloned()) {
Ok(x)
} else if let Some(e) = SYMBOL_ERR_CACHE.with(|f| f.borrow().get(&key).cloned()) {
Err(e)
} else {
match SYMBOL_TABLE.with(|f| {
f.borrow().resolve(
&key.0,
&[],
ResolveContext::from_scope(scope, define_context),
)
}) {
Ok(x) => {
let x = Rc::new(x);
SYMBOL_CACHE.with(|f| f.borrow_mut().insert(key, Rc::clone(&x)));
Ok(x)
}
Err(e) => {
SYMBOL_ERR_CACHE.with(|f| f.borrow_mut().insert(key, e.clone()));
Err(e)
}
}
}
}
pub enum StructuralRoot {
Scope(scope::ScopeId, DefineContext),
Token(resource_table::TokenId),
}
impl From<(scope::ScopeId, DefineContext)> for StructuralRoot {
fn from((scope, define_context): (scope::ScopeId, DefineContext)) -> Self {
StructuralRoot::Scope(scope, define_context)
}
}
impl From<resource_table::TokenId> for StructuralRoot {
fn from(token: resource_table::TokenId) -> Self {
StructuralRoot::Token(token)
}
}
impl From<&Namespace> for StructuralRoot {
fn from(namespace: &Namespace) -> Self {
StructuralRoot::Scope(
scope::intern_namespace(namespace),
namespace.define_context.clone(),
)
}
}
impl StructuralRoot {
fn structural_scope(&self) -> Option<(scope::ScopeId, DefineContext)> {
match self {
StructuralRoot::Scope(scope, define_context) => Some((*scope, define_context.clone())),
StructuralRoot::Token(token) => scope::token_scope(*token),
}
}
fn fallback_path(&self, path: SymbolPath) -> SymbolPathNamespace {
match self {
StructuralRoot::Scope(scope, define_context) => {
SymbolPathNamespace::from_scope(path, *scope, define_context.clone())
}
StructuralRoot::Token(token) => SymbolPathNamespace::from_token(path, *token),
}
}
}
pub fn resolve_generic_structural(
path: &GenericSymbolPath,
root: impl Into<StructuralRoot>,
) -> Result<Rc<ResolveResult>, ResolveError> {
let root = root.into();
let structural = root.structural_scope().and_then(|(scope, define_context)| {
SYMBOL_TABLE.with(|f| {
f.borrow()
.resolve_generic_structural(path, scope, define_context)
})
});
match structural {
Some(structural) => Ok(Rc::new(structural)),
None => resolve(root.fallback_path(path.mangled_path())),
}
}
pub fn resolve_base_path(
path: &GenericSymbolPath,
i: usize,
root: impl Into<StructuralRoot>,
) -> Result<Rc<ResolveResult>, ResolveError> {
let mut sliced = path.slice(i);
if let Some(last) = sliced.paths.last_mut() {
last.arguments.clear();
}
resolve_generic_structural(&sliced, root)
}
pub fn get_namespace_symbol(namespace: &Namespace) -> Option<Symbol> {
let mut namespace = namespace.clone();
namespace.strip_anonymous_path();
if let Some(path) = namespace.pop()
&& namespace.depth() >= 1
{
resolve((path, &namespace)).map(|x| (*x.found).clone()).ok()
} else {
None
}
}
pub fn get_all() -> Vec<Symbol> {
SYMBOL_TABLE.with(|f| f.borrow().get_all())
}
pub fn dump() -> String {
SYMBOL_TABLE.with(|f| f.borrow().dump())
}
pub fn drop(file_path: PathId, prj: Option<StrId>) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().drop(file_path, prj))
}
pub fn add_reference(target: SymbolId, token: &Token) {
SYMBOL_TABLE.with(|f| f.borrow_mut().add_reference(target, token))
}
pub fn add_generic_instance(target: SymbolId, instance: SymbolId) {
clear_cache();
SYMBOL_TABLE.with(|f| f.borrow_mut().add_generic_instance(target, instance))
}
pub fn update_generic_instance_affiliation(target: SymbolId, instance: &Symbol) {
clear_cache();
SYMBOL_TABLE.with(|f| {
f.borrow_mut()
.update_generic_instance_affiliation(target, instance)
})
}
pub fn add_import(import: Import) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().add_import(import))
}
pub fn apply_import() {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().apply_import());
}
pub fn resolve_interfaces() -> Vec<AnalyzerError> {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().resolve_interfaces())
}
pub fn add_bind(bind: Bind) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().add_bind(bind))
}
pub fn apply_bind() -> Vec<AnalyzerError> {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().apply_bind())
}
pub fn add_msb(msb: Msb) {
SYMBOL_TABLE.with(|f| f.borrow_mut().add_msb(msb))
}
pub fn apply_msb() -> Vec<AnalyzerError> {
let msb_list = SYMBOL_TABLE.with(|f| f.borrow_mut().get_msb());
check_msb(msb_list)
}
pub fn add_connect(connect: Connect) {
SYMBOL_TABLE.with(|f| f.borrow_mut().add_connect(connect))
}
pub fn apply_connect() -> Vec<AnalyzerError> {
let connect_list = SYMBOL_TABLE.with(|f| f.borrow_mut().get_connect());
check_connect(connect_list)
}
pub fn resolve_user_defined() {
clear_resolve_caches();
let resolved = SYMBOL_TABLE.with(|f| f.borrow().resolve_user_defined());
SYMBOL_TABLE.with(|f| f.borrow_mut().set_user_defined(resolved));
}
pub fn resolve_function() {
let list = SYMBOL_TABLE.with(|f| f.borrow().get_function_symbols());
function::resolve_function(&list);
}
pub fn resolve_enum() -> Vec<AnalyzerError> {
let list = SYMBOL_TABLE.with(|f| f.borrow().get_enum_symbols());
r#enum::resolve_enum(&list)
}
pub fn add_project_local(prj: StrId, from: StrId, to: StrId) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().add_project_local(prj, from, to))
}
pub fn add_reference_functions(id: SymbolId, functions: Vec<GenericSymbolPath>) {
clear_resolve_caches();
SYMBOL_TABLE.with(|f| f.borrow_mut().add_reference_functions(id, functions))
}
pub fn get_reference_functions(id: SymbolId) -> Option<Vec<GenericSymbolPath>> {
SYMBOL_TABLE.with(|f| f.borrow().get_reference_functions(id))
}
pub fn get_references(id: SymbolId) -> Option<Vec<Token>> {
SYMBOL_TABLE.with(|f| f.borrow().get_references(id))
}
pub fn pending_watermark() -> PendingWatermark {
SYMBOL_TABLE.with(|f| f.borrow().pending_watermark())
}
pub fn export_fragment(
symbol_window_start: usize,
symbol_window_end: usize,
watermark: &PendingWatermark,
) -> SymbolTableFragment {
SYMBOL_TABLE.with(|f| {
f.borrow()
.export_fragment(symbol_window_start, symbol_window_end, watermark)
})
}
pub fn restore_fragment(fragment: SymbolTableFragment) -> Result<(), Box<Symbol>> {
let ret = SYMBOL_TABLE.with(|f| f.borrow_mut().restore_fragment(fragment));
clear_resolve_caches();
ret
}
pub fn clear() {
clear_resolve_caches();
GENERIC_INSTANCE_INDEX.with(|f| f.borrow_mut().clear());
SYMBOL_TABLE.with(|f| f.borrow_mut().clear())
}
pub fn check_unused_variable() -> Vec<AnalyzerError> {
SYMBOL_TABLE.with(|f| f.borrow().check_unused_variable())
}
pub fn get_tests(project_name: &str) -> Vec<(StrId, TestProperty)> {
SYMBOL_TABLE.with(|f| f.borrow().get_tests(project_name))
}
pub fn get_doc_tests(project_name: &str) -> Vec<DocTestTarget> {
SYMBOL_TABLE.with(|f| f.borrow().get_doc_tests(project_name))
}
pub fn check_wavedrom() -> Vec<AnalyzerError> {
SYMBOL_TABLE.with(|f| f.borrow().check_wavedrom())
}
#[cfg(test)]
mod tests {
use crate::namespace::Namespace;
use crate::symbol_table::{ResolveError, ResolveResult, SymbolPath};
use crate::{Analyzer, symbol_table};
use veryl_metadata::Metadata;
use veryl_parser::{Parser, resource_table};
const CODE: &str = r##"
module ModuleA #(
param paramA: u32 = 1,
param paramB: PackageA::StructA = 1,
) (
portA: input logic<10>,
portB: modport InterfaceA::modportA,
) {
const localA: u32 = 1;
const localB: PackageA::StructA = 1;
type TypeA = PackageA::StructA;
var memberA: logic;
var memberB: PackageA::StructA;
var memberC: TypeA;
var memberD: $sv::SvTypeA;
var memberE: PackageA::UnionA;
inst instA: InterfaceA;
}
interface InterfaceA #(
param paramA: u32 = 1,
param paramB: PackageA::StructA = 1,
) {
const localA: u32 = 1;
const localB: PackageA::StructA = 1;
type TypeA = PackageA::StructA;
var memberA: logic;
var memberB: PackageA::StructA;
var memberC: TypeA;
modport modportA {
memberA: input,
memberB: output,
memberC: output,
}
}
package PackageA {
const localA: u32 = 1;
struct StructA {
memberA: logic,
memberB: StructB,
}
struct StructB {
memberA: logic,
}
enum EnumA: logic<2> {
memberA,
}
union UnionA {
memberA: logic<2>,
memberB: EnumA,
}
}
"##;
fn parse() {
let metadata = Metadata::create_default("prj").unwrap();
let parser = Parser::parse(CODE, &"").unwrap();
let analyzer = Analyzer::new(&metadata);
analyzer.analyze_pass1("prj", &parser.veryl);
}
#[track_caller]
fn check_found(result: Result<std::rc::Rc<ResolveResult>, ResolveError>, expect: &str) {
assert_eq!(format!("{}", result.unwrap().found.namespace), expect);
}
#[track_caller]
fn check_not_found(result: Result<std::rc::Rc<ResolveResult>, ResolveError>) {
assert!(result.is_err());
}
fn create_path(paths: &[&str]) -> SymbolPath {
let mut ret = SymbolPath::default();
for path in paths {
ret.push(resource_table::insert_str(path));
}
ret
}
fn create_namespace(paths: &[&str]) -> Namespace {
let mut ret = Namespace::new();
ret.push(resource_table::insert_str("prj"));
for path in paths {
ret.push(resource_table::insert_str(path));
}
ret
}
fn resolve(
paths: &[&str],
namespace: &[&str],
) -> Result<std::rc::Rc<ResolveResult>, ResolveError> {
let path = create_path(paths);
let namespace = create_namespace(namespace);
symbol_table::resolve((&path, &namespace))
}
#[test]
fn module() {
parse();
let symbol = resolve(&["ModuleA"], &[]);
check_found(symbol, "prj");
let symbol = resolve(&["ModuleA"], &["ModuleA"]);
check_found(symbol, "prj");
let symbol = resolve(&["ModuleA"], &["InterfaceA"]);
check_found(symbol, "prj");
let symbol = resolve(&["ModuleA"], &["PackageA"]);
check_found(symbol, "prj");
}
#[test]
fn interface() {
parse();
let symbol = resolve(&["InterfaceA"], &[]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["ModuleA"]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["InterfaceA"]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["PackageA"]);
check_found(symbol, "prj");
}
#[test]
fn package() {
parse();
let symbol = resolve(&["PackageA"], &[]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["ModuleA"]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["InterfaceA"]);
check_found(symbol, "prj");
let symbol = resolve(&["InterfaceA"], &["PackageA"]);
check_found(symbol, "prj");
}
#[test]
fn param() {
parse();
let symbol = resolve(&["paramA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["paramA"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["paramA"], &["InterfaceA"]);
check_found(symbol, "prj::InterfaceA");
let symbol = resolve(&["paramA"], &["PackageA"]);
check_not_found(symbol);
let symbol = resolve(&["paramB", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["paramB", "memberB"], &["InterfaceA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["paramB", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructB");
let symbol = resolve(&["paramB", "memberB", "memberA"], &["InterfaceA"]);
check_found(symbol, "prj::PackageA::StructB");
}
#[test]
fn local() {
parse();
let symbol = resolve(&["localA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["localA"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["localA"], &["InterfaceA"]);
check_found(symbol, "prj::InterfaceA");
let symbol = resolve(&["localA"], &["PackageA"]);
check_found(symbol, "prj::PackageA");
let symbol = resolve(&["localB", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["localB", "memberB"], &["InterfaceA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["localB", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructB");
let symbol = resolve(&["localB", "memberB", "memberA"], &["InterfaceA"]);
check_found(symbol, "prj::PackageA::StructB");
}
#[test]
fn port() {
parse();
let symbol = resolve(&["portA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["portA"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["portA"], &["InterfaceA"]);
check_not_found(symbol);
let symbol = resolve(&["portA"], &["PackageA"]);
check_not_found(symbol);
}
#[test]
fn variable() {
parse();
let symbol = resolve(&["memberA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["memberA"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["memberA"], &["InterfaceA"]);
check_found(symbol, "prj::InterfaceA");
let symbol = resolve(&["memberA"], &["PackageA"]);
check_not_found(symbol);
}
#[test]
fn r#struct() {
parse();
let symbol = resolve(&["StructA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["StructA"], &["ModuleA"]);
check_not_found(symbol);
let symbol = resolve(&["StructA"], &["InterfaceA"]);
check_not_found(symbol);
let symbol = resolve(&["StructA"], &["PackageA"]);
check_found(symbol, "prj::PackageA");
}
#[test]
fn struct_member() {
parse();
let symbol = resolve(&["memberA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["memberA"], &["PackageA", "StructA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["memberB", "memberX"], &["ModuleA"]);
check_not_found(symbol);
}
#[test]
fn r#enum() {
parse();
let symbol = resolve(&["EnumA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["EnumA"], &["ModuleA"]);
check_not_found(symbol);
let symbol = resolve(&["EnumA"], &["InterfaceA"]);
check_not_found(symbol);
let symbol = resolve(&["EnumA"], &["PackageA"]);
check_found(symbol, "prj::PackageA");
}
#[test]
fn enum_member() {
parse();
let symbol = resolve(&["EnumA", "memberA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["EnumA", "memberA"], &["ModuleA"]);
check_not_found(symbol);
let symbol = resolve(&["EnumA", "memberA"], &["InterfaceA"]);
check_not_found(symbol);
let symbol = resolve(&["EnumA", "memberA"], &["PackageA"]);
check_found(symbol, "prj::PackageA::EnumA");
}
#[test]
fn union() {
parse();
let symbol = resolve(&["UnionA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["UnionA"], &["ModuleA"]);
check_not_found(symbol);
let symbol = resolve(&["UnionA"], &["InterfaceA"]);
check_not_found(symbol);
let symbol = resolve(&["UnionA"], &["PackageA"]);
check_found(symbol, "prj::PackageA");
}
#[test]
fn union_member() {
parse();
let symbol = resolve(&["memberE"], &[]);
check_not_found(symbol);
let symbol = resolve(&["memberE"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["memberE", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::UnionA");
let symbol = resolve(&["memberE", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::UnionA");
}
#[test]
fn modport() {
parse();
let symbol = resolve(&["portB"], &[]);
check_not_found(symbol);
let symbol = resolve(&["portB"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["portB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::InterfaceA::modportA");
let symbol = resolve(&["portB", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::InterfaceA::modportA");
let symbol = resolve(&["portB", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["portB", "memberB", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["portB", "memberB", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructB");
let symbol = resolve(&["portB", "memberC"], &["ModuleA"]);
check_found(symbol, "prj::InterfaceA::modportA");
let symbol = resolve(&["portB", "memberC", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["portB", "memberC", "memberB"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["portB", "memberC", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructB");
}
#[test]
fn typedef() {
parse();
let symbol = resolve(&["memberC"], &[]);
check_not_found(symbol);
let symbol = resolve(&["memberC"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["memberC", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructA");
let symbol = resolve(&["memberC", "memberX"], &["ModuleA"]);
check_not_found(symbol);
}
#[test]
fn sv_member() {
parse();
let symbol = resolve(&["memberD"], &[]);
check_not_found(symbol);
let symbol = resolve(&["memberD"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["memberD", "memberA"], &["ModuleA"]);
check_found(symbol, "$sv::SvTypeA");
let symbol = resolve(&["memberD", "memberA", "memberA", "memberA"], &["ModuleA"]);
check_found(symbol, "$sv::SvTypeA");
}
#[test]
fn inst() {
parse();
let symbol = resolve(&["instA"], &[]);
check_not_found(symbol);
let symbol = resolve(&["instA"], &["ModuleA"]);
check_found(symbol, "prj::ModuleA");
let symbol = resolve(&["instA", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::InterfaceA");
let symbol = resolve(&["instA", "memberB", "memberB", "memberA"], &["ModuleA"]);
check_found(symbol, "prj::PackageA::StructB");
}
const IMPORT_PRECEDENCE_CODE: &str = r##"
package PkgA {
enum EnumA: u32 {
val = 100,
}
}
package PkgB {
const val: u32 = 200;
}
module ModLocal {
import PkgA::EnumA::*;
const val: u32 = 7;
}
module ModExplicit {
import PkgA::EnumA::*;
import PkgB::val;
}
module ModAmbiguous {
import PkgA::EnumA::*;
import PkgB::*;
}
"##;
#[test]
fn import_precedence() {
let handle = std::thread::Builder::new()
.stack_size(16 * 1024 * 1024)
.spawn(|| {
let metadata = Metadata::create_default("prj").unwrap();
let parser = Parser::parse(IMPORT_PRECEDENCE_CODE, &"").unwrap();
let analyzer = Analyzer::new(&metadata);
analyzer.analyze_pass1("prj", &parser.veryl);
Analyzer::analyze_post_pass1();
check_found(resolve(&["val"], &["ModLocal"]), "prj::ModLocal");
check_found(resolve(&["val"], &["ModExplicit"]), "prj::PkgB");
check_not_found(resolve(&["val"], &["ModAmbiguous"]));
})
.unwrap();
handle.join().unwrap();
}
}