| 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, ¶ms) { |
| 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 |