Skip to main content

mago_analyzer/plugin/libraries/stdlib/math/
max.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 `max()` function.
18#[derive(Default)]
19pub struct MaxProvider;
20
21impl Provider for MaxProvider {
22    fn meta() -> &'static ProviderMeta {
23        static META: ProviderMeta = ProviderMeta::new(
24            "php::math::max",
25            "max",
26            "Return the maximum value of the values in the array passed as an argument.",
27        );
28
29        &META
30    }
31}
32
33impl FunctionReturnTypeProvider for MaxProvider {
34    fn targets() -> FunctionTarget {
35        FunctionTarget::ExactMultiple(&[b"max", b"psl\\math\\max", b"psl\\math\\maxva"])
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_max_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 max_lb = integers[0].get_minimum_value();
69            let mut max_ub = integers[0].get_maximum_value();
70            for t in integers.iter().skip(1) {
71                let (lb, ub) = t.get_bounds();
72
73                max_lb = std::cmp::max(max_lb, lb);
74
75                if max_ub.is_none() || ub.is_none() {
76                    max_ub = None;
77                } else {
78                    max_ub = std::cmp::max(max_ub, ub);
79                }
80            }
81
82            return Some(TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(TInteger::from_bounds(max_lb, max_ub)))));
83        }
84
85        resulting_type
86    }
87}
88
89#[allow(clippy::similar_names)]
90fn get_max_of_args(context: &ProviderContext<'_, '_, '_>, invocation: &InvocationInfo<'_, '_, '_>) -> Option<TUnion> {
91    let first = invocation.get_argument(0, &[b"value"])?;
92    let first_type = context.get_expression_type(first)?;
93    let mut result = get_integer_from_type(first_type)?;
94
95    for i in 1..invocation.argument_count() {
96        let arg = invocation.get_argument(i, &[])?;
97        let arg_type = context.get_expression_type(arg)?;
98        let integer = get_integer_from_type(arg_type)?;
99
100        let (a_lb, a_ub) = result.get_bounds();
101        let (b_lb, b_ub) = integer.get_bounds();
102
103        let lb = std::cmp::max(a_lb, b_lb);
104        let ub = match (a_ub, b_ub) {
105            (Some(a), Some(b)) => Some(std::cmp::max(a, b)),
106            _ => None,
107        };
108
109        result = TInteger::from_bounds(lb, ub);
110    }
111
112    Some(TUnion::from_atomic(TAtomic::Scalar(TScalar::Integer(result))))
113}