mago_analyzer/plugin/libraries/stdlib/closure/
get_current.rs1use 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#[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}