Skip to main content

mago_analyzer/plugin/
registry.rs

1//! Plugin registry for managing and dispatching to providers and hooks.
2
3use mago_codex::identifier::function_like::FunctionLikeIdentifier;
4use mago_codex::metadata::CodebaseMetadata;
5use mago_codex::metadata::class_like::ClassLikeMetadata;
6use mago_codex::metadata::function_like::FunctionLikeMetadata;
7use mago_codex::metadata::property::PropertyMetadata;
8use mago_codex::ttype::union::TUnion;
9use mago_database::file::File;
10use mago_syntax::cst::Class;
11use mago_syntax::cst::Enum;
12use mago_syntax::cst::Expression;
13use mago_syntax::cst::Function;
14use mago_syntax::cst::FunctionCall;
15use mago_syntax::cst::Interface;
16use mago_syntax::cst::MethodCall;
17use mago_syntax::cst::NullSafeMethodCall;
18use mago_syntax::cst::Program;
19use mago_syntax::cst::Statement;
20use mago_syntax::cst::StaticMethodCall;
21use mago_syntax::cst::Trait;
22use mago_word::Word;
23use mago_word::WordMap;
24use mago_word::WordSet;
25use mago_word::ascii_lowercase_word;
26use mago_word::concat_word;
27
28use crate::artifacts::AnalysisArtifacts;
29use crate::context::block::BlockContext;
30use crate::invocation::Invocation;
31use crate::plugin::context::HookContext;
32use crate::plugin::context::InvocationInfo;
33use crate::plugin::context::ProviderContext;
34use crate::plugin::context::ReportedIssue;
35use crate::plugin::error::PluginResult;
36use crate::plugin::hook::ClassDeclarationHook;
37use crate::plugin::hook::EnumDeclarationHook;
38use crate::plugin::hook::ExpressionHook;
39use crate::plugin::hook::ExpressionHookResult;
40use crate::plugin::hook::FunctionCallHook;
41use crate::plugin::hook::FunctionDeclarationHook;
42use crate::plugin::hook::HookAction;
43use crate::plugin::hook::InterfaceDeclarationHook;
44use crate::plugin::hook::IssueFilterDecision;
45use crate::plugin::hook::IssueFilterHook;
46use crate::plugin::hook::MethodCallHook;
47use crate::plugin::hook::NullSafeMethodCallHook;
48use crate::plugin::hook::ProgramHook;
49use crate::plugin::hook::StatementHook;
50use crate::plugin::hook::StaticMethodCallHook;
51use crate::plugin::hook::TraitDeclarationHook;
52use crate::plugin::provider::assertion::FunctionAssertionProvider;
53use crate::plugin::provider::assertion::InvocationAssertions;
54use crate::plugin::provider::assertion::MethodAssertionProvider;
55use crate::plugin::provider::function::FunctionReturnTypeProvider;
56use crate::plugin::provider::function::FunctionTarget;
57use crate::plugin::provider::method::MethodReturnTypeProvider;
58use crate::plugin::provider::method::MethodTarget;
59use crate::plugin::provider::property::PropertyInitializationProvider;
60use crate::plugin::provider::throw::ExpressionThrowTypeProvider;
61use crate::plugin::provider::throw::FunctionThrowTypeProvider;
62use crate::plugin::provider::throw::MethodThrowTypeProvider;
63
64use mago_reporting::IssueCollection;
65
66pub struct ProviderResult {
67    pub return_type: Option<TUnion>,
68    pub issues: Vec<ReportedIssue>,
69}
70
71#[derive(Default)]
72pub struct PluginRegistry {
73    function_exact: WordMap<Vec<usize>>,
74    function_prefix: Vec<(Word, usize)>,
75    function_namespace: Vec<(Word, usize)>,
76    function_providers: Vec<Box<dyn FunctionReturnTypeProvider>>,
77    method_exact: WordMap<Vec<usize>>,
78    method_wildcard: Vec<(Vec<MethodTarget>, usize)>,
79    method_providers: Vec<Box<dyn MethodReturnTypeProvider>>,
80    program_hooks: Vec<Box<dyn ProgramHook>>,
81    statement_hooks: Vec<Box<dyn StatementHook>>,
82    expression_hooks: Vec<Box<dyn ExpressionHook>>,
83    function_call_hooks: Vec<Box<dyn FunctionCallHook>>,
84    method_call_hooks: Vec<Box<dyn MethodCallHook>>,
85    static_method_call_hooks: Vec<Box<dyn StaticMethodCallHook>>,
86    nullsafe_method_call_hooks: Vec<Box<dyn NullSafeMethodCallHook>>,
87    class_hooks: Vec<Box<dyn ClassDeclarationHook>>,
88    interface_hooks: Vec<Box<dyn InterfaceDeclarationHook>>,
89    trait_hooks: Vec<Box<dyn TraitDeclarationHook>>,
90    enum_hooks: Vec<Box<dyn EnumDeclarationHook>>,
91    function_decl_hooks: Vec<Box<dyn FunctionDeclarationHook>>,
92    property_initialization_providers: Vec<Box<dyn PropertyInitializationProvider>>,
93    issue_filter_hooks: Vec<Box<dyn IssueFilterHook>>,
94    function_assertion_exact: WordMap<Vec<usize>>,
95    function_assertion_prefix: Vec<(Word, usize)>,
96    function_assertion_namespace: Vec<(Word, usize)>,
97    function_assertion_providers: Vec<Box<dyn FunctionAssertionProvider>>,
98    method_assertion_exact: WordMap<Vec<usize>>,
99    method_assertion_wildcard: Vec<(Vec<MethodTarget>, usize)>,
100    method_assertion_providers: Vec<Box<dyn MethodAssertionProvider>>,
101    expression_throw_providers: Vec<Box<dyn ExpressionThrowTypeProvider>>,
102    function_throw_exact: WordMap<Vec<usize>>,
103    function_throw_prefix: Vec<(Word, usize)>,
104    function_throw_namespace: Vec<(Word, usize)>,
105    function_throw_providers: Vec<Box<dyn FunctionThrowTypeProvider>>,
106    method_throw_exact: WordMap<Vec<usize>>,
107    method_throw_wildcard: Vec<(Vec<MethodTarget>, usize)>,
108    method_throw_providers: Vec<Box<dyn MethodThrowTypeProvider>>,
109}
110
111#[allow(clippy::missing_fields_in_debug)]
112impl std::fmt::Debug for PluginRegistry {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.debug_struct("PluginRegistry")
115            .field("function_providers", &self.function_providers.len())
116            .field("method_providers", &self.method_providers.len())
117            .field("program_hooks", &self.program_hooks.len())
118            .field("statement_hooks", &self.statement_hooks.len())
119            .field("expression_hooks", &self.expression_hooks.len())
120            .field("function_call_hooks", &self.function_call_hooks.len())
121            .field("method_call_hooks", &self.method_call_hooks.len())
122            .field("static_method_call_hooks", &self.static_method_call_hooks.len())
123            .field("nullsafe_method_call_hooks", &self.nullsafe_method_call_hooks.len())
124            .field("class_hooks", &self.class_hooks.len())
125            .field("interface_hooks", &self.interface_hooks.len())
126            .field("trait_hooks", &self.trait_hooks.len())
127            .field("enum_hooks", &self.enum_hooks.len())
128            .field("function_decl_hooks", &self.function_decl_hooks.len())
129            .field("property_initialization_providers", &self.property_initialization_providers.len())
130            .field("issue_filter_hooks", &self.issue_filter_hooks.len())
131            .field("function_assertion_providers", &self.function_assertion_providers.len())
132            .field("method_assertion_providers", &self.method_assertion_providers.len())
133            .field("expression_throw_providers", &self.expression_throw_providers.len())
134            .field("function_throw_providers", &self.function_throw_providers.len())
135            .field("method_throw_providers", &self.method_throw_providers.len())
136            .finish()
137    }
138}
139
140impl PluginRegistry {
141    #[inline]
142    #[must_use]
143    pub fn new() -> Self {
144        Self::default()
145    }
146
147    #[must_use]
148    pub fn with_library_providers() -> Self {
149        crate::plugin::create_registry()
150    }
151
152    pub fn register_function_provider<P>(&mut self, provider: P)
153    where
154        P: FunctionReturnTypeProvider + 'static,
155    {
156        let index = self.function_providers.len();
157
158        match P::targets() {
159            FunctionTarget::Exact(name) => {
160                self.function_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
161            }
162            FunctionTarget::ExactMultiple(names) => {
163                for name in names {
164                    self.function_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
165                }
166            }
167            FunctionTarget::Prefix(prefix) => {
168                self.function_prefix.push((ascii_lowercase_word(prefix), index));
169            }
170            FunctionTarget::Namespace(ns) => {
171                let ns_lower = ascii_lowercase_word(ns);
172                let ns_pattern = if ns_lower.as_bytes().last() == Some(&b'\\') {
173                    ns_lower
174                } else {
175                    concat_word!(ns_lower.as_bytes(), b"\\")
176                };
177                self.function_namespace.push((ns_pattern, index));
178            }
179        }
180
181        self.function_providers.push(Box::new(provider));
182    }
183
184    pub fn register_method_provider<P>(&mut self, provider: P)
185    where
186        P: MethodReturnTypeProvider + 'static,
187    {
188        let index = self.method_providers.len();
189        let targets = P::targets();
190
191        let mut has_wildcards = false;
192        let mut wildcard_targets = Vec::new();
193
194        for target in targets {
195            if let Some(key) = target.index_key() {
196                self.method_exact.entry(key).or_default().push(index);
197            } else {
198                has_wildcards = true;
199                wildcard_targets.push(*target);
200            }
201        }
202
203        if has_wildcards {
204            self.method_wildcard.push((wildcard_targets, index));
205        }
206
207        self.method_providers.push(Box::new(provider));
208    }
209
210    pub fn register_program_hook<H>(&mut self, hook: H)
211    where
212        H: ProgramHook + 'static,
213    {
214        self.program_hooks.push(Box::new(hook));
215    }
216
217    pub fn register_statement_hook<H>(&mut self, hook: H)
218    where
219        H: StatementHook + 'static,
220    {
221        self.statement_hooks.push(Box::new(hook));
222    }
223
224    pub fn register_expression_hook<H>(&mut self, hook: H)
225    where
226        H: ExpressionHook + 'static,
227    {
228        self.expression_hooks.push(Box::new(hook));
229    }
230
231    pub fn register_function_call_hook<H>(&mut self, hook: H)
232    where
233        H: FunctionCallHook + 'static,
234    {
235        self.function_call_hooks.push(Box::new(hook));
236    }
237
238    pub fn register_method_call_hook<H>(&mut self, hook: H)
239    where
240        H: MethodCallHook + 'static,
241    {
242        self.method_call_hooks.push(Box::new(hook));
243    }
244
245    pub fn register_static_method_call_hook<H>(&mut self, hook: H)
246    where
247        H: StaticMethodCallHook + 'static,
248    {
249        self.static_method_call_hooks.push(Box::new(hook));
250    }
251
252    pub fn register_nullsafe_method_call_hook<H>(&mut self, hook: H)
253    where
254        H: NullSafeMethodCallHook + 'static,
255    {
256        self.nullsafe_method_call_hooks.push(Box::new(hook));
257    }
258
259    pub fn register_class_hook<H>(&mut self, hook: H)
260    where
261        H: ClassDeclarationHook + 'static,
262    {
263        self.class_hooks.push(Box::new(hook));
264    }
265
266    pub fn register_interface_hook<H>(&mut self, hook: H)
267    where
268        H: InterfaceDeclarationHook + 'static,
269    {
270        self.interface_hooks.push(Box::new(hook));
271    }
272
273    pub fn register_trait_hook<H>(&mut self, hook: H)
274    where
275        H: TraitDeclarationHook + 'static,
276    {
277        self.trait_hooks.push(Box::new(hook));
278    }
279
280    pub fn register_enum_hook<H>(&mut self, hook: H)
281    where
282        H: EnumDeclarationHook + 'static,
283    {
284        self.enum_hooks.push(Box::new(hook));
285    }
286
287    pub fn register_function_decl_hook<H>(&mut self, hook: H)
288    where
289        H: FunctionDeclarationHook + 'static,
290    {
291        self.function_decl_hooks.push(Box::new(hook));
292    }
293
294    pub fn register_property_initialization_provider<P>(&mut self, provider: P)
295    where
296        P: PropertyInitializationProvider + 'static,
297    {
298        self.property_initialization_providers.push(Box::new(provider));
299    }
300
301    pub fn register_issue_filter_hook<H>(&mut self, hook: H)
302    where
303        H: IssueFilterHook + 'static,
304    {
305        self.issue_filter_hooks.push(Box::new(hook));
306    }
307
308    pub fn register_function_assertion_provider<P>(&mut self, provider: P)
309    where
310        P: FunctionAssertionProvider + 'static,
311    {
312        let index = self.function_assertion_providers.len();
313
314        match P::targets() {
315            FunctionTarget::Exact(name) => {
316                self.function_assertion_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
317            }
318            FunctionTarget::ExactMultiple(names) => {
319                for name in names {
320                    self.function_assertion_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
321                }
322            }
323            FunctionTarget::Prefix(prefix) => {
324                self.function_assertion_prefix.push((ascii_lowercase_word(prefix), index));
325            }
326            FunctionTarget::Namespace(ns) => {
327                let ns_lower = ascii_lowercase_word(ns);
328                let ns_pattern = if ns_lower.as_bytes().last() == Some(&b'\\') {
329                    ns_lower
330                } else {
331                    concat_word!(ns_lower.as_bytes(), b"\\")
332                };
333                self.function_assertion_namespace.push((ns_pattern, index));
334            }
335        }
336
337        self.function_assertion_providers.push(Box::new(provider));
338    }
339
340    pub fn register_method_assertion_provider<P>(&mut self, provider: P)
341    where
342        P: MethodAssertionProvider + 'static,
343    {
344        let index = self.method_assertion_providers.len();
345        let targets = P::targets();
346
347        let mut has_wildcards = false;
348        let mut wildcard_targets = Vec::new();
349
350        for target in targets {
351            if let Some(key) = target.index_key() {
352                self.method_assertion_exact.entry(key).or_default().push(index);
353            } else {
354                has_wildcards = true;
355                wildcard_targets.push(*target);
356            }
357        }
358
359        if has_wildcards {
360            self.method_assertion_wildcard.push((wildcard_targets, index));
361        }
362
363        self.method_assertion_providers.push(Box::new(provider));
364    }
365
366    pub fn register_expression_throw_provider<P>(&mut self, provider: P)
367    where
368        P: ExpressionThrowTypeProvider + 'static,
369    {
370        self.expression_throw_providers.push(Box::new(provider));
371    }
372
373    pub fn register_function_throw_provider<P>(&mut self, provider: P)
374    where
375        P: FunctionThrowTypeProvider + 'static,
376    {
377        let index = self.function_throw_providers.len();
378
379        match P::targets() {
380            FunctionTarget::Exact(name) => {
381                self.function_throw_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
382            }
383            FunctionTarget::ExactMultiple(names) => {
384                for name in names {
385                    self.function_throw_exact.entry(ascii_lowercase_word(name)).or_default().push(index);
386                }
387            }
388            FunctionTarget::Prefix(prefix) => {
389                self.function_throw_prefix.push((ascii_lowercase_word(prefix), index));
390            }
391            FunctionTarget::Namespace(ns) => {
392                let ns_lower = ascii_lowercase_word(ns);
393                let ns_pattern = if ns_lower.as_bytes().last() == Some(&b'\\') {
394                    ns_lower
395                } else {
396                    concat_word!(ns_lower.as_bytes(), b"\\")
397                };
398                self.function_throw_namespace.push((ns_pattern, index));
399            }
400        }
401
402        self.function_throw_providers.push(Box::new(provider));
403    }
404
405    pub fn register_method_throw_provider<P>(&mut self, provider: P)
406    where
407        P: MethodThrowTypeProvider + 'static,
408    {
409        let index = self.method_throw_providers.len();
410        let targets = P::targets();
411
412        let mut has_wildcards = false;
413        let mut wildcard_targets = Vec::new();
414
415        for target in targets {
416            if let Some(key) = target.index_key() {
417                self.method_throw_exact.entry(key).or_default().push(index);
418            } else {
419                has_wildcards = true;
420                wildcard_targets.push(*target);
421            }
422        }
423
424        if has_wildcards {
425            self.method_throw_wildcard.push((wildcard_targets, index));
426        }
427
428        self.method_throw_providers.push(Box::new(provider));
429    }
430
431    #[inline]
432    #[must_use]
433    pub fn has_program_hooks(&self) -> bool {
434        !self.program_hooks.is_empty()
435    }
436
437    #[inline]
438    #[must_use]
439    pub fn has_statement_hooks(&self) -> bool {
440        !self.statement_hooks.is_empty()
441    }
442
443    #[inline]
444    #[must_use]
445    pub fn has_expression_hooks(&self) -> bool {
446        !self.expression_hooks.is_empty()
447    }
448
449    #[inline]
450    #[must_use]
451    pub fn has_function_call_hooks(&self) -> bool {
452        !self.function_call_hooks.is_empty()
453    }
454
455    #[inline]
456    #[must_use]
457    pub fn has_method_call_hooks(&self) -> bool {
458        !self.method_call_hooks.is_empty()
459    }
460
461    #[inline]
462    #[must_use]
463    pub fn has_static_method_call_hooks(&self) -> bool {
464        !self.static_method_call_hooks.is_empty()
465    }
466
467    #[inline]
468    #[must_use]
469    pub fn has_nullsafe_method_call_hooks(&self) -> bool {
470        !self.nullsafe_method_call_hooks.is_empty()
471    }
472
473    #[inline]
474    #[must_use]
475    pub fn has_class_hooks(&self) -> bool {
476        !self.class_hooks.is_empty()
477    }
478
479    #[inline]
480    #[must_use]
481    pub fn has_interface_hooks(&self) -> bool {
482        !self.interface_hooks.is_empty()
483    }
484
485    #[inline]
486    #[must_use]
487    pub fn has_trait_hooks(&self) -> bool {
488        !self.trait_hooks.is_empty()
489    }
490
491    #[inline]
492    #[must_use]
493    pub fn has_enum_hooks(&self) -> bool {
494        !self.enum_hooks.is_empty()
495    }
496
497    #[inline]
498    #[must_use]
499    pub fn has_function_decl_hooks(&self) -> bool {
500        !self.function_decl_hooks.is_empty()
501    }
502
503    #[inline]
504    #[must_use]
505    pub fn has_property_initialization_providers(&self) -> bool {
506        !self.property_initialization_providers.is_empty()
507    }
508
509    #[inline]
510    #[must_use]
511    pub fn has_issue_filter_hooks(&self) -> bool {
512        !self.issue_filter_hooks.is_empty()
513    }
514
515    #[inline]
516    #[must_use]
517    pub fn has_function_assertion_providers(&self) -> bool {
518        !self.function_assertion_providers.is_empty()
519    }
520
521    #[inline]
522    #[must_use]
523    pub fn has_method_assertion_providers(&self) -> bool {
524        !self.method_assertion_providers.is_empty()
525    }
526
527    #[inline]
528    #[must_use]
529    pub fn has_expression_throw_providers(&self) -> bool {
530        !self.expression_throw_providers.is_empty()
531    }
532
533    #[inline]
534    #[must_use]
535    pub fn has_function_throw_providers(&self) -> bool {
536        !self.function_throw_providers.is_empty()
537    }
538
539    #[inline]
540    #[must_use]
541    pub fn has_method_throw_providers(&self) -> bool {
542        !self.method_throw_providers.is_empty()
543    }
544
545    /// Run all registered program hooks before analysis.
546    ///
547    /// # Errors
548    ///
549    /// Returns [`PluginError`] if any registered hook propagates one.
550    pub fn before_program(
551        &self,
552        file: &File,
553        program: &Program<'_>,
554        context: &mut HookContext<'_, '_>,
555    ) -> PluginResult<HookAction> {
556        for hook in &self.program_hooks {
557            if hook.before_program(file, program, context)? == HookAction::Skip {
558                return Ok(HookAction::Skip);
559            }
560        }
561        Ok(HookAction::Continue)
562    }
563
564    /// Run all registered program hooks after analysis.
565    ///
566    /// # Errors
567    ///
568    /// Returns [`PluginError`] if any registered hook propagates one.
569    pub fn after_program(
570        &self,
571        file: &File,
572        program: &Program<'_>,
573        context: &mut HookContext<'_, '_>,
574    ) -> PluginResult<()> {
575        for hook in &self.program_hooks {
576            hook.after_program(file, program, context)?;
577        }
578        Ok(())
579    }
580
581    /// Run all registered statement hooks before analysis.
582    ///
583    /// # Errors
584    ///
585    /// Returns [`PluginError`] if any registered hook propagates one.
586    pub fn before_statement(
587        &self,
588        stmt: &Statement<'_>,
589        context: &mut HookContext<'_, '_>,
590    ) -> PluginResult<HookAction> {
591        for hook in &self.statement_hooks {
592            if hook.before_statement(stmt, context)? == HookAction::Skip {
593                return Ok(HookAction::Skip);
594            }
595        }
596        Ok(HookAction::Continue)
597    }
598
599    /// Run all registered statement hooks after analysis.
600    ///
601    /// # Errors
602    ///
603    /// Returns [`PluginError`] if any registered hook propagates one.
604    pub fn after_statement(&self, stmt: &Statement<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
605        for hook in &self.statement_hooks {
606            hook.after_statement(stmt, context)?;
607        }
608        Ok(())
609    }
610
611    /// Run all registered expression hooks before analysis.
612    ///
613    /// # Errors
614    ///
615    /// Returns [`PluginError`] if any registered hook propagates one.
616    pub fn before_expression(
617        &self,
618        expr: &Expression<'_>,
619        context: &mut HookContext<'_, '_>,
620    ) -> PluginResult<ExpressionHookResult> {
621        for hook in &self.expression_hooks {
622            let result = hook.before_expression(expr, context)?;
623            if result.should_skip() {
624                return Ok(result);
625            }
626        }
627        Ok(ExpressionHookResult::Continue)
628    }
629
630    /// Run all registered expression hooks after analysis.
631    ///
632    /// # Errors
633    ///
634    /// Returns [`PluginError`] if any registered hook propagates one.
635    pub fn after_expression(&self, expr: &Expression<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
636        for hook in &self.expression_hooks {
637            hook.after_expression(expr, context)?;
638        }
639        Ok(())
640    }
641
642    /// Run all registered function call hooks before analysis.
643    ///
644    /// # Errors
645    ///
646    /// Returns [`PluginError`] if any registered hook propagates one.
647    pub fn before_function_call(
648        &self,
649        call: &FunctionCall<'_>,
650        context: &mut HookContext<'_, '_>,
651    ) -> PluginResult<ExpressionHookResult> {
652        for hook in &self.function_call_hooks {
653            let result = hook.before_function_call(call, context)?;
654            if result.should_skip() {
655                return Ok(result);
656            }
657        }
658        Ok(ExpressionHookResult::Continue)
659    }
660
661    /// Run all registered function call hooks after analysis.
662    ///
663    /// # Errors
664    ///
665    /// Returns [`PluginError`] if any registered hook propagates one.
666    pub fn after_function_call(&self, call: &FunctionCall<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
667        for hook in &self.function_call_hooks {
668            hook.after_function_call(call, context)?;
669        }
670        Ok(())
671    }
672
673    /// Run all registered method call hooks before analysis.
674    ///
675    /// # Errors
676    ///
677    /// Returns [`PluginError`] if any registered hook propagates one.
678    pub fn before_method_call(
679        &self,
680        call: &MethodCall<'_>,
681        context: &mut HookContext<'_, '_>,
682    ) -> PluginResult<ExpressionHookResult> {
683        for hook in &self.method_call_hooks {
684            let result = hook.before_method_call(call, context)?;
685            if result.should_skip() {
686                return Ok(result);
687            }
688        }
689        Ok(ExpressionHookResult::Continue)
690    }
691
692    /// Run all registered method call hooks after analysis.
693    ///
694    /// # Errors
695    ///
696    /// Returns [`PluginError`] if any registered hook propagates one.
697    pub fn after_method_call(&self, call: &MethodCall<'_>, context: &mut HookContext<'_, '_>) -> PluginResult<()> {
698        for hook in &self.method_call_hooks {
699            hook.after_method_call(call, context)?;
700        }
701        Ok(())
702    }
703
704    /// Run all registered static method call hooks before analysis.
705    ///
706    /// # Errors
707    ///
708    /// Returns [`PluginError`] if any registered hook propagates one.
709    pub fn before_static_method_call(
710        &self,
711        call: &StaticMethodCall<'_>,
712        context: &mut HookContext<'_, '_>,
713    ) -> PluginResult<ExpressionHookResult> {
714        for hook in &self.static_method_call_hooks {
715            let result = hook.before_static_method_call(call, context)?;
716            if result.should_skip() {
717                return Ok(result);
718            }
719        }
720        Ok(ExpressionHookResult::Continue)
721    }
722
723    /// Run all registered static method call hooks after analysis.
724    ///
725    /// # Errors
726    ///
727    /// Returns [`PluginError`] if any registered hook propagates one.
728    pub fn after_static_method_call(
729        &self,
730        call: &StaticMethodCall<'_>,
731        context: &mut HookContext<'_, '_>,
732    ) -> PluginResult<()> {
733        for hook in &self.static_method_call_hooks {
734            hook.after_static_method_call(call, context)?;
735        }
736        Ok(())
737    }
738
739    /// Run all registered nullsafe method call hooks before analysis.
740    ///
741    /// # Errors
742    ///
743    /// Returns [`PluginError`] if any registered hook propagates one.
744    pub fn before_nullsafe_method_call(
745        &self,
746        call: &NullSafeMethodCall<'_>,
747        context: &mut HookContext<'_, '_>,
748    ) -> PluginResult<ExpressionHookResult> {
749        for hook in &self.nullsafe_method_call_hooks {
750            let result = hook.before_nullsafe_method_call(call, context)?;
751            if result.should_skip() {
752                return Ok(result);
753            }
754        }
755        Ok(ExpressionHookResult::Continue)
756    }
757
758    /// Run all registered nullsafe method call hooks after analysis.
759    ///
760    /// # Errors
761    ///
762    /// Returns [`PluginError`] if any registered hook propagates one.
763    pub fn after_nullsafe_method_call(
764        &self,
765        call: &NullSafeMethodCall<'_>,
766        context: &mut HookContext<'_, '_>,
767    ) -> PluginResult<()> {
768        for hook in &self.nullsafe_method_call_hooks {
769            hook.after_nullsafe_method_call(call, context)?;
770        }
771        Ok(())
772    }
773
774    /// Run all registered class declaration hooks on entry.
775    ///
776    /// # Errors
777    ///
778    /// Returns [`PluginError`] if any registered hook propagates one.
779    pub fn on_enter_class(
780        &self,
781        class: &Class<'_>,
782        metadata: &ClassLikeMetadata,
783        context: &mut HookContext<'_, '_>,
784    ) -> PluginResult<()> {
785        for hook in &self.class_hooks {
786            hook.on_enter_class(class, metadata, context)?;
787        }
788        Ok(())
789    }
790
791    /// Run all registered class declaration hooks on exit.
792    ///
793    /// # Errors
794    ///
795    /// Returns [`PluginError`] if any registered hook propagates one.
796    pub fn on_leave_class(
797        &self,
798        class: &Class<'_>,
799        metadata: &ClassLikeMetadata,
800        context: &mut HookContext<'_, '_>,
801    ) -> PluginResult<()> {
802        for hook in &self.class_hooks {
803            hook.on_leave_class(class, metadata, context)?;
804        }
805        Ok(())
806    }
807
808    /// Run all registered interface declaration hooks on entry.
809    ///
810    /// # Errors
811    ///
812    /// Returns [`PluginError`] if any registered hook propagates one.
813    pub fn on_enter_interface(
814        &self,
815        interface: &Interface<'_>,
816        metadata: &ClassLikeMetadata,
817        context: &mut HookContext<'_, '_>,
818    ) -> PluginResult<()> {
819        for hook in &self.interface_hooks {
820            hook.on_enter_interface(interface, metadata, context)?;
821        }
822        Ok(())
823    }
824
825    /// Run all registered interface declaration hooks on exit.
826    ///
827    /// # Errors
828    ///
829    /// Returns [`PluginError`] if any registered hook propagates one.
830    pub fn on_leave_interface(
831        &self,
832        interface: &Interface<'_>,
833        metadata: &ClassLikeMetadata,
834        context: &mut HookContext<'_, '_>,
835    ) -> PluginResult<()> {
836        for hook in &self.interface_hooks {
837            hook.on_leave_interface(interface, metadata, context)?;
838        }
839        Ok(())
840    }
841
842    /// Run all registered trait declaration hooks on entry.
843    ///
844    /// # Errors
845    ///
846    /// Returns [`PluginError`] if any registered hook propagates one.
847    pub fn on_enter_trait(
848        &self,
849        trait_: &Trait<'_>,
850        metadata: &ClassLikeMetadata,
851        context: &mut HookContext<'_, '_>,
852    ) -> PluginResult<()> {
853        for hook in &self.trait_hooks {
854            hook.on_enter_trait(trait_, metadata, context)?;
855        }
856        Ok(())
857    }
858
859    /// Run all registered trait declaration hooks on exit.
860    ///
861    /// # Errors
862    ///
863    /// Returns [`PluginError`] if any registered hook propagates one.
864    pub fn on_leave_trait(
865        &self,
866        trait_: &Trait<'_>,
867        metadata: &ClassLikeMetadata,
868        context: &mut HookContext<'_, '_>,
869    ) -> PluginResult<()> {
870        for hook in &self.trait_hooks {
871            hook.on_leave_trait(trait_, metadata, context)?;
872        }
873        Ok(())
874    }
875
876    /// Run all registered enum declaration hooks on entry.
877    ///
878    /// # Errors
879    ///
880    /// Returns [`PluginError`] if any registered hook propagates one.
881    pub fn on_enter_enum(
882        &self,
883        enum_: &Enum<'_>,
884        metadata: &ClassLikeMetadata,
885        context: &mut HookContext<'_, '_>,
886    ) -> PluginResult<()> {
887        for hook in &self.enum_hooks {
888            hook.on_enter_enum(enum_, metadata, context)?;
889        }
890        Ok(())
891    }
892
893    /// Run all registered enum declaration hooks on exit.
894    ///
895    /// # Errors
896    ///
897    /// Returns [`PluginError`] if any registered hook propagates one.
898    pub fn on_leave_enum(
899        &self,
900        enum_: &Enum<'_>,
901        metadata: &ClassLikeMetadata,
902        context: &mut HookContext<'_, '_>,
903    ) -> PluginResult<()> {
904        for hook in &self.enum_hooks {
905            hook.on_leave_enum(enum_, metadata, context)?;
906        }
907        Ok(())
908    }
909
910    /// Run all registered function declaration hooks on entry.
911    ///
912    /// # Errors
913    ///
914    /// Returns [`PluginError`] if any registered hook propagates one.
915    pub fn on_enter_function(
916        &self,
917        function: &Function<'_>,
918        metadata: &FunctionLikeMetadata,
919        context: &mut HookContext<'_, '_>,
920    ) -> PluginResult<()> {
921        for hook in &self.function_decl_hooks {
922            hook.on_enter_function(function, metadata, context)?;
923        }
924        Ok(())
925    }
926
927    /// Run all registered function declaration hooks on exit.
928    ///
929    /// # Errors
930    ///
931    /// Returns [`PluginError`] if any registered hook propagates one.
932    pub fn on_leave_function(
933        &self,
934        function: &Function<'_>,
935        metadata: &FunctionLikeMetadata,
936        context: &mut HookContext<'_, '_>,
937    ) -> PluginResult<()> {
938        for hook in &self.function_decl_hooks {
939            hook.on_leave_function(function, metadata, context)?;
940        }
941        Ok(())
942    }
943
944    fn get_function_provider_indices(&self, name: &[u8]) -> Vec<usize> {
945        let lower_name = ascii_lowercase_word(name);
946        let mut indices = Vec::new();
947
948        if let Some(idxs) = self.function_exact.get(&lower_name) {
949            indices.extend(idxs.iter().copied());
950        }
951
952        for (prefix, idx) in &self.function_prefix {
953            if lower_name.as_bytes().starts_with(prefix.as_bytes()) && !indices.contains(idx) {
954                indices.push(*idx);
955            }
956        }
957
958        for (ns, idx) in &self.function_namespace {
959            if lower_name.as_bytes().starts_with(ns.as_bytes()) && !indices.contains(idx) {
960                indices.push(*idx);
961            }
962        }
963
964        indices
965    }
966
967    fn get_method_provider_indices(&self, class_name: &[u8], method_name: &[u8]) -> Vec<usize> {
968        use mago_word::concat_word;
969        let key = concat_word!(ascii_lowercase_word(class_name), b"::", ascii_lowercase_word(method_name));
970        let mut indices = Vec::new();
971
972        if let Some(idxs) = self.method_exact.get(&key) {
973            indices.extend(idxs.iter().copied());
974        }
975
976        for (targets, idx) in &self.method_wildcard {
977            if !indices.contains(idx) {
978                for target in targets {
979                    if target.matches(class_name, method_name) {
980                        indices.push(*idx);
981                        break;
982                    }
983                }
984            }
985        }
986
987        indices
988    }
989
990    #[must_use]
991    pub fn get_function_like_return_type<'ctx>(
992        &self,
993        codebase: &'ctx CodebaseMetadata,
994        source_file: &'ctx File,
995        block_context: &BlockContext<'ctx>,
996        artifacts: &AnalysisArtifacts,
997        function_like: &FunctionLikeIdentifier,
998        invocation: &Invocation<'ctx, '_, '_>,
999    ) -> Option<ProviderResult> {
1000        match function_like {
1001            FunctionLikeIdentifier::Function(name) => Some(self.get_function_return_type(
1002                codebase,
1003                source_file,
1004                block_context,
1005                artifacts,
1006                name.as_bytes(),
1007                invocation,
1008            )),
1009            FunctionLikeIdentifier::Method(class_name, method_name) => Some(self.get_method_return_type(
1010                codebase,
1011                source_file,
1012                block_context,
1013                artifacts,
1014                class_name.as_bytes(),
1015                method_name.as_bytes(),
1016                invocation,
1017            )),
1018            _ => None,
1019        }
1020    }
1021
1022    #[must_use]
1023    pub fn get_function_return_type<'ctx>(
1024        &self,
1025        codebase: &'ctx CodebaseMetadata,
1026        source_file: &'ctx File,
1027        block_context: &BlockContext<'ctx>,
1028        artifacts: &AnalysisArtifacts,
1029        function_name: &[u8],
1030        invocation: &Invocation<'ctx, '_, '_>,
1031    ) -> ProviderResult {
1032        let indices = self.get_function_provider_indices(function_name);
1033        let mut all_issues = Vec::new();
1034
1035        for idx in indices {
1036            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1037            let invocation_info = InvocationInfo::new(invocation);
1038
1039            if let Some(ty) = self.function_providers[idx].get_return_type(&provider_context, &invocation_info) {
1040                all_issues.extend(provider_context.take_issues());
1041                return ProviderResult { return_type: Some(ty), issues: all_issues };
1042            }
1043
1044            all_issues.extend(provider_context.take_issues());
1045        }
1046
1047        ProviderResult { return_type: None, issues: all_issues }
1048    }
1049
1050    #[must_use]
1051    pub fn get_method_return_type<'ctx>(
1052        &self,
1053        codebase: &'ctx CodebaseMetadata,
1054        source_file: &'ctx File,
1055        block_context: &BlockContext<'ctx>,
1056        artifacts: &AnalysisArtifacts,
1057        class_name: &[u8],
1058        method_name: &[u8],
1059        invocation: &Invocation<'ctx, '_, '_>,
1060    ) -> ProviderResult {
1061        let indices = self.get_method_provider_indices(class_name, method_name);
1062        let mut all_issues = Vec::new();
1063
1064        for idx in indices {
1065            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1066            let invocation_info = InvocationInfo::new(invocation);
1067
1068            if let Some(ty) =
1069                self.method_providers[idx].get_return_type(&provider_context, class_name, method_name, &invocation_info)
1070            {
1071                all_issues.extend(provider_context.take_issues());
1072                return ProviderResult { return_type: Some(ty), issues: all_issues };
1073            }
1074
1075            all_issues.extend(provider_context.take_issues());
1076        }
1077
1078        ProviderResult { return_type: None, issues: all_issues }
1079    }
1080
1081    #[inline]
1082    #[must_use]
1083    pub fn function_provider_count(&self) -> usize {
1084        self.function_providers.len()
1085    }
1086
1087    #[inline]
1088    #[must_use]
1089    pub fn method_provider_count(&self) -> usize {
1090        self.method_providers.len()
1091    }
1092
1093    /// Check if a property should be considered initialized by any registered provider.
1094    ///
1095    /// Returns `true` if any provider considers the property initialized.
1096    #[must_use]
1097    pub fn is_property_initialized(
1098        &self,
1099        class_metadata: &ClassLikeMetadata,
1100        property_metadata: &PropertyMetadata,
1101    ) -> bool {
1102        for provider in &self.property_initialization_providers {
1103            if provider.is_property_initialized(class_metadata, property_metadata) {
1104                return true;
1105            }
1106        }
1107
1108        false
1109    }
1110
1111    fn get_function_assertion_provider_indices(&self, name: &[u8]) -> Vec<usize> {
1112        if self.function_assertion_exact.is_empty()
1113            && self.function_assertion_prefix.is_empty()
1114            && self.function_assertion_namespace.is_empty()
1115        {
1116            return Vec::new();
1117        }
1118
1119        let lower_name = ascii_lowercase_word(name);
1120        let mut indices = Vec::new();
1121
1122        if let Some(idxs) = self.function_assertion_exact.get(&lower_name) {
1123            indices.extend(idxs.iter().copied());
1124        }
1125
1126        for (prefix, idx) in &self.function_assertion_prefix {
1127            if lower_name.as_bytes().starts_with(prefix.as_bytes()) && !indices.contains(idx) {
1128                indices.push(*idx);
1129            }
1130        }
1131
1132        for (ns, idx) in &self.function_assertion_namespace {
1133            if lower_name.as_bytes().starts_with(ns.as_bytes()) && !indices.contains(idx) {
1134                indices.push(*idx);
1135            }
1136        }
1137
1138        indices
1139    }
1140
1141    fn get_method_assertion_provider_indices(&self, class_name: &[u8], method_name: &[u8]) -> Vec<usize> {
1142        if self.method_assertion_exact.is_empty() && self.method_assertion_wildcard.is_empty() {
1143            return Vec::new();
1144        }
1145
1146        use mago_word::concat_word;
1147        let key = concat_word!(ascii_lowercase_word(class_name), b"::", ascii_lowercase_word(method_name));
1148        let mut indices = Vec::new();
1149
1150        if let Some(idxs) = self.method_assertion_exact.get(&key) {
1151            indices.extend(idxs.iter().copied());
1152        }
1153
1154        for (targets, idx) in &self.method_assertion_wildcard {
1155            if !indices.contains(idx) {
1156                for target in targets {
1157                    if target.matches(class_name, method_name) {
1158                        indices.push(*idx);
1159                        break;
1160                    }
1161                }
1162            }
1163        }
1164
1165        indices
1166    }
1167
1168    #[must_use]
1169    pub fn get_function_like_assertions<'ctx>(
1170        &self,
1171        codebase: &'ctx CodebaseMetadata,
1172        source_file: &'ctx File,
1173        block_context: &BlockContext<'ctx>,
1174        artifacts: &AnalysisArtifacts,
1175        function_like: &FunctionLikeIdentifier,
1176        invocation: &Invocation<'ctx, '_, '_>,
1177    ) -> Option<InvocationAssertions> {
1178        match function_like {
1179            FunctionLikeIdentifier::Function(name) => self.get_function_assertions(
1180                codebase,
1181                source_file,
1182                block_context,
1183                artifacts,
1184                name.as_bytes(),
1185                invocation,
1186            ),
1187            FunctionLikeIdentifier::Method(class_name, method_name) => self.get_method_assertions(
1188                codebase,
1189                source_file,
1190                block_context,
1191                artifacts,
1192                class_name.as_bytes(),
1193                method_name.as_bytes(),
1194                invocation,
1195            ),
1196            _ => None,
1197        }
1198    }
1199
1200    /// Get assertions for a function invocation from registered providers.
1201    #[must_use]
1202    pub fn get_function_assertions<'ctx>(
1203        &self,
1204        codebase: &'ctx CodebaseMetadata,
1205        source_file: &'ctx File,
1206        block_context: &BlockContext<'ctx>,
1207        artifacts: &AnalysisArtifacts,
1208        function_name: &[u8],
1209        invocation: &Invocation<'ctx, '_, '_>,
1210    ) -> Option<InvocationAssertions> {
1211        if self.function_assertion_providers.is_empty() {
1212            return None;
1213        }
1214
1215        let indices = self.get_function_assertion_provider_indices(function_name);
1216
1217        for idx in indices {
1218            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1219            let invocation_info = InvocationInfo::new(invocation);
1220
1221            if let Some(assertions) =
1222                self.function_assertion_providers[idx].get_assertions(&provider_context, &invocation_info)
1223                && !assertions.is_empty()
1224            {
1225                return Some(assertions);
1226            }
1227        }
1228
1229        None
1230    }
1231
1232    /// Get assertions for a method invocation from registered providers.
1233    #[must_use]
1234    pub fn get_method_assertions<'ctx>(
1235        &self,
1236        codebase: &'ctx CodebaseMetadata,
1237        source_file: &'ctx File,
1238        block_context: &BlockContext<'ctx>,
1239        artifacts: &AnalysisArtifacts,
1240        class_name: &[u8],
1241        method_name: &[u8],
1242        invocation: &Invocation<'ctx, '_, '_>,
1243    ) -> Option<InvocationAssertions> {
1244        if self.method_assertion_providers.is_empty() {
1245            return None;
1246        }
1247
1248        let indices = self.get_method_assertion_provider_indices(class_name, method_name);
1249
1250        for idx in indices {
1251            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1252            let invocation_info = InvocationInfo::new(invocation);
1253
1254            if let Some(assertions) = self.method_assertion_providers[idx].get_assertions(
1255                &provider_context,
1256                class_name,
1257                method_name,
1258                &invocation_info,
1259            ) && !assertions.is_empty()
1260            {
1261                return Some(assertions);
1262            }
1263        }
1264
1265        None
1266    }
1267
1268    fn get_function_throw_provider_indices(&self, name: &[u8]) -> Vec<usize> {
1269        if self.function_throw_exact.is_empty()
1270            && self.function_throw_prefix.is_empty()
1271            && self.function_throw_namespace.is_empty()
1272        {
1273            return Vec::new();
1274        }
1275
1276        let lower_name = ascii_lowercase_word(name);
1277        let mut indices = Vec::new();
1278
1279        if let Some(idxs) = self.function_throw_exact.get(&lower_name) {
1280            indices.extend(idxs.iter().copied());
1281        }
1282
1283        for (prefix, idx) in &self.function_throw_prefix {
1284            if lower_name.as_bytes().starts_with(prefix.as_bytes()) && !indices.contains(idx) {
1285                indices.push(*idx);
1286            }
1287        }
1288
1289        for (ns, idx) in &self.function_throw_namespace {
1290            if lower_name.as_bytes().starts_with(ns.as_bytes()) && !indices.contains(idx) {
1291                indices.push(*idx);
1292            }
1293        }
1294
1295        indices
1296    }
1297
1298    fn get_method_throw_provider_indices(&self, class_name: &[u8], method_name: &[u8]) -> Vec<usize> {
1299        if self.method_throw_providers.is_empty()
1300            && self.method_throw_exact.is_empty()
1301            && self.method_throw_wildcard.is_empty()
1302        {
1303            return Vec::new();
1304        }
1305
1306        use mago_word::concat_word;
1307        let key = concat_word!(ascii_lowercase_word(class_name), b"::", ascii_lowercase_word(method_name));
1308        let mut indices = Vec::new();
1309
1310        if let Some(idxs) = self.method_throw_exact.get(&key) {
1311            indices.extend(idxs.iter().copied());
1312        }
1313
1314        for (targets, idx) in &self.method_throw_wildcard {
1315            if !indices.contains(idx) {
1316                for target in targets {
1317                    if target.matches(class_name, method_name) {
1318                        indices.push(*idx);
1319                        break;
1320                    }
1321                }
1322            }
1323        }
1324
1325        indices
1326    }
1327
1328    /// Get thrown exception class names for an expression from registered providers.
1329    #[must_use]
1330    pub fn get_expression_thrown_exceptions<'ctx>(
1331        &self,
1332        codebase: &'ctx CodebaseMetadata,
1333        source_file: &'ctx File,
1334        block_context: &BlockContext<'ctx>,
1335        artifacts: &AnalysisArtifacts,
1336        expression: &mago_syntax::cst::Expression<'_>,
1337    ) -> WordSet {
1338        let mut exceptions = WordSet::default();
1339
1340        for provider in &self.expression_throw_providers {
1341            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1342            exceptions.extend(provider.get_thrown_exceptions(&provider_context, expression));
1343        }
1344
1345        exceptions
1346    }
1347
1348    /// Get thrown exception class names for a function invocation from registered providers.
1349    #[must_use]
1350    pub fn get_function_thrown_exceptions<'ctx>(
1351        &self,
1352        codebase: &'ctx CodebaseMetadata,
1353        source_file: &'ctx File,
1354        block_context: &BlockContext<'ctx>,
1355        artifacts: &AnalysisArtifacts,
1356        function_name: &[u8],
1357        invocation: &Invocation<'ctx, '_, '_>,
1358    ) -> WordSet {
1359        let mut exceptions = WordSet::default();
1360        let indices = self.get_function_throw_provider_indices(function_name);
1361
1362        for idx in indices {
1363            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1364            let invocation_info = InvocationInfo::new(invocation);
1365            exceptions
1366                .extend(self.function_throw_providers[idx].get_thrown_exceptions(&provider_context, &invocation_info));
1367        }
1368
1369        exceptions
1370    }
1371
1372    /// Get thrown exception class names for a method invocation from registered providers.
1373    #[must_use]
1374    pub fn get_method_thrown_exceptions<'ctx>(
1375        &self,
1376        codebase: &'ctx CodebaseMetadata,
1377        source_file: &'ctx File,
1378        block_context: &BlockContext<'ctx>,
1379        artifacts: &AnalysisArtifacts,
1380        class_name: &[u8],
1381        method_name: &[u8],
1382        invocation: &Invocation<'ctx, '_, '_>,
1383    ) -> WordSet {
1384        let mut exceptions = WordSet::default();
1385        let indices = self.get_method_throw_provider_indices(class_name, method_name);
1386
1387        for idx in indices {
1388            let provider_context = ProviderContext::new(codebase, source_file, block_context, artifacts);
1389            let invocation_info = InvocationInfo::new(invocation);
1390            exceptions.extend(self.method_throw_providers[idx].get_thrown_exceptions(
1391                &provider_context,
1392                class_name,
1393                method_name,
1394                &invocation_info,
1395            ));
1396        }
1397
1398        exceptions
1399    }
1400
1401    /// Filter issues through all registered issue filter hooks.
1402    ///
1403    /// Returns a new `IssueCollection` with filtered issues.
1404    #[must_use]
1405    pub fn filter_issues(&self, file: &File, issues: IssueCollection) -> IssueCollection {
1406        if self.issue_filter_hooks.is_empty() {
1407            return issues;
1408        }
1409
1410        let mut filtered = IssueCollection::default();
1411
1412        for issue in issues {
1413            let mut keep = true;
1414            for hook in &self.issue_filter_hooks {
1415                if hook.filter_issue(file, &issue) == Ok(IssueFilterDecision::Remove) {
1416                    keep = false;
1417                    break;
1418                }
1419            }
1420
1421            if keep {
1422                filtered.push(issue);
1423            }
1424        }
1425
1426        filtered
1427    }
1428}
1429
1430#[cfg(test)]
1431mod tests {
1432    use super::*;
1433    use crate::plugin::provider::Provider;
1434    use crate::plugin::provider::ProviderMeta;
1435
1436    static TEST_META: ProviderMeta = ProviderMeta::new("test::provider", "Test Provider", "A test provider");
1437
1438    struct TestFunctionProvider;
1439
1440    impl Provider for TestFunctionProvider {
1441        fn meta() -> &'static ProviderMeta {
1442            &TEST_META
1443        }
1444    }
1445
1446    impl FunctionReturnTypeProvider for TestFunctionProvider {
1447        fn targets() -> FunctionTarget {
1448            FunctionTarget::Exact(b"test_func")
1449        }
1450
1451        fn get_return_type(
1452            &self,
1453            _context: &ProviderContext<'_, '_, '_>,
1454            _invocation: &InvocationInfo<'_, '_, '_>,
1455        ) -> Option<TUnion> {
1456            None
1457        }
1458    }
1459
1460    #[test]
1461    fn test_register_function_provider() {
1462        let mut registry = PluginRegistry::new();
1463        registry.register_function_provider(TestFunctionProvider);
1464
1465        assert_eq!(registry.function_provider_count(), 1);
1466        let indices = registry.get_function_provider_indices(b"test_func");
1467        assert_eq!(indices.len(), 1);
1468    }
1469
1470    #[test]
1471    fn test_function_exact_match() {
1472        let mut registry = PluginRegistry::new();
1473        registry.register_function_provider(TestFunctionProvider);
1474
1475        let indices = registry.get_function_provider_indices(b"test_func");
1476        assert_eq!(indices.len(), 1);
1477
1478        let indices = registry.get_function_provider_indices(b"TEST_FUNC");
1479        assert_eq!(indices.len(), 1);
1480
1481        let indices = registry.get_function_provider_indices(b"other_func");
1482        assert!(indices.is_empty());
1483    }
1484}