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}