(
scope: &mut ScopeState,
parameter_list: &FunctionLikeParameterList<'_>,
fn_span_start: u32,
inferred_types: &[PhpType],
ctx: &ForwardWalkCtx<'_>,
)
| 807 | } |
| 808 | |
| 809 | fn seed_closure_params( |
| 810 | scope: &mut ScopeState, |
| 811 | parameter_list: &FunctionLikeParameterList<'_>, |
| 812 | fn_span_start: u32, |
| 813 | inferred_types: &[PhpType], |
| 814 | ctx: &ForwardWalkCtx<'_>, |
| 815 | ) { |
| 816 | for (idx, param) in parameter_list.parameters.iter().enumerate() { |
| 817 | let pname = param.variable.name.to_string(); |
| 818 | let is_variadic = param.ellipsis.is_some(); |
| 819 | |
| 820 | let native_type = param.hint.as_ref().map(|h| extract_hint_type(h)); |
| 821 | |
| 822 | // Check the `@param` docblock annotation. |
| 823 | // |
| 824 | // Only trust the result when the docblock is directly attached |
| 825 | // to this closure/arrow function (no intervening code). Without |
| 826 | // this guard, sibling arrow functions that share a parameter |
| 827 | // name (e.g. two `array_map(fn($row) => …)` calls) would leak |
| 828 | // `@param` annotations from one closure to the other, because |
| 829 | // arrow functions don't introduce `{`/`}` scope boundaries and |
| 830 | // `find_iterable_raw_type_in_source` scans backward freely. |
| 831 | let raw_docblock_type = crate::docblock::find_iterable_raw_type_in_source( |
| 832 | ctx.content, |
| 833 | fn_span_start as usize, |
| 834 | &pname, |
| 835 | ) |
| 836 | .filter(|_| is_docblock_adjacent(ctx.content, fn_span_start as usize)) |
| 837 | .map(|t| crate::util::resolve_php_type_names(&t, ctx.class_loader)); |
| 838 | |
| 839 | let effective_type = crate::docblock::resolve_effective_type_typed( |
| 840 | native_type.as_ref(), |
| 841 | raw_docblock_type.as_ref(), |
| 842 | ); |
| 843 | |
| 844 | // Substitute method-level template params with their bounds. |
| 845 | let effective_type = effective_type.map(|ty| { |
| 846 | let ty = super::resolution::substitute_template_param_bounds( |
| 847 | ty, |
| 848 | ctx.content, |
| 849 | fn_span_start as usize, |
| 850 | ); |
| 851 | super::resolution::substitute_class_string_template_bounds( |
| 852 | ty, |
| 853 | ctx.content, |
| 854 | fn_span_start as usize, |
| 855 | ) |
| 856 | }); |
| 857 | |
| 858 | let inferred_for_idx = inferred_types.get(idx); |
| 859 | |
| 860 | // When the explicit hint is a bare class name and the inferred |
| 861 | // type is the same class WITH generic args, prefer the inferred |
| 862 | // type (preserves template substitution). |
| 863 | let use_inferred_over_explicit = if let Some(ref eff) = effective_type |
| 864 | && let Some(inferred) = inferred_for_idx |
| 865 | { |
| 866 | super::closure_resolution::inferred_type_is_more_specific_pub(eff, inferred) |
no test coverage detected