diff options
Diffstat (limited to 'compiler/rustc_lint')
| -rw-r--r-- | compiler/rustc_lint/Cargo.toml | 1 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/builtin.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/context.rs | 4 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/internal.rs | 46 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/levels.rs | 70 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/nonstandard_style.rs | 58 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/panic_fmt.rs | 150 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/redundant_semicolon.rs | 21 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/types.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/unused.rs | 2 |
11 files changed, 283 insertions, 79 deletions
diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml index 760a8e385d6..c56eb09b634 100644 --- a/compiler/rustc_lint/Cargo.toml +++ b/compiler/rustc_lint/Cargo.toml @@ -20,3 +20,4 @@ rustc_feature = { path = "../rustc_feature" } rustc_index = { path = "../rustc_index" } rustc_session = { path = "../rustc_session" } rustc_trait_selection = { path = "../rustc_trait_selection" } +rustc_parse_format = { path = "../rustc_parse_format" } diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index c65cf65b1c7..676c85e4afd 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -2345,7 +2345,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { enum InitKind { Zeroed, Uninit, - }; + } /// Information about why a type cannot be initialized this way. /// Contains an error message and optionally a span to point at. diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 4cfeb0d968b..16563d21ff1 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -19,7 +19,6 @@ use self::TargetLint::*; use crate::levels::LintLevelsBuilder; use crate::passes::{EarlyLintPassObject, LateLintPassObject}; use rustc_ast as ast; -use rustc_ast::util::lev_distance::find_best_match_for_name; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync; use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err, Applicability}; @@ -37,6 +36,7 @@ use rustc_session::lint::BuiltinLintDiagnostics; use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; use rustc_session::Session; use rustc_session::SessionLintStore; +use rustc_span::lev_distance::find_best_match_for_name; use rustc_span::{symbol::Symbol, MultiSpan, Span, DUMMY_SP}; use rustc_target::abi::LayoutOf; @@ -411,7 +411,7 @@ impl LintStore { self.by_name.keys().map(|name| Symbol::intern(&name)).collect::<Vec<_>>(); let suggestion = find_best_match_for_name( - symbols.iter(), + &symbols, Symbol::intern(&lint_name.to_lowercase()), None, ); diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index c2d98b8e4ad..af5972c6c81 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -10,7 +10,7 @@ use rustc_hir::{GenericArg, HirId, MutTy, Mutability, Path, PathSegment, QPath, use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::hygiene::{ExpnKind, MacroKind}; -use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; declare_tool_lint! { pub rustc::DEFAULT_HASH_TYPES, @@ -267,3 +267,47 @@ impl EarlyLintPass for LintPassImpl { } } } + +declare_tool_lint! { + pub rustc::EXISTING_DOC_KEYWORD, + Allow, + "Check that documented keywords in std and core actually exist", + report_in_external_macro: true +} + +declare_lint_pass!(ExistingDocKeyword => [EXISTING_DOC_KEYWORD]); + +fn is_doc_keyword(s: Symbol) -> bool { + s <= kw::Union +} + +impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword { + fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { + for attr in item.attrs { + if !attr.has_name(sym::doc) { + continue; + } + if let Some(list) = attr.meta_item_list() { + for nested in list { + if nested.has_name(sym::keyword) { + let v = nested + .value_str() + .expect("#[doc(keyword = \"...\")] expected a value!"); + if is_doc_keyword(v) { + return; + } + cx.struct_span_lint(EXISTING_DOC_KEYWORD, attr.span, |lint| { + lint.build(&format!( + "Found non-existing keyword `{}` used in \ + `#[doc(keyword = \"...\")]`", + v, + )) + .help("only existing keywords are allowed in core/std") + .emit(); + }); + } + } + } + } + } +} diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 02da85d25d5..3e22eba15aa 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -108,18 +108,32 @@ impl<'s> LintLevelsBuilder<'s> { id: LintId, (level, src): LevelSource, ) { - if let Some((old_level, old_src)) = specs.get(&id) { - if old_level == &Level::Forbid && level != Level::Forbid { + // Setting to a non-forbid level is an error if the lint previously had + // a forbid level. Note that this is not necessarily true even with a + // `#[forbid(..)]` attribute present, as that is overriden by `--cap-lints`. + // + // This means that this only errors if we're truly lowering the lint + // level from forbid. + if level != Level::Forbid { + if let (Level::Forbid, old_src) = + self.sets.get_lint_level(id.lint, self.cur, Some(&specs), &self.sess) + { let mut diag_builder = struct_span_err!( self.sess, src.span(), E0453, - "{}({}) incompatible with previous forbid in same scope", + "{}({}) incompatible with previous forbid", level.as_str(), src.name(), ); - match *old_src { - LintSource::Default => {} + diag_builder.span_label(src.span(), "overruled by previous forbid"); + match old_src { + LintSource::Default => { + diag_builder.note(&format!( + "`forbid` lint level is the default for {}", + id.to_string() + )); + } LintSource::Node(_, forbid_source_span, reason) => { diag_builder.span_label(forbid_source_span, "`forbid` level set here"); if let Some(rationale) = reason { @@ -131,6 +145,8 @@ impl<'s> LintLevelsBuilder<'s> { } } diag_builder.emit(); + + // Retain the forbid lint level return; } } @@ -414,50 +430,6 @@ impl<'s> LintLevelsBuilder<'s> { } } - for (id, &(level, ref src)) in specs.iter() { - if level == Level::Forbid { - continue; - } - let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) { - (Some(Level::Forbid), src) => src, - _ => continue, - }; - let forbidden_lint_name = match forbid_src { - LintSource::Default => id.to_string(), - LintSource::Node(name, _, _) => name.to_string(), - LintSource::CommandLine(name, _) => name.to_string(), - }; - let (lint_attr_name, lint_attr_span) = match *src { - LintSource::Node(name, span, _) => (name, span), - _ => continue, - }; - let mut diag_builder = struct_span_err!( - self.sess, - lint_attr_span, - E0453, - "{}({}) overruled by outer forbid({})", - level.as_str(), - lint_attr_name, - forbidden_lint_name - ); - diag_builder.span_label(lint_attr_span, "overruled by previous forbid"); - match forbid_src { - LintSource::Default => {} - LintSource::Node(_, forbid_source_span, reason) => { - diag_builder.span_label(forbid_source_span, "`forbid` level set here"); - if let Some(rationale) = reason { - diag_builder.note(&rationale.as_str()); - } - } - LintSource::CommandLine(_, _) => { - diag_builder.note("`forbid` lint level was set on command line"); - } - } - diag_builder.emit(); - // don't set a separate error for every lint in the group - break; - } - let prev = self.cur; if !specs.is_empty() { self.cur = self.sets.list.len() as u32; diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 24bfdad970a..80ef855c385 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -55,6 +55,7 @@ mod levels; mod methods; mod non_ascii_idents; mod nonstandard_style; +mod panic_fmt; mod passes; mod redundant_semicolon; mod traits; @@ -80,6 +81,7 @@ use internal::*; use methods::*; use non_ascii_idents::*; use nonstandard_style::*; +use panic_fmt::PanicFmt; use redundant_semicolon::*; use traits::*; use types::*; @@ -166,6 +168,7 @@ macro_rules! late_lint_passes { ClashingExternDeclarations: ClashingExternDeclarations::new(), DropTraitConstraints: DropTraitConstraints, TemporaryCStringAsPtr: TemporaryCStringAsPtr, + PanicFmt: PanicFmt, ] ); }; @@ -460,6 +463,8 @@ fn register_internals(store: &mut LintStore) { store.register_early_pass(|| box DefaultHashTypes::new()); store.register_lints(&LintPassImpl::get_lints()); store.register_early_pass(|| box LintPassImpl); + store.register_lints(&ExistingDocKeyword::get_lints()); + store.register_late_pass(|| box ExistingDocKeyword); store.register_lints(&TyTyKind::get_lints()); store.register_late_pass(|| box TyTyKind); store.register_group( @@ -472,6 +477,7 @@ fn register_internals(store: &mut LintStore) { LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO), LintId::of(TY_PASS_BY_REFERENCE), LintId::of(USAGE_OF_QUALIFIED_TY), + LintId::of(EXISTING_DOC_KEYWORD), ], ); } diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index f117ce1f805..6d61b86f32e 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -94,9 +94,9 @@ fn to_camel_case(s: &str) -> String { } if new_word { - camel_cased_component.push_str(&c.to_uppercase().to_string()); + camel_cased_component.extend(c.to_uppercase()); } else { - camel_cased_component.push_str(&c.to_lowercase().to_string()); + camel_cased_component.extend(c.to_lowercase()); } prev_is_lower_case = c.is_lowercase(); @@ -127,14 +127,20 @@ impl NonCamelCaseTypes { if !is_camel_case(name) { cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| { let msg = format!("{} `{}` should have an upper camel case name", sort, name); - lint.build(&msg) - .span_suggestion( + let mut err = lint.build(&msg); + let cc = to_camel_case(name); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Lowercase Letter". + if *name != cc { + err.span_suggestion( ident.span, "convert the identifier to upper camel case", to_camel_case(name), Applicability::MaybeIncorrect, - ) - .emit() + ); + } + + err.emit(); }) } } @@ -263,17 +269,21 @@ impl NonSnakeCase { let sc = NonSnakeCase::to_snake_case(name); let msg = format!("{} `{}` should have a snake case name", sort, name); let mut err = lint.build(&msg); - // We have a valid span in almost all cases, but we don't have one when linting a crate - // name provided via the command line. - if !ident.span.is_dummy() { - err.span_suggestion( - ident.span, - "convert the identifier to snake case", - sc, - Applicability::MaybeIncorrect, - ); - } else { - err.help(&format!("convert the identifier to snake case: `{}`", sc)); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Uppercase Letter". + if *name != sc { + // We have a valid span in almost all cases, but we don't have one when linting a crate + // name provided via the command line. + if !ident.span.is_dummy() { + err.span_suggestion( + ident.span, + "convert the identifier to snake case", + sc, + Applicability::MaybeIncorrect, + ); + } else { + err.help(&format!("convert the identifier to snake case: `{}`", sc)); + } } err.emit(); @@ -441,14 +451,20 @@ impl NonUpperCaseGlobals { if name.chars().any(|c| c.is_lowercase()) { cx.struct_span_lint(NON_UPPER_CASE_GLOBALS, ident.span, |lint| { let uc = NonSnakeCase::to_snake_case(&name).to_uppercase(); - lint.build(&format!("{} `{}` should have an upper case name", sort, name)) - .span_suggestion( + let mut err = + lint.build(&format!("{} `{}` should have an upper case name", sort, name)); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Lowercase Letter". + if *name != uc { + err.span_suggestion( ident.span, "convert the identifier to upper case", uc, Applicability::MaybeIncorrect, - ) - .emit(); + ); + } + + err.emit(); }) } } diff --git a/compiler/rustc_lint/src/panic_fmt.rs b/compiler/rustc_lint/src/panic_fmt.rs new file mode 100644 index 00000000000..0d2b20989b0 --- /dev/null +++ b/compiler/rustc_lint/src/panic_fmt.rs @@ -0,0 +1,150 @@ +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_errors::{pluralize, Applicability}; +use rustc_hir as hir; +use rustc_middle::ty; +use rustc_parse_format::{ParseMode, Parser, Piece}; +use rustc_span::{sym, InnerSpan}; + +declare_lint! { + /// The `panic_fmt` lint detects `panic!("..")` with `{` or `}` in the string literal. + /// + /// ### Example + /// + /// ```rust,no_run + /// panic!("{}"); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `panic!("{}")` panics with the message `"{}"`, as a `panic!()` invocation + /// with a single argument does not use `format_args!()`. + /// A future edition of Rust will interpret this string as format string, + /// which would break this. + PANIC_FMT, + Warn, + "detect braces in single-argument panic!() invocations", + report_in_external_macro +} + +declare_lint_pass!(PanicFmt => [PANIC_FMT]); + +impl<'tcx> LateLintPass<'tcx> for PanicFmt { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Call(f, [arg]) = &expr.kind { + if let &ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(f).kind() { + if Some(def_id) == cx.tcx.lang_items().begin_panic_fn() + || Some(def_id) == cx.tcx.lang_items().panic_fn() + { + check_panic(cx, f, arg); + } + } + } + } +} + +fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Lit(lit) = &arg.kind { + if let ast::LitKind::Str(sym, _) = lit.node { + let mut expn = f.span.ctxt().outer_expn_data(); + if let Some(id) = expn.macro_def_id { + if cx.tcx.is_diagnostic_item(sym::std_panic_macro, id) + || cx.tcx.is_diagnostic_item(sym::core_panic_macro, id) + { + let fmt = sym.as_str(); + if !fmt.contains(&['{', '}'][..]) { + return; + } + + let fmt_span = arg.span.source_callsite(); + + let (snippet, style) = + match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) { + Ok(snippet) => { + // Count the number of `#`s between the `r` and `"`. + let style = snippet.strip_prefix('r').and_then(|s| s.find('"')); + (Some(snippet), style) + } + Err(_) => (None, None), + }; + + let mut fmt_parser = + Parser::new(fmt.as_ref(), style, snippet.clone(), false, ParseMode::Format); + let n_arguments = + (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count(); + + // Unwrap another level of macro expansion if this panic!() + // was expanded from assert!() or debug_assert!(). + for &assert in &[sym::assert_macro, sym::debug_assert_macro] { + let parent = expn.call_site.ctxt().outer_expn_data(); + if parent + .macro_def_id + .map_or(false, |id| cx.tcx.is_diagnostic_item(assert, id)) + { + expn = parent; + } + } + + if n_arguments > 0 && fmt_parser.errors.is_empty() { + let arg_spans: Vec<_> = match &fmt_parser.arg_places[..] { + [] => vec![fmt_span], + v => v.iter().map(|span| fmt_span.from_inner(*span)).collect(), + }; + cx.struct_span_lint(PANIC_FMT, arg_spans, |lint| { + let mut l = lint.build(match n_arguments { + 1 => "panic message contains an unused formatting placeholder", + _ => "panic message contains unused formatting placeholders", + }); + l.note("this message is not used as a format string when given without arguments, but will be in a future Rust edition"); + if expn.call_site.contains(arg.span) { + l.span_suggestion( + arg.span.shrink_to_hi(), + &format!("add the missing argument{}", pluralize!(n_arguments)), + ", ...".into(), + Applicability::HasPlaceholders, + ); + l.span_suggestion( + arg.span.shrink_to_lo(), + "or add a \"{}\" format string to use the message literally", + "\"{}\", ".into(), + Applicability::MachineApplicable, + ); + } + l.emit(); + }); + } else { + let brace_spans: Option<Vec<_>> = snippet + .filter(|s| s.starts_with('"') || s.starts_with("r#")) + .map(|s| { + s.char_indices() + .filter(|&(_, c)| c == '{' || c == '}') + .map(|(i, _)| { + fmt_span.from_inner(InnerSpan { start: i, end: i + 1 }) + }) + .collect() + }); + let msg = match &brace_spans { + Some(v) if v.len() == 1 => "panic message contains a brace", + _ => "panic message contains braces", + }; + cx.struct_span_lint(PANIC_FMT, brace_spans.unwrap_or(vec![expn.call_site]), |lint| { + let mut l = lint.build(msg); + l.note("this message is not used as a format string, but will be in a future Rust edition"); + if expn.call_site.contains(arg.span) { + l.span_suggestion( + arg.span.shrink_to_lo(), + "add a \"{}\" format string to use the message literally", + "\"{}\", ".into(), + Applicability::MachineApplicable, + ); + } + l.emit(); + }); + } + } + } + } + } +} diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs index 84cc7b68d4c..428198cae89 100644 --- a/compiler/rustc_lint/src/redundant_semicolon.rs +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -28,25 +28,40 @@ declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]); impl EarlyLintPass for RedundantSemicolons { fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) { + let mut after_item_stmt = false; let mut seq = None; for stmt in block.stmts.iter() { match (&stmt.kind, &mut seq) { (StmtKind::Empty, None) => seq = Some((stmt.span, false)), (StmtKind::Empty, Some(seq)) => *seq = (seq.0.to(stmt.span), true), - (_, seq) => maybe_lint_redundant_semis(cx, seq), + (_, seq) => { + maybe_lint_redundant_semis(cx, seq, after_item_stmt); + after_item_stmt = matches!(stmt.kind, StmtKind::Item(_)); + } } } - maybe_lint_redundant_semis(cx, &mut seq); + maybe_lint_redundant_semis(cx, &mut seq, after_item_stmt); } } -fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) { +fn maybe_lint_redundant_semis( + cx: &EarlyContext<'_>, + seq: &mut Option<(Span, bool)>, + after_item_stmt: bool, +) { if let Some((span, multiple)) = seq.take() { // FIXME: Find a better way of ignoring the trailing // semicolon from macro expansion if span == rustc_span::DUMMY_SP { return; } + + // FIXME: Lint on semicolons after item statements + // once doing so doesn't break bootstrapping + if after_item_stmt { + return; + } + cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| { let (msg, rem) = if multiple { ("unnecessary trailing semicolons", "remove these semicolons") diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 38c71e6e925..9ad9d53cd0d 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -1131,7 +1131,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { struct ProhibitOpaqueTypes<'a, 'tcx> { cx: &'a LateContext<'tcx>, - }; + } impl<'a, 'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> { type BreakTy = Ty<'tcx>; diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 4bbc180b226..2a5ad5e6c98 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1152,7 +1152,7 @@ declare_lint! { /// ```rust /// #![feature(box_syntax)] /// fn main() { - /// let a = (box [1,2,3]).len(); + /// let a = (box [1, 2, 3]).len(); /// } /// ``` /// |
