use itertools::Itertools;
use crate::diagnostic::Diagnostic;
use crate::pr::{self, *};
use crate::utils::fold::{self, PrFold};
use crate::{Result, Span, utils};
use super::TypeResolver;
use super::scope::{Scope, ScopeKind};
impl TypeResolver<'_> {
#[tracing::instrument(name = "func", skip_all, fields(f = func.params.iter().map(|p| &p.name).join(",")))]
pub fn resolve_func(&mut self, scope_id: usize, mut func: Box<Func>) -> Result<Box<Func>> {
tracing::debug!(
"resolving func with params: ({})",
func.params.iter().map(|p| &p.name).join(", ")
);
let mut scope = Scope::new(
scope_id,
if func.ty_params.is_empty() {
ScopeKind::Nested
} else {
ScopeKind::Isolated
},
);
scope.insert_type_params(&func.ty_params);
self.scopes.push(scope);
func.params = fold::fold_func_params(self, func.params)?;
func.return_ty = fold::fold_type_opt(self, func.return_ty)?;
if func.ty_params.is_empty() {
for param in func.params.iter_mut() {
if param.ty.is_none() {
param.ty = Some(self.introduce_ty_var(pr::TyDomain::Open, param.span));
}
}
}
let res = self.scopes.last_mut().unwrap().insert_params(&func);
res.map_err(|mut d| d.remove(0))?;
let mut body = Box::new(self.fold_expr(*func.body)?);
if let Some(return_ty) = &func.return_ty {
self.validate_expr_type(&mut body, return_ty, &|| None)?;
}
func.body = body;
tracing::debug!("func done, popping scope");
let mapping = self.finalize_type_vars()?;
let func = utils::TypeReplacer::on_func(*func, mapping);
self.scopes.pop().unwrap();
Ok(Box::new(func))
}
pub fn resolve_func_call(
&mut self,
func: Box<Expr>,
args: Vec<CallArg>,
span: Option<Span>,
) -> Result<Expr> {
let metadata = self.gather_func_metadata(&func);
let tspan = tracing::span!(tracing::Level::TRACE, "call", f = metadata.as_debug_name());
let _enter = tspan.enter();
let fn_ty = func.ty.as_ref().unwrap();
let fn_ty = fn_ty.kind.as_func().unwrap().clone();
let args = self.resolve_call_args(args, &fn_ty.params, metadata, span);
Ok(Expr {
ty: fn_ty.body.clone(),
span,
..Expr::new(ExprKind::Call(Call {
subject: func,
args,
}))
})
}
pub fn resolve_call_args(
&mut self,
args: Vec<CallArg>,
params: &[pr::TyFuncParam],
metadata: FuncMetadata,
span: Option<Span>,
) -> Vec<CallArg> {
let args = self.match_args_to_params(args, params, &metadata, span);
let mut args_resolved = Vec::with_capacity(params.len());
for (param, arg) in std::iter::zip(params, args) {
let Some(mut arg) = arg else {
continue;
};
match self.fold_expr(arg.expr) {
Ok(e) => arg.expr = e,
Err(e) => {
self.diagnostics.push(e);
continue;
}
}
let who = || metadata.as_who();
if let Some(param_ty) = ¶m.ty {
self.validate_expr_type(&mut arg.expr, param_ty, &who)
.unwrap_or_else(self.push_diagnostic());
}
if param.constant {
self.const_validator
.validate_is_const(&arg.expr)
.map_err(|span| {
Diagnostic::new_custom("non-constant expression")
.with_span(span.or(arg.span))
})
.unwrap_or_else(self.push_diagnostic())
}
args_resolved.push(arg);
}
args_resolved
}
fn match_args_to_params(
&self,
mut args: Vec<pr::CallArg>,
params: &[pr::TyFuncParam],
metadata: &FuncMetadata,
span: Option<Span>,
) -> Vec<Option<pr::CallArg>> {
if args.len() != params.len() {
let who = metadata
.as_who()
.map(|n| format!("{n} "))
.unwrap_or_default();
let message = format!(
"{who}expected {} arguments, but got {}",
params.len(),
args.len()
);
self.diagnostics
.push(Diagnostic::new_custom(message).with_span(span));
}
let mut args_reordered = vec![None; args.len()];
let first_labelled = args
.iter()
.find_position(|p| p.label.is_some())
.map(|(p, _)| p)
.unwrap_or(args.len());
let labelled = args.split_off(first_labelled);
for arg in labelled {
if arg.label.is_none() {
self.diagnostics.push(
Diagnostic::new_custom("positional arg must come before labelled arg")
.with_span(arg.span),
);
continue;
}
let Some((param_pos, _)) = params.iter().find_position(|p| p.label == arg.label) else {
self.diagnostics.push(
Diagnostic::new_custom(format!(
"unknown parameter `{}`",
arg.label.as_ref().unwrap()
))
.with_span(arg.span),
);
continue;
};
args_reordered[param_pos] = Some(arg);
}
for arg in args {
assert!(arg.label.is_none());
let Some((pos, _)) = args_reordered.iter().find_position(|a| a.is_none()) else {
continue;
};
args_reordered[pos] = Some(arg);
}
args_reordered
}
fn gather_func_metadata(&self, func: &Expr) -> FuncMetadata {
let mut res = FuncMetadata::default();
let ExprKind::Ident(fq_ident) = &func.kind else {
return res;
};
res.name_hint = Some(fq_ident.clone());
res
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct FuncMetadata {
pub name_hint: Option<Path>,
}
impl FuncMetadata {
fn as_debug_name(&self) -> &str {
let ident = self.name_hint.as_ref();
ident.map(|n| n.last()).unwrap_or("<anonymous>")
}
fn as_who(&self) -> Option<String> {
self.name_hint.as_ref().map(|n| format!("func {n}"))
}
}