1mod external_check;
9mod manifest;
10mod resolve;
11mod validate;
12
13use std::collections::BTreeMap;
14
15pub use brink_ir::FileId;
16pub use brink_ir::ResolutionMap;
17pub use external_check::{
18 ExternalCheckSeverity, InferredType, ResolvedParam, ResolvedType, SymbolMeta, ValueMeta,
19};
20
21use brink_format::DefinitionId;
22use brink_ir::{
23 Diagnostic, DocBlock, HirFile, HostManifest, ManifestExternal, SemanticTypeDef, SymbolIndex,
24 SymbolKind, SymbolManifest,
25};
26
27#[derive(Debug, Clone, Default)]
30pub struct AnalysisOptions {
31 pub host_manifest: Option<HostManifest>,
33 pub external_check: ExternalCheckSeverity,
35}
36
37#[derive(Debug, Clone)]
39pub struct AnalysisResult {
40 pub index: SymbolIndex,
42 pub resolutions: ResolutionMap,
44 pub diagnostics: Vec<Diagnostic>,
46 pub symbol_meta: BTreeMap<DefinitionId, SymbolMeta>,
50}
51
52pub fn analyze(files: &[(FileId, &HirFile, &SymbolManifest)]) -> AnalysisResult {
57 analyze_with_options(files, &AnalysisOptions::default())
58}
59
60pub fn analyze_with_options(
63 files: &[(FileId, &HirFile, &SymbolManifest)],
64 opts: &AnalysisOptions,
65) -> AnalysisResult {
66 let manifest_inputs: Vec<(FileId, &SymbolManifest)> = files
67 .iter()
68 .map(|&(id, _hir, manifest)| (id, manifest))
69 .collect();
70
71 let hir_inputs: Vec<(FileId, &HirFile)> = files.iter().map(|&(id, hir, _)| (id, hir)).collect();
72
73 let (index, mut diagnostics) = manifest::merge_manifests(&manifest_inputs);
74 let (resolutions, resolve_diags) = resolve::resolve_refs(&index, &manifest_inputs);
75 diagnostics.extend(resolve_diags);
76 diagnostics.extend(validate::validate(&hir_inputs));
77
78 let inline_docs = collect_inline_docs(&manifest_inputs);
80 let (types, registered) = manifest_maps(opts.host_manifest.as_ref());
81 let (mut symbol_meta, ext_diags) = external_check::analyze_externals(
82 &index,
83 &inline_docs,
84 &types,
85 ®istered,
86 opts.external_check,
87 );
88 diagnostics.extend(ext_diags);
89
90 let (callable_meta, callable_diags) =
93 external_check::enrich_callables(&index, &inline_docs, &types, opts.external_check);
94 diagnostics.extend(callable_diags);
95 symbol_meta.extend(callable_meta);
96
97 symbol_meta.extend(external_check::infer_value_meta(
99 &hir_inputs,
100 &index,
101 &inline_docs,
102 ));
103
104 if opts.external_check != ExternalCheckSeverity::Off {
107 let name_to_meta: BTreeMap<&str, &SymbolMeta> = symbol_meta
108 .iter()
109 .filter_map(|(id, meta)| {
110 index.symbols.get(id).and_then(|s| {
111 (s.kind == SymbolKind::External).then_some((s.name.as_str(), meta))
112 })
113 })
114 .collect();
115 diagnostics.extend(external_check::check_call_sites(&hir_inputs, &name_to_meta));
116 }
117
118 AnalysisResult {
119 index,
120 resolutions,
121 diagnostics,
122 symbol_meta,
123 }
124}
125
126fn collect_inline_docs(
128 files: &[(FileId, &SymbolManifest)],
129) -> BTreeMap<(SymbolKind, String), DocBlock> {
130 let mut out = BTreeMap::new();
131 for &(_id, manifest) in files {
132 for (key, doc) in &manifest.docs {
133 out.insert(key.clone(), doc.clone());
134 }
135 }
136 out
137}
138
139fn manifest_maps(
142 manifest: Option<&HostManifest>,
143) -> (
144 BTreeMap<String, SemanticTypeDef>,
145 BTreeMap<String, &ManifestExternal>,
146) {
147 let mut types = BTreeMap::new();
148 let mut registered = BTreeMap::new();
149 if let Some(manifest) = manifest {
150 for ty in &manifest.types {
151 types.insert(ty.name.clone(), ty.clone());
152 }
153 for ext in &manifest.externals {
154 registered.insert(ext.name.clone(), ext);
155 }
156 }
157 (types, registered)
158}