Skip to main content

frs/cmds/rsl/rules/common/
import_resolution.rs

1use syn::UseTree;
2
3use super::module_idx::ModuleIdx;
4use super::module_idx::ScopeInfo;
5use super::path_resolution::normalize_path;
6
7pub struct ImportBinding {
8    pub name: String,
9    pub path: Vec<String>,
10    pub source_path: Vec<String>,
11}
12
13#[derive(Default)]
14pub struct UseBindings {
15    pub bindings: Vec<ImportBinding>,
16    pub glob_imports: Vec<Vec<String>>,
17    pub unknown: bool,
18}
19
20pub fn use_bindings(tree: &UseTree, current_module: &[String]) -> UseBindings {
21    let mut bindings = UseBindings::default();
22    let mut pending = vec![(tree, Vec::<String>::new())];
23
24    while let Some((tree, prefix)) = pending.pop() {
25        match tree {
26            UseTree::Path(path) => {
27                let mut next_prefix = prefix;
28                next_prefix.push(path.ident.to_string());
29                pending.push((path.tree.as_ref(), next_prefix));
30            }
31            UseTree::Group(group) => {
32                pending.extend(group.items.iter().map(|tree| (tree, prefix.clone())));
33            }
34            UseTree::Name(name) => {
35                if name.ident == "self" {
36                    if let Some(binding) = prefix.last() {
37                        bindings.bindings.push(ImportBinding {
38                            name: binding.clone(),
39                            path: normalize_path(current_module, &prefix),
40                            source_path: prefix,
41                        });
42                    }
43                } else {
44                    let mut path = prefix;
45                    path.push(name.ident.to_string());
46                    bindings.bindings.push(ImportBinding {
47                        name: name.ident.to_string(),
48                        path: normalize_path(current_module, &path),
49                        source_path: path,
50                    });
51                }
52            }
53            UseTree::Rename(rename) => {
54                if rename.rename != "_" {
55                    let mut path = prefix;
56                    path.push(rename.ident.to_string());
57                    bindings.bindings.push(ImportBinding {
58                        name: rename.rename.to_string(),
59                        path: normalize_path(current_module, &path),
60                        source_path: path,
61                    });
62                }
63            }
64            UseTree::Glob(_) => {
65                bindings.glob_imports.push(normalize_path(current_module, &prefix));
66                bindings.unknown = true;
67            }
68        }
69    }
70
71    bindings
72}
73
74pub fn has_name_clash_parts(idx: &ModuleIdx<'_>, current_module: &[String], parts: &[String]) -> bool {
75    let Some(scope) = idx.info.get(current_module) else {
76        return true;
77    };
78    if scope.unknown_imports {
79        return true;
80    }
81
82    let Some(name) = parts.last() else {
83        return true;
84    };
85    let Some(target_module_parts) = parts.get(..parts.len().saturating_sub(1)) else {
86        return true;
87    };
88    let target_module = normalize_path(current_module, target_module_parts);
89    let target_path = normalize_path(current_module, parts);
90
91    if target_module != current_module && scope.definitions.contains(name) {
92        return true;
93    }
94
95    scope
96        .imports
97        .iter()
98        .any(|binding| binding.name == *name && binding.path != target_path)
99}
100
101pub fn imported_binding<'idx>(
102    idx: &'idx ModuleIdx<'_>,
103    current_module: &[String],
104    name: &str,
105) -> Option<&'idx ImportBinding> {
106    let scope = idx.info.get(current_module)?;
107    if let Some(binding) = self::direct_imported_binding(scope, name) {
108        return Some(binding);
109    }
110
111    let mut candidate = None;
112    for glob_module in &scope.glob_imports {
113        let Some(glob_scope) = idx.info.get(glob_module) else {
114            // TODO: Resolve explicit imports exported by modules outside this file.
115            continue;
116        };
117        let Some(binding) = self::direct_imported_binding(glob_scope, name) else {
118            continue;
119        };
120        if candidate.is_some() {
121            // TODO: Resolve ambiguous explicit imports exported by multiple glob sources.
122            return None;
123        }
124        candidate = Some(binding);
125    }
126    candidate
127}
128
129fn direct_imported_binding<'idx>(scope: &'idx ScopeInfo, name: &str) -> Option<&'idx ImportBinding> {
130    let mut bindings = scope.imports.iter().filter(|binding| binding.name == name);
131    let binding = bindings.next()?;
132    bindings.next().is_none().then_some(binding)
133}