-
Notifications
You must be signed in to change notification settings - Fork 0
Description
The Problem
I found a case where the existing formatting mechanisms were not sufficient to format a struct type the way I wanted:
#[derive(SyntaxFmt)]
#[syntax(pre = "struct ")]
pub struct StructNamed {
pub ident: Ident,
pub generic_params: GenericParams,
pub where_clause: WhereClause,
#[syntax(pre = " ", eval = !fields.is_empty())]
#[syntax_else(cont = ";")]
pub fields: FieldsNamed,
}This works great for:
struct Foo;
struct Foo { }
struct Foo { field: i32 }
struct Foo<T>;
struct Foo<T> { }
struct Foo<T> { field: i32 }
struct Foo<T> where T: Display;
struct Foo<T> where T: Display { }But it doesn't work well for:
struct Foo<T> where T: Display { field: i32 }Which outputs:
struct Foo<T>
where
T: Display
{ // Note the extra space
field: i32
}Of course, in my case, I can just define another struct StructUnit, but this issue probably needs to be tackled for the general case.
The case for inserting the extra space depends upon both where_clause.is_empty() and fields.is_empty(), but we can't test both of these in the eval because their condition will affect the evaluation of whatever field they're added to.
We need to separate the insertion of the space from the fields themselves somehow.
Possible Solutions
One option would be a special insertion attribute:
#[syntax_insert(cont = " ", eval = where_clause.is_empty() && !fields.is_empty())]
#[syntax(eval = !fields.is_empty())]
#[syntax_else(cont = ";")]
pub fields: FieldsNamed,But that feels like a hack.
I'm wondering if we need a more future proof solution. Something like:
#[derive(SyntaxFmt))]
#[syntax(pre = "struct ")]
#[syntax(var(sp) = eval(self.where_clause.is_empty() && !self.fields.is_empty(), " ", ""))]
#[syntax(var(semi) = eval(self.fields.is_empty(), ";", ""))]
pub struct StructNamed {
pub ident: Ident,
pub generic_params: GenericParams,
pub where_clause: WhereClause,
#[syntax(pre = "{sp}", suf = "{semi}")]
pub fields: FieldsNamed,
}This is a little more verbose up front, but could potentially pave the way for complex formatting support, which would declutter the struct definition.
#[derive(SyntaxFmt))]
#[syntax(var(sp) = eval(self.where_clause.is_empty() && !self.fields.is_empty(), " ", ""))]
#[syntax(var(semi) = eval(self.fields.is_empty(), ";", ""))]
#[syntax(fmt = "struct {ident}{generic_params}{where_clause}{sp}{fields}{semi}"]
pub struct StructNamed {
pub ident: Ident,
pub generic_params: GenericParams,
pub where_clause: WhereClause,
pub fields: FieldsNamed,
}Workaround
It's possible to achieve this behaviour with a custom formatter via the cont_with attribute argument. Either with a closure, or a function.
#[derive(SyntaxFmt))]
#[syntax(cont_with = format_struct_named)]
pub struct StructNamed {
pub ident: Ident,
pub generic_params: GenericParams,
pub where_clause: WhereClause,
pub fields: FieldsNamed,
}
fn format_struct_named<S>(struct_named: &StructNamed, f: SyntaxFormatter<S>) std::fmt::Result {
let StructNamed { ident, generic_params, where_clause, fields } = struct_named;
ident.syntax_fmt(f)?;
generic_params.syntax_fmt(f)?;
where_clause.syntax_fmt(f)?;
if fields.is_empty() {
f.write_str(";")?;
} else {
if where_clause.is_empty() {
f.write_str(" ")?;
}
fields.syntax_fmt(f)?;
}
Ok(())
}In some ways, this is better, because the mess around the struct is moved out of the struct definition. The downside is that the function requires multiple lines of code to achieve the desired effect.