Hey, author of rust-analyzer here. Long term, rust-analyzer needs a code formatter, and I like the IR approach you are taking, so I thought I'll dump some of my thoughts on formatting in case they are useful.
To be clear, this is in no way a feature request or something like that, feel free to ignore completely! :)
In rust-analyzer, we sometimes have to synthesize new code and insert such generated code into something written by a user (mostly for refactorings). Our syntax trees represent whitespaces and comments explicitly, so we can represent properly formatted and indented code. However, just manually producing formatted code is a huge pain, so the ideal solution for authors of refactorings is to build code with bad formatting, pipe it through the formatter and insert the good version.
The specifics of IDE places some constraits on the formatting tool however (which are not met by rustfmt). Specifically:
- The formatter has to handle incomplete code reasonably, as in IDE use-case almost all code is half-writtn and contain syntax errors. For example, the formatter should somehow reasonably format the following code
fn example() {
let x = if bar
foo()
}
which is parsed into the following syntax tree:
FN_DEF@[0; 45)
FN_KW@[0; 2) "fn"
WHITESPACE@[2; 3) " "
NAME@[3; 10)
IDENT@[3; 10) "example"
PARAM_LIST@[10; 12)
L_PAREN@[10; 11) "("
R_PAREN@[11; 12) ")"
WHITESPACE@[12; 13) " "
BLOCK_EXPR@[13; 45)
BLOCK@[13; 45)
L_CURLY@[13; 14) "{"
WHITESPACE@[14; 19) "\n "
LET_STMT@[19; 33)
LET_KW@[19; 22) "let"
WHITESPACE@[22; 23) " "
BIND_PAT@[23; 24)
NAME@[23; 24)
IDENT@[23; 24) "x"
WHITESPACE@[24; 25) " "
EQ@[25; 26) "="
WHITESPACE@[26; 27) " "
IF_EXPR@[27; 33)
IF_KW@[27; 29) "if"
WHITESPACE@[29; 30) " "
CONDITION@[30; 33)
PATH_EXPR@[30; 33)
PATH@[30; 33)
PATH_SEGMENT@[30; 33)
NAME_REF@[30; 33)
IDENT@[30; 33) "bar"
WHITESPACE@[33; 38) "\n "
CALL_EXPR@[38; 43)
PATH_EXPR@[38; 41)
PATH@[38; 41)
PATH_SEGMENT@[38; 41)
NAME_REF@[38; 41)
IDENT@[38; 41) "foo"
ARG_LIST@[41; 43)
L_PAREN@[41; 42) "("
R_PAREN@[42; 43) ")"
WHITESPACE@[43; 44) "\n"
R_CURLY@[44; 45) "}"
- The formatter should be able to reformat only part of the code. Specifically, during refactorings we want to reformat only the parts touched by refactorings, and freeze the rest. For example, if "extract variable" transforms
into
fn foo() {
let var = 1+1;
bar(var)
}
we want the formatter to handle spaces around =
and indentation of the whole let, but we do not want it to "fix" spaces around +
, as that's what the user originally written, and we should not "touch" that (but note that might have to re-indent it anyway)
- (This is mostly subjective, and I am willing to concede this constraint) In general, formatting should not mess with what the user has written. So, it should introduce spaces around operators and re-indent stuff, but, in general, it should not decide how to break large expressions into multiple lines.
So, this all leads me to the idea that rust-analyzer will be better off with a rule-based formatter, where you specify the rules like "a +
should always be surrounded by one space on each side", and let the formatting engine to figure out the necessary whitespace changes to satisfy all the rules. Possible alternatives are pretty-printers, where the formatter mostly disregards the original formatting and just pretty-prints the code in one true way (rustfmt works like this) or cost-optimization formatters, where there's a certain "pretiness" metric, and the formatter literary searches for the best layout that optimizes this metric (dartfmt works like this I think).
Your IR approach resembles in some aspects what I have in mind for rust-analyzer. In particular, rules like "insert spaces around +
" or "if function call is multiline, there should be a \n before )" could be expressed with conditionals. The main differnece is that I am thinking about tree-shaped, and not linear IR, for formatting, but perhaps lowering to a sequence migth be a better approach.
Overall, I am super excited about dprint, and would love to study its approach in more details :) Thanks for building it!