use super::helpers::{ast_params_to_fn_params_resolved, resolve_named_objects_in_union};
use super::ExpressionAnalyzer;
use crate::flow_state::FlowState;
use crate::stmt::widen_for_check;
use mir_issues::{IssueKind, Severity};
use mir_types::{Atomic, Name, Type};
use php_ast::owned::{ArrowFunctionExpr, ClosureExpr, ExprKind};
use std::sync::Arc;
impl<'a> ExpressionAnalyzer<'a> {
pub(super) fn analyze_closure(&mut self, c: &ClosureExpr, ctx: &mut FlowState) -> Type {
for param in c.params.iter() {
if let Some(hint) = ¶m.type_hint {
self.check_type_hint(hint);
}
}
if let Some(hint) = &c.return_type {
self.check_type_hint(hint);
}
let params = ast_params_to_fn_params_resolved(
&c.params,
ctx.self_fqcn.as_deref(),
self.db,
&self.file,
);
let return_ty_hint = c
.return_type
.as_ref()
.map(|h| crate::parser::type_from_hint_owned(h, ctx.self_fqcn.as_deref()))
.map(|u| resolve_named_objects_in_union(u, self.db, &self.file));
let mut closure_ctx = crate::flow_state::FlowState::for_function(
¶ms,
return_ty_hint.clone(),
Arc::from([]),
ctx.self_fqcn.clone(),
ctx.parent_fqcn.clone(),
ctx.static_fqcn.clone(),
ctx.strict_types,
c.is_static,
);
for use_var in c.use_vars.iter() {
let name = use_var.name.trim_start_matches('$');
if !ctx.var_is_defined(name) {
if ctx.var_possibly_defined(name) {
self.emit(
mir_issues::IssueKind::PossiblyUndefinedVariable {
name: name.to_string(),
},
mir_issues::Severity::Warning,
use_var.span,
);
} else {
self.emit(
mir_issues::IssueKind::UndefinedVariable {
name: name.to_string(),
},
mir_issues::Severity::Error,
use_var.span,
);
}
}
closure_ctx.set_var(name, ctx.get_var(name));
if ctx.is_tainted(name) {
closure_ctx.taint_var(name);
}
ctx.read_vars.insert(mir_types::Name::from(name));
}
let mut sa = crate::stmt::StatementsAnalyzer::new(
self.db,
self.file.clone(),
self.source,
self.source_map,
self.issues,
self.symbols,
self.php_version,
self.mode,
);
sa.analyze_stmts(&c.body.stmts, &mut closure_ctx);
let inferred_return = crate::body_analysis::merge_return_types(&sa.return_types);
for name in &closure_ctx.read_vars {
if ctx.var_is_defined(name) || ctx.var_possibly_defined(name) {
ctx.read_vars.insert(*name);
}
}
let return_ty = return_ty_hint.unwrap_or(inferred_return);
let closure_params: Vec<mir_types::atomic::FnParam> = params
.iter()
.map(|p| mir_types::atomic::FnParam {
name: Name::from(p.name.as_ref()),
ty: p
.ty
.as_ref()
.map(|arc| mir_types::SimpleType::from_union((**arc).clone())),
default: if p.has_default {
Some(mir_types::SimpleType::from_union(Type::mixed()))
} else {
None
},
is_variadic: p.is_variadic,
is_byref: p.is_byref,
is_optional: p.is_optional,
})
.collect();
Type::single(Atomic::TClosure {
params: closure_params,
return_type: Box::new(return_ty),
this_type: ctx.self_fqcn.clone().map(|f| {
Box::new(Type::single(Atomic::TNamedObject {
fqcn: Name::from(f.as_ref()),
type_params: mir_types::union::empty_type_params(),
}))
}),
})
}
pub(super) fn analyze_arrow_function(
&mut self,
af: &ArrowFunctionExpr,
ctx: &mut FlowState,
) -> Type {
for param in af.params.iter() {
if let Some(hint) = ¶m.type_hint {
self.check_type_hint(hint);
}
}
if let Some(hint) = &af.return_type {
self.check_type_hint(hint);
}
let params = ast_params_to_fn_params_resolved(
&af.params,
ctx.self_fqcn.as_deref(),
self.db,
&self.file,
);
let return_ty_hint = af
.return_type
.as_ref()
.map(|h| crate::parser::type_from_hint_owned(h, ctx.self_fqcn.as_deref()))
.map(|u| resolve_named_objects_in_union(u, self.db, &self.file));
let mut arrow_ctx = crate::flow_state::FlowState::for_function(
¶ms,
return_ty_hint.clone(),
Arc::from([]),
ctx.self_fqcn.clone(),
ctx.parent_fqcn.clone(),
ctx.static_fqcn.clone(),
ctx.strict_types,
af.is_static,
);
for (name, ty) in ctx.vars.iter() {
if !arrow_ctx.vars.contains_key(name) {
std::sync::Arc::make_mut(&mut arrow_ctx.vars).insert(*name, ty.clone());
std::sync::Arc::make_mut(&mut arrow_ctx.assigned_vars).insert(*name);
}
}
let check_target = match &af.body.kind {
ExprKind::Parenthesized(inner) => inner.as_ref(),
_ => &af.body,
};
if let Some(doc) =
crate::parser::find_preceding_docblock(self.source, check_target.span.start)
{
let checks = crate::parser::DocblockParser::parse(&doc).mir_checks;
for (var_name, expected_str) in checks {
let expected = crate::parser::docblock::parse_type_string(&expected_str);
let actual = widen_for_check(arrow_ctx.get_var(&var_name));
if expected.to_string() != actual.to_string() {
self.emit(
IssueKind::TypeCheckMismatch {
var: var_name,
expected: expected.to_string(),
actual: actual.to_string(),
},
Severity::Error,
check_target.span,
);
}
}
}
let inferred_return = self.analyze(&af.body, &mut arrow_ctx);
for name in &arrow_ctx.read_vars {
ctx.read_vars.insert(*name);
}
let return_ty = return_ty_hint.unwrap_or(inferred_return);
let closure_params: Vec<mir_types::atomic::FnParam> = params
.iter()
.map(|p| mir_types::atomic::FnParam {
name: Name::from(p.name.as_ref()),
ty: p
.ty
.as_ref()
.map(|arc| mir_types::SimpleType::from_union((**arc).clone())),
default: if p.has_default {
Some(mir_types::SimpleType::from_union(Type::mixed()))
} else {
None
},
is_variadic: p.is_variadic,
is_byref: p.is_byref,
is_optional: p.is_optional,
})
.collect();
Type::single(Atomic::TClosure {
params: closure_params,
return_type: Box::new(return_ty),
this_type: if af.is_static {
None
} else {
ctx.self_fqcn.clone().map(|f| {
Box::new(Type::single(Atomic::TNamedObject {
fqcn: Name::from(f.as_ref()),
type_params: mir_types::union::empty_type_params(),
}))
})
},
})
}
}