Skip to main content

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