use std::rc::Rc;
use rustc_hash::{FxHashMap, FxHashSet};
use super::EmissionWork;
use super::fragment::{Code, Fragment, Precedence};
use super::let_annotation::LetAnnotation;
use super::local_closures::LocalClosures;
use super::locals::LocalNames;
use super::mangle::NameMangler;
use super::open_literals::{OpenLiterals, TypeContext};
use super::operator::{RustOperator, Side};
use super::plan::{EmitPlan, ItemKind};
use super::problems::EmitProblems;
use super::result_borrows::ResultBorrows;
use super::reused_locals::ReusedLocals;
use super::stored_functions::StoredFunctions;
use super::template::{Template, TemplateError};
use super::types::{LifetimeScope, RustTypes, TypePosition};
use super::unsized_parameters::UnsizedParameters;
use crate::core::{
BindingId, BindingKind, FieldLabel, Literal, ModuleId, ParsedTemplate, PrimitiveType,
ReferenceKind, RustTemplate, VariantInfo,
};
use crate::diagnostic::Diagnostic;
use crate::ownership::StructUpdateOrder;
use crate::reader::{Number, NumberValue};
use crate::source::Span;
use crate::types::{
Borrow, Bound, ClosureKind, Collection, FieldOrder, GenericId, Type, TypeChecker,
TypedArgument, TypedArm, TypedBinder, TypedBlock, TypedBodyItem, TypedControl, TypedExpr,
TypedExprKind, TypedLambda, TypedLeaf, TypedMatch, TypedPattern, TypedPatternKind,
TypedProgram, TypedStructExpr, VariantPayload,
};
mod tail_loop;
pub(super) use tail_loop::TailLoop;
struct UpdateArgument {
name: Option<String>,
code: Code,
}
impl UpdateArgument {
fn usage(&self) -> Code {
match &self.name {
Some(name) => Code::atom(name.clone()),
None => self.code.clone(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ArgumentRole {
Receiver,
IndexReceiver,
Borrowed,
MutPlace,
Ordinary,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum LoopExit {
Return,
Break,
}
impl LoopExit {
fn keyword(self) -> &'static str {
match self {
LoopExit::Return => "return",
LoopExit::Break => "break",
}
}
}
enum Prologue {
Peel {
binding: BindingId,
statement: String,
expression: String,
},
Destructure {
statement: String,
},
}
impl Prologue {
fn statement(&self) -> &str {
match self {
Prologue::Peel { statement, .. } | Prologue::Destructure { statement } => statement,
}
}
fn expression_of(&self, binding: BindingId) -> Option<&str> {
match self {
Prologue::Peel {
binding: peeled,
expression,
..
} if *peeled == binding => Some(expression),
_ => None,
}
}
}
struct PatternEmission {
code: Code,
guards: Vec<Code>,
prologue: Vec<Prologue>,
}
struct PatternShape {
guard: Code,
binding: Code,
destructure: Code,
guards: Vec<Code>,
prologue: Vec<Prologue>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum GuardBox {
Arm,
Nested,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum BoxPosition {
Owned,
Shared,
Mutable,
}
impl BoxPosition {
fn of(ty: &Type) -> Self {
match ty {
Type::Ref(_, _) => BoxPosition::Shared,
Type::Mut(_, _) => BoxPosition::Mutable,
_ => BoxPosition::Owned,
}
}
}
impl PatternShape {
fn plain(code: Code) -> Self {
Self {
guard: code.clone(),
binding: code.clone(),
destructure: code,
guards: Vec::new(),
prologue: Vec::new(),
}
}
}
impl ArgumentRole {
fn borrows_by_itself(self, borrow: Borrow) -> bool {
matches!(
(self, borrow),
(
ArgumentRole::Receiver | ArgumentRole::IndexReceiver,
Borrow::Shared | Borrow::Mutable,
) | (ArgumentRole::Borrowed, Borrow::Shared)
)
}
}
pub struct ExpressionEmitter<'a> {
program: &'a TypedProgram,
plan: &'a EmitPlan<'a>,
unsized_parameters: &'a UnsizedParameters,
module: ModuleId,
pub locals: LocalNames,
pub problems: Vec<Diagnostic>,
mutable_locals: FxHashSet<BindingId>,
stored_functions: StoredFunctions<'a>,
local_closures: LocalClosures<'a>,
reused_locals: ReusedLocals,
open_literals: OpenLiterals,
enclosing_lifetime: Option<String>,
result_parameters: FxHashSet<BindingId>,
tail_loop: Option<TailLoop>,
loop_exit: LoopExit,
emission: EmissionWork,
}
impl<'a> ExpressionEmitter<'a> {
const ETA_PARAMETER: &'static str = "argument_";
const STRING_GUARD_BINDING: &'static str = "text";
const BOX_BINDING: &'static str = "inner";
const MAP_UPDATE_TEMPLATE: &'static str = "@map-update";
const COMPARED_OPERANDS: usize = 2;
const VIEW_DEPTH_ALLOWANCE: usize = 1;
const INTEGER_CONVERSION: &'static str = "from_literal";
const FLOAT_CONVERSION: &'static str = "from_float_literal";
pub fn new(
program: &'a TypedProgram,
plan: &'a EmitPlan<'a>,
unsized_parameters: &'a UnsizedParameters,
module: ModuleId,
locals: LocalNames,
) -> Self {
Self {
program,
plan,
unsized_parameters,
module,
locals,
problems: Vec::new(),
mutable_locals: FxHashSet::default(),
stored_functions: StoredFunctions::new(program),
local_closures: LocalClosures::new(program),
reused_locals: ReusedLocals::empty(),
open_literals: OpenLiterals::default(),
enclosing_lifetime: None,
result_parameters: FxHashSet::default(),
tail_loop: None,
loop_exit: LoopExit::Return,
emission: EmissionWork::default(),
}
}
pub fn emission(&self) -> EmissionWork {
self.emission
}
pub fn prepare_tail_loop(&mut self, function: BindingId, lambda: &TypedLambda) {
self.tail_loop = TailLoop::of(function, lambda);
}
pub fn with_mutable_locals(mut self, mutable_locals: FxHashSet<BindingId>) -> Self {
self.mutable_locals = mutable_locals;
self
}
pub fn with_local_closures(mut self, local_closures: LocalClosures<'a>) -> Self {
self.local_closures = local_closures;
self
}
pub fn with_reused_locals(mut self, reused_locals: ReusedLocals) -> Self {
self.reused_locals = reused_locals;
self
}
pub fn with_open_literals(mut self, open_literals: OpenLiterals) -> Self {
self.open_literals = open_literals;
self
}
pub fn with_enclosing_lifetime(mut self, lifetime: Option<String>) -> Self {
self.enclosing_lifetime = lifetime;
self
}
pub fn types(&self) -> RustTypes<'a> {
RustTypes::new(
self.program,
self.plan,
self.unsized_parameters,
self.module,
)
}
pub fn bind_parameter(&mut self, param: &TypedBinder, position: TypePosition) -> String {
self.bind_parameter_in(param, position, &LifetimeScope::default())
}
pub fn bind_parameter_in(
&mut self,
param: &TypedBinder,
position: TypePosition,
lifetimes: &LifetimeScope,
) -> String {
let position = if self.stored_functions.stores(param.binding) {
TypePosition::Value
} else {
position
};
let ty = self.types().show_in(¶m.ty, position, lifetimes);
let mutable = self.is_reassigned(param) || self.changes_in_place(param);
format!(
"{}{}: {ty}",
Self::mutability(mutable),
self.bind_name(param)
)
}
fn bind_closure_parameter(&mut self, param: &TypedBinder) -> String {
let (Some(borrow), Type::Function(_, parameters, result)) = (
self.local_closures.parameter_borrow(param.binding),
¶m.ty,
) else {
return self.bind_value_closure_parameter(param);
};
let ty = self.types().borrowed_function(
borrow,
parameters,
result,
self.enclosing_lifetime.as_deref(),
);
let mutable = self.is_reassigned(param);
format!(
"{}{}: {ty}",
Self::mutability(mutable),
self.bind_name(param)
)
}
fn bind_value_closure_parameter(&mut self, param: &TypedBinder) -> String {
if self.result_parameters.contains(¶m.binding) {
return self.bind_result_parameter(param);
}
self.bind_parameter(param, TypePosition::Value)
}
fn bind_result_parameter(&mut self, param: &TypedBinder) -> String {
let (position, scope) =
RustTypes::closure_parameter_scope(self.enclosing_lifetime.as_deref());
self.bind_parameter_in(param, position, &scope)
}
fn bind_name(&mut self, param: &TypedBinder) -> String {
self.locals.bind(
param.binding,
self.program.bindings.get(param.binding).name(),
)
}
fn is_reassigned(&self, param: &TypedBinder) -> bool {
self.tail_loop
.as_ref()
.is_some_and(|tail_loop| tail_loop.reassigns(param.binding))
}
fn changes_in_place(&self, param: &TypedBinder) -> bool {
self.mutable_locals.contains(¶m.binding) && !param.ty.is_reference()
}
fn mutability(mutable: bool) -> &'static str {
if mutable { "mut " } else { "" }
}
pub fn function_body(&mut self, block: &TypedBlock) -> Vec<Fragment> {
if self.tail_loop.is_some() {
let mut fragments = vec![Fragment::Newline];
fragments.extend(self.loop_expression(block, LoopExit::Return));
return fragments;
}
let Some((TypedBodyItem::Expr(tail), leading)) = block.items.split_last() else {
return self.statements(block);
};
let TypedExprKind::Fn(lambda) = &tail.kind else {
return self.statements(block);
};
let mut fragments = self.statements(&TypedBlock {
items: leading.to_vec(),
});
fragments.push(Fragment::Newline);
fragments.extend(
Code::joined(
vec![Code::atom("move "), self.closure(lambda, &tail.ty)],
Precedence::Operator,
)
.spanned(tail.span)
.fragments,
);
fragments
}
pub fn statements(&mut self, block: &TypedBlock) -> Vec<Fragment> {
let Some((tail, leading)) = block.items.split_last() else {
return Vec::new();
};
let mut fragments = self.leading_statements(leading);
fragments.extend(self.tail_statement(tail));
fragments
}
fn leading_statements(&mut self, items: &[TypedBodyItem]) -> Vec<Fragment> {
let mut fragments = Vec::new();
for item in items {
if Self::is_empty_block_statement(item) {
continue;
}
fragments.push(Fragment::Newline);
fragments.extend(self.statement(item, false).fragments);
}
fragments
}
fn is_empty_block_statement(item: &TypedBodyItem) -> bool {
matches!(item, TypedBodyItem::Expr(expr) if Self::is_empty_block(expr))
}
fn tail_statement(&mut self, item: &TypedBodyItem) -> Vec<Fragment> {
if let TypedBodyItem::Expr(expr) = item
&& let Some(contents) = self.tail_block_contents(expr)
{
return contents;
}
let mut fragments = vec![Fragment::Newline];
fragments.extend(self.statement(item, true).fragments);
fragments
}
fn tail_block_contents(&mut self, expr: &TypedExpr) -> Option<Vec<Fragment>> {
if Self::bound_literal(expr).is_some() {
return None;
}
self.inlined_contents(expr)
}
fn inlined_contents(&mut self, expr: &TypedExpr) -> Option<Vec<Fragment>> {
match &expr.kind {
TypedExprKind::Control(TypedControl::Block(block)) => Some(self.block_contents(block)),
TypedExprKind::Control(TypedControl::Match(matched)) => {
let arm = self.sole_sure_arm(&matched.arms)?;
Some(self.sure_arm_contents(&matched.value, arm))
}
TypedExprKind::Call(callee, arguments) => match &callee.kind {
TypedExprKind::Fn(lambda) if !Self::contains_try(&lambda.body) => {
Some(self.inline_call_contents(lambda, arguments))
}
_ => None,
},
_ => None,
}
}
pub fn expr(&mut self, expr: &TypedExpr) -> Code {
self.expr_in(expr, TypeContext::Known).spanned(expr.span)
}
fn expr_in(&mut self, expr: &TypedExpr, context: TypeContext) -> Code {
self.emission.emitted_expressions += 1;
match &expr.kind {
TypedExprKind::Leaf(leaf) => self.leaf(leaf, expr, context),
TypedExprKind::Fn(lambda) => self.closure(lambda, &expr.ty),
TypedExprKind::Call(callee, arguments) => self.call(callee, arguments, expr.span),
TypedExprKind::Recur(_) => {
unreachable!("expansion allows `recur` only in tail position")
}
TypedExprKind::Try { .. } | TypedExprKind::Borrow(..) | TypedExprKind::Deref(..) => {
self.written(expr)
}
_ => self.value(expr),
}
}
fn leaf(&mut self, leaf: &TypedLeaf, expr: &TypedExpr, context: TypeContext) -> Code {
match leaf {
TypedLeaf::Literal(literal) => {
self.literal(literal, &expr.ty, self.literal_context(expr, context))
}
TypedLeaf::Local(binding) => {
Code::atom(self.locals.get(*binding).unwrap_or("_").to_string())
}
TypedLeaf::Global(binding) => self.global(*binding, &expr.ty, expr.span),
TypedLeaf::Rust(template) if !template.clauses.is_empty() => {
self.report_inline_clauses(expr.span)
}
TypedLeaf::Rust(template) => self.rust_value(template, &expr.ty, Vec::new(), expr.span),
}
}
fn propagate(&mut self, value: &TypedExpr) -> Code {
let value = self.expr_in(value, TypeContext::Known);
let value = if value.is_atomic() {
value
} else {
value.parenthesized()
};
Code::joined_outside_delimiters(vec![value, Code::atom("?")], Precedence::Atom)
}
fn written(&mut self, expr: &TypedExpr) -> Code {
match &expr.kind {
TypedExprKind::Try { value, .. } => self.propagate(value),
TypedExprKind::Borrow(kind, value) => self.borrow(*kind, value),
TypedExprKind::Deref(value) => self.deref(value),
_ => unreachable!("the caller matches a written form"),
}
}
fn value(&mut self, expr: &TypedExpr) -> Code {
if let Some(literal) = Self::bound_literal(expr) {
return self.expr_in(literal, TypeContext::Unknown);
}
self.compound_value(expr)
}
fn compound_value(&mut self, expr: &TypedExpr) -> Code {
match &expr.kind {
TypedExprKind::Control(control) => self.control(control),
TypedExprKind::Struct(structure) => self.structure(structure, &expr.ty),
TypedExprKind::Variant(binding, arguments, order) => {
self.variant(*binding, arguments, order)
}
TypedExprKind::Assoc(value, keys, item) => self.assoc_block(value, keys, item),
TypedExprKind::TraitObject(value, trait_binding) => {
self.trait_object(value, *trait_binding)
}
_ => unreachable!("the caller matches a value form"),
}
}
fn bound_literal(expr: &TypedExpr) -> Option<&TypedExpr> {
expr.let_block_value()
.filter(|value| matches!(value.kind, TypedExprKind::Leaf(TypedLeaf::Literal(_))))
}
fn borrow(&mut self, kind: ReferenceKind, value: &TypedExpr) -> Code {
if Self::passes_through_reference(self.program, kind, value) {
return self.expr_in(value, TypeContext::Known);
}
let place = self.borrowed_operand(value, TypeContext::Unknown);
let prefix = match kind {
ReferenceKind::Shared => "&",
ReferenceKind::Mutable => "&mut ",
};
Self::prefixed(prefix, place)
}
fn passes_through_reference(
program: &TypedProgram,
kind: ReferenceKind,
value: &TypedExpr,
) -> bool {
let TypedExprKind::Leaf(TypedLeaf::Local(binding)) = &value.kind else {
return false;
};
program.borrowed_parameters.contains(binding)
&& matches!(
(kind, &value.ty),
(ReferenceKind::Shared, Type::Ref(_, _))
| (ReferenceKind::Mutable, Type::Mut(_, _))
)
}
fn deref(&mut self, value: &TypedExpr) -> Code {
match &value.kind {
TypedExprKind::Borrow(_, place) => self.borrowed_operand(place, TypeContext::Known),
TypedExprKind::Call(callee, _) if self.borrowed_index_template(callee).is_some() => {
self.place(value, TypeContext::Known)
}
_ => Self::prefixed("*", self.expr_in(value, TypeContext::Known)),
}
}
fn borrowed_operand(&mut self, value: &TypedExpr, context: TypeContext) -> Code {
match &value.kind {
TypedExprKind::Borrow(..) => self.expr_in(value, context),
_ => self.place(value, context),
}
}
fn place(&mut self, value: &TypedExpr, context: TypeContext) -> Code {
match &value.kind {
TypedExprKind::Struct(TypedStructExpr::Field(owner, label)) => {
self.field_place(owner, label)
}
TypedExprKind::Borrow(ReferenceKind::Shared, inner) => self.place(inner, context),
TypedExprKind::Call(callee, arguments) => match self.borrowed_index_template(callee) {
Some(template) => {
let types = self.callee_type_arguments(callee);
self.template_call(&template, arguments, types, value.span)
}
None => self.expr_in(value, context),
},
_ => self.expr_in(value, context),
}
}
fn borrowed_index_template(&self, callee: &TypedExpr) -> Option<RustTemplate> {
let template = self.called_template(callee)?;
let place = template.template.strip_prefix('&')?;
if !ParsedTemplate::parse(place).ok()?.is_single_index_place() {
return None;
}
Some(RustTemplate {
template: Rc::from(place),
type_expr: template.type_expr.clone(),
clauses: Vec::new(),
})
}
fn control(&mut self, control: &TypedControl) -> Code {
match control {
TypedControl::Block(block) if block.items.is_empty() => Code::atom("()"),
TypedControl::Block(block) => self.block(block),
TypedControl::If(test, then, otherwise) => self.conditional(test, then, otherwise),
TypedControl::Match(matched) => self.matching(matched),
}
}
fn matching(&mut self, matched: &TypedMatch) -> Code {
if let Some(arm) = self.sole_sure_arm(&matched.arms) {
return self.sure_arm(&matched.value, arm);
}
let uses_strings = Self::uses_string_literals(&matched.arms);
let string_value = Self::is_string_value(&matched.value.ty);
let value = self.expr(&matched.value);
let value = if uses_strings && string_value {
self.string_view(value)
} else {
value
};
let value = value.into_head();
let mut fragments = vec![Fragment::Text("match ".to_string())];
fragments.extend(value.fragments);
fragments.push(Fragment::Text(" {".to_string()));
let position = BoxPosition::of(&matched.value.ty);
let mut inner = Vec::new();
for arm in &matched.arms {
inner.push(Fragment::Newline);
self.locals.enter();
let (head, prologue) = self.arm_head(&arm.pattern, position);
inner.extend(head);
inner.push(Fragment::Text(" => ".to_string()));
inner.extend(self.arm_body(&arm.body, prologue));
inner.push(Fragment::Text(",".to_string()));
self.locals.leave();
}
Self::add_fallback(&mut inner, matched.unguarded_exhaustive);
fragments.push(Fragment::Indented(inner));
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text("}".to_string()));
Code::block(fragments)
}
fn add_fallback(arms: &mut Vec<Fragment>, unguarded_exhaustive: bool) {
if unguarded_exhaustive {
return;
}
arms.push(Fragment::Newline);
arms.push(Fragment::Text("_ => unreachable!(),".to_string()));
}
fn sole_sure_arm<'e>(&self, arms: &'e [TypedArm]) -> Option<&'e TypedArm> {
match arms {
[arm] if self.sure_pattern(&arm.pattern) => Some(arm),
_ => None,
}
}
fn sure_pattern(&self, pattern: &TypedPattern) -> bool {
match &pattern.kind {
TypedPatternKind::Variant(binding, parts) => {
let BindingKind::Variant(_, VariantInfo { owner, .. }) =
self.program.bindings.get(*binding).kind()
else {
unreachable!("a typed variant pattern names a variant binding");
};
self.program
.enums
.get(owner)
.is_some_and(|definition| definition.variants.len() == 1)
&& parts.iter().all(|part| self.sure_pattern(part))
}
TypedPatternKind::Struct(_, parts) => parts.iter().all(|part| self.sure_pattern(part)),
TypedPatternKind::Literal(..) => false,
TypedPatternKind::Wildcard | TypedPatternKind::Bind(_) => true,
}
}
fn sure_arm(&mut self, value: &TypedExpr, arm: &TypedArm) -> Code {
let statements = self.sure_arm_contents(value, arm);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(statements),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn sure_arm_contents(&mut self, value: &TypedExpr, arm: &TypedArm) -> Vec<Fragment> {
let position = BoxPosition::of(&value.ty);
let value = self.expr(value);
self.locals.enter();
let PatternEmission { code, prologue, .. } = self.pattern(&arm.pattern, position);
let mut statements = vec![Fragment::Newline, Fragment::Text("let ".to_string())];
statements.extend(code.fragments);
statements.push(Fragment::Text(" = ".to_string()));
statements.extend(value.fragments);
statements.push(Fragment::Text(";".to_string()));
let body = self.block_contents(&arm.body);
statements.extend(Self::with_prologue(body, &prologue));
self.locals.leave();
statements
}
fn arm_head(
&mut self,
pattern: &TypedPattern,
position: BoxPosition,
) -> (Vec<Fragment>, Vec<Prologue>) {
let PatternEmission {
code,
guards,
prologue,
} = self.pattern(pattern, position);
let mut fragments = code.fragments;
if !guards.is_empty() {
fragments.push(Fragment::Text(" if ".to_string()));
for (position, guard) in guards.into_iter().enumerate() {
if position > 0 {
fragments.push(Fragment::Text(" && ".to_string()));
}
fragments.extend(guard.fragments);
}
}
(fragments, prologue)
}
fn uses_string_literals(arms: &[TypedArm]) -> bool {
arms.iter().any(|arm| {
matches!(
arm.pattern.kind,
TypedPatternKind::Literal(Literal::String(_), _)
)
})
}
fn is_string_value(ty: &Type) -> bool {
match ty {
Type::Primitive(PrimitiveType::String) => true,
Type::Ref(_, inner) => matches!(inner.as_ref(), Type::Primitive(PrimitiveType::String)),
_ => false,
}
}
fn string_view(&self, value: Code) -> Code {
let value = if value.is_atomic() {
value
} else {
value.parenthesized()
};
Code::joined_outside_delimiters(vec![value, Code::atom(".as_str()")], Precedence::Atom)
}
fn arm_body(&mut self, body: &TypedBlock, prologue: Vec<Prologue>) -> Vec<Fragment> {
if let [TypedBodyItem::Expr(only)] = body.items.as_slice()
&& let TypedExprKind::Leaf(TypedLeaf::Local(binding)) = &only.kind
&& let Some(expression) = Self::peeled_local(&prologue, *binding)
{
let expression = expression.to_string();
return Self::peeled_arm_body(&expression, *binding, prologue);
}
if let Some(fragments) = self.sure_match_arm_body(body, &prologue) {
return fragments;
}
self.prologue_body(body, prologue)
}
fn peeled_arm_body(
expression: &str,
binding: BindingId,
prologue: Vec<Prologue>,
) -> Vec<Fragment> {
if !prologue
.iter()
.any(|entry| matches!(entry, Prologue::Destructure { .. }))
{
return vec![Fragment::Text(expression.to_string())];
}
let expression = expression.to_string();
let remaining: Vec<Prologue> = prologue
.into_iter()
.filter(|entry| entry.expression_of(binding).is_none())
.collect();
let body = vec![Fragment::Text(expression)];
let contents = Self::with_prologue(body, &remaining);
Self::block_with(contents)
}
fn sure_match_arm_body(
&mut self,
body: &TypedBlock,
prologue: &[Prologue],
) -> Option<Vec<Fragment>> {
let [TypedBodyItem::Expr(only)] = body.items.as_slice() else {
return None;
};
let TypedExprKind::Control(TypedControl::Match(matched)) = &only.kind else {
return None;
};
let arm = self.sole_sure_arm(&matched.arms)?;
let contents = self.sure_arm_contents(&matched.value, arm);
Some(Self::block_with(Self::with_prologue(contents, prologue)))
}
fn prologue_body(&mut self, body: &TypedBlock, prologue: Vec<Prologue>) -> Vec<Fragment> {
let body = self.body_fragments(body);
if prologue.is_empty() {
return body;
}
Self::block_with(Self::with_prologue(body, &prologue))
}
fn block_with(contents: Vec<Fragment>) -> Vec<Fragment> {
vec![
Fragment::Text("{".to_string()),
Fragment::Indented(contents),
Fragment::Newline,
Fragment::Text("}".to_string()),
]
}
fn peeled_local(prologue: &[Prologue], binding: BindingId) -> Option<&str> {
prologue
.iter()
.find_map(|entry| entry.expression_of(binding))
}
fn body_fragments(&mut self, body: &TypedBlock) -> Vec<Fragment> {
match body.items.as_slice() {
[] => vec![Fragment::Text("{}".to_string())],
[TypedBodyItem::Expr(only)] if Self::is_empty_block(only) => {
vec![Fragment::Text("{}".to_string())]
}
[TypedBodyItem::Expr(only)] => self.expr(only).fragments,
_ => self.block(body).fragments,
}
}
fn with_prologue(body: Vec<Fragment>, prologue: &[Prologue]) -> Vec<Fragment> {
let mut fragments = Vec::new();
for entry in prologue {
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text(entry.statement().to_string()));
}
if !fragments.is_empty() && !matches!(body.first(), Some(Fragment::Newline)) {
fragments.push(Fragment::Newline);
}
fragments.extend(body);
fragments
}
fn pattern(&mut self, pattern: &TypedPattern, position: BoxPosition) -> PatternEmission {
let shape = self.child_shape(pattern, position, GuardBox::Arm);
PatternEmission {
code: shape.binding,
guards: shape.guards,
prologue: shape.prologue,
}
}
fn child_shape(
&mut self,
pattern: &TypedPattern,
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
match pattern.boxed {
true => self.boxed_shape(pattern, position, guard_box),
false => self.shape(pattern, position, guard_box),
}
}
fn shape(
&mut self,
pattern: &TypedPattern,
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
match &pattern.kind {
TypedPatternKind::Wildcard => PatternShape::plain(Code::atom("_")),
TypedPatternKind::Bind(binder) => {
let name = self.pattern_binding(binder);
PatternShape {
guard: Code::atom("_"),
binding: Code::atom(name.clone()),
destructure: Code::atom(name),
guards: Vec::new(),
prologue: Vec::new(),
}
}
TypedPatternKind::Variant(binding, patterns) => {
self.variant_shape(*binding, patterns, position, guard_box)
}
TypedPatternKind::Struct(binding, patterns) => {
self.struct_shape(*binding, patterns, position, guard_box)
}
TypedPatternKind::Literal(literal, ty) => self.literal_shape(literal, ty),
}
}
fn pattern_binding(&mut self, binder: &TypedBinder) -> String {
let name = self.locals.bind(
binder.binding,
self.program.bindings.get(binder.binding).name(),
);
if self.mutable_locals.contains(&binder.binding) && !binder.ty.is_reference() {
format!("mut {name}")
} else {
name
}
}
fn boxed_shape(
&mut self,
pattern: &TypedPattern,
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
match &pattern.kind {
TypedPatternKind::Wildcard => PatternShape::plain(Code::atom("_")),
TypedPatternKind::Bind(binder) => self.boxed_binding_shape(binder, position),
_ => self.boxed_value_shape(pattern, position, guard_box),
}
}
fn boxed_binding_shape(&mut self, binder: &TypedBinder, position: BoxPosition) -> PatternShape {
let name = self.locals.bind(
binder.binding,
self.program.bindings.get(binder.binding).name(),
);
let expression = Self::peel_expression(&name, position);
let peeled =
match self.mutable_locals.contains(&binder.binding) && !binder.ty.is_reference() {
true => format!("mut {name}"),
false => name.clone(),
};
PatternShape {
guard: Code::atom("_"),
binding: Code::atom(name.clone()),
destructure: Code::atom(name.clone()),
guards: Vec::new(),
prologue: vec![Prologue::Peel {
binding: binder.binding,
statement: format!("let {peeled} = {expression};"),
expression,
}],
}
}
fn boxed_value_shape(
&mut self,
pattern: &TypedPattern,
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let sure = self.sure_pattern(pattern);
let name = self.locals.temporary(Self::BOX_BINDING);
let expression = Self::peel_expression(&name, position);
let guard = Self::guard_expression(&name, position, guard_box);
let inner = self.shape(pattern, position, GuardBox::Nested);
let guard_condition = Self::matches_guard(&guard, &inner);
let mut prologue = Vec::new();
let destructure = match Self::needs_prologue(pattern) {
true => {
let statement = Self::destructure_statement(&expression, &inner.destructure, sure);
prologue.push(Prologue::Destructure { statement });
prologue.extend(inner.prologue);
Code::atom(name.clone())
}
false => Code::atom("_"),
};
PatternShape {
guards: vec![guard_condition],
guard: Code::atom(name.clone()),
binding: Code::atom(name),
destructure,
prologue,
}
}
fn needs_prologue(pattern: &TypedPattern) -> bool {
match &pattern.kind {
TypedPatternKind::Wildcard | TypedPatternKind::Literal(..) => false,
TypedPatternKind::Bind(_) => true,
TypedPatternKind::Variant(_, patterns) | TypedPatternKind::Struct(_, patterns) => {
patterns.iter().any(Self::needs_prologue)
}
}
}
fn peel_expression(name: &str, position: BoxPosition) -> String {
match position {
BoxPosition::Owned => format!("*{name}"),
BoxPosition::Shared => format!("&**{name}"),
BoxPosition::Mutable => format!("&mut **{name}"),
}
}
fn guard_expression(name: &str, position: BoxPosition, guard_box: GuardBox) -> String {
match guard_box {
GuardBox::Nested => format!("&**{name}"),
GuardBox::Arm => match position {
BoxPosition::Owned => format!("&*{name}"),
BoxPosition::Shared | BoxPosition::Mutable => format!("&**{name}"),
},
}
}
fn matches_guard(expression: &str, shape: &PatternShape) -> Code {
let conditions: Vec<String> = shape.guards.iter().map(Code::text).collect();
let guard = match conditions.is_empty() {
true => shape.guard.text(),
false => format!("{} if {}", shape.guard.text(), conditions.join(" && ")),
};
Code::atom(format!("matches!({expression}, {guard})"))
}
fn destructure_statement(expression: &str, binding: &Code, sure: bool) -> String {
let pattern = binding.text();
match sure {
true => format!("let {pattern} = {expression};"),
false => format!("let {pattern} = {expression} else {{ unreachable!() }};"),
}
}
fn literal_shape(&mut self, literal: &Literal, ty: &Type) -> PatternShape {
match literal {
Literal::Number(number) => {
let code = Code::atom(Self::number_digits(number, ty));
PatternShape::plain(code)
}
Literal::Bool(value) => {
let code = Code::atom(value.to_string());
PatternShape::plain(code)
}
Literal::String(text) if matches!(ty, Type::Primitive(PrimitiveType::String)) => {
let binding = self.locals.temporary(Self::STRING_GUARD_BINDING);
PatternShape {
guard: Code::atom(binding.clone()),
binding: Code::atom(binding.clone()),
destructure: Code::atom("_"),
guards: vec![Code::atom(format!("{binding} == {text:?}"))],
prologue: Vec::new(),
}
}
Literal::String(text) => {
let code = Code::atom(format!("{text:?}"));
PatternShape::plain(code)
}
}
}
fn struct_shape(
&mut self,
binding: BindingId,
patterns: &[TypedPattern],
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let fields = self
.program
.structs
.get(binding)
.expect("a reached struct has a definition")
.fields
.clone();
let path = self.types().struct_path(binding);
self.named_shape(path, &fields, patterns, position, guard_box)
}
fn variant_shape(
&mut self,
binding: BindingId,
patterns: &[TypedPattern],
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let BindingKind::Variant(_, VariantInfo { owner, index, .. }) =
self.program.bindings.get(binding).kind()
else {
unreachable!("a typed variant pattern names a variant binding");
};
let definition = self
.program
.enums
.get(owner)
.expect("a reached enum has a definition");
let variant = &definition.variants[index];
let name = variant.name.clone();
let payload = variant.payload.clone();
let template = definition
.rust
.as_ref()
.filter(|rust| rust.template_clause)
.map(|rust| rust.template.clone());
if let Some(template) = template
&& let VariantPayload::Positional(_) = &payload
{
return self.template_shape(&template, patterns, position, guard_box);
}
let bare = definition.rust.as_ref().is_some_and(|rust| rust.bare);
let rust_path = definition.rust.as_ref().map(|rust| rust.path.clone());
let path = Self::variant_path(self.types().struct_path(owner), rust_path, &name, bare);
match &payload {
VariantPayload::None => PatternShape::plain(Code::atom(path)),
VariantPayload::Positional(_) => {
self.positional_shape(path, patterns, position, guard_box)
}
VariantPayload::Named(fields) => {
self.named_shape(path, fields, patterns, position, guard_box)
}
}
}
fn template_shape(
&mut self,
template: &ParsedTemplate,
patterns: &[TypedPattern],
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let mut guard_arguments = Vec::new();
let mut binding_arguments = Vec::new();
let mut destructure_arguments = Vec::new();
let mut guards = Vec::new();
let mut prologue = Vec::new();
for pattern in patterns {
let shape = self.child_shape(pattern, position, guard_box);
guard_arguments.push(shape.guard);
binding_arguments.push(shape.binding);
destructure_arguments.push(shape.destructure);
guards.extend(shape.guards);
prologue.extend(shape.prologue);
}
PatternShape {
guard: self.fill_enum_template(template, guard_arguments),
binding: self.fill_enum_template(template, binding_arguments),
destructure: self.fill_enum_template(template, destructure_arguments),
guards,
prologue,
}
}
fn variant_path(
owner_path: String,
rust_path: Option<std::rc::Rc<str>>,
name: &str,
bare: bool,
) -> String {
if bare {
return NameMangler::variant_name(name);
}
let owner = rust_path.map_or(owner_path, |path| path.to_string());
format!("{owner}::{}", NameMangler::variant_name(name))
}
fn positional_shape(
&mut self,
path: String,
patterns: &[TypedPattern],
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let mut guard_parts = vec![Code::atom(format!("{path}("))];
let mut binding_parts = vec![Code::atom(format!("{path}("))];
let mut destructure_parts = vec![Code::atom(format!("{path}("))];
let mut guards = Vec::new();
let mut prologue = Vec::new();
for (index, pattern) in patterns.iter().enumerate() {
if index > 0 {
guard_parts.push(Code::atom(", "));
binding_parts.push(Code::atom(", "));
destructure_parts.push(Code::atom(", "));
}
let shape = self.child_shape(pattern, position, guard_box);
guard_parts.push(shape.guard);
binding_parts.push(shape.binding);
destructure_parts.push(shape.destructure);
guards.extend(shape.guards);
prologue.extend(shape.prologue);
}
guard_parts.push(Code::atom(")"));
binding_parts.push(Code::atom(")"));
destructure_parts.push(Code::atom(")"));
PatternShape {
guard: Code::joined(guard_parts, Precedence::Atom),
binding: Code::joined(binding_parts, Precedence::Atom),
destructure: Code::joined(destructure_parts, Precedence::Atom),
guards,
prologue,
}
}
fn named_shape(
&mut self,
path: String,
fields: &[(std::rc::Rc<str>, Type)],
patterns: &[TypedPattern],
position: BoxPosition,
guard_box: GuardBox,
) -> PatternShape {
let mut guard_parts = vec![Code::atom(format!("{path} {{ "))];
let mut binding_parts = vec![Code::atom(format!("{path} {{ "))];
let mut destructure_parts = vec![Code::atom(format!("{path} {{ "))];
let mut guards = Vec::new();
let mut prologue = Vec::new();
let mut written = 0;
let mut ignores = false;
for ((label, _), pattern) in fields.iter().zip(patterns) {
if matches!(pattern.kind, TypedPatternKind::Wildcard) {
ignores = true;
continue;
}
if written > 0 {
guard_parts.push(Code::atom(", "));
binding_parts.push(Code::atom(", "));
destructure_parts.push(Code::atom(", "));
}
written += 1;
let field = NameMangler::value(label);
let shape = self.child_shape(pattern, position, guard_box);
Self::push_field(&mut guard_parts, &field, shape.guard);
Self::push_field(&mut binding_parts, &field, shape.binding);
Self::push_field(&mut destructure_parts, &field, shape.destructure);
guards.extend(shape.guards);
prologue.extend(shape.prologue);
}
if ignores {
if written > 0 {
guard_parts.push(Code::atom(", "));
binding_parts.push(Code::atom(", "));
destructure_parts.push(Code::atom(", "));
}
guard_parts.push(Code::atom(".."));
binding_parts.push(Code::atom(".."));
destructure_parts.push(Code::atom(".."));
}
guard_parts.push(Code::atom(" }"));
binding_parts.push(Code::atom(" }"));
destructure_parts.push(Code::atom(" }"));
PatternShape {
guard: Code::joined(guard_parts, Precedence::Atom),
binding: Code::joined(binding_parts, Precedence::Atom),
destructure: Code::joined(destructure_parts, Precedence::Atom),
guards,
prologue,
}
}
fn push_field(parts: &mut Vec<Code>, field: &str, code: Code) {
if code.text() == field {
parts.push(code);
} else {
parts.push(Code::atom(format!("{field}: ")));
parts.push(code);
}
}
fn variant(&mut self, binding: BindingId, arguments: &[TypedExpr], order: &FieldOrder) -> Code {
let BindingKind::Variant(_, VariantInfo { owner, index, .. }) =
self.program.bindings.get(binding).kind()
else {
unreachable!("a typed variant expression names a variant binding");
};
let definition = self
.program
.enums
.get(owner)
.expect("a reached enum has a definition");
let variant = &definition.variants[index];
let name = variant.name.clone();
let payload = variant.payload.clone();
let template = definition
.rust
.as_ref()
.filter(|rust| rust.template_clause)
.map(|rust| rust.template.clone());
if let Some(template) = template
&& let VariantPayload::Positional(types) = &payload
{
let arguments = arguments
.iter()
.zip(types)
.map(|(argument, payload)| self.payload_argument(argument, payload))
.collect();
return self.fill_enum_template(&template, arguments);
}
let bare = definition.rust.as_ref().is_some_and(|rust| rust.bare);
let rust_path = definition.rust.as_ref().map(|rust| rust.path.clone());
let path = Self::variant_path(self.types().struct_path(owner), rust_path, &name, bare);
match &payload {
VariantPayload::None => Code::atom(path),
VariantPayload::Positional(types) => self.positional_variant(path, types, arguments),
VariantPayload::Named(fields) => self.named_variant(path, fields, arguments, order),
}
}
fn fill_enum_template(&mut self, template: &ParsedTemplate, arguments: Vec<Code>) -> Code {
Template::new(template.text())
.inline_with_types(arguments, &[], &[])
.expect("the expansion checked the template")
}
fn positional_variant(
&mut self,
path: String,
payload_types: &[Type],
arguments: &[TypedExpr],
) -> Code {
let mut parts = vec![Code::atom(format!("{path}("))];
for (position, (payload, argument)) in payload_types.iter().zip(arguments).enumerate() {
if position > 0 {
parts.push(Code::atom(", "));
}
parts.push(self.payload_argument(argument, payload));
}
parts.push(Code::atom(")"));
Code::joined(parts, Precedence::Atom)
}
fn named_variant(
&mut self,
path: String,
fields: &[(std::rc::Rc<str>, Type)],
arguments: &[TypedExpr],
order: &FieldOrder,
) -> Code {
if fields.is_empty() {
return Code::atom(path);
}
let mut parts = vec![Code::atom(format!("{path} {{ "))];
for (written, argument) in arguments.iter().enumerate() {
if written > 0 {
parts.push(Code::atom(", "));
}
let (label, payload) = &fields[order.field_of(written)];
let field = NameMangler::value(label);
parts.push(Code::atom(format!("{field}: ")));
parts.push(self.payload_argument(argument, payload));
}
parts.push(Code::atom(" }"));
Code::struct_literal(parts)
}
fn payload_argument(&mut self, argument: &TypedExpr, payload: &Type) -> Code {
let value = self.expr(argument);
if self.program.is_box(payload) && !self.program.is_box(&argument.ty) {
self.box_new(value)
} else {
value
}
}
fn trait_object(&mut self, value: &TypedExpr, trait_binding: BindingId) -> Code {
let box_binding = self
.program
.box_binding
.expect("a trait object names the prelude's box type");
let shown = {
let types = self.types();
types.show(
&Type::Named(box_binding, vec![Type::TraitObject(trait_binding)]),
TypePosition::Value,
)
};
let value = self.expr_in(value, TypeContext::Known);
Code::joined_outside_delimiters(
vec![value, Code::atom(format!(" as {shown}"))],
Precedence::Operator,
)
}
fn box_new(&self, argument: Code) -> Code {
let binding = self
.program
.box_binding
.expect("an inferred box names the prelude's box type");
let rust_type = self
.program
.rust_types
.get(binding)
.expect("the prelude's box type is a Rust type");
Code::joined(
vec![
Code::atom(format!("{}::new(", rust_type.leading_name())),
argument,
Code::atom(")"),
],
Precedence::Atom,
)
}
fn structure(&mut self, structure: &TypedStructExpr, ty: &Type) -> Code {
match structure {
TypedStructExpr::New(binding, fields) => self.construction(*binding, fields),
TypedStructExpr::Update(binding, base, fields) => self.update(*binding, base, fields),
TypedStructExpr::Field(value, label) => self.field_access(value, label, ty),
}
}
fn assoc_block(&mut self, value: &TypedExpr, keys: &[TypedExpr], item: &TypedExpr) -> Code {
self.locals.enter();
let name = self.assoc_name(value);
let inner = self.assoc_assignment(&name, value, keys, item);
self.locals.leave();
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(inner),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn assoc_name(&mut self, value: &TypedExpr) -> String {
match &value.kind {
TypedExprKind::Leaf(TypedLeaf::Local(binding)) => self
.locals
.get(*binding)
.map(str::to_string)
.unwrap_or_else(|| self.locals.temporary("value")),
_ => self.locals.temporary("value"),
}
}
fn assoc_assignment(
&mut self,
name: &str,
value: &TypedExpr,
keys: &[TypedExpr],
item: &TypedExpr,
) -> Vec<Fragment> {
let value_code = self.expr(value);
let item_code = self.expr(item);
let mut inner = vec![
Fragment::Newline,
Fragment::Text(format!("let mut {name} = ")),
];
inner.extend(value_code.fragments);
inner.push(Fragment::Text(";".to_string()));
inner.push(Fragment::Newline);
if let Some(collection) = Collection::of(&value.ty, &self.program.rust_types)
&& collection.is_map()
{
let key_code = self.expr(&keys[0]);
inner.push(Fragment::Text(format!("{name}.insert(")));
inner.extend(key_code.fragments);
inner.push(Fragment::Text(", ".to_string()));
inner.extend(item_code.fragments);
inner.push(Fragment::Text(");".to_string()));
inner.push(Fragment::Newline);
inner.push(Fragment::Text(name.to_string()));
return inner;
}
let key_codes: Vec<Code> = keys.iter().map(|key| self.expr(key)).collect();
inner.push(Fragment::Text(name.to_string()));
for key in key_codes {
inner.push(Fragment::Text("[".to_string()));
inner.extend(key.fragments);
inner.push(Fragment::Text("]".to_string()));
}
inner.push(Fragment::Text(" = ".to_string()));
inner.extend(item_code.fragments);
inner.push(Fragment::Text(";".to_string()));
inner.push(Fragment::Newline);
inner.push(Fragment::Text(name.to_string()));
inner
}
fn statement(&mut self, item: &TypedBodyItem, is_tail: bool) -> Code {
match item {
TypedBodyItem::Let(binder, value) => self.let_statement(binder, value),
TypedBodyItem::Expr(expr) if is_tail => self.expr(expr),
TypedBodyItem::Expr(expr) => {
let code = self.expr(expr);
let ends_with_block =
code.precedence == Precedence::Block && Type::unit() == expr.ty;
let terminator = if ends_with_block { "" } else { ";" };
Code::joined(vec![code, Code::atom(terminator)], Precedence::Block)
}
}
}
fn let_statement(&mut self, binder: &TypedBinder, value: &TypedExpr) -> Code {
let binder = &Self::with_written_type(binder, value);
let value = self.let_value(binder, value);
let name = self.locals.bind(
binder.binding,
self.program.bindings.get(binder.binding).name(),
);
let annotation = if LetAnnotation::new(self.program).is_emitted(binder) {
format!(": {}", self.types().show(&binder.ty, TypePosition::Value))
} else {
String::new()
};
Code::joined(
vec![
Code::atom(format!(
"{} {name}{annotation} = ",
self.declaration(binder)
)),
value,
Code::atom(";"),
],
Precedence::Block,
)
}
fn with_written_type(binder: &TypedBinder, value: &TypedExpr) -> TypedBinder {
let written = value.needs_written_type() && !binder.ty.holds_function();
TypedBinder {
annotated: binder.annotated || written,
..binder.clone()
}
}
fn let_value(&mut self, binder: &TypedBinder, value: &TypedExpr) -> Code {
if let Some(inner) = value.let_block_value() {
return self.let_value(binder, inner);
}
if !binder.annotated {
return self.unannotated_let_value(binder, value);
}
match &value.kind {
TypedExprKind::TraitObject(boxed, _) => self.expr(boxed),
_ => self.expr(value),
}
}
fn unannotated_let_value(&mut self, binder: &TypedBinder, value: &TypedExpr) -> Code {
let TypedExprKind::Fn(lambda) = &value.kind else {
return self.expr(value);
};
let result_parameters = self.result_parameters_of(lambda, &value.ty);
if !self.local_closures.borrows_arguments_of(binder.binding) && result_parameters.is_empty()
{
return self.expr(value);
}
self.result_parameters.extend(result_parameters);
self.closure_binding(lambda, &value.ty, Self::bind_closure_parameter)
.spanned(value.span)
}
fn result_parameters_of(&self, lambda: &TypedLambda, ty: &Type) -> FxHashSet<BindingId> {
match ty {
Type::Function(_, _, result) if self.program.function_type_returns_borrow(result) => {
ResultBorrows::new(self.program, self.plan)
.analyse(lambda)
.flows
}
_ => FxHashSet::default(),
}
}
fn declaration(&self, binder: &TypedBinder) -> &'static str {
if self.mutable_locals.contains(&binder.binding) {
"let mut"
} else {
"let"
}
}
fn literal_context(&self, literal: &TypedExpr, context: TypeContext) -> TypeContext {
if self.open_literals.contains(literal) {
TypeContext::Unknown
} else {
context
}
}
fn literal(&self, literal: &Literal, ty: &Type, context: TypeContext) -> Code {
match (literal, ty) {
(Literal::String(text), _) => Code::atom(format!("{text:?}")),
(Literal::Bool(value), _) => Code::atom(value.to_string()),
(Literal::Number(number), Type::Generic(_)) => self.converted_literal(number, ty),
(Literal::Number(number), _) => Self::number(number, ty, context),
}
}
fn number(number: &Number, ty: &Type, context: TypeContext) -> Code {
let text = Self::number_text(number, ty, context);
let precedence = if text.starts_with('-') {
Precedence::Prefix
} else {
Precedence::Atom
};
Code::with(text, precedence)
}
fn number_text(number: &Number, ty: &Type, context: TypeContext) -> String {
let digits = Self::number_digits(number, ty);
match (ty, context) {
(Type::Primitive(primitive), TypeContext::Unknown) => {
format!("{digits}_{}", primitive.name())
}
_ => digits,
}
}
fn number_digits(number: &Number, ty: &Type) -> String {
let digits = number.text().trim_start_matches('+').to_string();
match (number.value(), ty) {
(NumberValue::Integer(_), Type::Primitive(primitive)) if primitive.is_float() => {
format!("{digits}.0")
}
_ => digits,
}
}
fn converted_literal(&self, number: &Number, ty: &Type) -> Code {
let conversion = match number.value() {
NumberValue::Integer(_) => Self::INTEGER_CONVERSION,
NumberValue::Float(_) => Self::FLOAT_CONVERSION,
};
let parameter = self.types().show(ty, TypePosition::Value);
let text = number.text().trim_start_matches('+');
Code::atom(format!("{parameter}::{conversion}({text})"))
}
fn global(&mut self, binding: BindingId, ty: &Type, span: Span) -> Code {
if let Some(info) = self.program.trait_methods.get(&binding) {
let name = NameMangler::value(self.program.bindings.get(binding).name());
let path = if self.plan.uses_trait(self.module, info.trait_binding) {
info.rust_name.to_string()
} else {
self.plan.trait_path(info.trait_binding, self.module)
};
return Code::atom(format!("{path}::{name}"));
}
if let Some(template) = self.definition_template(binding, ty) {
let types = self.definition_type_arguments(binding, ty);
return self.rust_value(&template, ty, types, span);
}
let path = self.plan.path_to(binding, self.module);
match self.plan.item(binding).map(|item| item.kind) {
Some(ItemKind::Static) => Code::with(format!("*{path}"), Precedence::Prefix),
_ => Code::atom(path),
}
}
fn rust_value(
&mut self,
template: &Rc<RustTemplate>,
ty: &Type,
types: Vec<(String, Code)>,
span: Span,
) -> Code {
let parsed = Template::new(&template.template);
if matches!(ty, Type::Function(..)) && parsed.is_path() {
return Code::atom(template.template.to_string());
}
self.locals.enter();
let parsed = self.with_fresh_template_binders(&parsed);
let code = match ty {
Type::Function(_, parameters, _) => self.eta_expanded(&parsed, parameters, types, span),
_ => self.inline(&parsed, Vec::new(), types, span),
};
self.locals.leave();
code
}
fn eta_expanded(
&mut self,
template: &Template,
parameters: &[Type],
types: Vec<(String, Code)>,
span: Span,
) -> Code {
let names: Vec<String> = (0..parameters.len())
.map(|index| format!("{}{index}", Self::ETA_PARAMETER))
.collect();
let parameters: Vec<String> = names
.iter()
.zip(parameters)
.map(|(name, ty)| format!("{name}: {}", self.types().show(ty, TypePosition::Value)))
.collect();
let arguments = names.iter().map(|name| Code::atom(name.clone())).collect();
let body = self.inline(template, arguments, types, span);
Code::joined(
vec![Code::atom(format!("|{}| ", parameters.join(", "))), body],
Precedence::Operator,
)
}
fn inline(
&mut self,
template: &Template,
arguments: Vec<Code>,
types: Vec<(String, Code)>,
span: Span,
) -> Code {
match template.inline_with_types(arguments, &types, &[]) {
Ok(code) => code,
Err(error) => self.template_problem(error, span),
}
}
fn inline_with_places(
&mut self,
template: &Template,
arguments: Vec<Code>,
types: Vec<(String, Code)>,
places: &[bool],
span: Span,
) -> Code {
match template.inline_with_types(arguments, &types, places) {
Ok(code) => code,
Err(error) => self.template_problem(error, span),
}
}
fn template_problem(&mut self, error: TemplateError, span: Span) -> Code {
let message = match error {
TemplateError::MissingArgument(index) => {
format!("`{{{index}}}` has no matching argument")
}
TemplateError::MissingTypeArgument(name) => {
format!("`{{{name}}}` has no matching type argument")
}
TemplateError::UnresolvedBinder(name) => {
format!("`{{!{name}}}` has no fresh name")
}
};
self.report_bad_template(&message, span)
}
fn report_inline_clauses(&mut self, span: Span) -> Code {
self.report_bad_template(
"a template with clauses needs a `def`, whose calls give the type that chooses a clause",
span,
)
}
fn report_bad_template(&mut self, message: &str, span: Span) -> Code {
self.problems
.push(EmitProblems::bad_template(message, span));
Code::atom("unreachable!()")
}
fn call(&mut self, callee: &TypedExpr, arguments: &[TypedArgument], span: Span) -> Code {
if let TypedExprKind::Fn(lambda) = &callee.kind
&& !Self::contains_try(&lambda.body)
{
return self.inline_call(lambda, arguments);
}
if let TypedExprKind::Leaf(TypedLeaf::Global(binding)) = &callee.kind
&& self.program.trait_methods.contains_key(binding)
{
return self.trait_method_call(*binding, arguments);
}
if let Some(template) = self.called_template(callee) {
return self.rust_call(callee, &template, arguments, span);
}
if let Some(last) = self
.last_argument_mentioning_mutable_callee(callee, arguments)
.max(Self::last_conflicting_argument(arguments))
{
return self.call_with_temporaries(callee, arguments, last);
}
let arguments = arguments
.iter()
.enumerate()
.map(|(position, argument)| self.call_argument(callee, position, argument))
.collect();
self.call_code(callee, arguments)
}
fn rust_call(
&mut self,
callee: &TypedExpr,
template: &RustTemplate,
arguments: &[TypedArgument],
span: Span,
) -> Code {
if Self::is_inline_with_clauses(callee) {
return self.report_inline_clauses(callee.span);
}
let types = self.callee_type_arguments(callee);
self.template_call(template, arguments, types, span)
}
fn is_inline_with_clauses(callee: &TypedExpr) -> bool {
matches!(&callee.kind, TypedExprKind::Leaf(TypedLeaf::Rust(template)) if !template.clauses.is_empty())
}
fn call_argument(
&mut self,
callee: &TypedExpr,
position: usize,
argument: &TypedArgument,
) -> Code {
let value = self.argument(argument, TypeContext::Known, ArgumentRole::Ordinary);
match self
.local_closures
.argument_borrow(callee, position, &argument.value)
{
Some(borrow) => Code::joined_outside_delimiters(
vec![Code::atom(borrow.prefix()), value],
Precedence::Prefix,
),
None => value,
}
}
fn trait_method_call(&mut self, binding: BindingId, arguments: &[TypedArgument]) -> Code {
let info = self
.program
.trait_methods
.get(&binding)
.expect("a trait method call names a method");
let name = NameMangler::value(self.program.bindings.get(binding).name());
if info.receiver.is_some() {
let (receiver, rest) = arguments
.split_first()
.expect("a receiver method has a receiver argument");
let receiver = self.argument(receiver, TypeContext::Unknown, ArgumentRole::Receiver);
let receiver = if receiver.is_atomic() {
receiver
} else {
receiver.parenthesized()
};
let rest: Vec<Code> = rest
.iter()
.map(|argument| self.argument(argument, TypeContext::Known, ArgumentRole::Ordinary))
.collect();
let mut call = vec![Code::atom(format!(".{name}("))];
call.extend(Self::separated(rest));
call.push(Code::atom(")"));
return Code::joined_outside_delimiters(
vec![receiver, Code::joined(call, Precedence::Atom)],
Precedence::Atom,
);
}
let arguments: Vec<Code> = arguments
.iter()
.map(|argument| self.argument(argument, TypeContext::Known, ArgumentRole::Ordinary))
.collect();
let path = self.plan.trait_path(info.trait_binding, self.module);
let mut parts = vec![Code::atom(format!("{path}::{name}("))];
parts.extend(Self::separated(arguments));
parts.push(Code::atom(")"));
Code::joined(parts, Precedence::Atom)
}
fn call_code(&mut self, callee: &TypedExpr, arguments: Vec<Code>) -> Code {
let reads_field = matches!(
callee.kind,
TypedExprKind::Struct(TypedStructExpr::Field(..))
);
let callee = self.expr(callee);
let callee = if callee.is_atomic() && !reads_field {
callee
} else {
callee.parenthesized()
};
let mut parts = vec![callee, Code::atom("(")];
parts.extend(Self::separated(arguments));
parts.push(Code::atom(")"));
Code::joined(parts, Precedence::Atom)
}
fn call_with_temporaries(
&mut self,
callee: &TypedExpr,
arguments: &[TypedArgument],
last: usize,
) -> Code {
self.locals.enter();
let mut statements = Vec::new();
let mut codes = Vec::new();
for (position, argument) in arguments.iter().enumerate() {
let moves = argument.borrow != Borrow::Mutable && position <= last;
if !moves {
codes.push(self.call_argument(callee, position, argument));
continue;
}
let temporary = self.locals.temporary(&format!("call_argument_{position}"));
let value = self.call_argument(callee, position, argument);
statements.push(Fragment::Newline);
statements.push(Fragment::Text(format!("let {temporary} = ")));
statements.extend(value.fragments);
statements.push(Fragment::Text(";".to_string()));
codes.push(Code::atom(temporary));
}
let call = self.call_code(callee, codes);
self.locals.leave();
statements.push(Fragment::Newline);
statements.extend(call.fragments);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(statements),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
pub(super) fn contains_try(block: &TypedBlock) -> bool {
block.expressions().any(Self::expression_contains_try)
}
fn expression_contains_try(expr: &TypedExpr) -> bool {
match &expr.kind {
TypedExprKind::Try { .. } => true,
TypedExprKind::Fn(_) => false,
_ => expr
.children()
.into_iter()
.any(Self::expression_contains_try),
}
}
fn last_conflicting_argument(arguments: &[TypedArgument]) -> Option<usize> {
let borrowed: Vec<BindingId> = arguments
.iter()
.filter(|argument| argument.borrow == Borrow::Mutable)
.filter_map(|argument| argument.value.place_root())
.collect();
arguments.iter().rposition(|argument| {
argument.borrow != Borrow::Mutable
&& borrowed
.iter()
.any(|binding| argument.value.mentions_local(*binding))
})
}
fn last_argument_mentioning_mutable_callee(
&self,
callee: &TypedExpr,
arguments: &[TypedArgument],
) -> Option<usize> {
let TypedExprKind::Leaf(TypedLeaf::Local(binding)) = callee.kind else {
return None;
};
if !self.mutable_locals.contains(&binding) {
return None;
}
arguments
.iter()
.rposition(|argument| argument.value.mentions_local(binding))
}
fn called_template(&self, callee: &TypedExpr) -> Option<Rc<RustTemplate>> {
match &callee.kind {
TypedExprKind::Leaf(TypedLeaf::Rust(template)) => Some(template.clone()),
TypedExprKind::Leaf(TypedLeaf::Global(binding)) => {
self.definition_template(*binding, &callee.ty)
}
_ => None,
}
}
fn definition_template(
&self,
binding: BindingId,
instantiated: &Type,
) -> Option<Rc<RustTemplate>> {
let TypedExprKind::Leaf(TypedLeaf::Rust(template)) =
&self.plan.definition(binding)?.value.kind
else {
return None;
};
let chosen = self
.definition_type_bindings(binding, instantiated)
.first()
.and_then(|(_, parameter)| self.chosen_clause(binding, parameter));
Some(match chosen {
Some(clause) => Rc::new(RustTemplate {
template: Rc::clone(clause),
..RustTemplate::clone(template)
}),
None => Rc::clone(template),
})
}
fn chosen_clause(&self, binding: BindingId, parameter: &Type) -> Option<&Rc<str>> {
let implements = |bound: &Bound| {
self.program
.traits
.satisfies(bound, parameter, &|generic, required| {
self.generic_declares(generic, required)
})
};
self.program
.template_clauses
.chosen(binding, parameter, &implements)
}
fn generic_declares(&self, generic: GenericId, required: &Bound) -> bool {
self.program.generics.get(&generic).is_some_and(|param| {
param
.bounds
.iter()
.any(|declared| self.program.traits.implies(declared, required, generic))
})
}
fn callee_type_arguments(&self, callee: &TypedExpr) -> Vec<(String, Code)> {
match &callee.kind {
TypedExprKind::Leaf(TypedLeaf::Global(binding)) => {
self.definition_type_arguments(*binding, &callee.ty)
}
_ => Vec::new(),
}
}
fn definition_type_arguments(
&self,
binding: BindingId,
instantiated: &Type,
) -> Vec<(String, Code)> {
self.definition_type_bindings(binding, instantiated)
.into_iter()
.map(|(name, ty)| {
(
name,
Code::atom(self.types().show(&ty, TypePosition::Value)),
)
})
.collect()
}
fn definition_type_bindings(
&self,
binding: BindingId,
instantiated: &Type,
) -> Vec<(String, Type)> {
let Some(definition) = self.plan.definition(binding) else {
return Vec::new();
};
let mut bindings = FxHashMap::default();
Self::collect_generic_bindings(&definition.scheme.body, instantiated, &mut bindings);
definition
.scheme
.params
.iter()
.filter_map(|param| Some((param.name.to_string(), bindings.remove(¶m.id)?)))
.collect()
}
fn collect_generic_bindings(
pattern: &Type,
instantiated: &Type,
bindings: &mut FxHashMap<GenericId, Type>,
) {
match (pattern, instantiated) {
(Type::Generic(generic), ty) => {
bindings.entry(*generic).or_insert_with(|| ty.clone());
}
(Type::Named(owner, arguments), Type::Named(other, other_arguments))
if owner == other =>
{
for (argument, other_argument) in arguments.iter().zip(other_arguments) {
Self::collect_generic_bindings(argument, other_argument, bindings);
}
}
(Type::Function(_, parameters, result), Type::Function(_, other, other_result)) => {
for (parameter, other_parameter) in parameters.iter().zip(other) {
Self::collect_generic_bindings(parameter, other_parameter, bindings);
}
Self::collect_generic_bindings(result, other_result, bindings);
}
(Type::Ref(_, inner), Type::Ref(_, other))
| (Type::Mut(_, inner), Type::Mut(_, other)) => {
Self::collect_generic_bindings(inner, other, bindings);
}
_ => {}
}
}
fn template_call(
&mut self,
template: &RustTemplate,
arguments: &[TypedArgument],
types: Vec<(String, Code)>,
span: Span,
) -> Code {
if template.template.as_ref() == Self::MAP_UPDATE_TEMPLATE {
return self.map_update_call(arguments);
}
let mut parsed = Template::new(&template.template);
self.locals.enter();
parsed = self.with_fresh_template_binders(&parsed);
let context = Self::argument_context(arguments);
let roles: Vec<ArgumentRole> = arguments
.iter()
.enumerate()
.map(|(position, argument)| Self::argument_role(&parsed, position, argument))
.collect();
let places: Vec<bool> = roles
.iter()
.map(|role| *role == ArgumentRole::MutPlace)
.collect();
let compared = self.compares_borrowed_operands(&parsed, &roles);
let mut codes: Vec<Code> = arguments
.iter()
.zip(&roles)
.map(|(argument, role)| {
let levels = if compared {
self.depth_above_shallowest((argument, *role), arguments, &roles)
} else {
0
};
self.argument_at_depth(argument, context, *role, levels)
})
.collect();
for (position, argument) in arguments.iter().enumerate() {
let Some(call_arguments) = parsed.called_arguments(position) else {
continue;
};
let TypedExprKind::Fn(lambda) = &argument.value.kind else {
continue;
};
if !Self::inlines_call(lambda, &call_arguments) {
continue;
}
let Some(without) = parsed.without_call(position) else {
continue;
};
codes[position] = self.inlined_closure_body(lambda, &call_arguments);
parsed = without;
}
let code = self.inline_with_places(&parsed, codes, types, &places, span);
self.locals.leave();
code
}
fn compares_borrowed_operands(&self, template: &Template, roles: &[ArgumentRole]) -> bool {
roles.len() >= Self::COMPARED_OPERANDS
&& roles.contains(&ArgumentRole::Borrowed)
&& template.is_binary_operation()
}
fn depth_above_shallowest(
&self,
(operand, role): (&TypedArgument, ArgumentRole),
arguments: &[TypedArgument],
roles: &[ArgumentRole],
) -> usize {
let referent = operand.value.ty.innermost_referent();
let depth = self.emitted_depth(operand, role);
arguments
.iter()
.zip(roles)
.filter_map(|(other, other_role)| {
let allowance =
Self::depth_allowance(referent, other.value.ty.innermost_referent())?;
Some(depth.saturating_sub(self.emitted_depth(other, *other_role) + allowance))
})
.max()
.unwrap_or(0)
}
fn depth_allowance(operand: &Type, other: &Type) -> Option<usize> {
if TypeChecker::views_string(operand, other) {
Some(Self::VIEW_DEPTH_ALLOWANCE)
} else if operand == other {
Some(0)
} else {
None
}
}
fn emitted_depth(&self, argument: &TypedArgument, role: ArgumentRole) -> usize {
let value = &argument.value;
match argument.borrow {
Borrow::Deref(levels) | Borrow::Collapse(levels) => {
value.ty.reference_depth().saturating_sub(levels)
}
_ if Self::writes_borrow_prefix(argument, role) => value.ty.reference_depth() + 1,
_ if role == ArgumentRole::Borrowed => self
.place_depth(value)
.unwrap_or_else(|| value.ty.reference_depth()),
_ => value.ty.reference_depth(),
}
}
fn place_depth(&self, value: &TypedExpr) -> Option<usize> {
match &value.kind {
TypedExprKind::Struct(TypedStructExpr::Field(owner, label)) => {
self.declared_field_depth(owner, &label.name)
}
_ if self.is_borrowed_index_call(value) => {
Some(value.ty.reference_depth().saturating_sub(1))
}
_ => None,
}
}
fn declared_field_depth(&self, owner: &TypedExpr, field: &str) -> Option<usize> {
let mut owner_type = &owner.ty;
while let Type::Ref(_, inner) | Type::Mut(_, inner) = owner_type {
owner_type = inner;
}
let Type::Named(binding, arguments) = owner_type else {
return None;
};
self.program
.structs
.get(*binding)?
.instantiated_field_depth(field, arguments)
}
fn argument_at_depth(
&mut self,
argument: &TypedArgument,
context: TypeContext,
role: ArgumentRole,
levels: usize,
) -> Code {
let span = argument.value.span;
if levels == 0 {
return self.argument(argument, context, role);
}
match self.without_own_borrow(argument, context, role) {
Some(code) => Self::dereferenced(code, levels - 1, span),
None => Self::dereferenced(self.argument(argument, context, role), levels, span),
}
}
fn without_own_borrow(
&mut self,
argument: &TypedArgument,
context: TypeContext,
role: ArgumentRole,
) -> Option<Code> {
if Self::writes_borrow_prefix(argument, role) {
let value = self.argument_value(&argument.value, context, role);
return Some(value.spanned(argument.value.span));
}
match &argument.value.kind {
TypedExprKind::Borrow(ReferenceKind::Shared, place)
if !Self::passes_through_reference(self.program, ReferenceKind::Shared, place) =>
{
Some(self.borrowed_operand(place, context))
}
_ if argument.borrow == Borrow::None
&& self.is_borrowed_index_call(&argument.value) =>
{
Some(self.place(&argument.value, context))
}
_ => None,
}
}
fn writes_borrow_prefix(argument: &TypedArgument, role: ArgumentRole) -> bool {
matches!(argument.borrow, Borrow::Shared | Borrow::Mutable)
&& !Self::borrowed_already(argument, role)
}
fn dereferenced(code: Code, levels: usize, span: Span) -> Code {
if levels == 0 {
return code;
}
Self::prefixed(&"*".repeat(levels), code).spanned(span)
}
fn with_fresh_template_binders(&mut self, template: &Template) -> Template {
let renames: Vec<(String, String)> = template
.binder_names()
.into_iter()
.map(|name| (name.to_string(), self.locals.temporary(name)))
.collect();
template.with_binders(&renames)
}
fn inlines_call(lambda: &TypedLambda, call_arguments: &[&str]) -> bool {
lambda.kind == ClosureKind::Fn
&& lambda.moved_capture.is_none()
&& lambda.wraps.is_none()
&& !Self::contains_try(&lambda.body)
&& matches!(lambda.body.items.as_slice(), [TypedBodyItem::Expr(_)])
&& lambda.params.len() == call_arguments.len()
}
fn inlined_closure_body(&mut self, lambda: &TypedLambda, call_arguments: &[&str]) -> Code {
let previous = std::mem::replace(&mut self.tail_loop, TailLoop::of_closure(lambda));
self.locals.enter();
for (param, name) in lambda.params.iter().zip(call_arguments) {
self.locals.bind_exact(param.binding, name);
}
let body = self.closure_body(lambda, false);
self.locals.leave();
self.tail_loop = previous;
body
}
fn map_update_call(&mut self, arguments: &[TypedArgument]) -> Code {
self.locals.enter();
let map_name = self.map_update_base(&arguments[0].value);
let map = self.expr(&arguments[0].value);
let key = self.update_argument(&arguments[1].value, "key");
let function = self.update_argument(&arguments[2].value, "function");
let default = self.update_argument(&arguments[3].value, "default");
let old = self.locals.temporary("old");
let mut inner = vec![
Fragment::Newline,
Fragment::Text(format!("let mut {map_name} = ")),
];
inner.extend(map.fragments);
inner.push(Fragment::Text(";".to_string()));
for argument in [&key, &function, &default] {
if let Some(name) = &argument.name {
inner.push(Fragment::Newline);
inner.push(Fragment::Text(format!("let {name} = ")));
inner.extend(argument.code.fragments.clone());
inner.push(Fragment::Text(";".to_string()));
}
}
let key_use = key.usage();
let function_use = function.usage();
let default_use = default.usage();
inner.push(Fragment::Newline);
inner.push(Fragment::Text(format!("let {old} = {map_name}.remove(&")));
inner.extend(key_use.fragments.clone());
inner.push(Fragment::Text(").unwrap_or(".to_string()));
inner.extend(default_use.fragments);
inner.push(Fragment::Text(");".to_string()));
inner.push(Fragment::Newline);
inner.push(Fragment::Text(format!("{map_name}.insert(")));
inner.extend(key_use.fragments);
inner.push(Fragment::Text(", ".to_string()));
inner.extend(function_use.fragments);
inner.push(Fragment::Text(format!("({old}));")));
inner.push(Fragment::Newline);
inner.push(Fragment::Text(map_name));
self.locals.leave();
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(inner),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn map_update_base(&mut self, value: &TypedExpr) -> String {
match &value.kind {
TypedExprKind::Leaf(TypedLeaf::Local(binding)) => self
.locals
.get(*binding)
.map(str::to_string)
.unwrap_or_else(|| self.locals.temporary("map")),
_ => self.locals.temporary("map"),
}
}
fn update_argument(&mut self, value: &TypedExpr, role: &str) -> UpdateArgument {
let code = self.expr(value);
if matches!(
value.kind,
TypedExprKind::Leaf(TypedLeaf::Literal(_)) | TypedExprKind::Leaf(TypedLeaf::Local(_))
) {
UpdateArgument { name: None, code }
} else {
let name = self.locals.temporary(role);
UpdateArgument {
name: Some(name),
code,
}
}
}
fn argument_context(arguments: &[TypedArgument]) -> TypeContext {
let all_literals = arguments.iter().all(|argument| {
matches!(
argument.value.kind,
TypedExprKind::Leaf(TypedLeaf::Literal(_))
)
});
if all_literals {
TypeContext::Unknown
} else {
TypeContext::Known
}
}
fn argument_role(parsed: &Template, position: usize, argument: &TypedArgument) -> ArgumentRole {
if position == 0 && parsed.receives(position) {
ArgumentRole::Receiver
} else if parsed.indexes(position) {
ArgumentRole::IndexReceiver
} else if Self::is_mut_place(parsed, position, argument) {
ArgumentRole::MutPlace
} else if parsed.borrows(position) {
ArgumentRole::Borrowed
} else {
ArgumentRole::Ordinary
}
}
fn is_mut_place(parsed: &Template, position: usize, argument: &TypedArgument) -> bool {
parsed.dereferences(position)
&& (Self::written_mut_place(&argument.value) || Self::borrows_owned_place(argument))
}
fn borrows_owned_place(argument: &TypedArgument) -> bool {
argument.borrow == Borrow::Mutable
&& argument.value.place_root().is_some()
&& !matches!(argument.value.ty, Type::Mut(..))
}
fn written_mut_place(value: &TypedExpr) -> bool {
matches!(
&value.kind,
TypedExprKind::Borrow(ReferenceKind::Mutable, place)
if place.is_place() && !place.ty.is_reference()
)
}
fn argument(
&mut self,
argument: &TypedArgument,
context: TypeContext,
role: ArgumentRole,
) -> Code {
if let Some(value) = self.adjusted_argument(argument, context) {
return value;
}
let value = match (&argument.value.kind, role) {
(TypedExprKind::Borrow(_, place), ArgumentRole::Receiver) => self.place(place, context),
_ => self.argument_value(&argument.value, context, role),
}
.spanned(argument.value.span);
let prefix = match role {
ArgumentRole::MutPlace => None,
_ => Self::borrow_prefix(argument, role),
};
match prefix {
Some(prefix) => Self::prefixed(&prefix, value).spanned(argument.value.span),
None => value,
}
}
fn adjusted_argument(
&mut self,
argument: &TypedArgument,
context: TypeContext,
) -> Option<Code> {
self.borrowed_index_argument(argument, context)
.or_else(|| self.argument_under_written_borrows(argument, context))
.or_else(|| self.dereferenced_field(argument))
}
fn dereferenced_field(&mut self, argument: &TypedArgument) -> Option<Code> {
let Borrow::Deref(levels) = argument.borrow else {
return None;
};
let TypedExprKind::Struct(TypedStructExpr::Field(owner, label)) = &argument.value.kind
else {
return None;
};
let place_depth = self.declared_field_depth(owner, &label.name)?;
let own_reference = argument
.value
.ty
.reference_depth()
.saturating_sub(place_depth);
let span = argument.value.span;
let place = self.field_place(owner, label).spanned(span);
Some(Self::dereferenced(
place,
levels.saturating_sub(own_reference),
span,
))
}
fn argument_under_written_borrows(
&mut self,
argument: &TypedArgument,
context: TypeContext,
) -> Option<Code> {
let value = &argument.value;
let code = match argument.borrow {
Borrow::Deref(levels) => {
self.place(Self::under_written_borrows(value, levels)?, context)
}
Borrow::Collapse(levels) => {
self.expr_in(Self::under_written_borrows(value, levels)?, context)
}
_ => return None,
};
Some(code.spanned(value.span))
}
fn under_written_borrows(value: &TypedExpr, levels: usize) -> Option<&TypedExpr> {
(0..levels).try_fold(
Self::without_dereferenced_borrows(value),
|value, _| match &value.kind {
TypedExprKind::Borrow(_, place) => Some(place.as_ref()),
_ => None,
},
)
}
fn without_dereferenced_borrows(value: &TypedExpr) -> &TypedExpr {
match &value.kind {
TypedExprKind::Deref(inner) => match &inner.kind {
TypedExprKind::Borrow(_, place) => Self::without_dereferenced_borrows(place),
_ => value,
},
_ => value,
}
}
fn borrowed_index_argument(
&mut self,
argument: &TypedArgument,
context: TypeContext,
) -> Option<Code> {
let levels = match argument.borrow {
Borrow::Deref(levels) => levels,
Borrow::Collapse(1) => 1,
_ => return None,
};
if !self.is_borrowed_index_call(&argument.value) {
return None;
}
let span = argument.value.span;
let element = self.place(&argument.value, context).spanned(span);
Some(Self::prefixed(&"*".repeat(levels - 1), element).spanned(span))
}
fn argument_value(
&mut self,
value: &TypedExpr,
context: TypeContext,
role: ArgumentRole,
) -> Code {
match (&value.kind, role) {
(_, ArgumentRole::Ordinary) => self.expr_in(value, context),
(TypedExprKind::Struct(TypedStructExpr::Field(owner, label)), _) => {
self.field_place(owner, label)
}
(_, ArgumentRole::IndexReceiver) => self.place(value, context),
(_, ArgumentRole::Borrowed) if self.is_borrowed_index_call(value) => {
self.place(value, context)
}
(TypedExprKind::Borrow(ReferenceKind::Mutable, place), ArgumentRole::MutPlace) => {
self.place(place, TypeContext::Known)
}
(TypedExprKind::Leaf(TypedLeaf::Global(binding)), ArgumentRole::Receiver)
if self.is_static(*binding) =>
{
Code::atom(self.plan.path_to(*binding, self.module))
}
_ => self.expr_in(value, context),
}
}
fn is_borrowed_index_call(&self, value: &TypedExpr) -> bool {
matches!(&value.kind, TypedExprKind::Call(callee, _) if self.borrowed_index_template(callee).is_some())
}
fn is_static(&self, binding: BindingId) -> bool {
self.plan.item(binding).map(|item| item.kind) == Some(ItemKind::Static)
}
fn borrow_prefix(argument: &TypedArgument, role: ArgumentRole) -> Option<String> {
let borrowed_already = Self::borrowed_already(argument, role);
match argument.borrow {
Borrow::Shared if !borrowed_already => Some("&".to_string()),
Borrow::Mutable if !borrowed_already => Some("&mut ".to_string()),
Borrow::Deref(levels) => Some("*".repeat(levels)),
Borrow::Reborrow => None,
Borrow::Pending(_) => {
unreachable!("the type checker decides every pending borrow")
}
_ => None,
}
}
fn borrowed_already(argument: &TypedArgument, role: ArgumentRole) -> bool {
argument.value.ty.is_reference() || role.borrows_by_itself(argument.borrow)
}
fn prefixed(prefix: &str, value: Code) -> Code {
let value = if matches!(value.precedence, Precedence::Operator | Precedence::Block) {
value.parenthesized()
} else {
value
};
Code::joined_outside_delimiters(vec![Code::atom(prefix), value], Precedence::Prefix)
}
fn separated(arguments: Vec<Code>) -> Vec<Code> {
let mut parts = Vec::new();
for (position, argument) in arguments.into_iter().enumerate() {
if position > 0 {
parts.push(Code::atom(", "));
}
parts.push(argument);
}
parts
}
fn closure(&mut self, lambda: &TypedLambda, ty: &Type) -> Code {
self.closure_binding(lambda, ty, |emitter, param| {
emitter.bind_parameter(param, TypePosition::Value)
})
}
fn closure_binding(
&mut self,
lambda: &TypedLambda,
ty: &Type,
bind: fn(&mut Self, &TypedBinder) -> String,
) -> Code {
let annotated = Self::contains_try(&lambda.body);
let previous = std::mem::replace(&mut self.tail_loop, TailLoop::of_closure(lambda));
self.locals.enter();
let inferred = lambda
.params
.iter()
.any(|param| self.is_boxed_object_reference(¶m.ty));
let params: Vec<String> = lambda
.params
.iter()
.map(|param| {
if inferred {
self.locals.bind(
param.binding,
self.program.bindings.get(param.binding).name(),
)
} else {
bind(self, param)
}
})
.collect();
let body = self.closure_body(lambda, annotated);
self.locals.leave();
self.tail_loop = previous;
let head = match (annotated, ty) {
(true, Type::Function(_, _, result)) => {
let result = self.types().show(result, TypePosition::Value);
Code::atom(format!("|{}| -> {result} ", params.join(", ")))
}
_ => Code::atom(format!("|{}| ", params.join(", "))),
};
Code::joined(vec![head, body], Precedence::Operator)
}
fn is_boxed_object_reference(&self, ty: &Type) -> bool {
let Some(box_binding) = self.program.box_binding else {
return false;
};
match ty {
Type::Ref(_, inner) | Type::Mut(_, inner) => matches!(
inner.as_ref(),
Type::Named(binding, arguments)
if *binding == box_binding
&& matches!(arguments.as_slice(), [Type::TraitObject(_)])
),
_ => false,
}
}
fn closure_body(&mut self, lambda: &TypedLambda, annotated: bool) -> Code {
if self.tail_loop.is_some() {
let fragments = self.loop_expression(&lambda.body, LoopExit::Return);
return if annotated {
Self::braced(fragments)
} else {
Code::block(fragments)
};
}
if annotated {
return self.block(&lambda.body);
}
match lambda.body.items.as_slice() {
[TypedBodyItem::Expr(only)] => self.expr(only),
_ => self.block(&lambda.body),
}
}
fn braced(fragments: Vec<Fragment>) -> Code {
let mut inner = vec![Fragment::Newline];
inner.extend(fragments);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(inner),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn inline_call(&mut self, lambda: &TypedLambda, arguments: &[TypedArgument]) -> Code {
let fragments = self.inline_call_contents(lambda, arguments);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(fragments),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn inline_call_contents(
&mut self,
lambda: &TypedLambda,
arguments: &[TypedArgument],
) -> Vec<Fragment> {
let previous = std::mem::replace(&mut self.tail_loop, TailLoop::of_closure(lambda));
self.locals.enter();
let mut fragments = Vec::new();
for (param, argument) in lambda.params.iter().zip(arguments) {
if let Some(local) = self.reused_locals.local_of(param.binding) {
self.locals.reuse(param.binding, local);
continue;
}
let binding = self.bind_parameter(param, TypePosition::Value);
let value = self.annotated_binding_value(argument);
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text(format!("let {binding} = ")));
fragments.extend(value.fragments);
fragments.push(Fragment::Text(";".to_string()));
}
if self.tail_loop.is_some() {
fragments.push(Fragment::Newline);
fragments.extend(self.loop_expression(&lambda.body, LoopExit::Break));
} else {
fragments.extend(self.statements(&lambda.body));
}
self.locals.leave();
self.tail_loop = previous;
fragments
}
fn annotated_binding_value(&mut self, argument: &TypedArgument) -> Code {
let Some(literal) = Self::bound_literal(&argument.value) else {
return self.argument(argument, TypeContext::Known, ArgumentRole::Ordinary);
};
let unwrapped = TypedArgument {
value: literal.clone(),
borrow: argument.borrow,
};
self.argument(&unwrapped, TypeContext::Known, ArgumentRole::Ordinary)
}
fn block(&mut self, block: &TypedBlock) -> Code {
let statements = self.block_contents(block);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(statements),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn block_contents(&mut self, block: &TypedBlock) -> Vec<Fragment> {
self.locals.enter();
let statements = self.statements(block);
self.locals.leave();
statements
}
fn conditional(&mut self, test: &TypedExpr, then: &TypedExpr, otherwise: &TypedExpr) -> Code {
if Self::is_empty_block(then) && !Self::is_empty_block(otherwise) {
return self.negated_conditional(test, otherwise);
}
if let Some(operator) = Self::short_circuit_operator(then, otherwise) {
let value = if Self::boolean_literal(then) == Some(true) {
otherwise
} else {
then
};
return self.short_circuit(operator, test, value);
}
let test = self.expr(test).into_head();
let mut fragments = vec![Fragment::Text("if ".to_string())];
fragments.extend(test.fragments);
fragments.push(Fragment::Text(" {".to_string()));
fragments.push(Fragment::Indented(self.branch(then)));
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text("}".to_string()));
if let TypedExprKind::Control(TypedControl::If(next_test, next_then, next_otherwise)) =
&otherwise.kind
{
fragments.push(Fragment::Text(" else ".to_string()));
fragments.extend(
self.conditional(next_test, next_then, next_otherwise)
.fragments,
);
} else if !Self::is_empty_block(otherwise) {
fragments.push(Fragment::Text(" else {".to_string()));
fragments.push(Fragment::Indented(self.branch(otherwise)));
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text("}".to_string()));
}
Code::block(fragments)
}
fn negated_conditional(&mut self, test: &TypedExpr, branch: &TypedExpr) -> Code {
let test = Self::prefixed("!", self.expr(test)).into_head();
let mut fragments = vec![Fragment::Text("if ".to_string())];
fragments.extend(test.fragments);
fragments.push(Fragment::Text(" {".to_string()));
fragments.push(Fragment::Indented(self.branch(branch)));
fragments.push(Fragment::Newline);
fragments.push(Fragment::Text("}".to_string()));
Code::block(fragments)
}
fn short_circuit(
&mut self,
operator: RustOperator,
test: &TypedExpr,
value: &TypedExpr,
) -> Code {
let test = self.expr(test);
let value = self.expr(value);
let test = self.binary_operand(test, operator, Side::Left);
let value = self.binary_operand(value, operator, Side::Right);
Code::joined_outside_delimiters(
vec![test, Code::atom(format!(" {} ", operator.symbol())), value],
Precedence::Operator,
)
}
fn binary_operand(&self, code: Code, operator: RustOperator, side: Side) -> Code {
let needs_parentheses = match code.precedence {
Precedence::Atom | Precedence::Prefix => false,
Precedence::Block => true,
Precedence::Operator => RustOperator::outermost(&code.text())
.is_none_or(|inner| inner.needs_parentheses(operator, side)),
};
if needs_parentheses {
code.parenthesized()
} else {
code
}
}
fn short_circuit_operator(then: &TypedExpr, otherwise: &TypedExpr) -> Option<RustOperator> {
match (
Self::boolean_literal(then),
Self::boolean_literal(otherwise),
) {
(Some(true), None) => Some(RustOperator::or()),
(None, Some(false)) => Some(RustOperator::and()),
_ => None,
}
}
fn boolean_literal(expr: &TypedExpr) -> Option<bool> {
match &expr.kind {
TypedExprKind::Leaf(TypedLeaf::Literal(Literal::Bool(value))) => Some(*value),
_ => None,
}
}
fn branch(&mut self, branch: &TypedExpr) -> Vec<Fragment> {
if let Some(contents) = self.tail_block_contents(branch) {
return contents;
}
let mut fragments = vec![Fragment::Newline];
fragments.extend(self.expr(branch).fragments);
fragments
}
fn is_empty_block(expr: &TypedExpr) -> bool {
matches!(&expr.kind, TypedExprKind::Control(TypedControl::Block(block)) if block.items.is_empty())
}
fn update(
&mut self,
binding: BindingId,
base: &TypedExpr,
fields: &[(FieldLabel, TypedExpr)],
) -> Code {
let path = self.types().struct_path(binding);
self.locals.enter();
let base = self.update_base(base);
let update = self.struct_update(path, fields, base.usage());
self.locals.leave();
match base.name {
Some(name) => Self::bound_block(&name, base.code, update),
None => update,
}
}
fn update_base(&mut self, base: &TypedExpr) -> UpdateArgument {
match StructUpdateOrder::of(base) {
StructUpdateOrder::FieldsBeforePlace(_) => UpdateArgument {
name: None,
code: self.expr(base),
},
StructUpdateOrder::BaseFirst => self.update_argument(base, "base"),
}
}
fn struct_update(
&mut self,
path: String,
fields: &[(FieldLabel, TypedExpr)],
base: Code,
) -> Code {
let mut parts = vec![Code::atom(format!("{path} {{ "))];
for (position, (label, value)) in fields.iter().enumerate() {
if position > 0 {
parts.push(Code::atom(", "));
}
let field = NameMangler::value(&label.name);
let value = self.expr(value);
if value.text() == field {
parts.push(value);
} else {
parts.push(Code::atom(format!("{field}: ")));
parts.push(value);
}
}
parts.push(Code::atom(", .."));
parts.push(base);
parts.push(Code::atom(" }"));
Code::struct_literal(parts)
}
fn bound_block(name: &str, value: Code, body: Code) -> Code {
let mut contents = vec![Fragment::Newline, Fragment::Text(format!("let {name} = "))];
contents.extend(value.fragments);
contents.push(Fragment::Text(";".to_string()));
contents.push(Fragment::Newline);
contents.extend(body.fragments);
Code::block(vec![
Fragment::Text("{".to_string()),
Fragment::Indented(contents),
Fragment::Newline,
Fragment::Text("}".to_string()),
])
}
fn construction(&mut self, binding: BindingId, fields: &[(FieldLabel, TypedExpr)]) -> Code {
let path = self.types().struct_path(binding);
let mut parts = vec![Code::atom(format!("{path} {{ "))];
for (position, (label, value)) in fields.iter().enumerate() {
if position > 0 {
parts.push(Code::atom(", "));
}
let field = NameMangler::value(&label.name);
let value = self.expr(value);
if value.text() == field {
parts.push(value);
} else {
parts.push(Code::atom(format!("{field}: ")));
parts.push(value);
}
}
parts.push(Code::atom(" }"));
Code::struct_literal(parts)
}
fn field_access(&mut self, value: &TypedExpr, label: &FieldLabel, result: &Type) -> Code {
let borrows_field = result.is_reference()
&& !self
.declared_field_type(value, label)
.is_some_and(|declared| declared.is_reference());
let access = self.field_place(value, label);
if borrows_field {
Code::joined_outside_delimiters(vec![Code::atom("&"), access], Precedence::Prefix)
} else {
access
}
}
fn field_place(&mut self, value: &TypedExpr, label: &FieldLabel) -> Code {
let value = self.field_owner(value);
let value = if value.is_atomic() {
value
} else {
value.parenthesized()
};
Code::joined_outside_delimiters(
vec![
value,
Code::atom(format!(".{}", NameMangler::value(&label.name))),
],
Precedence::Atom,
)
}
fn field_owner(&mut self, owner: &TypedExpr) -> Code {
let owner = Self::without_dereferenced_borrows(owner);
match &owner.kind {
TypedExprKind::Deref(referent) => self.field_owner(referent),
_ => self.place(owner, TypeContext::Known),
}
}
fn declared_field_type(&self, value: &TypedExpr, label: &FieldLabel) -> Option<Type> {
let mut owner = &value.ty;
while let Type::Ref(_, inner) | Type::Mut(_, inner) = owner {
owner = inner;
}
let Type::Named(binding, arguments) = owner else {
return None;
};
self.program
.structs
.get(*binding)?
.instantiated_field(&label.name, arguments)
}
}