Skip to main content

mago_analyzer/plugin/libraries/stdlib/math/
min.rs

1use mago_codex::ttype::add_optional_union_type;
2use mago_codex::ttype::atomic::TAtomic;
3use mago_codex::ttype::atomic::scalar::TScalar;
4use mago_codex::ttype::atomic::scalar::int::TInteger;
5use mago_codex::ttype::get_array_value_parameter;
6use mago_codex::ttype::union::TUnion;
7
8use crate::plugin::context::InvocationInfo;
9use crate::plugin::context::ProviderContext;
10use crate::plugin::libraries::stdlib::math::collect_integers;
11use crate::plugin::libraries::stdlib::math::get_integer_from_type;
12use crate::plugin::provider::Provider;
13use crate::plugin::provider::ProviderMeta;
14use crate::plugin::provider::function::FunctionReturnTypeProvider;
15use crate::plugin::provider::function::FunctionTarget;
16
17/// Provider for the `min()` function.
18#[derive(Default)]
19pub struct MinProvider;
20
21impl Provider for MinProvider {
22    fn meta() -> &'static ProviderMeta {
23        static META: ProviderMeta = ProviderMeta::new(
24            "php::math::min",
25            "min",
26            "Return the minimum value of the values in the array passed as an argument.",
27        );
28
29        &META
30    }
31}
32
33impl FunctionReturnTypeProvider for MinProvider {
34    fn targets() -> FunctionTarget {
35        FunctionTarget::ExactMultiple(&[b"min", b"psl\\math\\min", b"psl\\math\\minva"])
36    }
37
38    #[allow(clippy::similar_names)]
39    fn get_return_type(
40        &self,
41        context: &ProviderContext<'_, '_, '_>,
42        invocation: &InvocationInfo<'_, '_, '_>,
43    ) -> Option<TUnion> {
44        let arg_count = invocation.argument_count();
45        if arg_count >= 2 {
46            return get_min_of_args(context, invocation);
47        }
48
49        let value = invocation.get_argument(0, &[b"value"])?;
50        let value_type = context.get_expression_type(value)?;
51
52        let mut resulting_type = None;
53        for atomic in value_type.types.iter() {
54            let TAtomic::Array(array_type) = atomic else {
55                return None;
56            };
57
58            resulting_type = Some(add_optional_union_type(
59                get_array_value_parameter(array_type, context.codebase),
60                resulting_type.as_ref(),
61                context.codebase,
62            ));
63        }
64
65        if let Some(known_resulting_type) = &resulting_type
66            && let Some(integers) = collect_integers(known_resulting_type)
67        {
68            let mut min_lb = integers[0].get_minimum_value();
69            let mut min_ub = integers[0].get_maximum_value();
70            for t in integers.iter().skip(1) {
71                let (lb, ub) = t.get_bounds();
72
73                match (min_lb, lb) {
74                    (Some(cur), Some(new)) => min_lb = Some(cur.min(new)),
75                    (_, None) => min_lb = None,
76                    _ => {}
77                }
78
79                match (min_ub, ub) {
80                    (Some(cur), Some(new)) => min_ub = Some(cur.min(new)),
81                    (None, Some(new)) => min_ub = Some(new),
82                    _ => {}
83                }
84            }
85
86            return Some(TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::from_bounds(min_lb, min_ub)))));
87        }
88
89        resulting_type
90    }
91}
92
93#[allow(clippy::similar_names)]
94fn get_min_of_args(context: &ProviderContext<'_, '_, '_>, invocation: &InvocationInfo<'_, '_, '_>) -> Option<TUnion> {
95    let first = invocation.get_argument(0, &[b"value"])?;
96    let first_type = context.get_expression_type(first)?;
97    let mut result = get_integer_from_type(first_type)?;
98
99    for i in 1..invocation.argument_count() {
100        let arg = invocation.get_argument(i, &[])?;
101        let arg_type = context.get_expression_type(arg)?;
102        let integer = get_integer_from_type(arg_type)?;
103
104        let (a_lb, a_ub) = result.get_bounds();
105        let (b_lb, b_ub) = integer.get_bounds();
106
107        let lb = match (a_lb, b_lb) {
108            (Some(a), Some(b)) => Some(std::cmp::min(a, b)),
109            _ => None,
110        };
111
112        let ub = match (a_ub, b_ub) {
113            (Some(a), Some(b)) => Some(std::cmp::min(a, b)),
114            (Some(a), None) => Some(a),
115            (None, Some(b)) => Some(b),
116            (None, None) => None,
117        };
118
119        result = TInteger::from_bounds(lb, ub);
120    }
121
122    Some(TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(result))))
123}