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}