mago_analyzer/plugin/libraries/stdlib/array/
range.rs1use mago_codex::ttype::atomic::TAtomic;
4use mago_codex::ttype::atomic::scalar::TScalar;
5use mago_codex::ttype::atomic::scalar::float::TFloat;
6use mago_codex::ttype::atomic::scalar::int::TInteger;
7use mago_codex::ttype::atomic::scalar::string::TString;
8use mago_codex::ttype::get_non_empty_list;
9use mago_codex::ttype::union::TUnion;
10use mago_codex::ttype::wrap_atomic;
11
12use crate::plugin::context::InvocationInfo;
13use crate::plugin::context::ProviderContext;
14use crate::plugin::provider::Provider;
15use crate::plugin::provider::ProviderMeta;
16use crate::plugin::provider::function::FunctionReturnTypeProvider;
17use crate::plugin::provider::function::FunctionTarget;
18
19static META: ProviderMeta =
20 ProviderMeta::new("php::array::range", "range", "Returns non-empty-list with element type based on arguments");
21
22#[derive(Default)]
31pub struct RangeProvider;
32
33impl Provider for RangeProvider {
34 fn meta() -> &'static ProviderMeta {
35 &META
36 }
37}
38
39impl FunctionReturnTypeProvider for RangeProvider {
40 fn targets() -> FunctionTarget {
41 FunctionTarget::Exact(b"range")
42 }
43
44 fn get_return_type(
45 &self,
46 context: &ProviderContext<'_, '_, '_>,
47 invocation: &InvocationInfo<'_, '_, '_>,
48 ) -> Option<TUnion> {
49 let start_expr = invocation.get_argument(0, &[b"start"])?;
50 let end_expr = invocation.get_argument(1, &[b"end"])?;
51
52 let start_type = context.get_expression_type(start_expr)?;
53 let end_type = context.get_expression_type(end_expr)?;
54
55 if !start_type.is_single() || !end_type.is_single() {
56 return None;
57 }
58
59 let start_atomic = start_type.get_single();
60 let end_atomic = end_type.get_single();
61
62 let element_type = infer_range_element_type(start_atomic, end_atomic)?;
63
64 Some(get_non_empty_list(wrap_atomic(element_type)))
65 }
66}
67
68fn infer_range_element_type(start: &TAtomic, end: &TAtomic) -> Option<TAtomic> {
69 let start_kind = classify(start);
70 let end_kind = classify(end);
71
72 match (start_kind, end_kind) {
73 (ArgKind::Int(start_val), ArgKind::Int(end_val)) => Some(int_element_type(start_val, end_val)),
74 (ArgKind::Float(start_val), ArgKind::Float(end_val)) => Some(float_element_type(start_val, end_val)),
75 (ArgKind::Int(start_val), ArgKind::Float(end_val)) => {
76 Some(float_element_type(start_val.map(|v| v as f64), end_val))
77 }
78 (ArgKind::Float(start_val), ArgKind::Int(end_val)) => {
79 Some(float_element_type(start_val, end_val.map(|v| v as f64)))
80 }
81 (ArgKind::NonEmptyString, ArgKind::NonEmptyString) => {
82 Some(TAtomic::Scalar(TScalar::String(TString::non_empty())))
83 }
84 (ArgKind::EmptyString, ArgKind::EmptyString) => Some(TAtomic::Scalar(TScalar::Integer(TInteger::Literal(0)))),
85 (ArgKind::EmptyString, ArgKind::Int(end_val)) => Some(int_element_type(Some(0), end_val)),
86 (ArgKind::EmptyString, ArgKind::Float(end_val)) => Some(float_element_type(Some(0.0), end_val)),
87 (ArgKind::Int(start_val), ArgKind::EmptyString) => Some(int_element_type(start_val, Some(0))),
88 (ArgKind::Float(start_val), ArgKind::EmptyString) => Some(float_element_type(start_val, Some(0.0))),
89 _ => None,
90 }
91}
92
93#[derive(Debug, Clone, Copy)]
95enum ArgKind {
96 Int(Option<i64>),
97 Float(Option<f64>),
98 NonEmptyString,
99 EmptyString,
100 Unknown,
101}
102
103fn classify(atomic: &TAtomic) -> ArgKind {
104 match atomic {
105 TAtomic::Scalar(TScalar::Integer(int)) => ArgKind::Int(int.get_literal_value()),
106 TAtomic::Scalar(TScalar::Float(float)) => ArgKind::Float(float.get_literal_value()),
107 TAtomic::Scalar(TScalar::String(string)) => {
108 if let Some(value) = string.get_known_literal_value() {
109 if value.is_empty() { ArgKind::EmptyString } else { ArgKind::NonEmptyString }
110 } else if string.is_non_empty() {
111 ArgKind::NonEmptyString
112 } else {
113 ArgKind::Unknown
114 }
115 }
116 _ => ArgKind::Unknown,
117 }
118}
119
120fn int_element_type(start: Option<i64>, end: Option<i64>) -> TAtomic {
122 let (min, max) = match (start, end) {
123 (Some(a), Some(b)) => (Some(a.min(b)), Some(a.max(b))),
124 _ => (None, None),
125 };
126
127 let int = match (min, max) {
128 (Some(min), Some(max)) if min == max => TInteger::Literal(min),
129 (Some(min), Some(max)) if min >= 1 => TInteger::Range(min, max),
130 (Some(min), Some(max)) if min >= 0 => TInteger::Range(min, max),
131 (Some(min), Some(max)) if max <= -1 => TInteger::Range(min, max),
132 (Some(min), Some(max)) if max <= 0 => TInteger::Range(min, max),
133 (Some(min), Some(max)) => TInteger::Range(min, max),
134 (Some(min), None) if min >= 1 => TInteger::positive(),
135 (Some(min), None) if min >= 0 => TInteger::non_negative(),
136 (Some(min), None) => TInteger::From(min),
137 (None, Some(max)) if max <= -1 => TInteger::negative(),
138 (None, Some(max)) if max <= 0 => TInteger::non_positive(),
139 (None, Some(max)) => TInteger::To(max),
140 (None, None) => TInteger::Unspecified,
141 };
142
143 TAtomic::Scalar(TScalar::Integer(int))
144}
145
146fn float_element_type(_start: Option<f64>, _end: Option<f64>) -> TAtomic {
148 TAtomic::Scalar(TScalar::Float(TFloat::Float))
149}