use std::collections::{HashMap, HashSet};
use crate::ir;
use crate::types::{DiagCode, Diagnostic, DiagnosticConfig, Language, ResolverStrictness, Span};
use super::super::mib::Mib;
use super::super::types::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum UnresolvedReason {
ModuleNotFound,
SymbolNotExported,
TypeNotFound,
DependencyCycle,
ComponentNotFound,
EnterpriseNotFound,
AugmentsTargetNotFound,
IndexObjectNotFound,
ObjectNotFound,
}
impl UnresolvedReason {
pub fn as_str(self) -> &'static str {
match self {
Self::ModuleNotFound => "module_not_found",
Self::SymbolNotExported => "symbol_not_exported",
Self::TypeNotFound => "unknown_type",
Self::DependencyCycle => "dependency_cycle",
Self::ComponentNotFound => "unknown_parent",
Self::EnterpriseNotFound => "unknown_parent",
Self::AugmentsTargetNotFound => "unknown_parent",
Self::IndexObjectNotFound => "unknown_index_object",
Self::ObjectNotFound => "unknown_object",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub(super) struct IrModuleId(pub u32);
impl IrModuleId {
pub fn index(self) -> usize {
self.0 as usize
}
}
pub(super) struct ResolverContext {
pub mib: Mib,
pub modules: Vec<ir::Module>,
pub module_index: HashMap<String, Vec<IrModuleId>>,
pub module_to_resolved: HashMap<IrModuleId, ModuleId>,
pub resolved_to_module: HashMap<ModuleId, IrModuleId>,
pub module_symbol_to_node: HashMap<IrModuleId, HashMap<String, NodeId>>,
pub module_imports: HashMap<IrModuleId, HashMap<String, IrModuleId>>,
pub module_symbol_to_type: HashMap<IrModuleId, HashMap<String, TypeId>>,
pub module_def_names: HashMap<IrModuleId, HashSet<String>>,
pub module_oid_def_names: HashMap<IrModuleId, HashSet<String>>,
pub snmpv2_smi: Option<IrModuleId>,
pub rfc1155_smi: Option<IrModuleId>,
pub snmpv2_tc: Option<IrModuleId>,
pub used_imports: HashMap<IrModuleId, HashSet<String>>,
pub unresolved_imports: Vec<UnresolvedTracking>,
pub unresolved_types: Vec<UnresolvedTracking>,
pub unresolved_oids: Vec<UnresolvedTracking>,
pub unresolved_indexes: Vec<UnresolvedTracking>,
pub unresolved_notif_objects: Vec<UnresolvedTracking>,
pub strictness: ResolverStrictness,
pub diag_config: DiagnosticConfig,
}
pub(super) struct UnresolvedTracking {
pub kind: UnresolvedKind,
pub symbol: String,
pub module: String,
pub reason: UnresolvedReason,
}
impl ResolverContext {
pub fn new(strictness: ResolverStrictness, diag_config: DiagnosticConfig) -> Self {
Self {
mib: Mib::new(),
modules: Vec::new(),
module_index: HashMap::new(),
module_to_resolved: HashMap::new(),
resolved_to_module: HashMap::new(),
module_symbol_to_node: HashMap::new(),
module_imports: HashMap::new(),
module_symbol_to_type: HashMap::new(),
module_def_names: HashMap::new(),
module_oid_def_names: HashMap::new(),
snmpv2_smi: None,
rfc1155_smi: None,
snmpv2_tc: None,
used_imports: HashMap::new(),
unresolved_imports: Vec::new(),
unresolved_types: Vec::new(),
unresolved_oids: Vec::new(),
unresolved_indexes: Vec::new(),
unresolved_notif_objects: Vec::new(),
strictness,
diag_config,
}
}
pub fn all_modules(&self) -> impl Iterator<Item = (IrModuleId, &ir::Module)> {
self.modules
.iter()
.enumerate()
.map(|(idx, m)| (IrModuleId(idx as u32), m))
}
pub fn user_modules(&self) -> impl Iterator<Item = (IrModuleId, &ir::Module)> {
self.all_modules()
.filter(|(_, m)| !crate::lower::base_modules::is_base_module(&m.name))
}
pub fn collect_definitions(&self, filter: fn(&ir::Definition) -> bool) -> Vec<(usize, usize)> {
(0..self.modules.len())
.flat_map(|idx| {
self.modules[idx]
.definitions
.iter()
.enumerate()
.filter_map(
move |(di, def)| {
if filter(def) { Some((idx, di)) } else { None }
},
)
})
.collect()
}
pub fn emit_diagnostic(
&mut self,
code: DiagCode,
ir_mod: Option<IrModuleId>,
span: Span,
message: String,
) {
let severity = code.severity();
if !self.diag_config.should_report(code) {
return;
}
let (module_name, line, col) = match ir_mod {
Some(id) => {
let m = &self.modules[id.index()];
let (l, c) = line_col_from_module(m, span);
(m.name.clone(), l, c)
}
None => (String::new(), 0, 0),
};
self.mib.add_diagnostic(Diagnostic {
severity,
code,
message,
module: Some(module_name).filter(|s| !s.is_empty()),
line: if line > 0 { Some(line) } else { None },
column: if col > 0 { Some(col) } else { None },
});
}
pub fn mark_import_used(&mut self, ir_mod: IrModuleId, name: &str) {
self.used_imports
.entry(ir_mod)
.or_default()
.insert(name.to_string());
}
pub fn lookup_node_for_module(&self, mod_id: IrModuleId, name: &str) -> Option<(NodeId, bool)> {
if let Some(node) = self
.module_symbol_to_node
.get(&mod_id)
.and_then(|syms| syms.get(name))
{
return Some((*node, false));
}
if let Some(source) = self
.module_imports
.get(&mod_id)
.and_then(|imps| imps.get(name))
&& let Some(node) = self
.module_symbol_to_node
.get(source)
.and_then(|syms| syms.get(name))
{
return Some((*node, true));
}
None
}
pub fn lookup_object_for_module(
&self,
mod_id: IrModuleId,
name: &str,
) -> Option<(ObjectId, bool)> {
if let Some(&resolved_mod) = self.module_to_resolved.get(&mod_id)
&& let Some(obj_id) = self.mib.raw().module(resolved_mod).object_by_name(name)
{
return Some((obj_id, false));
}
if let Some(&source_ir) = self
.module_imports
.get(&mod_id)
.and_then(|imps| imps.get(name))
&& let Some(&source_resolved) = self.module_to_resolved.get(&source_ir)
&& let Some(obj_id) = self.mib.raw().module(source_resolved).object_by_name(name)
{
return Some((obj_id, true));
}
None
}
pub fn lookup_type_for_module(&self, mod_id: IrModuleId, name: &str) -> Option<(TypeId, bool)> {
if let Some(result) = self.lookup_type_in_module_scope(mod_id, name) {
return Some(result);
}
self.try_well_known_type_fallbacks(name)
.map(|id| (id, false))
}
fn lookup_type_in_module_scope(
&self,
mod_id: IrModuleId,
name: &str,
) -> Option<(TypeId, bool)> {
if let Some(t) = self
.module_symbol_to_type
.get(&mod_id)
.and_then(|syms| syms.get(name))
{
return Some((*t, false));
}
if let Some(source) = self
.module_imports
.get(&mod_id)
.and_then(|imps| imps.get(name))
&& let Some(t) = self
.module_symbol_to_type
.get(source)
.and_then(|syms| syms.get(name))
{
return Some((*t, true));
}
None
}
fn try_well_known_type_fallbacks(&self, name: &str) -> Option<TypeId> {
if matches!(
name,
"INTEGER" | "OCTET STRING" | "OBJECT IDENTIFIER" | "BITS"
) && let Some(smi) = self.snmpv2_smi
{
return self
.module_symbol_to_type
.get(&smi)
.and_then(|syms| syms.get(name))
.copied();
}
if !self.strictness.allow_constrained_fallbacks() {
return None;
}
if let Some(smi) = self.snmpv2_smi
&& let Some(t) = self
.module_symbol_to_type
.get(&smi)
.and_then(|syms| syms.get(name))
{
return Some(*t);
}
if let Some(rfc) = self.rfc1155_smi
&& let Some(t) = self
.module_symbol_to_type
.get(&rfc)
.and_then(|syms| syms.get(name))
{
return Some(*t);
}
if let Some(tc) = self.snmpv2_tc
&& let Some(t) = self
.module_symbol_to_type
.get(&tc)
.and_then(|syms| syms.get(name))
{
return Some(*t);
}
None
}
pub fn lookup_node_global(&self, name: &str) -> Option<NodeId> {
for (id, _) in self.all_modules() {
if let Some(node) = self
.module_symbol_to_node
.get(&id)
.and_then(|syms| syms.get(name))
{
return Some(*node);
}
}
None
}
pub fn lookup_node_in_module(&self, module_name: &str, name: &str) -> Option<NodeId> {
let candidates = self.module_index.get(module_name)?;
for &cand in candidates {
if let Some(node) = self
.module_symbol_to_node
.get(&cand)
.and_then(|syms| syms.get(name))
{
return Some(*node);
}
}
None
}
pub fn module_language(&self, id: IrModuleId) -> Language {
self.modules[id.index()].language
}
pub fn extract_last_updated(&self, id: IrModuleId) -> String {
let m = &self.modules[id.index()];
for def in &m.definitions {
if let ir::Definition::ModuleIdentity(mi) = def {
return mi.last_updated.clone();
}
}
String::new()
}
pub fn record_unresolved_import(
&mut self,
symbol: impl Into<String>,
importing_module: impl Into<String>,
from_module: impl AsRef<str>,
reason: UnresolvedReason,
ir_mod: IrModuleId,
span: Span,
) {
let symbol = symbol.into();
let importing_module = importing_module.into();
let code = if reason == UnresolvedReason::ModuleNotFound {
DiagCode::ImportModuleNotFound
} else {
DiagCode::ImportNotFound
};
self.unresolved_imports.push(UnresolvedTracking {
kind: UnresolvedKind::Import,
symbol: symbol.clone(),
module: importing_module,
reason,
});
self.emit_diagnostic(
code,
Some(ir_mod),
span,
format!(
"unresolved import: {:?} from {:?} ({})",
symbol,
from_module.as_ref(),
reason.as_str()
),
);
}
pub fn record_unresolved_type(
&mut self,
referrer: impl AsRef<str>,
symbol: impl Into<String>,
module: impl Into<String>,
ir_mod: IrModuleId,
span: Span,
) {
let symbol = symbol.into();
let module = module.into();
self.unresolved_types.push(UnresolvedTracking {
kind: UnresolvedKind::Type,
symbol: symbol.clone(),
module,
reason: UnresolvedReason::TypeNotFound,
});
self.emit_diagnostic(
DiagCode::TypeUnknown,
Some(ir_mod),
span,
format!(
"unresolved type: {:?} references unknown type {:?}",
referrer.as_ref(),
symbol
),
);
}
pub fn record_unresolved_oid(
&mut self,
def_name: impl AsRef<str>,
component: impl Into<String>,
module: impl Into<String>,
reason: UnresolvedReason,
ir_mod: IrModuleId,
span: Span,
) {
let component = component.into();
let module = module.into();
self.unresolved_oids.push(UnresolvedTracking {
kind: UnresolvedKind::Oid,
symbol: component.clone(),
module,
reason,
});
let code = if reason == UnresolvedReason::DependencyCycle {
DiagCode::OidRecursive
} else {
DiagCode::OidOrphan
};
self.emit_diagnostic(
code,
Some(ir_mod),
span,
format!(
"unresolved OID: {:?} references unknown parent {:?}",
def_name.as_ref(),
component
),
);
}
pub fn record_unresolved_index(
&mut self,
row: impl AsRef<str>,
index_object: impl Into<String>,
module: impl Into<String>,
ir_mod: IrModuleId,
span: Span,
) {
let index_object = index_object.into();
let module = module.into();
self.unresolved_indexes.push(UnresolvedTracking {
kind: UnresolvedKind::Index,
symbol: index_object.clone(),
module,
reason: UnresolvedReason::IndexObjectNotFound,
});
self.emit_diagnostic(
DiagCode::IndexUnresolved,
Some(ir_mod),
span,
format!(
"unresolved INDEX: {:?} references unknown object {:?}",
row.as_ref(),
index_object
),
);
}
pub fn record_unresolved_notification_object(
&mut self,
notification: impl AsRef<str>,
object: impl Into<String>,
module: impl Into<String>,
ir_mod: IrModuleId,
span: Span,
) {
let object = object.into();
let module = module.into();
self.unresolved_notif_objects.push(UnresolvedTracking {
kind: UnresolvedKind::NotificationObject,
symbol: object.clone(),
module,
reason: UnresolvedReason::ObjectNotFound,
});
self.emit_diagnostic(
DiagCode::ObjectsUnresolved,
Some(ir_mod),
span,
format!(
"unresolved OBJECTS: {:?} references unknown object {:?}",
notification.as_ref(),
object
),
);
}
pub fn drop_modules(&mut self) {
self.modules.clear();
self.module_index.clear();
self.module_def_names.clear();
self.module_oid_def_names.clear();
}
pub fn finalize_unresolved(&mut self) {
let all = self
.unresolved_imports
.drain(..)
.chain(self.unresolved_types.drain(..))
.chain(self.unresolved_oids.drain(..))
.chain(self.unresolved_indexes.drain(..))
.chain(self.unresolved_notif_objects.drain(..));
for u in all {
self.mib.add_unresolved(UnresolvedRef {
kind: u.kind,
symbol: u.symbol,
module: u.module,
reason: u.reason.as_str().to_string(),
});
}
}
}
fn line_col_from_module(m: &ir::Module, span: Span) -> (usize, usize) {
if span.is_synthetic() || m.line_table.is_empty() {
return (0, 0);
}
crate::types::line_col_from_table(&m.line_table, span.start)
}