Resolve the namespace for a type string by loading the base type through the class loader, falling back to parsing FQN strings. Extracts the first class-like name from the type string (before any `<` generic params), resolves it via the class loader, and returns the resolved class's `file_namespace`. When the class loader cannot find the type (e.g. a cross-file FQN like `\App\Models\User` that i
(
var_name: &str,
ty: &PhpType,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
template_line: Option<&str>,
)
| 1483 | /// (`$ambiguous = Lamp` and `$ambiguous = Faucet`) joined by a |
| 1484 | /// markdown horizontal rule so the editor renders a visible divider. |
| 1485 | fn build_variable_hover_body( |
| 1486 | var_name: &str, |
| 1487 | ty: &PhpType, |
| 1488 | class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>, |
| 1489 | template_line: Option<&str>, |
| 1490 | ) -> String { |
| 1491 | let members = ty.union_members(); |
| 1492 | |
| 1493 | // Count how many members are non-trivial class types (not scalars, |
| 1494 | // not `null`, not `void`, etc.). Only render separate blocks when |
| 1495 | // there are 2+ class-like types; a simple `Foo|null` should stay |
| 1496 | // in one block. |
| 1497 | let class_like_count = members.iter().filter(|m| !m.is_scalar()).count(); |
| 1498 | |
| 1499 | // When there is only one component, or only one class-like type |
| 1500 | // (the rest being scalars / null), render a single code block. |
| 1501 | if members.len() <= 1 || class_like_count < 2 { |
| 1502 | let short_type = ty.shorten().to_string(); |
| 1503 | let ns = resolve_type_namespace_structured(ty, class_loader); |
| 1504 | let ns_line = namespace_line(ns.as_deref()); |
| 1505 | let code_block = format!( |
| 1506 | "```php\n<?php\n{}{} = {}\n```", |
| 1507 | ns_line, var_name, short_type |
| 1508 | ); |
| 1509 | return if let Some(tpl) = template_line { |
| 1510 | format!("{}\n\n{}", tpl, code_block) |
| 1511 | } else { |
| 1512 | code_block |
| 1513 | }; |
| 1514 | } |
| 1515 | |
| 1516 | // Multiple union branches — render each as its own code block |
| 1517 | // separated by a markdown horizontal rule. |
| 1518 | let mut blocks: Vec<String> = Vec::with_capacity(members.len()); |
| 1519 | for member in &members { |
| 1520 | let short = member.shorten().to_string(); |
| 1521 | let ns = resolve_type_namespace_structured(member, class_loader); |
| 1522 | let ns_line = namespace_line(ns.as_deref()); |
| 1523 | blocks.push(format!( |
| 1524 | "```php\n<?php\n{}{} = {}\n```", |
| 1525 | ns_line, var_name, short |
| 1526 | )); |
| 1527 | } |
| 1528 | |
| 1529 | let body = blocks.join("\n\n---\n\n"); |
| 1530 | if let Some(tpl) = template_line { |
| 1531 | format!("{}\n\n{}", tpl, body) |
| 1532 | } else { |
| 1533 | body |
| 1534 | } |
| 1535 | } |
| 1536 | |
| 1537 | /// Extract the namespace for a structured `PhpType` by looking up its |
| 1538 | /// base class name via the class loader, or by parsing the namespace |