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