mod external_check;
mod manifest;
mod resolve;
mod validate;
use std::collections::BTreeMap;
pub use brink_ir::FileId;
pub use brink_ir::ResolutionMap;
pub use external_check::{
ExternalCheckSeverity, InferredType, ResolvedParam, ResolvedType, SymbolMeta, ValueMeta,
};
use brink_format::DefinitionId;
use brink_ir::{
Diagnostic, DocBlock, HirFile, HostManifest, ManifestExternal, SemanticTypeDef, SymbolIndex,
SymbolKind, SymbolManifest,
};
#[derive(Debug, Clone, Default)]
pub struct AnalysisOptions {
pub host_manifest: Option<HostManifest>,
pub external_check: ExternalCheckSeverity,
}
#[derive(Debug, Clone)]
pub struct AnalysisResult {
pub index: SymbolIndex,
pub resolutions: ResolutionMap,
pub diagnostics: Vec<Diagnostic>,
pub symbol_meta: BTreeMap<DefinitionId, SymbolMeta>,
}
pub fn analyze(files: &[(FileId, &HirFile, &SymbolManifest)]) -> AnalysisResult {
analyze_with_options(files, &AnalysisOptions::default())
}
pub fn analyze_with_options(
files: &[(FileId, &HirFile, &SymbolManifest)],
opts: &AnalysisOptions,
) -> AnalysisResult {
let manifest_inputs: Vec<(FileId, &SymbolManifest)> = files
.iter()
.map(|&(id, _hir, manifest)| (id, manifest))
.collect();
let hir_inputs: Vec<(FileId, &HirFile)> = files.iter().map(|&(id, hir, _)| (id, hir)).collect();
let (index, mut diagnostics) = manifest::merge_manifests(&manifest_inputs);
let (resolutions, resolve_diags) = resolve::resolve_refs(&index, &manifest_inputs);
diagnostics.extend(resolve_diags);
diagnostics.extend(validate::validate(&hir_inputs));
let inline_docs = collect_inline_docs(&manifest_inputs);
let (types, registered) = manifest_maps(opts.host_manifest.as_ref());
let (mut symbol_meta, ext_diags) = external_check::analyze_externals(
&index,
&inline_docs,
&types,
®istered,
opts.external_check,
);
diagnostics.extend(ext_diags);
let (callable_meta, callable_diags) =
external_check::enrich_callables(&index, &inline_docs, &types, opts.external_check);
diagnostics.extend(callable_diags);
symbol_meta.extend(callable_meta);
symbol_meta.extend(external_check::infer_value_meta(
&hir_inputs,
&index,
&inline_docs,
));
if opts.external_check != ExternalCheckSeverity::Off {
let name_to_meta: BTreeMap<&str, &SymbolMeta> = symbol_meta
.iter()
.filter_map(|(id, meta)| {
index.symbols.get(id).and_then(|s| {
(s.kind == SymbolKind::External).then_some((s.name.as_str(), meta))
})
})
.collect();
diagnostics.extend(external_check::check_call_sites(&hir_inputs, &name_to_meta));
}
AnalysisResult {
index,
resolutions,
diagnostics,
symbol_meta,
}
}
fn collect_inline_docs(
files: &[(FileId, &SymbolManifest)],
) -> BTreeMap<(SymbolKind, String), DocBlock> {
let mut out = BTreeMap::new();
for &(_id, manifest) in files {
for (key, doc) in &manifest.docs {
out.insert(key.clone(), doc.clone());
}
}
out
}
fn manifest_maps(
manifest: Option<&HostManifest>,
) -> (
BTreeMap<String, SemanticTypeDef>,
BTreeMap<String, &ManifestExternal>,
) {
let mut types = BTreeMap::new();
let mut registered = BTreeMap::new();
if let Some(manifest) = manifest {
for ty in &manifest.types {
types.insert(ty.name.clone(), ty.clone());
}
for ext in &manifest.externals {
registered.insert(ext.name.clone(), ext);
}
}
(types, registered)
}