use crate::abi::is_tagged_shape_fn_value;
use crate::expressions::access::struct_call::emit_struct_literal;
use syntax::program::DefinitionBody;
use crate::EmitEffects;
use crate::GoCallStrategy;
use crate::Planner;
use crate::Renderer;
use crate::abi::AbiShape;
use crate::abi::coercion::{Coercion, CoercionDirection};
use crate::abi::transition::{emit_lisette_callback_wrapper, lower_callee_abi_wrapping};
use crate::calls::CallBoundary;
use crate::context::expression::ExpressionContext;
use crate::expressions::emission::StagedExpression;
use crate::is_order_sensitive;
use crate::plan::bodies::{
ExpressionStatementForm, ExpressionStatementPlan, LoweredBlock, LoweredStatement,
};
use crate::plan::values::{ValuePlan, setup_from_string, value_plan_from_statements};
use crate::state::bindings::BindingValue;
use crate::write_line;
use syntax::ast::Expression;
use syntax::program::CallKind;
use syntax::types::Type;
impl Planner<'_> {
pub(crate) fn declare_result_var(
&mut self,
output: &mut String,
ty: &Type,
fx: &mut EmitEffects,
) -> String {
let result_var = self.fresh_var(None);
write_line!(output, "var {} {}", result_var, self.go_type_string(ty, fx));
self.declare(&result_var);
result_var
}
pub(crate) fn lower_value(
&mut self,
expression: &Expression,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
if let Some(strategy) = self.classify_go_fn_value(expression) {
if !self.go_fn_matches_lowered_slot(expression, &strategy, ctx) {
let mut buffer = String::new();
let value = if self.go_fn_needs_lowered_tuple_adapter(expression, ctx) {
self.emit_go_fn_lowered_tuple_adapter(&mut buffer, expression, fx)
} else {
self.emit_go_fn_wrapper(&mut buffer, expression, &strategy, fx)
};
return (setup_from_string(buffer), value);
}
} else if self.is_go_array_return_value(expression) {
let mut buffer = String::new();
let value = self.emit_array_return_wrapper(&mut buffer, expression, fx);
return (setup_from_string(buffer), value);
}
let plan = self.plan_operand(expression, ctx, fx);
let staged = StagedExpression::from_plan(plan, expression);
(staged.setup, staged.value)
}
pub(crate) fn lower_composite_value(
&mut self,
expression: &Expression,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> (Vec<LoweredStatement>, String) {
if expression.get_type().is_unit()
&& matches!(
expression.unwrap_parens(),
Expression::Call { .. } | Expression::Block { .. }
)
{
let (mut setup, call_str) = self.lower_value(expression, ctx, fx);
if !call_str.is_empty() {
setup.push(LoweredStatement::RawGo(format!("{call_str}\n")));
}
return (setup, "struct{}{}".to_string());
}
self.lower_value(expression, ctx, fx)
}
fn maybe_lower_tagged_fn_ref(
&mut self,
output: &mut String,
expression: &Expression,
ty: &Type,
raw: String,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> String {
if ctx.is_callee() || ctx.forces_tagged_go_function() {
return raw;
}
if !is_tagged_shape_fn_value(expression) {
return raw;
}
let fn_ty = ty.unwrap_forall();
let Type::Function(f) = fn_ty else {
return raw;
};
if self.classify_direct_emission(&f.return_type).is_none() {
return raw;
}
emit_lisette_callback_wrapper(self, output, &raw, fn_ty, fx)
}
fn go_fn_matches_lowered_slot(
&self,
expression: &Expression,
strategy: &GoCallStrategy,
ctx: ExpressionContext<'_>,
) -> bool {
if ctx.forces_tagged_go_function() {
return false;
}
let fn_ty = expression.get_type();
let Type::Function(f) = fn_ty.unwrap_forall() else {
return false;
};
let Some(shape) = self.classify_direct_emission(&f.return_type) else {
return false;
};
if self.fallible_tuple_return(&f.return_type) {
return false;
}
shape.matches_go_strategy(strategy)
}
fn fallible_tuple_return(&self, return_type: &Type) -> bool {
matches!(
self.classify_direct_emission(return_type),
Some(AbiShape::ResultTuple | AbiShape::PartialTuple)
) && self
.facts
.peel_alias(return_type)
.ok_type()
.tuple_arity()
.is_some_and(|arity| arity >= 2)
}
fn go_fn_needs_lowered_tuple_adapter(
&self,
expression: &Expression,
ctx: ExpressionContext<'_>,
) -> bool {
if ctx.forces_tagged_go_function() {
return false;
}
let fn_ty = expression.get_type();
let Type::Function(f) = fn_ty.unwrap_forall() else {
return false;
};
self.fallible_tuple_return(&f.return_type)
}
pub(crate) fn plan_operand_leaf(
&mut self,
expression: &Expression,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> ValuePlan {
match expression {
Expression::Literal { literal, ty, .. } => {
let mut buffer = String::new();
let value = self.emit_literal(&mut buffer, literal, ty, fx);
value_plan_from_statements(setup_from_string(buffer), value)
}
Expression::Identifier { value, ty, .. } => {
let mut buffer = String::new();
let raw = self.emit_identifier(value, ty, ctx, fx);
let value =
self.maybe_lower_tagged_fn_ref(&mut buffer, expression, ty, raw, ctx, fx);
value_plan_from_statements(setup_from_string(buffer), value)
}
Expression::Call { ty, .. } => match self.classify_call(expression) {
CallBoundary::GoWrapped(strategy) => {
let (setup, value) = self.lower_go_wrapped_call(expression, &strategy, ty, fx);
value_plan_from_statements(setup, value)
}
CallBoundary::LoweredCallee(shape) => {
fx.require_stdlib();
let (mut setup, call_str) = self.lower_call(expression, Some(ty), ctx, fx);
let (wrap, value) = lower_callee_abi_wrapping(self, &shape, &call_str, ty, fx);
setup.extend(wrap);
value_plan_from_statements(setup, value)
}
CallBoundary::Plain => {
let (setup, value) = self.lower_call(expression, Some(ty), ctx, fx);
value_plan_from_statements(setup, value)
}
},
Expression::RawGo { text } => ValuePlan::Operand(text.clone()),
Expression::Unit { .. } => ValuePlan::Operand("struct{}{}".to_string()),
Expression::NoOp => ValuePlan::Operand(String::new()),
Expression::Lambda {
params, body, ty, ..
}
| Expression::Function {
params, body, ty, ..
} => ValuePlan::Operand(self.emit_lambda(params, body, ty, ctx, fx)),
Expression::Match { ty, .. }
| Expression::Select { ty, .. }
| Expression::Block { ty, .. } => {
let mut buffer = String::new();
let value = self.emit_to_operand_temp(&mut buffer, expression, ty, fx);
value_plan_from_statements(setup_from_string(buffer), value)
}
Expression::Return {
expression: return_expression,
..
} => {
let plan = self.build_return_plan(return_expression, String::new(), fx);
value_plan_from_statements(vec![LoweredStatement::Return(plan)], String::new())
}
Expression::Assignment { target, value, .. } => {
let mut buffer = String::new();
self.emit_assignment_operand(&mut buffer, target, value, fx);
value_plan_from_statements(setup_from_string(buffer), "struct{}{}".to_string())
}
Expression::IfLet { .. } => {
unreachable!("IfLet should be desugared to Match before emit")
}
_ => unreachable!(
"unexpected leaf expression in plan_operand: {:?}",
expression
),
}
}
pub(crate) fn plan_tuple_value(
&mut self,
elements: &[Expression],
ty: &Type,
in_tail: bool,
fx: &mut EmitEffects,
) -> ValuePlan {
use value_plan_from_statements;
let inferred_slot_types: Vec<Type> = match ty {
Type::Tuple(slots) => slots.clone(),
_ => Vec::new(),
};
let slot_types = self.resolve_tuple_slot_types(inferred_slot_types, in_tail);
let stages: Vec<StagedExpression> = elements
.iter()
.enumerate()
.map(|(i, e)| {
let element_ctx =
ExpressionContext::value().with_expected_slot_type(slot_types.get(i));
self.stage_composite(e, element_ctx, fx)
})
.collect();
let (mut setup, element_expressions) = self.sequence_structured(stages, "_v");
let mut wrapped_expressions: Vec<String> = Vec::with_capacity(element_expressions.len());
for (i, (expr, emitted)) in elements.iter().zip(element_expressions).enumerate() {
let value = match slot_types.get(i) {
Some(slot) => {
let coercion = Coercion::resolve(
self,
&expr.get_type(),
slot,
CoercionDirection::Internal,
);
let (coercion_setup, coerced) = coercion.lower(self, emitted, fx);
setup.extend(coercion_setup);
coerced
}
None => emitted,
};
wrapped_expressions.push(value);
}
let element_expressions = wrapped_expressions;
fx.require_stdlib();
let arity = element_expressions.len();
let needs_explicit_type_args =
!slot_types.is_empty() && slot_types.iter().any(|t| self.facts.is_interface(t));
let value = if !needs_explicit_type_args {
format!(
"lisette.MakeTuple{}({})",
arity,
element_expressions.join(", ")
)
} else {
let slot_ty_strs: Vec<String> = slot_types
.iter()
.map(|t| self.go_type_string(t, fx))
.collect();
format!(
"lisette.MakeTuple{}[{}]({})",
arity,
slot_ty_strs.join(", "),
element_expressions.join(", ")
)
};
value_plan_from_statements(setup, value)
}
pub(crate) fn plan_cast(
&mut self,
expression: &Expression,
target_type: &syntax::ast::Annotation,
ty: &Type,
ctx: ExpressionContext<'_>,
fx: &mut EmitEffects,
) -> ValuePlan {
let inner = self.plan_operand(expression, ctx, fx);
if let Type::Nominal { id, .. } = &self.facts.peel_alias(ty)
&& matches!(
self.facts.definition(id.as_str()).map(|d| &d.body),
Some(DefinitionBody::Interface { .. })
)
{
let staged = StagedExpression::from_plan(inner, expression);
let mut setup = staged.setup;
let source_ty = expression.get_type();
let coercion = Coercion::resolve(self, &source_ty, ty, CoercionDirection::Internal);
let (coercion_setup, coerced) = coercion.lower(self, staged.value, fx);
setup.extend(coercion_setup);
return value_plan_from_statements(setup, coerced);
}
let go_type = self.annotation_to_go_type(target_type, fx);
ValuePlan::Cast {
go_type,
inner: Box::new(inner),
}
}
pub(crate) fn plan_reference(
&mut self,
inner: &Expression,
ty: &Type,
fx: &mut EmitEffects,
) -> ValuePlan {
if inner.get_type().is_unit() && matches!(inner.unwrap_parens(), Expression::Call { .. }) {
let staged = self.stage_operand(inner.unwrap_parens(), ExpressionContext::value(), fx);
let mut setup = staged.setup;
if !staged.value.is_empty() {
setup.push(LoweredStatement::Expression(ExpressionStatementPlan {
directive: String::new(),
form: ExpressionStatementForm::Async {
value: ValuePlan::Operand(staged.value),
},
}));
}
let tmp = self.hoist_tmp_value_statement(&mut setup, "ref", "struct{}{}");
return value_plan_from_statements(setup, format!("&{}", tmp));
}
let (mut setup, emitted) = self.lower_value(inner, ExpressionContext::value(), fx);
let value = if inner.get_type() == *ty {
emitted
} else if self.is_go_unaddressable(inner) || matches!(inner.get_type(), Type::Function(_)) {
let tmp = self.hoist_tmp_value_statement(&mut setup, "ref", &emitted);
format!("&{}", tmp)
} else {
format!("&{}", emitted)
};
value_plan_from_statements(setup, value)
}
pub(crate) fn contains_newtype_access(&self, expression: &Expression) -> bool {
let mut current = expression;
while let Expression::DotAccess {
expression: inner,
member,
..
} = current
{
if member.parse::<usize>().is_ok()
&& self.is_newtype_struct(&inner.get_type().strip_refs())
{
return true;
}
current = inner;
}
false
}
fn emit_assignment_operand(
&mut self,
output: &mut String,
target: &Expression,
value: &Expression,
fx: &mut EmitEffects,
) {
let rhs_staged = self.stage_composite(value, ExpressionContext::value(), fx);
let target_str = if is_order_sensitive(target) {
self.emit_left_value_capturing(output, target, !rhs_staged.setup.is_empty(), fx)
} else {
self.emit_left_value(output, target, fx)
};
output.push_str(&Renderer.render_setup(&rhs_staged.setup));
if let Expression::DotAccess {
expression: receiver,
ty,
..
} = target
&& self.go_imported_shape(&receiver.get_type()).is_some()
&& self.is_go_nullable(ty)
{
let coercion =
Coercion::resolve(self, &value.get_type(), ty, CoercionDirection::ToGoBoundary);
let (coercion_setup, unwrapped) = coercion.lower(self, rhs_staged.value, fx);
output.push_str(&Renderer.render_setup(&coercion_setup));
write_line!(output, "{} = {}", target_str, unwrapped);
} else {
write_line!(output, "{} = {}", target_str, rhs_staged.value);
}
}
pub(crate) fn plan_range_value(
&mut self,
start: &Option<Box<Expression>>,
end: &Option<Box<Expression>>,
_inclusive: bool,
ty: &Type,
fx: &mut EmitEffects,
) -> ValuePlan {
let type_string = self.go_type_string(ty, fx);
let mut stages: Vec<StagedExpression> = Vec::new();
let has_start = start.is_some();
if let Some(s) = start {
stages.push(self.stage_operand(s, ExpressionContext::value(), fx));
}
if let Some(e) = end {
stages.push(self.stage_operand(e, ExpressionContext::value(), fx));
}
if stages.is_empty() {
return ValuePlan::Operand("struct{}{}".to_string());
}
let (setup, values) = self.sequence_structured(stages, "_range");
let mut fields = Vec::new();
if has_start {
fields.push(("Start".to_string(), values[0].clone()));
if values.len() > 1 {
fields.push(("End".to_string(), values[1].clone()));
}
} else {
fields.push(("End".to_string(), values[0].clone()));
}
let value = emit_struct_literal(&type_string, &fields, ExpressionContext::value());
value_plan_from_statements(setup, value)
}
pub(crate) fn with_fresh_scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
let frame = self.scope.enter_isolated_function();
let result = f(self);
self.scope.exit_isolated_function(frame);
result
}
fn with_eager_operand_capture<R>(
&mut self,
enabled: bool,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let previous = self.function_state.set_eager_operand_capture(enabled);
let result = f(self);
self.function_state.set_eager_operand_capture(previous);
result
}
pub(crate) fn plan_async_wrapper(
&mut self,
keyword: &str,
expression: &Expression,
fx: &mut EmitEffects,
) -> ValuePlan {
if let Expression::Block { .. } = expression {
let body = self.with_fresh_scope(|planner| planner.lower_block_as_body(expression, fx));
let setup = vec![LoweredStatement::Expression(ExpressionStatementPlan {
directive: String::new(),
form: ExpressionStatementForm::AsyncBlock {
keyword: keyword.to_string(),
body,
},
})];
return value_plan_from_statements(setup, String::new());
}
let mut buffer = String::new();
if let Some(call_str) = self.emit_go_call_discarded(&mut buffer, expression, fx) {
return value_plan_from_statements(
setup_from_string(buffer),
format!("{} {}", keyword, call_str),
);
}
let (setup, inner) = self.lower_value(expression, ExpressionContext::value(), fx);
if needs_iife_for_async(expression, &inner) {
let (mut setup, inner) = self.with_eager_operand_capture(true, |planner| {
planner.lower_value(expression, ExpressionContext::value(), fx)
});
let mut body_statements = Vec::new();
if !inner.is_empty() {
let line = if expression.get_type().is_unit() {
format!("{}\n", inner)
} else {
format!("_ = {}\n", inner)
};
body_statements.push(LoweredStatement::RawGo(line));
}
let body = LoweredBlock {
statements: body_statements,
};
setup.push(LoweredStatement::Expression(ExpressionStatementPlan {
directive: String::new(),
form: ExpressionStatementForm::AsyncBlock {
keyword: keyword.to_string(),
body,
},
}));
return value_plan_from_statements(setup, String::new());
}
value_plan_from_statements(setup, format!("{} {}", keyword, inner))
}
}
impl Planner<'_> {
fn is_go_unaddressable(&self, expression: &Expression) -> bool {
match expression.unwrap_parens() {
Expression::Call { .. } => true,
Expression::Identifier { value, ty, .. }
if !matches!(ty.unwrap_forall(), Type::Function(_)) =>
{
self.identifier_is_unaddressable(value, ty)
}
Expression::DotAccess { expression, ty, .. }
if !matches!(ty.unwrap_forall(), Type::Function(_)) =>
{
self.dot_access_is_unaddressable(expression, ty)
}
_ => false,
}
}
fn identifier_is_unaddressable(&self, value: &str, ty: &Type) -> bool {
match self.scope.resolve_identifier_binding(value) {
Some(BindingValue::GoName(_)) => false,
Some(BindingValue::InlineExpr(_)) => true,
None => self.ty_is_enum(ty),
}
}
fn dot_access_is_unaddressable(&self, receiver: &Expression, ty: &Type) -> bool {
if !self.ty_is_enum(ty) {
return false;
}
let Type::Nominal {
id: receiver_id, ..
} = &receiver.get_type()
else {
return false;
};
matches!(
self.facts.definition(receiver_id.as_str()).map(|d| &d.body),
Some(DefinitionBody::Enum { .. } | DefinitionBody::TypeAlias { .. })
)
}
fn ty_is_enum(&self, ty: &Type) -> bool {
let Type::Nominal { id, .. } = ty else {
return false;
};
matches!(
self.facts.definition(id.as_str()).map(|d| &d.body),
Some(DefinitionBody::Enum { .. })
)
}
}
fn is_native_method_call(expression: &Expression) -> bool {
matches!(
expression.unwrap_parens(),
Expression::Call {
call_kind: Some(CallKind::NativeMethod(_) | CallKind::NativeMethodIdentifier(_)),
..
}
)
}
fn needs_iife_for_async(expression: &Expression, emitted: &str) -> bool {
if !is_native_method_call(expression) {
return false;
}
!is_valid_go_async_target(emitted)
}
fn is_valid_go_async_target(emitted: &str) -> bool {
let trimmed = emitted.trim();
if !trimmed.ends_with(')') {
return false;
}
let Some(open) = trimmed.find('(') else {
return false;
};
let callee = trimmed[..open].trim_end();
if callee.is_empty() || callee.starts_with('[') {
return false;
}
!matches!(
callee,
"len" | "cap" | "append" | "copy" | "new" | "make" | "complex" | "real" | "imag"
)
}