mod nullable;
mod wrappers;
pub(crate) use wrappers::{TupleReturnLayout, WrapperTarget};
use crate::EmitEffects;
use crate::Planner;
use crate::Renderer;
use crate::calls::CallBoundary;
use crate::context::expression::ExpressionContext;
use crate::names::go_name;
use crate::plan::bodies::LoweredStatement;
use syntax::ast::Expression;
use syntax::types::Type;
#[derive(Debug, Clone)]
pub(crate) enum GoCallStrategy {
Tuple { arity: usize },
Result,
CommaOk,
NullableReturn,
Partial,
Sentinel { value: i64 },
}
impl GoCallStrategy {
pub(crate) fn is_multi_return(&self) -> bool {
!matches!(
self,
GoCallStrategy::NullableReturn | GoCallStrategy::Sentinel { .. }
)
}
}
impl Planner<'_> {
pub(crate) fn lower_go_wrapped_call(
&mut self,
call_expression: &Expression,
strategy: &GoCallStrategy,
result_ty: &Type,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
match strategy {
GoCallStrategy::Tuple { arity } => {
self.lower_go_tuple_call_wrapped(call_expression, *arity, fx)
}
GoCallStrategy::Result => {
self.lower_go_result_call_wrapped(call_expression, result_ty, fx)
}
GoCallStrategy::Partial => {
self.lower_go_partial_call_wrapped(call_expression, result_ty, fx)
}
GoCallStrategy::CommaOk => {
self.lower_go_option_call_wrapped(call_expression, result_ty, fx)
}
GoCallStrategy::NullableReturn => {
self.lower_go_single_return_option_wrapped(call_expression, result_ty, fx)
}
GoCallStrategy::Sentinel { value } => {
self.lower_go_sentinel_call_wrapped(call_expression, result_ty, *value, fx)
}
}
}
pub(crate) fn lower_go_wrapped_call_to(
&mut self,
expression: &Expression,
strategy: &GoCallStrategy,
result_ty: &Type,
target: WrapperTarget<'_>,
fx: &mut EmitEffects,
) -> Option<Vec<LoweredStatement>> {
if matches!(strategy, GoCallStrategy::Tuple { .. }) {
return None;
}
let (mut statements, call_str) =
self.lower_call(expression, None, ExpressionContext::value(), fx);
let wrap = match strategy {
GoCallStrategy::Tuple { .. } => unreachable!("handled above"),
GoCallStrategy::Result => {
fx.require_stdlib();
self.lower_result_wrapping(
&call_str,
result_ty,
TupleReturnLayout::Flattened,
target,
fx,
)
.0
}
GoCallStrategy::CommaOk => {
self.lower_comma_ok_wrapping(
&call_str,
result_ty,
TupleReturnLayout::Flattened,
target,
fx,
)
.0
}
GoCallStrategy::NullableReturn => {
let raw_var = self.hoist_tmp_value_statement(&mut statements, "raw", &call_str);
self.lower_nil_check_option_wrap(&raw_var, result_ty, target, fx)
.0
}
GoCallStrategy::Partial => {
fx.require_stdlib();
self.lower_partial_wrapping(
&call_str,
result_ty,
TupleReturnLayout::Flattened,
target,
fx,
)
.0
}
GoCallStrategy::Sentinel { value } => {
self.lower_sentinel_wrapping(&call_str, result_ty, *value, target, fx)
.0
}
};
statements.extend(wrap);
Some(statements)
}
fn has_go_hint(&self, receiver_expression: &Expression, member: &str, hint: &str) -> bool {
let Some(qualified_name) = go_qualified_name(receiver_expression, member) else {
return false;
};
self.facts
.definition(qualified_name.as_str())
.map(|definition| definition.go_hints().iter().any(|s| s == hint))
.unwrap_or(false)
}
pub(crate) fn has_go_array_return(
&self,
receiver_expression: &Expression,
member: &str,
) -> bool {
self.has_go_hint(receiver_expression, member, "array_return")
}
pub(crate) fn emit_go_call_discarded(
&mut self,
output: &mut String,
call_expression: &Expression,
fx: &mut EmitEffects,
) -> Option<String> {
let Expression::Call {
expression: callee, ..
} = call_expression
else {
return None;
};
let boundary = self.classify_call(call_expression);
let has_array_return = if let Expression::DotAccess {
expression: receiver_expression,
member,
..
} = callee.unwrap_parens()
&& is_go_receiver(receiver_expression)
{
self.has_go_array_return(receiver_expression, member)
} else {
false
};
if matches!(boundary, CallBoundary::Plain) && !has_array_return {
return None;
}
let mut ctx = ExpressionContext::value();
if has_array_return {
ctx = ctx.with_raw_go_array_return();
}
let (setup, call_str) = self.lower_call(call_expression, None, ctx, fx);
output.push_str(&Renderer.render_setup(&setup));
Some(call_str)
}
pub(crate) fn create_temp_vars(&mut self, hint: &str, count: usize) -> Vec<String> {
(0..count)
.map(|_| {
let v = self.fresh_var(Some(hint));
self.declare(&v);
v
})
.collect()
}
pub(crate) fn emit_tuple_from_vars(
&mut self,
output: &mut String,
vars: &[String],
tuple_ty: &Type,
fx: &mut EmitEffects,
) -> String {
let constructor = build_tuple_literal(vars, tuple_ty, fx);
self.hoist_tmp_value(output, "tup", &constructor)
}
pub(crate) fn plan_tuple_from_vars(
&mut self,
statements: &mut Vec<LoweredStatement>,
vars: &[String],
tuple_ty: &Type,
fx: &mut EmitEffects,
) -> String {
let constructor = build_tuple_literal(vars, tuple_ty, fx);
self.hoist_tmp_value_statement(statements, "tup", &constructor)
}
}
pub(crate) fn go_qualified_name(receiver_expression: &Expression, member: &str) -> Option<String> {
let ty = receiver_expression.get_type();
if let Some(module_path) = ty.as_import_namespace() {
return Some(format!("{}.{}", module_path, member));
}
if let Type::Nominal { id, .. } = ty.strip_refs()
&& go_name::is_go_import(&id)
{
return Some(format!("{}.{}", id, member));
}
None
}
pub(crate) fn is_go_receiver(expression: &Expression) -> bool {
let ty = expression.get_type();
if let Some(module_id) = ty.as_import_namespace()
&& module_id.starts_with(go_name::GO_IMPORT_PREFIX)
{
return true;
}
if let Type::Nominal { id, .. } = ty.strip_refs()
&& go_name::is_go_import(&id)
{
return true;
}
false
}
pub(super) fn build_tuple_literal(
vars: &[String],
_tuple_ty: &Type,
fx: &mut EmitEffects,
) -> String {
fx.require_stdlib();
format!("lisette.MakeTuple{}({})", vars.len(), vars.join(", "))
}