use rustc_hash::FxHashSet as HashSet;
use crate::Emitter;
use crate::names::go_name;
use crate::types::coercion::Coercion;
use crate::utils::Staged;
use crate::write_line;
use syntax::ast::{Annotation, Expression, UnaryOperator};
use syntax::types::Type;
struct CallArgsContext<'a> {
fn_param_types: &'a [Type],
pointer_indices: &'a HashSet<usize>,
is_go_call: bool,
spread: Option<&'a Expression>,
wrap_spread_to_any: bool,
}
fn collapse_fmt_print(function_string: &str, args_strings: &[String], call_str: String) -> String {
if function_string != "fmt.Print" && function_string != "fmt.Println" {
return call_str;
}
if args_strings.len() != 1 {
return call_str;
}
let arg = &args_strings[0];
if let Some(inner) = arg
.strip_prefix("fmt.Sprintf(")
.and_then(|s| s.strip_suffix(')'))
{
let suffix = if function_string == "fmt.Println" {
"\\n"
} else {
""
};
if suffix.is_empty() {
return format!("fmt.Printf({})", inner);
}
if let Some(close_quote) = inner.find("\", ") {
let format_str = &inner[..close_quote];
let rest = &inner[close_quote + 1..];
return format!("fmt.Printf({}{}\"{})", format_str, suffix, rest);
}
if inner.starts_with('"') && inner.ends_with('"') {
let format_str = &inner[..inner.len() - 1];
return format!("fmt.Printf({}{}\")", format_str, suffix);
}
return call_str;
}
if let Some(inner) = arg
.strip_prefix("fmt.Sprint(")
.and_then(|s| s.strip_suffix(')'))
{
return format!("{}({})", function_string, inner);
}
call_str
}
impl Emitter<'_> {
pub(super) fn emit_regular_call(
&mut self,
output: &mut String,
function: &Expression,
args: &[Expression],
type_args: &[Annotation],
call_ty: Option<&Type>,
spread: Option<&Expression>,
) -> String {
if let Some(go_name) = self.get_callee_go_name(function).map(str::to_string) {
let stages: Vec<Staged> = args.iter().map(|a| self.stage_operand(a)).collect();
let wrap_to_any = Self::spread_needs_any_wrap(function, spread);
let args_strings =
self.sequence_with_spread(output, stages, spread, wrap_to_any, "_arg");
return format!("{}({})", go_name, args_strings.join(", "));
}
let saved = self.emitting_call_callee;
self.emitting_call_callee = true;
let mut function_string = self.emit_operand(output, function);
self.emitting_call_callee = saved;
if matches!(
function,
Expression::Unary {
operator: UnaryOperator::Deref,
..
}
) {
function_string = format!("({})", function_string);
}
let type_args_string =
self.resolve_call_type_args(function, type_args, call_ty, &mut function_string);
let pointer_indices = self.get_recursive_enum_pointer_indices(function);
let fn_param_types: Vec<Type> = match function.get_type().unwrap_forall() {
Type::Function { params, .. } => params.clone(),
_ => vec![],
};
let is_go_call = matches!(
function.unwrap_parens(),
Expression::DotAccess { expression, .. } if Self::is_go_receiver(expression)
);
let ctx = CallArgsContext {
fn_param_types: &fn_param_types,
pointer_indices: &pointer_indices,
is_go_call,
spread,
wrap_spread_to_any: Self::spread_needs_any_wrap(function, spread),
};
let args_strings = self.emit_call_args(output, args, &ctx);
let call_str = format!(
"{}{}({})",
function_string,
type_args_string,
args_strings.join(", ")
);
let call_str = collapse_fmt_print(&function_string, &args_strings, call_str);
if let Some(wrapped) = self.wrap_go_array_return(output, function, &call_str) {
return wrapped;
}
call_str
}
fn wrap_go_array_return(
&mut self,
output: &mut String,
function: &Expression,
call_str: &str,
) -> Option<String> {
if self.skip_array_return_wrap {
return None;
}
let Expression::DotAccess {
expression: receiver_expression,
member,
..
} = function.unwrap_parens()
else {
return None;
};
if !Self::is_go_receiver(receiver_expression)
|| !self.has_go_array_return(receiver_expression, member)
{
return None;
}
let temp = self.fresh_var(Some("arr"));
self.declare(&temp);
write_line!(output, "{} := {}", temp, call_str);
Some(format!("{}[:]", temp))
}
fn resolve_call_type_args(
&mut self,
function: &Expression,
type_args: &[Annotation],
call_ty: Option<&Type>,
function_string: &mut String,
) -> String {
let mut type_args_string = self.format_type_args_from_annotations(type_args);
let slot_ty = self.current_slot_expected_ty.clone();
if type_args_string.is_empty()
&& let Some(inferred) = self.infer_return_only_type_args(function)
{
type_args_string = slot_ty
.as_ref()
.and_then(|t| self.prelude_container_type_args(t))
.unwrap_or(inferred);
}
if type_args_string.is_empty() && Self::is_prelude_variant_constructor(function) {
let candidate = call_ty
.and_then(|t| self.prelude_container_type_args(t))
.or_else(|| {
slot_ty
.as_ref()
.and_then(|t| self.prelude_container_type_args(t))
});
type_args_string = candidate.unwrap_or_default();
}
if !type_args_string.is_empty()
&& let Some(bracket_start) = function_string.find('[')
{
function_string.truncate(bracket_start);
}
type_args_string
}
fn emit_call_args(
&mut self,
output: &mut String,
args: &[Expression],
ctx: &CallArgsContext<'_>,
) -> Vec<String> {
let stages: Vec<Staged> = args
.iter()
.enumerate()
.map(|(i, arg)| {
let mut setup = String::new();
let value = self.emit_call_arg(&mut setup, arg, i, ctx);
Staged::new(setup, value)
})
.collect();
self.sequence_with_spread(output, stages, ctx.spread, ctx.wrap_spread_to_any, "_arg")
}
fn spread_needs_any_wrap(function: &Expression, spread: Option<&Expression>) -> bool {
let Some(spread_expr) = spread else {
return false;
};
let Some(variadic_elem) = function.get_type().unwrap_forall().is_variadic() else {
return false;
};
if !variadic_elem.is_unknown() {
return false;
}
spread_expr
.get_type()
.inner()
.is_some_and(|t| !t.is_unknown())
}
fn emit_call_arg(
&mut self,
output: &mut String,
arg: &Expression,
index: usize,
ctx: &CallArgsContext<'_>,
) -> String {
let effective_param_ty = self.effective_param_type(index, ctx.fn_param_types);
if ctx.is_go_call
&& let Some(result) = self.try_emit_callback_wrapper(output, arg, effective_param_ty)
{
return result;
}
if let Some(result) = self.try_emit_nullable_coercion(output, arg, effective_param_ty) {
return result;
}
if ctx.pointer_indices.contains(&index) {
let value = self.emit_value(output, arg);
if matches!(arg, Expression::Reference { .. }) || arg.get_type().is_ref() {
return value;
}
let temp = self.fresh_var(Some("ptr"));
self.declare(&temp);
write_line!(output, "{} := {}", temp, value);
return format!("&{}", temp);
}
let value = self.emit_composite_value(output, arg);
match effective_param_ty {
Some(target) => {
let coercion = Coercion::resolve(self, &arg.get_type(), target);
coercion.apply(self, output, value)
}
None => value,
}
}
fn effective_param_type<'a>(
&self,
index: usize,
fn_param_types: &'a [Type],
) -> Option<&'a Type> {
fn_param_types.get(index).or_else(|| {
fn_param_types
.last()
.filter(|t| t.get_name() == Some("VarArgs"))
})
}
fn try_emit_callback_wrapper(
&mut self,
output: &mut String,
arg: &Expression,
effective_param_ty: Option<&Type>,
) -> Option<String> {
let param_fn_ty = effective_param_ty.and_then(|param_ty| {
let unwrapped = param_ty.unwrap_forall();
let fn_ty = match unwrapped {
Type::Function { .. } => unwrapped.clone(),
Type::Nominal {
underlying_ty: Some(inner),
..
} => inner.unwrap_forall().clone(),
_ => return None,
};
if let Type::Function { return_type, .. } = &fn_ty
&& (return_type.is_result()
|| return_type.is_option()
|| return_type.tuple_arity().is_some_and(|a| a >= 2))
{
return Some(fn_ty);
}
None
})?;
let value = self.emit_value(output, arg);
Some(self.emit_lisette_callback_wrapper(output, &value, ¶m_fn_ty))
}
fn try_emit_nullable_coercion(
&mut self,
output: &mut String,
arg: &Expression,
effective_param_ty: Option<&Type>,
) -> Option<String> {
let param_ty = effective_param_ty?;
let arg_ty = arg.get_type();
if !self.is_nullable_option(&arg_ty) {
return None;
}
let check_ty = if param_ty.get_name() == Some("VarArgs") {
param_ty.inner().unwrap_or_else(|| param_ty.clone())
} else {
param_ty.clone()
};
let needs_coercion = self
.as_interface(&check_ty)
.is_some_and(|id| go_name::is_go_import(&id))
|| (check_ty.has_name("Unknown") && {
let inner = arg_ty.ok_type();
self.as_interface(&inner)
.is_some_and(|id| go_name::is_go_import(&id))
});
if !needs_coercion {
return None;
}
if matches!(arg, Expression::Identifier { value, .. } if value == "None") {
return Some("nil".to_string());
}
let value = self.emit_value(output, arg);
let coercion = Coercion::resolve_unwrap_go_nullable(self, &arg.get_type());
Some(coercion.apply(self, output, value))
}
}