1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
//! Module aware name resolution. Every top level item gets a canonical key,
//! `foo::bar` for an item in module `foo`, a bare `bar` for a root item, so
//! single file scripts keep their old keys. Paths are resolved against the
//! module they appear in, at compile time for calls and at runtime for type
//! coercions, and anything that never lands on a user item falls through to
//! the bridge dispatch unchanged.
use std::collections::HashMap;
use std::rc::Rc;
use anyhow::{Result, bail};
/// Symbols of one module.
#[derive(Default)]
pub(super) struct ModuleSyms {
pub path: Vec<String>,
pub parent: Option<usize>,
pub children: HashMap<String, usize>,
/// Local name to global function index.
pub fns: HashMap<String, u32>,
/// Local name to global constant index.
pub consts: HashMap<String, u32>,
/// Local name to canonical struct key.
pub structs: HashMap<String, Rc<str>>,
/// Local name to canonical enum key.
pub enums: HashMap<String, Rc<str>>,
/// Local alias name to its target type.
pub aliases: HashMap<String, Rc<syn::Type>>,
/// Import name to the path it stands for.
pub uses: HashMap<String, Vec<String>>,
/// Prefixes of `use ...::*` imports, checked against user modules at load.
pub globs: Vec<Vec<String>>,
}
pub(super) struct StructDef {
pub ast: Rc<syn::ItemStruct>,
pub module: usize,
}
/// What a path resolved to.
pub(super) enum Res {
Fn(u32),
Const(u32),
Struct(Rc<str>),
Enum(Rc<str>),
/// `Type::rest` where the type is a user struct or enum: an associated
/// function, a method used UFCS style, or an enum variant.
TypeMember(Rc<str>, Vec<String>),
/// A type alias hit exactly, resolved in its defining module.
Alias(usize, Rc<syn::Type>),
Module,
/// Not a user item. Segments have imports already expanded.
External(Vec<String>),
}
pub(super) struct Resolver {
pub modules: Vec<ModuleSyms>,
pub structs: HashMap<Rc<str>, StructDef>,
pub enums: HashMap<Rc<str>, Rc<syn::ItemEnum>>,
}
/// Bound on import chains, so `pub use` cycles error instead of hanging.
const MAX_DEPTH: usize = 64;
impl Resolver {
/// Canonical key for an item named `name` in module `m`.
pub fn canon(&self, m: usize, name: &str) -> String {
let path = &self.modules[m].path;
if path.is_empty() {
name.to_string()
} else {
format!("{}::{name}", path.join("::"))
}
}
/// Resolve an expression path as written in module `m`.
pub fn resolve(&self, m: usize, segs: &[String]) -> Result<Res> {
self.resolve_at(m, segs, 0)
}
/// Resolve a `use` target written in module `m`. A `self` or `super` start
/// pins the current module. Otherwise the first segment can still name one
/// of the current module's own children, like `use ctx::Ctx` written beside
/// `mod ctx`, which rustc resolves locally, so a submodule tries itself
/// first and falls back to the crate root and the external prelude.
fn resolve_use(&self, m: usize, segs: &[String], depth: usize) -> Result<Res> {
if let Some("self" | "super") = segs.first().map(String::as_str) {
return self.resolve_at(m, segs, depth);
}
// Only submodules need the local-first try. At the crate root the two
// resolutions are the same walk, so the retry would just repeat the
// whole use-alias expansion and blow up.
if m != 0
&& let Ok(res) = self.resolve_at(m, segs, depth)
&& !matches!(res, Res::External(_))
{
return Ok(res);
}
self.resolve_at(0, segs, depth)
}
fn resolve_at(&self, mut m: usize, segs: &[String], depth: usize) -> Result<Res> {
if depth > MAX_DEPTH {
bail!("import chain too deep resolving `{}`", segs.join("::"));
}
let mut i = 0;
// A leading crate/self/super run pins the starting module. After it,
// only user items can match, so external fallback is off.
let mut anchored = false;
while i < segs.len() {
match segs[i].as_str() {
"crate" => m = 0,
"self" => {}
"super" => {
m = match self.modules[m].parent {
Some(p) => p,
None => bail!("`super` used at the crate root"),
};
}
_ => break,
}
anchored = true;
i += 1;
}
if i == segs.len() {
return Ok(Res::Module);
}
// Walk the remaining segments through the module tree.
let start = m;
loop {
let seg = &segs[i];
let last = i == segs.len() - 1;
let syms = &self.modules[m];
if let Some(&f) = syms.fns.get(seg) {
if last {
return Ok(Res::Fn(f));
}
bail!("`{}` is a function, not a module", segs[..=i].join("::"));
}
if let Some(&c) = syms.consts.get(seg) {
if last {
return Ok(Res::Const(c));
}
bail!("`{}` is a constant, not a module", segs[..=i].join("::"));
}
if let Some(canon) = syms.structs.get(seg) {
return Ok(if last {
Res::Struct(canon.clone())
} else {
Res::TypeMember(canon.clone(), segs[i + 1..].to_vec())
});
}
if let Some(canon) = syms.enums.get(seg) {
return Ok(if last {
Res::Enum(canon.clone())
} else {
Res::TypeMember(canon.clone(), segs[i + 1..].to_vec())
});
}
if let Some(target) = syms.aliases.get(seg) {
if last {
return Ok(Res::Alias(m, target.clone()));
}
// `Alias::assoc(..)`: follow the alias when it names a type
// directly, then continue with the rest of the path.
let Some(mut spliced) = type_path_segs(target) else {
bail!("`{seg}` does not name a type with members");
};
spliced.extend_from_slice(&segs[i + 1..]);
return self.resolve_at(m, &spliced, depth + 1);
}
if let Some(&child) = syms.children.get(seg) {
if last {
return Ok(Res::Module);
}
m = child;
anchored = true;
i += 1;
continue;
}
if let Some(target) = syms.uses.get(seg) {
let mut spliced = target.clone();
spliced.extend_from_slice(&segs[i + 1..]);
return match self.resolve_use(m, &spliced, depth + 1)? {
// An import of something we do not model, `use std::fs`,
// stays external with the alias expanded.
Res::External(_) => Ok(Res::External(spliced)),
other => Ok(other),
};
}
if anchored || m != start {
bail!("cannot find `{seg}` in {}", module_name(syms));
}
return Ok(Res::External(segs[i..].to_vec()));
}
}
/// Resolve a type path to a user struct canonical key, following aliases.
pub fn resolve_struct_key(&self, m: usize, path: &syn::Path) -> Option<Rc<str>> {
let segs: Vec<String> = path.segments.iter().map(|s| s.ident.to_string()).collect();
match self.resolve(m, &segs).ok()? {
Res::Struct(c) => Some(c),
Res::Alias(am, target) => {
if let syn::Type::Path(p) = &*target {
self.resolve_struct_key(am, &p.path)
} else {
None
}
}
_ => None,
}
}
/// Fail on `use ...::*` imports that point into script modules. Globs of
/// external crates keep their old ignored behavior.
pub fn reject_module_globs(&self) -> Result<()> {
for (m, syms) in self.modules.iter().enumerate() {
for prefix in &syms.globs {
if let Ok(Res::Module) = self.resolve_use(m, prefix, 0) {
bail!(
"unsupported feature: glob import `use {}::*` of a script module",
prefix.join("::")
);
}
}
}
Ok(())
}
}
fn module_name(syms: &ModuleSyms) -> String {
if syms.path.is_empty() {
"the script root".to_string()
} else {
format!("module `{}`", syms.path.join("::"))
}
}
/// The plain segments of a path type, `a::b::C` without generics on the way.
fn type_path_segs(ty: &syn::Type) -> Option<Vec<String>> {
if let syn::Type::Path(p) = ty {
Some(
p.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect(),
)
} else {
None
}
}
/// Trailing name of a canonical key, what compiled Rust would print.
pub(super) fn bare(name: &str) -> &str {
name.rsplit("::").next().unwrap_or(name)
}