Rust · 24129 bytes Raw Blame History
1 //! USE-statement resolution.
2 //!
3 //! Extracted from `core.rs` in Sprint 14. Contains the four functions
4 //! that handle USE association: `process_uses` (the main entry point),
5 //! `ensure_uses_loaded`, `preload_stmt_uses`, and `load_external_module`
6 //! (the .amod loader that synthesises a module scope when a USE'd
7 //! module wasn't seen in-file).
8
9 use crate::ast::decl::{ArraySpec, Decl, OnlyItem, SpannedDecl};
10 use crate::sema::symtab::*;
11
12 use super::core::{
13 backfill_procedure_pointer_interfaces, merge_specific_names, resolve_unit,
14 LOADED_EXTERNAL_MODULES,
15 };
16
17 pub(super) fn process_uses(
18 st: &mut SymbolTable,
19 uses: &[SpannedDecl],
20 module_search_paths: &[std::path::PathBuf],
21 type_layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
22 ) -> Result<(), SemaError> {
23 for use_decl in uses {
24 if let Decl::UseStmt {
25 module,
26 nature: _,
27 renames,
28 only,
29 } = &use_decl.node
30 {
31 // If the module isn't defined in-file, try loading from .amod.
32 let mod_scope = st
33 .find_module_scope(module)
34 .or_else(|| load_external_module(st, module, module_search_paths, type_layouts));
35 if let Some(mod_scope) = mod_scope {
36 // Reject self-USE: a module cannot USE itself.
37 if mod_scope == st.current_scope() {
38 return Err(SemaError {
39 msg: format!("module '{}' cannot USE itself", module),
40 span: use_decl.span,
41 });
42 }
43 if let Some(only_items) = only {
44 // USE ... ONLY: import specific names.
45 for item in only_items {
46 match item {
47 OnlyItem::Name(name) => {
48 st.add_use_association(UseAssociation {
49 local_name: name.clone(),
50 original_name: name.clone(),
51 source_scope: mod_scope,
52 is_submodule_access: false,
53 from_bare_use: false,
54 });
55 }
56 OnlyItem::Generic(name) => {
57 st.add_use_association(UseAssociation {
58 local_name: name.clone(),
59 original_name: name.clone(),
60 source_scope: mod_scope,
61 is_submodule_access: false,
62 from_bare_use: false,
63 });
64 }
65 OnlyItem::Rename(rename) => {
66 st.add_use_association(UseAssociation {
67 local_name: rename.local.clone(),
68 original_name: rename.remote.clone(),
69 source_scope: mod_scope,
70 is_submodule_access: false,
71 from_bare_use: false,
72 });
73 }
74 }
75 }
76 } else {
77 // USE without ONLY: import all public symbols.
78 let mod_symbols: Vec<(String, String)> = st
79 .scope(mod_scope)
80 .symbols
81 .iter()
82 .filter(|(_, sym)| sym.attrs.access != Access::Private)
83 .map(|(key, sym)| (sym.name.clone(), key.clone()))
84 .collect();
85 for (name, _key) in &mod_symbols {
86 st.add_use_association(UseAssociation {
87 local_name: name.clone(),
88 original_name: name.clone(),
89 source_scope: mod_scope,
90 is_submodule_access: false,
91 from_bare_use: true,
92 });
93 }
94 // Apply renames. Renames inside a bare USE rebind a
95 // single name; the name itself is no longer bare so
96 // it doesn't extend transitive lookup.
97 for rename in renames {
98 st.add_use_association(UseAssociation {
99 local_name: rename.local.clone(),
100 original_name: rename.remote.clone(),
101 source_scope: mod_scope,
102 is_submodule_access: false,
103 from_bare_use: false,
104 });
105 }
106 }
107 } else {
108 return Err(SemaError {
109 msg: format!("module '{}' not found (searched -I paths and current directory for {}.amod)", module, module.to_lowercase()),
110 span: use_decl.span,
111 });
112 }
113 }
114 }
115 Ok(())
116 }
117
118 /// BLOCK constructs can carry their own USE statements inside a statement body.
119 /// We do not model block-local use associations in the symbol table yet, but we
120 /// still need the referenced modules loaded so later validation and lowering can
121 /// resolve imported procedures, derived types, and module globals.
122 pub(super) fn ensure_uses_loaded(
123 st: &mut SymbolTable,
124 uses: &[SpannedDecl],
125 module_search_paths: &[std::path::PathBuf],
126 type_layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
127 ) {
128 for use_decl in uses {
129 if let Decl::UseStmt { module, .. } = &use_decl.node {
130 if st.find_module_scope(module).is_none() {
131 let _ = load_external_module(st, module, module_search_paths, type_layouts);
132 }
133 }
134 }
135 }
136
137 pub(super) fn preload_stmt_uses(
138 st: &mut SymbolTable,
139 stmts: &[crate::ast::stmt::SpannedStmt],
140 module_search_paths: &[std::path::PathBuf],
141 type_layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
142 ) {
143 use crate::ast::stmt::Stmt;
144
145 for stmt in stmts {
146 match &stmt.node {
147 Stmt::IfConstruct {
148 then_body,
149 else_ifs,
150 else_body,
151 ..
152 } => {
153 preload_stmt_uses(st, then_body, module_search_paths, type_layouts);
154 for (_, body) in else_ifs {
155 preload_stmt_uses(st, body, module_search_paths, type_layouts);
156 }
157 if let Some(body) = else_body {
158 preload_stmt_uses(st, body, module_search_paths, type_layouts);
159 }
160 }
161 Stmt::IfStmt { action, .. } => {
162 preload_stmt_uses(
163 st,
164 std::slice::from_ref(action.as_ref()),
165 module_search_paths,
166 type_layouts,
167 );
168 }
169 Stmt::DoLoop { body, .. }
170 | Stmt::DoWhile { body, .. }
171 | Stmt::DoConcurrent { body, .. }
172 | Stmt::Associate { body, .. }
173 | Stmt::ForallConstruct { body, .. }
174 | Stmt::WhereConstruct { body, .. } => {
175 preload_stmt_uses(st, body, module_search_paths, type_layouts);
176 }
177 Stmt::ForallStmt { stmt: inner, .. }
178 | Stmt::WhereStmt { stmt: inner, .. }
179 | Stmt::Labeled { stmt: inner, .. } => {
180 preload_stmt_uses(
181 st,
182 std::slice::from_ref(inner.as_ref()),
183 module_search_paths,
184 type_layouts,
185 );
186 }
187 Stmt::Block {
188 uses, ifaces, body, ..
189 } => {
190 ensure_uses_loaded(st, uses, module_search_paths, type_layouts);
191 for iface in ifaces {
192 let _ = resolve_unit(st, iface, module_search_paths, type_layouts);
193 }
194 preload_stmt_uses(st, body, module_search_paths, type_layouts);
195 }
196 Stmt::SelectCase { cases, .. } => {
197 for case in cases {
198 preload_stmt_uses(st, &case.body, module_search_paths, type_layouts);
199 }
200 }
201 Stmt::SelectType { guards, .. } => {
202 for guard in guards {
203 match guard {
204 crate::ast::stmt::TypeGuard::TypeIs { body, .. }
205 | crate::ast::stmt::TypeGuard::ClassIs { body, .. }
206 | crate::ast::stmt::TypeGuard::ClassDefault { body } => {
207 preload_stmt_uses(st, body, module_search_paths, type_layouts);
208 }
209 }
210 }
211 }
212 _ => {}
213 }
214 }
215 }
216
217
218 /// Try to load a module interface from an .amod file on the search path.
219 /// Creates a synthetic module scope in the symbol table and returns its ID.
220 pub(super) fn load_external_module(
221 st: &mut SymbolTable,
222 module_name: &str,
223 search_paths: &[std::path::PathBuf],
224 type_layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
225 ) -> Option<ScopeId> {
226 use crate::lexer::{Position, Span};
227 use crate::sema::amod;
228
229 let filename = format!("{}.amod", module_name.to_lowercase());
230
231 // Search -I paths then CWD.
232 let mut candidates: Vec<std::path::PathBuf> =
233 search_paths.iter().map(|p| p.join(&filename)).collect();
234 candidates.push(std::path::PathBuf::from(&filename));
235
236 let amod_path = candidates.iter().find(|p| p.exists())?;
237
238 let iface = match amod::read_amod(amod_path) {
239 Ok(iface) => iface,
240 Err(e) => {
241 eprintln!("warning: {}", e);
242 return None;
243 }
244 };
245
246 let dummy_span = Span {
247 file_id: 0,
248 start: Position { line: 0, col: 0 },
249 end: Position { line: 0, col: 0 },
250 };
251
252 // Create a synthetic module scope.
253 let scope_id = st.push_scope(ScopeKind::Module(iface.module_name.clone()));
254
255 // Recursively resolve `@uses` dependencies so transitive USE
256 // chains see re-exported symbols. Each dep becomes a
257 // UseAssociation on this scope, exactly like `use foo` inside a
258 // real source module, which makes lookup_in_guarded walk into
259 // the dep's symbols. Without this, `USE amod_middle` where
260 // middle does `use amod_base` never sees amod_base's symbols.
261 for dep in &iface.dependencies {
262 let dep_scope = st
263 .find_module_scope(dep)
264 .or_else(|| load_external_module(st, dep, search_paths, type_layouts));
265 if let Some(dep_scope) = dep_scope {
266 st.enter_scope(scope_id);
267 // Re-export every public symbol of the dep by name, like
268 // a bare `use <dep>` in source. The transitive lookup in
269 // SymbolTable::lookup_in_guarded handles onward chaining.
270 for (name, sym) in st
271 .scope(dep_scope)
272 .symbols
273 .iter()
274 .map(|(n, s)| (n.clone(), s.clone()))
275 .collect::<Vec<_>>()
276 {
277 if matches!(sym.attrs.access, Access::Private) {
278 continue;
279 }
280 st.add_use_association(crate::sema::symtab::UseAssociation {
281 local_name: name.clone(),
282 original_name: name,
283 source_scope: dep_scope,
284 is_submodule_access: false,
285 from_bare_use: true,
286 });
287 }
288 }
289 }
290
291 // Replay use renames recorded by the writer (`@use_rename a = b from m`).
292 // Without this, `use stdlib_kinds, only: block_kind => int64` is lost
293 // when stdlib_bitsets is serialized, and submodule bodies can no
294 // longer resolve `block_kind` for kind selectors — `integer(block_kind)
295 // :: dummy` falls back to default kind=4 and silently truncates a
296 // 64-bit local to 32 bits.
297 for rename in &iface.renames {
298 let src_scope = st
299 .find_module_scope(&rename.source_module)
300 .or_else(|| load_external_module(st, &rename.source_module, search_paths, type_layouts));
301 let Some(src_scope) = src_scope else {
302 continue;
303 };
304 st.enter_scope(scope_id);
305 st.add_use_association(crate::sema::symtab::UseAssociation {
306 local_name: rename.local.clone(),
307 original_name: rename.original.clone(),
308 source_scope: src_scope,
309 is_submodule_access: false,
310 from_bare_use: false,
311 });
312 }
313
314 // Populate variables and parameters.
315 for var in &iface.variables {
316 let kind = if var.is_parameter {
317 SymbolKind::Parameter
318 } else if var.proc_pointer {
319 SymbolKind::ProcedurePointer
320 } else {
321 SymbolKind::Variable
322 };
323 let attrs = SymbolAttrs {
324 access: var.access,
325 allocatable: var.allocatable,
326 save: var.save,
327 pointer: var.pointer,
328 target: var.target,
329 parameter: var.is_parameter,
330 external: var.proc_pointer,
331 procedure_iface: if var.proc_pointer {
332 match &var.type_info {
333 Some(TypeInfo::Derived(name)) => Some(name.clone()),
334 _ => None,
335 }
336 } else {
337 None
338 },
339 ..Default::default()
340 };
341 let _ = st.define(Symbol {
342 name: var.name.clone(),
343 kind,
344 type_info: var.type_info.clone(),
345 attrs,
346 defined_at: dummy_span,
347 scope: scope_id,
348 arg_names: vec![],
349 const_value: var.const_value,
350 });
351 }
352
353 // Populate procedures. Each proc is defined as a symbol in the
354 // module scope AND given its own Function/Subroutine scope whose
355 // symbols carry the argument type_info. The dedicated scope is
356 // what `resolve_generic_call` walks to match argument types at
357 // call sites — without it, cross-TU generic dispatch sees no
358 // candidates and fails.
359 for proc in &iface.procedures {
360 // Sprint35-SMP Phase 2: rebuild the function result's array_spec
361 // from result_rank + result_allocatable/pointer flags so the
362 // SMP-body synthesizer can recover the result's shape from a
363 // pure .amod load (where the result variable isn't otherwise
364 // present as a separate symbol).
365 let result_array_spec: Vec<ArraySpec> = if proc.result_rank == 0 {
366 Vec::new()
367 } else {
368 let template = if proc.result_allocatable || proc.result_pointer {
369 ArraySpec::Deferred
370 } else {
371 ArraySpec::AssumedShape { lower: None }
372 };
373 vec![template; proc.result_rank as usize]
374 };
375 let attrs = SymbolAttrs {
376 access: proc.access,
377 allocatable: proc.result_allocatable,
378 pointer: proc.result_pointer,
379 pure: proc.pure,
380 elemental: proc.elemental,
381 binding_label: proc.binding_label.clone(),
382 result_rank: proc.result_rank,
383 array_spec: result_array_spec,
384 ..Default::default()
385 };
386 let arg_names: Vec<String> = proc
387 .args
388 .iter()
389 .filter(|a| !a.hidden)
390 .map(|a| a.name.clone())
391 .collect();
392 let _ = st.define(Symbol {
393 name: proc.name.clone(),
394 kind: proc.kind.clone(),
395 type_info: proc.return_type.clone(),
396 attrs,
397 defined_at: dummy_span,
398 scope: scope_id,
399 arg_names: arg_names.clone(),
400 const_value: None,
401 });
402 // Synthesise a Function/Subroutine scope for this procedure
403 // so arg types survive to generic dispatch.
404 let proc_scope_kind = match &proc.kind {
405 crate::sema::symtab::SymbolKind::Function => ScopeKind::Function(proc.name.clone()),
406 crate::sema::symtab::SymbolKind::Subroutine => ScopeKind::Subroutine(proc.name.clone()),
407 _ => continue,
408 };
409 let proc_scope = st.push_scope(proc_scope_kind);
410 st.scope_mut(proc_scope).arg_order = arg_names.clone();
411 for arg in &proc.args {
412 if arg.hidden {
413 continue;
414 }
415 // Sprint35-SMP Phase 1: rebuild a same-rank array_spec from
416 // the encoded rank + descriptor/allocatable/pointer flags.
417 // Bound expressions are not preserved across .amod boundaries;
418 // assumed-shape / deferred-shape kinds are sufficient for the
419 // synthesizer's uses in Phase 2 (the bound expressions are
420 // unused for descriptor-passed dummies anyway — extents come
421 // from the caller's runtime descriptor).
422 let array_spec: Vec<ArraySpec> = if arg.rank == 0 {
423 Vec::new()
424 } else {
425 let template = if arg.allocatable || arg.pointer {
426 ArraySpec::Deferred
427 } else if arg.descriptor {
428 ArraySpec::AssumedShape { lower: None }
429 } else {
430 // Non-descriptor array dummy: explicit-shape with
431 // unknown bounds. The Phase-2 synthesizer treats
432 // this as a placeholder and the lowering helpers
433 // pull actual bounds from the caller's array.
434 ArraySpec::AssumedShape { lower: None }
435 };
436 vec![template; arg.rank as usize]
437 };
438 let arg_attrs = SymbolAttrs {
439 intent: arg.intent,
440 optional: arg.optional,
441 value: arg.value,
442 allocatable: arg.allocatable,
443 pointer: arg.pointer,
444 external: arg.external,
445 procedure_iface: arg.procedure_iface.clone(),
446 array_spec,
447 ..Default::default()
448 };
449 let _ = st.define(Symbol {
450 name: arg.name.clone(),
451 kind: crate::sema::symtab::SymbolKind::Variable,
452 type_info: arg.type_info.clone(),
453 attrs: arg_attrs,
454 defined_at: dummy_span,
455 scope: proc_scope,
456 arg_names: vec![],
457 const_value: None,
458 });
459 }
460 // Sprint35-SMP Phase 2: also define the function's result
461 // variable in the proc scope under a name that won't collide
462 // with the user's own local declarations. Same-name SMP-body
463 // procedures push their own Function scope on top, so the
464 // duplicate name `result` would otherwise shadow the local
465 // and the validator's lookup would walk to this stale symbol
466 // and reject `allocate(result(...))`. Use a doubly-underscored
467 // synth name so SMP-body synthesis can find it (via the body
468 // scope after sema injection) but no user code can collide.
469 if matches!(proc.kind, crate::sema::symtab::SymbolKind::Function)
470 && proc.result_rank > 0
471 {
472 let synth_name = format!(
473 "__amod_result_{}",
474 proc.result_name.as_deref().unwrap_or(&proc.name)
475 );
476 // Sprint35-SMP Phase 3: prefer the .amod-preserved
477 // explicit-shape bounds when available so split-file
478 // submodule lowering of `res = …` allocates a runtime-shape
479 // result in the function prologue. Falls through to the
480 // legacy AssumedShape template when bounds aren't in .amod
481 // (rank-only, allocatable, or pointer results).
482 let parsed_bounds = proc
483 .result_array_bounds
484 .as_deref()
485 .and_then(amod::parse_array_bounds);
486 let result_array_spec: Vec<ArraySpec> = if proc.result_rank == 0 {
487 Vec::new()
488 } else if let Some(specs) = parsed_bounds {
489 if specs.len() == proc.result_rank as usize {
490 specs
491 } else {
492 let template = if proc.result_allocatable || proc.result_pointer {
493 ArraySpec::Deferred
494 } else {
495 ArraySpec::AssumedShape { lower: None }
496 };
497 vec![template; proc.result_rank as usize]
498 }
499 } else {
500 let template = if proc.result_allocatable || proc.result_pointer {
501 ArraySpec::Deferred
502 } else {
503 ArraySpec::AssumedShape { lower: None }
504 };
505 vec![template; proc.result_rank as usize]
506 };
507 let result_attrs = SymbolAttrs {
508 allocatable: proc.result_allocatable,
509 pointer: proc.result_pointer,
510 array_spec: result_array_spec,
511 ..Default::default()
512 };
513 let _ = st.define(Symbol {
514 name: synth_name,
515 kind: crate::sema::symtab::SymbolKind::Variable,
516 type_info: proc.return_type.clone(),
517 attrs: result_attrs,
518 defined_at: dummy_span,
519 scope: proc_scope,
520 arg_names: vec![],
521 const_value: None,
522 });
523 }
524 st.pop_scope();
525 }
526 backfill_procedure_pointer_interfaces(st, scope_id);
527
528 // Register type layouts.
529 for layout in &iface.types {
530 type_layouts.insert(layout.clone());
531 // Also add a DerivedType symbol.
532 let attrs = SymbolAttrs {
533 access: Access::Public,
534 ..Default::default()
535 };
536 let _ = st.define(Symbol {
537 name: layout.name.clone(),
538 kind: SymbolKind::DerivedType,
539 type_info: None,
540 attrs,
541 defined_at: dummy_span,
542 scope: scope_id,
543 arg_names: vec![],
544 const_value: None,
545 });
546 }
547
548 // Register named generic interfaces. The specifics list rides
549 // in `arg_names` to match how intra-file INTERFACE blocks are
550 // stored by process_decls — `resolve_generic_call` reads it
551 // when dispatching a call through the generic name. The access
552 // attribute is preserved from the .amod so that submodules can
553 // dispatch private parent interfaces via host association while
554 // ordinary `USE` consumers filter them out (F2018 §11.2.3).
555 for iface_def in &iface.interfaces {
556 let attrs = SymbolAttrs {
557 access: iface_def.access,
558 ..Default::default()
559 };
560 let define_result = st.define(Symbol {
561 name: iface_def.name.clone(),
562 kind: SymbolKind::NamedInterface,
563 type_info: None,
564 attrs,
565 defined_at: dummy_span,
566 scope: scope_id,
567 arg_names: iface_def.specifics.clone(),
568 const_value: None,
569 });
570 if define_result.is_err() {
571 let key = iface_def.name.to_ascii_lowercase();
572 if let Some(existing) = st.scope_mut(scope_id).symbols.get_mut(&key) {
573 if existing.kind == SymbolKind::NamedInterface
574 || existing.kind == SymbolKind::DerivedType
575 {
576 merge_specific_names(&mut existing.arg_names, &iface_def.specifics);
577 }
578 }
579 }
580 }
581
582 st.pop_scope();
583
584 // Track the loaded interface so resolve_file can return it.
585 LOADED_EXTERNAL_MODULES.with(|cell| cell.borrow_mut().push(iface));
586
587 Some(scope_id)
588 }
589