Skip to main content

mago_analyzer/plugin/libraries/stdlib/closure/
get_current.rs

1//! `Closure::getCurrent()` return type provider.
2
3use mago_codex::ttype::atomic::TAtomic;
4use mago_codex::ttype::atomic::callable::TCallable;
5use mago_codex::ttype::expander::TypeExpansionOptions;
6use mago_codex::ttype::expander::get_signature_of_function_like_metadata;
7use mago_codex::ttype::get_never;
8use mago_codex::ttype::union::TUnion;
9use mago_reporting::Annotation;
10use mago_reporting::Issue;
11
12use crate::code::IssueCode;
13use crate::plugin::context::InvocationInfo;
14use crate::plugin::context::ProviderContext;
15use crate::plugin::provider::Provider;
16use crate::plugin::provider::ProviderMeta;
17use crate::plugin::provider::method::MethodReturnTypeProvider;
18use crate::plugin::provider::method::MethodTarget;
19
20static META: ProviderMeta = ProviderMeta::new(
21    "php::closure::getcurrent",
22    "Closure::getCurrent",
23    "Returns the current closure's signature type",
24);
25
26static TARGETS: [MethodTarget; 1] = [MethodTarget::exact("closure", "getcurrent")];
27
28/// Provider for the `Closure::getCurrent()` method.
29///
30/// Returns the callable signature of the current closure when called from within
31/// a closure or arrow function. Reports an error and returns `never` if called
32/// outside a closure.
33#[derive(Default)]
34pub struct ClosureGetCurrentProvider;
35
36impl Provider for ClosureGetCurrentProvider {
37    fn meta() -> &'static ProviderMeta {
38        &META
39    }
40}
41
42impl MethodReturnTypeProvider for ClosureGetCurrentProvider {
43    fn targets() -> &'static [MethodTarget] {
44        &TARGETS
45    }
46
47    fn get_return_type(
48        &self,
49        context: &ProviderContext<'_, '_, '_>,
50        _class_name: &str,
51        _method_name: &str,
52        invocation: &InvocationInfo<'_, '_, '_>,
53    ) -> Option<TUnion> {
54        let scope = context.scope();
55        let (Some(closure), Some(closure_identifier)) =
56            (scope.get_function_like(), scope.get_function_like_identifier())
57        else {
58            context.report(
59                IssueCode::InvalidStaticMethodCall,
60                Issue::error("`Closure::getCurrent()` must be called from within a closure.")
61                    .with_annotation(
62                        Annotation::primary(invocation.span()).with_message("This call is in the global scope"),
63                    )
64                    .with_note("This method is only available inside a closure or an arrow function to get a reference to itself, which is useful for recursion.")
65                    .with_help("Move this call inside a closure or use a different approach if you are not in a closure context."),
66            );
67
68            return Some(get_never());
69        };
70
71        if !closure_identifier.is_closure() {
72            let kind = closure_identifier.kind_str();
73
74            context.report(
75                IssueCode::InvalidStaticMethodCall,
76                Issue::error(format!(
77                    "`Closure::getCurrent()` must be called from within a closure, but it is currently inside a {kind}."
78                ))
79                .with_annotation(
80                    Annotation::primary(invocation.span())
81                        .with_message(format!("This call is inside a {kind}, not a closure")),
82                )
83                .with_note("This method is only available inside a closure or an arrow function to get a reference to itself, which is useful for recursion.")
84                .with_help("Ensure this method is only called within the body of a closure or an arrow function."),
85            );
86
87            return Some(get_never());
88        }
89
90        let codebase = context.codebase();
91
92        Some(if closure.template_types.is_empty() {
93            TUnion::from_atomic(TAtomic::Callable(TCallable::Signature(get_signature_of_function_like_metadata(
94                &closure_identifier,
95                closure,
96                codebase,
97                &TypeExpansionOptions::default(),
98            ))))
99        } else {
100            TUnion::from_atomic(TAtomic::Callable(TCallable::Alias(closure_identifier)))
101        })
102    }
103}