use php_ast::ast::BinaryOp;
use php_ast::owned::ExprKind;
use crate::db::MirDatabase;
use crate::flow_state::FlowState;
use super::super::core::{
apply_prop_narrowed, count_or_strlen_emptiness, extract_static_prop_access,
narrow_receiver_non_null_on_prop_match, resolve_prop_current_type,
resolve_static_prop_current_type, ScalarArgTarget,
};
use super::super::literals::{extract_int_literal, flip_comparison_op};
pub(crate) fn extract_count_arg(expr: &php_ast::owned::Expr) -> Option<ScalarArgTarget> {
if let ExprKind::FunctionCall(call) = &expr.kind {
let name = match &call.name.kind {
ExprKind::Identifier(n) => n.as_ref(),
_ => return None,
};
let bare = name.trim_start_matches('\\');
if bare.eq_ignore_ascii_case("count")
|| bare.eq_ignore_ascii_case("sizeof")
|| bare.eq_ignore_ascii_case("iterator_count")
{
if let Some(arg) = call.args.first() {
return ScalarArgTarget::extract(&arg.value);
}
}
}
None
}
pub(crate) fn extract_count_static_prop_arg(
expr: &php_ast::owned::Expr,
ctx: &FlowState,
db: &dyn MirDatabase,
file: &str,
) -> Option<(std::sync::Arc<str>, String)> {
if let ExprKind::FunctionCall(call) = &expr.kind {
let name = match &call.name.kind {
ExprKind::Identifier(n) => n.as_ref(),
_ => return None,
};
let bare = name.trim_start_matches('\\');
if bare.eq_ignore_ascii_case("count")
|| bare.eq_ignore_ascii_case("sizeof")
|| bare.eq_ignore_ascii_case("iterator_count")
{
if let Some(arg) = call.args.first() {
return extract_static_prop_access(&arg.value, ctx, db, file);
}
}
}
None
}
pub(crate) fn extract_array_key_first_or_last_arg(
expr: &php_ast::owned::Expr,
) -> Option<ScalarArgTarget> {
if let ExprKind::FunctionCall(call) = &expr.kind {
let name = match &call.name.kind {
ExprKind::Identifier(n) => n.as_ref(),
_ => return None,
};
let bare = name.trim_start_matches('\\');
if bare.eq_ignore_ascii_case("array_key_first")
|| bare.eq_ignore_ascii_case("array_key_last")
{
if let Some(arg) = call.args.first() {
return ScalarArgTarget::extract(&arg.value);
}
}
}
None
}
pub(crate) fn extract_array_key_first_or_last_static_prop_arg(
expr: &php_ast::owned::Expr,
ctx: &FlowState,
db: &dyn MirDatabase,
file: &str,
) -> Option<(std::sync::Arc<str>, String)> {
if let ExprKind::FunctionCall(call) = &expr.kind {
let name = match &call.name.kind {
ExprKind::Identifier(n) => n.as_ref(),
_ => return None,
};
let bare = name.trim_start_matches('\\');
if bare.eq_ignore_ascii_case("array_key_first")
|| bare.eq_ignore_ascii_case("array_key_last")
{
if let Some(arg) = call.args.first() {
return extract_static_prop_access(&arg.value, ctx, db, file);
}
}
}
None
}
pub(crate) fn narrow_array_key_first_or_last_null(
ctx: &mut FlowState,
arr_var: &str,
is_null: bool,
) {
let current = ctx.get_var(arr_var);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = if is_null {
non_null.narrow_to_empty_collection()
} else {
non_null.narrow_to_non_empty_collection()
};
if !narrowed.is_empty() && narrowed != current {
ctx.set_var(arr_var, narrowed);
}
}
pub(crate) fn narrow_prop_array_key_first_or_last_null(
ctx: &mut FlowState,
obj_var: &str,
prop: &str,
db: &dyn MirDatabase,
file: &str,
is_null: bool,
) {
let current = resolve_prop_current_type(ctx, obj_var, prop, db, file);
narrow_receiver_non_null_on_prop_match(ctx, obj_var, true);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = if is_null {
non_null.narrow_to_empty_collection()
} else {
non_null.narrow_to_non_empty_collection()
};
apply_prop_narrowed(ctx, obj_var, prop, current, narrowed, false);
}
pub(crate) fn narrow_static_prop_array_key_first_or_last_null(
ctx: &mut FlowState,
fqcn: &str,
prop: &str,
db: &dyn MirDatabase,
is_null: bool,
) {
let current = resolve_static_prop_current_type(ctx, fqcn, prop, db);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = if is_null {
non_null.narrow_to_empty_collection()
} else {
non_null.narrow_to_non_empty_collection()
};
if !narrowed.is_empty() && narrowed != current {
apply_prop_narrowed(ctx, fqcn, prop, current, narrowed, false);
}
}
pub(crate) fn narrow_array_count_comparison(
ctx: &mut FlowState,
arr_var: &str,
op: BinaryOp,
n: i64,
is_true: bool,
) {
let current = ctx.get_var(arr_var);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = match count_or_strlen_emptiness(op, n, is_true) {
Some(true) => non_null.narrow_to_non_empty_collection(),
Some(false) => non_null.narrow_to_empty_collection(),
None => non_null,
};
if !narrowed.is_empty() && narrowed != current {
ctx.set_var(arr_var, narrowed);
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn narrow_prop_array_count_comparison(
ctx: &mut FlowState,
obj_var: &str,
prop: &str,
db: &dyn MirDatabase,
file: &str,
op: BinaryOp,
n: i64,
is_true: bool,
) {
narrow_receiver_non_null_on_prop_match(ctx, obj_var, true);
let current = resolve_prop_current_type(ctx, obj_var, prop, db, file);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = match count_or_strlen_emptiness(op, n, is_true) {
Some(true) => non_null.narrow_to_non_empty_collection(),
Some(false) => non_null.narrow_to_empty_collection(),
None => non_null,
};
apply_prop_narrowed(ctx, obj_var, prop, current, narrowed, false);
}
pub(crate) fn narrow_static_prop_array_count_comparison(
ctx: &mut FlowState,
fqcn: &str,
prop: &str,
db: &dyn MirDatabase,
op: BinaryOp,
n: i64,
is_true: bool,
) {
let current = resolve_static_prop_current_type(ctx, fqcn, prop, db);
if current.is_mixed() {
return;
}
let non_null = current.remove_null();
let narrowed = match count_or_strlen_emptiness(op, n, is_true) {
Some(true) => non_null.narrow_to_non_empty_collection(),
Some(false) => non_null.narrow_to_empty_collection(),
None => non_null,
};
apply_prop_narrowed(ctx, fqcn, prop, current, narrowed, false);
}
pub(crate) fn narrow_array_count_condition(
ctx: &mut FlowState,
db: &dyn MirDatabase,
file: &str,
left: &php_ast::owned::Expr,
right: &php_ast::owned::Expr,
op: BinaryOp,
is_true: bool,
) {
let count_call_on_left = extract_count_arg(left).is_some()
|| extract_count_static_prop_arg(left, ctx, db, file).is_some();
let (count_expr, count_cmp_op, count_lit) = if count_call_on_left {
(left, op, right)
} else {
(right, flip_comparison_op(op), left)
};
if let (Some(target), Some(n)) = (
extract_count_arg(count_expr),
extract_int_literal(count_lit),
) {
match target {
ScalarArgTarget::Var(arr_var) => {
narrow_array_count_comparison(ctx, &arr_var, count_cmp_op, n, is_true)
}
ScalarArgTarget::Prop(obj, prop) => narrow_prop_array_count_comparison(
ctx,
&obj,
&prop,
db,
file,
count_cmp_op,
n,
is_true,
),
}
} else if let (Some((fqcn, prop)), Some(n)) = (
extract_count_static_prop_arg(count_expr, ctx, db, file),
extract_int_literal(count_lit),
) {
narrow_static_prop_array_count_comparison(ctx, &fqcn, &prop, db, count_cmp_op, n, is_true);
}
}