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
32pub fn check_target(
35 modules: &[ModuleInput<'_>],
36 target: &str,
37 built: impl Fn(Role) -> bool,
38) -> Vec<Diagnostic> {
39 let index: BTreeMap<&[String], usize> = modules
40 .iter()
41 .enumerate()
42 .map(|(i, m)| (m.path.as_slice(), i))
43 .collect();
44 let mut imports: Vec<Vec<usize>> = vec![Vec::new(); modules.len()];
45 let mut import_spans: BTreeMap<(usize, usize), Span> = BTreeMap::new();
47 let mut externs: BTreeMap<usize, Externs> = BTreeMap::new();
48 for (i, m) in modules.iter().enumerate() {
49 if matches!(m.role, Role::Std | Role::Prelude) {
50 continue;
51 }
52 for section in &m.ast.sections {
53 for item in §ion.items {
54 match &item.kind {
55 ItemKind::Import(import) => {
56 let path: Vec<String> = import
57 .path
58 .segments
59 .iter()
60 .map(|s| s.name.clone())
61 .collect();
62 if let (Some(j), Some(list)) =
63 (index.get(path.as_slice()), imports.get_mut(i))
64 {
65 list.push(*j);
66 import_spans.entry((i, *j)).or_insert(import.path.span);
67 }
68 }
69 ItemKind::ExternTarget(block) => {
70 let e = externs.entry(i).or_insert_with(|| Externs {
71 header: section.name_span,
72 names: BTreeMap::new(),
73 });
74 let names = e.names.entry(block.target.name.clone()).or_default();
75 for decl in &block.decls {
76 let name = match &decl.kind {
77 ExternDeclKind::Fn(sig) => &sig.name.name,
78 ExternDeclKind::Type(name) => &name.name,
79 ExternDeclKind::Struct(_) => continue,
80 };
81 names.insert(name.clone());
82 }
83 }
84 _ => {}
85 }
86 }
87 }
88 }
89 let roots: Vec<usize> = modules
91 .iter()
92 .enumerate()
93 .filter(|(_, m)| {
94 matches!(m.role, Role::Entry | Role::Example | Role::Test) && built(m.role)
95 })
96 .map(|(i, _)| i)
97 .collect();
98 let rest: Vec<usize> = modules
100 .iter()
101 .enumerate()
102 .filter(|(_, m)| m.role == Role::Src)
103 .map(|(i, _)| i)
104 .collect();
105 let mut parent: BTreeMap<usize, Option<usize>> = BTreeMap::new();
106 for group in [roots, rest] {
107 let mut queue: VecDeque<usize> = VecDeque::new();
108 for r in group {
109 if let Entry::Vacant(v) = parent.entry(r) {
110 v.insert(None);
111 queue.push_back(r);
112 }
113 }
114 while let Some(i) = queue.pop_front() {
115 for &j in imports.get(i).map_or(&[][..], Vec::as_slice) {
116 if let Entry::Vacant(v) = parent.entry(j) {
117 v.insert(Some(i));
118 queue.push_back(j);
119 }
120 }
121 }
122 }
123 let mut out = Vec::new();
124 for (&i, e) in &externs {
125 if !parent.contains_key(&i) {
126 continue;
127 }
128 let missing = e.missing(target);
129 if missing.is_empty() {
130 continue;
131 }
132 let path_of = |k: usize| modules.get(k).map(|m| m.path.join(".")).unwrap_or_default();
133 let name = path_of(i);
134 let mut chain = vec![name.clone()];
135 let mut cur = i;
136 while let Some(Some(p)) = parent.get(&cur) {
137 chain.push(path_of(*p));
138 cur = *p;
139 }
140 chain.reverse();
141 let bound: Vec<&str> = e
142 .names
143 .keys()
144 .filter(|t| e.missing(t).is_empty())
145 .map(String::as_str)
146 .collect();
147 let list = missing
148 .iter()
149 .map(|n| format!("`{n}`"))
150 .collect::<Vec<_>>()
151 .join(", ");
152 if modules.get(i).is_some_and(|m| m.role == Role::Native) {
154 let importer = parent.get(&i).copied().flatten();
155 let span = importer.and_then(|p| import_spans.get(&(p, i)).copied());
156 let mut d = Diagnostic::new(
157 codes::E0704,
158 format!("the native module `{name}` is not available for `{target}`"),
159 );
160 if let Some(span) = span {
161 d = d.primary(span, format!("no `{target}` code for {list}"));
162 }
163 if chain.len() > 2 {
164 d = d.note(format!("imported through {}", chain.join(" -> ")));
165 }
166 let others = bound
167 .iter()
168 .map(|t| format!("`{t}`"))
169 .collect::<Vec<_>>()
170 .join(" or ");
171 out.push(d.help(format!(
172 "add the `{target}` code of the module, or build for {others}"
173 )));
174 continue;
175 }
176 let mut d = Diagnostic::new(
177 codes::E0704,
178 format!("module `{name}` is not available for `{target}`"),
179 )
180 .primary(e.header, format!("no `{target}` declaration for {list}"));
181 if chain.len() > 1 {
182 d = d.note(format!("imported through {}", chain.join(" -> ")));
183 }
184 d = if bound.is_empty() {
185 d.help(format!("declare {list} in a `{target}` block"))
186 } else {
187 d.help(format!(
188 "declare {list} in a `{target}` block, or build for {}",
189 bound
190 .iter()
191 .map(|t| format!("`{t}`"))
192 .collect::<Vec<_>>()
193 .join(" or ")
194 ))
195 };
196 out.push(d);
197 }
198 out
199}