Skip to main content

wolf_resolve/
targets.rs

1//! Target binding of modules (spec chapter 7, section 8.5): a module whose extern names are not
2//! declared for a target is not available for it, and neither is a module that imports it.
3
4use 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
13/// The extern facts of one module: the span of its `extern` header and the names each target
14/// block declares.
15struct Externs {
16    header: Span,
17    names: BTreeMap<String, BTreeSet<String>>,
18}
19
20impl Externs {
21    /// The extern names that the block of `target` misses.
22    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
32/// E0704 for every module of a build for `target` that is not available for it. `built` says
33/// which modules the build contains; std modules are always available.
34pub 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    // Where each module imports another, for native modules (they have no source of their own).
46    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 &section.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    // The roots of the build and, for each module, the module it was first reached from.
90    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    // Modules of `src/` that no root imports are built too; they are reached last.
99    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        // A native module (spec chapter 6, section 1a) is reported where it is imported.
153        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}