mago_analyzer/plugin/libraries/stdlib/math/
min.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 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}