René Kijewski f201285ede Use if-let-chains
I ran `cargo +nightly clippy --all-targets --fix  -- -D warnings` and
made only tiny manual improvements.
2025-07-13 23:16:26 +02:00

840 lines
28 KiB
Rust

use std::borrow::Cow;
use parser::node::CondTest;
use parser::{
AssociatedItem, CharLit, CharPrefix, Expr, Span, StrLit, Target, TyGenerics, WithSpan,
};
use quote::quote;
use super::{
DisplayWrap, FILTER_SOURCE, Generator, LocalMeta, Writable, compile_time_escape, is_copyable,
normalize_identifier,
};
use crate::CompileError;
use crate::heritage::Context;
use crate::integration::Buffer;
impl<'a> Generator<'a, '_> {
pub(crate) fn visit_expr_root(
&mut self,
ctx: &Context<'_>,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<String, CompileError> {
let mut buf = Buffer::new();
self.visit_expr(ctx, &mut buf, expr)?;
Ok(buf.into_string())
}
pub(super) fn visit_loop_iter(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
iter: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
let expr_code = self.visit_expr_root(ctx, iter)?;
match &**iter {
Expr::Range(..) => buf.write(expr_code),
Expr::Array(..) => buf.write(format_args!("{expr_code}.iter()")),
// If `iter` is a call then we assume it's something that returns
// an iterator. If not then the user can explicitly add the needed
// call without issues.
Expr::Call { .. } | Expr::Index(..) => {
buf.write(format_args!("({expr_code}).into_iter()"));
}
// If accessing `self` then it most likely needs to be
// borrowed, to prevent an attempt of moving.
_ if expr_code.starts_with("self.") => {
buf.write(format_args!("(&{expr_code}).into_iter()"));
}
// If accessing a field then it most likely needs to be
// borrowed, to prevent an attempt of moving.
Expr::AssociatedItem(..) => buf.write(format_args!("(&{expr_code}).into_iter()")),
// Otherwise, we borrow `iter` assuming that it implements `IntoIterator`.
_ => buf.write(format_args!("({expr_code}).into_iter()")),
}
Ok(DisplayWrap::Unwrapped)
}
pub(super) fn visit_expr(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
Ok(match **expr {
Expr::BoolLit(s) => self.visit_bool_lit(buf, s),
Expr::NumLit(s, _) => self.visit_num_lit(buf, s),
Expr::StrLit(ref s) => self.visit_str_lit(buf, s),
Expr::CharLit(ref s) => self.visit_char_lit(buf, s),
Expr::Var(s) => self.visit_var(buf, s),
Expr::Path(ref path) => self.visit_path(buf, path),
Expr::Array(ref elements) => self.visit_array(ctx, buf, elements)?,
Expr::AssociatedItem(ref obj, ref associated_item) => {
self.visit_associated_item(ctx, buf, obj, associated_item)?
}
Expr::Index(ref obj, ref key) => self.visit_index(ctx, buf, obj, key)?,
Expr::Filter(ref v) => {
self.visit_filter(ctx, buf, &v.name, &v.arguments, &v.generics, expr.span())?
}
Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?,
Expr::BinOp(ref v) => self.visit_binop(ctx, buf, v.op, &v.lhs, &v.rhs)?,
Expr::Range(ref v) => {
self.visit_range(ctx, buf, v.op, v.lhs.as_ref(), v.rhs.as_ref())?
}
Expr::Group(ref inner) => self.visit_group(ctx, buf, inner)?,
Expr::Call(ref v) => self.visit_call(ctx, buf, &v.path, &v.args, &v.generics)?,
Expr::RustMacro(ref path, args) => self.visit_rust_macro(buf, path, args),
Expr::Try(ref expr) => self.visit_try(ctx, buf, expr)?,
Expr::Tuple(ref exprs) => self.visit_tuple(ctx, buf, exprs)?,
Expr::NamedArgument(_, ref expr) => self.visit_named_argument(ctx, buf, expr)?,
Expr::FilterSource => self.visit_filter_source(buf),
Expr::IsDefined(var_name) => self.visit_is_defined(buf, true, var_name)?,
Expr::IsNotDefined(var_name) => self.visit_is_defined(buf, false, var_name)?,
Expr::As(ref expr, target) => self.visit_as(ctx, buf, expr, target)?,
Expr::Concat(ref exprs) => self.visit_concat(ctx, buf, exprs)?,
Expr::LetCond(ref cond) => self.visit_let_cond(ctx, buf, cond)?,
Expr::ArgumentPlaceholder => DisplayWrap::Unwrapped,
})
}
/// This method and `visit_expr_not_first` are needed because in case we have
/// `{% if let Some(x) = x && x == "a" %}`, if we first start to visit `Some(x)`, then we end
/// up with `if let Some(x) = x && x == "a"`, however if we first visit the expr, we end up with
/// `if let Some(x) = self.x && self.x == "a"`. It's all a big "variable declaration" mess.
///
/// So instead, we first visit the expression, but only the first "level" to ensure we won't
/// go after the `&&` and badly generate the rest of the expression.
pub(super) fn visit_expr_first(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
match **expr {
Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => {
let ret = self.visit_expr(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {} ", &v.op));
return Ok(ret);
}
Expr::Unary(op, ref inner) => {
buf.write(op);
return self.visit_expr_first(ctx, buf, inner);
}
_ => {}
}
self.visit_expr(ctx, buf, expr)
}
pub(super) fn visit_expr_not_first(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
prev_display_wrap: DisplayWrap,
) -> Result<DisplayWrap, CompileError> {
match **expr {
Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => {
self.visit_condition(ctx, buf, &v.rhs)?;
Ok(DisplayWrap::Unwrapped)
}
Expr::Unary(_, ref inner) => {
self.visit_expr_not_first(ctx, buf, inner, prev_display_wrap)
}
_ => Ok(prev_display_wrap),
}
}
pub(super) fn visit_condition(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<(), CompileError> {
match &**expr {
Expr::BoolLit(_) | Expr::IsDefined(_) | Expr::IsNotDefined(_) => {
self.visit_expr(ctx, buf, expr)?;
}
Expr::Unary("!", expr) => {
buf.write('!');
self.visit_condition(ctx, buf, expr)?;
}
Expr::BinOp(v) if matches!(v.op, "&&" | "||") => {
self.visit_condition(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {} ", v.op));
self.visit_condition(ctx, buf, &v.rhs)?;
}
Expr::Group(expr) => {
buf.write('(');
self.visit_condition(ctx, buf, expr)?;
buf.write(')');
}
Expr::LetCond(cond) => {
self.visit_let_cond(ctx, buf, cond)?;
}
_ => {
buf.write("askama::helpers::as_bool(&(");
self.visit_expr(ctx, buf, expr)?;
buf.write("))");
}
}
Ok(())
}
fn visit_is_defined(
&mut self,
buf: &mut Buffer,
is_defined: bool,
left: &str,
) -> Result<DisplayWrap, CompileError> {
match (is_defined, self.is_var_defined(left)) {
(true, true) | (false, false) => buf.write("true"),
_ => buf.write("false"),
}
Ok(DisplayWrap::Unwrapped)
}
fn visit_as(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
target: &str,
) -> Result<DisplayWrap, CompileError> {
buf.write("askama::helpers::get_primitive_value(&(");
self.visit_expr(ctx, buf, expr)?;
buf.write(format_args!(
")) as askama::helpers::core::primitive::{target}"
));
Ok(DisplayWrap::Unwrapped)
}
fn visit_concat(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
exprs: &[WithSpan<'a, Expr<'a>>],
) -> Result<DisplayWrap, CompileError> {
match exprs {
[] => unreachable!(),
[expr] => self.visit_expr(ctx, buf, expr),
exprs => {
let (l, r) = exprs.split_at(exprs.len().div_ceil(2));
buf.write("askama::helpers::Concat(&(");
self.visit_concat(ctx, buf, l)?;
buf.write("), &(");
self.visit_concat(ctx, buf, r)?;
buf.write("))");
Ok(DisplayWrap::Unwrapped)
}
}
}
fn visit_let_cond(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
cond: &WithSpan<'a, CondTest<'a>>,
) -> Result<DisplayWrap, CompileError> {
let mut expr_buf = Buffer::new();
let display_wrap = self.visit_expr_first(ctx, &mut expr_buf, &cond.expr)?;
buf.write(" let ");
if let Some(ref target) = cond.target {
self.visit_target(buf, true, true, target);
}
buf.write(format_args!("= &{expr_buf}"));
self.visit_expr_not_first(ctx, buf, &cond.expr, display_wrap)
}
fn visit_try(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
buf.write("match (");
self.visit_expr(ctx, buf, expr)?;
buf.write(
") { res => (&&askama::helpers::ErrorMarker::of(&res)).askama_conv_result(res)? }",
);
Ok(DisplayWrap::Unwrapped)
}
fn visit_rust_macro(&mut self, buf: &mut Buffer, path: &[&str], args: &str) -> DisplayWrap {
let [path @ .., name] = path else {
unreachable!("path cannot be empty");
};
let name = match normalize_identifier(name) {
"loop" => "r#loop",
name => name,
};
if !path.is_empty() {
self.visit_path(buf, path);
buf.write("::");
}
buf.write(name);
buf.write("!(");
buf.write(args);
buf.write(')');
DisplayWrap::Unwrapped
}
pub(super) fn visit_value(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'a, Expr<'a>>],
generics: &[WithSpan<'a, TyGenerics<'a>>],
node: Span<'_>,
kind: &str,
) -> Result<DisplayWrap, CompileError> {
let [key] = args else {
return Err(ctx.generate_error(
format_args!("{kind} only takes one argument, found {}", args.len()),
node,
));
};
let [r#gen] = generics else {
return Err(ctx.generate_error(
format_args!("{kind} expects one generic, found {}", generics.len()),
node,
));
};
buf.write("askama::helpers::get_value");
buf.write("::<");
self.visit_ty_generic(buf, r#gen);
buf.write('>');
buf.write("(&__askama_values, &(");
self.visit_arg(ctx, buf, key)?;
buf.write("))");
Ok(DisplayWrap::Unwrapped)
}
pub(super) fn visit_args(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
args: &[WithSpan<'a, Expr<'a>>],
) -> Result<(), CompileError> {
for (i, arg) in args.iter().enumerate() {
if i > 0 {
buf.write(',');
}
self.visit_arg(ctx, buf, arg)?;
}
Ok(())
}
pub(super) fn visit_arg(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
arg: &WithSpan<'a, Expr<'a>>,
) -> Result<(), CompileError> {
self.visit_arg_inner(ctx, buf, arg, false)
}
fn visit_arg_inner(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
arg: &WithSpan<'a, Expr<'a>>,
// This parameter is needed because even though Expr::Unary is not copyable, we might still
// be able to skip a few levels.
need_borrow: bool,
) -> Result<(), CompileError> {
if let Expr::Unary(expr @ ("*" | "&"), ref arg) = **arg {
buf.write(expr);
return self.visit_arg_inner(ctx, buf, arg, true);
}
let borrow = need_borrow || !is_copyable(arg);
if borrow {
buf.write("&(");
}
match **arg {
Expr::Call(ref v) if !matches!(*v.path, Expr::Path(_)) => {
buf.write('{');
self.visit_expr(ctx, buf, arg)?;
buf.write('}');
}
_ => {
self.visit_expr(ctx, buf, arg)?;
}
}
if borrow {
buf.write(')');
}
Ok(())
}
pub(super) fn visit_auto_escaped_arg(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
arg: &WithSpan<'a, Expr<'a>>,
) -> Result<(), CompileError> {
if let Some(Writable::Lit(arg)) = compile_time_escape(arg, self.input.escaper) {
if !arg.is_empty() {
buf.write("askama::filters::Safe(");
buf.write_escaped_str(&arg);
buf.write(')');
} else {
buf.write("askama::helpers::Empty");
}
} else {
buf.write("(&&askama::filters::AutoEscaper::new(");
self.visit_arg(ctx, buf, arg)?;
buf.write(format_args!(
", {})).askama_auto_escape()?",
self.input.escaper
));
}
Ok(())
}
pub(crate) fn visit_associated_item(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
obj: &WithSpan<'a, Expr<'a>>,
associated_item: &AssociatedItem<'a>,
) -> Result<DisplayWrap, CompileError> {
if let Expr::Var("loop") = **obj {
buf.write(match associated_item.name {
"index0" => "__askama_item.index0",
"index" => "(__askama_item.index0 + 1)",
"first" => "(__askama_item.index0 == 0)",
"last" => "__askama_item.last",
name => {
return Err(ctx.generate_error(
format!("unknown loop variable `{}`", name.escape_debug()),
obj.span(),
));
}
});
return Ok(DisplayWrap::Unwrapped);
}
self.visit_expr(ctx, buf, obj)?;
buf.write(format_args!(
".{}",
normalize_identifier(associated_item.name)
));
self.visit_call_generics(buf, &associated_item.generics);
Ok(DisplayWrap::Unwrapped)
}
pub(super) fn visit_call_generics(
&mut self,
buf: &mut Buffer,
generics: &[WithSpan<'a, TyGenerics<'a>>],
) {
if generics.is_empty() {
return;
}
buf.write("::");
self.visit_ty_generics(buf, generics);
}
fn visit_ty_generics(&mut self, buf: &mut Buffer, generics: &[WithSpan<'a, TyGenerics<'a>>]) {
if generics.is_empty() {
return;
}
buf.write('<');
for generic in generics {
self.visit_ty_generic(buf, generic);
buf.write(',');
}
buf.write('>');
}
pub(super) fn visit_ty_generic(
&mut self,
buf: &mut Buffer,
generic: &WithSpan<'a, TyGenerics<'a>>,
) {
let TyGenerics { refs, path, args } = &**generic;
for _ in 0..*refs {
buf.write('&');
}
self.visit_path(buf, path);
self.visit_ty_generics(buf, args);
}
fn visit_index(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
obj: &WithSpan<'a, Expr<'a>>,
key: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
buf.write('&');
self.visit_expr(ctx, buf, obj)?;
buf.write('[');
self.visit_expr(ctx, buf, key)?;
buf.write(']');
Ok(DisplayWrap::Unwrapped)
}
fn visit_call(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
left: &WithSpan<'a, Expr<'a>>,
args: &[WithSpan<'a, Expr<'a>>],
generics: &[WithSpan<'a, TyGenerics<'a>>],
) -> Result<DisplayWrap, CompileError> {
match &**left {
Expr::AssociatedItem(sub_left, AssociatedItem { name, .. })
if ***sub_left == Expr::Var("loop") =>
{
match *name {
"cycle" => {
if let [generic, ..] = generics {
return Err(ctx.generate_error(
"loop.cycle(…) doesn't use generics",
generic.span(),
));
}
match args {
[arg] => {
if matches!(**arg, Expr::Array(ref arr) if arr.is_empty()) {
return Err(ctx.generate_error(
"loop.cycle(…) cannot use an empty array",
arg.span(),
));
}
buf.write(
"\
({\
let _cycle = &(",
);
self.visit_expr(ctx, buf, arg)?;
buf.write(
"\
);\
let __askama_len = _cycle.len();\
if __askama_len == 0 {\
return askama::helpers::core::result::Result::Err(askama::Error::Fmt);\
}\
_cycle[__askama_item.index0 % __askama_len]\
})",
);
}
_ => {
return Err(ctx.generate_error(
"loop.cycle(…) cannot use an empty array",
left.span(),
));
}
}
}
s => {
return Err(ctx.generate_error(
format_args!("unknown loop method: {s:?}"),
left.span(),
));
}
}
}
// We special-case "askama::get_value".
Expr::Path(path) if path == &["askama", "get_value"] => {
self.visit_value(
ctx,
buf,
args,
generics,
left.span(),
"`get_value` function",
)?;
}
sub_left => {
match sub_left {
Expr::Var(name) => match self.locals.resolve(name) {
Some(resolved) => buf.write(resolved),
None => buf.write(format_args!("self.{}", normalize_identifier(name))),
},
_ => {
self.visit_expr(ctx, buf, left)?;
}
}
self.visit_call_generics(buf, generics);
buf.write('(');
self.visit_args(ctx, buf, args)?;
buf.write(')');
}
}
Ok(DisplayWrap::Unwrapped)
}
fn visit_unary(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
op: &str,
inner: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
buf.write(op);
self.visit_expr(ctx, buf, inner)?;
Ok(DisplayWrap::Unwrapped)
}
fn visit_range(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
op: &str,
left: Option<&WithSpan<'a, Expr<'a>>>,
right: Option<&WithSpan<'a, Expr<'a>>>,
) -> Result<DisplayWrap, CompileError> {
if let Some(left) = left {
self.visit_expr(ctx, buf, left)?;
}
buf.write(op);
if let Some(right) = right {
self.visit_expr(ctx, buf, right)?;
}
Ok(DisplayWrap::Unwrapped)
}
fn visit_binop(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
op: &str,
left: &WithSpan<'a, Expr<'a>>,
right: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
self.visit_expr(ctx, buf, left)?;
buf.write(format_args!(" {op} "));
self.visit_expr(ctx, buf, right)?;
Ok(DisplayWrap::Unwrapped)
}
fn visit_group(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
inner: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
buf.write('(');
self.visit_expr(ctx, buf, inner)?;
buf.write(')');
Ok(DisplayWrap::Unwrapped)
}
fn visit_tuple(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
exprs: &[WithSpan<'a, Expr<'a>>],
) -> Result<DisplayWrap, CompileError> {
buf.write('(');
for (index, expr) in exprs.iter().enumerate() {
if index > 0 {
buf.write(' ');
}
self.visit_expr(ctx, buf, expr)?;
buf.write(',');
}
buf.write(')');
Ok(DisplayWrap::Unwrapped)
}
fn visit_named_argument(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
expr: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> {
self.visit_expr(ctx, buf, expr)?;
Ok(DisplayWrap::Unwrapped)
}
fn visit_array(
&mut self,
ctx: &Context<'_>,
buf: &mut Buffer,
elements: &[WithSpan<'a, Expr<'a>>],
) -> Result<DisplayWrap, CompileError> {
buf.write('[');
for (i, el) in elements.iter().enumerate() {
if i > 0 {
buf.write(',');
}
self.visit_expr(ctx, buf, el)?;
}
buf.write(']');
Ok(DisplayWrap::Unwrapped)
}
pub(super) fn visit_path(&mut self, buf: &mut Buffer, path: &[&str]) -> DisplayWrap {
for (i, part) in path.iter().copied().enumerate() {
if i > 0 {
buf.write("::");
} else if let Some(enum_ast) = self.input.enum_ast
&& part == "Self"
{
let this = &enum_ast.ident;
let (_, generics, _) = enum_ast.generics.split_for_impl();
let generics = generics.as_turbofish();
buf.write(quote!(#this #generics));
continue;
}
buf.write(part);
}
DisplayWrap::Unwrapped
}
fn visit_var(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
if s == "self" {
buf.write(s);
return DisplayWrap::Unwrapped;
}
buf.write(normalize_identifier(&self.locals.resolve_or_self(s)));
DisplayWrap::Unwrapped
}
fn visit_filter_source(&mut self, buf: &mut Buffer) -> DisplayWrap {
// We can assume that the body of the `{% filter %}` was already escaped.
// And if it's not, then this was done intentionally.
buf.write(format_args!("askama::filters::Safe(&{FILTER_SOURCE})"));
DisplayWrap::Wrapped
}
fn visit_bool_lit(&mut self, buf: &mut Buffer, s: bool) -> DisplayWrap {
if s {
buf.write("true");
} else {
buf.write("false");
}
DisplayWrap::Unwrapped
}
pub(super) fn visit_str_lit(&mut self, buf: &mut Buffer, s: &StrLit<'_>) -> DisplayWrap {
if let Some(prefix) = s.prefix {
buf.write(prefix.to_char());
}
buf.write(format_args!("\"{}\"", s.content));
DisplayWrap::Unwrapped
}
fn visit_char_lit(&mut self, buf: &mut Buffer, c: &CharLit<'_>) -> DisplayWrap {
if c.prefix == Some(CharPrefix::Binary) {
buf.write('b');
}
buf.write(format_args!("'{}'", c.content));
DisplayWrap::Unwrapped
}
fn visit_num_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
buf.write(s);
DisplayWrap::Unwrapped
}
pub(super) fn visit_target(
&mut self,
buf: &mut Buffer,
initialized: bool,
first_level: bool,
target: &Target<'a>,
) {
match target {
Target::Placeholder(_) => buf.write('_'),
Target::Rest(s) => {
if let Some(var_name) = &**s {
self.locals
.insert(Cow::Borrowed(var_name), LocalMeta::var_def());
buf.write(var_name);
buf.write(" @ ");
}
buf.write("..");
}
Target::Name(name) => {
let name = normalize_identifier(name);
match initialized {
true => self
.locals
.insert(Cow::Borrowed(name), LocalMeta::var_def()),
false => self.locals.insert_with_default(Cow::Borrowed(name)),
}
buf.write(name);
}
Target::OrChain(targets) => match targets.first() {
None => buf.write('_'),
Some(first_target) => {
self.visit_target(buf, initialized, first_level, first_target);
for target in &targets[1..] {
buf.write('|');
self.visit_target(buf, initialized, first_level, target);
}
}
},
Target::Tuple(path, targets) => {
buf.write_separated_path(path);
buf.write('(');
for target in targets {
self.visit_target(buf, initialized, false, target);
buf.write(',');
}
buf.write(')');
}
Target::Array(path, targets) => {
buf.write_separated_path(path);
buf.write('[');
for target in targets {
self.visit_target(buf, initialized, false, target);
buf.write(',');
}
buf.write(']');
}
Target::Struct(path, targets) => {
buf.write_separated_path(path);
buf.write('{');
for (name, target) in targets {
if let Target::Rest(_) = target {
buf.write("..");
continue;
}
buf.write(normalize_identifier(name));
buf.write(": ");
self.visit_target(buf, initialized, false, target);
buf.write(',');
}
buf.write('}');
}
Target::Path(path) => {
self.visit_path(buf, path);
buf.write("{}");
}
Target::StrLit(s) => {
if first_level {
buf.write('&');
}
self.visit_str_lit(buf, s);
}
Target::NumLit(s, _) => {
if first_level {
buf.write('&');
}
self.visit_num_lit(buf, s);
}
Target::CharLit(s) => {
if first_level {
buf.write('&');
}
self.visit_char_lit(buf, s);
}
Target::BoolLit(s) => {
if first_level {
buf.write('&');
}
buf.write(s);
}
}
}
}