@@ -40350,7 +40350,61 @@ fn expr_is_character_expr( |
| 40350 | 40350 | })) |
| 40351 | 40351 | }) |
| 40352 | 40352 | .unwrap_or(false) |
| 40353 | | - } else if let Expr::ComponentAccess { .. } = &callee.node { |
| 40353 | + } else if let Expr::ComponentAccess { |
| 40354 | + base: tbp_base, |
| 40355 | + component: tbp_component, |
| 40356 | + } = &callee.node |
| 40357 | + { |
| 40358 | + // F2008 §4.5.4 type-bound procedure call: `obj%method()` |
| 40359 | + // where `method` is a TBP (not a data field). The data- |
| 40360 | + // field lookups below return None for a TBP since TBPs |
| 40361 | + // don't appear in `layout.fields`. Without this branch |
| 40362 | + // the print path mis-classifies a TBP returning |
| 40363 | + // `character(:), allocatable :: r` as non-character and |
| 40364 | + // emits a scalar writer that reads the descriptor |
| 40365 | + // pointer as an integer — surfaced as a silently |
| 40366 | + // empty `print *, e%print()` for stdlib_linalg_state's |
| 40367 | + // state_message TBP. |
| 40368 | + let tbp_char = type_layouts.and_then(|tl| { |
| 40369 | + let (_, base_type) = |
| 40370 | + resolve_component_base_for_method(b, locals, tbp_base, st, tl)?; |
| 40371 | + let layout = tl.get(&base_type)?; |
| 40372 | + let bp = layout.bound_proc(tbp_component)?; |
| 40373 | + // The TBP `target_name` carries the mangled IR symbol |
| 40374 | + // (e.g. `afs_modproc_mod_proc`); the symbol table is |
| 40375 | + // keyed by the source-level procedure name. Strip the |
| 40376 | + // mangling prefix and the leading module segment so |
| 40377 | + // the lookup hits the function symbol with its |
| 40378 | + // type_info populated. |
| 40379 | + let raw_target = bp.target_name.as_str(); |
| 40380 | + let stripped = raw_target |
| 40381 | + .strip_prefix("afs_modproc_") |
| 40382 | + .map(|s| { |
| 40383 | + // Format: afs_modproc_<module>_<proc>. Module |
| 40384 | + // name itself can contain underscores, so |
| 40385 | + // try progressively shorter prefixes until |
| 40386 | + // the symtab finds a match. |
| 40387 | + let mut candidates: Vec<String> = Vec::new(); |
| 40388 | + candidates.push(s.to_string()); |
| 40389 | + let mut cursor = s; |
| 40390 | + while let Some(idx) = cursor.find('_') { |
| 40391 | + cursor = &cursor[idx + 1..]; |
| 40392 | + candidates.push(cursor.to_string()); |
| 40393 | + } |
| 40394 | + candidates |
| 40395 | + }) |
| 40396 | + .unwrap_or_else(|| vec![raw_target.to_string()]); |
| 40397 | + let target = stripped |
| 40398 | + .iter() |
| 40399 | + .find_map(|cand| st.find_symbol_any_scope(cand))?; |
| 40400 | + Some(matches!( |
| 40401 | + target.type_info, |
| 40402 | + Some(crate::sema::symtab::TypeInfo::Character { .. }) |
| 40403 | + )) |
| 40404 | + }); |
| 40405 | + if let Some(true) = tbp_char { |
| 40406 | + return true; |
| 40407 | + } |
| 40354 | 40408 | type_layouts |
| 40355 | 40409 | .and_then(|tl| { |
| 40356 | 40410 | component_array_local_info(b, locals, callee, st, tl).or_else(|| { |