Skip to content

Commit aea9629

Browse files
committed
Don't insert semicolons inside of a macro_rules! arm body
Currently, rustfmt inserts semicolons for certain trailing expressions (`return`, `continue`, and `break`). However, this should not be done in the body of a `macro_rules!` arm, since the macro might be used in expression position (where a trailing semicolon will be invalid). Currently, this rewriting has no effect due to rust-lang/rust#33953 If this issue is fixed, then this rewriting will prevent some macros from being used in expression position. This commit prevents rustfmt from inserting semicolons for trailing expressions in `macro_rules!` arms. The user is free to either include or omit the semicolon - rustfmt will not modify either case.
1 parent 5afd2e4 commit aea9629

File tree

9 files changed

+61
-10
lines changed

9 files changed

+61
-10
lines changed

src/formatting.rs

+3
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ fn format_project(
149149
&format_report,
150150
&files,
151151
original_snippet.clone(),
152+
operation_setting.is_macro_def,
152153
)?;
153154
}
154155
timer = timer.done_formatting();
@@ -173,6 +174,7 @@ fn format_file(
173174
report: &FormatReport,
174175
file_mod_map: &FileModMap<'_>,
175176
original_snippet: Option<String>,
177+
is_macro_def: bool,
176178
) -> Result<(), OperationError> {
177179
let snippet_provider = parse_session.snippet_provider(module.as_ref().inner);
178180
let mut visitor = FmtVisitor::from_parse_sess(
@@ -182,6 +184,7 @@ fn format_file(
182184
file_mod_map,
183185
report.clone(),
184186
);
187+
visitor.is_macro_def = is_macro_def;
185188
visitor.skip_context.update_with_attrs(&krate.attrs);
186189
visitor.last_pos = snippet_provider.start_pos();
187190
visitor.skip_empty_lines(snippet_provider.end_pos());

src/formatting/comment.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,9 @@ impl<'a> CommentRewrite<'a> {
684684
let mut config = self.fmt.config.clone();
685685
config.set().wrap_comments(false);
686686
if config.format_code_in_doc_comments() {
687-
if let Some(s) = format_code_block(&self.code_block_buffer, &config) {
687+
if let Some(s) =
688+
format_code_block(&self.code_block_buffer, &config, false)
689+
{
688690
trim_custom_comment_prefix(s.as_ref())
689691
} else {
690692
trim_custom_comment_prefix(&self.code_block_buffer)

src/formatting/macros.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1379,12 +1379,12 @@ impl MacroBranch {
13791379
config.set().max_width(new_width);
13801380

13811381
// First try to format as items, then as statements.
1382-
let new_body_snippet = match format_snippet(&body_str, &config) {
1382+
let new_body_snippet = match format_snippet(&body_str, &config, true) {
13831383
Some(new_body) => new_body,
13841384
None => {
13851385
let new_width = new_width + config.tab_spaces();
13861386
config.set().max_width(new_width);
1387-
match format_code_block(&body_str, &config) {
1387+
match format_code_block(&body_str, &config, true) {
13881388
Some(new_body) => new_body,
13891389
None => return None,
13901390
}

src/formatting/rewrite.rs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub(crate) struct RewriteContext<'a> {
3333
pub(crate) file_mod_map: &'a FileModMap<'a>,
3434
pub(crate) config: &'a Config,
3535
pub(crate) inside_macro: Rc<Cell<bool>>,
36+
pub(crate) is_macro_def: bool,
3637
// Force block indent style even if we are using visual indent style.
3738
pub(crate) use_block: Cell<bool>,
3839
// When `is_if_else_block` is true, unindent the comment on top

src/formatting/utils.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ pub(crate) fn contains_skip(attrs: &[Attribute]) -> bool {
300300

301301
#[inline]
302302
pub(crate) fn semicolon_for_expr(context: &RewriteContext<'_>, expr: &ast::Expr) -> bool {
303+
// Never try to insert semicolons on expressions when we're inside
304+
// a macro definition - this can prevent the macro from compiling
305+
// when used in expression position
306+
if context.is_macro_def {
307+
return false;
308+
}
303309
match expr.kind {
304310
ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => {
305311
context.config.trailing_semicolon()
@@ -700,7 +706,11 @@ pub(crate) fn unicode_str_width(s: &str) -> usize {
700706

701707
/// Format the given snippet. The snippet is expected to be *complete* code.
702708
/// When we cannot parse the given snippet, this function returns `None`.
703-
pub(crate) fn format_snippet(snippet: &str, config: &Config) -> Option<FormattedSnippet> {
709+
pub(crate) fn format_snippet(
710+
snippet: &str,
711+
config: &Config,
712+
is_macro_def: bool,
713+
) -> Option<FormattedSnippet> {
704714
let mut config = config.clone();
705715
std::panic::catch_unwind(move || {
706716
config.set().hide_parse_errors(true);
@@ -712,6 +722,7 @@ pub(crate) fn format_snippet(snippet: &str, config: &Config) -> Option<Formatted
712722
&config,
713723
OperationSetting {
714724
verbosity: Verbosity::Quiet,
725+
is_macro_def,
715726
..OperationSetting::default()
716727
},
717728
)
@@ -740,7 +751,11 @@ pub(crate) fn format_snippet(snippet: &str, config: &Config) -> Option<Formatted
740751
/// The code block may be incomplete (i.e., parser may be unable to parse it).
741752
/// To avoid panic in parser, we wrap the code block with a dummy function.
742753
/// The returned code block does **not** end with newline.
743-
pub(crate) fn format_code_block(code_snippet: &str, config: &Config) -> Option<FormattedSnippet> {
754+
pub(crate) fn format_code_block(
755+
code_snippet: &str,
756+
config: &Config,
757+
is_macro_def: bool,
758+
) -> Option<FormattedSnippet> {
744759
const FN_MAIN_PREFIX: &str = "fn main() {\n";
745760

746761
fn enclose_in_main_block(s: &str, config: &Config) -> String {
@@ -773,7 +788,7 @@ pub(crate) fn format_code_block(code_snippet: &str, config: &Config) -> Option<F
773788
config_with_unix_newline
774789
.set()
775790
.newline_style(NewlineStyle::Unix);
776-
let mut formatted = format_snippet(&snippet, &config_with_unix_newline)?;
791+
let mut formatted = format_snippet(&snippet, &config_with_unix_newline, is_macro_def)?;
777792
// Remove wrapping main block
778793
formatted.unwrap_code_block();
779794

@@ -854,15 +869,15 @@ mod test {
854869
// `format_snippet()` and `format_code_block()` should not panic
855870
// even when we cannot parse the given snippet.
856871
let snippet = "let";
857-
assert!(format_snippet(snippet, &Config::default()).is_none());
858-
assert!(format_code_block(snippet, &Config::default()).is_none());
872+
assert!(format_snippet(snippet, &Config::default(), false).is_none());
873+
assert!(format_code_block(snippet, &Config::default(), false).is_none());
859874
}
860875

861876
fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
862877
where
863-
F: Fn(&str, &Config) -> Option<FormattedSnippet>,
878+
F: Fn(&str, &Config, bool /* is_code_block */) -> Option<FormattedSnippet>,
864879
{
865-
let output = formatter(input, &Config::default());
880+
let output = formatter(input, &Config::default(), false);
866881
output.is_some() && output.unwrap().snippet == expected
867882
}
868883

src/formatting/visitor.rs

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ pub(crate) struct FmtVisitor<'a> {
9090
pub(crate) skip_context: SkipContext,
9191
/// If set to `true`, normalize number of vertical spaces on formatting missing snippets.
9292
pub(crate) normalize_vertical_spaces: bool,
93+
/// If set to `true`, we are formatting a macro definition
94+
pub(crate) is_macro_def: bool,
9395
}
9496

9597
impl<'a> Drop for FmtVisitor<'a> {
@@ -888,6 +890,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
888890
report,
889891
skip_context: Default::default(),
890892
normalize_vertical_spaces: false,
893+
is_macro_def: false,
891894
}
892895
}
893896

@@ -1091,6 +1094,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
10911094
report: self.report.clone(),
10921095
skip_context: self.skip_context.clone(),
10931096
skipped_range: self.skipped_range.clone(),
1097+
is_macro_def: self.is_macro_def,
10941098
}
10951099
}
10961100
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ mod test;
3939
pub struct OperationSetting {
4040
/// If set to `true`, format sub-modules which are defined in the given input.
4141
pub recursive: bool,
42+
/// If set to `true`, we are formatting a macro definition
43+
pub is_macro_def: bool,
4244
pub verbosity: Verbosity,
4345
}
4446

src/rustfmt/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ fn format_string(input: String, opt: Opt) -> Result<i32> {
438438
let setting = OperationSetting {
439439
recursive: opt.recursive,
440440
verbosity: Verbosity::Quiet,
441+
is_macro_def: false,
441442
};
442443
let report = rustfmt_nightly::format(Input::Text(input), &config, setting)?;
443444

@@ -538,6 +539,7 @@ fn format(opt: Opt) -> Result<i32> {
538539
let setting = OperationSetting {
539540
recursive: opt.recursive,
540541
verbosity: opt.verbosity(),
542+
is_macro_def: false,
541543
};
542544

543545
let inputs = FileConfigPairIter::new(&opt, config_paths.is_some()).collect::<Vec<_>>();

tests/target/macro_rules_semi.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
macro_rules! expr {
2+
(no_semi) => {
3+
return true
4+
};
5+
(semi) => {
6+
return true;
7+
};
8+
}
9+
10+
fn foo() -> bool {
11+
match true {
12+
true => expr!(no_semi),
13+
false if false => {
14+
expr!(semi)
15+
}
16+
false => {
17+
expr!(semi);
18+
}
19+
}
20+
}
21+
22+
fn main() {}

0 commit comments

Comments
 (0)