rue_compiler/compile/
imports.rs

1use std::collections::HashMap;
2
3use indexmap::IndexMap;
4use rowan::TextRange;
5use rue_ast::{AstImportItem, AstImportPathSegment};
6use rue_diagnostic::{DiagnosticKind, Name};
7use rue_hir::{Declaration, Import, ImportId, Items, ScopeId, Symbol, SymbolId};
8
9use crate::{Compiler, SyntaxItemKind};
10
11pub fn declare_import_item(ctx: &mut Compiler, import: &AstImportItem) {
12    let Some(path) = import.path() else {
13        return;
14    };
15
16    let base_scope = ctx.last_scope_id();
17    let module_stack = ctx.parent_module_stack().to_vec();
18
19    let imports = construct_imports(
20        ctx,
21        base_scope,
22        module_stack,
23        Vec::new(),
24        &path.segments().collect::<Vec<_>>(),
25        import.export().is_some(),
26    );
27
28    for import in imports {
29        ctx.last_scope_mut().add_import(import);
30        ctx.add_relevant_import(import);
31    }
32}
33
34fn construct_imports(
35    ctx: &mut Compiler,
36    mut base_scope: ScopeId,
37    mut module_stack: Vec<SymbolId>,
38    mut path: Vec<Name>,
39    segments: &[AstImportPathSegment],
40    exported: bool,
41) -> Vec<ImportId> {
42    let mut has_non_super = false;
43
44    if let Some(segment) = segments.first()
45        && let Some(separator) = segment.separator()
46    {
47        ctx.diagnostic(&separator, DiagnosticKind::PathSeparatorInFirstSegment);
48    }
49
50    for segment in segments.iter().take(segments.len() - 1) {
51        if let Some(name) = segment.name() {
52            if name.text() == "super" {
53                if has_non_super {
54                    ctx.diagnostic(&name, DiagnosticKind::SuperAfterNamedImport);
55                } else if let Some(module) = module_stack.pop() {
56                    base_scope = ctx.module(module).scope;
57
58                    ctx.add_syntax(SyntaxItemKind::SymbolReference(module), name.text_range());
59                } else {
60                    ctx.diagnostic(&name, DiagnosticKind::UnresolvedSuper);
61                }
62            } else {
63                path.push(ctx.local_name(&name));
64                has_non_super = true;
65            }
66        }
67    }
68
69    let Some(last) = segments.last() else {
70        return vec![];
71    };
72
73    if let Some(name) = last.name()
74        && name.text() == "super"
75    {
76        ctx.diagnostic(&name, DiagnosticKind::SuperAtEnd);
77    }
78
79    let source = ctx.source().clone();
80
81    if let Some(name) = last.name() {
82        let name = ctx.local_name(&name);
83
84        vec![ctx.alloc_import(Import {
85            base_scope,
86            source,
87            path,
88            items: Items::Named(name),
89            exported,
90            declarations: Vec::new(),
91        })]
92    } else if let Some(star) = last.star() {
93        let star = ctx.local_name(&star);
94
95        vec![ctx.alloc_import(Import {
96            base_scope,
97            source,
98            path,
99            items: Items::All(star),
100            exported,
101            declarations: Vec::new(),
102        })]
103    } else if let items = last.items().collect::<Vec<_>>()
104        && !items.is_empty()
105    {
106        let mut imports = Vec::new();
107
108        for item in items {
109            imports.extend(construct_imports(
110                ctx,
111                base_scope,
112                module_stack.clone(),
113                path.clone(),
114                &item.segments().collect::<Vec<_>>(),
115                exported,
116            ));
117        }
118
119        imports
120    } else {
121        vec![]
122    }
123}
124
125#[derive(Debug, Default)]
126pub struct ImportCache {
127    scopes: HashMap<Vec<String>, (ScopeId, SymbolId)>,
128    unused_imports: IndexMap<ImportId, IndexMap<String, Name>>,
129    glob_import_counts: IndexMap<ImportId, (Name, usize)>,
130}
131
132fn flatten_imports(
133    ctx: &mut Compiler,
134    top_level_modules: Vec<SymbolId>,
135) -> Vec<(ScopeId, ImportId)> {
136    let mut imports = Vec::new();
137    let mut stack = vec![(None, top_level_modules)];
138
139    while let Some((scope, modules)) = stack.pop() {
140        if let Some(scope) = scope {
141            for import in ctx.scope(scope).imports() {
142                imports.push((scope, import));
143            }
144        }
145
146        for module in modules {
147            let Symbol::Module(module) = ctx.symbol(module).clone() else {
148                unreachable!();
149            };
150
151            stack.push((Some(module.scope), module.declarations.modules.clone()));
152        }
153    }
154
155    imports
156}
157
158pub fn resolve_imports(
159    ctx: &mut Compiler,
160    all_modules: Vec<SymbolId>,
161    cache: &mut ImportCache,
162    diagnostics: bool,
163) {
164    let imports = flatten_imports(ctx, all_modules);
165
166    let mut updated = true;
167    let mut missing_imports = IndexMap::new();
168
169    while updated {
170        updated = false;
171
172        for &(import_scope, import) in &imports {
173            updated |= resolve_import(
174                ctx,
175                import_scope,
176                import,
177                diagnostics,
178                cache,
179                &mut missing_imports,
180            );
181        }
182    }
183
184    if diagnostics {
185        for missing_imports in missing_imports.into_values() {
186            for name in missing_imports.into_values() {
187                ctx.diagnostic_name(
188                    &name,
189                    DiagnosticKind::UnresolvedImport(name.text().to_string()),
190                );
191            }
192        }
193
194        for unused_imports in cache.unused_imports.values() {
195            for name in unused_imports.values() {
196                ctx.diagnostic_name(name, DiagnosticKind::UnusedImport(name.text().to_string()));
197            }
198        }
199
200        for (name, count) in cache.glob_import_counts.values() {
201            if *count == 0 {
202                ctx.diagnostic_name(name, DiagnosticKind::UnusedGlobImport);
203            }
204        }
205    }
206}
207
208fn resolve_import(
209    ctx: &mut Compiler,
210    import_scope: ScopeId,
211    import_id: ImportId,
212    diagnostics: bool,
213    cache: &mut ImportCache,
214    missing_imports: &mut IndexMap<ImportId, IndexMap<String, Name>>,
215) -> bool {
216    let import = ctx.import(import_id).clone();
217    let source = import.source.clone();
218
219    let mut base = None;
220    let mut path_so_far = Vec::new();
221
222    for i in (1..=import.path.len()).rev() {
223        let subpath = import
224            .path
225            .iter()
226            .take(i)
227            .map(|t| t.text().to_string())
228            .collect::<Vec<_>>();
229
230        if let Some(cached) = cache.scopes.get(&subpath) {
231            base = Some(cached.0);
232            path_so_far = subpath;
233
234            for name in import.path.iter().take(path_so_far.len()) {
235                if diagnostics && let Some(srcloc) = name.srcloc() {
236                    ctx.add_syntax_for_source(
237                        SyntaxItemKind::SymbolReference(cached.1),
238                        TextRange::new(
239                            srcloc.span.start.try_into().unwrap(),
240                            srcloc.span.end.try_into().unwrap(),
241                        ),
242                        source.kind.clone(),
243                    );
244                }
245            }
246
247            break;
248        }
249    }
250
251    for name in import.path.iter().skip(path_so_far.len()) {
252        let symbol = if let Some(base) = base {
253            let base = ctx.scope(base);
254            base.symbol(name.text())
255                .filter(|s| base.is_symbol_exported(*s))
256                .map(|s| (s, base.symbol_import(s)))
257        } else if import.base_scope == import_scope {
258            ctx.resolve_symbol_in(import.base_scope, name.text())
259        } else {
260            let base = ctx.scope(import.base_scope);
261            base.symbol(name.text())
262                .filter(|s| base.is_symbol_exported(*s))
263                .map(|s| (s, base.symbol_import(s)))
264        };
265
266        let Some((symbol, import)) = symbol else {
267            if diagnostics {
268                ctx.diagnostic_name(
269                    name,
270                    DiagnosticKind::UndeclaredSymbol(name.text().to_string()),
271                );
272            }
273            return false;
274        };
275
276        if let Some(import) = import {
277            ctx.add_import_reference(import, Declaration::Symbol(symbol));
278        }
279
280        if diagnostics && let Some(srcloc) = name.srcloc() {
281            ctx.add_syntax_for_source(
282                SyntaxItemKind::SymbolReference(symbol),
283                TextRange::new(
284                    srcloc.span.start.try_into().unwrap(),
285                    srcloc.span.end.try_into().unwrap(),
286                ),
287                source.kind.clone(),
288            );
289        }
290
291        let Symbol::Module(module) = ctx.symbol(symbol) else {
292            if diagnostics {
293                ctx.diagnostic_name(
294                    name,
295                    DiagnosticKind::SubpathNotSupported(name.text().to_string()),
296                );
297            }
298            return false;
299        };
300
301        base = Some(module.scope);
302        path_so_far.push(name.text().to_string());
303        cache
304            .scopes
305            .insert(path_so_far.clone(), (module.scope, symbol));
306    }
307
308    let mut updated = false;
309
310    match import.items {
311        Items::All(star) => {
312            let count = &mut cache
313                .glob_import_counts
314                .entry(import_id)
315                .or_insert_with(|| (star.clone(), 0))
316                .1;
317
318            let symbols = if let Some(base) = base {
319                ctx.scope(base)
320                    .exported_symbols()
321                    .map(|(name, symbol)| (name.to_string(), symbol))
322                    .collect::<Vec<_>>()
323            } else {
324                let base = ctx.scope(import.base_scope);
325
326                base.symbol_names()
327                    .map(|name| (name.to_string(), base.symbol(name).unwrap()))
328                    .collect::<Vec<_>>()
329            };
330
331            let types = if let Some(base) = base {
332                ctx.scope(base)
333                    .exported_types()
334                    .map(|(name, ty)| (name.to_string(), ty))
335                    .collect::<Vec<_>>()
336            } else {
337                let base = ctx.scope(import.base_scope);
338
339                base.type_names()
340                    .map(|name| (name.to_string(), base.ty(name).unwrap()))
341                    .collect::<Vec<_>>()
342            };
343
344            for (name, symbol) in symbols {
345                let target = ctx.scope_mut(import_scope);
346
347                if target.symbol(&name).is_none() {
348                    target.insert_symbol(name.clone(), symbol, import.exported);
349                    target.add_symbol_import(symbol, import_id);
350                    ctx.import_mut(import_id)
351                        .declarations
352                        .push((name, Declaration::Symbol(symbol)));
353                    updated = true;
354                    *count += 1;
355                } else if !target.is_symbol_exported(symbol)
356                    && import.exported
357                    && target.symbol(&name) == Some(symbol)
358                {
359                    target.export_symbol(symbol);
360                    ctx.import_mut(import_id)
361                        .declarations
362                        .push((name, Declaration::Symbol(symbol)));
363                    updated = true;
364                    *count += 1;
365                }
366            }
367
368            for (name, ty) in types {
369                let target = ctx.scope_mut(import_scope);
370
371                if target.ty(&name).is_none() {
372                    target.insert_type(name.clone(), ty, import.exported);
373                    target.add_type_import(ty, import_id);
374                    ctx.import_mut(import_id)
375                        .declarations
376                        .push((name, Declaration::Type(ty)));
377                    updated = true;
378                    *count += 1;
379                } else if !target.is_type_exported(ty)
380                    && import.exported
381                    && target.ty(&name) == Some(ty)
382                {
383                    target.export_type(ty);
384                    ctx.import_mut(import_id)
385                        .declarations
386                        .push((name, Declaration::Type(ty)));
387                    updated = true;
388                    *count += 1;
389                }
390            }
391        }
392        Items::Named(item) => {
393            let missing_imports = missing_imports.entry(import_id).or_insert_with(|| {
394                [item.clone()]
395                    .into_iter()
396                    .map(|item| (item.text().to_string(), item))
397                    .collect()
398            });
399
400            let unused_imports = cache.unused_imports.entry(import_id).or_insert_with(|| {
401                [item.clone()]
402                    .into_iter()
403                    .map(|item| (item.text().to_string(), item))
404                    .collect()
405            });
406
407            let name = item.text();
408
409            let (symbol, ty) = if let Some(base) = base {
410                let base = ctx.scope(base);
411                let symbol = base.symbol(name).filter(|s| base.is_symbol_exported(*s));
412                let ty = base.ty(name).filter(|t| base.is_type_exported(*t));
413                (symbol, ty)
414            } else {
415                let symbol = ctx
416                    .resolve_symbol_in(import.base_scope, name)
417                    .filter(|s| {
418                        import.base_scope == import_scope
419                            || ctx.scope(import.base_scope).is_symbol_exported(s.0)
420                    })
421                    .map(|(s, _)| s);
422                let ty = ctx
423                    .resolve_type_in(import.base_scope, name)
424                    .filter(|t| {
425                        import.base_scope == import_scope
426                            || ctx.scope(import.base_scope).is_type_exported(t.0)
427                    })
428                    .map(|(t, _)| t);
429                (symbol, ty)
430            };
431
432            if let Some(symbol) = symbol {
433                if diagnostics && let Some(srcloc) = item.srcloc() {
434                    ctx.add_syntax_for_source(
435                        SyntaxItemKind::SymbolReference(symbol),
436                        TextRange::new(
437                            srcloc.span.start.try_into().unwrap(),
438                            srcloc.span.end.try_into().unwrap(),
439                        ),
440                        source.kind.clone(),
441                    );
442                }
443
444                let target = ctx.scope_mut(import_scope);
445
446                if target.symbol(name).is_none() {
447                    target.insert_symbol(name.to_string(), symbol, import.exported);
448                    target.add_symbol_import(symbol, import_id);
449                    ctx.import_mut(import_id)
450                        .declarations
451                        .push((name.to_string(), Declaration::Symbol(symbol)));
452                    updated = true;
453                    unused_imports.shift_remove(name);
454                } else if !target.is_symbol_exported(symbol)
455                    && import.exported
456                    && target.symbol(name) == Some(symbol)
457                {
458                    target.export_symbol(symbol);
459                    ctx.import_mut(import_id)
460                        .declarations
461                        .push((name.to_string(), Declaration::Symbol(symbol)));
462                    updated = true;
463                    unused_imports.shift_remove(name);
464                }
465            }
466
467            if let Some(ty) = ty {
468                if diagnostics && let Some(srcloc) = item.srcloc() {
469                    ctx.add_syntax_for_source(
470                        SyntaxItemKind::TypeReference(ty),
471                        TextRange::new(
472                            srcloc.span.start.try_into().unwrap(),
473                            srcloc.span.end.try_into().unwrap(),
474                        ),
475                        source.kind.clone(),
476                    );
477                }
478
479                let target = ctx.scope_mut(import_scope);
480
481                if target.ty(name).is_none() {
482                    target.insert_type(name.to_string(), ty, import.exported);
483                    target.add_type_import(ty, import_id);
484                    ctx.import_mut(import_id)
485                        .declarations
486                        .push((name.to_string(), Declaration::Type(ty)));
487                    updated = true;
488                    unused_imports.shift_remove(name);
489                } else if !target.is_type_exported(ty)
490                    && import.exported
491                    && target.ty(name) == Some(ty)
492                {
493                    target.export_type(ty);
494                    ctx.import_mut(import_id)
495                        .declarations
496                        .push((name.to_string(), Declaration::Type(ty)));
497                    updated = true;
498                    unused_imports.shift_remove(name);
499                }
500            }
501
502            if symbol.is_some() || ty.is_some() {
503                missing_imports.shift_remove(name);
504            } else if diagnostics {
505                unused_imports.shift_remove(name);
506            }
507        }
508    }
509
510    updated
511}