Auto merge of #69506 - Centril:stmt-semi-none, r=petrochenkov

encode `;` stmt without expr as `StmtKind::Empty`

Instead of encoding `;` statements without a an expression as a tuple in AST, encode it as `ast::StmtKind::Empty`.

r? @petrochenkov
This commit is contained in:
bors 2020-03-03 19:57:07 +00:00
commit 4ad6248825
19 changed files with 73 additions and 102 deletions

View File

@ -913,6 +913,8 @@ pub enum StmtKind {
Expr(P<Expr>), Expr(P<Expr>),
/// Expr with a trailing semi-colon. /// Expr with a trailing semi-colon.
Semi(P<Expr>), Semi(P<Expr>),
/// Just a trailing semi-colon.
Empty,
/// Macro. /// Macro.
Mac(P<(Mac, MacStmtStyle, AttrVec)>), Mac(P<(Mac, MacStmtStyle, AttrVec)>),
} }

View File

@ -674,8 +674,8 @@ impl HasAttrs for StmtKind {
fn attrs(&self) -> &[Attribute] { fn attrs(&self) -> &[Attribute] {
match *self { match *self {
StmtKind::Local(ref local) => local.attrs(), StmtKind::Local(ref local) => local.attrs(),
StmtKind::Item(..) => &[],
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => expr.attrs(), StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => expr.attrs(),
StmtKind::Empty | StmtKind::Item(..) => &[],
StmtKind::Mac(ref mac) => { StmtKind::Mac(ref mac) => {
let (_, _, ref attrs) = **mac; let (_, _, ref attrs) = **mac;
attrs.attrs() attrs.attrs()
@ -686,9 +686,8 @@ impl HasAttrs for StmtKind {
fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>)) { fn visit_attrs(&mut self, f: impl FnOnce(&mut Vec<Attribute>)) {
match self { match self {
StmtKind::Local(local) => local.visit_attrs(f), StmtKind::Local(local) => local.visit_attrs(f),
StmtKind::Item(..) => {} StmtKind::Expr(expr) | StmtKind::Semi(expr) => expr.visit_attrs(f),
StmtKind::Expr(expr) => expr.visit_attrs(f), StmtKind::Empty | StmtKind::Item(..) => {}
StmtKind::Semi(expr) => expr.visit_attrs(f),
StmtKind::Mac(mac) => { StmtKind::Mac(mac) => {
let (_mac, _style, attrs) = mac.deref_mut(); let (_mac, _style, attrs) = mac.deref_mut();
attrs.visit_attrs(f); attrs.visit_attrs(f);

View File

@ -1265,6 +1265,7 @@ pub fn noop_flat_map_stmt_kind<T: MutVisitor>(
StmtKind::Item(item) => vis.flat_map_item(item).into_iter().map(StmtKind::Item).collect(), StmtKind::Item(item) => vis.flat_map_item(item).into_iter().map(StmtKind::Item).collect(),
StmtKind::Expr(expr) => vis.filter_map_expr(expr).into_iter().map(StmtKind::Expr).collect(), StmtKind::Expr(expr) => vis.filter_map_expr(expr).into_iter().map(StmtKind::Expr).collect(),
StmtKind::Semi(expr) => vis.filter_map_expr(expr).into_iter().map(StmtKind::Semi).collect(), StmtKind::Semi(expr) => vis.filter_map_expr(expr).into_iter().map(StmtKind::Semi).collect(),
StmtKind::Empty => smallvec![StmtKind::Empty],
StmtKind::Mac(mut mac) => { StmtKind::Mac(mut mac) => {
let (mac_, _semi, attrs) = mac.deref_mut(); let (mac_, _semi, attrs) = mac.deref_mut();
vis.visit_mac(mac_); vis.visit_mac(mac_);

View File

@ -669,9 +669,8 @@ pub fn walk_stmt<'a, V: Visitor<'a>>(visitor: &mut V, statement: &'a Stmt) {
match statement.kind { match statement.kind {
StmtKind::Local(ref local) => visitor.visit_local(local), StmtKind::Local(ref local) => visitor.visit_local(local),
StmtKind::Item(ref item) => visitor.visit_item(item), StmtKind::Item(ref item) => visitor.visit_item(item),
StmtKind::Expr(ref expression) | StmtKind::Semi(ref expression) => { StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => visitor.visit_expr(expr),
visitor.visit_expr(expression) StmtKind::Empty => {}
}
StmtKind::Mac(ref mac) => { StmtKind::Mac(ref mac) => {
let (ref mac, _, ref attrs) = **mac; let (ref mac, _, ref attrs) = **mac;
visitor.visit_mac(mac); visitor.visit_mac(mac);

View File

@ -2281,6 +2281,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
} }
StmtKind::Expr(ref e) => hir::StmtKind::Expr(self.lower_expr(e)), StmtKind::Expr(ref e) => hir::StmtKind::Expr(self.lower_expr(e)),
StmtKind::Semi(ref e) => hir::StmtKind::Semi(self.lower_expr(e)), StmtKind::Semi(ref e) => hir::StmtKind::Semi(self.lower_expr(e)),
StmtKind::Empty => return smallvec![],
StmtKind::Mac(..) => panic!("shouldn't exist here"), StmtKind::Mac(..) => panic!("shouldn't exist here"),
}; };
smallvec![hir::Stmt { hir_id: self.lower_node_id(s.id), kind, span: s.span }] smallvec![hir::Stmt { hir_id: self.lower_node_id(s.id), kind, span: s.span }]

View File

@ -1446,19 +1446,13 @@ impl<'a> State<'a> {
} }
} }
ast::StmtKind::Semi(ref expr) => { ast::StmtKind::Semi(ref expr) => {
match expr.kind {
// Filter out empty `Tup` exprs created for the `redundant_semicolon`
// lint, as they shouldn't be visible and interact poorly
// with proc macros.
ast::ExprKind::Tup(ref exprs) if exprs.is_empty() && expr.attrs.is_empty() => {
()
}
_ => {
self.space_if_not_bol(); self.space_if_not_bol();
self.print_expr_outer_attr_style(expr, false); self.print_expr_outer_attr_style(expr, false);
self.s.word(";"); self.s.word(";");
} }
} ast::StmtKind::Empty => {
self.space_if_not_bol();
self.s.word(";");
} }
ast::StmtKind::Mac(ref mac) => { ast::StmtKind::Mac(ref mac) => {
let (ref mac, style, ref attrs) = **mac; let (ref mac, style, ref attrs) = **mac;

View File

@ -775,7 +775,10 @@ impl EarlyLintPass for UnusedDocComment {
ast::StmtKind::Local(..) => "statements", ast::StmtKind::Local(..) => "statements",
ast::StmtKind::Item(..) => "inner items", ast::StmtKind::Item(..) => "inner items",
// expressions will be reported by `check_expr`. // expressions will be reported by `check_expr`.
ast::StmtKind::Semi(..) | ast::StmtKind::Expr(..) | ast::StmtKind::Mac(..) => return, ast::StmtKind::Empty
| ast::StmtKind::Semi(_)
| ast::StmtKind::Expr(_)
| ast::StmtKind::Mac(_) => return,
}; };
warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs()); warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs());

View File

@ -113,7 +113,7 @@ macro_rules! early_lint_passes {
WhileTrue: WhileTrue, WhileTrue: WhileTrue,
NonAsciiIdents: NonAsciiIdents, NonAsciiIdents: NonAsciiIdents,
IncompleteFeatures: IncompleteFeatures, IncompleteFeatures: IncompleteFeatures,
RedundantSemicolon: RedundantSemicolon, RedundantSemicolons: RedundantSemicolons,
UnusedDocComment: UnusedDocComment, UnusedDocComment: UnusedDocComment,
] ]
); );
@ -274,7 +274,8 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
UNUSED_EXTERN_CRATES, UNUSED_EXTERN_CRATES,
UNUSED_FEATURES, UNUSED_FEATURES,
UNUSED_LABELS, UNUSED_LABELS,
UNUSED_PARENS UNUSED_PARENS,
REDUNDANT_SEMICOLONS
); );
add_lint_group!( add_lint_group!(
@ -307,6 +308,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
store.register_renamed("unused_doc_comment", "unused_doc_comments"); store.register_renamed("unused_doc_comment", "unused_doc_comments");
store.register_renamed("async_idents", "keyword_idents"); store.register_renamed("async_idents", "keyword_idents");
store.register_renamed("exceeding_bitshifts", "arithmetic_overflow"); store.register_renamed("exceeding_bitshifts", "arithmetic_overflow");
store.register_renamed("redundant_semicolon", "redundant_semicolons");
store.register_removed("unknown_features", "replaced by an error"); store.register_removed("unknown_features", "replaced by an error");
store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate"); store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
store.register_removed("negate_unsigned", "cast a signed value instead"); store.register_removed("negate_unsigned", "cast a signed value instead");

View File

@ -1,50 +1,41 @@
use crate::{EarlyContext, EarlyLintPass, LintContext}; use crate::{EarlyContext, EarlyLintPass, LintContext};
use rustc_ast::ast::{ExprKind, Stmt, StmtKind}; use rustc_ast::ast::{Block, StmtKind};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_span::Span;
declare_lint! { declare_lint! {
pub REDUNDANT_SEMICOLON, pub REDUNDANT_SEMICOLONS,
Warn, Warn,
"detects unnecessary trailing semicolons" "detects unnecessary trailing semicolons"
} }
declare_lint_pass!(RedundantSemicolon => [REDUNDANT_SEMICOLON]); declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]);
impl EarlyLintPass for RedundantSemicolon { impl EarlyLintPass for RedundantSemicolons {
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &Stmt) { fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
if let StmtKind::Semi(expr) = &stmt.kind { let mut seq = None;
if let ExprKind::Tup(ref v) = &expr.kind { for stmt in block.stmts.iter() {
if v.is_empty() { match (&stmt.kind, &mut seq) {
// Strings of excess semicolons are encoded as empty tuple expressions (StmtKind::Empty, None) => seq = Some((stmt.span, false)),
// during the parsing stage, so we check for empty tuple expressions (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true),
// which span only semicolons (_, seq) => maybe_lint_redundant_semis(cx, seq),
if let Ok(source_str) = cx.sess().source_map().span_to_snippet(stmt.span) { }
if source_str.chars().all(|c| c == ';') { }
let multiple = (stmt.span.hi() - stmt.span.lo()).0 > 1; maybe_lint_redundant_semis(cx, &mut seq);
let msg = if multiple { }
"unnecessary trailing semicolons" }
fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) {
if let Some((span, multiple)) = seq.take() {
cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
let (msg, rem) = if multiple {
("unnecessary trailing semicolons", "remove these semicolons")
} else { } else {
"unnecessary trailing semicolon" ("unnecessary trailing semicolon", "remove this semicolon")
}; };
cx.struct_span_lint(REDUNDANT_SEMICOLON, stmt.span, |lint| { lint.build(msg)
let mut err = lint.build(&msg); .span_suggestion(span, rem, String::new(), Applicability::MaybeIncorrect)
let suggest_msg = if multiple { .emit();
"remove these semicolons"
} else {
"remove this semicolon"
};
err.span_suggestion(
stmt.span,
&suggest_msg,
String::new(),
Applicability::MaybeIncorrect,
);
err.emit();
}); });
} }
} }
}
}
}
}
}

View File

@ -59,23 +59,10 @@ impl<'a> Parser<'a> {
} else if let Some(item) = self.parse_stmt_item(attrs.clone())? { } else if let Some(item) = self.parse_stmt_item(attrs.clone())? {
// FIXME: Bad copy of attrs // FIXME: Bad copy of attrs
self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item))) self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item)))
} else if self.token == token::Semi { } else if self.eat(&token::Semi) {
// Do not attempt to parse an expression if we're done here. // Do not attempt to parse an expression if we're done here.
self.error_outer_attrs(&attrs); self.error_outer_attrs(&attrs);
self.bump(); self.mk_stmt(lo, StmtKind::Empty)
let mut last_semi = lo;
while self.token == token::Semi {
last_semi = self.token.span;
self.bump();
}
// We are encoding a string of semicolons as an an empty tuple that spans
// the excess semicolons to preserve this info until the lint stage.
let kind = StmtKind::Semi(self.mk_expr(
lo.to(last_semi),
ExprKind::Tup(Vec::new()),
AttrVec::new(),
));
self.mk_stmt(lo.to(last_semi), kind)
} else if self.token != token::CloseDelim(token::Brace) { } else if self.token != token::CloseDelim(token::Brace) {
// Remainder are line-expr stmts. // Remainder are line-expr stmts.
let e = self.parse_expr_res(Restrictions::STMT_EXPR, Some(attrs.into()))?; let e = self.parse_expr_res(Restrictions::STMT_EXPR, Some(attrs.into()))?;
@ -144,12 +131,11 @@ impl<'a> Parser<'a> {
/// Error on outer attributes in this context. /// Error on outer attributes in this context.
/// Also error if the previous token was a doc comment. /// Also error if the previous token was a doc comment.
fn error_outer_attrs(&self, attrs: &[Attribute]) { fn error_outer_attrs(&self, attrs: &[Attribute]) {
if !attrs.is_empty() { if let [.., last] = attrs {
if matches!(self.prev_token.kind, TokenKind::DocComment(..)) { if last.is_doc_comment() {
self.span_fatal_err(self.prev_token.span, Error::UselessDocComment).emit(); self.span_fatal_err(last.span, Error::UselessDocComment).emit();
} else if attrs.iter().any(|a| a.style == AttrStyle::Outer) { } else if attrs.iter().any(|a| a.style == AttrStyle::Outer) {
self.struct_span_err(self.token.span, "expected statement after outer attribute") self.struct_span_err(last.span, "expected statement after outer attribute").emit();
.emit();
} }
} }
} }
@ -401,6 +387,7 @@ impl<'a> Parser<'a> {
self.expect_semi()?; self.expect_semi()?;
eat_semi = false; eat_semi = false;
} }
StmtKind::Empty => eat_semi = false,
_ => {} _ => {}
} }

