Rust · 83230 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 crate::sema::symtab::*;
7 use crate::ast::decl;
8 use crate::ast::decl::{Attribute, Decl, SpannedDecl, TypeSpec};
9 use crate::ast::unit::*;
10 use std::cell::RefCell;
11 use std::collections::{HashMap, HashSet};
12
13 use super::statement_functions::detect_statement_functions;
14 use super::type_resolution::{derived_char_init_len, type_spec_to_info};
15 use super::use_resolution::{load_external_module, preload_stmt_uses, process_uses};
16
17 thread_local! {
18 /// Track externally loaded module interfaces so resolve_file can
19 /// return them to the driver for globals extraction.
20 pub(super) static LOADED_EXTERNAL_MODULES: RefCell<Vec<crate::sema::amod::ModuleInterface>> = const { RefCell::new(Vec::new()) };
21 }
22
23 pub(super) fn merge_specific_names(into: &mut Vec<String>, additional: &[String]) {
24 let mut seen: HashSet<String> = into.iter().map(|name| name.to_ascii_lowercase()).collect();
25 for name in additional {
26 let key = name.to_ascii_lowercase();
27 if seen.insert(key) {
28 into.push(name.clone());
29 }
30 }
31 }
32
33 fn merged_visible_generic_specifics(
34 st: &SymbolTable,
35 scope_id: ScopeId,
36 generic_name: &str,
37 local_specifics: &[String],
38 ) -> Vec<String> {
39 let mut merged = Vec::new();
40 if let Some(existing) = st.lookup_in(scope_id, generic_name) {
41 if existing.kind == SymbolKind::NamedInterface
42 || (existing.kind == SymbolKind::DerivedType && !existing.arg_names.is_empty())
43 {
44 merge_specific_names(&mut merged, &existing.arg_names);
45 }
46 }
47 merge_specific_names(&mut merged, local_specifics);
48 merged
49 }
50
51 /// Walk a list of program units and build the symbol table.
52 /// Result of resolving a file: symbol table, type layouts, and any
53 /// external module interfaces loaded from .amod files during USE
54 /// resolution.
55 pub struct ResolveResult {
56 pub st: SymbolTable,
57 pub type_layouts: crate::sema::type_layout::TypeLayoutRegistry,
58 pub external_modules: Vec<crate::sema::amod::ModuleInterface>,
59 }
60
61 pub fn resolve_file(
62 units: &[SpannedUnit],
63 module_search_paths: &[std::path::PathBuf],
64 ) -> Result<ResolveResult, SemaError> {
65 let mut st = SymbolTable::new();
66 let mut layouts = crate::sema::type_layout::TypeLayoutRegistry::new();
67
68 // Register intrinsic modules (iso_c_binding, iso_fortran_env) so USE can find them.
69 crate::sema::intrinsic_modules::register_intrinsic_modules(&mut st);
70
71 // First pass: create module scopes so USE can find them.
72 for unit in units {
73 if let ProgramUnit::Module { name, .. } = &unit.node {
74 st.push_scope(ScopeKind::Module(name.clone()));
75 st.pop_scope();
76 }
77 }
78
79 // Second pass: populate all scopes (loads .amod files lazily on USE miss).
80 // Track which external modules were loaded.
81 LOADED_EXTERNAL_MODULES.with(|cell| cell.borrow_mut().clear());
82 for unit in units {
83 resolve_unit(&mut st, unit, module_search_paths, &mut layouts)?;
84 }
85 let external_modules = LOADED_EXTERNAL_MODULES.with(|cell| {
86 let v = cell.borrow();
87 v.iter().cloned().collect::<Vec<_>>()
88 });
89
90 // Third pass: compute layouts for all derived types.
91 compute_all_layouts(units, &st, &mut layouts);
92
93 Ok(ResolveResult {
94 st,
95 type_layouts: layouts,
96 external_modules,
97 })
98 }
99
100 pub(super) fn backfill_procedure_pointer_interfaces(st: &mut SymbolTable, scope_id: ScopeId) {
101 let updates: Vec<(String, Option<TypeInfo>, Vec<String>)> = st
102 .scope(scope_id)
103 .symbols
104 .iter()
105 .filter_map(|(key, sym)| {
106 if sym.kind != SymbolKind::ProcedurePointer {
107 return None;
108 }
109 let TypeInfo::Derived(iface_name) = sym.type_info.as_ref()? else {
110 return None;
111 };
112 let iface_sym = st.find_symbol_any_scope(&iface_name.to_lowercase())?;
113 Some((
114 key.clone(),
115 iface_sym.type_info.clone(),
116 iface_sym.arg_names.clone(),
117 ))
118 })
119 .collect();
120
121 for (key, type_info, arg_names) in updates {
122 if let Some(sym) = st.scope_mut(scope_id).symbols.get_mut(&key) {
123 if let Some(type_info) = type_info {
124 sym.type_info = Some(type_info);
125 }
126 sym.arg_names = arg_names;
127 }
128 }
129 }
130
131 fn backfill_function_result_type(
132 st: &mut SymbolTable,
133 host_scope: ScopeId,
134 function_scope: ScopeId,
135 function_name: &str,
136 result_name: &str,
137 ) {
138 let result_key = result_name.to_ascii_lowercase();
139 let (type_info, pointer, allocatable) = match st.scope(function_scope).symbols.get(&result_key)
140 {
141 Some(sym) => (sym.type_info.clone(), sym.attrs.pointer, sym.attrs.allocatable),
142 None => return,
143 };
144 let Some(type_info) = type_info else {
145 return;
146 };
147 let function_key = function_name.to_ascii_lowercase();
148 if let Some(sym) = st.scope_mut(host_scope).symbols.get_mut(&function_key) {
149 sym.type_info = Some(type_info);
150 if pointer {
151 sym.attrs.pointer = true;
152 }
153 if allocatable {
154 sym.attrs.allocatable = true;
155 }
156 }
157 }
158
159 fn normalized_bind_name(
160 bind: Option<&crate::ast::unit::BindInfo>,
161 default_name: &str,
162 ) -> Option<String> {
163 bind.map(|info| {
164 info.name
165 .as_deref()
166 .unwrap_or(default_name)
167 .trim_matches('\'')
168 .trim_matches('"')
169 .to_string()
170 })
171 }
172
173 type InterfaceOuterRef = (
174 String,
175 SymbolKind,
176 Option<TypeInfo>,
177 Vec<String>,
178 Option<String>,
179 bool, // pure
180 bool, // elemental
181 u8, // result_rank
182 );
183
184 pub(super) fn resolve_unit(
185 st: &mut SymbolTable,
186 unit: &SpannedUnit,
187 module_search_paths: &[std::path::PathBuf],
188 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
189 ) -> Result<(), SemaError> {
190 match &unit.node {
191 ProgramUnit::Program {
192 name,
193 uses,
194 imports: _,
195 implicit,
196 decls,
197 body,
198 contains,
199 } => {
200 let scope_name = name.clone().unwrap_or_else(|| "<main>".into());
201 st.push_scope(ScopeKind::Program(scope_name));
202 let scope_id = st.current_scope();
203 process_uses(st, uses, module_search_paths, layouts)?;
204 process_implicit(st, implicit)?;
205 process_decls(st, decls)?;
206 detect_statement_functions(st, scope_id, body);
207 preload_stmt_uses(st, body, module_search_paths, layouts);
208 process_contains(st, contains, module_search_paths, layouts)?;
209 backfill_procedure_pointer_interfaces(st, scope_id);
210 st.pop_scope();
211 }
212 ProgramUnit::Module {
213 name,
214 uses,
215 imports: _,
216 implicit,
217 decls,
218 contains,
219 } => {
220 // Find the pre-created module scope and enter it.
221 if let Some(mod_id) = st.find_module_scope(name) {
222 let saved = st.enter_scope(mod_id);
223
224 process_uses(st, uses, module_search_paths, layouts)?;
225 process_implicit(st, implicit)?;
226 process_decls(st, decls)?;
227 process_contains(st, contains, module_search_paths, layouts)?;
228 backfill_procedure_pointer_interfaces(st, mod_id);
229
230 st.enter_scope(saved);
231 }
232 }
233 ProgramUnit::Subroutine {
234 name,
235 args,
236 prefix,
237 bind: _,
238 uses,
239 imports: _,
240 implicit,
241 decls,
242 body,
243 contains,
244 } => {
245 let scope_id = st.push_scope(ScopeKind::Subroutine(name.clone()));
246
247 // F2008 §12.6.2.5: separate module procedure body — args
248 // are inherited from the parent module's interface block.
249 // The parser emits args=[] and prefix=[Module]; sema
250 // injects the args from the parent module's procedure
251 // scope (populated during interface resolution / .amod
252 // load).
253 let is_separate_body = args.is_empty()
254 && prefix.iter().any(|p| matches!(p, Prefix::Module))
255 && matches!(
256 st.scope(st.scope(scope_id).parent.unwrap_or(0)).kind,
257 ScopeKind::Submodule(_)
258 );
259 if is_separate_body {
260 inject_separate_module_procedure_args(st, name, scope_id, unit.span);
261 } else {
262 // Store ordered arg names for VALUE lookup by callers.
263 st.scope_mut(scope_id).arg_order = args
264 .iter()
265 .filter_map(|a| {
266 if let DummyArg::Name(n) = a {
267 Some(n.to_lowercase())
268 } else {
269 None
270 }
271 })
272 .collect();
273 // Define dummy arguments as symbols.
274 for arg in args {
275 if let DummyArg::Name(arg_name) = arg {
276 st.define(Symbol {
277 name: arg_name.clone(),
278 kind: SymbolKind::Variable,
279 type_info: None,
280 attrs: SymbolAttrs::default(),
281 defined_at: unit.span,
282 scope: st.current_scope(),
283 arg_names: vec![],
284 const_value: None,
285 })?;
286 }
287 }
288 }
289 process_uses(st, uses, module_search_paths, layouts)?;
290 process_implicit(st, implicit)?;
291 process_decls(st, decls)?;
292 detect_statement_functions(st, scope_id, body);
293 preload_stmt_uses(st, body, module_search_paths, layouts);
294 process_contains(st, contains, module_search_paths, layouts)?;
295 backfill_procedure_pointer_interfaces(st, scope_id);
296 st.pop_scope();
297 }
298 ProgramUnit::Function {
299 name,
300 args,
301 result,
302 return_type,
303 bind: _,
304 prefix: _,
305 uses,
306 imports: _,
307 implicit,
308 decls,
309 body,
310 contains,
311 } => {
312 let host_scope = st.current_scope();
313 let scope_id = st.push_scope(ScopeKind::Function(name.clone()));
314 st.scope_mut(scope_id).arg_order = args
315 .iter()
316 .filter_map(|a| {
317 if let DummyArg::Name(n) = a {
318 Some(n.to_lowercase())
319 } else {
320 None
321 }
322 })
323 .collect();
324 for arg in args {
325 if let DummyArg::Name(arg_name) = arg {
326 st.define(Symbol {
327 name: arg_name.clone(),
328 kind: SymbolKind::Variable,
329 type_info: None,
330 attrs: SymbolAttrs::default(),
331 defined_at: unit.span,
332 scope: st.current_scope(),
333 arg_names: vec![],
334 const_value: None,
335 })?;
336 }
337 }
338 // Define result variable.
339 let result_name = result.as_deref().unwrap_or(name.as_str());
340 st.define(Symbol {
341 name: result_name.into(),
342 kind: SymbolKind::Variable,
343 type_info: return_type.as_ref().map(|ts| type_spec_to_info(ts, st)),
344 attrs: SymbolAttrs::default(),
345 defined_at: unit.span,
346 scope: st.current_scope(),
347 arg_names: vec![],
348 const_value: None,
349 })?;
350 process_uses(st, uses, module_search_paths, layouts)?;
351 process_implicit(st, implicit)?;
352 process_decls(st, decls)?;
353 backfill_function_result_type(
354 st,
355 host_scope,
356 scope_id,
357 name,
358 result.as_deref().unwrap_or(name.as_str()),
359 );
360 detect_statement_functions(st, scope_id, body);
361 preload_stmt_uses(st, body, module_search_paths, layouts);
362 process_contains(st, contains, module_search_paths, layouts)?;
363 backfill_procedure_pointer_interfaces(st, scope_id);
364 st.pop_scope();
365 }
366 ProgramUnit::BlockData { name, uses, decls } => {
367 let scope_name = name.clone().unwrap_or_else(|| "<block_data>".into());
368 st.push_scope(ScopeKind::Program(scope_name));
369 process_uses(st, uses, module_search_paths, layouts)?;
370 process_decls(st, decls)?;
371 st.pop_scope();
372 }
373 ProgramUnit::Submodule {
374 parent,
375 ancestor: _,
376 name,
377 uses,
378 decls,
379 contains,
380 } => {
381 // Find the parent module scope and inherit its symbols.
382 // If the parent isn't compiled in this TU, load it from .amod.
383 let parent_scope = st
384 .find_module_scope(parent)
385 .or_else(|| load_external_module(st, parent, module_search_paths, layouts));
386 st.push_scope(ScopeKind::Submodule(name.clone()));
387 // Import all parent module symbols into the submodule scope.
388 // Per F2008 12.2.3.2: submodules see ALL parent entities,
389 // including private ones — that's the whole point of the
390 // submodule mechanism (host association).
391 if let Some(pid) = parent_scope {
392 let parent_syms: Vec<(String, String)> = st
393 .scope(pid)
394 .symbols
395 .iter()
396 .map(|(key, sym)| (sym.name.clone(), key.clone()))
397 .collect();
398 for (sym_name, _key) in &parent_syms {
399 st.add_use_association(UseAssociation {
400 local_name: sym_name.clone(),
401 original_name: sym_name.clone(),
402 source_scope: pid,
403 is_submodule_access: true,
404 from_bare_use: true,
405 });
406 }
407 }
408 process_uses(st, uses, module_search_paths, layouts)?;
409 process_decls(st, decls)?;
410 process_contains(st, contains, module_search_paths, layouts)?;
411 let scope_id = st.current_scope();
412 backfill_procedure_pointer_interfaces(st, scope_id);
413 st.pop_scope();
414 }
415 ProgramUnit::InterfaceBlock {
416 name,
417 is_abstract: _,
418 bodies,
419 } => {
420 // Collect each subprogram's name and return type BEFORE
421 // pushing the Interface scope — the subprogram body gets
422 // its own scope via resolve_unit, and we need to surface
423 // the *declared* callable back into the enclosing scope
424 // (otherwise IMPLICIT NONE rejects the call at the use
425 // site, and generic dispatch can't see the body types).
426 let mut outer_refs: Vec<InterfaceOuterRef> = Vec::new();
427 for body in bodies {
428 if let InterfaceBody::Subprogram(sub) = body {
429 match &sub.node {
430 ProgramUnit::Function {
431 name: fn_name,
432 return_type,
433 result,
434 decls,
435 args,
436 bind,
437 prefix,
438 ..
439 } => {
440 let arg_names = args
441 .iter()
442 .filter_map(|a| {
443 if let DummyArg::Name(n) = a {
444 Some(n.clone())
445 } else {
446 None
447 }
448 })
449 .collect();
450 let ti = return_type
451 .as_ref()
452 .map(|ts| type_spec_to_info(ts, st))
453 .or_else(|| {
454 let result_name = result.as_deref().unwrap_or(fn_name.as_str());
455 let key = result_name.to_lowercase();
456 for d in decls {
457 if let decl::Decl::TypeDecl {
458 type_spec,
459 entities,
460 ..
461 } = &d.node
462 {
463 for e in entities {
464 if e.name.to_lowercase() == key {
465 return Some(type_spec_to_info(type_spec, st));
466 }
467 }
468 }
469 }
470 None
471 });
472 let elemental = prefix
473 .iter()
474 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
475 let pure = elemental
476 || prefix
477 .iter()
478 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
479 // Capture result rank from the function's
480 // own decls (interface-block bodies declare
481 // the result variable here).
482 let result_attrs_for_iface =
483 function_result_attrs(fn_name, result, decls);
484 outer_refs.push((
485 fn_name.clone(),
486 SymbolKind::Function,
487 ti,
488 arg_names,
489 normalized_bind_name(bind.as_ref(), fn_name),
490 pure,
491 elemental,
492 result_attrs_for_iface.result_rank,
493 ));
494 }
495 ProgramUnit::Subroutine {
496 name: fn_name,
497 args,
498 bind,
499 prefix,
500 ..
501 } => {
502 let arg_names = args
503 .iter()
504 .filter_map(|a| {
505 if let DummyArg::Name(n) = a {
506 Some(n.clone())
507 } else {
508 None
509 }
510 })
511 .collect();
512 let elemental = prefix
513 .iter()
514 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
515 let pure = elemental
516 || prefix
517 .iter()
518 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
519 outer_refs.push((
520 fn_name.clone(),
521 SymbolKind::Subroutine,
522 None,
523 arg_names,
524 normalized_bind_name(bind.as_ref(), fn_name),
525 pure,
526 elemental,
527 0,
528 ));
529 }
530 _ => {}
531 }
532 }
533 }
534
535 st.push_scope(ScopeKind::Interface);
536 let mut specific_names = Vec::new();
537 for body in bodies {
538 match body {
539 InterfaceBody::Subprogram(sub) => {
540 match &sub.node {
541 ProgramUnit::Function { name: fn_name, .. }
542 | ProgramUnit::Subroutine { name: fn_name, .. } => {
543 specific_names.push(fn_name.to_lowercase());
544 }
545 _ => {}
546 }
547 resolve_unit(st, sub, module_search_paths, layouts)?;
548 }
549 InterfaceBody::ModuleProcedure(names) => {
550 for n in names {
551 specific_names.push(n.to_lowercase());
552 }
553 }
554 }
555 }
556 st.pop_scope();
557
558 // Surface each declared procedure to the enclosing scope
559 // so callers under IMPLICIT NONE can resolve the name,
560 // and so BIND(C) external prototypes are callable.
561 for (
562 fn_name,
563 kind,
564 ti,
565 arg_names,
566 binding_label,
567 pure,
568 elemental,
569 result_rank,
570 ) in outer_refs
571 {
572 let span = unit.span;
573 let _ = st.define(Symbol {
574 name: fn_name,
575 kind,
576 type_info: ti,
577 attrs: SymbolAttrs {
578 external: true,
579 binding_label,
580 pure,
581 elemental,
582 result_rank,
583 ..Default::default()
584 },
585 defined_at: span,
586 scope: st.current_scope(),
587 arg_names,
588 const_value: None,
589 });
590 }
591
592 // Register the generic interface name in the enclosing scope.
593 if let Some(generic_name) = name {
594 if !generic_name.is_empty() && !specific_names.is_empty() {
595 let merged_specifics = merged_visible_generic_specifics(
596 st,
597 st.current_scope(),
598 generic_name,
599 &specific_names,
600 );
601 let span = unit.span;
602 let define_result = st.define(Symbol {
603 name: generic_name.clone(),
604 kind: SymbolKind::NamedInterface,
605 type_info: None,
606 attrs: SymbolAttrs {
607 ..Default::default()
608 },
609 defined_at: span,
610 scope: st.current_scope(),
611 arg_names: merged_specifics.clone(),
612 const_value: None,
613 });
614 if define_result.is_err() {
615 let key = generic_name.to_ascii_lowercase();
616 if let Some(existing) =
617 st.scope_mut(st.current_scope()).symbols.get_mut(&key)
618 {
619 if existing.kind == SymbolKind::NamedInterface
620 || existing.kind == SymbolKind::DerivedType
621 {
622 merge_specific_names(&mut existing.arg_names, &merged_specifics);
623 }
624 }
625 }
626 }
627 }
628 }
629 }
630 Ok(())
631 }
632
633
634 /// Walk all program units and compute layouts for derived types.
635 fn compute_all_layouts(
636 units: &[SpannedUnit],
637 st: &SymbolTable,
638 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
639 ) {
640 let inherited_params = HashMap::new();
641 let mut visible_param_cache: HashMap<ScopeId, HashMap<String, i64>> = HashMap::new();
642 let mut exported_param_cache: HashMap<ScopeId, HashMap<String, i64>> = HashMap::new();
643 for unit in units {
644 let scope_id = find_unit_scope(st, 0, &unit.node).unwrap_or(0);
645 collect_derived_type_layouts(
646 &unit.node,
647 scope_id,
648 st,
649 layouts,
650 &inherited_params,
651 &mut visible_param_cache,
652 &mut exported_param_cache,
653 );
654 }
655 }
656
657 fn scope_matches_unit(kind: &ScopeKind, unit: &ProgramUnit) -> bool {
658 match (kind, unit) {
659 (ScopeKind::Program(lhs), ProgramUnit::Program { name, .. }) => name
660 .as_deref()
661 .map(|rhs| lhs.eq_ignore_ascii_case(rhs))
662 .unwrap_or(false),
663 (ScopeKind::Module(lhs), ProgramUnit::Module { name, .. })
664 | (ScopeKind::Submodule(lhs), ProgramUnit::Submodule { name, .. })
665 | (ScopeKind::Subroutine(lhs), ProgramUnit::Subroutine { name, .. })
666 | (ScopeKind::Function(lhs), ProgramUnit::Function { name, .. }) => {
667 lhs.eq_ignore_ascii_case(name)
668 }
669 _ => false,
670 }
671 }
672
673 fn find_unit_scope(st: &SymbolTable, parent_scope: ScopeId, unit: &ProgramUnit) -> Option<ScopeId> {
674 st.all_scopes().iter().find_map(|scope| {
675 if scope.parent != Some(parent_scope) {
676 return None;
677 }
678 if scope_matches_unit(&scope.kind, unit) {
679 Some(scope.id)
680 } else {
681 None
682 }
683 })
684 }
685
686 /// Inject dummy-argument symbols into a separate-module-procedure body
687 /// scope (F2008 §12.6.2.5). The body's parent submodule was already
688 /// linked to its parent module via UseAssociation; we walk that link
689 /// to find the parent module's scope, locate the matching
690 /// `Subroutine(name)` / `Function(name)` child scope (created either
691 /// by the parent module's interface block resolution or by .amod
692 /// loading), and clone its argument symbols into the body scope.
693 fn inject_separate_module_procedure_args(
694 st: &mut SymbolTable,
695 proc_name: &str,
696 body_scope: ScopeId,
697 span: crate::lexer::Span,
698 ) {
699 let submodule_id = match st.scope(body_scope).parent {
700 Some(p) => p,
701 None => return,
702 };
703 let parent_module_scope = st
704 .scope(submodule_id)
705 .use_associations
706 .iter()
707 .find(|u| u.is_submodule_access)
708 .map(|u| u.source_scope);
709 let Some(parent_module_scope) = parent_module_scope else {
710 return;
711 };
712
713 // Find the matching procedure scope inside the parent module.
714 // F2008-style submodules declare the procedure inside an explicit
715 // `interface ... end interface` block at module scope, which adds
716 // an intermediate Interface scope between the module and the
717 // procedure scope. Tolerate one Interface hop.
718 let proc_lc = proc_name.to_lowercase();
719 let iface_scope = st.all_scopes().iter().find_map(|scope| {
720 let direct_parent_matches = scope.parent == Some(parent_module_scope);
721 let via_interface = scope.parent.map(|pid| {
722 matches!(st.scope(pid).kind, ScopeKind::Interface)
723 && st.scope(pid).parent == Some(parent_module_scope)
724 }).unwrap_or(false);
725 if !direct_parent_matches && !via_interface {
726 return None;
727 }
728 match &scope.kind {
729 ScopeKind::Subroutine(n) | ScopeKind::Function(n)
730 if n.eq_ignore_ascii_case(&proc_lc) =>
731 {
732 Some(scope.id)
733 }
734 _ => None,
735 }
736 });
737 let Some(iface_scope) = iface_scope else {
738 return;
739 };
740
741 // Snapshot arg_order and dummy-arg symbols from the interface
742 // scope before mutating the body scope.
743 let arg_order = st.scope(iface_scope).arg_order.clone();
744 let arg_symbols: Vec<Symbol> = arg_order
745 .iter()
746 .filter_map(|n| {
747 st.scope(iface_scope)
748 .symbols
749 .get(n)
750 .cloned()
751 .or_else(|| {
752 st.scope(iface_scope)
753 .symbols
754 .iter()
755 .find(|(_, s)| s.name.eq_ignore_ascii_case(n))
756 .map(|(_, s)| s.clone())
757 })
758 })
759 .collect();
760
761 // Sprint35-SMP Phase 2: also clone the result variable (function
762 // case) so the body's `res = ...` references resolve to a
763 // properly-typed Variable rather than implicit-typing as a scalar.
764 // The result variable is the non-arg Variable in the iface scope:
765 // - For interface bodies parsed from source: sema's Function arm
766 // defined a Symbol with the user's `result(NAME)` clause.
767 // - For .amod-loaded modules: load_external_module synthesized
768 // a `__amod_result_NAME` Variable that we strip the prefix off.
769 let result_sym: Option<Symbol> = {
770 let arg_set: std::collections::HashSet<String> = st
771 .scope(iface_scope)
772 .arg_order
773 .iter()
774 .map(|n| n.to_lowercase())
775 .collect();
776 st.scope(iface_scope)
777 .symbols
778 .iter()
779 .find(|(key, sym)| {
780 !arg_set.contains(*key)
781 && matches!(sym.kind, SymbolKind::Variable | SymbolKind::Parameter)
782 })
783 .map(|(_, sym)| sym.clone())
784 };
785
786 st.scope_mut(body_scope).arg_order = arg_order;
787 for mut sym in arg_symbols {
788 sym.scope = body_scope;
789 sym.defined_at = span;
790 let _ = st.define(sym);
791 }
792 if let Some(mut sym) = result_sym {
793 sym.scope = body_scope;
794 sym.defined_at = span;
795 // For .amod-loaded result vars the name carries the
796 // `__amod_result_` prefix to avoid shadowing user locals in
797 // the parent module's procedure scope. Strip it for the body
798 // scope so user code referencing the result by its declared
799 // name resolves correctly.
800 if let Some(stripped) = sym.name.strip_prefix("__amod_result_") {
801 sym.name = stripped.to_string();
802 }
803 let _ = st.define(sym);
804 }
805 }
806
807 fn exported_const_int_params(
808 st: &SymbolTable,
809 scope_id: ScopeId,
810 _visible_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
811 exported_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
812 ) -> HashMap<String, i64> {
813 if let Some(cached) = exported_cache.get(&scope_id) {
814 return cached.clone();
815 }
816
817 let scope = st.scope(scope_id);
818 let mut out = HashMap::new();
819
820 for (name, sym) in &scope.symbols {
821 if sym.attrs.parameter && sym.attrs.access != Access::Private {
822 if let Some(value) = sym.const_value {
823 out.entry(name.clone()).or_insert(value);
824 }
825 }
826 }
827
828 for assoc in &scope.use_associations {
829 // First try the source scope's own symbols, then chase through
830 // its USE chain. stdlib_kinds re-exports `int32` from
831 // iso_fortran_env, so `use stdlib_kinds, only: bits_kind => int32`
832 // can't find `int32` in stdlib_kinds's own symbol table — it
833 // lives one hop further up. Without the chase, kind selectors
834 // resolve to None and downstream layout falls back to default
835 // kind, which silently shrinks `integer(block_kind) :: blk`
836 // from 8 bytes to 4 inside derived types.
837 let sym = st
838 .scope(assoc.source_scope)
839 .symbols
840 .get(&assoc.original_name)
841 .or_else(|| st.lookup_in(assoc.source_scope, &assoc.original_name));
842 if let Some(sym) = sym {
843 if sym.attrs.parameter
844 && (sym.attrs.access != Access::Private || assoc.is_submodule_access)
845 {
846 if let Some(value) = sym.const_value {
847 out.entry(assoc.local_name.clone()).or_insert(value);
848 }
849 }
850 }
851 }
852
853 let mut seen_use_scopes = HashSet::new();
854 for assoc in &scope.use_associations {
855 if assoc.local_name != assoc.original_name {
856 continue;
857 }
858 if !seen_use_scopes.insert(assoc.source_scope) {
859 continue;
860 }
861 for (name, value) in
862 exported_const_int_params(st, assoc.source_scope, _visible_cache, exported_cache)
863 {
864 out.entry(name).or_insert(value);
865 }
866 }
867
868 exported_cache.insert(scope_id, out.clone());
869 out
870 }
871
872 fn visible_const_int_params(
873 st: &SymbolTable,
874 scope_id: ScopeId,
875 visible_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
876 exported_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
877 ) -> HashMap<String, i64> {
878 if let Some(cached) = visible_cache.get(&scope_id) {
879 return cached.clone();
880 }
881
882 let scope = st.scope(scope_id);
883 let mut out = HashMap::new();
884
885 for (name, sym) in &scope.symbols {
886 if sym.attrs.parameter {
887 if let Some(value) = sym.const_value {
888 out.entry(name.clone()).or_insert(value);
889 }
890 }
891 }
892
893 for assoc in &scope.use_associations {
894 if out.contains_key(&assoc.local_name) {
895 continue;
896 }
897 let sym = st
898 .scope(assoc.source_scope)
899 .symbols
900 .get(&assoc.original_name)
901 .or_else(|| st.lookup_in(assoc.source_scope, &assoc.original_name));
902 if let Some(sym) = sym {
903 if sym.attrs.parameter
904 && (sym.attrs.access != Access::Private || assoc.is_submodule_access)
905 {
906 if let Some(value) = sym.const_value {
907 out.insert(assoc.local_name.clone(), value);
908 }
909 }
910 }
911 }
912
913 let mut seen_use_scopes = HashSet::new();
914 for assoc in &scope.use_associations {
915 if assoc.local_name != assoc.original_name {
916 continue;
917 }
918 if !seen_use_scopes.insert(assoc.source_scope) {
919 continue;
920 }
921 for (name, value) in
922 exported_const_int_params(st, assoc.source_scope, visible_cache, exported_cache)
923 {
924 out.entry(name).or_insert(value);
925 }
926 }
927
928 if let Some(parent) = scope.parent {
929 if st.scope(parent).kind != ScopeKind::Global {
930 for (name, value) in visible_const_int_params(st, parent, visible_cache, exported_cache)
931 {
932 out.entry(name).or_insert(value);
933 }
934 }
935 }
936
937 visible_cache.insert(scope_id, out.clone());
938 out
939 }
940
941 fn collect_derived_type_layouts(
942 unit: &ProgramUnit,
943 scope_id: ScopeId,
944 st: &SymbolTable,
945 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
946 inherited_params: &HashMap<String, i64>,
947 visible_param_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
948 exported_param_cache: &mut HashMap<ScopeId, HashMap<String, i64>>,
949 ) {
950 let (decls, contains) = match unit {
951 ProgramUnit::Program {
952 decls, contains, ..
953 }
954 | ProgramUnit::Module {
955 decls, contains, ..
956 }
957 | ProgramUnit::Submodule {
958 decls, contains, ..
959 }
960 | ProgramUnit::Subroutine {
961 decls, contains, ..
962 }
963 | ProgramUnit::Function {
964 decls, contains, ..
965 } => (decls, contains),
966 _ => return,
967 };
968 let mut seed_params = inherited_params.clone();
969 seed_params.extend(visible_const_int_params(
970 st,
971 scope_id,
972 visible_param_cache,
973 exported_param_cache,
974 ));
975 let host_module = match &st.scope(scope_id).kind {
976 crate::sema::symtab::ScopeKind::Module(module_name)
977 | crate::sema::symtab::ScopeKind::Submodule(module_name) => Some(module_name.as_str()),
978 _ => None,
979 };
980 let const_params = collect_const_int_params(decls, &seed_params);
981 let empty_derived_field_inits = HashMap::new();
982 register_local_type_layouts(
983 decls,
984 host_module,
985 layouts,
986 &const_params,
987 &empty_derived_field_inits,
988 );
989 let const_derived_field_inits =
990 collect_const_derived_field_inits(decls, layouts, &const_params);
991 register_local_type_layouts(
992 decls,
993 host_module,
994 layouts,
995 &const_params,
996 &const_derived_field_inits,
997 );
998 resolve_proc_pointer_default_targets(st, scope_id, layouts);
999 for sub in contains {
1000 let sub_scope_id = find_unit_scope(st, scope_id, &sub.node).unwrap_or(scope_id);
1001 collect_derived_type_layouts(
1002 &sub.node,
1003 sub_scope_id,
1004 st,
1005 layouts,
1006 &const_params,
1007 visible_param_cache,
1008 exported_param_cache,
1009 );
1010 }
1011 }
1012
1013 fn parse_boz_i64(text: &str, base: crate::ast::expr::BozBase) -> Option<i64> {
1014 let radix = match base {
1015 crate::ast::expr::BozBase::Binary => 2,
1016 crate::ast::expr::BozBase::Octal => 8,
1017 crate::ast::expr::BozBase::Hex => 16,
1018 };
1019 let digits: String = text
1020 .chars()
1021 .skip_while(|c| !matches!(c, '\'' | '"'))
1022 .skip(1)
1023 .take_while(|c| !matches!(c, '\'' | '"'))
1024 .collect();
1025 i64::from_str_radix(&digits, radix).ok()
1026 }
1027
1028 fn eval_const_int_expr_with_params(
1029 expr: &crate::ast::expr::SpannedExpr,
1030 const_params: &HashMap<String, i64>,
1031 ) -> Option<i64> {
1032 use crate::ast::expr::Expr;
1033 match &expr.node {
1034 Expr::IntegerLiteral { text, .. } => {
1035 let clean = text.split('_').next().unwrap_or(text);
1036 clean.parse::<i64>().ok()
1037 }
1038 Expr::BozLiteral { text, base } => parse_boz_i64(text, *base),
1039 Expr::Name { name } => const_params.get(&name.to_lowercase()).copied(),
1040 Expr::UnaryOp { op, operand } => {
1041 let v = eval_const_int_expr_with_params(operand, const_params)?;
1042 match op {
1043 crate::ast::expr::UnaryOp::Minus => Some(-v),
1044 crate::ast::expr::UnaryOp::Plus => Some(v),
1045 _ => None,
1046 }
1047 }
1048 Expr::BinaryOp { op, left, right } => {
1049 let l = eval_const_int_expr_with_params(left, const_params)?;
1050 let r = eval_const_int_expr_with_params(right, const_params)?;
1051 match op {
1052 crate::ast::expr::BinaryOp::Add => Some(l + r),
1053 crate::ast::expr::BinaryOp::Sub => Some(l - r),
1054 crate::ast::expr::BinaryOp::Mul => Some(l * r),
1055 crate::ast::expr::BinaryOp::Div if r != 0 => Some(l / r),
1056 _ => None,
1057 }
1058 }
1059 Expr::ParenExpr { inner } => eval_const_int_expr_with_params(inner, const_params),
1060 Expr::FunctionCall { callee, args } => {
1061 let Expr::Name { name } = &callee.node else {
1062 return None;
1063 };
1064 let first_arg_val = args.first().and_then(|a| {
1065 if let crate::ast::expr::SectionSubscript::Element(e) = &a.value {
1066 eval_const_int_expr_with_params(e, const_params)
1067 } else {
1068 None
1069 }
1070 });
1071 match name.to_lowercase().as_str() {
1072 "selected_int_kind" => {
1073 let r = first_arg_val?;
1074 Some(if r <= 2 {
1075 1
1076 } else if r <= 4 {
1077 2
1078 } else if r <= 9 {
1079 4
1080 } else if r <= 18 {
1081 8
1082 } else if r <= 38 {
1083 16
1084 } else {
1085 -1
1086 })
1087 }
1088 "selected_real_kind" => {
1089 let p = first_arg_val?;
1090 Some(if p <= 6 {
1091 4
1092 } else if p <= 15 {
1093 8
1094 } else {
1095 -1
1096 })
1097 }
1098 "kind" => {
1099 let arg = args.first()?;
1100 let crate::ast::expr::SectionSubscript::Element(e) = &arg.value else {
1101 return None;
1102 };
1103 match &e.node {
1104 Expr::RealLiteral { text, .. } => {
1105 Some(if text.contains('d') || text.contains('D') {
1106 8
1107 } else {
1108 4
1109 })
1110 }
1111 Expr::IntegerLiteral { .. } => Some(4),
1112 _ => None,
1113 }
1114 }
1115 "int" => {
1116 let arg = args.first()?;
1117 let crate::ast::expr::SectionSubscript::Element(e) = &arg.value else {
1118 return None;
1119 };
1120 match &e.node {
1121 Expr::RealLiteral { text, .. } => text
1122 .replace('d', "e")
1123 .replace('D', "E")
1124 .split('_')
1125 .next()
1126 .unwrap_or(text)
1127 .parse::<f64>()
1128 .ok()
1129 .map(|v| v.trunc() as i64),
1130 _ => eval_const_int_expr_with_params(e, const_params),
1131 }
1132 }
1133 _ => None,
1134 }
1135 }
1136 _ => None,
1137 }
1138 }
1139
1140 fn collect_const_int_params(
1141 decls: &[SpannedDecl],
1142 inherited_params: &HashMap<String, i64>,
1143 ) -> HashMap<String, i64> {
1144 let mut params = inherited_params.clone();
1145 for decl in decls {
1146 let Decl::TypeDecl {
1147 attrs, entities, ..
1148 } = &decl.node
1149 else {
1150 continue;
1151 };
1152 if !attrs.iter().any(|a| matches!(a, Attribute::Parameter)) {
1153 continue;
1154 }
1155 for entity in entities {
1156 params.remove(&entity.name.to_lowercase());
1157 }
1158 }
1159
1160 let mut changed = true;
1161 while changed {
1162 changed = false;
1163 for decl in decls {
1164 let Decl::TypeDecl {
1165 attrs, entities, ..
1166 } = &decl.node
1167 else {
1168 continue;
1169 };
1170 if !attrs.iter().any(|a| matches!(a, Attribute::Parameter)) {
1171 continue;
1172 }
1173 for entity in entities {
1174 let key = entity.name.to_lowercase();
1175 if params.contains_key(&key) {
1176 continue;
1177 }
1178 let Some(init) = entity.init.as_ref() else {
1179 continue;
1180 };
1181 if let Some(value) = eval_const_int_expr_with_params(init, &params) {
1182 params.insert(key, value);
1183 changed = true;
1184 }
1185 }
1186 }
1187 }
1188
1189 params
1190 }
1191
1192 fn collect_const_derived_field_inits(
1193 decls: &[SpannedDecl],
1194 layouts: &crate::sema::type_layout::TypeLayoutRegistry,
1195 const_params: &HashMap<String, i64>,
1196 ) -> HashMap<String, crate::sema::type_layout::FieldDefaultInit> {
1197 use crate::sema::type_layout::{
1198 derived_param_field_lookup_key, eval_const_field_default_init_for_layout, FieldDefaultInit,
1199 };
1200
1201 let mut field_inits = HashMap::new();
1202
1203 let mut changed = true;
1204 while changed {
1205 changed = false;
1206 for decl in decls {
1207 let Decl::TypeDecl {
1208 type_spec,
1209 attrs,
1210 entities,
1211 } = &decl.node
1212 else {
1213 continue;
1214 };
1215 if !attrs.iter().any(|a| matches!(a, Attribute::Parameter)) {
1216 continue;
1217 }
1218
1219 let type_name = match type_spec {
1220 TypeSpec::Type(name) | TypeSpec::Class(name) => name.as_str(),
1221 _ => continue,
1222 };
1223 let Some(layout) = layouts.get(type_name) else {
1224 continue;
1225 };
1226
1227 for entity in entities {
1228 let Some(init_expr) = entity.init.as_ref() else {
1229 continue;
1230 };
1231 let Some(FieldDefaultInit::Derived(overrides)) =
1232 eval_const_field_default_init_for_layout(
1233 &TypeInfo::Derived(type_name.to_string()),
1234 init_expr,
1235 layouts,
1236 const_params,
1237 &field_inits,
1238 )
1239 else {
1240 continue;
1241 };
1242
1243 let mut combined = HashMap::new();
1244 for field in &layout.fields {
1245 if let Some(default_init) = &field.default_init {
1246 combined.insert(
1247 field.name.to_ascii_lowercase(),
1248 (field.name.clone(), default_init.clone()),
1249 );
1250 }
1251 }
1252 for (field_name, field_init) in overrides {
1253 combined.insert(
1254 field_name.to_ascii_lowercase(),
1255 (field_name, field_init),
1256 );
1257 }
1258
1259 for (_field_key, (field_name, field_init)) in combined {
1260 let key = derived_param_field_lookup_key(&entity.name, &field_name);
1261 let should_update = field_inits
1262 .get(&key)
1263 .map(|existing| existing != &field_init)
1264 .unwrap_or(true);
1265 if should_update {
1266 field_inits.insert(key, field_init);
1267 changed = true;
1268 }
1269 }
1270 }
1271 }
1272 }
1273
1274 field_inits
1275 }
1276
1277 fn register_local_type_layouts(
1278 decls: &[SpannedDecl],
1279 host_module: Option<&str>,
1280 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
1281 const_params: &HashMap<String, i64>,
1282 const_derived_field_inits: &HashMap<String, crate::sema::type_layout::FieldDefaultInit>,
1283 ) {
1284 for decl in decls {
1285 if let Decl::DerivedTypeDef {
1286 name,
1287 extends,
1288 attrs,
1289 components,
1290 type_bound_procs,
1291 final_procs,
1292 ..
1293 } = &decl.node
1294 {
1295 let parent = extends.as_ref().and_then(|p| layouts.get(p)).cloned();
1296 let is_abstract = attrs
1297 .iter()
1298 .any(|attr| matches!(attr, crate::ast::decl::TypeAttr::Abstract));
1299 let layout = crate::sema::type_layout::compute_layout_with_attrs(
1300 name,
1301 host_module,
1302 type_bound_procs,
1303 final_procs,
1304 components,
1305 parent.as_ref(),
1306 is_abstract,
1307 layouts,
1308 const_params,
1309 const_derived_field_inits,
1310 );
1311 // Don't overwrite a layout that has bound_procs or final_procs with one that doesn't.
1312 // This handles the case where a subroutine redefines a type without CONTAINS.
1313 let dominated = layouts
1314 .get(&name.to_lowercase())
1315 .map(|existing| {
1316 let existing_has =
1317 !existing.bound_procs.is_empty() || !existing.final_procs.is_empty();
1318 let new_has = !layout.bound_procs.is_empty() || !layout.final_procs.is_empty();
1319 existing_has && !new_has
1320 })
1321 .unwrap_or(false);
1322 if !dominated {
1323 layouts.insert(layout);
1324 }
1325 }
1326 }
1327 }
1328
1329 /// Resolve procedure-pointer default-init targets stored as bare
1330 /// source-level names into their link-time symbols. A field declared
1331 /// `procedure(iface), pointer :: fn => default_hasher` lands in the
1332 /// layout with `FieldDefaultInit::ProcedurePointer("default_hasher")`,
1333 /// but `default_hasher` may itself be a USE-rename for a procedure
1334 /// living in a different module — `stdlib_hashmaps` aliases
1335 /// `fnv_1_hasher` from `stdlib_hashmap_wrappers` exactly this way.
1336 /// This pass walks every layout the file just registered, looks each
1337 /// proc-pointer target up in its owning type's host-module scope
1338 /// (chasing USE chains), and rewrites the stored string to the
1339 /// `afs_modproc_<origin_mod>_<proc>` mangle the runtime initializer
1340 /// emits via `global_addr`. Without it the linker reports an
1341 /// undefined `_afs_modproc_<host_mod>_<alias>` reference at example
1342 /// link.
1343 fn resolve_proc_pointer_default_targets(
1344 st: &SymbolTable,
1345 scope_id: ScopeId,
1346 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
1347 ) {
1348 use crate::sema::type_layout::FieldDefaultInit;
1349
1350 for layout in layouts.layouts.values_mut() {
1351 let owner = match layout.owner_module.as_deref() {
1352 Some(m) => m,
1353 None => continue,
1354 };
1355 let owner_scope = match st.find_module_scope(owner) {
1356 Some(s) => s,
1357 None => scope_id,
1358 };
1359 for field in layout.fields.iter_mut() {
1360 let target_name = match &field.default_init {
1361 Some(FieldDefaultInit::ProcedurePointer(name)) => name.clone(),
1362 _ => continue,
1363 };
1364 let resolved = resolve_proc_pointer_link_symbol(st, owner_scope, &target_name);
1365 field.default_init = Some(FieldDefaultInit::ProcedurePointer(resolved));
1366 }
1367 }
1368 }
1369
1370 /// Walk the symbol table from `from_scope` to find the link-time
1371 /// symbol that the source name refers to. Module procedures get the
1372 /// `afs_modproc_<origin_mod>_<proc>` mangle keyed on the procedure's
1373 /// declaring module; bare external/intrinsic references fall through
1374 /// unmodified. USE associations are followed transitively so renames
1375 /// like `default_hasher => fnv_1_hasher` resolve to the underlying
1376 /// procedure's origin module.
1377 fn resolve_proc_pointer_link_symbol(
1378 st: &SymbolTable,
1379 from_scope: ScopeId,
1380 target: &str,
1381 ) -> String {
1382 let key = target.to_lowercase();
1383 let mut seen = std::collections::HashSet::new();
1384 let mut current_scope = from_scope;
1385 let mut current_name = key.clone();
1386
1387 loop {
1388 if !seen.insert((current_scope, current_name.clone())) {
1389 break;
1390 }
1391 let scope = st.scope(current_scope);
1392
1393 if let Some(sym) = scope.symbols.get(&current_name) {
1394 return mangle_link_symbol_for(sym, scope, &current_name);
1395 }
1396
1397 let assoc = scope
1398 .use_associations
1399 .iter()
1400 .find(|a| a.local_name == current_name);
1401 if let Some(assoc) = assoc {
1402 current_scope = assoc.source_scope;
1403 current_name = assoc.original_name.to_lowercase();
1404 continue;
1405 }
1406
1407 break;
1408 }
1409
1410 target.to_string()
1411 }
1412
1413 fn mangle_link_symbol_for(
1414 sym: &crate::sema::symtab::Symbol,
1415 scope: &crate::sema::symtab::Scope,
1416 name_in_scope: &str,
1417 ) -> String {
1418 use crate::sema::symtab::{ScopeKind, SymbolKind};
1419 match sym.kind {
1420 SymbolKind::Function | SymbolKind::Subroutine => match &scope.kind {
1421 ScopeKind::Module(module_name) | ScopeKind::Submodule(module_name) => format!(
1422 "afs_modproc_{}_{}",
1423 module_name.to_lowercase(),
1424 name_in_scope
1425 ),
1426 _ => name_in_scope.to_string(),
1427 },
1428 _ => name_in_scope.to_string(),
1429 }
1430 }
1431
1432 fn process_implicit(st: &mut SymbolTable, implicit_stmts: &[SpannedDecl]) -> Result<(), SemaError> {
1433 for stmt in implicit_stmts {
1434 match &stmt.node {
1435 Decl::ImplicitNone { type_, external } => {
1436 st.set_implicit_none(*type_, *external);
1437 }
1438 Decl::ImplicitStmt { specs } => {
1439 for spec in specs {
1440 let itype = match &spec.type_spec {
1441 TypeSpec::Integer(_) => ImplicitType::Integer,
1442 TypeSpec::Real(_) => ImplicitType::Real,
1443 TypeSpec::DoublePrecision => ImplicitType::DoublePrecision,
1444 TypeSpec::Complex(_) => ImplicitType::Complex,
1445 TypeSpec::Logical(_) => ImplicitType::Logical,
1446 TypeSpec::Character(_) => ImplicitType::Character,
1447 _ => continue,
1448 };
1449 for (start, end) in &spec.ranges {
1450 st.set_implicit_rule(*start, *end, itype);
1451 }
1452 }
1453 }
1454 _ => {}
1455 }
1456 }
1457 Ok(())
1458 }
1459
1460 fn process_decls(st: &mut SymbolTable, decls: &[SpannedDecl]) -> Result<(), SemaError> {
1461 // Collect AccessList entries — they must be applied AFTER all TypeDecls
1462 // because the list may reference symbols declared later in the module.
1463 let mut pending_access: Vec<(Access, Vec<String>)> = Vec::new();
1464 for decl in decls {
1465 match &decl.node {
1466 Decl::AccessDefault { access } => match access {
1467 Attribute::Private => st.set_default_access(Access::Private),
1468 Attribute::Public => st.set_default_access(Access::Public),
1469 _ => {}
1470 },
1471 Decl::AccessList { access, names } => {
1472 let acc = match access {
1473 Attribute::Private => Access::Private,
1474 Attribute::Public => Access::Public,
1475 _ => continue,
1476 };
1477 pending_access.push((acc, names.clone()));
1478 }
1479 Decl::TypeDecl {
1480 type_spec,
1481 attrs,
1482 entities,
1483 } => {
1484 let mut type_info = type_spec_to_info(type_spec, st);
1485 let mut sym_attrs =
1486 attrs_to_symbol_attrs(attrs, st.default_access(st.current_scope()));
1487 let mut kind = if sym_attrs.parameter {
1488 SymbolKind::Parameter
1489 } else {
1490 SymbolKind::Variable
1491 };
1492 let mut arg_names = Vec::new();
1493 // Sprint35-SMP Phase 1: snapshot the per-decl `dimension(...)`
1494 // attribute as an array-spec fallback. Per-entity specs override
1495 // it (e.g. `real, dimension(10) :: a, b(:,:)` declares a as
1496 // rank-1 size 10 and b as assumed-shape rank-2).
1497 let attr_dimension: Option<&Vec<crate::ast::decl::ArraySpec>> =
1498 attrs.iter().find_map(|a| {
1499 if let crate::ast::decl::Attribute::Dimension(specs) = a {
1500 Some(specs)
1501 } else {
1502 None
1503 }
1504 });
1505
1506 if sym_attrs.external {
1507 if let TypeSpec::Type(iface_name) = type_spec {
1508 sym_attrs.procedure_iface = Some(iface_name.clone());
1509 if sym_attrs.pointer {
1510 kind = SymbolKind::ProcedurePointer;
1511 }
1512 if let Some(iface_sym) =
1513 st.find_symbol_any_scope(&iface_name.to_lowercase())
1514 {
1515 type_info = iface_sym
1516 .type_info
1517 .clone()
1518 .unwrap_or_else(|| type_info.clone());
1519 arg_names = iface_sym.arg_names.clone();
1520 }
1521 }
1522 }
1523
1524 for entity in entities {
1525 let key = entity.name.to_lowercase();
1526 // For `character(*), parameter :: name = init`, F2008
1527 // §5.3.2 says the parameter's length is taken from
1528 // `init`. type_spec_to_info loses that info because
1529 // LenSpec::Star → len=None; recover it here when the
1530 // init is a string literal or another character
1531 // parameter whose length we already know.
1532 let mut entity_type_info = type_info.clone();
1533 if sym_attrs.parameter
1534 && matches!(&entity_type_info, TypeInfo::Character { len: None, .. })
1535 {
1536 if let Some(init) = entity.init.as_ref() {
1537 let derived_len =
1538 derived_char_init_len(&init.node, st).map(|n| n as i64);
1539 if let Some(n) = derived_len {
1540 if let TypeInfo::Character { len, .. } = &mut entity_type_info {
1541 *len = Some(n);
1542 }
1543 }
1544 }
1545 }
1546 // Sprint35-SMP Phase 1: per-entity attrs clone so each
1547 // entity carries its own array_spec (entity-local spec
1548 // wins; otherwise fall back to the decl-level dimension
1549 // attribute).
1550 let mut entity_attrs = sym_attrs.clone();
1551 let entity_array_spec = entity
1552 .array_spec
1553 .clone()
1554 .or_else(|| attr_dimension.cloned())
1555 .unwrap_or_default();
1556 entity_attrs.array_spec = entity_array_spec;
1557 if st.scope(st.current_scope()).symbols.contains_key(&key) {
1558 // Symbol already exists (e.g., dummy argument) — update type info.
1559 let sym = st
1560 .scope_mut(st.current_scope())
1561 .symbols
1562 .get_mut(&key)
1563 .unwrap();
1564 sym.kind = kind.clone();
1565 sym.type_info = Some(entity_type_info.clone());
1566 sym.attrs = entity_attrs.clone();
1567 sym.arg_names = arg_names.clone();
1568 } else {
1569 // Try to fold PARAMETER initializers to a
1570 // compile-time integer so .amod can carry the
1571 // value and consumers can inline it.
1572 let const_value = if sym_attrs.parameter {
1573 entity
1574 .init
1575 .as_ref()
1576 .and_then(|e| eval_const_int_expr(e, st))
1577 } else {
1578 None
1579 };
1580 st.define(Symbol {
1581 name: entity.name.clone(),
1582 kind: kind.clone(),
1583 type_info: Some(entity_type_info.clone()),
1584 attrs: entity_attrs.clone(),
1585 defined_at: decl.span,
1586 scope: st.current_scope(),
1587 arg_names: arg_names.clone(),
1588 const_value,
1589 })?;
1590 }
1591 }
1592 }
1593 Decl::DerivedTypeDef { name, attrs, .. } => {
1594 let mut sym_attrs = SymbolAttrs {
1595 access: st.default_access(st.current_scope()),
1596 ..SymbolAttrs::default()
1597 };
1598 for attr in attrs {
1599 match attr {
1600 decl::TypeAttr::Public => sym_attrs.access = Access::Public,
1601 decl::TypeAttr::Private => sym_attrs.access = Access::Private,
1602 _ => {}
1603 }
1604 }
1605 st.define(Symbol {
1606 name: name.clone(),
1607 kind: SymbolKind::DerivedType,
1608 type_info: None,
1609 attrs: sym_attrs,
1610 defined_at: decl.span,
1611 scope: st.current_scope(),
1612 arg_names: vec![],
1613 const_value: None,
1614 })?;
1615 }
1616 Decl::EnumDef { enumerators } => {
1617 let mut next_value = 0i64;
1618 for (name, value_expr) in enumerators {
1619 let const_value = if let Some(expr) = value_expr {
1620 eval_const_int_expr(expr, st).unwrap_or(next_value)
1621 } else {
1622 next_value
1623 };
1624 next_value = const_value + 1;
1625 st.define(Symbol {
1626 name: name.clone(),
1627 kind: SymbolKind::Parameter,
1628 type_info: Some(TypeInfo::Integer { kind: None }),
1629 attrs: SymbolAttrs {
1630 access: st.default_access(st.current_scope()),
1631 parameter: true,
1632 ..Default::default()
1633 },
1634 defined_at: decl.span,
1635 scope: st.current_scope(),
1636 arg_names: vec![],
1637 const_value: Some(const_value),
1638 })?;
1639 }
1640 }
1641 _ => {}
1642 }
1643 }
1644 // Apply deferred access-list overrides after all symbols are declared.
1645 for (access, names) in &pending_access {
1646 for name in names {
1647 st.set_symbol_access(name, *access);
1648 }
1649 }
1650 Ok(())
1651 }
1652
1653 fn process_contains(
1654 st: &mut SymbolTable,
1655 contains: &[SpannedUnit],
1656 module_search_paths: &[std::path::PathBuf],
1657 layouts: &mut crate::sema::type_layout::TypeLayoutRegistry,
1658 ) -> Result<(), SemaError> {
1659 for unit in contains {
1660 // Register the subprogram name in the current scope before descending.
1661 let host_is_submodule = matches!(st.scope(st.current_scope()).kind, ScopeKind::Submodule(_));
1662 match &unit.node {
1663 ProgramUnit::Subroutine {
1664 name, prefix, bind, ..
1665 } => {
1666 let elemental = prefix
1667 .iter()
1668 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
1669 let pure = elemental
1670 || prefix
1671 .iter()
1672 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
1673 let is_smp = host_is_submodule
1674 && prefix
1675 .iter()
1676 .any(|p| matches!(p, crate::ast::unit::Prefix::Module));
1677 let attrs = SymbolAttrs {
1678 pure,
1679 elemental,
1680 binding_label: normalized_bind_name(bind.as_ref(), name),
1681 is_separate_module_procedure: is_smp,
1682 ..Default::default()
1683 };
1684 let _ignore_dup = st.define(Symbol {
1685 name: name.clone(),
1686 kind: SymbolKind::Subroutine,
1687 type_info: None,
1688 attrs,
1689 defined_at: unit.span,
1690 scope: st.current_scope(),
1691 arg_names: vec![],
1692 const_value: None,
1693 });
1694 }
1695 ProgramUnit::Function {
1696 name,
1697 return_type,
1698 result,
1699 decls,
1700 prefix,
1701 bind,
1702 ..
1703 } => {
1704 let ret_type_info = return_type
1705 .as_ref()
1706 .map(|ts| type_spec_to_info(ts, st))
1707 .or_else(|| {
1708 // Infer return type from result variable's declaration.
1709 let result_name = result.as_deref().unwrap_or(name.as_str());
1710 let key = result_name.to_lowercase();
1711 for d in decls {
1712 if let decl::Decl::TypeDecl {
1713 type_spec,
1714 entities,
1715 ..
1716 } = &d.node
1717 {
1718 for e in entities {
1719 if e.name.to_lowercase() == key {
1720 return Some(type_spec_to_info(type_spec, st));
1721 }
1722 }
1723 }
1724 }
1725 None
1726 });
1727 let fn_elemental = prefix
1728 .iter()
1729 .any(|p| matches!(p, crate::ast::unit::Prefix::Elemental));
1730 let fn_pure = fn_elemental
1731 || prefix
1732 .iter()
1733 .any(|p| matches!(p, crate::ast::unit::Prefix::Pure));
1734 let result_attrs = function_result_attrs(name, result, decls);
1735 let fn_is_smp = host_is_submodule
1736 && prefix
1737 .iter()
1738 .any(|p| matches!(p, crate::ast::unit::Prefix::Module));
1739 let fn_attrs = SymbolAttrs {
1740 allocatable: result_attrs.allocatable,
1741 pointer: result_attrs.pointer,
1742 pure: fn_pure,
1743 elemental: fn_elemental,
1744 binding_label: normalized_bind_name(bind.as_ref(), name),
1745 result_rank: result_attrs.result_rank,
1746 is_separate_module_procedure: fn_is_smp,
1747 ..Default::default()
1748 };
1749 let _ignore_dup = st.define(Symbol {
1750 name: name.clone(),
1751 kind: SymbolKind::Function,
1752 type_info: ret_type_info,
1753 attrs: fn_attrs,
1754 defined_at: unit.span,
1755 scope: st.current_scope(),
1756 arg_names: vec![],
1757 const_value: None,
1758 });
1759 }
1760 _ => {}
1761 }
1762 resolve_unit(st, unit, module_search_paths, layouts)?;
1763 }
1764 Ok(())
1765 }
1766
1767 // ---- Helpers ----
1768
1769 /// Extract a compile-time integer kind value from a KindSelector.
1770 /// Try to evaluate a PARAMETER initializer to a compile-time i64.
1771 /// Handles integer literals, negation, binary ops, parenthesized
1772 /// expressions, and Name references that resolve to PARAMETERs
1773 /// with known const_value in the current scope chain.
1774 pub(super) fn eval_const_int_expr(
1775 expr: &crate::ast::expr::SpannedExpr,
1776 st: &SymbolTable,
1777 ) -> Option<i64> {
1778 use crate::ast::expr::Expr;
1779 match &expr.node {
1780 Expr::IntegerLiteral { text, .. } => {
1781 let clean = text.split('_').next().unwrap_or(text);
1782 clean.parse::<i64>().ok()
1783 }
1784 Expr::BozLiteral { text, base } => parse_boz_i64(text, *base),
1785 Expr::Name { name } => {
1786 // Look up the name in the current scope chain.
1787 let sym = st.lookup_in(st.current_scope(), &name.to_lowercase())?;
1788 if sym.attrs.parameter {
1789 sym.const_value
1790 } else {
1791 None
1792 }
1793 }
1794 Expr::UnaryOp { op, operand } => {
1795 let v = eval_const_int_expr(operand, st)?;
1796 match op {
1797 crate::ast::expr::UnaryOp::Minus => Some(-v),
1798 crate::ast::expr::UnaryOp::Plus => Some(v),
1799 _ => None,
1800 }
1801 }
1802 Expr::BinaryOp { op, left, right } => {
1803 let l = eval_const_int_expr(left, st)?;
1804 let r = eval_const_int_expr(right, st)?;
1805 match op {
1806 crate::ast::expr::BinaryOp::Add => Some(l + r),
1807 crate::ast::expr::BinaryOp::Sub => Some(l - r),
1808 crate::ast::expr::BinaryOp::Mul => Some(l * r),
1809 crate::ast::expr::BinaryOp::Div if r != 0 => Some(l / r),
1810 _ => None,
1811 }
1812 }
1813 Expr::ParenExpr { inner } => eval_const_int_expr(inner, st),
1814 Expr::FunctionCall { callee, args } => {
1815 if let Expr::Name { name } = &callee.node {
1816 let key = name.to_lowercase();
1817 let first_arg_val = args.first().and_then(|a| {
1818 if let crate::ast::expr::SectionSubscript::Element(e) = &a.value {
1819 eval_const_int_expr(e, st)
1820 } else {
1821 None
1822 }
1823 });
1824 match key.as_str() {
1825 "selected_int_kind" => {
1826 let r = first_arg_val?;
1827 Some(if r <= 2 {
1828 1
1829 } else if r <= 4 {
1830 2
1831 } else if r <= 9 {
1832 4
1833 } else if r <= 18 {
1834 8
1835 } else if r <= 38 {
1836 16
1837 } else {
1838 -1
1839 })
1840 }
1841 "selected_real_kind" => {
1842 let p = first_arg_val?;
1843 Some(if p <= 6 {
1844 4
1845 } else if p <= 15 {
1846 8
1847 } else {
1848 -1
1849 })
1850 }
1851 "kind" => {
1852 if let Some(arg) = args.first() {
1853 if let crate::ast::expr::SectionSubscript::Element(e) = &arg.value {
1854 match &e.node {
1855 Expr::RealLiteral { text, .. } => {
1856 Some(if text.contains('d') || text.contains('D') {
1857 8
1858 } else {
1859 4
1860 })
1861 }
1862 Expr::IntegerLiteral { .. } => Some(4),
1863 _ => None,
1864 }
1865 } else {
1866 None
1867 }
1868 } else {
1869 None
1870 }
1871 }
1872 "int" => {
1873 let arg = args.first()?;
1874 let crate::ast::expr::SectionSubscript::Element(e) = &arg.value else {
1875 return None;
1876 };
1877 match &e.node {
1878 Expr::RealLiteral { text, .. } => text
1879 .replace('d', "e")
1880 .replace('D', "E")
1881 .split('_')
1882 .next()
1883 .unwrap_or(text)
1884 .parse::<f64>()
1885 .ok()
1886 .map(|v| v.trunc() as i64),
1887 _ => eval_const_int_expr(e, st),
1888 }
1889 }
1890 "range" => {
1891 let arg = args.first()?;
1892 let crate::ast::expr::SectionSubscript::Element(e) = &arg.value else {
1893 return None;
1894 };
1895 let ty = match &e.node {
1896 Expr::Name { name } => st
1897 .lookup_in(st.current_scope(), &name.to_lowercase())
1898 .and_then(|sym| sym.type_info.as_ref()),
1899 Expr::ParenExpr { inner } => match &inner.node {
1900 Expr::Name { name } => st
1901 .lookup_in(st.current_scope(), &name.to_lowercase())
1902 .and_then(|sym| sym.type_info.as_ref()),
1903 _ => None,
1904 },
1905 _ => None,
1906 }?;
1907 match ty {
1908 TypeInfo::Integer { kind } => Some(match kind
1909 .unwrap_or(crate::driver::defaults::default_int_kind())
1910 {
1911 1 => 2,
1912 2 => 4,
1913 4 => 9,
1914 8 => 18,
1915 16 => 38,
1916 _ => return None,
1917 }),
1918 TypeInfo::Real { kind } => Some(match kind
1919 .unwrap_or(crate::driver::defaults::default_real_kind())
1920 {
1921 4 => 37,
1922 8 => 307,
1923 _ => return None,
1924 }),
1925 TypeInfo::DoublePrecision => Some(307),
1926 _ => None,
1927 }
1928 }
1929 _ => None,
1930 }
1931 } else {
1932 None
1933 }
1934 }
1935 _ => None,
1936 }
1937 }
1938
1939
1940 fn attrs_to_symbol_attrs(attrs: &[Attribute], default_access: Access) -> SymbolAttrs {
1941 let mut sa = SymbolAttrs {
1942 access: default_access,
1943 ..SymbolAttrs::default()
1944 };
1945 for attr in attrs {
1946 match attr {
1947 Attribute::Allocatable => sa.allocatable = true,
1948 Attribute::Pointer => sa.pointer = true,
1949 Attribute::Target => sa.target = true,
1950 Attribute::Optional => sa.optional = true,
1951 Attribute::Save => sa.save = true,
1952 Attribute::Parameter => sa.parameter = true,
1953 Attribute::Value => sa.value = true,
1954 Attribute::External => sa.external = true,
1955 Attribute::Intrinsic => sa.intrinsic = true,
1956 Attribute::Public => sa.access = Access::Public,
1957 Attribute::Private => sa.access = Access::Private,
1958 Attribute::Intent(intent) => {
1959 sa.intent = Some(match intent {
1960 decl::Intent::In => Intent::In,
1961 decl::Intent::Out => Intent::Out,
1962 decl::Intent::InOut => Intent::InOut,
1963 });
1964 }
1965 _ => {}
1966 }
1967 }
1968 sa
1969 }
1970
1971 fn function_result_attrs(
1972 function_name: &str,
1973 result: &Option<String>,
1974 decls: &[crate::ast::decl::SpannedDecl],
1975 ) -> SymbolAttrs {
1976 let result_key = result
1977 .as_deref()
1978 .unwrap_or(function_name)
1979 .to_ascii_lowercase();
1980 for decl in decls {
1981 let crate::ast::decl::Decl::TypeDecl {
1982 attrs, entities, ..
1983 } = &decl.node
1984 else {
1985 continue;
1986 };
1987 let matching_entity = entities
1988 .iter()
1989 .find(|entity| entity.name.eq_ignore_ascii_case(&result_key));
1990 if let Some(entity) = matching_entity {
1991 let mut sym_attrs = attrs_to_symbol_attrs(attrs, Access::Default);
1992 // Capture result rank: prefer the entity-local array_spec
1993 // (e.g. `real :: w(:)`), falling back to a `dimension(...)`
1994 // attribute on the type-decl statement.
1995 let rank_from_entity = entity.array_spec.as_ref().map(|specs| specs.len());
1996 let rank_from_attrs = attrs.iter().find_map(|a| match a {
1997 crate::ast::decl::Attribute::Dimension(specs) => Some(specs.len()),
1998 _ => None,
1999 });
2000 sym_attrs.result_rank = rank_from_entity
2001 .or(rank_from_attrs)
2002 .unwrap_or(0)
2003 .min(u8::MAX as usize) as u8;
2004 return sym_attrs;
2005 }
2006 }
2007 SymbolAttrs::default()
2008 }
2009
2010 #[cfg(test)]
2011 mod tests {
2012 use super::*;
2013 use crate::lexer::Lexer;
2014 use crate::parser::Parser;
2015
2016 fn resolve_source(src: &str) -> SymbolTable {
2017 let tokens = Lexer::tokenize(src, 0).unwrap();
2018 let mut parser = Parser::new(&tokens);
2019 let units = parser.parse_file().unwrap();
2020 resolve_file(&units, &[]).unwrap().st
2021 }
2022
2023 fn resolve_source_with_layouts(src: &str) -> ResolveResult {
2024 let tokens = Lexer::tokenize(src, 0).unwrap();
2025 let mut parser = Parser::new(&tokens);
2026 let units = parser.parse_file().unwrap();
2027 resolve_file(&units, &[]).unwrap()
2028 }
2029
2030 // ---- Integration tests ----
2031
2032 #[test]
2033 fn simple_program_declarations() {
2034 let st = resolve_source(
2035 "program test\n implicit none\n integer :: x, y\n real :: z\nend program\n",
2036 );
2037 // Should have x, y, z defined.
2038 // Navigate to the program scope.
2039 let prog_scope = st
2040 .scopes
2041 .iter()
2042 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
2043 .unwrap();
2044 assert!(prog_scope.symbols.contains_key("x"));
2045 assert!(prog_scope.symbols.contains_key("y"));
2046 assert!(prog_scope.symbols.contains_key("z"));
2047 }
2048
2049 #[test]
2050 fn implicit_none_enforced() {
2051 let st = resolve_source("program test\n implicit none\n integer :: x\nend program\n");
2052 let prog_scope = st
2053 .scopes
2054 .iter()
2055 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
2056 .unwrap();
2057 assert!(prog_scope.implicit_rules.none_type);
2058 }
2059
2060 #[test]
2061 fn module_use_association() {
2062 let st = resolve_source(
2063 "\
2064 module mymod
2065 implicit none
2066 integer :: shared_var
2067 end module
2068
2069 program main
2070 use mymod
2071 implicit none
2072 end program
2073 ",
2074 );
2075 // shared_var should be in the module scope.
2076 let mod_scope = st
2077 .scopes
2078 .iter()
2079 .find(|s| matches!(s.kind, ScopeKind::Module(ref n) if n == "mymod"))
2080 .unwrap();
2081 assert!(mod_scope.symbols.contains_key("shared_var"));
2082
2083 // The program should have a USE association for shared_var.
2084 let prog_scope = st
2085 .scopes
2086 .iter()
2087 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
2088 .unwrap();
2089 assert!(!prog_scope.use_associations.is_empty());
2090 }
2091
2092 #[test]
2093 fn subroutine_with_args() {
2094 let st = resolve_source("subroutine foo(x, y)\n real :: x, y\nend subroutine\n");
2095 let sub_scope = st
2096 .scopes
2097 .iter()
2098 .find(|s| matches!(s.kind, ScopeKind::Subroutine(ref n) if n == "foo"))
2099 .unwrap();
2100 assert!(sub_scope.symbols.contains_key("x"));
2101 assert!(sub_scope.symbols.contains_key("y"));
2102 }
2103
2104 #[test]
2105 fn function_result_variable() {
2106 let st = resolve_source(
2107 "function square(x) result(y)\n real :: x, y\n y = x * x\nend function\n",
2108 );
2109 let fn_scope = st
2110 .scopes
2111 .iter()
2112 .find(|s| matches!(s.kind, ScopeKind::Function(ref n) if n == "square"))
2113 .unwrap();
2114 assert!(fn_scope.symbols.contains_key("x"));
2115 assert!(fn_scope.symbols.contains_key("y"));
2116 }
2117
2118 #[test]
2119 fn contains_creates_child_scope() {
2120 let st = resolve_source(
2121 "\
2122 program main
2123 implicit none
2124 integer :: x
2125 contains
2126 subroutine inner()
2127 integer :: local_var
2128 end subroutine
2129 end program
2130 ",
2131 );
2132 // inner should be its own scope.
2133 let inner_scope = st
2134 .scopes
2135 .iter()
2136 .find(|s| matches!(s.kind, ScopeKind::Subroutine(ref n) if n == "inner"))
2137 .unwrap();
2138 assert!(inner_scope.symbols.contains_key("local_var"));
2139
2140 // inner should be registered as a symbol in the program scope.
2141 let prog_scope = st
2142 .scopes
2143 .iter()
2144 .find(|s| matches!(s.kind, ScopeKind::Program(_)))
2145 .unwrap();
2146 assert!(prog_scope.symbols.contains_key("inner"));
2147 }
2148
2149 #[test]
2150 fn derived_type_defined() {
2151 let st = resolve_source(
2152 "module m\n type :: mytype\n integer :: field\n end type\nend module\n",
2153 );
2154 let mod_scope = st
2155 .scopes
2156 .iter()
2157 .find(|s| matches!(&s.kind, ScopeKind::Module(n) if n == "m"))
2158 .unwrap();
2159 assert!(mod_scope.symbols.contains_key("mytype"));
2160 assert_eq!(mod_scope.symbols["mytype"].kind, SymbolKind::DerivedType);
2161 }
2162
2163 #[test]
2164 fn imported_named_character_params_feed_type_layouts() {
2165 let resolved = resolve_source_with_layouts(
2166 "module cfg\n implicit none\n integer, parameter :: max_token_len = 16\nend module\n\nmodule m\n use cfg, only: max_token_len\n implicit none\n type :: token_t\n character(len=max_token_len), allocatable :: value(:)\n character(len=max_token_len) :: tag = ''\n end type\nend module\n",
2167 );
2168 let layout = resolved
2169 .type_layouts
2170 .get("token_t")
2171 .expect("missing token_t layout");
2172 let value = layout.field("value").expect("missing value field");
2173 let tag = layout.field("tag").expect("missing tag field");
2174
2175 assert!(matches!(
2176 value.type_info,
2177 TypeInfo::Character {
2178 len: Some(16),
2179 kind: None
2180 }
2181 ));
2182 assert!(matches!(
2183 tag.type_info,
2184 TypeInfo::Character {
2185 len: Some(16),
2186 kind: None
2187 }
2188 ));
2189 }
2190 }
2191