Rust · 63027 bytes Raw Blame History
1 //! Symbol resolution — walks the AST and populates symbol tables.
2 //!
3 //! First pass: collect declarations, create scopes, process USE/IMPLICIT.
4 //! This establishes the symbol table that type checking (Sprint 13) will use.
5
6 use super::symtab::*;
7 use crate::ast::decl;
8 use crate::ast::decl::{Attribute, Decl, OnlyItem, SpannedDecl, TypeSpec};
9 use crate::ast::unit::*;
10 use std::cell::RefCell;
11 use std::collections::HashMap;
12
13 thread_local! {
14 /// Track externally loaded module interfaces so resolve_file can
15 /// return them to the driver for globals extraction.
16 static LOADED_EXTERNAL_MODULES: RefCell<Vec<super::amod::ModuleInterface>> = const { RefCell::new(Vec::new()) };
17 }
18
19 /// Walk a list of program units and build the symbol table.
20 /// Result of resolving a file: symbol table, type layouts, and any
21 /// external module interfaces loaded from .amod files during USE
22 /// resolution.
23 pub struct ResolveResult {
24 pub st: SymbolTable,
25 pub type_layouts: super::type_layout::TypeLayoutRegistry,
26 pub external_modules: Vec<super::amod::ModuleInterface>,
27 }
28
29 pub fn resolve_file(
30 units: &[SpannedUnit],
31 module_search_paths: &[std::path::PathBuf],
32 ) -> Result<ResolveResult, SemaError> {
33 let mut st = SymbolTable::new();
34 let mut layouts = super::type_layout::TypeLayoutRegistry::new();
35
36 // Register intrinsic modules (iso_c_binding, iso_fortran_env) so USE can find them.
37 super::intrinsic_modules::register_intrinsic_modules(&mut st);
38
39 // First pass: create module scopes so USE can find them.
40 for unit in units {
41 if let ProgramUnit::Module { name, .. } = &unit.node {
42 st.push_scope(ScopeKind::Module(name.clone()));
43 st.pop_scope();
44 }
45 }
46
47 // Second pass: populate all scopes (loads .amod files lazily on USE miss).
48 // Track which external modules were loaded.
49 LOADED_EXTERNAL_MODULES.with(|cell| cell.borrow_mut().clear());
50 for unit in units {
51 resolve_unit(&mut st, unit, module_search_paths, &mut layouts)?;
52 }
53 let external_modules = LOADED_EXTERNAL_MODULES.with(|cell| {
54 let v = cell.borrow();
55 v.iter().cloned().collect::<Vec<_>>()
56 });
57
58 // Third pass: compute layouts for all derived types.
59 compute_all_layouts(units, &mut layouts);
60
61 Ok(ResolveResult {
62 st,
63 type_layouts: layouts,
64 external_modules,
65 })
66 }
67
68 fn backfill_procedure_pointer_interfaces(st: &mut SymbolTable, scope_id: ScopeId) {
69 let updates: Vec<(String, Option<TypeInfo>, Vec<String>)> = st
70 .scope(scope_id)
71 .symbols
72 .iter()
73 .filter_map(|(key, sym)| {
74 if sym.kind != SymbolKind::ProcedurePointer {
75 return None;
76 }
77 let TypeInfo::Derived(iface_name) = sym.type_info.as_ref()? else {
78 return None;
79 };
80 let iface_sym = st.find_symbol_any_scope(&iface_name.to_lowercase())?;
81 Some((
82 key.clone(),
83 iface_sym.type_info.clone(),
84 iface_sym.arg_names.clone(),
85 ))
86 })
87 .collect();
88
89 for (key, type_info, arg_names) in updates {
90 if let Some(sym) = st.scope_mut(scope_id).symbols.get_mut(&key) {
91 if let Some(type_info) = type_info {
92 sym.type_info = Some(type_info);
93 }
94 sym.arg_names = arg_names;
95 }
96 }
97 }
98
99 fn normalized_bind_name(bind: Option<&crate::ast::unit::BindInfo>) -> Option<String> {
100 bind.and_then(|info| info.name.as_ref())
101 .map(|name| name.trim_matches('\'').trim_matches('"').to_string())
102 }
103
104 type InterfaceOuterRef = (
105 String,
106 SymbolKind,
107 Option<TypeInfo>,
108 Vec<String>,
109 Option<String>,
110 );
111
112 fn resolve_unit(
113 st: &mut SymbolTable,
114 unit: &SpannedUnit,
115 module_search_paths: &[std::path::PathBuf],
116 layouts: &mut super::type_layout::TypeLayoutRegistry,
117 ) -> Result<(), SemaError> {
118 match &unit.node {
119 ProgramUnit::Program {
120 name,
121 uses,
122 imports: _,
123 implicit,
124 decls,
125 body,
126 contains,
127 } => {
128 let scope_name = name.clone().unwrap_or_else(|| "<main>".into());
129 st.push_scope(ScopeKind::Program(scope_name));
130 let scope_id = st.current_scope();
131 process_uses(st, uses, module_search_paths, layouts)?;
132 process_implicit(st, implicit)?;
133 process_decls(st, decls)?;
134 preload_stmt_uses(st, body, module_search_paths, layouts);
135 process_contains(st, contains, module_search_paths, layouts)?;
136 backfill_procedure_pointer_interfaces(st, scope_id);
137 st.pop_scope();
138 }
139 ProgramUnit::Module {
140 name,
141 uses,
142 imports: _,
143 implicit,
144 decls,
145 contains,
146 } => {
147 // Find the pre-created module scope and enter it.
148 if let Some(mod_id) = st.find_module_scope(name) {
149 let saved = st.enter_scope(mod_id);
150
151 process_uses(st, uses, module_search_paths, layouts)?;
152 process_implicit(st, implicit)?;
153 process_decls(st, decls)?;
154 process_contains(st, contains, module_search_paths, layouts)?;
155 backfill_procedure_pointer_interfaces(st, mod_id);
156
157 st.enter_scope(saved);
158 }
159 }
160 ProgramUnit::Subroutine {
161 name,
162 args,
163 prefix: _,
164 bind: _,
165 uses,
166 imports: _,
167 implicit,
168 decls,
169 body,
170 contains,
171 } => {
172 let scope_id = st.push_scope(ScopeKind::Subroutine(name.clone()));
173 // Store ordered arg names for VALUE lookup by callers.
174 st.scope_mut(scope_id).arg_order = args
175 .iter()
176 .filter_map(|a| {
177 if let DummyArg::Name(n) = a {
178 Some(n.to_lowercase())
179 } else {
180 None
181 }
182 })
183 .collect();
184 // Define dummy arguments as symbols.
185 for arg in args {
186 if let DummyArg::Name(arg_name) = arg {
187 st.define(Symbol {
188 name: arg_name.clone(),
189 kind: SymbolKind::Variable,
190 type_info: None,
191 attrs: SymbolAttrs::default(),
192 defined_at: unit.span,
193 scope: st.current_scope(),
194 arg_names: vec![],
195 const_value: None,
196 })?;
197 }
198 }
199 process_uses(st, uses, module_search_paths, layouts)?;
200 process_implicit(st, implicit)?;
201 process_decls(st, decls)?;
202 preload_stmt_uses(st, body, module_search_paths, layouts);
203 process_contains(st, contains, module_search_paths, layouts)?;
204 backfill_procedure_pointer_interfaces(st, scope_id);
205 st.pop_scope();
206 }
207 ProgramUnit::Function {
208 name,
209 args,
210 result,
211 return_type,
212 bind: _,
213 prefix: _,
214 uses,
215 imports: _,
216 implicit,
217 decls,
218 body,
219 contains,
220 } => {
221 let scope_id = st.push_scope(ScopeKind::Function(name.clone()));
222 st.scope_mut(scope_id).arg_order = args
223 .iter()
224 .filter_map(|a| {
225 if let DummyArg::Name(n) = a {
226 Some(n.to_lowercase())
227 } else {
228 None
229 }
230 })
231 .collect();
232 for arg in args {
233 if let DummyArg::Name(arg_name) = arg {
234 st.define(Symbol {
235 name: arg_name.clone(),
236 kind: SymbolKind::Variable,
237 type_info: None,
238 attrs: SymbolAttrs::default(),
239 defined_at: unit.span,
240 scope: st.current_scope(),
241 arg_names: vec![],
242 const_value: None,
243 })?;
244 }
245 }
246 // Define result variable.
247 let result_name = result.as_deref().unwrap_or(name.as_str());
248 st.define(Symbol {
249 name: result_name.into(),
250 kind: SymbolKind::Variable,
251 type_info: return_type.as_ref().map(|ts| type_spec_to_info(ts, st)),
252 attrs: SymbolAttrs::default(),
253 defined_at: unit.span,
254 scope: st.current_scope(),
255 arg_names: vec![],
256 const_value: None,
257 })?;
258 process_uses(st, uses, module_search_paths, layouts)?;
259 process_implicit(st, implicit)?;
260 process_decls(st, decls)?;
261 preload_stmt_uses(st, body, module_search_paths, layouts);
262 process_contains(st, contains, module_search_paths, layouts)?;
263 backfill_procedure_pointer_interfaces(st, scope_id);
264 st.pop_scope();
265 }
266 ProgramUnit::BlockData { name, uses, decls } => {
267 let scope_name = name.clone().unwrap_or_else(|| "<block_data>".into());
268 st.push_scope(ScopeKind::Program(scope_name));
269 process_uses(st, uses, module_search_paths, layouts)?;
270 process_decls(st, decls)?;
271 st.pop_scope();
272 }
273 ProgramUnit::Submodule {
274 parent,
275 ancestor: _,
276 name,
277 uses,
278 decls,
279 contains,
280 } => {
281 // Find the parent module scope and inherit its symbols.
282 let parent_scope = st.find_module_scope(parent);
283 st.push_scope(ScopeKind::Submodule(name.clone()));
284 // Import all parent module symbols into the submodule scope.
285 if let Some(pid) = parent_scope {
286 let parent_syms: Vec<(String, String)> = st
287 .scope(pid)
288 .symbols
289 .iter()
290 .filter(|(_, sym)| sym.attrs.access != Access::Private)
291 .map(|(key, sym)| (sym.name.clone(), key.clone()))
292 .collect();
293 for (sym_name, _key) in &parent_syms {
294 st.add_use_association(UseAssociation {
295 local_name: sym_name.clone(),
296 original_name: sym_name.clone(),
297 source_scope: pid,
298 is_submodule_access: true,
299 });
300 }
301 }
302 process_uses(st, uses, module_search_paths, layouts)?;
303 process_decls(st, decls)?;
304 process_contains(st, contains, module_search_paths, layouts)?;
305 let scope_id = st.current_scope();
306 backfill_procedure_pointer_interfaces(st, scope_id);
307 st.pop_scope();
308 }
309 ProgramUnit::InterfaceBlock {
310 name,
311 is_abstract: _,
312 bodies,
313 } => {
314 // Collect each subprogram's name and return type BEFORE
315 // pushing the Interface scope — the subprogram body gets
316 // its own scope via resolve_unit, and we need to surface
317 // the *declared* callable back into the enclosing scope
318 // (otherwise IMPLICIT NONE rejects the call at the use
319 // site, and generic dispatch can't see the body types).
320 let mut outer_refs: Vec<InterfaceOuterRef> = Vec::new();
321 for body in bodies {
322 if let InterfaceBody::Subprogram(sub) = body {
323 match &sub.node {
324 ProgramUnit::Function {
325 name: fn_name,
326 return_type,
327 args,
328 bind,
329 ..
330 } => {
331 let arg_names = args
332 .iter()
333 .filter_map(|a| {
334 if let DummyArg::Name(n) = a {
335 Some(n.clone())
336 } else {
337 None
338 }
339 })
340 .collect();
341 let ti = return_type.as_ref().map(|ts| type_spec_to_info(ts, st));
342 outer_refs.push((
343 fn_name.clone(),
344 SymbolKind::Function,
345 ti,
346 arg_names,
347 normalized_bind_name(bind.as_ref()),
348 ));
349 }
350 ProgramUnit::Subroutine {
351 name: fn_name,
352 args,
353 bind,
354 ..
355 } => {
356 let arg_names = args
357 .iter()
358 .filter_map(|a| {
359 if let DummyArg::Name(n) = a {
360 Some(n.clone())
361 } else {
362 None
363 }
364 })
365 .collect();
366 outer_refs.push((
367 fn_name.clone(),
368 SymbolKind::Subroutine,
369 None,
370 arg_names,
371 normalized_bind_name(bind.as_ref()),
372 ));
373 }
374 _ => {}
375 }
376 }
377 }
378
379 st.push_scope(ScopeKind::Interface);
380 let mut specific_names = Vec::new();
381 for body in bodies {
382 match body {
383 InterfaceBody::Subprogram(sub) => {
384 match &sub.node {
385 ProgramUnit::Function { name: fn_name, .. }
386 | ProgramUnit::Subroutine { name: fn_name, .. } => {
387 specific_names.push(fn_name.to_lowercase());
388 }
389 _ => {}
390 }
391 resolve_unit(st, sub, module_search_paths, layouts)?;
392 }
393 InterfaceBody::ModuleProcedure(names) => {
394 for n in names {
395 specific_names.push(n.to_lowercase());
396 }
397 }
398 }
399 }
400 st.pop_scope();
401
402 // Surface each declared procedure to the enclosing scope
403 // so callers under IMPLICIT NONE can resolve the name,
404 // and so BIND(C) external prototypes are callable.
405 for (fn_name, kind, ti, arg_names, binding_label) in outer_refs {
406 let span = unit.span;
407 let _ = st.define(Symbol {
408 name: fn_name,
409 kind,
410 type_info: ti,
411 attrs: SymbolAttrs {
412 external: true,
413 binding_label,
414 ..Default::default()
415 },
416 defined_at: span,
417 scope: st.current_scope(),
418 arg_names,
419 const_value: None,
420 });
421 }
422
423 // Register the generic interface name in the enclosing scope.
424 if let Some(generic_name) = name {
425 if !generic_name.is_empty() && !specific_names.is_empty() {
426 let span = unit.span;
427 let _ = st.define(Symbol {
428 name: generic_name.clone(),
429 kind: SymbolKind::NamedInterface,
430 type_info: None,
431 attrs: SymbolAttrs {
432 ..Default::default()
433 },
434 defined_at: span,
435 scope: st.current_scope(),
436 arg_names: specific_names,
437 const_value: None,
438 });
439 }
440 }
441 }
442 }
443 Ok(())
444 }
445
446 fn process_uses(
447 st: &mut SymbolTable,
448 uses: &[SpannedDecl],
449 module_search_paths: &[std::path::PathBuf],
450 type_layouts: &mut super::type_layout::TypeLayoutRegistry,
451 ) -> Result<(), SemaError> {
452 for use_decl in uses {
453 if let Decl::UseStmt {
454 module,
455 nature: _,
456 renames,
457 only,
458 } = &use_decl.node
459 {
460 // If the module isn't defined in-file, try loading from .amod.
461 let mod_scope = st
462 .find_module_scope(module)
463 .or_else(|| load_external_module(st, module, module_search_paths, type_layouts));
464 if let Some(mod_scope) = mod_scope {
465 // Reject self-USE: a module cannot USE itself.
466 if mod_scope == st.current_scope() {
467 return Err(SemaError {
468 msg: format!("module '{}' cannot USE itself", module),
469 span: use_decl.span,
470 });
471 }
472 if let Some(only_items) = only {
473 // USE ... ONLY: import specific names.
474 for item in only_items {
475 match item {
476 OnlyItem::Name(name) => {
477 st.add_use_association(UseAssociation {
478 local_name: name.clone(),
479 original_name: name.clone(),
480 source_scope: mod_scope,
481 is_submodule_access: false,
482 });
483 }
484 OnlyItem::Rename(rename) => {
485 st.add_use_association(UseAssociation {
486 local_name: rename.local.clone(),
487 original_name: rename.remote.clone(),
488 source_scope: mod_scope,
489 is_submodule_access: false,
490 });
491 }
492 }
493 }
494 } else {
495 // USE without ONLY: import all public symbols.
496 let mod_symbols: Vec<(String, String)> = st
497 .scope(mod_scope)
498 .symbols
499 .iter()
500 .filter(|(_, sym)| sym.attrs.access != Access::Private)
501 .map(|(key, sym)| (sym.name.clone(), key.clone()))
502 .collect();
503 for (name, _key) in &mod_symbols {
504 st.add_use_association(UseAssociation {
505 local_name: name.clone(),
506 original_name: name.clone(),
507 source_scope: mod_scope,
508 is_submodule_access: false,
509 });
510 }
511 // Apply renames.
512 for rename in renames {
513 st.add_use_association(UseAssociation {
514 local_name: rename.local.clone(),
515 original_name: rename.remote.clone(),
516 source_scope: mod_scope,
517 is_submodule_access: false,
518 });
519 }
520 }
521 } else {
522 return Err(SemaError {
523 msg: format!("module '{}' not found (searched -I paths and current directory for {}.amod)", module, module.to_lowercase()),
524 span: use_decl.span,
525 });
526 }
527 }
528 }
529 Ok(())
530 }
531
532 /// BLOCK constructs can carry their own USE statements inside a statement body.
533 /// We do not model block-local use associations in the symbol table yet, but we
534 /// still need the referenced modules loaded so later validation and lowering can
535 /// resolve imported procedures, derived types, and module globals.
536 fn ensure_uses_loaded(
537 st: &mut SymbolTable,
538 uses: &[SpannedDecl],
539 module_search_paths: &[std::path::PathBuf],
540 type_layouts: &mut super::type_layout::TypeLayoutRegistry,
541 ) {
542 for use_decl in uses {
543 if let Decl::UseStmt { module, .. } = &use_decl.node {
544 if st.find_module_scope(module).is_none() {
545 let _ = load_external_module(st, module, module_search_paths, type_layouts);
546 }
547 }
548 }
549 }
550
551 fn preload_stmt_uses(
552 st: &mut SymbolTable,
553 stmts: &[crate::ast::stmt::SpannedStmt],
554 module_search_paths: &[std::path::PathBuf],
555 type_layouts: &mut super::type_layout::TypeLayoutRegistry,
556 ) {
557 use crate::ast::stmt::Stmt;
558
559 for stmt in stmts {
560 match &stmt.node {
561 Stmt::IfConstruct {
562 then_body,
563 else_ifs,
564 else_body,
565 ..
566 } => {
567 preload_stmt_uses(st, then_body, module_search_paths, type_layouts);
568 for (_, body) in else_ifs {
569 preload_stmt_uses(st, body, module_search_paths, type_layouts);
570 }
571 if let Some(body) = else_body {
572 preload_stmt_uses(st, body, module_search_paths, type_layouts);
573 }
574 }
575 Stmt::IfStmt { action, .. } => {
576 preload_stmt_uses(
577 st,
578 std::slice::from_ref(action.as_ref()),
579 module_search_paths,
580 type_layouts,
581 );
582 }
583 Stmt::DoLoop { body, .. }
584 | Stmt::DoWhile { body, .. }
585 | Stmt::DoConcurrent { body, .. }
586 | Stmt::Associate { body, .. }
587 | Stmt::ForallConstruct { body, .. }
588 | Stmt::WhereConstruct { body, .. } => {
589 preload_stmt_uses(st, body, module_search_paths, type_layouts);
590 }
591 Stmt::ForallStmt { stmt: inner, .. }
592 | Stmt::WhereStmt { stmt: inner, .. }
593 | Stmt::Labeled { stmt: inner, .. } => {
594 preload_stmt_uses(
595 st,
596 std::slice::from_ref(inner.as_ref()),
597 module_search_paths,
598 type_layouts,
599 );
600 }
601 Stmt::Block {
602 uses, ifaces, body, ..
603 } => {
604 ensure_uses_loaded(st, uses, module_search_paths, type_layouts);
605 for iface in ifaces {
606 let _ = resolve_unit(st, iface, module_search_paths, type_layouts);
607 }
608 preload_stmt_uses(st, body, module_search_paths, type_layouts);
609 }
610 Stmt::SelectCase { cases, .. } => {
611 for case in cases {
612 preload_stmt_uses(st, &case.body, module_search_paths, type_layouts);
613 }
614 }
615 Stmt::SelectType { guards, .. } => {
616 for guard in guards {
617 match guard {
618 crate::ast::stmt::TypeGuard::TypeIs { body, .. }
619 | crate::ast::stmt::TypeGuard::ClassIs { body, .. }
620 | crate::ast::stmt::TypeGuard::ClassDefault { body } => {
621 preload_stmt_uses(st, body, module_search_paths, type_layouts);
622 }
623 }
624 }
625 }
626 _ => {}
627 }
628 }
629 }
630
631 /// Try to load a module interface from an .amod file on the search path.
632 /// Creates a synthetic module scope in the symbol table and returns its ID.
633 fn load_external_module(
634 st: &mut SymbolTable,
635 module_name: &str,
636 search_paths: &[std::path::PathBuf],
637 type_layouts: &mut super::type_layout::TypeLayoutRegistry,
638 ) -> Option<ScopeId> {
639 use crate::lexer::{Position, Span};
640 use crate::sema::amod;
641
642 let filename = format!("{}.amod", module_name.to_lowercase());
643
644 // Search -I paths then CWD.
645 let mut candidates: Vec<std::path::PathBuf> =
646 search_paths.iter().map(|p| p.join(&filename)).collect();
647 candidates.push(std::path::PathBuf::from(&filename));
648
649 let amod_path = candidates.iter().find(|p| p.exists())?;
650
651 let iface = match amod::read_amod(amod_path) {
652 Ok(iface) => iface,
653 Err(e) => {
654 eprintln!("warning: {}", e);
655 return None;
656 }
657 };
658
659 let dummy_span = Span {
660 file_id: 0,
661 start: Position { line: 0, col: 0 },
662 end: Position { line: 0, col: 0 },
663 };
664
665 // Create a synthetic module scope.
666 let scope_id = st.push_scope(ScopeKind::Module(iface.module_name.clone()));
667
668 // Recursively resolve `@uses` dependencies so transitive USE
669 // chains see re-exported symbols. Each dep becomes a
670 // UseAssociation on this scope, exactly like `use foo` inside a
671 // real source module, which makes lookup_in_guarded walk into
672 // the dep's symbols. Without this, `USE amod_middle` where
673 // middle does `use amod_base` never sees amod_base's symbols.
674 for dep in &iface.dependencies {
675 if let Some(dep_scope) = load_external_module(st, dep, search_paths, type_layouts) {
676 st.enter_scope(scope_id);
677 // Re-export every public symbol of the dep by name, like
678 // a bare `use <dep>` in source. The transitive lookup in
679 // SymbolTable::lookup_in_guarded handles onward chaining.
680 for (name, sym) in st
681 .scope(dep_scope)
682 .symbols
683 .iter()
684 .map(|(n, s)| (n.clone(), s.clone()))
685 .collect::<Vec<_>>()
686 {
687 if matches!(sym.attrs.access, Access::Private) {
688 continue;
689 }
690 st.add_use_association(crate::sema::symtab::UseAssociation {
691 local_name: name.clone(),
692 original_name: name,
693 source_scope: dep_scope,
694 is_submodule_access: false,
695 });
696 }
697 }
698 }
699
700 // Populate variables and parameters.
701 for var in &iface.variables {
702 let kind = if var.is_parameter {
703 SymbolKind::Parameter
704 } else if var.proc_pointer {
705 SymbolKind::ProcedurePointer
706 } else {
707 SymbolKind::Variable
708 };
709 let attrs = SymbolAttrs {
710 access: Access::Public,
711 allocatable: var.allocatable,
712 save: var.save,
713 pointer: var.pointer,
714 target: var.target,
715 parameter: var.is_parameter,
716 external: var.proc_pointer,
717 procedure_iface: if var.proc_pointer {
718 match &var.type_info {
719 Some(TypeInfo::Derived(name)) => Some(name.clone()),
720 _ => None,
721 }
722 } else {
723 None
724 },
725 ..Default::default()
726 };
727 let _ = st.define(Symbol {
728 name: var.name.clone(),
729 kind,
730 type_info: var.type_info.clone(),
731 attrs,
732 defined_at: dummy_span,
733 scope: scope_id,
734 arg_names: vec![],
735 const_value: var.const_value,
736 });
737 }
738
739 // Populate procedures. Each proc is defined as a symbol in the
740 // module scope AND given its own Function/Subroutine scope whose
741 // symbols carry the argument type_info. The dedicated scope is
742 // what `resolve_generic_call` walks to match argument types at
743 // call sites — without it, cross-TU generic dispatch sees no
744 // candidates and fails.
745 for proc in &iface.procedures {
746 let attrs = SymbolAttrs {
747 access: Access::Public,
748 allocatable: proc.result_allocatable,
749 pointer: proc.result_pointer,
750 pure: proc.pure,
751 elemental: proc.elemental,
752 binding_label: proc.binding_label.clone(),
753 ..Default::default()
754 };
755 let arg_names: Vec<String> = proc
756 .args
757 .iter()
758 .filter(|a| !a.hidden)
759 .map(|a| a.name.clone())
760 .collect();
761 let _ = st.define(Symbol {
762 name: proc.name.clone(),
763 kind: proc.kind.clone(),
764 type_info: proc.return_type.clone(),
765 attrs,
766 defined_at: dummy_span,
767 scope: scope_id,
768 arg_names: arg_names.clone(),
769 const_value: None,
770 });
771 // Synthesise a Function/Subroutine scope for this procedure
772 // so arg types survive to generic dispatch.
773 let proc_scope_kind = match &proc.kind {
774 crate::sema::symtab::SymbolKind::Function => ScopeKind::Function(proc.name.clone()),
775 crate::sema::symtab::SymbolKind::Subroutine => ScopeKind::Subroutine(proc.name.clone()),
776 _ => continue,
777 };
778 let proc_scope = st.push_scope(proc_scope_kind);
779 st.scope_mut(proc_scope).arg_order = arg_names.clone();
780 for arg in &proc.args {
781 if arg.hidden {
782 continue;
783 }
784 let arg_attrs = SymbolAttrs {
785 intent: arg.intent,
786 optional: arg.optional,
787 value: arg.value,
788 allocatable: arg.allocatable,
789 pointer: arg.pointer,
790 ..Default::default()
791 };
792 let _ = st.define(Symbol {
793 name: arg.name.clone(),
794 kind: crate::sema::symtab::SymbolKind::Variable,
795 type_info: arg.type_info.clone(),
796 attrs: arg_attrs,
797 defined_at: dummy_span,
798 scope: proc_scope,
799 arg_names: vec![],
800 const_value: None,
801 });
802 }
803 st.pop_scope();
804 }
805 backfill_procedure_pointer_interfaces(st, scope_id);
806
807 // Register type layouts.
808 for layout in &iface.types {
809 type_layouts.insert(layout.clone());
810 // Also add a DerivedType symbol.
811 let attrs = SymbolAttrs {
812 access: Access::Public,
813 ..Default::default()
814 };
815 let _ = st.define(Symbol {
816 name: layout.name.clone(),
817 kind: SymbolKind::DerivedType,
818 type_info: None,
819 attrs,
820 defined_at: dummy_span,
821 scope: scope_id,
822 arg_names: vec![],
823 const_value: None,
824 });
825 }
826
827 // Register named generic interfaces. The specifics list rides
828 // in `arg_names` to match how intra-file INTERFACE blocks are
829 // stored by process_decls — `resolve_generic_call` reads it
830 // when dispatching a call through the generic name.
831 for iface_def in &iface.interfaces {
832 let attrs = SymbolAttrs {
833 access: Access::Public,
834 ..Default::default()
835 };
836 let _ = st.define(Symbol {
837 name: iface_def.name.clone(),
838 kind: SymbolKind::NamedInterface,
839 type_info: None,
840 attrs,
841 defined_at: dummy_span,
842 scope: scope_id,
843 arg_names: iface_def.specifics.clone(),
844 const_value: None,
845 });
846 }
847
848 st.pop_scope();
849
850 // Track the loaded interface so resolve_file can return it.
851 LOADED_EXTERNAL_MODULES.with(|cell| cell.borrow_mut().push(iface));
852
853 Some(scope_id)
854 }
855
856 /// Walk all program units and compute layouts for derived types.
857 fn compute_all_layouts(
858 units: &[SpannedUnit],
859 layouts: &mut super::type_layout::TypeLayoutRegistry,
860 ) {
861 let inherited_params = HashMap::new();
862 for unit in units {
863 collect_derived_type_layouts(&unit.node, layouts, &inherited_params);
864 }
865 }
866
867 fn collect_derived_type_layouts(
868 unit: &ProgramUnit,
869 layouts: &mut super::type_layout::TypeLayoutRegistry,
870 inherited_params: &HashMap<String, i64>,
871 ) {
872 let (decls, contains) = match unit {
873 ProgramUnit::Program {
874 decls, contains, ..
875 }
876 | ProgramUnit::Module {
877 decls, contains, ..
878 }
879 | ProgramUnit::Subroutine {
880 decls, contains, ..
881 }
882 | ProgramUnit::Function {
883 decls, contains, ..
884 } => (decls, contains),
885 _ => return,
886 };
887 let const_params = collect_const_int_params(decls, inherited_params);
888 for decl in decls {
889 if let Decl::DerivedTypeDef {
890 name,
891 extends,
892 components,
893 type_bound_procs,
894 final_procs,
895 ..
896 } = &decl.node
897 {
898 let parent = extends.as_ref().and_then(|p| layouts.get(p)).cloned();
899 let layout = super::type_layout::compute_layout(
900 name,
901 type_bound_procs,
902 final_procs,
903 components,
904 parent.as_ref(),
905 layouts,
906 &const_params,
907 );
908 // Don't overwrite a layout that has bound_procs or final_procs with one that doesn't.
909 // This handles the case where a subroutine redefines a type without CONTAINS.
910 let dominated = layouts
911 .get(&name.to_lowercase())
912 .map(|existing| {
913 let existing_has =
914 !existing.bound_procs.is_empty() || !existing.final_procs.is_empty();
915 let new_has = !layout.bound_procs.is_empty() || !layout.final_procs.is_empty();
916 existing_has && !new_has
917 })
918 .unwrap_or(false);
919 if !dominated {
920 layouts.insert(layout);
921 }
922 }
923 }
924 for sub in contains {
925 collect_derived_type_layouts(&sub.node, layouts, &const_params);
926 }
927 }
928
929 fn eval_const_int_expr_with_params(
930 expr: &crate::ast::expr::SpannedExpr,
931 const_params: &HashMap<String, i64>,
932 ) -> Option<i64> {
933 use crate::ast::expr::Expr;
934 match &expr.node {
935 Expr::IntegerLiteral { text, .. } => {
936 let clean = text.split('_').next().unwrap_or(text);
937 clean.parse::<i64>().ok()
938 }
939 Expr::Name { name } => const_params.get(&name.to_lowercase()).copied(),
940 Expr::UnaryOp { op, operand } => {
941 let v = eval_const_int_expr_with_params(operand, const_params)?;
942 match op {
943 crate::ast::expr::UnaryOp::Minus => Some(-v),
944 crate::ast::expr::UnaryOp::Plus => Some(v),
945 _ => None,
946 }
947 }
948 Expr::BinaryOp { op, left, right } => {
949 let l = eval_const_int_expr_with_params(left, const_params)?;
950 let r = eval_const_int_expr_with_params(right, const_params)?;
951 match op {
952 crate::ast::expr::BinaryOp::Add => Some(l + r),
953 crate::ast::expr::BinaryOp::Sub => Some(l - r),
954 crate::ast::expr::BinaryOp::Mul => Some(l * r),
955 crate::ast::expr::BinaryOp::Div if r != 0 => Some(l / r),
956 _ => None,
957 }
958 }
959 Expr::ParenExpr { inner } => eval_const_int_expr_with_params(inner, const_params),
960 Expr::FunctionCall { callee, args } => {
961 let Expr::Name { name } = &callee.node else {
962 return None;
963 };
964 let first_arg_val = args.first().and_then(|a| {
965 if let crate::ast::expr::SectionSubscript::Element(e) = &a.value {
966 eval_const_int_expr_with_params(e, const_params)
967 } else {
968 None
969 }
970 });
971 match name.to_lowercase().as_str() {
972 "selected_int_kind" => {
973 let r = first_arg_val?;
974 Some(if r <= 2 {
975 1
976 } else if r <= 4 {
977 2
978 } else if r <= 9 {
979 4
980 } else if r <= 18 {
981 8
982 } else if r <= 38 {
983 16
984 } else {
985 -1
986 })
987 }
988 "selected_real_kind" => {
989 let p = first_arg_val?;
990 Some(if p <= 6 {
991 4
992 } else if p <= 15 {
993 8
994 } else {
995 -1
996 })
997 }
998 "kind" => {
999 let arg = args.first()?;
1000 let crate::ast::expr::SectionSubscript::Element(e) = &arg.value else {
1001 return None;
1002 };
1003 match &e.node {
1004 Expr::RealLiteral { text, .. } => {
1005 Some(if text.contains('d') || text.contains('D') {
1006 8
1007 } else {
1008 4
1009 })
1010 }
1011 Expr::IntegerLiteral { .. } => Some(4),
1012 _ => None,
1013 }
1014 }
1015 _ => None,
1016 }
1017 }
1018 _ => None,
1019 }
1020 }
1021
1022 fn collect_const_int_params(
1023 decls: &[SpannedDecl],
1024 inherited_params: &HashMap<String, i64>,
1025 ) -> HashMap<String, i64> {
1026 let mut params = inherited_params.clone();
1027 for decl in decls {
1028 let Decl::TypeDecl {
1029 attrs, entities, ..
1030 } = &decl.node
1031 else {
1032 continue;
1033 };
1034 if !attrs.iter().any(|a| matches!(a, Attribute::Parameter)) {
1035 continue;
1036 }
1037 for entity in entities {
1038 params.remove(&entity.name.to_lowercase());
1039 }
1040 }
1041
1042 let mut changed = true;
1043 while changed {
1044 changed = false;
1045 for decl in decls {
1046 let Decl::TypeDecl {
1047 attrs, entities, ..
1048 } = &decl.node
1049 else {
1050 continue;
1051 };
1052 if !attrs.iter().any(|a| matches!(a, Attribute::Parameter)) {
1053 continue;
1054 }
1055 for entity in entities {
1056 let key = entity.name.to_lowercase();
1057 if params.contains_key(&key) {
1058 continue;
1059 }
1060 let Some(init) = entity.init.as_ref() else {
1061 continue;
1062 };
1063 if let Some(value) = eval_const_int_expr_with_params(init, &params) {
1064 params.insert(key, value);
1065 changed = true;
1066 }
1067 }
1068 }
1069 }
1070
1071 params
1072 }
1073
1074 fn process_implicit(st: &mut SymbolTable, implicit_stmts: &[SpannedDecl]) -> Result<(), SemaError> {
1075 for stmt in implicit_stmts {
1076 match &stmt.node {
1077 Decl::ImplicitNone { type_, external } => {
1078 st.set_implicit_none(*type_, *external);
1079 }
1080 Decl::ImplicitStmt { specs } => {
1081 for spec in specs {
1082 let itype = match &spec.type_spec {
1083 TypeSpec::Integer(_) => ImplicitType::Integer,
1084 TypeSpec::Real(_) => ImplicitType::Real,
1085 TypeSpec::DoublePrecision => ImplicitType::DoublePrecision,
1086 TypeSpec::Complex(_) => ImplicitType::Complex,
1087 TypeSpec::Logical(_) => ImplicitType::Logical,
1088 TypeSpec::Character(_) => ImplicitType::Character,
1089 _ => continue,
1090 };
1091 for (start, end) in &spec.ranges {
1092 st.set_implicit_rule(*start, *end, itype);
1093 }
1094 }
1095 }
1096 _ => {}
1097 }
1098 }
1099 Ok(())
1100 }
1101
1102 fn process_decls(st: &mut SymbolTable, decls: &[SpannedDecl]) -> Result<(), SemaError> {
1103 // Collect AccessList entries — they must be applied AFTER all TypeDecls
1104 // because the list may reference symbols declared later in the module.
1105 let mut pending_access: Vec<(Access, Vec<String>)> = Vec::new();
1106 for decl in decls {
1107 match &decl.node {
1108 Decl::AccessDefault { access } => match access {
1109 Attribute::Private => st.set_default_access(Access::Private),
1110 Attribute::Public => st.set_default_access(Access::Public),
1111 _ => {}
1112 },
1113 Decl::AccessList { access, names } => {
1114 let acc = match access {
1115 Attribute::Private => Access::Private,
1116 Attribute::Public => Access::Public,
1117 _ => continue,
1118 };
1119 pending_access.push((acc, names.clone()));
1120 }
1121 Decl::TypeDecl {
1122 type_spec,
1123 attrs,
1124 entities,
1125 } => {
1126 let mut type_info = type_spec_to_info(type_spec, st);
1127 let mut sym_attrs =
1128 attrs_to_symbol_attrs(attrs, st.default_access(st.current_scope()));
1129 let mut kind = if sym_attrs.parameter {
1130 SymbolKind::Parameter
1131 } else {
1132 SymbolKind::Variable
1133 };
1134 let mut arg_names = Vec::new();
1135
1136 if sym_attrs.pointer && sym_attrs.external {
1137 kind = SymbolKind::ProcedurePointer;
1138 if let TypeSpec::Type(iface_name) = type_spec {
1139 sym_attrs.procedure_iface = Some(iface_name.clone());
1140 if let Some(iface_sym) =
1141 st.find_symbol_any_scope(&iface_name.to_lowercase())
1142 {
1143 type_info = iface_sym
1144 .type_info
1145 .clone()
1146 .unwrap_or_else(|| type_info.clone());
1147 arg_names = iface_sym.arg_names.clone();
1148 }
1149 }
1150 }
1151
1152 for entity in entities {
1153 let key = entity.name.to_lowercase();
1154 if st.scope(st.current_scope()).symbols.contains_key(&key) {
1155 // Symbol already exists (e.g., dummy argument) — update type info.
1156 let sym = st
1157 .scope_mut(st.current_scope())
1158 .symbols
1159 .get_mut(&key)
1160 .unwrap();
1161 sym.kind = kind.clone();
1162 sym.type_info = Some(type_info.clone());
1163 sym.attrs = sym_attrs.clone();
1164 sym.arg_names = arg_names.clone();
1165 } else {
1166 // Try to fold PARAMETER initializers to a
1167 // compile-time integer so .amod can carry the
1168 // value and consumers can inline it.
1169 let const_value = if sym_attrs.parameter {
1170 entity
1171 .init
1172 .as_ref()
1173 .and_then(|e| eval_const_int_expr(e, st))
1174 } else {
1175 None
1176 };
1177 st.define(Symbol {
1178 name: entity.name.clone(),
1179 kind: kind.clone(),
1180 type_info: Some(type_info.clone()),
1181 attrs: sym_attrs.clone(),
1182 defined_at: decl.span,
1183 scope: st.current_scope(),
1184 arg_names: arg_names.clone(),
1185 const_value,
1186 })?;
1187 }
1188 }
1189 }
1190 Decl::DerivedTypeDef { name, attrs, .. } => {
1191 let mut sym_attrs = SymbolAttrs {
1192 access: st.default_access(st.current_scope()),
1193 ..SymbolAttrs::default()
1194 };
1195 for attr in attrs {
1196 match attr {
1197 decl::TypeAttr::Public => sym_attrs.access = Access::Public,
1198 decl::TypeAttr::Private => sym_attrs.access = Access::Private,
1199 _ => {}
1200 }
1201 }
1202 st.define(Symbol {
1203 name: name.clone(),
1204 kind: SymbolKind::DerivedType,
1205 type_info: None,
1206 attrs: sym_attrs,
1207 defined_at: decl.span,
1208 scope: st.current_scope(),
1209 arg_names: vec![],
1210 const_value: None,
1211 })?;
1212 }
1213 _ => {}
1214 }
1215 }
1216 // Apply deferred access-list overrides after all symbols are declared.
1217 for (access, names) in &pending_access {
1218 for name in names {
1219 st.set_symbol_access(name, *access);
1220 }
1221 }
1222 Ok(())
1223 }
1224
1225 fn process_contains(
1226 st: &mut SymbolTable,
1227 contains: &[SpannedUnit],
1228 module_search_paths: &[std::path::PathBuf],
1229 layouts: &mut super::type_layout::TypeLayoutRegistry,
1230 ) -> Result<(), SemaError> {
1231 for unit in contains {
1232 // Register the subprogram name in the current scope before descending.
1233 match &unit.node {
1234 ProgramUnit::Subroutine {
1235 name, prefix, bind, ..
1236 } => {
1237 let elemental = prefix
1238 .iter()
1239 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
1240 let pure = elemental
1241 || prefix
1242 .iter()
1243 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
1244 let attrs = SymbolAttrs {
1245 pure,
1246 elemental,
1247 binding_label: normalized_bind_name(bind.as_ref()),
1248 ..Default::default()
1249 };
1250 let _ignore_dup = st.define(Symbol {
1251 name: name.clone(),
1252 kind: SymbolKind::Subroutine,
1253 type_info: None,
1254 attrs,
1255 defined_at: unit.span,
1256 scope: st.current_scope(),
1257 arg_names: vec![],
1258 const_value: None,
1259 });
1260 }
1261 ProgramUnit::Function {
1262 name,
1263 return_type,
1264 result,
1265 decls,
1266 prefix,
1267 bind,
1268 ..
1269 } => {
1270 let ret_type_info = return_type
1271 .as_ref()
1272 .map(|ts| type_spec_to_info(ts, st))
1273 .or_else(|| {
1274 // Infer return type from result variable's declaration.
1275 let result_name = result.as_deref().unwrap_or(name.as_str());
1276 let key = result_name.to_lowercase();
1277 for d in decls {
1278 if let decl::Decl::TypeDecl {
1279 type_spec,
1280 entities,
1281 ..
1282 } = &d.node
1283 {
1284 for e in entities {
1285 if e.name.to_lowercase() == key {
1286 return Some(type_spec_to_info(type_spec, st));
1287 }
1288 }
1289 }
1290 }
1291 None
1292 });
1293 let fn_elemental = prefix
1294 .iter()
1295 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
1296 let fn_pure = fn_elemental
1297 || prefix
1298 .iter()
1299 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
1300 let result_attrs = function_result_attrs(name, result, decls);
1301 let fn_attrs = SymbolAttrs {
1302 allocatable: result_attrs.allocatable,
1303 pointer: result_attrs.pointer,
1304 pure: fn_pure,
1305 elemental: fn_elemental,
1306 binding_label: normalized_bind_name(bind.as_ref()),
1307 ..Default::default()
1308 };
1309 let _ignore_dup = st.define(Symbol {
1310 name: name.clone(),
1311 kind: SymbolKind::Function,
1312 type_info: ret_type_info,
1313 attrs: fn_attrs,
1314 defined_at: unit.span,
1315 scope: st.current_scope(),
1316 arg_names: vec![],
1317 const_value: None,
1318 });
1319 }
1320 _ => {}
1321 }
1322 resolve_unit(st, unit, module_search_paths, layouts)?;
1323 }
1324 Ok(())
1325 }
1326
1327 // ---- Helpers ----
1328
1329 /// Extract a compile-time integer kind value from a KindSelector.
1330 /// Try to evaluate a PARAMETER initializer to a compile-time i64.
1331 /// Handles integer literals, negation, binary ops, parenthesized
1332 /// expressions, and Name references that resolve to PARAMETERs
1333 /// with known const_value in the current scope chain.
1334 fn eval_const_int_expr(expr: &crate::ast::expr::SpannedExpr, st: &SymbolTable) -> Option<i64> {
1335 use crate::ast::expr::Expr;
1336 match &expr.node {
1337 Expr::IntegerLiteral { text, .. } => {
1338 let clean = text.split('_').next().unwrap_or(text);
1339 clean.parse::<i64>().ok()
1340 }
1341 Expr::Name { name } => {
1342 // Look up the name in the current scope chain.
1343 let sym = st.lookup_in(st.current_scope(), &name.to_lowercase())?;
1344 if sym.attrs.parameter {
1345 sym.const_value
1346 } else {
1347 None
1348 }
1349 }
1350 Expr::UnaryOp { op, operand } => {
1351 let v = eval_const_int_expr(operand, st)?;
1352 match op {
1353 crate::ast::expr::UnaryOp::Minus => Some(-v),
1354 crate::ast::expr::UnaryOp::Plus => Some(v),
1355 _ => None,
1356 }
1357 }
1358 Expr::BinaryOp { op, left, right } => {
1359 let l = eval_const_int_expr(left, st)?;
1360 let r = eval_const_int_expr(right, st)?;
1361 match op {
1362 crate::ast::expr::BinaryOp::Add => Some(l + r),
1363 crate::ast::expr::BinaryOp::Sub => Some(l - r),
1364 crate::ast::expr::BinaryOp::Mul => Some(l * r),
1365 crate::ast::expr::BinaryOp::Div if r != 0 => Some(l / r),
1366 _ => None,
1367 }
1368 }
1369 Expr::ParenExpr { inner } => eval_const_int_expr(inner, st),
1370 Expr::FunctionCall { callee, args } => {
1371 if let Expr::Name { name } = &callee.node {
1372 let key = name.to_lowercase();
1373 let first_arg_val = args.first().and_then(|a| {
1374 if let crate::ast::expr::SectionSubscript::Element(e) = &a.value {
1375 eval_const_int_expr(e, st)
1376 } else {
1377 None
1378 }
1379 });
1380 match key.as_str() {
1381 "selected_int_kind" => {
1382 let r = first_arg_val?;
1383 Some(if r <= 2 {
1384 1
1385 } else if r <= 4 {
1386 2
1387 } else if r <= 9 {
1388 4
1389 } else if r <= 18 {
1390 8
1391 } else if r <= 38 {
1392 16
1393 } else {
1394 -1
1395 })
1396 }
1397 "selected_real_kind" => {
1398 let p = first_arg_val?;
1399 Some(if p <= 6 {
1400 4
1401 } else if p <= 15 {
1402 8
1403 } else {
1404 -1
1405 })
1406 }
1407 "kind" => {
1408 if let Some(arg) = args.first() {
1409 if let crate::ast::expr::SectionSubscript::Element(e) = &arg.value {
1410 match &e.node {
1411 Expr::RealLiteral { text, .. } => {
1412 Some(if text.contains('d') || text.contains('D') {
1413 8
1414 } else {
1415 4
1416 })
1417 }
1418 Expr::IntegerLiteral { .. } => Some(4),
1419 _ => None,
1420 }
1421 } else {
1422 None
1423 }
1424 } else {
1425 None
1426 }
1427 }
1428 _ => None,
1429 }
1430 } else {
1431 None
1432 }
1433 }
1434 _ => None,
1435 }
1436 }
1437
1438 fn extract_kind(sel: &Option<decl::KindSelector>, st: &SymbolTable) -> Option<u8> {
1439 use crate::ast::expr::Expr;
1440 match sel {
1441 Some(decl::KindSelector::Expr(e)) | Some(decl::KindSelector::Star(e)) => {
1442 match &e.node {
1443 Expr::IntegerLiteral { text, .. } => text.parse().ok(),
1444 Expr::Name { name } => {
1445 // Resolve named constant (e.g., c_double, real64, int64).
1446 let key = name.to_lowercase();
1447 st.lookup(&key)
1448 .and_then(|sym| sym.const_value.map(|v| v as u8))
1449 }
1450 _ => None,
1451 }
1452 }
1453 None => None,
1454 }
1455 }
1456
1457 /// Extract character length from a CharSelector.
1458 fn extract_char_len(sel: &Option<decl::CharSelector>, st: &SymbolTable) -> Option<i64> {
1459 match sel {
1460 Some(cs) => match &cs.len {
1461 Some(decl::LenSpec::Expr(e)) => eval_const_int_expr(e, st),
1462 Some(decl::LenSpec::Star) => None, // assumed length
1463 Some(decl::LenSpec::Colon) => None, // deferred length
1464 None => None,
1465 },
1466 None => None,
1467 }
1468 }
1469
1470 fn type_spec_to_info(ts: &TypeSpec, st: &SymbolTable) -> TypeInfo {
1471 match ts {
1472 TypeSpec::Integer(sel) => TypeInfo::Integer {
1473 kind: extract_kind(sel, st),
1474 },
1475 TypeSpec::Real(sel) => TypeInfo::Real {
1476 kind: extract_kind(sel, st),
1477 },
1478 TypeSpec::DoublePrecision => TypeInfo::DoublePrecision,
1479 TypeSpec::Complex(sel) => TypeInfo::Complex {
1480 kind: extract_kind(sel, st),
1481 },
1482 TypeSpec::DoubleComplex => TypeInfo::Complex { kind: Some(8) },
1483 TypeSpec::Logical(sel) => TypeInfo::Logical {
1484 kind: extract_kind(sel, st),
1485 },
1486 TypeSpec::Character(sel) => TypeInfo::Character {
1487 len: extract_char_len(sel, st),
1488 kind: None,
1489 },
1490 TypeSpec::Type(name) => TypeInfo::Derived(name.clone()),
1491 TypeSpec::Class(name) => TypeInfo::Class(name.clone()),
1492 TypeSpec::ClassStar => TypeInfo::ClassStar,
1493 TypeSpec::TypeStar => TypeInfo::TypeStar,
1494 }
1495 }
1496
1497 fn attrs_to_symbol_attrs(attrs: &[Attribute], default_access: Access) -> SymbolAttrs {
1498 let mut sa = SymbolAttrs {
1499 access: default_access,
1500 ..SymbolAttrs::default()
1501 };
1502 for attr in attrs {
1503 match attr {
1504 Attribute::Allocatable => sa.allocatable = true,
1505 Attribute::Pointer => sa.pointer = true,
1506 Attribute::Target => sa.target = true,
1507 Attribute::Optional => sa.optional = true,
1508 Attribute::Save => sa.save = true,
1509 Attribute::Parameter => sa.parameter = true,
1510 Attribute::Value => sa.value = true,
1511 Attribute::External => sa.external = true,
1512 Attribute::Intrinsic => sa.intrinsic = true,
1513 Attribute::Public => sa.access = Access::Public,
1514 Attribute::Private => sa.access = Access::Private,
1515 Attribute::Intent(intent) => {
1516 sa.intent = Some(match intent {
1517 decl::Intent::In => Intent::In,
1518 decl::Intent::Out => Intent::Out,
1519 decl::Intent::InOut => Intent::InOut,
1520 });
1521 }
1522 _ => {}
1523 }
1524 }
1525 sa
1526 }
1527
1528 fn function_result_attrs(
1529 function_name: &str,
1530 result: &Option<String>,
1531 decls: &[crate::ast::decl::SpannedDecl],
1532 ) -> SymbolAttrs {
1533 let result_key = result
1534 .as_deref()
1535 .unwrap_or(function_name)
1536 .to_ascii_lowercase();
1537 for decl in decls {
1538 let crate::ast::decl::Decl::TypeDecl {
1539 attrs, entities, ..
1540 } = &decl.node
1541 else {
1542 continue;
1543 };
1544 if entities
1545 .iter()
1546 .any(|entity| entity.name.eq_ignore_ascii_case(&result_key))
1547 {
1548 return attrs_to_symbol_attrs(attrs, Access::Default);
1549 }
1550 }
1551 SymbolAttrs::default()
1552 }
1553
1554 #[cfg(test)]
1555 mod tests {
1556 use super::*;
1557 use crate::lexer::Lexer;
1558 use crate::parser::Parser;
1559
1560 fn resolve_source(src: &str) -> SymbolTable {
1561 let tokens = Lexer::tokenize(src, 0).unwrap();
1562 let mut parser = Parser::new(&tokens);
1563 let units = parser.parse_file().unwrap();
1564 resolve_file(&units, &[]).unwrap().st
1565 }
1566
1567 // ---- Integration tests ----
1568
1569 #[test]
1570 fn simple_program_declarations() {
1571 let st = resolve_source(
1572 "program test\n implicit none\n integer :: x, y\n real :: z\nend program\n",
1573 );
1574 // Should have x, y, z defined.
1575 // Navigate to the program scope.
1576 let prog_scope = st
1577 .scopes
1578 .iter()
1579 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
1580 .unwrap();
1581 assert!(prog_scope.symbols.contains_key("x"));
1582 assert!(prog_scope.symbols.contains_key("y"));
1583 assert!(prog_scope.symbols.contains_key("z"));
1584 }
1585
1586 #[test]
1587 fn implicit_none_enforced() {
1588 let st = resolve_source("program test\n implicit none\n integer :: x\nend program\n");
1589 let prog_scope = st
1590 .scopes
1591 .iter()
1592 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
1593 .unwrap();
1594 assert!(prog_scope.implicit_rules.none_type);
1595 }
1596
1597 #[test]
1598 fn module_use_association() {
1599 let st = resolve_source(
1600 "\
1601 module mymod
1602 implicit none
1603 integer :: shared_var
1604 end module
1605
1606 program main
1607 use mymod
1608 implicit none
1609 end program
1610 ",
1611 );
1612 // shared_var should be in the module scope.
1613 let mod_scope = st
1614 .scopes
1615 .iter()
1616 .find(|s| matches!(s.kind, ScopeKind::Module(ref n) if n == "mymod"))
1617 .unwrap();
1618 assert!(mod_scope.symbols.contains_key("shared_var"));
1619
1620 // The program should have a USE association for shared_var.
1621 let prog_scope = st
1622 .scopes
1623 .iter()
1624 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
1625 .unwrap();
1626 assert!(!prog_scope.use_associations.is_empty());
1627 }
1628
1629 #[test]
1630 fn subroutine_with_args() {
1631 let st = resolve_source("subroutine foo(x, y)\n real :: x, y\nend subroutine\n");
1632 let sub_scope = st
1633 .scopes
1634 .iter()
1635 .find(|s| matches!(s.kind, ScopeKind::Subroutine(ref n) if n == "foo"))
1636 .unwrap();
1637 assert!(sub_scope.symbols.contains_key("x"));
1638 assert!(sub_scope.symbols.contains_key("y"));
1639 }
1640
1641 #[test]
1642 fn function_result_variable() {
1643 let st = resolve_source(
1644 "function square(x) result(y)\n real :: x, y\n y = x * x\nend function\n",
1645 );
1646 let fn_scope = st
1647 .scopes
1648 .iter()
1649 .find(|s| matches!(s.kind, ScopeKind::Function(ref n) if n == "square"))
1650 .unwrap();
1651 assert!(fn_scope.symbols.contains_key("x"));
1652 assert!(fn_scope.symbols.contains_key("y"));
1653 }
1654
1655 #[test]
1656 fn contains_creates_child_scope() {
1657 let st = resolve_source(
1658 "\
1659 program main
1660 implicit none
1661 integer :: x
1662 contains
1663 subroutine inner()
1664 integer :: local_var
1665 end subroutine
1666 end program
1667 ",
1668 );
1669 // inner should be its own scope.
1670 let inner_scope = st
1671 .scopes
1672 .iter()
1673 .find(|s| matches!(s.kind, ScopeKind::Subroutine(ref n) if n == "inner"))
1674 .unwrap();
1675 assert!(inner_scope.symbols.contains_key("local_var"));
1676
1677 // inner should be registered as a symbol in the program scope.
1678 let prog_scope = st
1679 .scopes
1680 .iter()
1681 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
1682 .unwrap();
1683 assert!(prog_scope.symbols.contains_key("inner"));
1684 }
1685
1686 #[test]
1687 fn derived_type_defined() {
1688 let st = resolve_source(
1689 "module m\n type :: mytype\n integer :: field\n end type\nend module\n",
1690 );
1691 let mod_scope = st
1692 .scopes
1693 .iter()
1694 .find(|s| matches!(&s.kind, ScopeKind::Module(n) if n == "m"))
1695 .unwrap();
1696 assert!(mod_scope.symbols.contains_key("mytype"));
1697 assert_eq!(mod_scope.symbols["mytype"].kind, SymbolKind::DerivedType);
1698 }
1699 }
1700