use crate::GoCallStrategy;
use crate::Planner;
use crate::abi::AbiShape;
use crate::calls::go_interop::go_qualified_name;
use crate::calls::go_interop::is_go_receiver;
use crate::expressions::staging::VariadicCombine;
use crate::types::native::NativeGoType;
use syntax::ast::Expression;
use syntax::program::{CallKind, NativeTypeKind};
use syntax::types::Type;
#[derive(Debug)]
pub(crate) struct CallPlan {
pub(crate) callee: CalleePlan,
pub(crate) return_shape: CallReturnShape,
pub(crate) wrapper: WrapperPlan,
}
#[derive(Debug)]
pub(crate) enum CalleePlan {
Regular,
GoInterop(GoCallStrategy),
UfcsMethod,
NativeConstructor(NativeTypeKind),
NativeMethod(NativeTypeKind),
NativeMethodIdentifier(NativeTypeKind),
ReceiverMethodUfcs { is_public: bool },
TupleStructConstructor,
AssertType,
}
#[derive(Debug, Clone)]
pub(crate) enum CallReturnShape {
Direct,
Lowered(AbiShape),
}
#[derive(Debug, Clone)]
pub(crate) enum ArgumentPlan {
Direct,
GoCallbackAdapter(CallbackWrapperKind),
LoweredFnShapeAdapter,
NullableCoercion(NullableCoerceKind),
GoPointerUnwrap,
RecursiveEnumPointer,
TaggedGoLowering,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum NullableCoerceKind {
OptionToUnknown,
NullableInterface,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum CallbackWrapperKind {
Identity,
Wrap,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct WrapperPlan {
pub(crate) variadic: Option<VariadicSpreadPlan>,
pub(crate) go_array_return: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct VariadicSpreadPlan {
pub(crate) element_ty: Type,
pub(crate) fixed_in_signature: usize,
}
impl VariadicSpreadPlan {
pub(crate) fn combine(&self, extra_leading: usize) -> VariadicCombine {
VariadicCombine {
element_ty: self.element_ty.clone(),
fixed_count: self.fixed_in_signature + extra_leading,
}
}
}
impl CallPlan {
pub(crate) fn variadic_combine(&self, extra_leading: usize) -> Option<VariadicCombine> {
self.wrapper
.variadic
.as_ref()
.map(|spread| spread.combine(extra_leading))
}
pub(crate) fn has_go_array_return(&self) -> bool {
self.wrapper.go_array_return
}
}
impl Planner<'_> {
pub(crate) fn plan_call(&self, expression: &Expression) -> Option<CallPlan> {
let Expression::Call {
expression: callee,
call_kind,
spread,
..
} = expression
else {
return None;
};
let function = callee.unwrap_parens();
let wrapper = self.plan_call_wrapper(function, (**spread).as_ref());
if let Some(strategy) = self.resolve_go_call_strategy(expression) {
return Some(CallPlan {
callee: CalleePlan::GoInterop(strategy),
return_shape: CallReturnShape::Direct,
wrapper,
});
}
let kind = call_kind.filter(|_| !self.is_local_binding(function));
let callee_plan = match kind {
Some(CallKind::TupleStructConstructor) => CalleePlan::TupleStructConstructor,
Some(CallKind::AssertType) => CalleePlan::AssertType,
Some(CallKind::UfcsMethod) => CalleePlan::UfcsMethod,
Some(CallKind::NativeConstructor(kind)) => CalleePlan::NativeConstructor(kind),
Some(CallKind::NativeMethod(kind)) => CalleePlan::NativeMethod(kind),
Some(CallKind::NativeMethodIdentifier(kind)) => {
CalleePlan::NativeMethodIdentifier(kind)
}
Some(CallKind::ReceiverMethodUfcs { is_public }) => {
CalleePlan::ReceiverMethodUfcs { is_public }
}
None | Some(CallKind::Regular) => CalleePlan::Regular,
};
let return_shape = match self.classify_callee_abi(callee) {
Some(shape) => CallReturnShape::Lowered(shape),
None => CallReturnShape::Direct,
};
Some(CallPlan {
callee: callee_plan,
return_shape,
wrapper,
})
}
fn plan_call_wrapper(&self, function: &Expression, spread: Option<&Expression>) -> WrapperPlan {
let variadic = plan_variadic_spread(function, spread);
let mut go_array_return = false;
if let Expression::DotAccess {
expression: receiver_expression,
member,
..
} = function.unwrap_parens()
&& is_go_receiver(receiver_expression)
&& self.has_go_array_return(receiver_expression, member)
{
go_array_return = true;
}
WrapperPlan {
variadic,
go_array_return,
}
}
pub(crate) fn classify_callee_abi(&self, callee: &Expression) -> Option<AbiShape> {
let callee_ty = callee.get_type();
let unwrapped = callee_ty.unwrap_forall();
let resolved = self
.facts
.resolve_to_function_type(unwrapped)
.unwrap_or_else(|| unwrapped.clone());
let Type::Function(f) = resolved else {
return None;
};
let inner = callee.unwrap_parens();
if let Expression::DotAccess {
expression: receiver,
..
} = inner
{
if is_go_receiver(receiver) {
return None;
}
let receiver_ty = receiver.get_type();
if NativeGoType::from_type(&receiver_ty).is_some()
|| receiver_is_prelude_type(&receiver_ty)
{
return None;
}
if matches!(
&**receiver,
Expression::Identifier { qualified: Some(q), .. } if q.starts_with("prelude.")
) {
return None;
}
}
if inner.as_result_constructor().is_some()
|| inner.as_option_constructor().is_some()
|| inner.as_partial_constructor().is_some()
{
return None;
}
if let Expression::Identifier {
qualified: Some(q), ..
} = inner
&& q.starts_with("prelude.")
{
return None;
}
let declared_return = self
.callee_definition(callee)
.and_then(|definition| definition.ty().unwrap_forall().get_function_ret());
let classify_ty = declared_return.unwrap_or(f.return_type.as_ref());
self.classify_direct_emission(classify_ty)
}
pub(crate) fn resolve_go_call_strategy(
&self,
expression: &Expression,
) -> Option<GoCallStrategy> {
let Expression::Call {
expression: callee,
ty,
..
} = expression
else {
return None;
};
let inner = callee.unwrap_parens();
if let Expression::DotAccess {
expression: receiver_expression,
member,
..
} = inner
&& is_go_receiver(receiver_expression)
{
if let Some(qualified_name) = go_qualified_name(receiver_expression, member)
&& let Some(strategy) = self.facts.go_call_strategy(&qualified_name)
{
return Some(strategy.clone());
}
let go_hints = go_qualified_name(receiver_expression, member)
.and_then(|name| self.facts.definition(name.as_str()))
.map(|d| d.go_hints())
.unwrap_or_default();
return self.facts.classify_go_return_type(ty, go_hints);
}
None
}
}
fn receiver_is_prelude_type(ty: &Type) -> bool {
matches!(
ty.strip_refs().unwrap_forall(),
Type::Nominal { id, .. } if id.starts_with("prelude.")
)
}
pub(crate) fn plan_variadic_spread(
function: &Expression,
spread: Option<&Expression>,
) -> Option<VariadicSpreadPlan> {
spread?;
let fn_ty = function.get_type();
let unwrapped = fn_ty.unwrap_forall();
let element_ty = unwrapped.is_variadic()?;
let fixed_in_signature = unwrapped.get_function_params()?.len().saturating_sub(1);
Some(VariadicSpreadPlan {
element_ty,
fixed_in_signature,
})
}