View File

@ -4,5 +4,5 @@ warning: unnecessary trailing semicolons
LL | if (true) { 12; };;; -num; LL | if (true) { 12; };;; -num;
| ^^ help: remove these semicolons | ^^ help: remove these semicolons
| |
= note: `#[warn(redundant_semicolon)]` on by default = note: `#[warn(redundant_semicolons)]` on by default

View File

@ -5,6 +5,7 @@ struct Bar<T> { x: T }
struct W(u32); struct W(u32);
struct A { a: u32 } struct A { a: u32 }
#[allow(redundant_semicolons)]
const fn basics((a,): (u32,)) -> u32 { const fn basics((a,): (u32,)) -> u32 {
// Deferred assignment: // Deferred assignment:
let b: u32; let b: u32;

View File

@ -7,6 +7,7 @@ struct Bar<T> { x: T }
struct W(f32); struct W(f32);
struct A { a: f32 } struct A { a: f32 }
#[allow(redundant_semicolons)]
const fn basics((a,): (f32,)) -> f32 { const fn basics((a,): (f32,)) -> f32 {
// Deferred assignment: // Deferred assignment:
let b: f32; let b: f32;

View File

@ -1,6 +1,6 @@
// aux-build:redundant-semi-proc-macro-def.rs // aux-build:redundant-semi-proc-macro-def.rs
#![deny(redundant_semicolon)] #![deny(redundant_semicolons)]
extern crate redundant_semi_proc_macro; extern crate redundant_semi_proc_macro;
use redundant_semi_proc_macro::should_preserve_spans; use redundant_semi_proc_macro::should_preserve_spans;

View File

@ -1,4 +1,4 @@
TokenStream [Ident { ident: "fn", span: #0 bytes(197..199) }, Ident { ident: "span_preservation", span: #0 bytes(200..217) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(217..219) }, Group { delimiter: Brace, stream: TokenStream [Ident { ident: "let", span: #0 bytes(227..230) }, Ident { ident: "tst", span: #0 bytes(231..234) }, Punct { ch: '=', spacing: Alone, span: #0 bytes(235..236) }, Literal { lit: Lit { kind: Integer, symbol: "123", suffix: None }, span: Span { lo: BytePos(237), hi: BytePos(240), ctxt: #0 } }, Punct { ch: ';', spacing: Joint, span: #0 bytes(240..241) }, Punct { ch: ';', spacing: Alone, span: #0 bytes(241..242) }, Ident { ident: "match", span: #0 bytes(288..293) }, Ident { ident: "tst", span: #0 bytes(294..297) }, Group { delimiter: Brace, stream: TokenStream [Literal { lit: Lit { kind: Integer, symbol: "123", suffix: None }, span: Span { lo: BytePos(482), hi: BytePos(485), ctxt: #0 } }, Punct { ch: '=', spacing: Joint, span: #0 bytes(486..488) }, Punct { ch: '>', spacing: Alone, span: #0 bytes(486..488) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(489..491) }, Punct { ch: ',', spacing: Alone, span: #0 bytes(491..492) }, Ident { ident: "_", span: #0 bytes(501..502) }, Punct { ch: '=', spacing: Joint, span: #0 bytes(503..505) }, Punct { ch: '>', spacing: Alone, span: #0 bytes(503..505) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(506..508) }], span: #0 bytes(298..514) }, Punct { ch: ';', spacing: Joint, span: #0 bytes(514..515) }, Punct { ch: ';', spacing: Joint, span: #0 bytes(515..516) }, Punct { ch: ';', spacing: Alone, span: #0 bytes(516..517) }], span: #0 bytes(221..561) }] TokenStream [Ident { ident: "fn", span: #0 bytes(198..200) }, Ident { ident: "span_preservation", span: #0 bytes(201..218) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(218..220) }, Group { delimiter: Brace, stream: TokenStream [Ident { ident: "let", span: #0 bytes(228..231) }, Ident { ident: "tst", span: #0 bytes(232..235) }, Punct { ch: '=', spacing: Alone, span: #0 bytes(236..237) }, Literal { lit: Lit { kind: Integer, symbol: "123", suffix: None }, span: Span { lo: BytePos(238), hi: BytePos(241), ctxt: #0 } }, Punct { ch: ';', spacing: Joint, span: #0 bytes(241..242) }, Punct { ch: ';', spacing: Alone, span: #0 bytes(242..243) }, Ident { ident: "match", span: #0 bytes(289..294) }, Ident { ident: "tst", span: #0 bytes(295..298) }, Group { delimiter: Brace, stream: TokenStream [Literal { lit: Lit { kind: Integer, symbol: "123", suffix: None }, span: Span { lo: BytePos(483), hi: BytePos(486), ctxt: #0 } }, Punct { ch: '=', spacing: Joint, span: #0 bytes(487..489) }, Punct { ch: '>', spacing: Alone, span: #0 bytes(487..489) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(490..492) }, Punct { ch: ',', spacing: Alone, span: #0 bytes(492..493) }, Ident { ident: "_", span: #0 bytes(502..503) }, Punct { ch: '=', spacing: Joint, span: #0 bytes(504..506) }, Punct { ch: '>', spacing: Alone, span: #0 bytes(504..506) }, Group { delimiter: Parenthesis, stream: TokenStream [], span: #0 bytes(507..509) }], span: #0 bytes(299..515) }, Punct { ch: ';', spacing: Joint, span: #0 bytes(515..516) }, Punct { ch: ';', spacing: Joint, span: #0 bytes(516..517) }, Punct { ch: ';', spacing: Alone, span: #0 bytes(517..518) }], span: #0 bytes(222..562) }]
error: unnecessary trailing semicolon error: unnecessary trailing semicolon
--> $DIR/redundant-semi-proc-macro.rs:9:19 --> $DIR/redundant-semi-proc-macro.rs:9:19
| |
@ -8,8 +8,8 @@ LL | let tst = 123;;
note: the lint level is defined here note: the lint level is defined here
--> $DIR/redundant-semi-proc-macro.rs:3:9 --> $DIR/redundant-semi-proc-macro.rs:3:9
| |
LL | #![deny(redundant_semicolon)] LL | #![deny(redundant_semicolons)]
| ^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^
error: unnecessary trailing semicolons error: unnecessary trailing semicolons
--> $DIR/redundant-semi-proc-macro.rs:16:7 --> $DIR/redundant-semi-proc-macro.rs:16:7

View File

@ -1,8 +1,8 @@
error: expected statement after outer attribute error: expected statement after outer attribute
--> $DIR/attr-dangling-in-fn.rs:5:1 --> $DIR/attr-dangling-in-fn.rs:4:3
| |
LL | } LL | #[foo = "bar"]
| ^ | ^^^^^^^^^^^^^^
error: aborting due to previous error error: aborting due to previous error

View File

@ -411,16 +411,16 @@ LL | #[cfg(FALSE)] fn e() { let _ = x.#[attr]foo(); }
| ^ expected one of `.`, `;`, `?`, or an operator | ^ expected one of `.`, `;`, `?`, or an operator
error: expected statement after outer attribute error: expected statement after outer attribute
--> $DIR/attr-stmt-expr-attr-bad.rs:114:44 --> $DIR/attr-stmt-expr-attr-bad.rs:114:37
| |
LL | #[cfg(FALSE)] fn e() { { fn foo() { #[attr]; } } } LL | #[cfg(FALSE)] fn e() { { fn foo() { #[attr]; } } }
| ^ | ^^^^^^^
error: expected statement after outer attribute error: expected statement after outer attribute
--> $DIR/attr-stmt-expr-attr-bad.rs:116:45 --> $DIR/attr-stmt-expr-attr-bad.rs:116:37
| |
LL | #[cfg(FALSE)] fn e() { { fn foo() { #[attr] } } } LL | #[cfg(FALSE)] fn e() { { fn foo() { #[attr] } } }
| ^ | ^^^^^^^
error: aborting due to 57 previous errors error: aborting due to 57 previous errors

View File

@ -3,6 +3,4 @@ fn main() {
//~^ ERROR found a documentation comment that doesn't document anything //~^ ERROR found a documentation comment that doesn't document anything
//~| HELP maybe a comment was intended //~| HELP maybe a comment was intended
; ;
//~^ WARNING unnecessary trailing semicolon
//~| HELP remove this semicolon
} }

View File

@ -6,14 +6,6 @@ LL | /// hi
| |
= help: doc comments must come before what they document, maybe a comment was intended with `//`? = help: doc comments must come before what they document, maybe a comment was intended with `//`?
warning: unnecessary trailing semicolon
--> $DIR/doc-before-semi.rs:5:5
|
LL | ;
| ^ help: remove this semicolon
|
= note: `#[warn(redundant_semicolon)]` on by default
error: aborting due to previous error error: aborting due to previous error
For more information about this error, try `rustc --explain E0585`. For more information about this error, try `rustc --explain E0585`.