Skip to main content

mago_analyzer/plugin/provider/
assertion.rs

1//! Assertion providers for function and method calls.
2//!
3//! These providers allow plugins to specify additional type assertions that
4//! should be applied after a function or method call.
5
6use std::collections::BTreeMap;
7
8use mago_algebra::assertion_set::Conjunction;
9use mago_atom::Atom;
10use mago_codex::assertion::Assertion;
11
12use crate::plugin::context::InvocationInfo;
13use crate::plugin::context::ProviderContext;
14use crate::plugin::provider::Provider;
15use crate::plugin::provider::function::FunctionTarget;
16use crate::plugin::provider::method::MethodTarget;
17
18/// Assertions to apply after an invocation.
19///
20/// Contains maps from variable names to assertion sets for:
21/// - `immediate`: assertions that apply unconditionally after the call
22/// - `if_true`: assertions that hold when the call returns truthy
23/// - `if_false`: assertions that hold when the call returns falsy
24#[derive(Debug, Clone, Default)]
25pub struct InvocationAssertions {
26    /// Assertions that apply unconditionally after the invocation.
27    ///
28    /// Keys are variable names (e.g., "$x"), values are assertion sets.
29    pub type_assertions: BTreeMap<Atom, Conjunction<Assertion>>,
30
31    /// Assertions that hold when the invocation returns truthy.
32    ///
33    /// Keys are variable names (e.g., "$x"), values are assertion sets.
34    pub if_true: BTreeMap<Atom, Conjunction<Assertion>>,
35
36    /// Assertions that hold when the invocation returns falsy.
37    ///
38    /// Keys are variable names (e.g., "$x"), values are assertion sets.
39    pub if_false: BTreeMap<Atom, Conjunction<Assertion>>,
40}
41
42impl InvocationAssertions {
43    /// Create new empty assertions.
44    #[inline]
45    #[must_use]
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Check if there are any assertions.
51    #[inline]
52    #[must_use]
53    pub fn is_empty(&self) -> bool {
54        self.type_assertions.is_empty() && self.if_true.is_empty() && self.if_false.is_empty()
55    }
56
57    /// Add an immediate assertion for a variable.
58    pub fn add_immediate(&mut self, variable: Atom, assertions: Conjunction<Assertion>) {
59        self.type_assertions.insert(variable, assertions);
60    }
61
62    /// Add an if-true assertion for a variable.
63    pub fn add_if_true(&mut self, variable: Atom, assertions: Conjunction<Assertion>) {
64        self.if_true.insert(variable, assertions);
65    }
66
67    /// Add an if-false assertion for a variable.
68    pub fn add_if_false(&mut self, variable: Atom, assertions: Conjunction<Assertion>) {
69        self.if_false.insert(variable, assertions);
70    }
71}
72
73/// Provider for getting additional assertions from function calls.
74///
75/// This allows plugins to specify type narrowing for calls like:
76/// - `assert($x instanceof Foo)` - narrows `$x` to `Foo` after the call
77/// - `Assert::assertIsString($x)` - narrows `$x` to `string` after the call
78pub trait FunctionAssertionProvider: Provider {
79    /// The functions this provider handles.
80    fn targets() -> FunctionTarget
81    where
82        Self: Sized;
83
84    /// Get assertions for the invocation.
85    ///
86    /// Returns `Some(assertions)` if this provider has assertions to add,
87    /// `None` otherwise.
88    fn get_assertions(
89        &self,
90        context: &ProviderContext<'_, '_, '_>,
91        invocation: &InvocationInfo<'_, '_, '_>,
92    ) -> Option<InvocationAssertions>;
93}
94
95/// Provider for getting additional assertions from method calls.
96///
97/// This allows plugins to specify type narrowing for method calls like:
98/// - `$validator->isString($x)` - narrows `$x` to `string` if returns true
99/// - `Assert::assertInstanceOf(Foo::class, $x)` - narrows `$x` to `Foo`
100pub trait MethodAssertionProvider: Provider {
101    /// The methods this provider handles.
102    fn targets() -> &'static [MethodTarget]
103    where
104        Self: Sized;
105
106    /// Get assertions for the method invocation.
107    ///
108    /// Returns `Some(assertions)` if this provider has assertions to add,
109    /// `None` otherwise.
110    fn get_assertions(
111        &self,
112        context: &ProviderContext<'_, '_, '_>,
113        class_name: &str,
114        method_name: &str,
115        invocation: &InvocationInfo<'_, '_, '_>,
116    ) -> Option<InvocationAssertions>;
117}