Extract methods, properties, constants, and used trait names from class-like members. This is shared between `Statement::Class`, `Statement::Interface`, and `Statement::Trait` since all use the same `ClassLikeMember` representation. When `doc_ctx` is provided, PHPDoc `@return` and `@var` tags are used to refine (or supply) type information for methods and properties.
(
members: impl Iterator<Item = &'a ClassLikeMember<'a>>,
doc_ctx: Option<&DocblockCtx<'a>>,
class_template_params: &[Atom],
)
| 1745 | /// When `doc_ctx` is provided, PHPDoc `@return` and `@var` tags are used |
| 1746 | /// to refine (or supply) type information for methods and properties. |
| 1747 | pub(crate) fn extract_class_like_members<'a>( |
| 1748 | members: impl Iterator<Item = &'a ClassLikeMember<'a>>, |
| 1749 | doc_ctx: Option<&DocblockCtx<'a>>, |
| 1750 | class_template_params: &[Atom], |
| 1751 | ) -> ExtractedMembers { |
| 1752 | /// Resolve a short class name to its FQN using the file's use-map |
| 1753 | /// and namespace from [`DocblockCtx`]. When no context is |
| 1754 | /// available the name is returned as-is. |
| 1755 | fn resolve_name_via_ctx(name: &str, doc_ctx: Option<&DocblockCtx<'_>>) -> String { |
| 1756 | // Already fully-qualified — strip the leading `\`. |
| 1757 | if let Some(stripped) = name.strip_prefix('\\') { |
| 1758 | return stripped.to_string(); |
| 1759 | } |
| 1760 | let Some(ctx) = doc_ctx else { |
| 1761 | return name.to_string(); |
| 1762 | }; |
| 1763 | // Check use-map (handles both unqualified and qualified names). |
| 1764 | if let Some(pos) = name.find('\\') { |
| 1765 | let first = &name[..pos]; |
| 1766 | let rest = &name[pos..]; |
| 1767 | if let Some(fqn) = ctx.use_map.get(first) { |
| 1768 | return format!("{fqn}{rest}"); |
| 1769 | } |
| 1770 | } else if let Some(fqn) = ctx.use_map.get(name) { |
| 1771 | return fqn.clone(); |
| 1772 | } |
| 1773 | // Prepend current namespace if available. |
| 1774 | if let Some(ref ns) = ctx.namespace { |
| 1775 | format!("{ns}\\{name}") |
| 1776 | } else { |
| 1777 | name.to_string() |
| 1778 | } |
| 1779 | } |
| 1780 | |
| 1781 | let mut methods = Vec::new(); |
| 1782 | let mut properties = Vec::new(); |
| 1783 | let mut constants = Vec::new(); |
| 1784 | let mut used_traits: Vec<Atom> = Vec::new(); |
| 1785 | let mut trait_precedences = Vec::new(); |
| 1786 | let mut trait_aliases = Vec::new(); |
| 1787 | let mut inline_use_generics: Vec<(Atom, Vec<PhpType>)> = Vec::new(); |
| 1788 | let mut constructor_body: Option<&MethodBody<'_>> = None; |
| 1789 | |
| 1790 | for member in members { |
| 1791 | match member { |
| 1792 | ClassLikeMember::Method(method) => { |
| 1793 | // Skip methods whose #[PhpStormStubsElementAvailable] |
| 1794 | // range excludes the target PHP version. |
| 1795 | if let Some(ctx) = doc_ctx |
| 1796 | && let Some(ver) = ctx.php_version |
| 1797 | && !is_available_for_version(&method.attribute_lists, ctx, ver) |
| 1798 | { |
| 1799 | continue; |
| 1800 | } |
| 1801 | |
| 1802 | // Skip methods whose docblock has `@removed X.Y` |
| 1803 | // where X.Y <= the target PHP version. |
| 1804 | if let Some(ctx) = doc_ctx |
nothing calls this directly
no test coverage detected