Enrich a child method with docblock information from an ancestor method. Propagates return types, parameter types, descriptions, template parameters, conditional return types, and type assertions from the ancestor when the child lacks its own docblock overrides. Return type rule:** If the child's `return_type` equals its `native_return_type` (no docblock), and the ancestor's `return_type` differ
(existing: &mut MethodInfo, ancestor: &MethodInfo)
| 152 | /// **Description rule:** Inherit `description` and `return_description` |
| 153 | /// when the child has `None`. |
| 154 | pub(crate) fn enrich_method_from_ancestor(existing: &mut MethodInfo, ancestor: &MethodInfo) { |
| 155 | // ── Return type ───────────────────────────────────────────── |
| 156 | // Propagate when (a) the child has no return type at all, or |
| 157 | // (b) the child's effective type equals its native type (no |
| 158 | // docblock override) and the ancestor has a richer docblock type. |
| 159 | if existing.return_type.is_none() && ancestor.return_type.is_some() |
| 160 | || lacks_docblock_override(&existing.return_type, &existing.native_return_type) |
| 161 | && ancestor_has_richer_type(&ancestor.return_type, &ancestor.native_return_type) |
| 162 | { |
| 163 | existing.return_type = ancestor.return_type.clone(); |
| 164 | } |
| 165 | |
| 166 | // ── Template parameters ───────────────────────────────────── |
| 167 | if existing.template_params.is_empty() && !ancestor.template_params.is_empty() { |
| 168 | existing.template_params = ancestor.template_params.clone(); |
| 169 | existing.template_param_bounds = ancestor.template_param_bounds.clone(); |
| 170 | existing.template_bindings = ancestor.template_bindings.clone(); |
| 171 | // Template return types like `T` only make sense when the |
| 172 | // template params are present — inherit the return type too |
| 173 | // if we haven't already set it. |
| 174 | if existing.return_type.is_none() { |
| 175 | existing.return_type = ancestor.return_type.clone(); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | // ── Conditional return type ───────────────────────────────── |
| 180 | if existing.conditional_return.is_none() && ancestor.conditional_return.is_some() { |
| 181 | existing.conditional_return = ancestor.conditional_return.clone(); |
| 182 | } |
| 183 | |
| 184 | // ── Type assertions ───────────────────────────────────────── |
| 185 | if existing.type_assertions.is_empty() && !ancestor.type_assertions.is_empty() { |
| 186 | existing.type_assertions = ancestor.type_assertions.clone(); |
| 187 | } |
| 188 | |
| 189 | // ── Parameters ────────────────────────────────────────────── |
| 190 | // For constructors, use **name-based** matching instead of |
| 191 | // positional. PHP constructors don't follow Liskov substitution |
| 192 | // — a child constructor can have a completely different signature |
| 193 | // (different parameter count, order, types). Positional |
| 194 | // enrichment would incorrectly map ancestor param types onto |
| 195 | // unrelated child params (e.g. Exception's `$code` type `int` |
| 196 | // onto a child's `$message` param at position 1). |
| 197 | // |
| 198 | // This follows PHPStan's `PhpDocInheritanceResolver`: for |
| 199 | // `__construct` the positional parameter name list falls back to |
| 200 | // the child's own names, so only same-named parameters inherit. |
| 201 | if existing.name == "__construct" { |
| 202 | enrich_constructor_parameters_by_name(&mut existing.parameters, &ancestor.parameters); |
| 203 | } else { |
| 204 | enrich_parameters_from_ancestor(&mut existing.parameters, &ancestor.parameters); |
| 205 | } |
| 206 | |
| 207 | // ── Descriptions ──────────────────────────────────────────── |
| 208 | if existing.description.is_none() && ancestor.description.is_some() { |
| 209 | existing.description = ancestor.description.clone(); |
| 210 | } |
| 211 | if existing.return_description.is_none() && ancestor.return_description.is_some() { |
no test coverage detected