use std::sync::Arc;
use mago_atom::Atom;
use mago_codex::identifier::function_like::FunctionLikeIdentifier;
use mago_codex::identifier::method::MethodIdentifier;
use mago_codex::metadata::class_like::ClassLikeMetadata;
use mago_codex::metadata::class_like::TemplateTypes;
use mago_codex::metadata::function_like::FunctionLikeMetadata;
use mago_codex::metadata::parameter::FunctionLikeParameterMetadata;
use mago_codex::misc::VariableIdentifier;
use mago_codex::ttype::atomic::callable::TCallableSignature;
use mago_codex::ttype::atomic::callable::parameter::TCallableParameter;
use mago_codex::ttype::expander::StaticClassType;
use mago_codex::ttype::union::TUnion;
use mago_span::HasSpan;
use mago_span::Span;
use mago_syntax::ast::Argument;
use mago_syntax::ast::ArgumentList;
use mago_syntax::ast::Expression;
use mago_syntax::ast::NamedArgument;
use mago_syntax::ast::NamedPlaceholderArgument;
use mago_syntax::ast::PartialArgument;
use mago_syntax::ast::PartialArgumentList;
use mago_syntax::ast::Pipe;
use mago_syntax::ast::PlaceholderArgument;
use mago_syntax::ast::PositionalArgument;
use mago_syntax::ast::VariadicPlaceholderArgument;
use crate::context::Context;
mod resolver;
mod template_inference;
pub(crate) mod arguments;
pub mod analyzer;
pub mod post_process;
pub mod return_type_fetcher;
pub mod template_result;
#[derive(Debug, Clone)]
pub struct Invocation<'ctx, 'ast, 'arena> {
pub target: InvocationTarget<'ctx>,
pub arguments_source: InvocationArgumentsSource<'ast, 'arena>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct MethodTargetContext<'ctx> {
pub declaring_method_id: Option<MethodIdentifier>,
pub class_like_metadata: &'ctx ClassLikeMetadata,
pub class_type: StaticClassType,
}
#[derive(Debug, Clone)]
pub enum InvocationTarget<'ctx> {
Callable {
source: Option<FunctionLikeIdentifier>,
signature: TCallableSignature,
span: Span,
},
FunctionLike {
identifier: FunctionLikeIdentifier,
metadata: &'ctx FunctionLikeMetadata,
inferred_return_type: Option<Arc<TUnion>>,
method_context: Option<MethodTargetContext<'ctx>>,
span: Span,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvocationTargetParameter<'ctx> {
FunctionLike(&'ctx FunctionLikeParameterMetadata),
Callable(&'ctx TCallableParameter),
}
#[derive(Debug, Clone, Copy)]
pub enum InvocationArgumentsSource<'ast, 'arena> {
None(Span),
ArgumentList(&'ast ArgumentList<'arena>),
PipeInput(&'ast Pipe<'arena>),
PartialArgumentList(&'ast PartialArgumentList<'arena>),
}
#[derive(Debug, Clone, Copy)]
pub enum InvocationArgument<'ast, 'arena> {
PipedValue(&'ast Expression<'arena>),
Positional(&'ast PositionalArgument<'arena>),
Named(&'ast NamedArgument<'arena>),
Placeholder(&'ast PlaceholderArgument),
NamedPlaceholder(&'ast NamedPlaceholderArgument<'arena>),
VariadicPlaceholder(&'ast VariadicPlaceholderArgument),
}
impl<'ctx, 'ast, 'arena> Invocation<'ctx, 'ast, 'arena> {
pub fn new(target: InvocationTarget<'ctx>, arguments: InvocationArgumentsSource<'ast, 'arena>, span: Span) -> Self {
Self { target, arguments_source: arguments, span }
}
}
impl<'ctx> InvocationTarget<'ctx> {
pub fn guess_name(&self, context: &Context<'_, '_>) -> String {
self.get_function_like_identifier()
.map(|identifier| crate::utils::names::display_function_like_identifier(context, identifier))
.unwrap_or_else(
|| {
if self.is_non_closure_callable() { "callable".to_string() } else { "Closure".to_string() }
},
)
}
pub fn guess_kind(&self) -> &'static str {
match self.get_function_like_identifier() {
Some(identifier) => match identifier {
FunctionLikeIdentifier::Function(_) => "function",
FunctionLikeIdentifier::Method(_, _) => "method",
FunctionLikeIdentifier::Closure(_, _) => "closure",
},
None => {
if self.is_non_closure_callable() {
"callable"
} else {
"closure"
}
}
}
}
pub const fn is_method_call(&self) -> bool {
matches!(self.get_function_like_identifier(), Some(FunctionLikeIdentifier::Method(_, _)))
}
pub const fn is_pure_or_mutation_free(&self) -> bool {
match self {
InvocationTarget::Callable { signature, .. } => signature.is_pure,
InvocationTarget::FunctionLike { metadata, .. } => {
metadata.flags.is_pure()
|| metadata.flags.is_mutation_free()
|| metadata.flags.is_external_mutation_free()
}
}
}
#[inline]
pub const fn is_non_closure_callable(&self) -> bool {
match self {
InvocationTarget::Callable { signature, .. } => !signature.is_closure(),
_ => false,
}
}
#[inline]
pub const fn get_function_like_metadata(&self) -> Option<&'ctx FunctionLikeMetadata> {
match self {
InvocationTarget::FunctionLike { metadata, .. } => Some(metadata),
_ => None,
}
}
#[inline]
pub const fn get_function_like_identifier(&self) -> Option<&FunctionLikeIdentifier> {
match self {
InvocationTarget::Callable { source, .. } => source.as_ref(),
InvocationTarget::FunctionLike { identifier, .. } => Some(identifier),
}
}
#[inline]
#[allow(dead_code)]
pub const fn get_method_class_like_name(&self) -> Option<Atom> {
match self.get_function_like_identifier() {
Some(FunctionLikeIdentifier::Method(fq_class_like_name, _)) => Some(*fq_class_like_name),
_ => None,
}
}
#[inline]
#[allow(dead_code)]
pub const fn get_method_identifier(&self) -> Option<MethodIdentifier> {
match self {
InvocationTarget::FunctionLike { identifier, .. } => identifier.as_method_identifier(),
_ => None,
}
}
#[inline]
#[allow(dead_code)]
pub const fn has_throw(&self) -> bool {
match self {
InvocationTarget::FunctionLike { metadata, .. } => metadata.flags.has_throw(),
_ => false,
}
}
#[inline]
pub fn get_template_types(&self) -> Option<&'ctx TemplateTypes> {
match self {
InvocationTarget::FunctionLike { metadata, .. } => Some(&metadata.template_types),
_ => None,
}
}
#[inline]
pub const fn allows_named_arguments(&self) -> bool {
match self {
InvocationTarget::FunctionLike { metadata, .. } => !metadata.flags.forbids_named_arguments(),
_ => false,
}
}
#[inline]
pub const fn get_method_context(&self) -> Option<&MethodTargetContext<'ctx>> {
match self {
InvocationTarget::FunctionLike { method_context, .. } => method_context.as_ref(),
_ => None,
}
}
#[inline]
pub fn get_parameters<'target>(&'target self) -> Vec<InvocationTargetParameter<'target>>
where
'ctx: 'target,
{
match self {
InvocationTarget::Callable { signature, .. } => {
signature.parameters.iter().map(InvocationTargetParameter::Callable).collect()
}
InvocationTarget::FunctionLike { metadata, .. } => {
metadata.parameters.iter().map(InvocationTargetParameter::FunctionLike).collect()
}
}
}
#[inline]
pub fn get_return_type(&self) -> Option<&TUnion> {
match self {
InvocationTarget::Callable { signature, .. } => signature.get_return_type(),
InvocationTarget::FunctionLike { metadata, inferred_return_type, .. } => inferred_return_type
.as_deref()
.or_else(|| metadata.return_type_metadata.as_ref().map(|type_metadata| &type_metadata.type_union)),
}
}
}
impl<'a> InvocationTargetParameter<'a> {
#[inline]
pub fn get_out_type(&self) -> Option<&'a TUnion> {
match self {
InvocationTargetParameter::FunctionLike(metadata) => {
metadata.out_type.as_ref().map(|type_metadata| &type_metadata.type_union)
}
_ => None,
}
}
#[inline]
pub fn get_type(&self) -> Option<&'a TUnion> {
match self {
InvocationTargetParameter::FunctionLike(metadata) => {
metadata.get_type_metadata().map(|type_metadata| &type_metadata.type_union)
}
InvocationTargetParameter::Callable(parameter) => parameter.get_type_signature(),
}
}
#[inline]
pub fn get_name(&self) -> Option<&'a VariableIdentifier> {
match self {
InvocationTargetParameter::FunctionLike(metadata) => Some(metadata.get_name()),
InvocationTargetParameter::Callable(_) => None,
}
}
#[inline]
#[allow(dead_code)]
pub const fn is_by_reference(&self) -> bool {
match self {
InvocationTargetParameter::FunctionLike(metadata) => metadata.flags.is_by_reference(),
InvocationTargetParameter::Callable(parameter) => parameter.is_by_reference(),
}
}
#[inline]
pub const fn is_variadic(&self) -> bool {
match self {
InvocationTargetParameter::FunctionLike(metadata) => metadata.flags.is_variadic(),
InvocationTargetParameter::Callable(parameter) => parameter.is_variadic(),
}
}
#[inline]
pub const fn has_default(&self) -> bool {
match self {
InvocationTargetParameter::FunctionLike(metadata) => metadata.flags.has_default(),
InvocationTargetParameter::Callable(parameter) => parameter.has_default(),
}
}
#[inline]
pub fn get_default_type(&self) -> Option<&'a TUnion> {
match self {
InvocationTargetParameter::FunctionLike(metadata) => {
metadata.get_default_type().map(|type_metadata| &type_metadata.type_union)
}
InvocationTargetParameter::Callable(_) => None,
}
}
}
impl<'ast, 'arena> InvocationArgumentsSource<'ast, 'arena> {
#[inline]
pub fn get_arguments(&self) -> Vec<InvocationArgument<'ast, 'arena>> {
match self {
InvocationArgumentsSource::ArgumentList(arg_list) => arg_list
.arguments
.iter()
.map(|arg| match arg {
Argument::Positional(pos_arg) => InvocationArgument::Positional(pos_arg),
Argument::Named(named_arg) => InvocationArgument::Named(named_arg),
})
.collect(),
InvocationArgumentsSource::PipeInput(pipe) => {
vec![InvocationArgument::PipedValue(pipe.input)]
}
InvocationArgumentsSource::None(_) => {
vec![]
}
InvocationArgumentsSource::PartialArgumentList(partial_arg_list) => partial_arg_list
.arguments
.iter()
.map(|partial_arg| match partial_arg {
PartialArgument::Positional(pos_arg) => InvocationArgument::Positional(pos_arg),
PartialArgument::Named(named_arg) => InvocationArgument::Named(named_arg),
PartialArgument::Placeholder(placeholder) => InvocationArgument::Placeholder(placeholder),
PartialArgument::NamedPlaceholder(named_placeholder) => {
InvocationArgument::NamedPlaceholder(named_placeholder)
}
PartialArgument::VariadicPlaceholder(variadic_placeholder) => {
InvocationArgument::VariadicPlaceholder(variadic_placeholder)
}
})
.collect(),
}
}
}
impl<'ast, 'arena> InvocationArgument<'ast, 'arena> {
#[inline]
pub const fn is_placeholder(&self) -> bool {
matches!(
self,
InvocationArgument::Placeholder(_)
| InvocationArgument::NamedPlaceholder(_)
| InvocationArgument::VariadicPlaceholder(_)
)
}
#[inline]
pub const fn is_positional(&self) -> bool {
!matches!(self, InvocationArgument::NamedPlaceholder(_) | InvocationArgument::Named(_))
}
#[inline]
pub const fn is_unpacked(&self) -> bool {
match self {
InvocationArgument::Positional(pos_arg) => pos_arg.ellipsis.is_some(),
InvocationArgument::VariadicPlaceholder(_) => true,
_ => false,
}
}
#[inline]
pub const fn value(&self) -> Option<&'ast Expression<'arena>> {
match self {
InvocationArgument::PipedValue(expr) => Some(expr),
InvocationArgument::Positional(pos_arg) => Some(pos_arg.value),
InvocationArgument::Named(named_arg) => Some(named_arg.value),
_ => None,
}
}
#[inline]
pub const fn get_named_argument(&self) -> Option<&'ast NamedArgument<'arena>> {
match self {
InvocationArgument::Named(named_arg) => Some(named_arg),
_ => None,
}
}
#[inline]
pub const fn get_parameter_name(&self) -> Option<&'arena str> {
match self {
InvocationArgument::Named(named_arg) => Some(named_arg.name.value),
InvocationArgument::NamedPlaceholder(named_ph) => Some(named_ph.name.value),
_ => None,
}
}
}
impl HasSpan for Invocation<'_, '_, '_> {
fn span(&self) -> Span {
self.span
}
}
impl HasSpan for InvocationTarget<'_> {
fn span(&self) -> Span {
match self {
InvocationTarget::Callable { span, .. } => *span,
InvocationTarget::FunctionLike { span, .. } => *span,
}
}
}
impl HasSpan for InvocationArgumentsSource<'_, '_> {
fn span(&self) -> Span {
match self {
InvocationArgumentsSource::ArgumentList(arg_list) => arg_list.span(),
InvocationArgumentsSource::PipeInput(pipe) => pipe.span(),
InvocationArgumentsSource::None(span) => *span,
InvocationArgumentsSource::PartialArgumentList(partial_arg_list) => partial_arg_list.span(),
}
}
}
impl HasSpan for InvocationArgument<'_, '_> {
fn span(&self) -> Span {
match self {
InvocationArgument::PipedValue(expr) => expr.span(),
InvocationArgument::Positional(pos_arg) => pos_arg.span(),
InvocationArgument::Named(named_arg) => named_arg.span(),
InvocationArgument::Placeholder(placeholder) => placeholder.span(),
InvocationArgument::NamedPlaceholder(named_placeholder) => named_placeholder.span(),
InvocationArgument::VariadicPlaceholder(variadic_placeholder) => variadic_placeholder.span(),
}
}
}