1use std::collections::btree_map::Entry;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6
7use wolf_ast::{ExternDeclKind, ItemKind};
8use wolf_diag::{Diagnostic, codes};
9use wolf_span::Span;
10
11use crate::{ModuleInput, Role};
12
13struct Externs {
16 header: Span,
17 names: BTreeMap<String, BTreeSet<String>>,
18}
19
20impl Externs {
21 fn missing(&self, target: &str) -> Vec<String> {
23 let all: BTreeSet<&String> = self.names.values().flatten().collect();
24 let declared = self.names.get(target);
25 all.into_iter()
26 .filter(|n| !declared.is_some_and(|d| d.contains(*n)))
27 .cloned()
28 .collect()
29 }
30}
31
32struct Graph {
34 imports: Vec<Vec<usize>>,
35 import_spans: BTreeMap<(usize, usize), Span>,
37 externs: BTreeMap<usize, Externs>,
38}
39
40fn graph(modules: &[ModuleInput<'_>]) -> Graph {
41 let index: BTreeMap<&[String], usize> = modules
42 .iter()
43 .enumerate()
44 .map(|(i, m)| (m.path.as_slice(), i))
45 .collect();
46 let mut imports: Vec<Vec<usize>> = vec![Vec::new(); modules.len()];
47 let mut import_spans: BTreeMap<(usize, usize), Span> = BTreeMap::new();
48 let mut externs: BTreeMap<usize, Externs> = BTreeMap::new();
49 for (i, m) in modules.iter().enumerate() {
50 if matches!(m.role, Role::Std | Role::Prelude) {
51 continue;
52 }
53 for section in &m.ast.sections {
54 for item in §ion.items {
55 match &item.kind {
56 ItemKind::Import(import) => {
57 let path: Vec<String> = import
58 .path
59 .segments
60 .iter()
61 .map(|s| s.name.clone())
62 .collect();
63 if let (Some(j), Some(list)) =
64 (index.get(path.as_slice()), imports.get_mut(i))
65 {
66 list.push(*j);
67 import_spans.entry((i, *j)).or_insert(import.path.span);
68 }
69 }
70 ItemKind::ExternTarget(block) => {
71 let e = externs.entry(i).or_insert_with(|| Externs {
72 header: section.name_span,
73 names: BTreeMap::new(),
74 });
75 let names = e.names.entry(block.target.name.clone()).or_default();
76 for decl in &block.decls {
77 let name = match &decl.kind {
78 ExternDeclKind::Fn(sig) => &sig.name.name,
79 ExternDeclKind::Type(name) => &name.name,
80 ExternDeclKind::Struct(_) => continue,
81 };
82 names.insert(name.clone());
83 }
84 }
85 _ => {}
86 }
87 }
88 }
89 }
90 Graph {
91 imports,
92 import_spans,
93 externs,
94 }
95}
96
97impl Graph {
98 fn reach(&self, groups: Vec<Vec<usize>>) -> BTreeMap<usize, Option<usize>> {
101 let mut parent: BTreeMap<usize, Option<usize>> = BTreeMap::new();
102 for group in groups {
103 let mut queue: VecDeque<usize> = VecDeque::new();
104 for r in group {
105 if let Entry::Vacant(v) = parent.entry(r) {
106 v.insert(None);
107 queue.push_back(r);
108 }
109 }
110 while let Some(i) = queue.pop_front() {
111 for &j in self.imports.get(i).map_or(&[][..], Vec::as_slice) {
112 if let Entry::Vacant(v) = parent.entry(j) {
113 v.insert(Some(i));
114 queue.push_back(j);
115 }
116 }
117 }
118 }
119 parent
120 }
121}
122
123fn chain(
125 modules: &[ModuleInput<'_>],
126 parent: &BTreeMap<usize, Option<usize>>,
127 i: usize,
128) -> Vec<String> {
129 let path_of = |k: usize| modules.get(k).map(|m| m.path.join(".")).unwrap_or_default();
130 let mut chain = vec![path_of(i)];
131 let mut cur = i;
132 while let Some(Some(p)) = parent.get(&cur) {
133 chain.push(path_of(*p));
134 cur = *p;
135 }
136 chain.reverse();
137 chain
138}
139
140pub fn check_vm(
144 modules: &[ModuleInput<'_>],
145 root: impl Fn(&ModuleInput<'_>) -> bool,
146 help: &str,
147) -> Vec<Diagnostic> {
148 let g = graph(modules);
149 let roots: Vec<usize> = modules
150 .iter()
151 .enumerate()
152 .filter(|(_, m)| !matches!(m.role, Role::Std | Role::Prelude) && root(m))
153 .map(|(i, _)| i)
154 .collect();
155 let parent = g.reach(vec![roots]);
156 let mut out = Vec::new();
157 for (&i, e) in &g.externs {
158 if !parent.contains_key(&i) {
159 continue;
160 }
161 let chain = chain(modules, &parent, i);
162 let name = modules.get(i).map(|m| m.path.join(".")).unwrap_or_default();
163 let native = modules.get(i).is_some_and(|m| m.role == Role::Native);
164 let mut d = if native {
165 let importer = parent.get(&i).copied().flatten();
166 let mut d = Diagnostic::new(
167 codes::E0807,
168 format!("the native module `{name}` cannot run in the VM"),
169 );
170 if let Some(span) = importer.and_then(|p| g.import_spans.get(&(p, i)).copied()) {
171 d = d.primary(span, "code of the targets is imported here");
172 }
173 d
174 } else {
175 Diagnostic::new(
176 codes::E0807,
177 format!("module `{name}` cannot run in the VM"),
178 )
179 .primary(e.header, "code of the targets is called here")
180 };
181 if chain.len() > 1 {
182 d = d.note(format!("imported through {}", chain.join(" -> ")));
183 }
184 out.push(d.help(help.to_string()));
185 }
186 out
187}
188
189pub fn check_target(
192 modules: &[ModuleInput<'_>],
193 target: &str,
194 built: impl Fn(Role) -> bool,
195) -> Vec<Diagnostic> {
196 let g = graph(modules);
197 let (import_spans, externs) = (&g.import_spans, &g.externs);
198 let roots: Vec<usize> = modules
200 .iter()
201 .enumerate()
202 .filter(|(_, m)| {
203 matches!(m.role, Role::Entry | Role::Example | Role::Test) && built(m.role)
204 })
205 .map(|(i, _)| i)
206 .collect();
207 let rest: Vec<usize> = modules
209 .iter()
210 .enumerate()
211 .filter(|(_, m)| m.role == Role::Src)
212 .map(|(i, _)| i)
213 .collect();
214 let parent = g.reach(vec![roots, rest]);
215 let mut out = Vec::new();
216 for (&i, e) in externs {
217 if !parent.contains_key(&i) {
218 continue;
219 }
220 let missing = e.missing(target);
221 if missing.is_empty() {
222 continue;
223 }
224 let name = modules.get(i).map(|m| m.path.join(".")).unwrap_or_default();
225 let chain = chain(modules, &parent, i);
226 let bound: Vec<&str> = e
227 .names
228 .keys()
229 .filter(|t| e.missing(t).is_empty())
230 .map(String::as_str)
231 .collect();
232 let list = missing
233 .iter()
234 .map(|n| format!("`{n}`"))
235 .collect::<Vec<_>>()
236 .join(", ");
237 if modules.get(i).is_some_and(|m| m.role == Role::Native) {
239 let importer = parent.get(&i).copied().flatten();
240 let span = importer.and_then(|p| import_spans.get(&(p, i)).copied());
241 let mut d = Diagnostic::new(
242 codes::E0704,
243 format!("the native module `{name}` is not available for `{target}`"),
244 );
245 if let Some(span) = span {
246 d = d.primary(span, format!("no `{target}` code for {list}"));
247 }
248 if chain.len() > 2 {
249 d = d.note(format!("imported through {}", chain.join(" -> ")));
250 }
251 let others = bound
252 .iter()
253 .map(|t| format!("`{t}`"))
254 .collect::<Vec<_>>()
255 .join(" or ");
256 out.push(d.help(format!(
257 "add the `{target}` code of the module, or build for {others}"
258 )));
259 continue;
260 }
261 let mut d = Diagnostic::new(
262 codes::E0704,
263 format!("module `{name}` is not available for `{target}`"),
264 )
265 .primary(e.header, format!("no `{target}` declaration for {list}"));
266 if chain.len() > 1 {
267 d = d.note(format!("imported through {}", chain.join(" -> ")));
268 }
269 d = if bound.is_empty() {
270 d.help(format!("declare {list} in a `{target}` block"))
271 } else {
272 d.help(format!(
273 "declare {list} in a `{target}` block, or build for {}",
274 bound
275 .iter()
276 .map(|t| format!("`{t}`"))
277 .collect::<Vec<_>>()
278 .join(" or ")
279 ))
280 };
281 out.push(d);
282 }
283 out
284}