Change:
: The current output is: We should detect cases where a semicolon would be appropriate (probe that the current token and maybe limited lookahead confirm that what comes next could be a valid statement on its own) and suggest a semicolon. . We might want to silence one or the other depending on whether the current token might be starting a statement or an item ( vs , etc.). <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
claim
Sorry, I don't have much time these days. release-assignment
Just a guess: could this be addressed with another block, as in
I think we would need to answer this question: why is the logic here not handling the cases we want to handle?
This seems to be on track given the clue "probe that the current token and maybe limited lookahead confirm that what comes next could be a valid statement on its own"...
claim
release-assignment
That is a great question: as the code is written it looks like it should work. I don't know why it isn't triggering :( It might be that isn't being called in these case. It might be a good idea to uplift most of this method into
I'd like to give this a try. claim

Discussion:
expected.sort_by_cached_key(|x| x.to_string()); expected.dedup(); <ins> let sm = self.sess.source_map(); let msg = format!("expected `;`, found {}", super::token_descr(&self.token)); let appl = Applicability::MachineApplicable; if expected.contains(&TokenType::Token(token::Semi)) { if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP { // Likely inside a macro, can't provide meaningful suggestions. } else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) { // The current token is in the same line as the prior token, not recoverable. } else if [token::Comma, token::Colon].contains(&self.token.kind) && self.prev_token.kind == token::CloseDelim(token::Paren) { // Likely typo: The current token is on a new line and is expected to be // `.`, `;`, `?`, or an operator after a close delimiter token. // // let a = std::process::Command::new("echo") //         .arg("1") //         ,arg("2") //         ^ // https://github.com/rust-lang/rust/issues/72253 } else if self.look_ahead(1, |t| { t == &token::CloseDelim(token::Brace) || t.can_begin_expr() && t.kind != token::Colon }) && [token::Comma, token::Colon].contains(&self.token.kind) { // Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is // either `,` or `:`, and the next token could either start a new statement or is a // block close. For example: // //   let x = 32: //   let y = 42; self.bump(); let sp = self.prev_token.span; self.struct_span_err(sp, &msg) .span_suggestion_short(sp, "change this to `;`", ";".to_string(), appl) .emit(); return Ok(false); } else if self.look_ahead(0, |t| { t == &token::CloseDelim(token::Brace) || ( t.can_begin_expr() && t != &token::Semi && t != &token::Pound // Avoid triggering with too many trailing `#` in raw string. ) }) { // Missing semicolon typo. This is triggered if the next token could either start a // new statement or is a block close. For example: // //   let x = 32 //   let y = 42; let sp = self.prev_token.span.shrink_to_hi(); self.struct_span_err(sp, &msg) .span_label(self.token.span, "unexpected token") .span_suggestion_short(sp, "add `;` here", ";".to_string(), appl) .emit(); return Ok(false); } } </ins> let expect = tokens_to_string(&expected[..]); let actual = super::token_descr(&self.token); let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 {

return Err(err); } <del> let sm = self.sess.source_map(); </del> if self.prev_token.span == DUMMY_SP { // Account for macro context where the previous span might not be // available to avoid incorrect output (#54841).

if self.eat(&token::Semi) { return Ok(()); } <del> let sm = self.sess.source_map(); let msg = format!("expected `;`, found {}", super::token_descr(&self.token)); let appl = Applicability::MachineApplicable; if self.token.span == DUMMY_SP || self.prev_token.span == DUMMY_SP { // Likely inside a macro, can't provide meaningful suggestions. return self.expect(&token::Semi).map(drop); } else if !sm.is_multiline(self.prev_token.span.until(self.token.span)) { // The current token is in the same line as the prior token, not recoverable. } else if [token::Comma, token::Colon].contains(&self.token.kind) && self.prev_token.kind == token::CloseDelim(token::Paren) { // Likely typo: The current token is on a new line and is expected to be // `.`, `;`, `?`, or an operator after a close delimiter token. // // let a = std::process::Command::new("echo") //         .arg("1") //         ,arg("2") //         ^ // https://github.com/rust-lang/rust/issues/72253 self.expect(&token::Semi)?; return Ok(()); } else if self.look_ahead(1, |t| { t == &token::CloseDelim(token::Brace) || t.can_begin_expr() && t.kind != token::Colon }) && [token::Comma, token::Colon].contains(&self.token.kind) { // Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is // either `,` or `:`, and the next token could either start a new statement or is a // block close. For example: // //   let x = 32: //   let y = 42; self.bump(); let sp = self.prev_token.span; self.struct_span_err(sp, &msg) .span_suggestion_short(sp, "change this to `;`", ";".to_string(), appl) .emit(); return Ok(()); } else if self.look_ahead(0, |t| { t == &token::CloseDelim(token::Brace) || ( t.can_begin_expr() && t != &token::Semi && t != &token::Pound // Avoid triggering with too many trailing `#` in raw string. ) }) { // Missing semicolon typo. This is triggered if the next token could either start a // new statement or is a block close. For example: // //   let x = 32 //   let y = 42; let sp = self.prev_token.span.shrink_to_hi(); self.struct_span_err(sp, &msg) .span_label(self.token.span, "unexpected token") .span_suggestion_short(sp, "add `;` here", ";".to_string(), appl) .emit(); return Ok(()); } </del> self.expect(&token::Semi).map(drop) // Error unconditionally }

<ins> // run-rustfix // Parser should know when a semicolon is missing. // https://github.com/rust-lang/rust/issues/87197 fn main() { let x = 100; //~ ERROR: expected `;` println!("{}", x); //~ ERROR: expected `;` let y = 200; //~ ERROR: expected `;` println!("{}", y); } </ins>

<ins> // run-rustfix // Parser should know when a semicolon is missing. // https://github.com/rust-lang/rust/issues/87197 fn main() { let x = 100 //~ ERROR: expected `;` println!("{}", x) //~ ERROR: expected `;` let y = 200 //~ ERROR: expected `;` println!("{}", y); } </ins>

<ins> error: expected `;`, found `println` --> $DIR/issue-87197-missing-semicolon.rs:6:16 | LL |     let x = 100 |                ^ help: add `;` here LL |     println!("{}", x) |     ------- unexpected token error: expected `;`, found keyword `let` --> $DIR/issue-87197-missing-semicolon.rs:7:22 | LL |     println!("{}", x) |                      ^ help: add `;` here LL |     let y = 200 |     --- unexpected token error: expected `;`, found `println` --> $DIR/issue-87197-missing-semicolon.rs:8:16 | LL |     let y = 200 |                ^ help: add `;` here LL |     println!("{}", y); |     ------- unexpected token error: aborting due to 3 previous errors </ins>

fn main() { <del> assert_eq!(1, 2) assert_eq!(3, 4) //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `assert_eq` </del> <ins> assert_eq!(1, 2) //~ ERROR: expected `;` assert_eq!(3, 4) //~ ERROR: expected `;` </ins> println!("hello"); }

<del> error: expected one of `.`, `;`, `?`, `}`, or an operator, found `assert_eq` --> $DIR/macros-no-semicolon.rs:3:5 </del> <ins> error: expected `;`, found `assert_eq` --> $DIR/macros-no-semicolon.rs:2:21 </ins> | LL |     assert_eq!(1, 2) <del> |                     - expected one of `.`, `;`, `?`, `}`, or an operator </del> <ins> |                     ^ help: add `;` here </ins> LL |     assert_eq!(3, 4) <del> |     ^^^^^^^^^ unexpected token </del> <ins> |     --------- unexpected token </ins> <del> error: aborting due to previous error </del> <ins> error: expected `;`, found `println` --> $DIR/macros-no-semicolon.rs:3:21 | LL |     assert_eq!(3, 4) |                     ^ help: add `;` here LL |     println!("hello"); |     ------- unexpected token error: aborting due to 2 previous errors </ins>