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),
}
#[derive(Debug)]
pub struct InvocationArgumentsIter<'ast, 'arena> {
source: InvocationArgumentsSource<'ast, 'arena>,
index: usize,
}
#[derive(Debug)]
pub struct InvocationTargetParametersIter<'target, 'ctx> {
target: &'target InvocationTarget<'ctx>,
index: usize,
}
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]
#[must_use]
pub fn parameter_count(&self) -> usize {
match self {
InvocationTarget::Callable { signature, .. } => signature.parameters.len(),
InvocationTarget::FunctionLike { metadata, .. } => metadata.parameters.len(),
}
}
#[inline]
#[must_use]
pub fn get_parameter<'target>(&'target self, index: usize) -> Option<InvocationTargetParameter<'target>>
where
'ctx: 'target,
{
match self {
InvocationTarget::Callable { signature, .. } => {
signature.parameters.get(index).map(InvocationTargetParameter::Callable)
}
InvocationTarget::FunctionLike { metadata, .. } => {
metadata.parameters.get(index).map(InvocationTargetParameter::FunctionLike)
}
}
}
#[inline]
#[must_use]
pub fn iter_parameters<'target>(&'target self) -> InvocationTargetParametersIter<'target, 'ctx>
where
'ctx: 'target,
{
InvocationTargetParametersIter { target: self, index: 0 }
}
#[inline]
pub fn get_parameters<'target>(&'target self) -> Vec<InvocationTargetParameter<'target>>
where
'ctx: 'target,
{
self.iter_parameters().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<'ctx> InvocationTargetParameter<'ctx> {
#[inline]
pub fn get_out_type(&self) -> Option<&'ctx 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<&'ctx 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<&'ctx 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<&'ctx 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]
#[must_use]
pub fn argument_count(&self) -> usize {
match self {
InvocationArgumentsSource::ArgumentList(argument_list) => argument_list.arguments.len(),
InvocationArgumentsSource::PipeInput(_) => 1,
InvocationArgumentsSource::None(_) => 0,
InvocationArgumentsSource::PartialArgumentList(partial_argument_list) => {
partial_argument_list.arguments.len()
}
}
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.argument_count() == 0
}
#[inline]
#[must_use]
pub fn get_argument(&self, index: usize) -> Option<InvocationArgument<'ast, 'arena>> {
match self {
InvocationArgumentsSource::ArgumentList(argument_list) => {
argument_list.arguments.get(index).map(|argument| match argument {
Argument::Positional(positional_argument) => InvocationArgument::Positional(positional_argument),
Argument::Named(named_argument) => InvocationArgument::Named(named_argument),
})
}
InvocationArgumentsSource::PipeInput(pipe) => {
if index == 0 {
Some(InvocationArgument::PipedValue(pipe.input))
} else {
None
}
}
InvocationArgumentsSource::None(_) => None,
InvocationArgumentsSource::PartialArgumentList(partial_argument_list) => {
partial_argument_list.arguments.get(index).map(|partial_argument| match partial_argument {
PartialArgument::Positional(positional_argument) => {
InvocationArgument::Positional(positional_argument)
}
PartialArgument::Named(named_argument) => InvocationArgument::Named(named_argument),
PartialArgument::Placeholder(placeholder) => InvocationArgument::Placeholder(placeholder),
PartialArgument::NamedPlaceholder(named_placeholder) => {
InvocationArgument::NamedPlaceholder(named_placeholder)
}
PartialArgument::VariadicPlaceholder(variadic_placeholder) => {
InvocationArgument::VariadicPlaceholder(variadic_placeholder)
}
})
}
}
}
#[inline]
#[must_use]
pub fn iter_arguments(&self) -> InvocationArgumentsIter<'ast, 'arena> {
InvocationArgumentsIter { source: *self, index: 0 }
}
#[inline]
pub fn get_arguments(&self) -> Vec<InvocationArgument<'ast, 'arena>> {
self.iter_arguments().collect()
}
}
impl<'ast, 'arena> Iterator for InvocationArgumentsIter<'ast, 'arena> {
type Item = InvocationArgument<'ast, 'arena>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let argument = self.source.get_argument(self.index);
if argument.is_some() {
self.index += 1;
}
argument
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.source.argument_count().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for InvocationArgumentsIter<'_, '_> {}
impl<'target, 'ctx> Iterator for InvocationTargetParametersIter<'target, 'ctx>
where
'ctx: 'target,
{
type Item = InvocationTargetParameter<'target>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let parameter = self.target.get_parameter(self.index);
if parameter.is_some() {
self.index += 1;
}
parameter
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.target.parameter_count().saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl<'target, 'ctx> ExactSizeIterator for InvocationTargetParametersIter<'target, 'ctx> where 'ctx: 'target {}
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(),
}
}
}