use mago_codex::ttype::atomic::TAtomic;
use mago_codex::ttype::atomic::scalar::TScalar;
use mago_codex::ttype::atomic::scalar::float::TFloat;
use mago_codex::ttype::atomic::scalar::int::TInteger;
use mago_codex::ttype::atomic::scalar::string::TString;
use mago_codex::ttype::get_non_empty_list;
use mago_codex::ttype::union::TUnion;
use mago_codex::ttype::wrap_atomic;
use crate::plugin::context::InvocationInfo;
use crate::plugin::context::ProviderContext;
use crate::plugin::provider::Provider;
use crate::plugin::provider::ProviderMeta;
use crate::plugin::provider::function::FunctionReturnTypeProvider;
use crate::plugin::provider::function::FunctionTarget;
static META: ProviderMeta =
ProviderMeta::new("php::array::range", "range", "Returns non-empty-list with element type based on arguments");
#[derive(Default)]
pub struct RangeProvider;
impl Provider for RangeProvider {
fn meta() -> &'static ProviderMeta {
&META
}
}
impl FunctionReturnTypeProvider for RangeProvider {
fn targets() -> FunctionTarget {
FunctionTarget::Exact(b"range")
}
fn get_return_type(
&self,
context: &ProviderContext<'_, '_, '_>,
invocation: &InvocationInfo<'_, '_, '_>,
) -> Option<TUnion> {
let start_expr = invocation.get_argument(0, &[b"start"])?;
let end_expr = invocation.get_argument(1, &[b"end"])?;
let start_type = context.get_expression_type(start_expr)?;
let end_type = context.get_expression_type(end_expr)?;
if !start_type.is_single() || !end_type.is_single() {
return None;
}
let start_atomic = start_type.get_single();
let end_atomic = end_type.get_single();
let element_type = infer_range_element_type(start_atomic, end_atomic)?;
Some(get_non_empty_list(wrap_atomic(element_type)))
}
}
fn infer_range_element_type(start: &TAtomic, end: &TAtomic) -> Option<TAtomic> {
let start_kind = classify(start);
let end_kind = classify(end);
match (start_kind, end_kind) {
(ArgKind::Int(start_val), ArgKind::Int(end_val)) => Some(int_element_type(start_val, end_val)),
(ArgKind::Float(start_val), ArgKind::Float(end_val)) => Some(float_element_type(start_val, end_val)),
(ArgKind::Int(start_val), ArgKind::Float(end_val)) => {
Some(float_element_type(start_val.map(|v| v as f64), end_val))
}
(ArgKind::Float(start_val), ArgKind::Int(end_val)) => {
Some(float_element_type(start_val, end_val.map(|v| v as f64)))
}
(ArgKind::NonEmptyString, ArgKind::NonEmptyString) => {
Some(TAtomic::Scalar(TScalar::String(TString::non_empty())))
}
(ArgKind::EmptyString, ArgKind::EmptyString) => Some(TAtomic::Scalar(TScalar::Integer(TInteger::Literal(0)))),
(ArgKind::EmptyString, ArgKind::Int(end_val)) => Some(int_element_type(Some(0), end_val)),
(ArgKind::EmptyString, ArgKind::Float(end_val)) => Some(float_element_type(Some(0.0), end_val)),
(ArgKind::Int(start_val), ArgKind::EmptyString) => Some(int_element_type(start_val, Some(0))),
(ArgKind::Float(start_val), ArgKind::EmptyString) => Some(float_element_type(start_val, Some(0.0))),
_ => None,
}
}
#[derive(Debug, Clone, Copy)]
enum ArgKind {
Int(Option<i64>),
Float(Option<f64>),
NonEmptyString,
EmptyString,
Unknown,
}
fn classify(atomic: &TAtomic) -> ArgKind {
match atomic {
TAtomic::Scalar(TScalar::Integer(int)) => ArgKind::Int(int.get_literal_value()),
TAtomic::Scalar(TScalar::Float(float)) => ArgKind::Float(float.get_literal_value()),
TAtomic::Scalar(TScalar::String(string)) => {
if let Some(value) = string.get_known_literal_value() {
if value.is_empty() { ArgKind::EmptyString } else { ArgKind::NonEmptyString }
} else if string.is_non_empty() {
ArgKind::NonEmptyString
} else {
ArgKind::Unknown
}
}
_ => ArgKind::Unknown,
}
}
fn int_element_type(start: Option<i64>, end: Option<i64>) -> TAtomic {
let (min, max) = match (start, end) {
(Some(a), Some(b)) => (Some(a.min(b)), Some(a.max(b))),
_ => (None, None),
};
let int = match (min, max) {
(Some(min), Some(max)) if min == max => TInteger::Literal(min),
(Some(min), Some(max)) if min >= 1 => TInteger::Range(min, max),
(Some(min), Some(max)) if min >= 0 => TInteger::Range(min, max),
(Some(min), Some(max)) if max <= -1 => TInteger::Range(min, max),
(Some(min), Some(max)) if max <= 0 => TInteger::Range(min, max),
(Some(min), Some(max)) => TInteger::Range(min, max),
(Some(min), None) if min >= 1 => TInteger::positive(),
(Some(min), None) if min >= 0 => TInteger::non_negative(),
(Some(min), None) => TInteger::From(min),
(None, Some(max)) if max <= -1 => TInteger::negative(),
(None, Some(max)) if max <= 0 => TInteger::non_positive(),
(None, Some(max)) => TInteger::To(max),
(None, None) => TInteger::Unspecified,
};
TAtomic::Scalar(TScalar::Integer(int))
}
fn float_element_type(_start: Option<f64>, _end: Option<f64>) -> TAtomic {
TAtomic::Scalar(TScalar::Float(TFloat::Float))
}