summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_lint/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src')
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs155
-rw-r--r--compiler/rustc_lint/src/builtin.rs3172
-rw-r--r--compiler/rustc_lint/src/context.rs1259
-rw-r--r--compiler/rustc_lint/src/early.rs456
-rw-r--r--compiler/rustc_lint/src/enum_intrinsics_non_enums.rs88
-rw-r--r--compiler/rustc_lint/src/expect.rs59
-rw-r--r--compiler/rustc_lint/src/hidden_unicode_codepoints.rs141
-rw-r--r--compiler/rustc_lint/src/internal.rs469
-rw-r--r--compiler/rustc_lint/src/late.rs482
-rw-r--r--compiler/rustc_lint/src/levels.rs813
-rw-r--r--compiler/rustc_lint/src/lib.rs538
-rw-r--r--compiler/rustc_lint/src/methods.rs103
-rw-r--r--compiler/rustc_lint/src/non_ascii_idents.rs345
-rw-r--r--compiler/rustc_lint/src/non_fmt_panic.rs357
-rw-r--r--compiler/rustc_lint/src/nonstandard_style.rs565
-rw-r--r--compiler/rustc_lint/src/nonstandard_style/tests.rs21
-rw-r--r--compiler/rustc_lint/src/noop_method_call.rs103
-rw-r--r--compiler/rustc_lint/src/pass_by_value.rs96
-rw-r--r--compiler/rustc_lint/src/passes.rs249
-rw-r--r--compiler/rustc_lint/src/redundant_semicolon.rs58
-rw-r--r--compiler/rustc_lint/src/tests.rs26
-rw-r--r--compiler/rustc_lint/src/traits.rs134
-rw-r--r--compiler/rustc_lint/src/types.rs1576
-rw-r--r--compiler/rustc_lint/src/unused.rs1197
24 files changed, 12462 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
new file mode 100644
index 000000000..121fefdc6
--- /dev/null
+++ b/compiler/rustc_lint/src/array_into_iter.rs
@@ -0,0 +1,155 @@
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_errors::{fluent, Applicability};
+use rustc_hir as hir;
+use rustc_middle::ty;
+use rustc_middle::ty::adjustment::{Adjust, Adjustment};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_span::edition::Edition;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+declare_lint! {
+ /// The `array_into_iter` lint detects calling `into_iter` on arrays.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,edition2018
+ /// # #![allow(unused)]
+ /// [1, 2, 3].into_iter().for_each(|n| { *n; });
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
+ /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
+ /// behave as `(&array).into_iter()`, returning an iterator over
+ /// references, just like in Rust 1.52 and earlier.
+ /// This only applies to the method call syntax `array.into_iter()`, not to
+ /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
+ pub ARRAY_INTO_ITER,
+ Warn,
+ "detects calling `into_iter` on arrays in Rust 2015 and 2018",
+ @future_incompatible = FutureIncompatibleInfo {
+ reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
+ reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
+ };
+}
+
+#[derive(Copy, Clone, Default)]
+pub struct ArrayIntoIter {
+ for_expr_span: Span,
+}
+
+impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]);
+
+impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+ // Save the span of expressions in `for _ in expr` syntax,
+ // so we can give a better suggestion for those later.
+ if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind {
+ if let hir::ExprKind::Call(path, [arg]) = &arg.kind {
+ if let hir::ExprKind::Path(hir::QPath::LangItem(
+ hir::LangItem::IntoIterIntoIter,
+ ..,
+ )) = &path.kind
+ {
+ self.for_expr_span = arg.span;
+ }
+ }
+ }
+
+ // We only care about method call expressions.
+ if let hir::ExprKind::MethodCall(call, args, _) = &expr.kind {
+ if call.ident.name != sym::into_iter {
+ return;
+ }
+
+ // Check if the method call actually calls the libcore
+ // `IntoIterator::into_iter`.
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
+ match cx.tcx.trait_of_item(def_id) {
+ Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {}
+ _ => return,
+ };
+
+ // As this is a method call expression, we have at least one argument.
+ let receiver_arg = &args[0];
+ let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
+ let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
+
+ let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else {
+ return
+ };
+
+ let types =
+ std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target));
+
+ let mut found_array = false;
+
+ for ty in types {
+ match ty.kind() {
+ // If we run into a &[T; N] or &[T] first, there's nothing to warn about.
+ // It'll resolve to the reference version.
+ ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return,
+ ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return,
+ // Found an actual array type without matching a &[T; N] first.
+ // This is the problematic case.
+ ty::Array(..) => {
+ found_array = true;
+ break;
+ }
+ _ => {}
+ }
+ }
+
+ if !found_array {
+ return;
+ }
+
+ // Emit lint diagnostic.
+ let target = match *target.kind() {
+ ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]",
+ ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]",
+ // We know the original first argument type is an array type,
+ // we know that the first adjustment was an autoref coercion
+ // and we know that `IntoIterator` is the trait involved. The
+ // array cannot be coerced to something other than a reference
+ // to an array or to a slice.
+ _ => bug!("array type coerced to something other than array or slice"),
+ };
+ cx.struct_span_lint(ARRAY_INTO_ITER, call.ident.span, |lint| {
+ let mut diag = lint.build(fluent::lint::array_into_iter);
+ diag.set_arg("target", target);
+ diag.span_suggestion(
+ call.ident.span,
+ fluent::lint::use_iter_suggestion,
+ "iter",
+ Applicability::MachineApplicable,
+ );
+ if self.for_expr_span == expr.span {
+ diag.span_suggestion(
+ receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+ fluent::lint::remove_into_iter_suggestion,
+ "",
+ Applicability::MaybeIncorrect,
+ );
+ } else if receiver_ty.is_array() {
+ diag.multipart_suggestion(
+ fluent::lint::use_explicit_into_iter_suggestion,
+ vec![
+ (expr.span.shrink_to_lo(), "IntoIterator::into_iter(".into()),
+ (
+ receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+ ")".into(),
+ ),
+ ],
+ Applicability::MaybeIncorrect,
+ );
+ }
+ diag.emit();
+ })
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs
new file mode 100644
index 000000000..bd58021f7
--- /dev/null
+++ b/compiler/rustc_lint/src/builtin.rs
@@ -0,0 +1,3172 @@
+//! Lints in the Rust compiler.
+//!
+//! This contains lints which can feasibly be implemented as their own
+//! AST visitor. Also see `rustc_session::lint::builtin`, which contains the
+//! definitions of lints that are emitted directly inside the main compiler.
+//!
+//! To add a new lint to rustc, declare it here using `declare_lint!()`.
+//! Then add code to emit the new lint in the appropriate circumstances.
+//! You can do that in an existing `LintPass` if it makes sense, or in a
+//! new `LintPass`, or using `Session::add_lint` elsewhere in the
+//! compiler. Only do the latter if the check can't be written cleanly as a
+//! `LintPass` (also, note that such lints will need to be defined in
+//! `rustc_session::lint::builtin`, not here).
+//!
+//! If you define a new `EarlyLintPass`, you will also need to add it to the
+//! `add_early_builtin!` or `add_early_builtin_with_new!` invocation in
+//! `lib.rs`. Use the former for unit-like structs and the latter for structs
+//! with a `pub fn new()`.
+//!
+//! If you define a new `LateLintPass`, you will also need to add it to the
+//! `late_lint_methods!` invocation in `lib.rs`.
+
+use crate::{
+ types::{transparent_newtype_field, CItemKind},
+ EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext,
+};
+use rustc_ast::attr;
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_ast::visit::{FnCtxt, FnKind};
+use rustc_ast::{self as ast, *};
+use rustc_ast_pretty::pprust::{self, expr_to_string};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::stack::ensure_sufficient_stack;
+use rustc_errors::{
+ fluent, Applicability, Diagnostic, DiagnosticMessage, DiagnosticStyledString,
+ LintDiagnosticBuilder, MultiSpan,
+};
+use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdSet, CRATE_DEF_ID};
+use rustc_hir::{ForeignItemKind, GenericParamKind, HirId, PatKind, PredicateOrigin};
+use rustc_index::vec::Idx;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty::layout::{LayoutError, LayoutOf};
+use rustc_middle::ty::print::with_no_trimmed_paths;
+use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::Instance;
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
+use rustc_span::edition::Edition;
+use rustc_span::source_map::Spanned;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{BytePos, InnerSpan, Span};
+use rustc_target::abi::VariantIdx;
+use rustc_trait_selection::traits::{self, misc::can_type_implement_copy};
+
+use crate::nonstandard_style::{method_context, MethodLateContext};
+
+use std::fmt::Write;
+use tracing::{debug, trace};
+
+// hardwired lints from librustc_middle
+pub use rustc_session::lint::builtin::*;
+
+declare_lint! {
+ /// The `while_true` lint detects `while true { }`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// while true {
+ ///
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// `while true` should be replaced with `loop`. A `loop` expression is
+ /// the preferred way to write an infinite loop because it more directly
+ /// expresses the intent of the loop.
+ WHILE_TRUE,
+ Warn,
+ "suggest using `loop { }` instead of `while true { }`"
+}
+
+declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
+
+/// Traverse through any amount of parenthesis and return the first non-parens expression.
+fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
+ while let ast::ExprKind::Paren(sub) = &expr.kind {
+ expr = sub;
+ }
+ expr
+}
+
+impl EarlyLintPass for WhileTrue {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ if let ast::ExprKind::While(cond, _, label) = &e.kind {
+ if let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind {
+ if let ast::LitKind::Bool(true) = lit.kind {
+ if !lit.span.from_expansion() {
+ let condition_span = e.span.with_hi(cond.span.hi());
+ cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
+ lint.build(fluent::lint::builtin_while_true)
+ .span_suggestion_short(
+ condition_span,
+ fluent::lint::suggestion,
+ format!(
+ "{}loop",
+ label.map_or_else(String::new, |label| format!(
+ "{}: ",
+ label.ident,
+ ))
+ ),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `box_pointers` lints use of the Box type.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(box_pointers)]
+ /// struct Foo {
+ /// x: Box<isize>,
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint is mostly historical, and not particularly useful. `Box<T>`
+ /// used to be built into the language, and the only way to do heap
+ /// allocation. Today's Rust can call into other allocators, etc.
+ BOX_POINTERS,
+ Allow,
+ "use of owned (Box type) heap memory"
+}
+
+declare_lint_pass!(BoxPointers => [BOX_POINTERS]);
+
+impl BoxPointers {
+ fn check_heap_type(&self, cx: &LateContext<'_>, span: Span, ty: Ty<'_>) {
+ for leaf in ty.walk() {
+ if let GenericArgKind::Type(leaf_ty) = leaf.unpack() {
+ if leaf_ty.is_box() {
+ cx.struct_span_lint(BOX_POINTERS, span, |lint| {
+ lint.build(fluent::lint::builtin_box_pointers).set_arg("ty", ty).emit();
+ });
+ }
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for BoxPointers {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ match it.kind {
+ hir::ItemKind::Fn(..)
+ | hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::Union(..) => {
+ self.check_heap_type(cx, it.span, cx.tcx.type_of(it.def_id))
+ }
+ _ => (),
+ }
+
+ // If it's a struct, we also have to check the fields' types
+ match it.kind {
+ hir::ItemKind::Struct(ref struct_def, _) | hir::ItemKind::Union(ref struct_def, _) => {
+ for struct_field in struct_def.fields() {
+ let def_id = cx.tcx.hir().local_def_id(struct_field.hir_id);
+ self.check_heap_type(cx, struct_field.span, cx.tcx.type_of(def_id));
+ }
+ }
+ _ => (),
+ }
+ }
+
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+ let ty = cx.typeck_results().node_type(e.hir_id);
+ self.check_heap_type(cx, e.span, ty);
+ }
+}
+
+declare_lint! {
+ /// The `non_shorthand_field_patterns` lint detects using `Struct { x: x }`
+ /// instead of `Struct { x }` in a pattern.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// struct Point {
+ /// x: i32,
+ /// y: i32,
+ /// }
+ ///
+ ///
+ /// fn main() {
+ /// let p = Point {
+ /// x: 5,
+ /// y: 5,
+ /// };
+ ///
+ /// match p {
+ /// Point { x: x, y: y } => (),
+ /// }
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The preferred style is to avoid the repetition of specifying both the
+ /// field name and the binding name if both identifiers are the same.
+ NON_SHORTHAND_FIELD_PATTERNS,
+ Warn,
+ "using `Struct { x: x }` instead of `Struct { x }` in a pattern"
+}
+
+declare_lint_pass!(NonShorthandFieldPatterns => [NON_SHORTHAND_FIELD_PATTERNS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns {
+ fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) {
+ if let PatKind::Struct(ref qpath, field_pats, _) = pat.kind {
+ let variant = cx
+ .typeck_results()
+ .pat_ty(pat)
+ .ty_adt_def()
+ .expect("struct pattern type is not an ADT")
+ .variant_of_res(cx.qpath_res(qpath, pat.hir_id));
+ for fieldpat in field_pats {
+ if fieldpat.is_shorthand {
+ continue;
+ }
+ if fieldpat.span.from_expansion() {
+ // Don't lint if this is a macro expansion: macro authors
+ // shouldn't have to worry about this kind of style issue
+ // (Issue #49588)
+ continue;
+ }
+ if let PatKind::Binding(binding_annot, _, ident, None) = fieldpat.pat.kind {
+ if cx.tcx.find_field_index(ident, &variant)
+ == Some(cx.tcx.field_index(fieldpat.hir_id, cx.typeck_results()))
+ {
+ cx.struct_span_lint(NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, |lint| {
+ let binding = match binding_annot {
+ hir::BindingAnnotation::Unannotated => None,
+ hir::BindingAnnotation::Mutable => Some("mut"),
+ hir::BindingAnnotation::Ref => Some("ref"),
+ hir::BindingAnnotation::RefMut => Some("ref mut"),
+ };
+ let suggested_ident = if let Some(binding) = binding {
+ format!("{} {}", binding, ident)
+ } else {
+ ident.to_string()
+ };
+ lint.build(fluent::lint::builtin_non_shorthand_field_patterns)
+ .set_arg("ident", ident.clone())
+ .span_suggestion(
+ fieldpat.span,
+ fluent::lint::suggestion,
+ suggested_ident,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ }
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unsafe_code` lint catches usage of `unsafe` code.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(unsafe_code)]
+ /// fn main() {
+ /// unsafe {
+ ///
+ /// }
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint is intended to restrict the usage of `unsafe`, which can be
+ /// difficult to use correctly.
+ UNSAFE_CODE,
+ Allow,
+ "usage of `unsafe` code"
+}
+
+declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]);
+
+impl UnsafeCode {
+ fn report_unsafe(
+ &self,
+ cx: &EarlyContext<'_>,
+ span: Span,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ // This comes from a macro that has `#[allow_internal_unsafe]`.
+ if span.allows_unsafe() {
+ return;
+ }
+
+ cx.struct_span_lint(UNSAFE_CODE, span, decorate);
+ }
+
+ fn report_overridden_symbol_name(
+ &self,
+ cx: &EarlyContext<'_>,
+ span: Span,
+ msg: DiagnosticMessage,
+ ) {
+ self.report_unsafe(cx, span, |lint| {
+ lint.build(msg).note(fluent::lint::builtin_overridden_symbol_name).emit();
+ })
+ }
+
+ fn report_overridden_symbol_section(
+ &self,
+ cx: &EarlyContext<'_>,
+ span: Span,
+ msg: DiagnosticMessage,
+ ) {
+ self.report_unsafe(cx, span, |lint| {
+ lint.build(msg).note(fluent::lint::builtin_overridden_symbol_section).emit();
+ })
+ }
+}
+
+impl EarlyLintPass for UnsafeCode {
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+ if attr.has_name(sym::allow_internal_unsafe) {
+ self.report_unsafe(cx, attr.span, |lint| {
+ lint.build(fluent::lint::builtin_allow_internal_unsafe).emit();
+ });
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ if let ast::ExprKind::Block(ref blk, _) = e.kind {
+ // Don't warn about generated blocks; that'll just pollute the output.
+ if blk.rules == ast::BlockCheckMode::Unsafe(ast::UserProvided) {
+ self.report_unsafe(cx, blk.span, |lint| {
+ lint.build(fluent::lint::builtin_unsafe_block).emit();
+ });
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+ match it.kind {
+ ast::ItemKind::Trait(box ast::Trait { unsafety: ast::Unsafe::Yes(_), .. }) => self
+ .report_unsafe(cx, it.span, |lint| {
+ lint.build(fluent::lint::builtin_unsafe_trait).emit();
+ }),
+
+ ast::ItemKind::Impl(box ast::Impl { unsafety: ast::Unsafe::Yes(_), .. }) => self
+ .report_unsafe(cx, it.span, |lint| {
+ lint.build(fluent::lint::builtin_unsafe_impl).emit();
+ }),
+
+ ast::ItemKind::Fn(..) => {
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_no_mangle_fn,
+ );
+ }
+
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_export_name_fn,
+ );
+ }
+
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::link_section) {
+ self.report_overridden_symbol_section(
+ cx,
+ attr.span,
+ fluent::lint::builtin_link_section_fn,
+ );
+ }
+ }
+
+ ast::ItemKind::Static(..) => {
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_no_mangle_static,
+ );
+ }
+
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_export_name_static,
+ );
+ }
+
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::link_section) {
+ self.report_overridden_symbol_section(
+ cx,
+ attr.span,
+ fluent::lint::builtin_link_section_static,
+ );
+ }
+ }
+
+ _ => {}
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+ if let ast::AssocItemKind::Fn(..) = it.kind {
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::no_mangle) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_no_mangle_method,
+ );
+ }
+ if let Some(attr) = cx.sess().find_by_name(&it.attrs, sym::export_name) {
+ self.report_overridden_symbol_name(
+ cx,
+ attr.span,
+ fluent::lint::builtin_export_name_method,
+ );
+ }
+ }
+ }
+
+ fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) {
+ if let FnKind::Fn(
+ ctxt,
+ _,
+ ast::FnSig { header: ast::FnHeader { unsafety: ast::Unsafe::Yes(_), .. }, .. },
+ _,
+ _,
+ body,
+ ) = fk
+ {
+ let msg = match ctxt {
+ FnCtxt::Foreign => return,
+ FnCtxt::Free => fluent::lint::builtin_decl_unsafe_fn,
+ FnCtxt::Assoc(_) if body.is_none() => fluent::lint::builtin_decl_unsafe_method,
+ FnCtxt::Assoc(_) => fluent::lint::builtin_impl_unsafe_method,
+ };
+ self.report_unsafe(cx, span, |lint| {
+ lint.build(msg).emit();
+ });
+ }
+ }
+}
+
+declare_lint! {
+ /// The `missing_docs` lint detects missing documentation for public items.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(missing_docs)]
+ /// pub fn foo() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint is intended to ensure that a library is well-documented.
+ /// Items without documentation can be difficult for users to understand
+ /// how to use properly.
+ ///
+ /// This lint is "allow" by default because it can be noisy, and not all
+ /// projects may want to enforce everything to be documented.
+ pub MISSING_DOCS,
+ Allow,
+ "detects missing documentation for public members",
+ report_in_external_macro
+}
+
+pub struct MissingDoc {
+ /// Stack of whether `#[doc(hidden)]` is set at each level which has lint attributes.
+ doc_hidden_stack: Vec<bool>,
+}
+
+impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
+
+fn has_doc(attr: &ast::Attribute) -> bool {
+ if attr.is_doc_comment() {
+ return true;
+ }
+
+ if !attr.has_name(sym::doc) {
+ return false;
+ }
+
+ if attr.value_str().is_some() {
+ return true;
+ }
+
+ if let Some(list) = attr.meta_item_list() {
+ for meta in list {
+ if meta.has_name(sym::hidden) {
+ return true;
+ }
+ }
+ }
+
+ false
+}
+
+impl MissingDoc {
+ pub fn new() -> MissingDoc {
+ MissingDoc { doc_hidden_stack: vec![false] }
+ }
+
+ fn doc_hidden(&self) -> bool {
+ *self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
+ }
+
+ fn check_missing_docs_attrs(
+ &self,
+ cx: &LateContext<'_>,
+ def_id: LocalDefId,
+ article: &'static str,
+ desc: &'static str,
+ ) {
+ // If we're building a test harness, then warning about
+ // documentation is probably not really relevant right now.
+ if cx.sess().opts.test {
+ return;
+ }
+
+ // `#[doc(hidden)]` disables missing_docs check.
+ if self.doc_hidden() {
+ return;
+ }
+
+ // Only check publicly-visible items, using the result from the privacy pass.
+ // It's an option so the crate root can also use this function (it doesn't
+ // have a `NodeId`).
+ if def_id != CRATE_DEF_ID {
+ if !cx.access_levels.is_exported(def_id) {
+ return;
+ }
+ }
+
+ let attrs = cx.tcx.hir().attrs(cx.tcx.hir().local_def_id_to_hir_id(def_id));
+ let has_doc = attrs.iter().any(has_doc);
+ if !has_doc {
+ cx.struct_span_lint(MISSING_DOCS, cx.tcx.def_span(def_id), |lint| {
+ lint.build(fluent::lint::builtin_missing_doc)
+ .set_arg("article", article)
+ .set_arg("desc", desc)
+ .emit();
+ });
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for MissingDoc {
+ fn enter_lint_attrs(&mut self, _cx: &LateContext<'_>, attrs: &[ast::Attribute]) {
+ let doc_hidden = self.doc_hidden()
+ || attrs.iter().any(|attr| {
+ attr.has_name(sym::doc)
+ && match attr.meta_item_list() {
+ None => false,
+ Some(l) => attr::list_contains_name(&l, sym::hidden),
+ }
+ });
+ self.doc_hidden_stack.push(doc_hidden);
+ }
+
+ fn exit_lint_attrs(&mut self, _: &LateContext<'_>, _attrs: &[ast::Attribute]) {
+ self.doc_hidden_stack.pop().expect("empty doc_hidden_stack");
+ }
+
+ fn check_crate(&mut self, cx: &LateContext<'_>) {
+ self.check_missing_docs_attrs(cx, CRATE_DEF_ID, "the", "crate");
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ match it.kind {
+ hir::ItemKind::Trait(..) => {
+ // Issue #11592: traits are always considered exported, even when private.
+ if cx.tcx.visibility(it.def_id)
+ == ty::Visibility::Restricted(
+ cx.tcx.parent_module_from_def_id(it.def_id).to_def_id(),
+ )
+ {
+ return;
+ }
+ }
+ hir::ItemKind::TyAlias(..)
+ | hir::ItemKind::Fn(..)
+ | hir::ItemKind::Macro(..)
+ | hir::ItemKind::Mod(..)
+ | hir::ItemKind::Enum(..)
+ | hir::ItemKind::Struct(..)
+ | hir::ItemKind::Union(..)
+ | hir::ItemKind::Const(..)
+ | hir::ItemKind::Static(..) => {}
+
+ _ => return,
+ };
+
+ let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
+
+ self.check_missing_docs_attrs(cx, it.def_id, article, desc);
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, trait_item: &hir::TraitItem<'_>) {
+ let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
+
+ self.check_missing_docs_attrs(cx, trait_item.def_id, article, desc);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+ // If the method is an impl for a trait, don't doc.
+ if method_context(cx, impl_item.hir_id()) == MethodLateContext::TraitImpl {
+ return;
+ }
+
+ // If the method is an impl for an item with docs_hidden, don't doc.
+ if method_context(cx, impl_item.hir_id()) == MethodLateContext::PlainImpl {
+ let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id());
+ let impl_ty = cx.tcx.type_of(parent);
+ let outerdef = match impl_ty.kind() {
+ ty::Adt(def, _) => Some(def.did()),
+ ty::Foreign(def_id) => Some(*def_id),
+ _ => None,
+ };
+ let is_hidden = match outerdef {
+ Some(id) => cx.tcx.is_doc_hidden(id),
+ None => false,
+ };
+ if is_hidden {
+ return;
+ }
+ }
+
+ let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
+ self.check_missing_docs_attrs(cx, impl_item.def_id, article, desc);
+ }
+
+ fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'_>) {
+ let (article, desc) = cx.tcx.article_and_description(foreign_item.def_id.to_def_id());
+ self.check_missing_docs_attrs(cx, foreign_item.def_id, article, desc);
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'_>, sf: &hir::FieldDef<'_>) {
+ if !sf.is_positional() {
+ let def_id = cx.tcx.hir().local_def_id(sf.hir_id);
+ self.check_missing_docs_attrs(cx, def_id, "a", "struct field")
+ }
+ }
+
+ fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) {
+ self.check_missing_docs_attrs(cx, cx.tcx.hir().local_def_id(v.id), "a", "variant");
+ }
+}
+
+declare_lint! {
+ /// The `missing_copy_implementations` lint detects potentially-forgotten
+ /// implementations of [`Copy`].
+ ///
+ /// [`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(missing_copy_implementations)]
+ /// pub struct Foo {
+ /// pub field: i32
+ /// }
+ /// # fn main() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Historically (before 1.0), types were automatically marked as `Copy`
+ /// if possible. This was changed so that it required an explicit opt-in
+ /// by implementing the `Copy` trait. As part of this change, a lint was
+ /// added to alert if a copyable type was not marked `Copy`.
+ ///
+ /// This lint is "allow" by default because this code isn't bad; it is
+ /// common to write newtypes like this specifically so that a `Copy` type
+ /// is no longer `Copy`. `Copy` types can result in unintended copies of
+ /// large data which can impact performance.
+ pub MISSING_COPY_IMPLEMENTATIONS,
+ Allow,
+ "detects potentially-forgotten implementations of `Copy`"
+}
+
+declare_lint_pass!(MissingCopyImplementations => [MISSING_COPY_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if !cx.access_levels.is_reachable(item.def_id) {
+ return;
+ }
+ let (def, ty) = match item.kind {
+ hir::ItemKind::Struct(_, ref ast_generics) => {
+ if !ast_generics.params.is_empty() {
+ return;
+ }
+ let def = cx.tcx.adt_def(item.def_id);
+ (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+ }
+ hir::ItemKind::Union(_, ref ast_generics) => {
+ if !ast_generics.params.is_empty() {
+ return;
+ }
+ let def = cx.tcx.adt_def(item.def_id);
+ (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+ }
+ hir::ItemKind::Enum(_, ref ast_generics) => {
+ if !ast_generics.params.is_empty() {
+ return;
+ }
+ let def = cx.tcx.adt_def(item.def_id);
+ (def, cx.tcx.mk_adt(def, cx.tcx.intern_substs(&[])))
+ }
+ _ => return,
+ };
+ if def.has_dtor(cx.tcx) {
+ return;
+ }
+ let param_env = ty::ParamEnv::empty();
+ if ty.is_copy_modulo_regions(cx.tcx.at(item.span), param_env) {
+ return;
+ }
+ if can_type_implement_copy(
+ cx.tcx,
+ param_env,
+ ty,
+ traits::ObligationCause::misc(item.span, item.hir_id()),
+ )
+ .is_ok()
+ {
+ cx.struct_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, |lint| {
+ lint.build(fluent::lint::builtin_missing_copy_impl).emit();
+ })
+ }
+ }
+}
+
+declare_lint! {
+ /// The `missing_debug_implementations` lint detects missing
+ /// implementations of [`fmt::Debug`].
+ ///
+ /// [`fmt::Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(missing_debug_implementations)]
+ /// pub struct Foo;
+ /// # fn main() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Having a `Debug` implementation on all types can assist with
+ /// debugging, as it provides a convenient way to format and display a
+ /// value. Using the `#[derive(Debug)]` attribute will automatically
+ /// generate a typical implementation, or a custom implementation can be
+ /// added by manually implementing the `Debug` trait.
+ ///
+ /// This lint is "allow" by default because adding `Debug` to all types can
+ /// have a negative impact on compile time and code size. It also requires
+ /// boilerplate to be added to every type, which can be an impediment.
+ MISSING_DEBUG_IMPLEMENTATIONS,
+ Allow,
+ "detects missing implementations of Debug"
+}
+
+#[derive(Default)]
+pub struct MissingDebugImplementations {
+ impling_types: Option<LocalDefIdSet>,
+}
+
+impl_lint_pass!(MissingDebugImplementations => [MISSING_DEBUG_IMPLEMENTATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ if !cx.access_levels.is_reachable(item.def_id) {
+ return;
+ }
+
+ match item.kind {
+ hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {}
+ _ => return,
+ }
+
+ let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else {
+ return
+ };
+
+ if self.impling_types.is_none() {
+ let mut impls = LocalDefIdSet::default();
+ cx.tcx.for_each_impl(debug, |d| {
+ if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
+ if let Some(def_id) = ty_def.did().as_local() {
+ impls.insert(def_id);
+ }
+ }
+ });
+
+ self.impling_types = Some(impls);
+ debug!("{:?}", self.impling_types);
+ }
+
+ if !self.impling_types.as_ref().unwrap().contains(&item.def_id) {
+ cx.struct_span_lint(MISSING_DEBUG_IMPLEMENTATIONS, item.span, |lint| {
+ lint.build(fluent::lint::builtin_missing_debug_impl)
+ .set_arg("debug", cx.tcx.def_path_str(debug))
+ .emit();
+ });
+ }
+ }
+}
+
+declare_lint! {
+ /// The `anonymous_parameters` lint detects anonymous parameters in trait
+ /// definitions.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,edition2015,compile_fail
+ /// #![deny(anonymous_parameters)]
+ /// // edition 2015
+ /// pub trait Foo {
+ /// fn foo(usize);
+ /// }
+ /// fn main() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This syntax is mostly a historical accident, and can be worked around
+ /// quite easily by adding an `_` pattern or a descriptive identifier:
+ ///
+ /// ```rust
+ /// trait Foo {
+ /// fn foo(_: usize);
+ /// }
+ /// ```
+ ///
+ /// This syntax is now a hard error in the 2018 edition. In the 2015
+ /// edition, this lint is "warn" by default. This lint
+ /// enables the [`cargo fix`] tool with the `--edition` flag to
+ /// automatically transition old code from the 2015 edition to 2018. The
+ /// tool will run this lint and automatically apply the
+ /// suggested fix from the compiler (which is to add `_` to each
+ /// parameter). This provides a completely automated way to update old
+ /// code for a new edition. See [issue #41686] for more details.
+ ///
+ /// [issue #41686]: https://github.com/rust-lang/rust/issues/41686
+ /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+ pub ANONYMOUS_PARAMETERS,
+ Warn,
+ "detects anonymous parameters",
+ @future_incompatible = FutureIncompatibleInfo {
+ reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
+ reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+ };
+}
+
+declare_lint_pass!(
+ /// Checks for use of anonymous parameters (RFC 1685).
+ AnonymousParameters => [ANONYMOUS_PARAMETERS]
+);
+
+impl EarlyLintPass for AnonymousParameters {
+ fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+ if cx.sess().edition() != Edition::Edition2015 {
+ // This is a hard error in future editions; avoid linting and erroring
+ return;
+ }
+ if let ast::AssocItemKind::Fn(box Fn { ref sig, .. }) = it.kind {
+ for arg in sig.decl.inputs.iter() {
+ if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind {
+ if ident.name == kw::Empty {
+ cx.struct_span_lint(ANONYMOUS_PARAMETERS, arg.pat.span, |lint| {
+ let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span);
+
+ let (ty_snip, appl) = if let Ok(ref snip) = ty_snip {
+ (snip.as_str(), Applicability::MachineApplicable)
+ } else {
+ ("<type>", Applicability::HasPlaceholders)
+ };
+
+ lint.build(fluent::lint::builtin_anonymous_params)
+ .span_suggestion(
+ arg.pat.span,
+ fluent::lint::suggestion,
+ format!("_: {}", ty_snip),
+ appl,
+ )
+ .emit();
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Check for use of attributes which have been deprecated.
+#[derive(Clone)]
+pub struct DeprecatedAttr {
+ // This is not free to compute, so we want to keep it around, rather than
+ // compute it for every attribute.
+ depr_attrs: Vec<&'static BuiltinAttribute>,
+}
+
+impl_lint_pass!(DeprecatedAttr => []);
+
+impl DeprecatedAttr {
+ pub fn new() -> DeprecatedAttr {
+ DeprecatedAttr { depr_attrs: deprecated_attributes() }
+ }
+}
+
+impl EarlyLintPass for DeprecatedAttr {
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+ for BuiltinAttribute { name, gate, .. } in &self.depr_attrs {
+ if attr.ident().map(|ident| ident.name) == Some(*name) {
+ if let &AttributeGate::Gated(
+ Stability::Deprecated(link, suggestion),
+ name,
+ reason,
+ _,
+ ) = gate
+ {
+ cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
+ // FIXME(davidtwco) translatable deprecated attr
+ lint.build(fluent::lint::builtin_deprecated_attr_link)
+ .set_arg("name", name)
+ .set_arg("reason", reason)
+ .set_arg("link", link)
+ .span_suggestion_short(
+ attr.span,
+ suggestion.map(|s| s.into()).unwrap_or(
+ fluent::lint::builtin_deprecated_attr_default_suggestion,
+ ),
+ "",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ return;
+ }
+ }
+ if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) {
+ cx.struct_span_lint(DEPRECATED, attr.span, |lint| {
+ lint.build(fluent::lint::builtin_deprecated_attr_used)
+ .set_arg("name", pprust::path_to_string(&attr.get_normal_item().path))
+ .span_suggestion_short(
+ attr.span,
+ fluent::lint::builtin_deprecated_attr_default_suggestion,
+ "",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ }
+}
+
+fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &[ast::Attribute]) {
+ use rustc_ast::token::CommentKind;
+
+ let mut attrs = attrs.iter().peekable();
+
+ // Accumulate a single span for sugared doc comments.
+ let mut sugared_span: Option<Span> = None;
+
+ while let Some(attr) = attrs.next() {
+ let is_doc_comment = attr.is_doc_comment();
+ if is_doc_comment {
+ sugared_span =
+ Some(sugared_span.map_or(attr.span, |span| span.with_hi(attr.span.hi())));
+ }
+
+ if attrs.peek().map_or(false, |next_attr| next_attr.is_doc_comment()) {
+ continue;
+ }
+
+ let span = sugared_span.take().unwrap_or(attr.span);
+
+ if is_doc_comment || attr.has_name(sym::doc) {
+ cx.struct_span_lint(UNUSED_DOC_COMMENTS, span, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_unused_doc_comment);
+ err.set_arg("kind", node_kind);
+ err.span_label(node_span, fluent::lint::label);
+ match attr.kind {
+ AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
+ err.help(fluent::lint::plain_help);
+ }
+ AttrKind::DocComment(CommentKind::Block, _) => {
+ err.help(fluent::lint::block_help);
+ }
+ }
+ err.emit();
+ });
+ }
+ }
+}
+
+impl EarlyLintPass for UnusedDocComment {
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
+ let kind = match stmt.kind {
+ ast::StmtKind::Local(..) => "statements",
+ // Disabled pending discussion in #78306
+ ast::StmtKind::Item(..) => return,
+ // expressions will be reported by `check_expr`.
+ ast::StmtKind::Empty
+ | ast::StmtKind::Semi(_)
+ | ast::StmtKind::Expr(_)
+ | ast::StmtKind::MacCall(_) => return,
+ };
+
+ warn_if_doc(cx, stmt.span, kind, stmt.kind.attrs());
+ }
+
+ fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+ let arm_span = arm.pat.span.with_hi(arm.body.span.hi());
+ warn_if_doc(cx, arm_span, "match arms", &arm.attrs);
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ warn_if_doc(cx, expr.span, "expressions", &expr.attrs);
+ }
+
+ fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
+ warn_if_doc(cx, param.ident.span, "generic parameters", &param.attrs);
+ }
+
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
+ warn_if_doc(cx, block.span, "blocks", &block.attrs());
+ }
+
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if let ast::ItemKind::ForeignMod(_) = item.kind {
+ warn_if_doc(cx, item.span, "extern blocks", &item.attrs);
+ }
+ }
+}
+
+declare_lint! {
+ /// The `no_mangle_const_items` lint detects any `const` items with the
+ /// [`no_mangle` attribute].
+ ///
+ /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #[no_mangle]
+ /// const FOO: i32 = 5;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Constants do not have their symbols exported, and therefore, this
+ /// probably means you meant to use a [`static`], not a [`const`].
+ ///
+ /// [`static`]: https://doc.rust-lang.org/reference/items/static-items.html
+ /// [`const`]: https://doc.rust-lang.org/reference/items/constant-items.html
+ NO_MANGLE_CONST_ITEMS,
+ Deny,
+ "const items will not have their symbols exported"
+}
+
+declare_lint! {
+ /// The `no_mangle_generic_items` lint detects generic items that must be
+ /// mangled.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// #[no_mangle]
+ /// fn foo<T>(t: T) {
+ ///
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// A function with generics must have its symbol mangled to accommodate
+ /// the generic parameter. The [`no_mangle` attribute] has no effect in
+ /// this situation, and should be removed.
+ ///
+ /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute
+ NO_MANGLE_GENERIC_ITEMS,
+ Warn,
+ "generic items must be mangled"
+}
+
+declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GENERIC_ITEMS]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ let check_no_mangle_on_generic_fn = |no_mangle_attr: &ast::Attribute,
+ impl_generics: Option<&hir::Generics<'_>>,
+ generics: &hir::Generics<'_>,
+ span| {
+ for param in
+ generics.params.iter().chain(impl_generics.map(|g| g.params).into_iter().flatten())
+ {
+ match param.kind {
+ GenericParamKind::Lifetime { .. } => {}
+ GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
+ cx.struct_span_lint(NO_MANGLE_GENERIC_ITEMS, span, |lint| {
+ lint.build(fluent::lint::builtin_no_mangle_generic)
+ .span_suggestion_short(
+ no_mangle_attr.span,
+ fluent::lint::suggestion,
+ "",
+ // Use of `#[no_mangle]` suggests FFI intent; correct
+ // fix may be to monomorphize source by hand
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ });
+ break;
+ }
+ }
+ }
+ };
+ match it.kind {
+ hir::ItemKind::Fn(.., ref generics, _) => {
+ if let Some(no_mangle_attr) = cx.sess().find_by_name(attrs, sym::no_mangle) {
+ check_no_mangle_on_generic_fn(no_mangle_attr, None, generics, it.span);
+ }
+ }
+ hir::ItemKind::Const(..) => {
+ if cx.sess().contains_name(attrs, sym::no_mangle) {
+ // Const items do not refer to a particular location in memory, and therefore
+ // don't have anything to attach a symbol to
+ cx.struct_span_lint(NO_MANGLE_CONST_ITEMS, it.span, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_const_no_mangle);
+
+ // account for "pub const" (#45562)
+ let start = cx
+ .tcx
+ .sess
+ .source_map()
+ .span_to_snippet(it.span)
+ .map(|snippet| snippet.find("const").unwrap_or(0))
+ .unwrap_or(0) as u32;
+ // `const` is 5 chars
+ let const_span = it.span.with_hi(BytePos(it.span.lo().0 + start + 5));
+ err.span_suggestion(
+ const_span,
+ fluent::lint::suggestion,
+ "pub static",
+ Applicability::MachineApplicable,
+ );
+ err.emit();
+ });
+ }
+ }
+ hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => {
+ for it in *items {
+ if let hir::AssocItemKind::Fn { .. } = it.kind {
+ if let Some(no_mangle_attr) = cx
+ .sess()
+ .find_by_name(cx.tcx.hir().attrs(it.id.hir_id()), sym::no_mangle)
+ {
+ check_no_mangle_on_generic_fn(
+ no_mangle_attr,
+ Some(generics),
+ cx.tcx.hir().get_generics(it.id.def_id).unwrap(),
+ it.span,
+ );
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+declare_lint! {
+ /// The `mutable_transmutes` lint catches transmuting from `&T` to `&mut
+ /// T` because it is [undefined behavior].
+ ///
+ /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// unsafe {
+ /// let y = std::mem::transmute::<&i32, &mut i32>(&5);
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Certain assumptions are made about aliasing of data, and this transmute
+ /// violates those assumptions. Consider using [`UnsafeCell`] instead.
+ ///
+ /// [`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
+ MUTABLE_TRANSMUTES,
+ Deny,
+ "transmuting &T to &mut T is undefined behavior, even if the reference is unused"
+}
+
+declare_lint_pass!(MutableTransmutes => [MUTABLE_TRANSMUTES]);
+
+impl<'tcx> LateLintPass<'tcx> for MutableTransmutes {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ if let Some((&ty::Ref(_, _, from_mt), &ty::Ref(_, _, to_mt))) =
+ get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind()))
+ {
+ if to_mt == hir::Mutability::Mut && from_mt == hir::Mutability::Not {
+ cx.struct_span_lint(MUTABLE_TRANSMUTES, expr.span, |lint| {
+ lint.build(fluent::lint::builtin_mutable_transmutes).emit();
+ });
+ }
+ }
+
+ fn get_transmute_from_to<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &hir::Expr<'_>,
+ ) -> Option<(Ty<'tcx>, Ty<'tcx>)> {
+ let def = if let hir::ExprKind::Path(ref qpath) = expr.kind {
+ cx.qpath_res(qpath, expr.hir_id)
+ } else {
+ return None;
+ };
+ if let Res::Def(DefKind::Fn, did) = def {
+ if !def_id_is_transmute(cx, did) {
+ return None;
+ }
+ let sig = cx.typeck_results().node_type(expr.hir_id).fn_sig(cx.tcx);
+ let from = sig.inputs().skip_binder()[0];
+ let to = sig.output().skip_binder();
+ return Some((from, to));
+ }
+ None
+ }
+
+ fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool {
+ cx.tcx.is_intrinsic(def_id) && cx.tcx.item_name(def_id) == sym::transmute
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unstable_features` is deprecated and should no longer be used.
+ UNSTABLE_FEATURES,
+ Allow,
+ "enabling unstable features (deprecated. do not use)"
+}
+
+declare_lint_pass!(
+ /// Forbids using the `#[feature(...)]` attribute
+ UnstableFeatures => [UNSTABLE_FEATURES]
+);
+
+impl<'tcx> LateLintPass<'tcx> for UnstableFeatures {
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+ if attr.has_name(sym::feature) {
+ if let Some(items) = attr.meta_item_list() {
+ for item in items {
+ cx.struct_span_lint(UNSTABLE_FEATURES, item.span(), |lint| {
+ lint.build(fluent::lint::builtin_unstable_features).emit();
+ });
+ }
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unreachable_pub` lint triggers for `pub` items not reachable from
+ /// the crate root.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(unreachable_pub)]
+ /// mod foo {
+ /// pub mod bar {
+ ///
+ /// }
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// A bare `pub` visibility may be misleading if the item is not actually
+ /// publicly exported from the crate. The `pub(crate)` visibility is
+ /// recommended to be used instead, which more clearly expresses the intent
+ /// that the item is only visible within its own crate.
+ ///
+ /// This lint is "allow" by default because it will trigger for a large
+ /// amount existing Rust code, and has some false-positives. Eventually it
+ /// is desired for this to become warn-by-default.
+ pub UNREACHABLE_PUB,
+ Allow,
+ "`pub` items not reachable from crate root"
+}
+
+declare_lint_pass!(
+ /// Lint for items marked `pub` that aren't reachable from other crates.
+ UnreachablePub => [UNREACHABLE_PUB]
+);
+
+impl UnreachablePub {
+ fn perform_lint(
+ &self,
+ cx: &LateContext<'_>,
+ what: &str,
+ def_id: LocalDefId,
+ vis_span: Span,
+ exportable: bool,
+ ) {
+ let mut applicability = Applicability::MachineApplicable;
+ if cx.tcx.visibility(def_id).is_public() && !cx.access_levels.is_reachable(def_id) {
+ if vis_span.from_expansion() {
+ applicability = Applicability::MaybeIncorrect;
+ }
+ let def_span = cx.tcx.def_span(def_id);
+ cx.struct_span_lint(UNREACHABLE_PUB, def_span, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_unreachable_pub);
+ err.set_arg("what", what);
+
+ err.span_suggestion(
+ vis_span,
+ fluent::lint::suggestion,
+ "pub(crate)",
+ applicability,
+ );
+ if exportable {
+ err.help(fluent::lint::help);
+ }
+ err.emit();
+ });
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnreachablePub {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ // Do not warn for fake `use` statements.
+ if let hir::ItemKind::Use(_, hir::UseKind::ListStem) = &item.kind {
+ return;
+ }
+ self.perform_lint(cx, "item", item.def_id, item.vis_span, true);
+ }
+
+ fn check_foreign_item(&mut self, cx: &LateContext<'_>, foreign_item: &hir::ForeignItem<'tcx>) {
+ self.perform_lint(cx, "item", foreign_item.def_id, foreign_item.vis_span, true);
+ }
+
+ fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
+ let def_id = cx.tcx.hir().local_def_id(field.hir_id);
+ self.perform_lint(cx, "field", def_id, field.vis_span, false);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
+ // Only lint inherent impl items.
+ if cx.tcx.associated_item(impl_item.def_id).trait_item_def_id.is_none() {
+ self.perform_lint(cx, "item", impl_item.def_id, impl_item.vis_span, false);
+ }
+ }
+}
+
+declare_lint! {
+ /// The `type_alias_bounds` lint detects bounds in type aliases.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// type SendVec<T: Send> = Vec<T>;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The trait bounds in a type alias are currently ignored, and should not
+ /// be included to avoid confusion. This was previously allowed
+ /// unintentionally; this may become a hard error in the future.
+ TYPE_ALIAS_BOUNDS,
+ Warn,
+ "bounds in type aliases are not enforced"
+}
+
+declare_lint_pass!(
+ /// Lint for trait and lifetime bounds in type aliases being mostly ignored.
+ /// They are relevant when using associated types, but otherwise neither checked
+ /// at definition site nor enforced at use site.
+ TypeAliasBounds => [TYPE_ALIAS_BOUNDS]
+);
+
+impl TypeAliasBounds {
+ fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool {
+ match *qpath {
+ hir::QPath::TypeRelative(ref ty, _) => {
+ // If this is a type variable, we found a `T::Assoc`.
+ match ty.kind {
+ hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => {
+ matches!(path.res, Res::Def(DefKind::TyParam, _))
+ }
+ _ => false,
+ }
+ }
+ hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false,
+ }
+ }
+
+ fn suggest_changing_assoc_types(ty: &hir::Ty<'_>, err: &mut Diagnostic) {
+ // Access to associates types should use `<T as Bound>::Assoc`, which does not need a
+ // bound. Let's see if this type does that.
+
+ // We use a HIR visitor to walk the type.
+ use rustc_hir::intravisit::{self, Visitor};
+ struct WalkAssocTypes<'a> {
+ err: &'a mut Diagnostic,
+ }
+ impl Visitor<'_> for WalkAssocTypes<'_> {
+ fn visit_qpath(&mut self, qpath: &hir::QPath<'_>, id: hir::HirId, span: Span) {
+ if TypeAliasBounds::is_type_variable_assoc(qpath) {
+ self.err.span_help(span, fluent::lint::builtin_type_alias_bounds_help);
+ }
+ intravisit::walk_qpath(self, qpath, id, span)
+ }
+ }
+
+ // Let's go for a walk!
+ let mut visitor = WalkAssocTypes { err };
+ visitor.visit_ty(ty);
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds {
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
+ let hir::ItemKind::TyAlias(ty, type_alias_generics) = &item.kind else {
+ return
+ };
+ if let hir::TyKind::OpaqueDef(..) = ty.kind {
+ // Bounds are respected for `type X = impl Trait`
+ return;
+ }
+ // There must not be a where clause
+ if type_alias_generics.predicates.is_empty() {
+ return;
+ }
+
+ let mut where_spans = Vec::new();
+ let mut inline_spans = Vec::new();
+ let mut inline_sugg = Vec::new();
+ for p in type_alias_generics.predicates {
+ let span = p.span();
+ if p.in_where_clause() {
+ where_spans.push(span);
+ } else {
+ for b in p.bounds() {
+ inline_spans.push(b.span());
+ }
+ inline_sugg.push((span, String::new()));
+ }
+ }
+
+ let mut suggested_changing_assoc_types = false;
+ if !where_spans.is_empty() {
+ cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_type_alias_where_clause);
+ err.set_span(where_spans);
+ err.span_suggestion(
+ type_alias_generics.where_clause_span,
+ fluent::lint::suggestion,
+ "",
+ Applicability::MachineApplicable,
+ );
+ if !suggested_changing_assoc_types {
+ TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+ suggested_changing_assoc_types = true;
+ }
+ err.emit();
+ });
+ }
+
+ if !inline_spans.is_empty() {
+ cx.lint(TYPE_ALIAS_BOUNDS, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_type_alias_generic_bounds);
+ err.set_span(inline_spans);
+ err.multipart_suggestion(
+ fluent::lint::suggestion,
+ inline_sugg,
+ Applicability::MachineApplicable,
+ );
+ if !suggested_changing_assoc_types {
+ TypeAliasBounds::suggest_changing_assoc_types(ty, &mut err);
+ }
+ err.emit();
+ });
+ }
+ }
+}
+
+declare_lint_pass!(
+ /// Lint constants that are erroneous.
+ /// Without this lint, we might not get any diagnostic if the constant is
+ /// unused within this crate, even though downstream crates can't use it
+ /// without producing an error.
+ UnusedBrokenConst => []
+);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ match it.kind {
+ hir::ItemKind::Const(_, body_id) => {
+ let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id();
+ // trigger the query once for all constants since that will already report the errors
+ cx.tcx.ensure().const_eval_poly(def_id);
+ }
+ hir::ItemKind::Static(_, _, body_id) => {
+ let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id();
+ cx.tcx.ensure().eval_static_initializer(def_id);
+ }
+ _ => {}
+ }
+ }
+}
+
+declare_lint! {
+ /// The `trivial_bounds` lint detects trait bounds that don't depend on
+ /// any type parameters.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// #![feature(trivial_bounds)]
+ /// pub struct A where i32: Copy;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Usually you would not write a trait bound that you know is always
+ /// true, or never true. However, when using macros, the macro may not
+ /// know whether or not the constraint would hold or not at the time when
+ /// generating the code. Currently, the compiler does not alert you if the
+ /// constraint is always true, and generates an error if it is never true.
+ /// The `trivial_bounds` feature changes this to be a warning in both
+ /// cases, giving macros more freedom and flexibility to generate code,
+ /// while still providing a signal when writing non-macro code that
+ /// something is amiss.
+ ///
+ /// See [RFC 2056] for more details. This feature is currently only
+ /// available on the nightly channel, see [tracking issue #48214].
+ ///
+ /// [RFC 2056]: https://github.com/rust-lang/rfcs/blob/master/text/2056-allow-trivial-where-clause-constraints.md
+ /// [tracking issue #48214]: https://github.com/rust-lang/rust/issues/48214
+ TRIVIAL_BOUNDS,
+ Warn,
+ "these bounds don't depend on an type parameters"
+}
+
+declare_lint_pass!(
+ /// Lint for trait and lifetime bounds that don't depend on type parameters
+ /// which either do nothing, or stop the item from being used.
+ TrivialConstraints => [TRIVIAL_BOUNDS]
+);
+
+impl<'tcx> LateLintPass<'tcx> for TrivialConstraints {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+ use rustc_middle::ty::visit::TypeVisitable;
+ use rustc_middle::ty::PredicateKind::*;
+
+ if cx.tcx.features().trivial_bounds {
+ let predicates = cx.tcx.predicates_of(item.def_id);
+ for &(predicate, span) in predicates.predicates {
+ let predicate_kind_name = match predicate.kind().skip_binder() {
+ Trait(..) => "trait",
+ TypeOutlives(..) |
+ RegionOutlives(..) => "lifetime",
+
+ // Ignore projections, as they can only be global
+ // if the trait bound is global
+ Projection(..) |
+ // Ignore bounds that a user can't type
+ WellFormed(..) |
+ ObjectSafe(..) |
+ ClosureKind(..) |
+ Subtype(..) |
+ Coerce(..) |
+ ConstEvaluatable(..) |
+ ConstEquate(..) |
+ TypeWellFormedFromEnv(..) => continue,
+ };
+ if predicate.is_global() {
+ cx.struct_span_lint(TRIVIAL_BOUNDS, span, |lint| {
+ lint.build(fluent::lint::builtin_trivial_bounds)
+ .set_arg("predicate_kind_name", predicate_kind_name)
+ .set_arg("predicate", predicate)
+ .emit();
+ });
+ }
+ }
+ }
+ }
+}
+
+declare_lint_pass!(
+ /// Does nothing as a lint pass, but registers some `Lint`s
+ /// which are used by other parts of the compiler.
+ SoftLints => [
+ WHILE_TRUE,
+ BOX_POINTERS,
+ NON_SHORTHAND_FIELD_PATTERNS,
+ UNSAFE_CODE,
+ MISSING_DOCS,
+ MISSING_COPY_IMPLEMENTATIONS,
+ MISSING_DEBUG_IMPLEMENTATIONS,
+ ANONYMOUS_PARAMETERS,
+ UNUSED_DOC_COMMENTS,
+ NO_MANGLE_CONST_ITEMS,
+ NO_MANGLE_GENERIC_ITEMS,
+ MUTABLE_TRANSMUTES,
+ UNSTABLE_FEATURES,
+ UNREACHABLE_PUB,
+ TYPE_ALIAS_BOUNDS,
+ TRIVIAL_BOUNDS
+ ]
+);
+
+declare_lint! {
+ /// The `ellipsis_inclusive_range_patterns` lint detects the [`...` range
+ /// pattern], which is deprecated.
+ ///
+ /// [`...` range pattern]: https://doc.rust-lang.org/reference/patterns.html#range-patterns
+ ///
+ /// ### Example
+ ///
+ /// ```rust,edition2018
+ /// let x = 123;
+ /// match x {
+ /// 0...100 => {}
+ /// _ => {}
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The `...` range pattern syntax was changed to `..=` to avoid potential
+ /// confusion with the [`..` range expression]. Use the new form instead.
+ ///
+ /// [`..` range expression]: https://doc.rust-lang.org/reference/expressions/range-expr.html
+ pub ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+ Warn,
+ "`...` range patterns are deprecated",
+ @future_incompatible = FutureIncompatibleInfo {
+ reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>",
+ reason: FutureIncompatibilityReason::EditionError(Edition::Edition2021),
+ };
+}
+
+#[derive(Default)]
+pub struct EllipsisInclusiveRangePatterns {
+ /// If `Some(_)`, suppress all subsequent pattern
+ /// warnings for better diagnostics.
+ node_id: Option<ast::NodeId>,
+}
+
+impl_lint_pass!(EllipsisInclusiveRangePatterns => [ELLIPSIS_INCLUSIVE_RANGE_PATTERNS]);
+
+impl EarlyLintPass for EllipsisInclusiveRangePatterns {
+ fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) {
+ if self.node_id.is_some() {
+ // Don't recursively warn about patterns inside range endpoints.
+ return;
+ }
+
+ use self::ast::{PatKind, RangeSyntax::DotDotDot};
+
+ /// If `pat` is a `...` pattern, return the start and end of the range, as well as the span
+ /// corresponding to the ellipsis.
+ fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> {
+ match &pat.kind {
+ PatKind::Range(
+ a,
+ Some(b),
+ Spanned { span, node: RangeEnd::Included(DotDotDot) },
+ ) => Some((a.as_deref(), b, *span)),
+ _ => None,
+ }
+ }
+
+ let (parenthesise, endpoints) = match &pat.kind {
+ PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(&subpat)),
+ _ => (false, matches_ellipsis_pat(pat)),
+ };
+
+ if let Some((start, end, join)) = endpoints {
+ let msg = fluent::lint::builtin_ellipsis_inclusive_range_patterns;
+ let suggestion = fluent::lint::suggestion;
+ if parenthesise {
+ self.node_id = Some(pat.id);
+ let end = expr_to_string(&end);
+ let replace = match start {
+ Some(start) => format!("&({}..={})", expr_to_string(&start), end),
+ None => format!("&(..={})", end),
+ };
+ if join.edition() >= Edition::Edition2021 {
+ let mut err = cx.sess().struct_span_err_with_code(
+ pat.span,
+ msg,
+ rustc_errors::error_code!(E0783),
+ );
+ err.span_suggestion(
+ pat.span,
+ suggestion,
+ replace,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ } else {
+ cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, |lint| {
+ lint.build(msg)
+ .span_suggestion(
+ pat.span,
+ suggestion,
+ replace,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ } else {
+ let replace = "..=";
+ if join.edition() >= Edition::Edition2021 {
+ let mut err = cx.sess().struct_span_err_with_code(
+ pat.span,
+ msg,
+ rustc_errors::error_code!(E0783),
+ );
+ err.span_suggestion_short(
+ join,
+ suggestion,
+ replace,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ } else {
+ cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, |lint| {
+ lint.build(msg)
+ .span_suggestion_short(
+ join,
+ suggestion,
+ replace,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ };
+ }
+ }
+
+ fn check_pat_post(&mut self, _cx: &EarlyContext<'_>, pat: &ast::Pat) {
+ if let Some(node_id) = self.node_id {
+ if pat.id == node_id {
+ self.node_id = None
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unnameable_test_items` lint detects [`#[test]`][test] functions
+ /// that are not able to be run by the test harness because they are in a
+ /// position where they are not nameable.
+ ///
+ /// [test]: https://doc.rust-lang.org/reference/attributes/testing.html#the-test-attribute
+ ///
+ /// ### Example
+ ///
+ /// ```rust,test
+ /// fn main() {
+ /// #[test]
+ /// fn foo() {
+ /// // This test will not fail because it does not run.
+ /// assert_eq!(1, 2);
+ /// }
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// In order for the test harness to run a test, the test function must be
+ /// located in a position where it can be accessed from the crate root.
+ /// This generally means it must be defined in a module, and not anywhere
+ /// else such as inside another function. The compiler previously allowed
+ /// this without an error, so a lint was added as an alert that a test is
+ /// not being used. Whether or not this should be allowed has not yet been
+ /// decided, see [RFC 2471] and [issue #36629].
+ ///
+ /// [RFC 2471]: https://github.com/rust-lang/rfcs/pull/2471#issuecomment-397414443
+ /// [issue #36629]: https://github.com/rust-lang/rust/issues/36629
+ UNNAMEABLE_TEST_ITEMS,
+ Warn,
+ "detects an item that cannot be named being marked as `#[test_case]`",
+ report_in_external_macro
+}
+
+pub struct UnnameableTestItems {
+ boundary: Option<LocalDefId>, // Id of the item under which things are not nameable
+ items_nameable: bool,
+}
+
+impl_lint_pass!(UnnameableTestItems => [UNNAMEABLE_TEST_ITEMS]);
+
+impl UnnameableTestItems {
+ pub fn new() -> Self {
+ Self { boundary: None, items_nameable: true }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for UnnameableTestItems {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ if self.items_nameable {
+ if let hir::ItemKind::Mod(..) = it.kind {
+ } else {
+ self.items_nameable = false;
+ self.boundary = Some(it.def_id);
+ }
+ return;
+ }
+
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ if let Some(attr) = cx.sess().find_by_name(attrs, sym::rustc_test_marker) {
+ cx.struct_span_lint(UNNAMEABLE_TEST_ITEMS, attr.span, |lint| {
+ lint.build(fluent::lint::builtin_unnameable_test_items).emit();
+ });
+ }
+ }
+
+ fn check_item_post(&mut self, _cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ if !self.items_nameable && self.boundary == Some(it.def_id) {
+ self.items_nameable = true;
+ }
+ }
+}
+
+declare_lint! {
+ /// The `keyword_idents` lint detects edition keywords being used as an
+ /// identifier.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,edition2015,compile_fail
+ /// #![deny(keyword_idents)]
+ /// // edition 2015
+ /// fn dyn() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Rust [editions] allow the language to evolve without breaking
+ /// backwards compatibility. This lint catches code that uses new keywords
+ /// that are added to the language that are used as identifiers (such as a
+ /// variable name, function name, etc.). If you switch the compiler to a
+ /// new edition without updating the code, then it will fail to compile if
+ /// you are using a new keyword as an identifier.
+ ///
+ /// You can manually change the identifiers to a non-keyword, or use a
+ /// [raw identifier], for example `r#dyn`, to transition to a new edition.
+ ///
+ /// This lint solves the problem automatically. It is "allow" by default
+ /// because the code is perfectly valid in older editions. The [`cargo
+ /// fix`] tool with the `--edition` flag will switch this lint to "warn"
+ /// and automatically apply the suggested fix from the compiler (which is
+ /// to use a raw identifier). This provides a completely automated way to
+ /// update old code for a new edition.
+ ///
+ /// [editions]: https://doc.rust-lang.org/edition-guide/
+ /// [raw identifier]: https://doc.rust-lang.org/reference/identifiers.html
+ /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html
+ pub KEYWORD_IDENTS,
+ Allow,
+ "detects edition keywords being used as an identifier",
+ @future_incompatible = FutureIncompatibleInfo {
+ reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",
+ reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+ };
+}
+
+declare_lint_pass!(
+ /// Check for uses of edition keywords used as an identifier.
+ KeywordIdents => [KEYWORD_IDENTS]
+);
+
+struct UnderMacro(bool);
+
+impl KeywordIdents {
+ fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: TokenStream) {
+ for tt in tokens.into_trees() {
+ match tt {
+ // Only report non-raw idents.
+ TokenTree::Token(token, _) => {
+ if let Some((ident, false)) = token.ident() {
+ self.check_ident_token(cx, UnderMacro(true), ident);
+ }
+ }
+ TokenTree::Delimited(_, _, tts) => self.check_tokens(cx, tts),
+ }
+ }
+ }
+
+ fn check_ident_token(
+ &mut self,
+ cx: &EarlyContext<'_>,
+ UnderMacro(under_macro): UnderMacro,
+ ident: Ident,
+ ) {
+ let next_edition = match cx.sess().edition() {
+ Edition::Edition2015 => {
+ match ident.name {
+ kw::Async | kw::Await | kw::Try => Edition::Edition2018,
+
+ // rust-lang/rust#56327: Conservatively do not
+ // attempt to report occurrences of `dyn` within
+ // macro definitions or invocations, because `dyn`
+ // can legitimately occur as a contextual keyword
+ // in 2015 code denoting its 2018 meaning, and we
+ // do not want rustfix to inject bugs into working
+ // code by rewriting such occurrences.
+ //
+ // But if we see `dyn` outside of a macro, we know
+ // its precise role in the parsed AST and thus are
+ // assured this is truly an attempt to use it as
+ // an identifier.
+ kw::Dyn if !under_macro => Edition::Edition2018,
+
+ _ => return,
+ }
+ }
+
+ // There are no new keywords yet for the 2018 edition and beyond.
+ _ => return,
+ };
+
+ // Don't lint `r#foo`.
+ if cx.sess().parse_sess.raw_identifier_spans.borrow().contains(&ident.span) {
+ return;
+ }
+
+ cx.struct_span_lint(KEYWORD_IDENTS, ident.span, |lint| {
+ lint.build(fluent::lint::builtin_keyword_idents)
+ .set_arg("kw", ident.clone())
+ .set_arg("next", next_edition)
+ .span_suggestion(
+ ident.span,
+ fluent::lint::suggestion,
+ format!("r#{}", ident),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+}
+
+impl EarlyLintPass for KeywordIdents {
+ fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef, _id: ast::NodeId) {
+ self.check_tokens(cx, mac_def.body.inner_tokens());
+ }
+ fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) {
+ self.check_tokens(cx, mac.args.inner_tokens());
+ }
+ fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
+ self.check_ident_token(cx, UnderMacro(false), ident);
+ }
+}
+
+declare_lint_pass!(ExplicitOutlivesRequirements => [EXPLICIT_OUTLIVES_REQUIREMENTS]);
+
+impl ExplicitOutlivesRequirements {
+ fn lifetimes_outliving_lifetime<'tcx>(
+ inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)],
+ index: u32,
+ ) -> Vec<ty::Region<'tcx>> {
+ inferred_outlives
+ .iter()
+ .filter_map(|(pred, _)| match pred.kind().skip_binder() {
+ ty::PredicateKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a {
+ ty::ReEarlyBound(ebr) if ebr.index == index => Some(b),
+ _ => None,
+ },
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn lifetimes_outliving_type<'tcx>(
+ inferred_outlives: &'tcx [(ty::Predicate<'tcx>, Span)],
+ index: u32,
+ ) -> Vec<ty::Region<'tcx>> {
+ inferred_outlives
+ .iter()
+ .filter_map(|(pred, _)| match pred.kind().skip_binder() {
+ ty::PredicateKind::TypeOutlives(ty::OutlivesPredicate(a, b)) => {
+ a.is_param(index).then_some(b)
+ }
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn collect_outlives_bound_spans<'tcx>(
+ &self,
+ tcx: TyCtxt<'tcx>,
+ bounds: &hir::GenericBounds<'_>,
+ inferred_outlives: &[ty::Region<'tcx>],
+ ) -> Vec<(usize, Span)> {
+ use rustc_middle::middle::resolve_lifetime::Region;
+
+ bounds
+ .iter()
+ .enumerate()
+ .filter_map(|(i, bound)| {
+ if let hir::GenericBound::Outlives(lifetime) = bound {
+ let is_inferred = match tcx.named_region(lifetime.hir_id) {
+ Some(Region::EarlyBound(index, ..)) => inferred_outlives.iter().any(|r| {
+ if let ty::ReEarlyBound(ebr) = **r { ebr.index == index } else { false }
+ }),
+ _ => false,
+ };
+ is_inferred.then_some((i, bound.span()))
+ } else {
+ None
+ }
+ })
+ .filter(|(_, span)| !in_external_macro(tcx.sess, *span))
+ .collect()
+ }
+
+ fn consolidate_outlives_bound_spans(
+ &self,
+ lo: Span,
+ bounds: &hir::GenericBounds<'_>,
+ bound_spans: Vec<(usize, Span)>,
+ ) -> Vec<Span> {
+ if bounds.is_empty() {
+ return Vec::new();
+ }
+ if bound_spans.len() == bounds.len() {
+ let (_, last_bound_span) = bound_spans[bound_spans.len() - 1];
+ // If all bounds are inferable, we want to delete the colon, so
+ // start from just after the parameter (span passed as argument)
+ vec![lo.to(last_bound_span)]
+ } else {
+ let mut merged = Vec::new();
+ let mut last_merged_i = None;
+
+ let mut from_start = true;
+ for (i, bound_span) in bound_spans {
+ match last_merged_i {
+ // If the first bound is inferable, our span should also eat the leading `+`.
+ None if i == 0 => {
+ merged.push(bound_span.to(bounds[1].span().shrink_to_lo()));
+ last_merged_i = Some(0);
+ }
+ // If consecutive bounds are inferable, merge their spans
+ Some(h) if i == h + 1 => {
+ if let Some(tail) = merged.last_mut() {
+ // Also eat the trailing `+` if the first
+ // more-than-one bound is inferable
+ let to_span = if from_start && i < bounds.len() {
+ bounds[i + 1].span().shrink_to_lo()
+ } else {
+ bound_span
+ };
+ *tail = tail.to(to_span);
+ last_merged_i = Some(i);
+ } else {
+ bug!("another bound-span visited earlier");
+ }
+ }
+ _ => {
+ // When we find a non-inferable bound, subsequent inferable bounds
+ // won't be consecutive from the start (and we'll eat the leading
+ // `+` rather than the trailing one)
+ from_start = false;
+ merged.push(bounds[i - 1].span().shrink_to_hi().to(bound_span));
+ last_merged_i = Some(i);
+ }
+ }
+ }
+ merged
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
+ use rustc_middle::middle::resolve_lifetime::Region;
+
+ let def_id = item.def_id;
+ if let hir::ItemKind::Struct(_, ref hir_generics)
+ | hir::ItemKind::Enum(_, ref hir_generics)
+ | hir::ItemKind::Union(_, ref hir_generics) = item.kind
+ {
+ let inferred_outlives = cx.tcx.inferred_outlives_of(def_id);
+ if inferred_outlives.is_empty() {
+ return;
+ }
+
+ let ty_generics = cx.tcx.generics_of(def_id);
+
+ let mut bound_count = 0;
+ let mut lint_spans = Vec::new();
+ let mut where_lint_spans = Vec::new();
+ let mut dropped_predicate_count = 0;
+ let num_predicates = hir_generics.predicates.len();
+ for (i, where_predicate) in hir_generics.predicates.iter().enumerate() {
+ let (relevant_lifetimes, bounds, span, in_where_clause) = match where_predicate {
+ hir::WherePredicate::RegionPredicate(predicate) => {
+ if let Some(Region::EarlyBound(index, ..)) =
+ cx.tcx.named_region(predicate.lifetime.hir_id)
+ {
+ (
+ Self::lifetimes_outliving_lifetime(inferred_outlives, index),
+ &predicate.bounds,
+ predicate.span,
+ predicate.in_where_clause,
+ )
+ } else {
+ continue;
+ }
+ }
+ hir::WherePredicate::BoundPredicate(predicate) => {
+ // FIXME we can also infer bounds on associated types,
+ // and should check for them here.
+ match predicate.bounded_ty.kind {
+ hir::TyKind::Path(hir::QPath::Resolved(None, ref path)) => {
+ let Res::Def(DefKind::TyParam, def_id) = path.res else {
+ continue
+ };
+ let index = ty_generics.param_def_id_to_index[&def_id];
+ (
+ Self::lifetimes_outliving_type(inferred_outlives, index),
+ &predicate.bounds,
+ predicate.span,
+ predicate.origin == PredicateOrigin::WhereClause,
+ )
+ }
+ _ => {
+ continue;
+ }
+ }
+ }
+ _ => continue,
+ };
+ if relevant_lifetimes.is_empty() {
+ continue;
+ }
+
+ let bound_spans =
+ self.collect_outlives_bound_spans(cx.tcx, bounds, &relevant_lifetimes);
+ bound_count += bound_spans.len();
+
+ let drop_predicate = bound_spans.len() == bounds.len();
+ if drop_predicate {
+ dropped_predicate_count += 1;
+ }
+
+ if drop_predicate && !in_where_clause {
+ lint_spans.push(span);
+ } else if drop_predicate && i + 1 < num_predicates {
+ // If all the bounds on a predicate were inferable and there are
+ // further predicates, we want to eat the trailing comma.
+ let next_predicate_span = hir_generics.predicates[i + 1].span();
+ where_lint_spans.push(span.to(next_predicate_span.shrink_to_lo()));
+ } else {
+ where_lint_spans.extend(self.consolidate_outlives_bound_spans(
+ span.shrink_to_lo(),
+ bounds,
+ bound_spans,
+ ));
+ }
+ }
+
+ // If all predicates are inferable, drop the entire clause
+ // (including the `where`)
+ if hir_generics.has_where_clause_predicates && dropped_predicate_count == num_predicates
+ {
+ let where_span = hir_generics.where_clause_span;
+ // Extend the where clause back to the closing `>` of the
+ // generics, except for tuple struct, which have the `where`
+ // after the fields of the struct.
+ let full_where_span =
+ if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind {
+ where_span
+ } else {
+ hir_generics.span.shrink_to_hi().to(where_span)
+ };
+ lint_spans.push(full_where_span);
+ } else {
+ lint_spans.extend(where_lint_spans);
+ }
+
+ if !lint_spans.is_empty() {
+ cx.struct_span_lint(EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), |lint| {
+ lint.build(fluent::lint::builtin_explicit_outlives)
+ .set_arg("count", bound_count)
+ .multipart_suggestion(
+ fluent::lint::suggestion,
+ lint_spans
+ .into_iter()
+ .map(|span| (span, String::new()))
+ .collect::<Vec<_>>(),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `incomplete_features` lint detects unstable features enabled with
+ /// the [`feature` attribute] that may function improperly in some or all
+ /// cases.
+ ///
+ /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// #![feature(generic_const_exprs)]
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Although it is encouraged for people to experiment with unstable
+ /// features, some of them are known to be incomplete or faulty. This lint
+ /// is a signal that the feature has not yet been finished, and you may
+ /// experience problems with it.
+ pub INCOMPLETE_FEATURES,
+ Warn,
+ "incomplete features that may function improperly in some or all cases"
+}
+
+declare_lint_pass!(
+ /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/active.rs`.
+ IncompleteFeatures => [INCOMPLETE_FEATURES]
+);
+
+impl EarlyLintPass for IncompleteFeatures {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ let features = cx.sess().features_untracked();
+ features
+ .declared_lang_features
+ .iter()
+ .map(|(name, span, _)| (name, span))
+ .chain(features.declared_lib_features.iter().map(|(name, span)| (name, span)))
+ .filter(|(&name, _)| features.incomplete(name))
+ .for_each(|(&name, &span)| {
+ cx.struct_span_lint(INCOMPLETE_FEATURES, span, |lint| {
+ let mut builder = lint.build(fluent::lint::builtin_incomplete_features);
+ builder.set_arg("name", name);
+ if let Some(n) = rustc_feature::find_feature_issue(name, GateIssue::Language) {
+ builder.set_arg("n", n);
+ builder.note(fluent::lint::note);
+ }
+ if HAS_MIN_FEATURES.contains(&name) {
+ builder.help(fluent::lint::help);
+ }
+ builder.emit();
+ })
+ });
+ }
+}
+
+const HAS_MIN_FEATURES: &[Symbol] = &[sym::specialization];
+
+declare_lint! {
+ /// The `invalid_value` lint detects creating a value that is not valid,
+ /// such as a null reference.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![allow(unused)]
+ /// unsafe {
+ /// let x: &'static i32 = std::mem::zeroed();
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// In some situations the compiler can detect that the code is creating
+ /// an invalid value, which should be avoided.
+ ///
+ /// In particular, this lint will check for improper use of
+ /// [`mem::zeroed`], [`mem::uninitialized`], [`mem::transmute`], and
+ /// [`MaybeUninit::assume_init`] that can cause [undefined behavior]. The
+ /// lint should provide extra information to indicate what the problem is
+ /// and a possible solution.
+ ///
+ /// [`mem::zeroed`]: https://doc.rust-lang.org/std/mem/fn.zeroed.html
+ /// [`mem::uninitialized`]: https://doc.rust-lang.org/std/mem/fn.uninitialized.html
+ /// [`mem::transmute`]: https://doc.rust-lang.org/std/mem/fn.transmute.html
+ /// [`MaybeUninit::assume_init`]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#method.assume_init
+ /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+ pub INVALID_VALUE,
+ Warn,
+ "an invalid value is being created (such as a null reference)"
+}
+
+declare_lint_pass!(InvalidValue => [INVALID_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for InvalidValue {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
+ #[derive(Debug, Copy, Clone, PartialEq)]
+ 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.
+ type InitError = (String, Option<Span>);
+
+ /// Test if this constant is all-0.
+ fn is_zero(expr: &hir::Expr<'_>) -> bool {
+ use hir::ExprKind::*;
+ use rustc_ast::LitKind::*;
+ match &expr.kind {
+ Lit(lit) => {
+ if let Int(i, _) = lit.node {
+ i == 0
+ } else {
+ false
+ }
+ }
+ Tup(tup) => tup.iter().all(is_zero),
+ _ => false,
+ }
+ }
+
+ /// Determine if this expression is a "dangerous initialization".
+ fn is_dangerous_init(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<InitKind> {
+ if let hir::ExprKind::Call(ref path_expr, ref args) = expr.kind {
+ // Find calls to `mem::{uninitialized,zeroed}` methods.
+ if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+ let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+ match cx.tcx.get_diagnostic_name(def_id) {
+ Some(sym::mem_zeroed) => return Some(InitKind::Zeroed),
+ Some(sym::mem_uninitialized) => return Some(InitKind::Uninit),
+ Some(sym::transmute) if is_zero(&args[0]) => return Some(InitKind::Zeroed),
+ _ => {}
+ }
+ }
+ } else if let hir::ExprKind::MethodCall(_, ref args, _) = expr.kind {
+ // Find problematic calls to `MaybeUninit::assume_init`.
+ let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
+ if cx.tcx.is_diagnostic_item(sym::assume_init, def_id) {
+ // This is a call to *some* method named `assume_init`.
+ // See if the `self` parameter is one of the dangerous constructors.
+ if let hir::ExprKind::Call(ref path_expr, _) = args[0].kind {
+ if let hir::ExprKind::Path(ref qpath) = path_expr.kind {
+ let def_id = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()?;
+ match cx.tcx.get_diagnostic_name(def_id) {
+ Some(sym::maybe_uninit_zeroed) => return Some(InitKind::Zeroed),
+ Some(sym::maybe_uninit_uninit) => return Some(InitKind::Uninit),
+ _ => {}
+ }
+ }
+ }
+ }
+ }
+
+ None
+ }
+
+ /// Test if this enum has several actually "existing" variants.
+ /// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist".
+ fn is_multi_variant<'tcx>(adt: ty::AdtDef<'tcx>) -> bool {
+ // As an approximation, we only count dataless variants. Those are definitely inhabited.
+ let existing_variants = adt.variants().iter().filter(|v| v.fields.is_empty()).count();
+ existing_variants > 1
+ }
+
+ /// Return `Some` only if we are sure this type does *not*
+ /// allow zero initialization.
+ fn ty_find_init_error<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ init: InitKind,
+ ) -> Option<InitError> {
+ use rustc_type_ir::sty::TyKind::*;
+ match ty.kind() {
+ // Primitive types that don't like 0 as a value.
+ Ref(..) => Some(("references must be non-null".to_string(), None)),
+ Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)),
+ FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)),
+ Never => Some(("the `!` type has no valid value".to_string(), None)),
+ RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) =>
+ // raw ptr to dyn Trait
+ {
+ Some(("the vtable of a wide raw pointer must be non-null".to_string(), None))
+ }
+ // Primitive types with other constraints.
+ Bool if init == InitKind::Uninit => {
+ Some(("booleans must be either `true` or `false`".to_string(), None))
+ }
+ Char if init == InitKind::Uninit => {
+ Some(("characters must be a valid Unicode codepoint".to_string(), None))
+ }
+ // Recurse and checks for some compound types.
+ Adt(adt_def, substs) if !adt_def.is_union() => {
+ // First check if this ADT has a layout attribute (like `NonNull` and friends).
+ use std::ops::Bound;
+ match cx.tcx.layout_scalar_valid_range(adt_def.did()) {
+ // We exploit here that `layout_scalar_valid_range` will never
+ // return `Bound::Excluded`. (And we have tests checking that we
+ // handle the attribute correctly.)
+ (Bound::Included(lo), _) if lo > 0 => {
+ return Some((format!("`{}` must be non-null", ty), None));
+ }
+ (Bound::Included(_), _) | (_, Bound::Included(_))
+ if init == InitKind::Uninit =>
+ {
+ return Some((
+ format!(
+ "`{}` must be initialized inside its custom valid range",
+ ty,
+ ),
+ None,
+ ));
+ }
+ _ => {}
+ }
+ // Now, recurse.
+ match adt_def.variants().len() {
+ 0 => Some(("enums with no variants have no valid value".to_string(), None)),
+ 1 => {
+ // Struct, or enum with exactly one variant.
+ // Proceed recursively, check all fields.
+ let variant = &adt_def.variant(VariantIdx::from_u32(0));
+ variant.fields.iter().find_map(|field| {
+ ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(
+ |(mut msg, span)| {
+ if span.is_none() {
+ // Point to this field, should be helpful for figuring
+ // out where the source of the error is.
+ let span = cx.tcx.def_span(field.did);
+ write!(
+ &mut msg,
+ " (in this {} field)",
+ adt_def.descr()
+ )
+ .unwrap();
+ (msg, Some(span))
+ } else {
+ // Just forward.
+ (msg, span)
+ }
+ },
+ )
+ })
+ }
+ // Multi-variant enum.
+ _ => {
+ if init == InitKind::Uninit && is_multi_variant(*adt_def) {
+ let span = cx.tcx.def_span(adt_def.did());
+ Some((
+ "enums have to be initialized to a variant".to_string(),
+ Some(span),
+ ))
+ } else {
+ // In principle, for zero-initialization we could figure out which variant corresponds
+ // to tag 0, and check that... but for now we just accept all zero-initializations.
+ None
+ }
+ }
+ }
+ }
+ Tuple(..) => {
+ // Proceed recursively, check all fields.
+ ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init))
+ }
+ Array(ty, len) => {
+ if matches!(len.try_eval_usize(cx.tcx, cx.param_env), Some(v) if v > 0) {
+ // Array length known at array non-empty -- recurse.
+ ty_find_init_error(cx, *ty, init)
+ } else {
+ // Empty array or size unknown.
+ None
+ }
+ }
+ // Conservative fallback.
+ _ => None,
+ }
+ }
+
+ if let Some(init) = is_dangerous_init(cx, expr) {
+ // This conjures an instance of a type out of nothing,
+ // using zeroed or uninitialized memory.
+ // We are extremely conservative with what we warn about.
+ let conjured_ty = cx.typeck_results().expr_ty(expr);
+ if let Some((msg, span)) =
+ with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init))
+ {
+ // FIXME(davidtwco): make translatable
+ cx.struct_span_lint(INVALID_VALUE, expr.span, |lint| {
+ let mut err = lint.build(&format!(
+ "the type `{}` does not permit {}",
+ conjured_ty,
+ match init {
+ InitKind::Zeroed => "zero-initialization",
+ InitKind::Uninit => "being left uninitialized",
+ },
+ ));
+ err.span_label(expr.span, "this code causes undefined behavior when executed");
+ err.span_label(
+ expr.span,
+ "help: use `MaybeUninit<T>` instead, \
+ and only call `assume_init` after initialization is done",
+ );
+ if let Some(span) = span {
+ err.span_note(span, &msg);
+ } else {
+ err.note(&msg);
+ }
+ err.emit();
+ });
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `clashing_extern_declarations` lint detects when an `extern fn`
+ /// has been declared with the same name but different types.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// mod m {
+ /// extern "C" {
+ /// fn foo();
+ /// }
+ /// }
+ ///
+ /// extern "C" {
+ /// fn foo(_: u32);
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Because two symbols of the same name cannot be resolved to two
+ /// different functions at link time, and one function cannot possibly
+ /// have two types, a clashing extern declaration is almost certainly a
+ /// mistake. Check to make sure that the `extern` definitions are correct
+ /// and equivalent, and possibly consider unifying them in one location.
+ ///
+ /// This lint does not run between crates because a project may have
+ /// dependencies which both rely on the same extern function, but declare
+ /// it in a different (but valid) way. For example, they may both declare
+ /// an opaque type for one or more of the arguments (which would end up
+ /// distinct types), or use types that are valid conversions in the
+ /// language the `extern fn` is defined in. In these cases, the compiler
+ /// can't say that the clashing declaration is incorrect.
+ pub CLASHING_EXTERN_DECLARATIONS,
+ Warn,
+ "detects when an extern fn has been declared with the same name but different types"
+}
+
+pub struct ClashingExternDeclarations {
+ /// Map of function symbol name to the first-seen hir id for that symbol name.. If seen_decls
+ /// contains an entry for key K, it means a symbol with name K has been seen by this lint and
+ /// the symbol should be reported as a clashing declaration.
+ // FIXME: Technically, we could just store a &'tcx str here without issue; however, the
+ // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime.
+ seen_decls: FxHashMap<Symbol, HirId>,
+}
+
+/// Differentiate between whether the name for an extern decl came from the link_name attribute or
+/// just from declaration itself. This is important because we don't want to report clashes on
+/// symbol name if they don't actually clash because one or the other links against a symbol with a
+/// different name.
+enum SymbolName {
+ /// The name of the symbol + the span of the annotation which introduced the link name.
+ Link(Symbol, Span),
+ /// No link name, so just the name of the symbol.
+ Normal(Symbol),
+}
+
+impl SymbolName {
+ fn get_name(&self) -> Symbol {
+ match self {
+ SymbolName::Link(s, _) | SymbolName::Normal(s) => *s,
+ }
+ }
+}
+
+impl ClashingExternDeclarations {
+ pub(crate) fn new() -> Self {
+ ClashingExternDeclarations { seen_decls: FxHashMap::default() }
+ }
+ /// Insert a new foreign item into the seen set. If a symbol with the same name already exists
+ /// for the item, return its HirId without updating the set.
+ fn insert(&mut self, tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> Option<HirId> {
+ let did = fi.def_id.to_def_id();
+ let instance = Instance::new(did, ty::List::identity_for_item(tcx, did));
+ let name = Symbol::intern(tcx.symbol_name(instance).name);
+ if let Some(&hir_id) = self.seen_decls.get(&name) {
+ // Avoid updating the map with the new entry when we do find a collision. We want to
+ // make sure we're always pointing to the first definition as the previous declaration.
+ // This lets us avoid emitting "knock-on" diagnostics.
+ Some(hir_id)
+ } else {
+ self.seen_decls.insert(name, fi.hir_id())
+ }
+ }
+
+ /// Get the name of the symbol that's linked against for a given extern declaration. That is,
+ /// the name specified in a #[link_name = ...] attribute if one was specified, else, just the
+ /// symbol's name.
+ fn name_of_extern_decl(tcx: TyCtxt<'_>, fi: &hir::ForeignItem<'_>) -> SymbolName {
+ if let Some((overridden_link_name, overridden_link_name_span)) =
+ tcx.codegen_fn_attrs(fi.def_id).link_name.map(|overridden_link_name| {
+ // FIXME: Instead of searching through the attributes again to get span
+ // information, we could have codegen_fn_attrs also give span information back for
+ // where the attribute was defined. However, until this is found to be a
+ // bottleneck, this does just fine.
+ (
+ overridden_link_name,
+ tcx.get_attr(fi.def_id.to_def_id(), sym::link_name).unwrap().span,
+ )
+ })
+ {
+ SymbolName::Link(overridden_link_name, overridden_link_name_span)
+ } else {
+ SymbolName::Normal(fi.ident.name)
+ }
+ }
+
+ /// Checks whether two types are structurally the same enough that the declarations shouldn't
+ /// clash. We need this so we don't emit a lint when two modules both declare an extern struct,
+ /// with the same members (as the declarations shouldn't clash).
+ fn structurally_same_type<'tcx>(
+ cx: &LateContext<'tcx>,
+ a: Ty<'tcx>,
+ b: Ty<'tcx>,
+ ckind: CItemKind,
+ ) -> bool {
+ fn structurally_same_type_impl<'tcx>(
+ seen_types: &mut FxHashSet<(Ty<'tcx>, Ty<'tcx>)>,
+ cx: &LateContext<'tcx>,
+ a: Ty<'tcx>,
+ b: Ty<'tcx>,
+ ckind: CItemKind,
+ ) -> bool {
+ debug!("structurally_same_type_impl(cx, a = {:?}, b = {:?})", a, b);
+ let tcx = cx.tcx;
+
+ // Given a transparent newtype, reach through and grab the inner
+ // type unless the newtype makes the type non-null.
+ let non_transparent_ty = |ty: Ty<'tcx>| -> Ty<'tcx> {
+ let mut ty = ty;
+ loop {
+ if let ty::Adt(def, substs) = *ty.kind() {
+ let is_transparent = def.repr().transparent();
+ let is_non_null = crate::types::nonnull_optimization_guaranteed(tcx, def);
+ debug!(
+ "non_transparent_ty({:?}) -- type is transparent? {}, type is non-null? {}",
+ ty, is_transparent, is_non_null
+ );
+ if is_transparent && !is_non_null {
+ debug_assert!(def.variants().len() == 1);
+ let v = &def.variant(VariantIdx::new(0));
+ ty = transparent_newtype_field(tcx, v)
+ .expect(
+ "single-variant transparent structure with zero-sized field",
+ )
+ .ty(tcx, substs);
+ continue;
+ }
+ }
+ debug!("non_transparent_ty -> {:?}", ty);
+ return ty;
+ }
+ };
+
+ let a = non_transparent_ty(a);
+ let b = non_transparent_ty(b);
+
+ if !seen_types.insert((a, b)) {
+ // We've encountered a cycle. There's no point going any further -- the types are
+ // structurally the same.
+ return true;
+ }
+ let tcx = cx.tcx;
+ if a == b {
+ // All nominally-same types are structurally same, too.
+ true
+ } else {
+ // Do a full, depth-first comparison between the two.
+ use rustc_type_ir::sty::TyKind::*;
+ let a_kind = a.kind();
+ let b_kind = b.kind();
+
+ let compare_layouts = |a, b| -> Result<bool, LayoutError<'tcx>> {
+ debug!("compare_layouts({:?}, {:?})", a, b);
+ let a_layout = &cx.layout_of(a)?.layout.abi();
+ let b_layout = &cx.layout_of(b)?.layout.abi();
+ debug!(
+ "comparing layouts: {:?} == {:?} = {}",
+ a_layout,
+ b_layout,
+ a_layout == b_layout
+ );
+ Ok(a_layout == b_layout)
+ };
+
+ #[allow(rustc::usage_of_ty_tykind)]
+ let is_primitive_or_pointer = |kind: &ty::TyKind<'_>| {
+ kind.is_primitive() || matches!(kind, RawPtr(..) | Ref(..))
+ };
+
+ ensure_sufficient_stack(|| {
+ match (a_kind, b_kind) {
+ (Adt(a_def, _), Adt(b_def, _)) => {
+ // We can immediately rule out these types as structurally same if
+ // their layouts differ.
+ match compare_layouts(a, b) {
+ Ok(false) => return false,
+ _ => (), // otherwise, continue onto the full, fields comparison
+ }
+
+ // Grab a flattened representation of all fields.
+ let a_fields = a_def.variants().iter().flat_map(|v| v.fields.iter());
+ let b_fields = b_def.variants().iter().flat_map(|v| v.fields.iter());
+
+ // Perform a structural comparison for each field.
+ a_fields.eq_by(
+ b_fields,
+ |&ty::FieldDef { did: a_did, .. },
+ &ty::FieldDef { did: b_did, .. }| {
+ structurally_same_type_impl(
+ seen_types,
+ cx,
+ tcx.type_of(a_did),
+ tcx.type_of(b_did),
+ ckind,
+ )
+ },
+ )
+ }
+ (Array(a_ty, a_const), Array(b_ty, b_const)) => {
+ // For arrays, we also check the constness of the type.
+ a_const.kind() == b_const.kind()
+ && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind)
+ }
+ (Slice(a_ty), Slice(b_ty)) => {
+ structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind)
+ }
+ (RawPtr(a_tymut), RawPtr(b_tymut)) => {
+ a_tymut.mutbl == b_tymut.mutbl
+ && structurally_same_type_impl(
+ seen_types, cx, a_tymut.ty, b_tymut.ty, ckind,
+ )
+ }
+ (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => {
+ // For structural sameness, we don't need the region to be same.
+ a_mut == b_mut
+ && structurally_same_type_impl(seen_types, cx, *a_ty, *b_ty, ckind)
+ }
+ (FnDef(..), FnDef(..)) => {
+ let a_poly_sig = a.fn_sig(tcx);
+ let b_poly_sig = b.fn_sig(tcx);
+
+ // We don't compare regions, but leaving bound regions around ICEs, so
+ // we erase them.
+ let a_sig = tcx.erase_late_bound_regions(a_poly_sig);
+ let b_sig = tcx.erase_late_bound_regions(b_poly_sig);
+
+ (a_sig.abi, a_sig.unsafety, a_sig.c_variadic)
+ == (b_sig.abi, b_sig.unsafety, b_sig.c_variadic)
+ && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| {
+ structurally_same_type_impl(seen_types, cx, *a, *b, ckind)
+ })
+ && structurally_same_type_impl(
+ seen_types,
+ cx,
+ a_sig.output(),
+ b_sig.output(),
+ ckind,
+ )
+ }
+ (Tuple(a_substs), Tuple(b_substs)) => {
+ a_substs.iter().eq_by(b_substs.iter(), |a_ty, b_ty| {
+ structurally_same_type_impl(seen_types, cx, a_ty, b_ty, ckind)
+ })
+ }
+ // For these, it's not quite as easy to define structural-sameness quite so easily.
+ // For the purposes of this lint, take the conservative approach and mark them as
+ // not structurally same.
+ (Dynamic(..), Dynamic(..))
+ | (Error(..), Error(..))
+ | (Closure(..), Closure(..))
+ | (Generator(..), Generator(..))
+ | (GeneratorWitness(..), GeneratorWitness(..))
+ | (Projection(..), Projection(..))
+ | (Opaque(..), Opaque(..)) => false,
+
+ // These definitely should have been caught above.
+ (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(),
+
+ // An Adt and a primitive or pointer type. This can be FFI-safe if non-null
+ // enum layout optimisation is being applied.
+ (Adt(..), other_kind) | (other_kind, Adt(..))
+ if is_primitive_or_pointer(other_kind) =>
+ {
+ let (primitive, adt) =
+ if is_primitive_or_pointer(a.kind()) { (a, b) } else { (b, a) };
+ if let Some(ty) = crate::types::repr_nullable_ptr(cx, adt, ckind) {
+ ty == primitive
+ } else {
+ compare_layouts(a, b).unwrap_or(false)
+ }
+ }
+ // Otherwise, just compare the layouts. This may fail to lint for some
+ // incompatible types, but at the very least, will stop reads into
+ // uninitialised memory.
+ _ => compare_layouts(a, b).unwrap_or(false),
+ }
+ })
+ }
+ }
+ let mut seen_types = FxHashSet::default();
+ structurally_same_type_impl(&mut seen_types, cx, a, b, ckind)
+ }
+}
+
+impl_lint_pass!(ClashingExternDeclarations => [CLASHING_EXTERN_DECLARATIONS]);
+
+impl<'tcx> LateLintPass<'tcx> for ClashingExternDeclarations {
+ fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, this_fi: &hir::ForeignItem<'_>) {
+ trace!("ClashingExternDeclarations: check_foreign_item: {:?}", this_fi);
+ if let ForeignItemKind::Fn(..) = this_fi.kind {
+ let tcx = cx.tcx;
+ if let Some(existing_hid) = self.insert(tcx, this_fi) {
+ let existing_decl_ty = tcx.type_of(tcx.hir().local_def_id(existing_hid));
+ let this_decl_ty = tcx.type_of(this_fi.def_id);
+ debug!(
+ "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}",
+ existing_hid, existing_decl_ty, this_fi.def_id, this_decl_ty
+ );
+ // Check that the declarations match.
+ if !Self::structurally_same_type(
+ cx,
+ existing_decl_ty,
+ this_decl_ty,
+ CItemKind::Declaration,
+ ) {
+ let orig_fi = tcx.hir().expect_foreign_item(existing_hid.expect_owner());
+ let orig = Self::name_of_extern_decl(tcx, orig_fi);
+
+ // We want to ensure that we use spans for both decls that include where the
+ // name was defined, whether that was from the link_name attribute or not.
+ let get_relevant_span =
+ |fi: &hir::ForeignItem<'_>| match Self::name_of_extern_decl(tcx, fi) {
+ SymbolName::Normal(_) => fi.span,
+ SymbolName::Link(_, annot_span) => fi.span.to(annot_span),
+ };
+ // Finally, emit the diagnostic.
+ tcx.struct_span_lint_hir(
+ CLASHING_EXTERN_DECLARATIONS,
+ this_fi.hir_id(),
+ get_relevant_span(this_fi),
+ |lint| {
+ let mut expected_str = DiagnosticStyledString::new();
+ expected_str.push(existing_decl_ty.fn_sig(tcx).to_string(), false);
+ let mut found_str = DiagnosticStyledString::new();
+ found_str.push(this_decl_ty.fn_sig(tcx).to_string(), true);
+
+ lint.build(if orig.get_name() == this_fi.ident.name {
+ fluent::lint::builtin_clashing_extern_same_name
+ } else {
+ fluent::lint::builtin_clashing_extern_diff_name
+ })
+ .set_arg("this_fi", this_fi.ident.name)
+ .set_arg("orig", orig.get_name())
+ .span_label(
+ get_relevant_span(orig_fi),
+ fluent::lint::previous_decl_label,
+ )
+ .span_label(get_relevant_span(this_fi), fluent::lint::mismatch_label)
+ // FIXME(davidtwco): translatable expected/found
+ .note_expected_found(&"", expected_str, &"", found_str)
+ .emit();
+ },
+ );
+ }
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `deref_nullptr` lint detects when an null pointer is dereferenced,
+ /// which causes [undefined behavior].
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run
+ /// # #![allow(unused)]
+ /// use std::ptr;
+ /// unsafe {
+ /// let x = &*ptr::null::<i32>();
+ /// let x = ptr::addr_of!(*ptr::null::<i32>());
+ /// let x = *(0 as *const i32);
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Dereferencing a null pointer causes [undefined behavior] even as a place expression,
+ /// like `&*(0 as *const i32)` or `addr_of!(*(0 as *const i32))`.
+ ///
+ /// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
+ pub DEREF_NULLPTR,
+ Warn,
+ "detects when an null pointer is dereferenced"
+}
+
+declare_lint_pass!(DerefNullPtr => [DEREF_NULLPTR]);
+
+impl<'tcx> LateLintPass<'tcx> for DerefNullPtr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) {
+ /// test if expression is a null ptr
+ fn is_null_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
+ match &expr.kind {
+ rustc_hir::ExprKind::Cast(ref expr, ref ty) => {
+ if let rustc_hir::TyKind::Ptr(_) = ty.kind {
+ return is_zero(expr) || is_null_ptr(cx, expr);
+ }
+ }
+ // check for call to `core::ptr::null` or `core::ptr::null_mut`
+ rustc_hir::ExprKind::Call(ref path, _) => {
+ if let rustc_hir::ExprKind::Path(ref qpath) = path.kind {
+ if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() {
+ return matches!(
+ cx.tcx.get_diagnostic_name(def_id),
+ Some(sym::ptr_null | sym::ptr_null_mut)
+ );
+ }
+ }
+ }
+ _ => {}
+ }
+ false
+ }
+
+ /// test if expression is the literal `0`
+ fn is_zero(expr: &hir::Expr<'_>) -> bool {
+ match &expr.kind {
+ rustc_hir::ExprKind::Lit(ref lit) => {
+ if let LitKind::Int(a, _) = lit.node {
+ return a == 0;
+ }
+ }
+ _ => {}
+ }
+ false
+ }
+
+ if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind {
+ if is_null_ptr(cx, expr_deref) {
+ cx.struct_span_lint(DEREF_NULLPTR, expr.span, |lint| {
+ let mut err = lint.build(fluent::lint::builtin_deref_nullptr);
+ err.span_label(expr.span, fluent::lint::label);
+ err.emit();
+ });
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `named_asm_labels` lint detects the use of named labels in the
+ /// inline `asm!` macro.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// use std::arch::asm;
+ ///
+ /// fn main() {
+ /// unsafe {
+ /// asm!("foo: bar");
+ /// }
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// LLVM is allowed to duplicate inline assembly blocks for any
+ /// reason, for example when it is in a function that gets inlined. Because
+ /// of this, GNU assembler [local labels] *must* be used instead of labels
+ /// with a name. Using named labels might cause assembler or linker errors.
+ ///
+ /// See the explanation in [Rust By Example] for more details.
+ ///
+ /// [local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels
+ /// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels
+ pub NAMED_ASM_LABELS,
+ Deny,
+ "named labels in inline assembly",
+}
+
+declare_lint_pass!(NamedAsmLabels => [NAMED_ASM_LABELS]);
+
+impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+ if let hir::Expr {
+ kind: hir::ExprKind::InlineAsm(hir::InlineAsm { template_strs, .. }),
+ ..
+ } = expr
+ {
+ for (template_sym, template_snippet, template_span) in template_strs.iter() {
+ let template_str = template_sym.as_str();
+ let find_label_span = |needle: &str| -> Option<Span> {
+ if let Some(template_snippet) = template_snippet {
+ let snippet = template_snippet.as_str();
+ if let Some(pos) = snippet.find(needle) {
+ let end = pos
+ + snippet[pos..]
+ .find(|c| c == ':')
+ .unwrap_or(snippet[pos..].len() - 1);
+ let inner = InnerSpan::new(pos, end);
+ return Some(template_span.from_inner(inner));
+ }
+ }
+
+ None
+ };
+
+ let mut found_labels = Vec::new();
+
+ // A semicolon might not actually be specified as a separator for all targets, but it seems like LLVM accepts it always
+ let statements = template_str.split(|c| matches!(c, '\n' | ';'));
+ for statement in statements {
+ // If there's a comment, trim it from the statement
+ let statement = statement.find("//").map_or(statement, |idx| &statement[..idx]);
+ let mut start_idx = 0;
+ for (idx, _) in statement.match_indices(':') {
+ let possible_label = statement[start_idx..idx].trim();
+ let mut chars = possible_label.chars();
+ let Some(c) = chars.next() else {
+ // Empty string means a leading ':' in this section, which is not a label
+ break
+ };
+ // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $
+ if (c.is_alphabetic() || matches!(c, '.' | '_'))
+ && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$'))
+ {
+ found_labels.push(possible_label);
+ } else {
+ // If we encounter a non-label, there cannot be any further labels, so stop checking
+ break;
+ }
+
+ start_idx = idx + 1;
+ }
+ }
+
+ debug!("NamedAsmLabels::check_expr(): found_labels: {:#?}", &found_labels);
+
+ if found_labels.len() > 0 {
+ let spans = found_labels
+ .into_iter()
+ .filter_map(|label| find_label_span(label))
+ .collect::<Vec<Span>>();
+ // If there were labels but we couldn't find a span, combine the warnings and use the template span
+ let target_spans: MultiSpan =
+ if spans.len() > 0 { spans.into() } else { (*template_span).into() };
+
+ cx.lookup_with_diagnostics(
+ NAMED_ASM_LABELS,
+ Some(target_spans),
+ |diag| {
+ diag.build(fluent::lint::builtin_asm_labels).emit();
+ },
+ BuiltinLintDiagnostics::NamedAsmLabel(
+ "only local labels of the form `<number>:` should be used in inline asm"
+ .to_string(),
+ ),
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
new file mode 100644
index 000000000..b95fc341d
--- /dev/null
+++ b/compiler/rustc_lint/src/context.rs
@@ -0,0 +1,1259 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use self::TargetLint::*;
+
+use crate::levels::LintLevelsBuilder;
+use crate::passes::{EarlyLintPassObject, LateLintPassObject};
+use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::sync;
+use rustc_errors::{add_elided_lifetime_in_path_suggestion, struct_span_err};
+use rustc_errors::{
+ Applicability, DecorateLint, LintDiagnosticBuilder, MultiSpan, SuggestionStyle,
+};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::def_id::{CrateNum, DefId};
+use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
+use rustc_middle::middle::privacy::AccessLevels;
+use rustc_middle::middle::stability;
+use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout};
+use rustc_middle::ty::print::with_no_trimmed_paths;
+use rustc_middle::ty::{self, print::Printer, subst::GenericArg, RegisteredTools, Ty, TyCtxt};
+use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId};
+use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId};
+use rustc_session::Session;
+use rustc_span::lev_distance::find_best_match_for_name;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{BytePos, Span, DUMMY_SP};
+use rustc_target::abi;
+use tracing::debug;
+
+use std::cell::Cell;
+use std::iter;
+use std::slice;
+
+/// Information about the registered lints.
+///
+/// This is basically the subset of `Context` that we can
+/// build early in the compile pipeline.
+pub struct LintStore {
+ /// Registered lints.
+ lints: Vec<&'static Lint>,
+
+ /// Constructor functions for each variety of lint pass.
+ ///
+ /// These should only be called once, but since we want to avoid locks or
+ /// interior mutability, we don't enforce this (and lints should, in theory,
+ /// be compatible with being constructed more than once, though not
+ /// necessarily in a sane manner. This is safe though.)
+ pub pre_expansion_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>,
+ pub early_passes: Vec<Box<dyn Fn() -> EarlyLintPassObject + sync::Send + sync::Sync>>,
+ pub late_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>,
+ /// This is unique in that we construct them per-module, so not once.
+ pub late_module_passes: Vec<Box<dyn Fn() -> LateLintPassObject + sync::Send + sync::Sync>>,
+
+ /// Lints indexed by name.
+ by_name: FxHashMap<String, TargetLint>,
+
+ /// Map of registered lint groups to what lints they expand to.
+ lint_groups: FxHashMap<&'static str, LintGroup>,
+}
+
+/// The target of the `by_name` map, which accounts for renaming/deprecation.
+#[derive(Debug)]
+enum TargetLint {
+ /// A direct lint target
+ Id(LintId),
+
+ /// Temporary renaming, used for easing migration pain; see #16545
+ Renamed(String, LintId),
+
+ /// Lint with this name existed previously, but has been removed/deprecated.
+ /// The string argument is the reason for removal.
+ Removed(String),
+
+ /// A lint name that should give no warnings and have no effect.
+ ///
+ /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints.
+ Ignored,
+}
+
+pub enum FindLintError {
+ NotFound,
+ Removed,
+}
+
+struct LintAlias {
+ name: &'static str,
+ /// Whether deprecation warnings should be suppressed for this alias.
+ silent: bool,
+}
+
+struct LintGroup {
+ lint_ids: Vec<LintId>,
+ from_plugin: bool,
+ depr: Option<LintAlias>,
+}
+
+#[derive(Debug)]
+pub enum CheckLintNameResult<'a> {
+ Ok(&'a [LintId]),
+ /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name.
+ NoLint(Option<Symbol>),
+ /// The lint refers to a tool that has not been registered.
+ NoTool,
+ /// The lint is either renamed or removed. This is the warning
+ /// message, and an optional new name (`None` if removed).
+ Warning(String, Option<String>),
+ /// The lint is from a tool. If the Option is None, then either
+ /// the lint does not exist in the tool or the code was not
+ /// compiled with the tool and therefore the lint was never
+ /// added to the `LintStore`. Otherwise the `LintId` will be
+ /// returned as if it where a rustc lint.
+ Tool(Result<&'a [LintId], (Option<&'a [LintId]>, String)>),
+}
+
+impl LintStore {
+ pub fn new() -> LintStore {
+ LintStore {
+ lints: vec![],
+ pre_expansion_passes: vec![],
+ early_passes: vec![],
+ late_passes: vec![],
+ late_module_passes: vec![],
+ by_name: Default::default(),
+ lint_groups: Default::default(),
+ }
+ }
+
+ pub fn get_lints<'t>(&'t self) -> &'t [&'static Lint] {
+ &self.lints
+ }
+
+ pub fn get_lint_groups<'t>(
+ &'t self,
+ ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't {
+ // This function is not used in a way which observes the order of lints.
+ #[allow(rustc::potential_query_instability)]
+ self.lint_groups
+ .iter()
+ .filter(|(_, LintGroup { depr, .. })| {
+ // Don't display deprecated lint groups.
+ depr.is_none()
+ })
+ .map(|(k, LintGroup { lint_ids, from_plugin, .. })| {
+ (*k, lint_ids.clone(), *from_plugin)
+ })
+ }
+
+ pub fn register_early_pass(
+ &mut self,
+ pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync,
+ ) {
+ self.early_passes.push(Box::new(pass));
+ }
+
+ /// This lint pass is softly deprecated. It misses expanded code and has caused a few
+ /// errors in the past. Currently, it is only used in Clippy. New implementations
+ /// should avoid using this interface, as it might be removed in the future.
+ ///
+ /// * See [rust#69838](https://github.com/rust-lang/rust/pull/69838)
+ /// * See [rust-clippy#5518](https://github.com/rust-lang/rust-clippy/pull/5518)
+ pub fn register_pre_expansion_pass(
+ &mut self,
+ pass: impl Fn() -> EarlyLintPassObject + 'static + sync::Send + sync::Sync,
+ ) {
+ self.pre_expansion_passes.push(Box::new(pass));
+ }
+
+ pub fn register_late_pass(
+ &mut self,
+ pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync,
+ ) {
+ self.late_passes.push(Box::new(pass));
+ }
+
+ pub fn register_late_mod_pass(
+ &mut self,
+ pass: impl Fn() -> LateLintPassObject + 'static + sync::Send + sync::Sync,
+ ) {
+ self.late_module_passes.push(Box::new(pass));
+ }
+
+ // Helper method for register_early/late_pass
+ pub fn register_lints(&mut self, lints: &[&'static Lint]) {
+ for lint in lints {
+ self.lints.push(lint);
+
+ let id = LintId::of(lint);
+ if self.by_name.insert(lint.name_lower(), Id(id)).is_some() {
+ bug!("duplicate specification of lint {}", lint.name_lower())
+ }
+
+ if let Some(FutureIncompatibleInfo { reason, .. }) = lint.future_incompatible {
+ if let Some(edition) = reason.edition() {
+ self.lint_groups
+ .entry(edition.lint_name())
+ .or_insert(LintGroup {
+ lint_ids: vec![],
+ from_plugin: lint.is_plugin,
+ depr: None,
+ })
+ .lint_ids
+ .push(id);
+ } else {
+ // Lints belonging to the `future_incompatible` lint group are lints where a
+ // future version of rustc will cause existing code to stop compiling.
+ // Lints tied to an edition don't count because they are opt-in.
+ self.lint_groups
+ .entry("future_incompatible")
+ .or_insert(LintGroup {
+ lint_ids: vec![],
+ from_plugin: lint.is_plugin,
+ depr: None,
+ })
+ .lint_ids
+ .push(id);
+ }
+ }
+ }
+ }
+
+ pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) {
+ self.lint_groups.insert(
+ alias,
+ LintGroup {
+ lint_ids: vec![],
+ from_plugin: false,
+ depr: Some(LintAlias { name: lint_name, silent: true }),
+ },
+ );
+ }
+
+ pub fn register_group(
+ &mut self,
+ from_plugin: bool,
+ name: &'static str,
+ deprecated_name: Option<&'static str>,
+ to: Vec<LintId>,
+ ) {
+ let new = self
+ .lint_groups
+ .insert(name, LintGroup { lint_ids: to, from_plugin, depr: None })
+ .is_none();
+ if let Some(deprecated) = deprecated_name {
+ self.lint_groups.insert(
+ deprecated,
+ LintGroup {
+ lint_ids: vec![],
+ from_plugin,
+ depr: Some(LintAlias { name, silent: false }),
+ },
+ );
+ }
+
+ if !new {
+ bug!("duplicate specification of lint group {}", name);
+ }
+ }
+
+ /// This lint should give no warning and have no effect.
+ ///
+ /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers them as tool lints.
+ #[track_caller]
+ pub fn register_ignored(&mut self, name: &str) {
+ if self.by_name.insert(name.to_string(), Ignored).is_some() {
+ bug!("duplicate specification of lint {}", name);
+ }
+ }
+
+ /// This lint has been renamed; warn about using the new name and apply the lint.
+ #[track_caller]
+ pub fn register_renamed(&mut self, old_name: &str, new_name: &str) {
+ let Some(&Id(target)) = self.by_name.get(new_name) else {
+ bug!("invalid lint renaming of {} to {}", old_name, new_name);
+ };
+ self.by_name.insert(old_name.to_string(), Renamed(new_name.to_string(), target));
+ }
+
+ pub fn register_removed(&mut self, name: &str, reason: &str) {
+ self.by_name.insert(name.into(), Removed(reason.into()));
+ }
+
+ pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> {
+ match self.by_name.get(lint_name) {
+ Some(&Id(lint_id)) => Ok(vec![lint_id]),
+ Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]),
+ Some(&Removed(_)) => Err(FindLintError::Removed),
+ Some(&Ignored) => Ok(vec![]),
+ None => loop {
+ return match self.lint_groups.get(lint_name) {
+ Some(LintGroup { lint_ids, depr, .. }) => {
+ if let Some(LintAlias { name, .. }) = depr {
+ lint_name = name;
+ continue;
+ }
+ Ok(lint_ids.clone())
+ }
+ None => Err(FindLintError::Removed),
+ };
+ },
+ }
+ }
+
+ /// Checks the validity of lint names derived from the command line.
+ pub fn check_lint_name_cmdline(
+ &self,
+ sess: &Session,
+ lint_name: &str,
+ level: Level,
+ registered_tools: &RegisteredTools,
+ ) {
+ let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name);
+ if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn(_)) {
+ struct_span_err!(
+ sess,
+ DUMMY_SP,
+ E0602,
+ "`{}` lint group is not supported with ´--force-warn´",
+ crate::WARNINGS.name_lower()
+ )
+ .emit();
+ return;
+ }
+ let db = match self.check_lint_name(lint_name_only, tool_name, registered_tools) {
+ CheckLintNameResult::Ok(_) => None,
+ CheckLintNameResult::Warning(ref msg, _) => Some(sess.struct_warn(msg)),
+ CheckLintNameResult::NoLint(suggestion) => {
+ let mut err =
+ struct_span_err!(sess, DUMMY_SP, E0602, "unknown lint: `{}`", lint_name);
+
+ if let Some(suggestion) = suggestion {
+ err.help(&format!("did you mean: `{}`", suggestion));
+ }
+
+ Some(err.forget_guarantee())
+ }
+ CheckLintNameResult::Tool(result) => match result {
+ Err((Some(_), new_name)) => Some(sess.struct_warn(&format!(
+ "lint name `{}` is deprecated \
+ and does not have an effect anymore. \
+ Use: {}",
+ lint_name, new_name
+ ))),
+ _ => None,
+ },
+ CheckLintNameResult::NoTool => Some(
+ struct_span_err!(
+ sess,
+ DUMMY_SP,
+ E0602,
+ "unknown lint tool: `{}`",
+ tool_name.unwrap()
+ )
+ .forget_guarantee(),
+ ),
+ };
+
+ if let Some(mut db) = db {
+ let msg = format!(
+ "requested on the command line with `{} {}`",
+ match level {
+ Level::Allow => "-A",
+ Level::Warn => "-W",
+ Level::ForceWarn(_) => "--force-warn",
+ Level::Deny => "-D",
+ Level::Forbid => "-F",
+ Level::Expect(_) => {
+ unreachable!("lints with the level of `expect` should not run this code");
+ }
+ },
+ lint_name
+ );
+ db.note(&msg);
+ db.emit();
+ }
+ }
+
+ /// True if this symbol represents a lint group name.
+ pub fn is_lint_group(&self, lint_name: Symbol) -> bool {
+ debug!(
+ "is_lint_group(lint_name={:?}, lint_groups={:?})",
+ lint_name,
+ self.lint_groups.keys().collect::<Vec<_>>()
+ );
+ let lint_name_str = lint_name.as_str();
+ self.lint_groups.contains_key(lint_name_str) || {
+ let warnings_name_str = crate::WARNINGS.name_lower();
+ lint_name_str == warnings_name_str
+ }
+ }
+
+ /// Checks the name of a lint for its existence, and whether it was
+ /// renamed or removed. Generates a DiagnosticBuilder containing a
+ /// warning for renamed and removed lints. This is over both lint
+ /// names from attributes and those passed on the command line. Since
+ /// it emits non-fatal warnings and there are *two* lint passes that
+ /// inspect attributes, this is only run from the late pass to avoid
+ /// printing duplicate warnings.
+ pub fn check_lint_name(
+ &self,
+ lint_name: &str,
+ tool_name: Option<Symbol>,
+ registered_tools: &RegisteredTools,
+ ) -> CheckLintNameResult<'_> {
+ if let Some(tool_name) = tool_name {
+ // FIXME: rustc and rustdoc are considered tools for lints, but not for attributes.
+ if tool_name != sym::rustc
+ && tool_name != sym::rustdoc
+ && !registered_tools.contains(&Ident::with_dummy_span(tool_name))
+ {
+ return CheckLintNameResult::NoTool;
+ }
+ }
+
+ let complete_name = if let Some(tool_name) = tool_name {
+ format!("{}::{}", tool_name, lint_name)
+ } else {
+ lint_name.to_string()
+ };
+ // If the lint was scoped with `tool::` check if the tool lint exists
+ if let Some(tool_name) = tool_name {
+ match self.by_name.get(&complete_name) {
+ None => match self.lint_groups.get(&*complete_name) {
+ // If the lint isn't registered, there are two possibilities:
+ None => {
+ // 1. The tool is currently running, so this lint really doesn't exist.
+ // FIXME: should this handle tools that never register a lint, like rustfmt?
+ tracing::debug!("lints={:?}", self.by_name.keys().collect::<Vec<_>>());
+ let tool_prefix = format!("{}::", tool_name);
+ return if self.by_name.keys().any(|lint| lint.starts_with(&tool_prefix)) {
+ self.no_lint_suggestion(&complete_name)
+ } else {
+ // 2. The tool isn't currently running, so no lints will be registered.
+ // To avoid giving a false positive, ignore all unknown lints.
+ CheckLintNameResult::Tool(Err((None, String::new())))
+ };
+ }
+ Some(LintGroup { lint_ids, .. }) => {
+ return CheckLintNameResult::Tool(Ok(&lint_ids));
+ }
+ },
+ Some(&Id(ref id)) => return CheckLintNameResult::Tool(Ok(slice::from_ref(id))),
+ // If the lint was registered as removed or renamed by the lint tool, we don't need
+ // to treat tool_lints and rustc lints different and can use the code below.
+ _ => {}
+ }
+ }
+ match self.by_name.get(&complete_name) {
+ Some(&Renamed(ref new_name, _)) => CheckLintNameResult::Warning(
+ format!("lint `{}` has been renamed to `{}`", complete_name, new_name),
+ Some(new_name.to_owned()),
+ ),
+ Some(&Removed(ref reason)) => CheckLintNameResult::Warning(
+ format!("lint `{}` has been removed: {}", complete_name, reason),
+ None,
+ ),
+ None => match self.lint_groups.get(&*complete_name) {
+ // If neither the lint, nor the lint group exists check if there is a `clippy::`
+ // variant of this lint
+ None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"),
+ Some(LintGroup { lint_ids, depr, .. }) => {
+ // Check if the lint group name is deprecated
+ if let Some(LintAlias { name, silent }) = depr {
+ let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+ return if *silent {
+ CheckLintNameResult::Ok(&lint_ids)
+ } else {
+ CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string())))
+ };
+ }
+ CheckLintNameResult::Ok(&lint_ids)
+ }
+ },
+ Some(&Id(ref id)) => CheckLintNameResult::Ok(slice::from_ref(id)),
+ Some(&Ignored) => CheckLintNameResult::Ok(&[]),
+ }
+ }
+
+ fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> {
+ let name_lower = lint_name.to_lowercase();
+
+ if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_ok() {
+ // First check if the lint name is (partly) in upper case instead of lower case...
+ return CheckLintNameResult::NoLint(Some(Symbol::intern(&name_lower)));
+ }
+ // ...if not, search for lints with a similar name
+ let groups = self.lint_groups.keys().copied().map(Symbol::intern);
+ let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower()));
+ let names: Vec<Symbol> = groups.chain(lints).collect();
+ let suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None);
+ CheckLintNameResult::NoLint(suggestion)
+ }
+
+ fn check_tool_name_for_backwards_compat(
+ &self,
+ lint_name: &str,
+ tool_name: &str,
+ ) -> CheckLintNameResult<'_> {
+ let complete_name = format!("{}::{}", tool_name, lint_name);
+ match self.by_name.get(&complete_name) {
+ None => match self.lint_groups.get(&*complete_name) {
+ // Now we are sure, that this lint exists nowhere
+ None => self.no_lint_suggestion(lint_name),
+ Some(LintGroup { lint_ids, depr, .. }) => {
+ // Reaching this would be weird, but let's cover this case anyway
+ if let Some(LintAlias { name, silent }) = depr {
+ let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap();
+ return if *silent {
+ CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name)))
+ } else {
+ CheckLintNameResult::Tool(Err((Some(&lint_ids), (*name).to_string())))
+ };
+ }
+ CheckLintNameResult::Tool(Err((Some(&lint_ids), complete_name)))
+ }
+ },
+ Some(&Id(ref id)) => {
+ CheckLintNameResult::Tool(Err((Some(slice::from_ref(id)), complete_name)))
+ }
+ Some(other) => {
+ tracing::debug!("got renamed lint {:?}", other);
+ CheckLintNameResult::NoLint(None)
+ }
+ }
+ }
+}
+
+/// Context for lint checking outside of type inference.
+pub struct LateContext<'tcx> {
+ /// Type context we're checking in.
+ pub tcx: TyCtxt<'tcx>,
+
+ /// Current body, or `None` if outside a body.
+ pub enclosing_body: Option<hir::BodyId>,
+
+ /// Type-checking results for the current body. Access using the `typeck_results`
+ /// and `maybe_typeck_results` methods, which handle querying the typeck results on demand.
+ // FIXME(eddyb) move all the code accessing internal fields like this,
+ // to this module, to avoid exposing it to lint logic.
+ pub(super) cached_typeck_results: Cell<Option<&'tcx ty::TypeckResults<'tcx>>>,
+
+ /// Parameter environment for the item we are in.
+ pub param_env: ty::ParamEnv<'tcx>,
+
+ /// Items accessible from the crate being checked.
+ pub access_levels: &'tcx AccessLevels,
+
+ /// The store of registered lints and the lint levels.
+ pub lint_store: &'tcx LintStore,
+
+ pub last_node_with_lint_attrs: hir::HirId,
+
+ /// Generic type parameters in scope for the item we are in.
+ pub generics: Option<&'tcx hir::Generics<'tcx>>,
+
+ /// We are only looking at one module
+ pub only_module: bool,
+}
+
+/// Context for lint checking of the AST, after expansion, before lowering to HIR.
+pub struct EarlyContext<'a> {
+ pub builder: LintLevelsBuilder<'a>,
+ pub buffered: LintBuffer,
+}
+
+pub trait LintPassObject: Sized {}
+
+impl LintPassObject for EarlyLintPassObject {}
+
+impl LintPassObject for LateLintPassObject {}
+
+pub trait LintContext: Sized {
+ type PassObject: LintPassObject;
+
+ fn sess(&self) -> &Session;
+ fn lints(&self) -> &LintStore;
+
+ fn lookup_with_diagnostics(
+ &self,
+ lint: &'static Lint,
+ span: Option<impl Into<MultiSpan>>,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ diagnostic: BuiltinLintDiagnostics,
+ ) {
+ self.lookup(lint, span, |lint| {
+ // We first generate a blank diagnostic.
+ let mut db = lint.build("");
+
+ // Now, set up surrounding context.
+ let sess = self.sess();
+ match diagnostic {
+ BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => {
+ let spans: Vec<_> = content
+ .char_indices()
+ .filter_map(|(i, c)| {
+ TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
+ let lo = span.lo() + BytePos(2 + i as u32);
+ (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
+ })
+ })
+ .collect();
+ let (an, s) = match spans.len() {
+ 1 => ("an ", ""),
+ _ => ("", "s"),
+ };
+ db.span_label(span, &format!(
+ "this comment contains {}invisible unicode text flow control codepoint{}",
+ an,
+ s,
+ ));
+ for (c, span) in &spans {
+ db.span_label(*span, format!("{:?}", c));
+ }
+ db.note(
+ "these kind of unicode codepoints change the way text flows on \
+ applications that support them, but can cause confusion because they \
+ change the order of characters on the screen",
+ );
+ if !spans.is_empty() {
+ db.multipart_suggestion_with_style(
+ "if their presence wasn't intentional, you can remove them",
+ spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(),
+ Applicability::MachineApplicable,
+ SuggestionStyle::HideCodeAlways,
+ );
+ }
+ },
+ BuiltinLintDiagnostics::Normal => (),
+ BuiltinLintDiagnostics::AbsPathWithModule(span) => {
+ let (sugg, app) = match sess.source_map().span_to_snippet(span) {
+ Ok(ref s) => {
+ // FIXME(Manishearth) ideally the emitting code
+ // can tell us whether or not this is global
+ let opt_colon =
+ if s.trim_start().starts_with("::") { "" } else { "::" };
+
+ (format!("crate{}{}", opt_colon, s), Applicability::MachineApplicable)
+ }
+ Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders),
+ };
+ db.span_suggestion(span, "use `crate`", sugg, app);
+ }
+ BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => {
+ db.span_label(
+ span,
+ "names from parent modules are not accessible without an explicit import",
+ );
+ }
+ BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths(
+ span_def,
+ ) => {
+ db.span_note(span_def, "the macro is defined here");
+ }
+ BuiltinLintDiagnostics::ElidedLifetimesInPaths(
+ n,
+ path_span,
+ incl_angl_brckt,
+ insertion_span,
+ ) => {
+ add_elided_lifetime_in_path_suggestion(
+ sess.source_map(),
+ &mut db,
+ n,
+ path_span,
+ incl_angl_brckt,
+ insertion_span,
+ );
+ }
+ BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => {
+ db.span_suggestion(span, &note, sugg, Applicability::MaybeIncorrect);
+ }
+ BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => {
+ if !replaces.is_empty() {
+ db.tool_only_multipart_suggestion(
+ &message,
+ replaces,
+ Applicability::MachineApplicable,
+ );
+ }
+
+ if let Some(span) = in_test_module {
+ db.span_help(
+ self.sess().source_map().guess_head_span(span),
+ "consider adding a `#[cfg(test)]` to the containing module",
+ );
+ }
+ }
+ BuiltinLintDiagnostics::RedundantImport(spans, ident) => {
+ for (span, is_imported) in spans {
+ let introduced = if is_imported { "imported" } else { "defined" };
+ db.span_label(
+ span,
+ format!("the item `{}` is already {} here", ident, introduced),
+ );
+ }
+ }
+ BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => {
+ stability::deprecation_suggestion(&mut db, "macro", suggestion, span)
+ }
+ BuiltinLintDiagnostics::UnusedDocComment(span) => {
+ db.span_label(span, "rustdoc does not generate documentation for macro invocations");
+ db.help("to document an item produced by a macro, \
+ the macro must produce the documentation as part of its expansion");
+ }
+ BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => {
+ db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable);
+ }
+ BuiltinLintDiagnostics::MissingAbi(span, default_abi) => {
+ db.span_label(span, "ABI should be specified here");
+ db.help(&format!("the default ABI is {}", default_abi.name()));
+ }
+ BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
+ db.span_label(span, "the attribute is introduced here");
+ }
+ BuiltinLintDiagnostics::ProcMacroBackCompat(note) => {
+ db.note(&note);
+ }
+ BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => {
+ db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable);
+ }
+ BuiltinLintDiagnostics::ReservedPrefix(span) => {
+ db.span_label(span, "unknown prefix");
+ db.span_suggestion_verbose(
+ span.shrink_to_hi(),
+ "insert whitespace here to avoid this being parsed as a prefix in Rust 2021",
+ " ",
+ Applicability::MachineApplicable,
+ );
+ }
+ BuiltinLintDiagnostics::UnusedBuiltinAttribute {
+ attr_name,
+ macro_name,
+ invoc_span
+ } => {
+ db.span_note(
+ invoc_span,
+ &format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`")
+ );
+ }
+ BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => {
+ if is_trailing {
+ db.note("macro invocations at the end of a block are treated as expressions");
+ db.note(&format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
+ }
+ }
+ BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => {
+ db.multipart_suggestion(
+ "wrap this expression in parentheses",
+ vec![(span.shrink_to_lo(), "(".to_string()),
+ (span.shrink_to_hi(), ")".to_string())],
+ Applicability::MachineApplicable
+ );
+ }
+ BuiltinLintDiagnostics::NamedAsmLabel(help) => {
+ db.help(&help);
+ db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information");
+ },
+ BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), None) => {
+ let Some(names_valid) = &sess.parse_sess.check_config.names_valid else {
+ bug!("it shouldn't be possible to have a diagnostic on a name if name checking is not enabled");
+ };
+ let possibilities: Vec<Symbol> = names_valid.iter().map(|s| *s).collect();
+
+ // Suggest the most probable if we found one
+ if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
+ db.span_suggestion(name_span, "did you mean", best_match, Applicability::MaybeIncorrect);
+ }
+ },
+ BuiltinLintDiagnostics::UnexpectedCfg((name, name_span), Some((value, value_span))) => {
+ let Some(values) = &sess.parse_sess.check_config.values_valid.get(&name) else {
+ bug!("it shouldn't be possible to have a diagnostic on a value whose name is not in values");
+ };
+ let possibilities: Vec<Symbol> = values.iter().map(|&s| s).collect();
+
+ // Show the full list if all possible values for a given name, but don't do it
+ // for names as the possibilities could be very long
+ if !possibilities.is_empty() {
+ {
+ let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>();
+ possibilities.sort();
+
+ let possibilities = possibilities.join(", ");
+ db.note(&format!("expected values for `{name}` are: {possibilities}"));
+ }
+
+ // Suggest the most probable if we found one
+ if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) {
+ db.span_suggestion(value_span, "did you mean", format!("\"{best_match}\""), Applicability::MaybeIncorrect);
+ }
+ } else {
+ db.note(&format!("no expected value for `{name}`"));
+ if name != sym::feature {
+ db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect);
+ }
+ }
+ },
+ BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => {
+ db.multipart_suggestion(
+ "move it to the end of the type declaration",
+ vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)],
+ Applicability::MachineApplicable,
+ );
+ db.note(
+ "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information",
+ );
+ },
+ BuiltinLintDiagnostics::SingleUseLifetime {
+ param_span,
+ use_span: Some((use_span, elide)),
+ deletion_span,
+ } => {
+ debug!(?param_span, ?use_span, ?deletion_span);
+ db.span_label(param_span, "this lifetime...");
+ db.span_label(use_span, "...is used only here");
+ let msg = "elide the single-use lifetime";
+ let (use_span, replace_lt) = if elide {
+ let use_span = sess.source_map().span_extend_while(
+ use_span,
+ char::is_whitespace,
+ ).unwrap_or(use_span);
+ (use_span, String::new())
+ } else {
+ (use_span, "'_".to_owned())
+ };
+ db.multipart_suggestion(
+ msg,
+ vec![(deletion_span, String::new()), (use_span, replace_lt)],
+ Applicability::MachineApplicable,
+ );
+ },
+ BuiltinLintDiagnostics::SingleUseLifetime {
+ param_span: _,
+ use_span: None,
+ deletion_span,
+ } => {
+ debug!(?deletion_span);
+ db.span_suggestion(
+ deletion_span,
+ "elide the unused lifetime",
+ "",
+ Applicability::MachineApplicable,
+ );
+ },
+ BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => {
+ db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string");
+ if let Some(positional_arg_for_msg) = position_sp_for_msg {
+ let msg = format!("this formatting argument uses named argument `{}` by position", named_arg_name);
+ db.span_label(positional_arg_for_msg, msg);
+ }
+
+ if let Some(positional_arg_to_replace) = position_sp_to_replace {
+ let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name };
+
+ db.span_suggestion_verbose(
+ positional_arg_to_replace,
+ "use the named argument by name to avoid ambiguity",
+ name,
+ Applicability::MaybeIncorrect,
+ );
+ }
+ }
+ }
+ // Rewrap `db`, and pass control to the user.
+ decorate(LintDiagnosticBuilder::new(db));
+ });
+ }
+
+ // FIXME: These methods should not take an Into<MultiSpan> -- instead, callers should need to
+ // set the span in their `decorate` function (preferably using set_span).
+ fn lookup<S: Into<MultiSpan>>(
+ &self,
+ lint: &'static Lint,
+ span: Option<S>,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ );
+
+ /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`,
+ /// typically generated by `#[derive(LintDiagnostic)]`).
+ fn emit_spanned_lint<S: Into<MultiSpan>>(
+ &self,
+ lint: &'static Lint,
+ span: S,
+ decorator: impl for<'a> DecorateLint<'a, ()>,
+ ) {
+ self.lookup(lint, Some(span), |diag| decorator.decorate_lint(diag));
+ }
+
+ fn struct_span_lint<S: Into<MultiSpan>>(
+ &self,
+ lint: &'static Lint,
+ span: S,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ self.lookup(lint, Some(span), decorate);
+ }
+
+ /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically
+ /// generated by `#[derive(LintDiagnostic)]`).
+ fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> DecorateLint<'a, ()>) {
+ self.lookup(lint, None as Option<Span>, |diag| decorator.decorate_lint(diag));
+ }
+
+ /// Emit a lint at the appropriate level, with no associated span.
+ fn lint(
+ &self,
+ lint: &'static Lint,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ self.lookup(lint, None as Option<Span>, decorate);
+ }
+
+ /// This returns the lint level for the given lint at the current location.
+ fn get_lint_level(&self, lint: &'static Lint) -> Level;
+
+ /// This function can be used to manually fulfill an expectation. This can
+ /// be used for lints which contain several spans, and should be suppressed,
+ /// if either location was marked with an expectation.
+ ///
+ /// Note that this function should only be called for [`LintExpectationId`]s
+ /// retrieved from the current lint pass. Buffered or manually created ids can
+ /// cause ICEs.
+ fn fulfill_expectation(&self, expectation: LintExpectationId) {
+ // We need to make sure that submitted expectation ids are correctly fulfilled suppressed
+ // and stored between compilation sessions. To not manually do these steps, we simply create
+ // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal
+ // expected lint diagnostic.
+ self.sess()
+ .struct_expect(
+ "this is a dummy diagnostic, to submit and store an expectation",
+ expectation,
+ )
+ .emit();
+ }
+}
+
+impl<'a> EarlyContext<'a> {
+ pub(crate) fn new(
+ sess: &'a Session,
+ warn_about_weird_lints: bool,
+ lint_store: &'a LintStore,
+ registered_tools: &'a RegisteredTools,
+ buffered: LintBuffer,
+ ) -> EarlyContext<'a> {
+ EarlyContext {
+ builder: LintLevelsBuilder::new(
+ sess,
+ warn_about_weird_lints,
+ lint_store,
+ registered_tools,
+ ),
+ buffered,
+ }
+ }
+}
+
+impl LintContext for LateContext<'_> {
+ type PassObject = LateLintPassObject;
+
+ /// Gets the overall compiler `Session` object.
+ fn sess(&self) -> &Session {
+ &self.tcx.sess
+ }
+
+ fn lints(&self) -> &LintStore {
+ &*self.lint_store
+ }
+
+ fn lookup<S: Into<MultiSpan>>(
+ &self,
+ lint: &'static Lint,
+ span: Option<S>,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ let hir_id = self.last_node_with_lint_attrs;
+
+ match span {
+ Some(s) => self.tcx.struct_span_lint_hir(lint, hir_id, s, decorate),
+ None => self.tcx.struct_lint_node(lint, hir_id, decorate),
+ }
+ }
+
+ fn get_lint_level(&self, lint: &'static Lint) -> Level {
+ self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0
+ }
+}
+
+impl LintContext for EarlyContext<'_> {
+ type PassObject = EarlyLintPassObject;
+
+ /// Gets the overall compiler `Session` object.
+ fn sess(&self) -> &Session {
+ &self.builder.sess()
+ }
+
+ fn lints(&self) -> &LintStore {
+ self.builder.lint_store()
+ }
+
+ fn lookup<S: Into<MultiSpan>>(
+ &self,
+ lint: &'static Lint,
+ span: Option<S>,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ self.builder.struct_lint(lint, span.map(|s| s.into()), decorate)
+ }
+
+ fn get_lint_level(&self, lint: &'static Lint) -> Level {
+ self.builder.lint_level(lint).0
+ }
+}
+
+impl<'tcx> LateContext<'tcx> {
+ /// Gets the type-checking results for the current body,
+ /// or `None` if outside a body.
+ pub fn maybe_typeck_results(&self) -> Option<&'tcx ty::TypeckResults<'tcx>> {
+ self.cached_typeck_results.get().or_else(|| {
+ self.enclosing_body.map(|body| {
+ let typeck_results = self.tcx.typeck_body(body);
+ self.cached_typeck_results.set(Some(typeck_results));
+ typeck_results
+ })
+ })
+ }
+
+ /// Gets the type-checking results for the current body.
+ /// As this will ICE if called outside bodies, only call when working with
+ /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies).
+ #[track_caller]
+ pub fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> {
+ self.maybe_typeck_results().expect("`LateContext::typeck_results` called outside of body")
+ }
+
+ /// Returns the final resolution of a `QPath`, or `Res::Err` if unavailable.
+ /// Unlike `.typeck_results().qpath_res(qpath, id)`, this can be used even outside
+ /// bodies (e.g. for paths in `hir::Ty`), without any risk of ICE-ing.
+ pub fn qpath_res(&self, qpath: &hir::QPath<'_>, id: hir::HirId) -> Res {
+ match *qpath {
+ hir::QPath::Resolved(_, ref path) => path.res,
+ hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self
+ .maybe_typeck_results()
+ .filter(|typeck_results| typeck_results.hir_owner == id.owner)
+ .or_else(|| {
+ if self.tcx.has_typeck_results(id.owner.to_def_id()) {
+ Some(self.tcx.typeck(id.owner))
+ } else {
+ None
+ }
+ })
+ .and_then(|typeck_results| typeck_results.type_dependent_def(id))
+ .map_or(Res::Err, |(kind, def_id)| Res::Def(kind, def_id)),
+ }
+ }
+
+ /// Check if a `DefId`'s path matches the given absolute type path usage.
+ ///
+ /// Anonymous scopes such as `extern` imports are matched with `kw::Empty`;
+ /// inherent `impl` blocks are matched with the name of the type.
+ ///
+ /// Instead of using this method, it is often preferable to instead use
+ /// `rustc_diagnostic_item` or a `lang_item`. This is less prone to errors
+ /// as paths get invalidated if the target definition moves.
+ ///
+ /// # Examples
+ ///
+ /// ```rust,ignore (no context or def id available)
+ /// if cx.match_def_path(def_id, &[sym::core, sym::option, sym::Option]) {
+ /// // The given `def_id` is that of an `Option` type
+ /// }
+ /// ```
+ ///
+ /// Used by clippy, but should be replaced by diagnostic items eventually.
+ pub fn match_def_path(&self, def_id: DefId, path: &[Symbol]) -> bool {
+ let names = self.get_def_path(def_id);
+
+ names.len() == path.len() && iter::zip(names, path).all(|(a, &b)| a == b)
+ }
+
+ /// Gets the absolute path of `def_id` as a vector of `Symbol`.
+ ///
+ /// # Examples
+ ///
+ /// ```rust,ignore (no context or def id available)
+ /// let def_path = cx.get_def_path(def_id);
+ /// if let &[sym::core, sym::option, sym::Option] = &def_path[..] {
+ /// // The given `def_id` is that of an `Option` type
+ /// }
+ /// ```
+ pub fn get_def_path(&self, def_id: DefId) -> Vec<Symbol> {
+ pub struct AbsolutePathPrinter<'tcx> {
+ pub tcx: TyCtxt<'tcx>,
+ }
+
+ impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> {
+ type Error = !;
+
+ type Path = Vec<Symbol>;
+ type Region = ();
+ type Type = ();
+ type DynExistential = ();
+ type Const = ();
+
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+
+ fn print_region(self, _region: ty::Region<'_>) -> Result<Self::Region, Self::Error> {
+ Ok(())
+ }
+
+ fn print_type(self, _ty: Ty<'tcx>) -> Result<Self::Type, Self::Error> {
+ Ok(())
+ }
+
+ fn print_dyn_existential(
+ self,
+ _predicates: &'tcx ty::List<ty::Binder<'tcx, ty::ExistentialPredicate<'tcx>>>,
+ ) -> Result<Self::DynExistential, Self::Error> {
+ Ok(())
+ }
+
+ fn print_const(self, _ct: ty::Const<'tcx>) -> Result<Self::Const, Self::Error> {
+ Ok(())
+ }
+
+ fn path_crate(self, cnum: CrateNum) -> Result<Self::Path, Self::Error> {
+ Ok(vec![self.tcx.crate_name(cnum)])
+ }
+
+ fn path_qualified(
+ self,
+ self_ty: Ty<'tcx>,
+ trait_ref: Option<ty::TraitRef<'tcx>>,
+ ) -> Result<Self::Path, Self::Error> {
+ if trait_ref.is_none() {
+ if let ty::Adt(def, substs) = self_ty.kind() {
+ return self.print_def_path(def.did(), substs);
+ }
+ }
+
+ // This shouldn't ever be needed, but just in case:
+ with_no_trimmed_paths!({
+ Ok(vec![match trait_ref {
+ Some(trait_ref) => Symbol::intern(&format!("{:?}", trait_ref)),
+ None => Symbol::intern(&format!("<{}>", self_ty)),
+ }])
+ })
+ }
+
+ fn path_append_impl(
+ self,
+ print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+ _disambiguated_data: &DisambiguatedDefPathData,
+ self_ty: Ty<'tcx>,
+ trait_ref: Option<ty::TraitRef<'tcx>>,
+ ) -> Result<Self::Path, Self::Error> {
+ let mut path = print_prefix(self)?;
+
+ // This shouldn't ever be needed, but just in case:
+ path.push(match trait_ref {
+ Some(trait_ref) => {
+ with_no_trimmed_paths!(Symbol::intern(&format!(
+ "<impl {} for {}>",
+ trait_ref.print_only_trait_path(),
+ self_ty
+ )))
+ }
+ None => {
+ with_no_trimmed_paths!(Symbol::intern(&format!("<impl {}>", self_ty)))
+ }
+ });
+
+ Ok(path)
+ }
+
+ fn path_append(
+ self,
+ print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+ disambiguated_data: &DisambiguatedDefPathData,
+ ) -> Result<Self::Path, Self::Error> {
+ let mut path = print_prefix(self)?;
+
+ // Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs.
+ if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data {
+ return Ok(path);
+ }
+
+ path.push(Symbol::intern(&disambiguated_data.data.to_string()));
+ Ok(path)
+ }
+
+ fn path_generic_args(
+ self,
+ print_prefix: impl FnOnce(Self) -> Result<Self::Path, Self::Error>,
+ _args: &[GenericArg<'tcx>],
+ ) -> Result<Self::Path, Self::Error> {
+ print_prefix(self)
+ }
+ }
+
+ AbsolutePathPrinter { tcx: self.tcx }.print_def_path(def_id, &[]).unwrap()
+ }
+}
+
+impl<'tcx> abi::HasDataLayout for LateContext<'tcx> {
+ #[inline]
+ fn data_layout(&self) -> &abi::TargetDataLayout {
+ &self.tcx.data_layout
+ }
+}
+
+impl<'tcx> ty::layout::HasTyCtxt<'tcx> for LateContext<'tcx> {
+ #[inline]
+ fn tcx(&self) -> TyCtxt<'tcx> {
+ self.tcx
+ }
+}
+
+impl<'tcx> ty::layout::HasParamEnv<'tcx> for LateContext<'tcx> {
+ #[inline]
+ fn param_env(&self) -> ty::ParamEnv<'tcx> {
+ self.param_env
+ }
+}
+
+impl<'tcx> LayoutOfHelpers<'tcx> for LateContext<'tcx> {
+ type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
+
+ #[inline]
+ fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
+ err
+ }
+}
+
+pub fn parse_lint_and_tool_name(lint_name: &str) -> (Option<Symbol>, &str) {
+ match lint_name.split_once("::") {
+ Some((tool_name, lint_name)) => {
+ let tool_name = Symbol::intern(tool_name);
+
+ (Some(tool_name), lint_name)
+ }
+ None => (None, lint_name),
+ }
+}
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
new file mode 100644
index 000000000..d13711c3a
--- /dev/null
+++ b/compiler/rustc_lint/src/early.rs
@@ -0,0 +1,456 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::context::{EarlyContext, LintContext, LintStore};
+use crate::passes::{EarlyLintPass, EarlyLintPassObject};
+use rustc_ast::ptr::P;
+use rustc_ast::visit::{self as ast_visit, Visitor};
+use rustc_ast::{self as ast, walk_list, HasAttrs};
+use rustc_middle::ty::RegisteredTools;
+use rustc_session::lint::{BufferedEarlyLint, LintBuffer, LintPass};
+use rustc_session::Session;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+use std::slice;
+use tracing::debug;
+
+macro_rules! run_early_pass { ($cx:expr, $f:ident, $($args:expr),*) => ({
+ $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+pub struct EarlyContextAndPass<'a, T: EarlyLintPass> {
+ context: EarlyContext<'a>,
+ pass: T,
+}
+
+impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> {
+ fn check_id(&mut self, id: ast::NodeId) {
+ for early_lint in self.context.buffered.take(id) {
+ let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint;
+ self.context.lookup_with_diagnostics(
+ lint_id.lint,
+ Some(span),
+ |lint| {
+ lint.build(&msg).emit();
+ },
+ diagnostic,
+ );
+ }
+ }
+
+ /// Merge the lints specified by any lint attributes into the
+ /// current lint context, call the provided function, then reset the
+ /// lints in effect to their previous state.
+ fn with_lint_attrs<F>(&mut self, id: ast::NodeId, attrs: &'a [ast::Attribute], f: F)
+ where
+ F: FnOnce(&mut Self),
+ {
+ let is_crate_node = id == ast::CRATE_NODE_ID;
+ let push = self.context.builder.push(attrs, is_crate_node, None);
+
+ self.check_id(id);
+ debug!("early context: enter_attrs({:?})", attrs);
+ run_early_pass!(self, enter_lint_attrs, attrs);
+ f(self);
+ debug!("early context: exit_attrs({:?})", attrs);
+ run_early_pass!(self, exit_lint_attrs, attrs);
+ self.context.builder.pop(push);
+ }
+}
+
+impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T> {
+ fn visit_param(&mut self, param: &'a ast::Param) {
+ self.with_lint_attrs(param.id, &param.attrs, |cx| {
+ run_early_pass!(cx, check_param, param);
+ ast_visit::walk_param(cx, param);
+ });
+ }
+
+ fn visit_item(&mut self, it: &'a ast::Item) {
+ self.with_lint_attrs(it.id, &it.attrs, |cx| {
+ run_early_pass!(cx, check_item, it);
+ ast_visit::walk_item(cx, it);
+ run_early_pass!(cx, check_item_post, it);
+ })
+ }
+
+ fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) {
+ self.with_lint_attrs(it.id, &it.attrs, |cx| {
+ ast_visit::walk_foreign_item(cx, it);
+ })
+ }
+
+ fn visit_pat(&mut self, p: &'a ast::Pat) {
+ run_early_pass!(self, check_pat, p);
+ self.check_id(p.id);
+ ast_visit::walk_pat(self, p);
+ run_early_pass!(self, check_pat_post, p);
+ }
+
+ fn visit_anon_const(&mut self, c: &'a ast::AnonConst) {
+ self.check_id(c.id);
+ ast_visit::walk_anon_const(self, c);
+ }
+
+ fn visit_expr(&mut self, e: &'a ast::Expr) {
+ self.with_lint_attrs(e.id, &e.attrs, |cx| {
+ run_early_pass!(cx, check_expr, e);
+ ast_visit::walk_expr(cx, e);
+ })
+ }
+
+ fn visit_expr_field(&mut self, f: &'a ast::ExprField) {
+ self.with_lint_attrs(f.id, &f.attrs, |cx| {
+ ast_visit::walk_expr_field(cx, f);
+ })
+ }
+
+ fn visit_stmt(&mut self, s: &'a ast::Stmt) {
+ // Add the statement's lint attributes to our
+ // current state when checking the statement itself.
+ // This allows us to handle attributes like
+ // `#[allow(unused_doc_comments)]`, which apply to
+ // sibling attributes on the same target
+ //
+ // Note that statements get their attributes from
+ // the AST struct that they wrap (e.g. an item)
+ self.with_lint_attrs(s.id, s.attrs(), |cx| {
+ run_early_pass!(cx, check_stmt, s);
+ cx.check_id(s.id);
+ });
+ // The visitor for the AST struct wrapped
+ // by the statement (e.g. `Item`) will call
+ // `with_lint_attrs`, so do this walk
+ // outside of the above `with_lint_attrs` call
+ ast_visit::walk_stmt(self, s);
+ }
+
+ fn visit_fn(&mut self, fk: ast_visit::FnKind<'a>, span: Span, id: ast::NodeId) {
+ run_early_pass!(self, check_fn, fk, span, id);
+ self.check_id(id);
+ ast_visit::walk_fn(self, fk, span);
+
+ // Explicitly check for lints associated with 'closure_id', since
+ // it does not have a corresponding AST node
+ if let ast_visit::FnKind::Fn(_, _, sig, _, _, _) = fk {
+ if let ast::Async::Yes { closure_id, .. } = sig.header.asyncness {
+ self.check_id(closure_id);
+ }
+ }
+ }
+
+ fn visit_variant_data(&mut self, s: &'a ast::VariantData) {
+ if let Some(ctor_hir_id) = s.ctor_id() {
+ self.check_id(ctor_hir_id);
+ }
+ ast_visit::walk_struct_def(self, s);
+ }
+
+ fn visit_field_def(&mut self, s: &'a ast::FieldDef) {
+ self.with_lint_attrs(s.id, &s.attrs, |cx| {
+ ast_visit::walk_field_def(cx, s);
+ })
+ }
+
+ fn visit_variant(&mut self, v: &'a ast::Variant) {
+ self.with_lint_attrs(v.id, &v.attrs, |cx| {
+ run_early_pass!(cx, check_variant, v);
+ ast_visit::walk_variant(cx, v);
+ })
+ }
+
+ fn visit_ty(&mut self, t: &'a ast::Ty) {
+ run_early_pass!(self, check_ty, t);
+ self.check_id(t.id);
+ ast_visit::walk_ty(self, t);
+ }
+
+ fn visit_ident(&mut self, ident: Ident) {
+ run_early_pass!(self, check_ident, ident);
+ }
+
+ fn visit_local(&mut self, l: &'a ast::Local) {
+ self.with_lint_attrs(l.id, &l.attrs, |cx| {
+ run_early_pass!(cx, check_local, l);
+ ast_visit::walk_local(cx, l);
+ })
+ }
+
+ fn visit_block(&mut self, b: &'a ast::Block) {
+ run_early_pass!(self, check_block, b);
+ self.check_id(b.id);
+ ast_visit::walk_block(self, b);
+ }
+
+ fn visit_arm(&mut self, a: &'a ast::Arm) {
+ self.with_lint_attrs(a.id, &a.attrs, |cx| {
+ run_early_pass!(cx, check_arm, a);
+ ast_visit::walk_arm(cx, a);
+ })
+ }
+
+ fn visit_expr_post(&mut self, e: &'a ast::Expr) {
+ // Explicitly check for lints associated with 'closure_id', since
+ // it does not have a corresponding AST node
+ match e.kind {
+ ast::ExprKind::Closure(_, _, ast::Async::Yes { closure_id, .. }, ..)
+ | ast::ExprKind::Async(_, closure_id, ..) => self.check_id(closure_id),
+ _ => {}
+ }
+ }
+
+ fn visit_generic_arg(&mut self, arg: &'a ast::GenericArg) {
+ run_early_pass!(self, check_generic_arg, arg);
+ ast_visit::walk_generic_arg(self, arg);
+ }
+
+ fn visit_generic_param(&mut self, param: &'a ast::GenericParam) {
+ run_early_pass!(self, check_generic_param, param);
+ self.check_id(param.id);
+ ast_visit::walk_generic_param(self, param);
+ }
+
+ fn visit_generics(&mut self, g: &'a ast::Generics) {
+ run_early_pass!(self, check_generics, g);
+ ast_visit::walk_generics(self, g);
+ }
+
+ fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) {
+ ast_visit::walk_where_predicate(self, p);
+ }
+
+ fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef, m: &'a ast::TraitBoundModifier) {
+ run_early_pass!(self, check_poly_trait_ref, t, m);
+ ast_visit::walk_poly_trait_ref(self, t, m);
+ }
+
+ fn visit_assoc_item(&mut self, item: &'a ast::AssocItem, ctxt: ast_visit::AssocCtxt) {
+ self.with_lint_attrs(item.id, &item.attrs, |cx| match ctxt {
+ ast_visit::AssocCtxt::Trait => {
+ run_early_pass!(cx, check_trait_item, item);
+ ast_visit::walk_assoc_item(cx, item, ctxt);
+ }
+ ast_visit::AssocCtxt::Impl => {
+ run_early_pass!(cx, check_impl_item, item);
+ ast_visit::walk_assoc_item(cx, item, ctxt);
+ }
+ });
+ }
+
+ fn visit_lifetime(&mut self, lt: &'a ast::Lifetime, _: ast_visit::LifetimeCtxt) {
+ self.check_id(lt.id);
+ }
+
+ fn visit_path(&mut self, p: &'a ast::Path, id: ast::NodeId) {
+ self.check_id(id);
+ ast_visit::walk_path(self, p);
+ }
+
+ fn visit_path_segment(&mut self, path_span: Span, s: &'a ast::PathSegment) {
+ self.check_id(s.id);
+ ast_visit::walk_path_segment(self, path_span, s);
+ }
+
+ fn visit_attribute(&mut self, attr: &'a ast::Attribute) {
+ run_early_pass!(self, check_attribute, attr);
+ }
+
+ fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) {
+ run_early_pass!(self, check_mac_def, mac, id);
+ self.check_id(id);
+ }
+
+ fn visit_mac_call(&mut self, mac: &'a ast::MacCall) {
+ run_early_pass!(self, check_mac, mac);
+ ast_visit::walk_mac(self, mac);
+ }
+}
+
+struct EarlyLintPassObjects<'a> {
+ lints: &'a mut [EarlyLintPassObject],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for EarlyLintPassObjects<'_> {
+ fn name(&self) -> &'static str {
+ panic!()
+ }
+}
+
+macro_rules! expand_early_lint_pass_impl_methods {
+ ([$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
+ for obj in self.lints.iter_mut() {
+ obj.$name(context, $($param),*);
+ }
+ })*
+ )
+}
+
+macro_rules! early_lint_pass_impl {
+ ([], [$($methods:tt)*]) => (
+ impl EarlyLintPass for EarlyLintPassObjects<'_> {
+ expand_early_lint_pass_impl_methods!([$($methods)*]);
+ }
+ )
+}
+
+crate::early_lint_methods!(early_lint_pass_impl, []);
+
+/// Early lints work on different nodes - either on the crate root, or on freshly loaded modules.
+/// This trait generalizes over those nodes.
+pub trait EarlyCheckNode<'a>: Copy {
+ fn id(self) -> ast::NodeId;
+ fn attrs<'b>(self) -> &'b [ast::Attribute]
+ where
+ 'a: 'b;
+ fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>)
+ where
+ 'a: 'b;
+}
+
+impl<'a> EarlyCheckNode<'a> for &'a ast::Crate {
+ fn id(self) -> ast::NodeId {
+ ast::CRATE_NODE_ID
+ }
+ fn attrs<'b>(self) -> &'b [ast::Attribute]
+ where
+ 'a: 'b,
+ {
+ &self.attrs
+ }
+ fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>)
+ where
+ 'a: 'b,
+ {
+ run_early_pass!(cx, check_crate, self);
+ ast_visit::walk_crate(cx, self);
+ run_early_pass!(cx, check_crate_post, self);
+ }
+}
+
+impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [P<ast::Item>]) {
+ fn id(self) -> ast::NodeId {
+ self.0
+ }
+ fn attrs<'b>(self) -> &'b [ast::Attribute]
+ where
+ 'a: 'b,
+ {
+ self.1
+ }
+ fn check<'b>(self, cx: &mut EarlyContextAndPass<'b, impl EarlyLintPass>)
+ where
+ 'a: 'b,
+ {
+ walk_list!(cx, visit_attribute, self.1);
+ walk_list!(cx, visit_item, self.2);
+ }
+}
+
+fn early_lint_node<'a>(
+ sess: &Session,
+ warn_about_weird_lints: bool,
+ lint_store: &LintStore,
+ registered_tools: &RegisteredTools,
+ buffered: LintBuffer,
+ pass: impl EarlyLintPass,
+ check_node: impl EarlyCheckNode<'a>,
+) -> LintBuffer {
+ let mut cx = EarlyContextAndPass {
+ context: EarlyContext::new(
+ sess,
+ warn_about_weird_lints,
+ lint_store,
+ registered_tools,
+ buffered,
+ ),
+ pass,
+ };
+
+ cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx));
+ cx.context.buffered
+}
+
+pub fn check_ast_node<'a>(
+ sess: &Session,
+ pre_expansion: bool,
+ lint_store: &LintStore,
+ registered_tools: &RegisteredTools,
+ lint_buffer: Option<LintBuffer>,
+ builtin_lints: impl EarlyLintPass,
+ check_node: impl EarlyCheckNode<'a>,
+) {
+ let passes =
+ if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes };
+ let mut passes: Vec<_> = passes.iter().map(|p| (p)()).collect();
+ let mut buffered = lint_buffer.unwrap_or_default();
+
+ if sess.opts.unstable_opts.no_interleave_lints {
+ for (i, pass) in passes.iter_mut().enumerate() {
+ buffered =
+ sess.prof.extra_verbose_generic_activity("run_lint", pass.name()).run(|| {
+ early_lint_node(
+ sess,
+ !pre_expansion && i == 0,
+ lint_store,
+ registered_tools,
+ buffered,
+ EarlyLintPassObjects { lints: slice::from_mut(pass) },
+ check_node,
+ )
+ });
+ }
+ } else {
+ buffered = early_lint_node(
+ sess,
+ !pre_expansion,
+ lint_store,
+ registered_tools,
+ buffered,
+ builtin_lints,
+ check_node,
+ );
+
+ if !passes.is_empty() {
+ buffered = early_lint_node(
+ sess,
+ false,
+ lint_store,
+ registered_tools,
+ buffered,
+ EarlyLintPassObjects { lints: &mut passes[..] },
+ check_node,
+ );
+ }
+ }
+
+ // All of the buffered lints should have been emitted at this point.
+ // If not, that means that we somehow buffered a lint for a node id
+ // that was not lint-checked (perhaps it doesn't exist?). This is a bug.
+ for (id, lints) in buffered.map {
+ for early_lint in lints {
+ sess.delay_span_bug(
+ early_lint.span,
+ &format!(
+ "failed to process buffered lint here (dummy = {})",
+ id == ast::DUMMY_NODE_ID
+ ),
+ );
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
new file mode 100644
index 000000000..f41ee6404
--- /dev/null
+++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs
@@ -0,0 +1,88 @@
+use crate::{context::LintContext, LateContext, LateLintPass};
+use rustc_errors::fluent;
+use rustc_hir as hir;
+use rustc_middle::ty::{visit::TypeVisitable, Ty};
+use rustc_span::{symbol::sym, Span};
+
+declare_lint! {
+ /// The `enum_intrinsics_non_enums` lint detects calls to
+ /// intrinsic functions that require an enum ([`core::mem::discriminant`],
+ /// [`core::mem::variant_count`]), but are called with a non-enum type.
+ ///
+ /// [`core::mem::discriminant`]: https://doc.rust-lang.org/core/mem/fn.discriminant.html
+ /// [`core::mem::variant_count`]: https://doc.rust-lang.org/core/mem/fn.variant_count.html
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(enum_intrinsics_non_enums)]
+ /// core::mem::discriminant::<i32>(&123);
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// In order to accept any enum, the `mem::discriminant` and
+ /// `mem::variant_count` functions are generic over a type `T`.
+ /// This makes it technically possible for `T` to be a non-enum,
+ /// in which case the return value is unspecified.
+ ///
+ /// This lint prevents such incorrect usage of these functions.
+ ENUM_INTRINSICS_NON_ENUMS,
+ Deny,
+ "detects calls to `core::mem::discriminant` and `core::mem::variant_count` with non-enum types"
+}
+
+declare_lint_pass!(EnumIntrinsicsNonEnums => [ENUM_INTRINSICS_NON_ENUMS]);
+
+/// Returns `true` if we know for sure that the given type is not an enum. Note that for cases where
+/// the type is generic, we can't be certain if it will be an enum so we have to assume that it is.
+fn is_non_enum(t: Ty<'_>) -> bool {
+ !t.is_enum() && !t.needs_subst()
+}
+
+fn enforce_mem_discriminant(
+ cx: &LateContext<'_>,
+ func_expr: &hir::Expr<'_>,
+ expr_span: Span,
+ args_span: Span,
+) {
+ let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
+ if is_non_enum(ty_param) {
+ cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, expr_span, |builder| {
+ builder
+ .build(fluent::lint::enum_intrinsics_mem_discriminant)
+ .set_arg("ty_param", ty_param)
+ .span_note(args_span, fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) {
+ let ty_param = cx.typeck_results().node_substs(func_expr.hir_id).type_at(0);
+ if is_non_enum(ty_param) {
+ cx.struct_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, |builder| {
+ builder
+ .build(fluent::lint::enum_intrinsics_mem_variant)
+ .set_arg("ty_param", ty_param)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for EnumIntrinsicsNonEnums {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
+ let hir::ExprKind::Call(func, args) = &expr.kind else { return };
+ let hir::ExprKind::Path(qpath) = &func.kind else { return };
+ let Some(def_id) = cx.qpath_res(qpath, func.hir_id).opt_def_id() else { return };
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return };
+ match name {
+ sym::mem_discriminant => enforce_mem_discriminant(cx, func, expr.span, args[0].span),
+ sym::mem_variant_count => enforce_mem_variant_count(cx, func, expr.span),
+ _ => {}
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
new file mode 100644
index 000000000..699e81543
--- /dev/null
+++ b/compiler/rustc_lint/src/expect.rs
@@ -0,0 +1,59 @@
+use crate::builtin;
+use rustc_errors::fluent;
+use rustc_hir::HirId;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::{lint::LintExpectation, ty::TyCtxt};
+use rustc_session::lint::LintExpectationId;
+use rustc_span::symbol::sym;
+use rustc_span::Symbol;
+
+pub(crate) fn provide(providers: &mut Providers) {
+ *providers = Providers { check_expectations, ..*providers };
+}
+
+fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) {
+ if !tcx.sess.features_untracked().enabled(sym::lint_reasons) {
+ return;
+ }
+
+ let fulfilled_expectations = tcx.sess.diagnostic().steal_fulfilled_expectation_ids();
+ let lint_expectations = &tcx.lint_levels(()).lint_expectations;
+
+ for (id, expectation) in lint_expectations {
+ // This check will always be true, since `lint_expectations` only
+ // holds stable ids
+ if let LintExpectationId::Stable { hir_id, .. } = id {
+ if !fulfilled_expectations.contains(&id)
+ && tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter))
+ {
+ emit_unfulfilled_expectation_lint(tcx, *hir_id, expectation);
+ }
+ } else {
+ unreachable!("at this stage all `LintExpectationId`s are stable");
+ }
+ }
+}
+
+fn emit_unfulfilled_expectation_lint(
+ tcx: TyCtxt<'_>,
+ hir_id: HirId,
+ expectation: &LintExpectation,
+) {
+ tcx.struct_span_lint_hir(
+ builtin::UNFULFILLED_LINT_EXPECTATIONS,
+ hir_id,
+ expectation.emission_span,
+ |diag| {
+ let mut diag = diag.build(fluent::lint::expectation);
+ if let Some(rationale) = expectation.reason {
+ diag.note(rationale.as_str());
+ }
+
+ if expectation.is_unfulfilled_lint_expectations {
+ diag.note(fluent::lint::note);
+ }
+
+ diag.emit();
+ },
+ );
+}
diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
new file mode 100644
index 000000000..fe2712525
--- /dev/null
+++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs
@@ -0,0 +1,141 @@
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use ast::util::unicode::{contains_text_flow_control_chars, TEXT_FLOW_CONTROL_CHARS};
+use rustc_ast as ast;
+use rustc_errors::{fluent, Applicability, SuggestionStyle};
+use rustc_span::{BytePos, Span, Symbol};
+
+declare_lint! {
+ /// The `text_direction_codepoint_in_literal` lint detects Unicode codepoints that change the
+ /// visual representation of text on screen in a way that does not correspond to their on
+ /// memory representation.
+ ///
+ /// ### Explanation
+ ///
+ /// The unicode characters `\u{202A}`, `\u{202B}`, `\u{202D}`, `\u{202E}`, `\u{2066}`,
+ /// `\u{2067}`, `\u{2068}`, `\u{202C}` and `\u{2069}` make the flow of text on screen change
+ /// its direction on software that supports these codepoints. This makes the text "abc" display
+ /// as "cba" on screen. By leveraging software that supports these, people can write specially
+ /// crafted literals that make the surrounding code seem like it's performing one action, when
+ /// in reality it is performing another. Because of this, we proactively lint against their
+ /// presence to avoid surprises.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(text_direction_codepoint_in_literal)]
+ /// fn main() {
+ /// println!("{:?}", '‮');
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ pub TEXT_DIRECTION_CODEPOINT_IN_LITERAL,
+ Deny,
+ "detect special Unicode codepoints that affect the visual representation of text on screen, \
+ changing the direction in which text flows",
+}
+
+declare_lint_pass!(HiddenUnicodeCodepoints => [TEXT_DIRECTION_CODEPOINT_IN_LITERAL]);
+
+impl HiddenUnicodeCodepoints {
+ fn lint_text_direction_codepoint(
+ &self,
+ cx: &EarlyContext<'_>,
+ text: Symbol,
+ span: Span,
+ padding: u32,
+ point_at_inner_spans: bool,
+ label: &str,
+ ) {
+ // Obtain the `Span`s for each of the forbidden chars.
+ let spans: Vec<_> = text
+ .as_str()
+ .char_indices()
+ .filter_map(|(i, c)| {
+ TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| {
+ let lo = span.lo() + BytePos(i as u32 + padding);
+ (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32)))
+ })
+ })
+ .collect();
+
+ cx.struct_span_lint(TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, |lint| {
+ let mut err = lint.build(fluent::lint::hidden_unicode_codepoints);
+ err.set_arg("label", label);
+ err.set_arg("count", spans.len());
+ err.span_label(span, fluent::lint::label);
+ err.note(fluent::lint::note);
+ if point_at_inner_spans {
+ for (c, span) in &spans {
+ err.span_label(*span, format!("{:?}", c));
+ }
+ }
+ if point_at_inner_spans && !spans.is_empty() {
+ err.multipart_suggestion_with_style(
+ fluent::lint::suggestion_remove,
+ spans.iter().map(|(_, span)| (*span, "".to_string())).collect(),
+ Applicability::MachineApplicable,
+ SuggestionStyle::HideCodeAlways,
+ );
+ err.multipart_suggestion(
+ fluent::lint::suggestion_escape,
+ spans
+ .into_iter()
+ .map(|(c, span)| {
+ let c = format!("{:?}", c);
+ (span, c[1..c.len() - 1].to_string())
+ })
+ .collect(),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ // FIXME: in other suggestions we've reversed the inner spans of doc comments. We
+ // should do the same here to provide the same good suggestions as we do for
+ // literals above.
+ err.set_arg(
+ "escaped",
+ spans
+ .into_iter()
+ .map(|(c, _)| format!("{:?}", c))
+ .collect::<Vec<String>>()
+ .join(", "),
+ );
+ err.note(fluent::lint::suggestion_remove);
+ err.note(fluent::lint::no_suggestion_note_escape);
+ }
+ err.emit();
+ });
+ }
+}
+impl EarlyLintPass for HiddenUnicodeCodepoints {
+ fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) {
+ if let ast::AttrKind::DocComment(_, comment) = attr.kind {
+ if contains_text_flow_control_chars(comment.as_str()) {
+ self.lint_text_direction_codepoint(cx, comment, attr.span, 0, false, "doc comment");
+ }
+ }
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
+ // byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString`
+ let (text, span, padding) = match &expr.kind {
+ ast::ExprKind::Lit(ast::Lit { token, kind, span }) => {
+ let text = token.symbol;
+ if !contains_text_flow_control_chars(text.as_str()) {
+ return;
+ }
+ let padding = match kind {
+ // account for `"` or `'`
+ ast::LitKind::Str(_, ast::StrStyle::Cooked) | ast::LitKind::Char(_) => 1,
+ // account for `r###"`
+ ast::LitKind::Str(_, ast::StrStyle::Raw(val)) => *val as u32 + 2,
+ _ => return,
+ };
+ (text, span, padding)
+ }
+ _ => return,
+ };
+ self.lint_text_direction_codepoint(cx, text, *span, padding, true, "literal");
+ }
+}
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
new file mode 100644
index 000000000..c26d78247
--- /dev/null
+++ b/compiler/rustc_lint/src/internal.rs
@@ -0,0 +1,469 @@
+//! Some lints that are only useful in the compiler or crates that use compiler internals, such as
+//! Clippy.
+
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_errors::{fluent, Applicability};
+use rustc_hir::def::Res;
+use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
+use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene::{ExpnKind, MacroKind};
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::Span;
+use tracing::debug;
+
+declare_tool_lint! {
+ pub rustc::DEFAULT_HASH_TYPES,
+ Allow,
+ "forbid HashMap and HashSet and suggest the FxHash* variants",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]);
+
+impl LateLintPass<'_> for DefaultHashTypes {
+ fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
+ let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return };
+ if matches!(cx.tcx.hir().get(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) {
+ // don't lint imports, only actual usages
+ return;
+ }
+ let replace = match cx.tcx.get_diagnostic_name(def_id) {
+ Some(sym::HashMap) => "FxHashMap",
+ Some(sym::HashSet) => "FxHashSet",
+ _ => return,
+ };
+ cx.struct_span_lint(DEFAULT_HASH_TYPES, path.span, |lint| {
+ lint.build(fluent::lint::default_hash_types)
+ .set_arg("preferred", replace)
+ .set_arg("used", cx.tcx.item_name(def_id))
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+/// Helper function for lints that check for expressions with calls and use typeck results to
+/// get the `DefId` and `SubstsRef` of the function.
+fn typeck_results_of_method_fn<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &Expr<'_>,
+) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
+ match expr.kind {
+ ExprKind::MethodCall(segment, _, _)
+ if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
+ {
+ Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
+ },
+ _ => {
+ match cx.typeck_results().node_type(expr.hir_id).kind() {
+ &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
+ _ => None,
+ }
+ }
+ }
+}
+
+declare_tool_lint! {
+ pub rustc::POTENTIAL_QUERY_INSTABILITY,
+ Allow,
+ "require explicit opt-in when using potentially unstable methods or functions",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
+
+impl LateLintPass<'_> for QueryStability {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
+ if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
+ let def_id = instance.def_id();
+ if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
+ cx.struct_span_lint(POTENTIAL_QUERY_INSTABILITY, span, |lint| {
+ lint.build(fluent::lint::query_instability)
+ .set_arg("query", cx.tcx.item_name(def_id))
+ .note(fluent::lint::note)
+ .emit();
+ })
+ }
+ }
+ }
+}
+
+declare_tool_lint! {
+ pub rustc::USAGE_OF_TY_TYKIND,
+ Allow,
+ "usage of `ty::TyKind` outside of the `ty::sty` module",
+ report_in_external_macro: true
+}
+
+declare_tool_lint! {
+ pub rustc::USAGE_OF_QUALIFIED_TY,
+ Allow,
+ "using `ty::{Ty,TyCtxt}` instead of importing it",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(TyTyKind => [
+ USAGE_OF_TY_TYKIND,
+ USAGE_OF_QUALIFIED_TY,
+]);
+
+impl<'tcx> LateLintPass<'tcx> for TyTyKind {
+ fn check_path(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ path: &'tcx rustc_hir::Path<'tcx>,
+ _: rustc_hir::HirId,
+ ) {
+ if let Some(segment) = path.segments.iter().nth_back(1)
+ && let Some(res) = &segment.res
+ && lint_ty_kind_usage(cx, res)
+ {
+ let span = path.span.with_hi(
+ segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
+ );
+ cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
+ lint.build(fluent::lint::tykind_kind)
+ .span_suggestion(
+ span,
+ fluent::lint::suggestion,
+ "ty",
+ Applicability::MaybeIncorrect, // ty maybe needs an import
+ )
+ .emit();
+ });
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
+ match &ty.kind {
+ TyKind::Path(QPath::Resolved(_, path)) => {
+ if lint_ty_kind_usage(cx, &path.res) {
+ cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
+ let hir = cx.tcx.hir();
+ match hir.find(hir.get_parent_node(ty.hir_id)) {
+ Some(Node::Pat(Pat {
+ kind:
+ PatKind::Path(qpath)
+ | PatKind::TupleStruct(qpath, ..)
+ | PatKind::Struct(qpath, ..),
+ ..
+ })) => {
+ if let QPath::TypeRelative(qpath_ty, ..) = qpath
+ && qpath_ty.hir_id == ty.hir_id
+ {
+ lint.build(fluent::lint::tykind_kind)
+ .span_suggestion(
+ path.span,
+ fluent::lint::suggestion,
+ "ty",
+ Applicability::MaybeIncorrect, // ty maybe needs an import
+ )
+ .emit();
+ return;
+ }
+ }
+ Some(Node::Expr(Expr {
+ kind: ExprKind::Path(qpath),
+ ..
+ })) => {
+ if let QPath::TypeRelative(qpath_ty, ..) = qpath
+ && qpath_ty.hir_id == ty.hir_id
+ {
+ lint.build(fluent::lint::tykind_kind)
+ .span_suggestion(
+ path.span,
+ fluent::lint::suggestion,
+ "ty",
+ Applicability::MaybeIncorrect, // ty maybe needs an import
+ )
+ .emit();
+ return;
+ }
+ }
+ // Can't unify these two branches because qpath below is `&&` and above is `&`
+ // and `A | B` paths don't play well together with adjustments, apparently.
+ Some(Node::Expr(Expr {
+ kind: ExprKind::Struct(qpath, ..),
+ ..
+ })) => {
+ if let QPath::TypeRelative(qpath_ty, ..) = qpath
+ && qpath_ty.hir_id == ty.hir_id
+ {
+ lint.build(fluent::lint::tykind_kind)
+ .span_suggestion(
+ path.span,
+ fluent::lint::suggestion,
+ "ty",
+ Applicability::MaybeIncorrect, // ty maybe needs an import
+ )
+ .emit();
+ return;
+ }
+ }
+ _ => {}
+ }
+ lint.build(fluent::lint::tykind).help(fluent::lint::help).emit();
+ })
+ } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
+ if path.segments.len() > 1 {
+ cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
+ lint.build(fluent::lint::ty_qualified)
+ .set_arg("ty", t.clone())
+ .span_suggestion(
+ path.span,
+ fluent::lint::suggestion,
+ t,
+ // The import probably needs to be changed
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ })
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
+ if let Some(did) = res.opt_def_id() {
+ cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
+ } else {
+ false
+ }
+}
+
+fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
+ match &path.res {
+ Res::Def(_, def_id) => {
+ if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
+ return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
+ }
+ }
+ // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
+ Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
+ if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
+ if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
+ {
+ // NOTE: This path is currently unreachable as `Ty<'tcx>` is
+ // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
+ // is not actually allowed.
+ //
+ // I(@lcnr) still kept this branch in so we don't miss this
+ // if we ever change it in the future.
+ return Some(format!("{}<{}>", name, substs[0]));
+ }
+ }
+ }
+ _ => (),
+ }
+
+ None
+}
+
+fn gen_args(segment: &PathSegment<'_>) -> String {
+ if let Some(args) = &segment.args {
+ let lifetimes = args
+ .args
+ .iter()
+ .filter_map(|arg| {
+ if let GenericArg::Lifetime(lt) = arg {
+ Some(lt.name.ident().to_string())
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
+ if !lifetimes.is_empty() {
+ return format!("<{}>", lifetimes.join(", "));
+ }
+ }
+
+ String::new()
+}
+
+declare_tool_lint! {
+ pub rustc::LINT_PASS_IMPL_WITHOUT_MACRO,
+ Allow,
+ "`impl LintPass` without the `declare_lint_pass!` or `impl_lint_pass!` macros"
+}
+
+declare_lint_pass!(LintPassImpl => [LINT_PASS_IMPL_WITHOUT_MACRO]);
+
+impl EarlyLintPass for LintPassImpl {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if let ast::ItemKind::Impl(box ast::Impl { of_trait: Some(lint_pass), .. }) = &item.kind {
+ if let Some(last) = lint_pass.path.segments.last() {
+ if last.ident.name == sym::LintPass {
+ let expn_data = lint_pass.path.span.ctxt().outer_expn_data();
+ let call_site = expn_data.call_site;
+ if expn_data.kind != ExpnKind::Macro(MacroKind::Bang, sym::impl_lint_pass)
+ && call_site.ctxt().outer_expn_data().kind
+ != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass)
+ {
+ cx.struct_span_lint(
+ LINT_PASS_IMPL_WITHOUT_MACRO,
+ lint_pass.path.span,
+ |lint| {
+ lint.build(fluent::lint::lintpass_by_hand)
+ .help(fluent::lint::help)
+ .emit();
+ },
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+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 cx.tcx.hir().attrs(item.hir_id()) {
+ 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(fluent::lint::non_existant_doc_keyword)
+ .set_arg("keyword", v)
+ .help(fluent::lint::help)
+ .emit();
+ });
+ }
+ }
+ }
+ }
+ }
+}
+
+declare_tool_lint! {
+ pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
+ Allow,
+ "prevent creation of diagnostics which cannot be translated",
+ report_in_external_macro: true
+}
+
+declare_tool_lint! {
+ pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
+ Allow,
+ "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
+
+impl LateLintPass<'_> for Diagnostics {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
+ debug!(?span, ?def_id, ?substs);
+ let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs)
+ .ok()
+ .and_then(|inst| inst)
+ .map(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics))
+ .unwrap_or(false);
+ if !has_attr {
+ return;
+ }
+
+ let mut found_impl = false;
+ for (_, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
+ debug!(?parent);
+ if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent &&
+ let Impl { of_trait: Some(of_trait), .. } = impl_ &&
+ let Some(def_id) = of_trait.trait_def_id() &&
+ let Some(name) = cx.tcx.get_diagnostic_name(def_id) &&
+ matches!(name, sym::SessionDiagnostic | sym::AddSubdiagnostic | sym::DecorateLint)
+ {
+ found_impl = true;
+ break;
+ }
+ }
+ debug!(?found_impl);
+ if !found_impl {
+ cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
+ lint.build(fluent::lint::diag_out_of_impl).emit();
+ })
+ }
+
+ let mut found_diagnostic_message = false;
+ for ty in substs.types() {
+ debug!(?ty);
+ if let Some(adt_def) = ty.ty_adt_def() &&
+ let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) &&
+ matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage)
+ {
+ found_diagnostic_message = true;
+ break;
+ }
+ }
+ debug!(?found_diagnostic_message);
+ if !found_diagnostic_message {
+ cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
+ lint.build(fluent::lint::untranslatable_diag).emit();
+ })
+ }
+ }
+}
+
+declare_tool_lint! {
+ pub rustc::BAD_OPT_ACCESS,
+ Deny,
+ "prevent using options by field access when there is a wrapper function",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
+
+impl LateLintPass<'_> for BadOptAccess {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ let ExprKind::Field(base, target) = expr.kind else { return };
+ let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return };
+ // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be
+ // avoided.
+ if !cx.tcx.has_attr(adt_def.did(), sym::rustc_lint_opt_ty) {
+ return;
+ }
+
+ for field in adt_def.all_fields() {
+ if field.name == target.name &&
+ let Some(attr) = cx.tcx.get_attr(field.did, sym::rustc_lint_opt_deny_field_access) &&
+ let Some(items) = attr.meta_item_list() &&
+ let Some(item) = items.first() &&
+ let Some(literal) = item.literal() &&
+ let ast::LitKind::Str(val, _) = literal.kind
+ {
+ cx.struct_span_lint(BAD_OPT_ACCESS, expr.span, |lint| {
+ lint.build(val.as_str()).emit(); }
+ );
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs
new file mode 100644
index 000000000..a329b3751
--- /dev/null
+++ b/compiler/rustc_lint/src/late.rs
@@ -0,0 +1,482 @@
+//! Implementation of lint checking.
+//!
+//! The lint checking is mostly consolidated into one pass which runs
+//! after all other analyses. Throughout compilation, lint warnings
+//! can be added via the `add_lint` method on the Session structure. This
+//! requires a span and an ID of the node that the lint is being added to. The
+//! lint isn't actually emitted at that time because it is unknown what the
+//! actual lint level at that location is.
+//!
+//! To actually emit lint warnings/errors, a separate pass is used.
+//! A context keeps track of the current state of all lint levels.
+//! Upon entering a node of the ast which can modify the lint settings, the
+//! previous lint state is pushed onto a stack and the ast is then recursed
+//! upon. As the ast is traversed, this keeps track of the current lint level
+//! for all lint attributes.
+
+use crate::{passes::LateLintPassObject, LateContext, LateLintPass, LintStore};
+use rustc_ast as ast;
+use rustc_data_structures::sync::join;
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::intravisit as hir_visit;
+use rustc_hir::intravisit::Visitor;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::lint::LintPass;
+use rustc_span::symbol::Symbol;
+use rustc_span::Span;
+
+use std::any::Any;
+use std::cell::Cell;
+use std::slice;
+use tracing::debug;
+
+/// Extract the `LintStore` from the query context.
+/// This function exists because we've erased `LintStore` as `dyn Any` in the context.
+pub fn unerased_lint_store(tcx: TyCtxt<'_>) -> &LintStore {
+ let store: &dyn Any = &*tcx.lint_store;
+ store.downcast_ref().unwrap()
+}
+
+macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({
+ $cx.pass.$f(&$cx.context, $($args),*);
+}) }
+
+struct LateContextAndPass<'tcx, T: LateLintPass<'tcx>> {
+ context: LateContext<'tcx>,
+ pass: T,
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> {
+ /// Merge the lints specified by any lint attributes into the
+ /// current lint context, call the provided function, then reset the
+ /// lints in effect to their previous state.
+ fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F)
+ where
+ F: FnOnce(&mut Self),
+ {
+ let attrs = self.context.tcx.hir().attrs(id);
+ let prev = self.context.last_node_with_lint_attrs;
+ self.context.last_node_with_lint_attrs = id;
+ debug!("late context: enter_attrs({:?})", attrs);
+ lint_callback!(self, enter_lint_attrs, attrs);
+ f(self);
+ debug!("late context: exit_attrs({:?})", attrs);
+ lint_callback!(self, exit_lint_attrs, attrs);
+ self.context.last_node_with_lint_attrs = prev;
+ }
+
+ fn with_param_env<F>(&mut self, id: hir::HirId, f: F)
+ where
+ F: FnOnce(&mut Self),
+ {
+ let old_param_env = self.context.param_env;
+ self.context.param_env =
+ self.context.tcx.param_env(self.context.tcx.hir().local_def_id(id));
+ f(self);
+ self.context.param_env = old_param_env;
+ }
+
+ fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) {
+ lint_callback!(self, check_mod, m, s, n);
+ hir_visit::walk_mod(self, m, n);
+ }
+}
+
+impl<'tcx, T: LateLintPass<'tcx>> hir_visit::Visitor<'tcx> for LateContextAndPass<'tcx, T> {
+ type NestedFilter = nested_filter::All;
+
+ /// Because lints are scoped lexically, we want to walk nested
+ /// items in the context of the outer item, so enable
+ /// deep-walking.
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.context.tcx.hir()
+ }
+
+ fn visit_nested_body(&mut self, body_id: hir::BodyId) {
+ let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+ let old_cached_typeck_results = self.context.cached_typeck_results.get();
+
+ // HACK(eddyb) avoid trashing `cached_typeck_results` when we're
+ // nested in `visit_fn`, which may have already resulted in them
+ // being queried.
+ if old_enclosing_body != Some(body_id) {
+ self.context.cached_typeck_results.set(None);
+ }
+
+ let body = self.context.tcx.hir().body(body_id);
+ self.visit_body(body);
+ self.context.enclosing_body = old_enclosing_body;
+
+ // See HACK comment above.
+ if old_enclosing_body != Some(body_id) {
+ self.context.cached_typeck_results.set(old_cached_typeck_results);
+ }
+ }
+
+ fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+ self.with_lint_attrs(param.hir_id, |cx| {
+ hir_visit::walk_param(cx, param);
+ });
+ }
+
+ fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
+ lint_callback!(self, check_body, body);
+ hir_visit::walk_body(self, body);
+ lint_callback!(self, check_body_post, body);
+ }
+
+ fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+ let generics = self.context.generics.take();
+ self.context.generics = it.kind.generics();
+ let old_cached_typeck_results = self.context.cached_typeck_results.take();
+ let old_enclosing_body = self.context.enclosing_body.take();
+ self.with_lint_attrs(it.hir_id(), |cx| {
+ cx.with_param_env(it.hir_id(), |cx| {
+ lint_callback!(cx, check_item, it);
+ hir_visit::walk_item(cx, it);
+ lint_callback!(cx, check_item_post, it);
+ });
+ });
+ self.context.enclosing_body = old_enclosing_body;
+ self.context.cached_typeck_results.set(old_cached_typeck_results);
+ self.context.generics = generics;
+ }
+
+ fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+ self.with_lint_attrs(it.hir_id(), |cx| {
+ cx.with_param_env(it.hir_id(), |cx| {
+ lint_callback!(cx, check_foreign_item, it);
+ hir_visit::walk_foreign_item(cx, it);
+ });
+ })
+ }
+
+ fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
+ lint_callback!(self, check_pat, p);
+ hir_visit::walk_pat(self, p);
+ }
+
+ fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+ self.with_lint_attrs(e.hir_id, |cx| {
+ lint_callback!(cx, check_expr, e);
+ hir_visit::walk_expr(cx, e);
+ lint_callback!(cx, check_expr_post, e);
+ })
+ }
+
+ fn visit_stmt(&mut self, s: &'tcx hir::Stmt<'tcx>) {
+ // See `EarlyContextAndPass::visit_stmt` for an explanation
+ // of why we call `walk_stmt` outside of `with_lint_attrs`
+ self.with_lint_attrs(s.hir_id, |cx| {
+ lint_callback!(cx, check_stmt, s);
+ });
+ hir_visit::walk_stmt(self, s);
+ }
+
+ fn visit_fn(
+ &mut self,
+ fk: hir_visit::FnKind<'tcx>,
+ decl: &'tcx hir::FnDecl<'tcx>,
+ body_id: hir::BodyId,
+ span: Span,
+ id: hir::HirId,
+ ) {
+ // Wrap in typeck results here, not just in visit_nested_body,
+ // in order for `check_fn` to be able to use them.
+ let old_enclosing_body = self.context.enclosing_body.replace(body_id);
+ let old_cached_typeck_results = self.context.cached_typeck_results.take();
+ let body = self.context.tcx.hir().body(body_id);
+ lint_callback!(self, check_fn, fk, decl, body, span, id);
+ hir_visit::walk_fn(self, fk, decl, body_id, span, id);
+ self.context.enclosing_body = old_enclosing_body;
+ self.context.cached_typeck_results.set(old_cached_typeck_results);
+ }
+
+ fn visit_variant_data(
+ &mut self,
+ s: &'tcx hir::VariantData<'tcx>,
+ _: Symbol,
+ _: &'tcx hir::Generics<'tcx>,
+ _: hir::HirId,
+ _: Span,
+ ) {
+ lint_callback!(self, check_struct_def, s);
+ hir_visit::walk_struct_def(self, s);
+ }
+
+ fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+ self.with_lint_attrs(s.hir_id, |cx| {
+ lint_callback!(cx, check_field_def, s);
+ hir_visit::walk_field_def(cx, s);
+ })
+ }
+
+ fn visit_variant(
+ &mut self,
+ v: &'tcx hir::Variant<'tcx>,
+ g: &'tcx hir::Generics<'tcx>,
+ item_id: hir::HirId,
+ ) {
+ self.with_lint_attrs(v.id, |cx| {
+ lint_callback!(cx, check_variant, v);
+ hir_visit::walk_variant(cx, v, g, item_id);
+ })
+ }
+
+ fn visit_ty(&mut self, t: &'tcx hir::Ty<'tcx>) {
+ lint_callback!(self, check_ty, t);
+ hir_visit::walk_ty(self, t);
+ }
+
+ fn visit_infer(&mut self, inf: &'tcx hir::InferArg) {
+ hir_visit::walk_inf(self, inf);
+ }
+
+ fn visit_mod(&mut self, m: &'tcx hir::Mod<'tcx>, s: Span, n: hir::HirId) {
+ if !self.context.only_module {
+ self.process_mod(m, s, n);
+ }
+ }
+
+ fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+ self.with_lint_attrs(l.hir_id, |cx| {
+ lint_callback!(cx, check_local, l);
+ hir_visit::walk_local(cx, l);
+ })
+ }
+
+ fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) {
+ lint_callback!(self, check_block, b);
+ hir_visit::walk_block(self, b);
+ lint_callback!(self, check_block_post, b);
+ }
+
+ fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+ lint_callback!(self, check_arm, a);
+ hir_visit::walk_arm(self, a);
+ }
+
+ fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) {
+ lint_callback!(self, check_generic_param, p);
+ hir_visit::walk_generic_param(self, p);
+ }
+
+ fn visit_generics(&mut self, g: &'tcx hir::Generics<'tcx>) {
+ lint_callback!(self, check_generics, g);
+ hir_visit::walk_generics(self, g);
+ }
+
+ fn visit_where_predicate(&mut self, p: &'tcx hir::WherePredicate<'tcx>) {
+ hir_visit::walk_where_predicate(self, p);
+ }
+
+ fn visit_poly_trait_ref(
+ &mut self,
+ t: &'tcx hir::PolyTraitRef<'tcx>,
+ m: hir::TraitBoundModifier,
+ ) {
+ lint_callback!(self, check_poly_trait_ref, t, m);
+ hir_visit::walk_poly_trait_ref(self, t, m);
+ }
+
+ fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+ let generics = self.context.generics.take();
+ self.context.generics = Some(&trait_item.generics);
+ self.with_lint_attrs(trait_item.hir_id(), |cx| {
+ cx.with_param_env(trait_item.hir_id(), |cx| {
+ lint_callback!(cx, check_trait_item, trait_item);
+ hir_visit::walk_trait_item(cx, trait_item);
+ });
+ });
+ self.context.generics = generics;
+ }
+
+ fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+ let generics = self.context.generics.take();
+ self.context.generics = Some(&impl_item.generics);
+ self.with_lint_attrs(impl_item.hir_id(), |cx| {
+ cx.with_param_env(impl_item.hir_id(), |cx| {
+ lint_callback!(cx, check_impl_item, impl_item);
+ hir_visit::walk_impl_item(cx, impl_item);
+ lint_callback!(cx, check_impl_item_post, impl_item);
+ });
+ });
+ self.context.generics = generics;
+ }
+
+ fn visit_lifetime(&mut self, lt: &'tcx hir::Lifetime) {
+ hir_visit::walk_lifetime(self, lt);
+ }
+
+ fn visit_path(&mut self, p: &'tcx hir::Path<'tcx>, id: hir::HirId) {
+ lint_callback!(self, check_path, p, id);
+ hir_visit::walk_path(self, p);
+ }
+
+ fn visit_attribute(&mut self, attr: &'tcx ast::Attribute) {
+ lint_callback!(self, check_attribute, attr);
+ }
+}
+
+struct LateLintPassObjects<'a> {
+ lints: &'a mut [LateLintPassObject],
+}
+
+#[allow(rustc::lint_pass_impl_without_macro)]
+impl LintPass for LateLintPassObjects<'_> {
+ fn name(&self) -> &'static str {
+ panic!()
+ }
+}
+
+macro_rules! expand_late_lint_pass_impl_methods {
+ ([$hir:tt], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(fn $name(&mut self, context: &LateContext<$hir>, $($param: $arg),*) {
+ for obj in self.lints.iter_mut() {
+ obj.$name(context, $($param),*);
+ }
+ })*
+ )
+}
+
+macro_rules! late_lint_pass_impl {
+ ([], [$hir:tt], $methods:tt) => {
+ impl<$hir> LateLintPass<$hir> for LateLintPassObjects<'_> {
+ expand_late_lint_pass_impl_methods!([$hir], $methods);
+ }
+ };
+}
+
+crate::late_lint_methods!(late_lint_pass_impl, [], ['tcx]);
+
+fn late_lint_mod_pass<'tcx, T: LateLintPass<'tcx>>(
+ tcx: TyCtxt<'tcx>,
+ module_def_id: LocalDefId,
+ pass: T,
+) {
+ let access_levels = &tcx.privacy_access_levels(());
+
+ let context = LateContext {
+ tcx,
+ enclosing_body: None,
+ cached_typeck_results: Cell::new(None),
+ param_env: ty::ParamEnv::empty(),
+ access_levels,
+ lint_store: unerased_lint_store(tcx),
+ last_node_with_lint_attrs: tcx.hir().local_def_id_to_hir_id(module_def_id),
+ generics: None,
+ only_module: true,
+ };
+
+ let mut cx = LateContextAndPass { context, pass };
+
+ let (module, span, hir_id) = tcx.hir().get_module(module_def_id);
+ cx.process_mod(module, span, hir_id);
+
+ // Visit the crate attributes
+ if hir_id == hir::CRATE_HIR_ID {
+ for attr in tcx.hir().attrs(hir::CRATE_HIR_ID).iter() {
+ cx.visit_attribute(attr)
+ }
+ }
+}
+
+pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx>>(
+ tcx: TyCtxt<'tcx>,
+ module_def_id: LocalDefId,
+ builtin_lints: T,
+) {
+ if tcx.sess.opts.unstable_opts.no_interleave_lints {
+ // These passes runs in late_lint_crate with -Z no_interleave_lints
+ return;
+ }
+
+ late_lint_mod_pass(tcx, module_def_id, builtin_lints);
+
+ let mut passes: Vec<_> =
+ unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
+
+ if !passes.is_empty() {
+ late_lint_mod_pass(tcx, module_def_id, LateLintPassObjects { lints: &mut passes[..] });
+ }
+}
+
+fn late_lint_pass_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, pass: T) {
+ let access_levels = &tcx.privacy_access_levels(());
+
+ let context = LateContext {
+ tcx,
+ enclosing_body: None,
+ cached_typeck_results: Cell::new(None),
+ param_env: ty::ParamEnv::empty(),
+ access_levels,
+ lint_store: unerased_lint_store(tcx),
+ last_node_with_lint_attrs: hir::CRATE_HIR_ID,
+ generics: None,
+ only_module: false,
+ };
+
+ let mut cx = LateContextAndPass { context, pass };
+
+ // Visit the whole crate.
+ cx.with_lint_attrs(hir::CRATE_HIR_ID, |cx| {
+ // since the root module isn't visited as an item (because it isn't an
+ // item), warn for it here.
+ lint_callback!(cx, check_crate,);
+ tcx.hir().walk_toplevel_module(cx);
+ tcx.hir().walk_attributes(cx);
+ lint_callback!(cx, check_crate_post,);
+ })
+}
+
+fn late_lint_crate<'tcx, T: LateLintPass<'tcx>>(tcx: TyCtxt<'tcx>, builtin_lints: T) {
+ let mut passes = unerased_lint_store(tcx).late_passes.iter().map(|p| (p)()).collect::<Vec<_>>();
+
+ if !tcx.sess.opts.unstable_opts.no_interleave_lints {
+ if !passes.is_empty() {
+ late_lint_pass_crate(tcx, LateLintPassObjects { lints: &mut passes[..] });
+ }
+
+ late_lint_pass_crate(tcx, builtin_lints);
+ } else {
+ for pass in &mut passes {
+ tcx.sess.prof.extra_verbose_generic_activity("run_late_lint", pass.name()).run(|| {
+ late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
+ });
+ }
+
+ let mut passes: Vec<_> =
+ unerased_lint_store(tcx).late_module_passes.iter().map(|pass| (pass)()).collect();
+
+ for pass in &mut passes {
+ tcx.sess.prof.extra_verbose_generic_activity("run_late_module_lint", pass.name()).run(
+ || {
+ late_lint_pass_crate(tcx, LateLintPassObjects { lints: slice::from_mut(pass) });
+ },
+ );
+ }
+ }
+}
+
+/// Performs lint checking on a crate.
+pub fn check_crate<'tcx, T: LateLintPass<'tcx>>(
+ tcx: TyCtxt<'tcx>,
+ builtin_lints: impl FnOnce() -> T + Send,
+) {
+ join(
+ || {
+ tcx.sess.time("crate_lints", || {
+ // Run whole crate non-incremental lints
+ late_lint_crate(tcx, builtin_lints());
+ });
+ },
+ || {
+ tcx.sess.time("module_lints", || {
+ // Run per-module lints
+ tcx.hir().par_for_each_module(|module| tcx.ensure().lint_mod(module));
+ });
+ },
+ );
+}
diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs
new file mode 100644
index 000000000..00e96f20d
--- /dev/null
+++ b/compiler/rustc_lint/src/levels.rs
@@ -0,0 +1,813 @@
+use crate::context::{CheckLintNameResult, LintStore};
+use crate::late::unerased_lint_store;
+use rustc_ast as ast;
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{struct_span_err, Applicability, Diagnostic, LintDiagnosticBuilder, MultiSpan};
+use rustc_hir as hir;
+use rustc_hir::{intravisit, HirId};
+use rustc_middle::hir::nested_filter;
+use rustc_middle::lint::{
+ struct_lint_level, LevelAndSource, LintExpectation, LintLevelMap, LintLevelSets,
+ LintLevelSource, LintSet, LintStackIndex, COMMAND_LINE,
+};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{RegisteredTools, TyCtxt};
+use rustc_session::lint::{
+ builtin::{self, FORBIDDEN_LINT_GROUPS, SINGLE_USE_LIFETIMES, UNFULFILLED_LINT_EXPECTATIONS},
+ Level, Lint, LintExpectationId, LintId,
+};
+use rustc_session::parse::{add_feature_diagnostics, feature_err};
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use tracing::debug;
+
+fn lint_levels(tcx: TyCtxt<'_>, (): ()) -> LintLevelMap {
+ let store = unerased_lint_store(tcx);
+ let levels =
+ LintLevelsBuilder::new(tcx.sess, false, &store, &tcx.resolutions(()).registered_tools);
+ let mut builder = LintLevelMapBuilder { levels, tcx };
+ let krate = tcx.hir().krate();
+
+ builder.levels.id_to_set.reserve(krate.owners.len() + 1);
+
+ let push =
+ builder.levels.push(tcx.hir().attrs(hir::CRATE_HIR_ID), true, Some(hir::CRATE_HIR_ID));
+
+ builder.levels.register_id(hir::CRATE_HIR_ID);
+ tcx.hir().walk_toplevel_module(&mut builder);
+ builder.levels.pop(push);
+
+ builder.levels.update_unstable_expectation_ids();
+ builder.levels.build_map()
+}
+
+pub struct LintLevelsBuilder<'s> {
+ sess: &'s Session,
+ lint_expectations: Vec<(LintExpectationId, LintExpectation)>,
+ /// Each expectation has a stable and an unstable identifier. This map
+ /// is used to map from unstable to stable [`LintExpectationId`]s.
+ expectation_id_map: FxHashMap<LintExpectationId, LintExpectationId>,
+ sets: LintLevelSets,
+ id_to_set: FxHashMap<HirId, LintStackIndex>,
+ cur: LintStackIndex,
+ warn_about_weird_lints: bool,
+ store: &'s LintStore,
+ registered_tools: &'s RegisteredTools,
+}
+
+pub struct BuilderPush {
+ prev: LintStackIndex,
+ pub changed: bool,
+}
+
+impl<'s> LintLevelsBuilder<'s> {
+ pub fn new(
+ sess: &'s Session,
+ warn_about_weird_lints: bool,
+ store: &'s LintStore,
+ registered_tools: &'s RegisteredTools,
+ ) -> Self {
+ let mut builder = LintLevelsBuilder {
+ sess,
+ lint_expectations: Default::default(),
+ expectation_id_map: Default::default(),
+ sets: LintLevelSets::new(),
+ cur: COMMAND_LINE,
+ id_to_set: Default::default(),
+ warn_about_weird_lints,
+ store,
+ registered_tools,
+ };
+ builder.process_command_line(sess, store);
+ assert_eq!(builder.sets.list.len(), 1);
+ builder
+ }
+
+ pub(crate) fn sess(&self) -> &Session {
+ self.sess
+ }
+
+ pub(crate) fn lint_store(&self) -> &LintStore {
+ self.store
+ }
+
+ fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> {
+ &self.sets.list[self.cur].specs
+ }
+
+ fn current_specs_mut(&mut self) -> &mut FxHashMap<LintId, LevelAndSource> {
+ &mut self.sets.list[self.cur].specs
+ }
+
+ fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
+ self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
+
+ self.cur =
+ self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE });
+ for &(ref lint_name, level) in &sess.opts.lint_opts {
+ store.check_lint_name_cmdline(sess, &lint_name, level, self.registered_tools);
+ let orig_level = level;
+ let lint_flag_val = Symbol::intern(lint_name);
+
+ let Ok(ids) = store.find_lints(&lint_name) else {
+ // errors handled in check_lint_name_cmdline above
+ continue
+ };
+ for id in ids {
+ // ForceWarn and Forbid cannot be overridden
+ if let Some((Level::ForceWarn(_) | Level::Forbid, _)) =
+ self.current_specs().get(&id)
+ {
+ continue;
+ }
+
+ if self.check_gated_lint(id, DUMMY_SP) {
+ let src = LintLevelSource::CommandLine(lint_flag_val, orig_level);
+ self.current_specs_mut().insert(id, (level, src));
+ }
+ }
+ }
+ }
+
+ /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful
+ /// (e.g. if a forbid was already inserted on the same scope), then emits a
+ /// diagnostic with no change to `specs`.
+ fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) {
+ let (old_level, old_src) =
+ self.sets.get_lint_level(id.lint, self.cur, Some(self.current_specs()), &self.sess);
+ // 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 overridden 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_level {
+ // Backwards compatibility check:
+ //
+ // We used to not consider `forbid(lint_group)`
+ // as preventing `allow(lint)` for some lint `lint` in
+ // `lint_group`. For now, issue a future-compatibility
+ // warning for this case.
+ let id_name = id.lint.name_lower();
+ let fcw_warning = match old_src {
+ LintLevelSource::Default => false,
+ LintLevelSource::Node(symbol, _, _) => self.store.is_lint_group(symbol),
+ LintLevelSource::CommandLine(symbol, _) => self.store.is_lint_group(symbol),
+ };
+ debug!(
+ "fcw_warning={:?}, specs.get(&id) = {:?}, old_src={:?}, id_name={:?}",
+ fcw_warning,
+ self.current_specs(),
+ old_src,
+ id_name
+ );
+
+ let decorate_diag = |diag: &mut Diagnostic| {
+ diag.span_label(src.span(), "overruled by previous forbid");
+ match old_src {
+ LintLevelSource::Default => {
+ diag.note(&format!(
+ "`forbid` lint level is the default for {}",
+ id.to_string()
+ ));
+ }
+ LintLevelSource::Node(_, forbid_source_span, reason) => {
+ diag.span_label(forbid_source_span, "`forbid` level set here");
+ if let Some(rationale) = reason {
+ diag.note(rationale.as_str());
+ }
+ }
+ LintLevelSource::CommandLine(_, _) => {
+ diag.note("`forbid` lint level was set on command line");
+ }
+ }
+ };
+ if !fcw_warning {
+ let mut diag_builder = struct_span_err!(
+ self.sess,
+ src.span(),
+ E0453,
+ "{}({}) incompatible with previous forbid",
+ level.as_str(),
+ src.name(),
+ );
+ decorate_diag(&mut diag_builder);
+ diag_builder.emit();
+ } else {
+ self.struct_lint(
+ FORBIDDEN_LINT_GROUPS,
+ Some(src.span().into()),
+ |diag_builder| {
+ let mut diag_builder = diag_builder.build(&format!(
+ "{}({}) incompatible with previous forbid",
+ level.as_str(),
+ src.name(),
+ ));
+ decorate_diag(&mut diag_builder);
+ diag_builder.emit();
+ },
+ );
+ }
+
+ // Retain the forbid lint level, unless we are
+ // issuing a FCW. In the FCW case, we want to
+ // respect the new setting.
+ if !fcw_warning {
+ return;
+ }
+ }
+ }
+
+ // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself.
+ // Handling expectations of this lint would add additional complexity with little to no
+ // benefit. The expect level for this lint will therefore be ignored.
+ if let Level::Expect(_) = level && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) {
+ return;
+ }
+
+ match (old_level, level) {
+ // If the new level is an expectation store it in `ForceWarn`
+ (Level::ForceWarn(_), Level::Expect(expectation_id)) => self
+ .current_specs_mut()
+ .insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)),
+ // Keep `ForceWarn` level but drop the expectation
+ (Level::ForceWarn(_), _) => {
+ self.current_specs_mut().insert(id, (Level::ForceWarn(None), old_src))
+ }
+ // Set the lint level as normal
+ _ => self.current_specs_mut().insert(id, (level, src)),
+ };
+ }
+
+ /// Pushes a list of AST lint attributes onto this context.
+ ///
+ /// This function will return a `BuilderPush` object which should be passed
+ /// to `pop` when this scope for the attributes provided is exited.
+ ///
+ /// This function will perform a number of tasks:
+ ///
+ /// * It'll validate all lint-related attributes in `attrs`
+ /// * It'll mark all lint-related attributes as used
+ /// * Lint levels will be updated based on the attributes provided
+ /// * Lint attributes are validated, e.g., a `#[forbid]` can't be switched to
+ /// `#[allow]`
+ ///
+ /// Don't forget to call `pop`!
+ pub(crate) fn push(
+ &mut self,
+ attrs: &[ast::Attribute],
+ is_crate_node: bool,
+ source_hir_id: Option<HirId>,
+ ) -> BuilderPush {
+ let prev = self.cur;
+ self.cur = self.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev });
+
+ let sess = self.sess;
+ let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
+ for (attr_index, attr) in attrs.iter().enumerate() {
+ if attr.has_name(sym::automatically_derived) {
+ self.current_specs_mut().insert(
+ LintId::of(SINGLE_USE_LIFETIMES),
+ (Level::Allow, LintLevelSource::Default),
+ );
+ continue;
+ }
+
+ let level = match Level::from_attr(attr) {
+ None => continue,
+ // This is the only lint level with a `LintExpectationId` that can be created from an attribute
+ Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => {
+ let stable_id = self.create_stable_id(unstable_id, hir_id, attr_index);
+
+ Level::Expect(stable_id)
+ }
+ Some(lvl) => lvl,
+ };
+
+ let Some(mut metas) = attr.meta_item_list() else {
+ continue
+ };
+
+ if metas.is_empty() {
+ // This emits the unused_attributes lint for `#[level()]`
+ continue;
+ }
+
+ // Before processing the lint names, look for a reason (RFC 2383)
+ // at the end.
+ let mut reason = None;
+ let tail_li = &metas[metas.len() - 1];
+ if let Some(item) = tail_li.meta_item() {
+ match item.kind {
+ ast::MetaItemKind::Word => {} // actual lint names handled later
+ ast::MetaItemKind::NameValue(ref name_value) => {
+ if item.path == sym::reason {
+ if let ast::LitKind::Str(rationale, _) = name_value.kind {
+ if !self.sess.features_untracked().lint_reasons {
+ feature_err(
+ &self.sess.parse_sess,
+ sym::lint_reasons,
+ item.span,
+ "lint reasons are experimental",
+ )
+ .emit();
+ }
+ reason = Some(rationale);
+ } else {
+ bad_attr(name_value.span)
+ .span_label(name_value.span, "reason must be a string literal")
+ .emit();
+ }
+ // found reason, reslice meta list to exclude it
+ metas.pop().unwrap();
+ } else {
+ bad_attr(item.span)
+ .span_label(item.span, "bad attribute argument")
+ .emit();
+ }
+ }
+ ast::MetaItemKind::List(_) => {
+ bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
+ }
+ }
+ }
+
+ for (lint_index, li) in metas.iter_mut().enumerate() {
+ let level = match level {
+ Level::Expect(mut id) => {
+ id.set_lint_index(Some(lint_index as u16));
+ Level::Expect(id)
+ }
+ level => level,
+ };
+
+ let sp = li.span();
+ let meta_item = match li {
+ ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item,
+ _ => {
+ let mut err = bad_attr(sp);
+ let mut add_label = true;
+ if let Some(item) = li.meta_item() {
+ if let ast::MetaItemKind::NameValue(_) = item.kind {
+ if item.path == sym::reason {
+ err.span_label(sp, "reason in lint attribute must come last");
+ add_label = false;
+ }
+ }
+ }
+ if add_label {
+ err.span_label(sp, "bad attribute argument");
+ }
+ err.emit();
+ continue;
+ }
+ };
+ let tool_ident = if meta_item.path.segments.len() > 1 {
+ Some(meta_item.path.segments.remove(0).ident)
+ } else {
+ None
+ };
+ let tool_name = tool_ident.map(|ident| ident.name);
+ let name = pprust::path_to_string(&meta_item.path);
+ let lint_result =
+ self.store.check_lint_name(&name, tool_name, self.registered_tools);
+ match &lint_result {
+ CheckLintNameResult::Ok(ids) => {
+ // This checks for instances where the user writes `#[expect(unfulfilled_lint_expectations)]`
+ // in that case we want to avoid overriding the lint level but instead add an expectation that
+ // can't be fulfilled. The lint message will include an explanation, that the
+ // `unfulfilled_lint_expectations` lint can't be expected.
+ if let Level::Expect(expect_id) = level {
+ // The `unfulfilled_lint_expectations` lint is not part of any lint groups. Therefore. we
+ // only need to check the slice if it contains a single lint.
+ let is_unfulfilled_lint_expectations = match ids {
+ [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS),
+ _ => false,
+ };
+ self.lint_expectations.push((
+ expect_id,
+ LintExpectation::new(
+ reason,
+ sp,
+ is_unfulfilled_lint_expectations,
+ tool_name,
+ ),
+ ));
+ }
+ let src = LintLevelSource::Node(
+ meta_item.path.segments.last().expect("empty lint name").ident.name,
+ sp,
+ reason,
+ );
+ for &id in *ids {
+ if self.check_gated_lint(id, attr.span) {
+ self.insert_spec(id, (level, src));
+ }
+ }
+ }
+
+ CheckLintNameResult::Tool(result) => {
+ match *result {
+ Ok(ids) => {
+ let complete_name =
+ &format!("{}::{}", tool_ident.unwrap().name, name);
+ let src = LintLevelSource::Node(
+ Symbol::intern(complete_name),
+ sp,
+ reason,
+ );
+ for id in ids {
+ self.insert_spec(*id, (level, src));
+ }
+ if let Level::Expect(expect_id) = level {
+ self.lint_expectations.push((
+ expect_id,
+ LintExpectation::new(reason, sp, false, tool_name),
+ ));
+ }
+ }
+ Err((Some(ids), ref new_lint_name)) => {
+ let lint = builtin::RENAMED_AND_REMOVED_LINTS;
+ let (lvl, src) = self.sets.get_lint_level(
+ lint,
+ self.cur,
+ Some(self.current_specs()),
+ &sess,
+ );
+ struct_lint_level(
+ self.sess,
+ lint,
+ lvl,
+ src,
+ Some(sp.into()),
+ |lint| {
+ let msg = format!(
+ "lint name `{}` is deprecated \
+ and may not have an effect in the future.",
+ name
+ );
+ lint.build(&msg)
+ .span_suggestion(
+ sp,
+ "change it to",
+ new_lint_name,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ },
+ );
+
+ let src = LintLevelSource::Node(
+ Symbol::intern(&new_lint_name),
+ sp,
+ reason,
+ );
+ for id in ids {
+ self.insert_spec(*id, (level, src));
+ }
+ if let Level::Expect(expect_id) = level {
+ self.lint_expectations.push((
+ expect_id,
+ LintExpectation::new(reason, sp, false, tool_name),
+ ));
+ }
+ }
+ Err((None, _)) => {
+ // If Tool(Err(None, _)) is returned, then either the lint does not
+ // exist in the tool or the code was not compiled with the tool and
+ // therefore the lint was never added to the `LintStore`. To detect
+ // this is the responsibility of the lint tool.
+ }
+ }
+ }
+
+ &CheckLintNameResult::NoTool => {
+ let mut err = struct_span_err!(
+ sess,
+ tool_ident.map_or(DUMMY_SP, |ident| ident.span),
+ E0710,
+ "unknown tool name `{}` found in scoped lint: `{}::{}`",
+ tool_name.unwrap(),
+ tool_name.unwrap(),
+ pprust::path_to_string(&meta_item.path),
+ );
+ if sess.is_nightly_build() {
+ err.help(&format!(
+ "add `#![register_tool({})]` to the crate root",
+ tool_name.unwrap()
+ ));
+ }
+ err.emit();
+ continue;
+ }
+
+ _ if !self.warn_about_weird_lints => {}
+
+ CheckLintNameResult::Warning(msg, renamed) => {
+ let lint = builtin::RENAMED_AND_REMOVED_LINTS;
+ let (renamed_lint_level, src) = self.sets.get_lint_level(
+ lint,
+ self.cur,
+ Some(self.current_specs()),
+ &sess,
+ );
+ struct_lint_level(
+ self.sess,
+ lint,
+ renamed_lint_level,
+ src,
+ Some(sp.into()),
+ |lint| {
+ let mut err = lint.build(msg);
+ if let Some(new_name) = &renamed {
+ err.span_suggestion(
+ sp,
+ "use the new name",
+ new_name,
+ Applicability::MachineApplicable,
+ );
+ }
+ err.emit();
+ },
+ );
+ }
+ CheckLintNameResult::NoLint(suggestion) => {
+ let lint = builtin::UNKNOWN_LINTS;
+ let (level, src) = self.sets.get_lint_level(
+ lint,
+ self.cur,
+ Some(self.current_specs()),
+ self.sess,
+ );
+ struct_lint_level(self.sess, lint, level, src, Some(sp.into()), |lint| {
+ let name = if let Some(tool_ident) = tool_ident {
+ format!("{}::{}", tool_ident.name, name)
+ } else {
+ name.to_string()
+ };
+ let mut db = lint.build(format!("unknown lint: `{}`", name));
+ if let Some(suggestion) = suggestion {
+ db.span_suggestion(
+ sp,
+ "did you mean",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ }
+ db.emit();
+ });
+ }
+ }
+ // If this lint was renamed, apply the new lint instead of ignoring the attribute.
+ // This happens outside of the match because the new lint should be applied even if
+ // we don't warn about the name change.
+ if let CheckLintNameResult::Warning(_, Some(new_name)) = lint_result {
+ // Ignore any errors or warnings that happen because the new name is inaccurate
+ // NOTE: `new_name` already includes the tool name, so we don't have to add it again.
+ if let CheckLintNameResult::Ok(ids) =
+ self.store.check_lint_name(&new_name, None, self.registered_tools)
+ {
+ let src = LintLevelSource::Node(Symbol::intern(&new_name), sp, reason);
+ for &id in ids {
+ if self.check_gated_lint(id, attr.span) {
+ self.insert_spec(id, (level, src));
+ }
+ }
+ if let Level::Expect(expect_id) = level {
+ self.lint_expectations.push((
+ expect_id,
+ LintExpectation::new(reason, sp, false, tool_name),
+ ));
+ }
+ } else {
+ panic!("renamed lint does not exist: {}", new_name);
+ }
+ }
+ }
+ }
+
+ if !is_crate_node {
+ for (id, &(level, ref src)) in self.current_specs().iter() {
+ if !id.lint.crate_level_only {
+ continue;
+ }
+
+ let LintLevelSource::Node(lint_attr_name, lint_attr_span, _) = *src else {
+ continue
+ };
+
+ let lint = builtin::UNUSED_ATTRIBUTES;
+ let (lint_level, lint_src) =
+ self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), self.sess);
+ struct_lint_level(
+ self.sess,
+ lint,
+ lint_level,
+ lint_src,
+ Some(lint_attr_span.into()),
+ |lint| {
+ let mut db = lint.build(&format!(
+ "{}({}) is ignored unless specified at crate level",
+ level.as_str(),
+ lint_attr_name
+ ));
+ db.emit();
+ },
+ );
+ // don't set a separate error for every lint in the group
+ break;
+ }
+ }
+
+ if self.current_specs().is_empty() {
+ self.sets.list.pop();
+ self.cur = prev;
+ }
+
+ BuilderPush { prev, changed: prev != self.cur }
+ }
+
+ fn create_stable_id(
+ &mut self,
+ unstable_id: LintExpectationId,
+ hir_id: HirId,
+ attr_index: usize,
+ ) -> LintExpectationId {
+ let stable_id =
+ LintExpectationId::Stable { hir_id, attr_index: attr_index as u16, lint_index: None };
+
+ self.expectation_id_map.insert(unstable_id, stable_id);
+
+ stable_id
+ }
+
+ /// Checks if the lint is gated on a feature that is not enabled.
+ ///
+ /// Returns `true` if the lint's feature is enabled.
+ fn check_gated_lint(&self, lint_id: LintId, span: Span) -> bool {
+ if let Some(feature) = lint_id.lint.feature_gate {
+ if !self.sess.features_untracked().enabled(feature) {
+ let lint = builtin::UNKNOWN_LINTS;
+ let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS);
+ struct_lint_level(self.sess, lint, level, src, Some(span.into()), |lint_db| {
+ let mut db =
+ lint_db.build(&format!("unknown lint: `{}`", lint_id.lint.name_lower()));
+ db.note(&format!("the `{}` lint is unstable", lint_id.lint.name_lower(),));
+ add_feature_diagnostics(&mut db, &self.sess.parse_sess, feature);
+ db.emit();
+ });
+ return false;
+ }
+ }
+ true
+ }
+
+ /// Called after `push` when the scope of a set of attributes are exited.
+ pub fn pop(&mut self, push: BuilderPush) {
+ self.cur = push.prev;
+ }
+
+ /// Find the lint level for a lint.
+ pub fn lint_level(&self, lint: &'static Lint) -> (Level, LintLevelSource) {
+ self.sets.get_lint_level(lint, self.cur, None, self.sess)
+ }
+
+ /// Used to emit a lint-related diagnostic based on the current state of
+ /// this lint context.
+ pub fn struct_lint(
+ &self,
+ lint: &'static Lint,
+ span: Option<MultiSpan>,
+ decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>),
+ ) {
+ let (level, src) = self.lint_level(lint);
+ struct_lint_level(self.sess, lint, level, src, span, decorate)
+ }
+
+ /// Registers the ID provided with the current set of lints stored in
+ /// this context.
+ pub fn register_id(&mut self, id: HirId) {
+ self.id_to_set.insert(id, self.cur);
+ }
+
+ fn update_unstable_expectation_ids(&self) {
+ self.sess.diagnostic().update_unstable_expectation_id(&self.expectation_id_map);
+ }
+
+ pub fn build_map(self) -> LintLevelMap {
+ LintLevelMap {
+ sets: self.sets,
+ id_to_set: self.id_to_set,
+ lint_expectations: self.lint_expectations,
+ }
+ }
+}
+
+struct LintLevelMapBuilder<'tcx> {
+ levels: LintLevelsBuilder<'tcx>,
+ tcx: TyCtxt<'tcx>,
+}
+
+impl LintLevelMapBuilder<'_> {
+ fn with_lint_attrs<F>(&mut self, id: hir::HirId, f: F)
+ where
+ F: FnOnce(&mut Self),
+ {
+ let is_crate_hir = id == hir::CRATE_HIR_ID;
+ let attrs = self.tcx.hir().attrs(id);
+ let push = self.levels.push(attrs, is_crate_hir, Some(id));
+
+ if push.changed {
+ self.levels.register_id(id);
+ }
+ f(self);
+ self.levels.pop(push);
+ }
+}
+
+impl<'tcx> intravisit::Visitor<'tcx> for LintLevelMapBuilder<'tcx> {
+ type NestedFilter = nested_filter::All;
+
+ fn nested_visit_map(&mut self) -> Self::Map {
+ self.tcx.hir()
+ }
+
+ fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) {
+ self.with_lint_attrs(param.hir_id, |builder| {
+ intravisit::walk_param(builder, param);
+ });
+ }
+
+ fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) {
+ self.with_lint_attrs(it.hir_id(), |builder| {
+ intravisit::walk_item(builder, it);
+ });
+ }
+
+ fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
+ self.with_lint_attrs(it.hir_id(), |builder| {
+ intravisit::walk_foreign_item(builder, it);
+ })
+ }
+
+ fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) {
+ // We will call `with_lint_attrs` when we walk
+ // the `StmtKind`. The outer statement itself doesn't
+ // define the lint levels.
+ intravisit::walk_stmt(self, e);
+ }
+
+ fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
+ self.with_lint_attrs(e.hir_id, |builder| {
+ intravisit::walk_expr(builder, e);
+ })
+ }
+
+ fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
+ self.with_lint_attrs(s.hir_id, |builder| {
+ intravisit::walk_field_def(builder, s);
+ })
+ }
+
+ fn visit_variant(
+ &mut self,
+ v: &'tcx hir::Variant<'tcx>,
+ g: &'tcx hir::Generics<'tcx>,
+ item_id: hir::HirId,
+ ) {
+ self.with_lint_attrs(v.id, |builder| {
+ intravisit::walk_variant(builder, v, g, item_id);
+ })
+ }
+
+ fn visit_local(&mut self, l: &'tcx hir::Local<'tcx>) {
+ self.with_lint_attrs(l.hir_id, |builder| {
+ intravisit::walk_local(builder, l);
+ })
+ }
+
+ fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) {
+ self.with_lint_attrs(a.hir_id, |builder| {
+ intravisit::walk_arm(builder, a);
+ })
+ }
+
+ fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
+ self.with_lint_attrs(trait_item.hir_id(), |builder| {
+ intravisit::walk_trait_item(builder, trait_item);
+ });
+ }
+
+ fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
+ self.with_lint_attrs(impl_item.hir_id(), |builder| {
+ intravisit::walk_impl_item(builder, impl_item);
+ });
+ }
+}
+
+pub fn provide(providers: &mut Providers) {
+ providers.lint_levels = lint_levels;
+}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
new file mode 100644
index 000000000..389a0b5d1
--- /dev/null
+++ b/compiler/rustc_lint/src/lib.rs
@@ -0,0 +1,538 @@
+//! Lints, aka compiler warnings.
+//!
+//! A 'lint' check is a kind of miscellaneous constraint that a user _might_
+//! want to enforce, but might reasonably want to permit as well, on a
+//! module-by-module basis. They contrast with static constraints enforced by
+//! other phases of the compiler, which are generally required to hold in order
+//! to compile the program at all.
+//!
+//! Most lints can be written as [LintPass] instances. These run after
+//! all other analyses. The `LintPass`es built into rustc are defined
+//! within [rustc_session::lint::builtin],
+//! which has further comments on how to add such a lint.
+//! rustc can also load user-defined lint plugins via the plugin mechanism.
+//!
+//! Some of rustc's lints are defined elsewhere in the compiler and work by
+//! calling `add_lint()` on the overall `Session` object. This works when
+//! it happens before the main lint pass, which emits the lints stored by
+//! `add_lint()`. To emit lints after the main lint pass (from codegen, for
+//! example) requires more effort. See `emit_lint` and `GatherNodeLevels`
+//! in `context.rs`.
+//!
+//! Some code also exists in [rustc_session::lint], [rustc_middle::lint].
+//!
+//! ## Note
+//!
+//! This API is completely unstable and subject to change.
+
+#![allow(rustc::potential_query_instability)]
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![feature(array_windows)]
+#![feature(box_patterns)]
+#![feature(control_flow_enum)]
+#![feature(if_let_guard)]
+#![feature(iter_intersperse)]
+#![feature(iter_order_by)]
+#![feature(let_chains)]
+#![feature(let_else)]
+#![feature(never_type)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate rustc_middle;
+#[macro_use]
+extern crate rustc_session;
+
+mod array_into_iter;
+pub mod builtin;
+mod context;
+mod early;
+mod enum_intrinsics_non_enums;
+mod expect;
+pub mod hidden_unicode_codepoints;
+mod internal;
+mod late;
+mod levels;
+mod methods;
+mod non_ascii_idents;
+mod non_fmt_panic;
+mod nonstandard_style;
+mod noop_method_call;
+mod pass_by_value;
+mod passes;
+mod redundant_semicolon;
+mod traits;
+mod types;
+mod unused;
+
+pub use array_into_iter::ARRAY_INTO_ITER;
+
+use rustc_ast as ast;
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_session::lint::builtin::{
+ BARE_TRAIT_OBJECTS, ELIDED_LIFETIMES_IN_PATHS, EXPLICIT_OUTLIVES_REQUIREMENTS,
+};
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+use array_into_iter::ArrayIntoIter;
+use builtin::*;
+use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
+use hidden_unicode_codepoints::*;
+use internal::*;
+use methods::*;
+use non_ascii_idents::*;
+use non_fmt_panic::NonPanicFmt;
+use nonstandard_style::*;
+use noop_method_call::*;
+use pass_by_value::*;
+use redundant_semicolon::*;
+use traits::*;
+use types::*;
+use unused::*;
+
+/// Useful for other parts of the compiler / Clippy.
+pub use builtin::SoftLints;
+pub use context::{CheckLintNameResult, FindLintError, LintStore};
+pub use context::{EarlyContext, LateContext, LintContext};
+pub use early::{check_ast_node, EarlyCheckNode};
+pub use late::{check_crate, unerased_lint_store};
+pub use passes::{EarlyLintPass, LateLintPass};
+pub use rustc_session::lint::Level::{self, *};
+pub use rustc_session::lint::{BufferedEarlyLint, FutureIncompatibleInfo, Lint, LintId};
+pub use rustc_session::lint::{LintArray, LintPass};
+
+pub fn provide(providers: &mut Providers) {
+ levels::provide(providers);
+ expect::provide(providers);
+ *providers = Providers { lint_mod, ..*providers };
+}
+
+fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
+ late::late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new());
+}
+
+macro_rules! pre_expansion_lint_passes {
+ ($macro:path, $args:tt) => {
+ $macro!($args, [KeywordIdents: KeywordIdents,]);
+ };
+}
+
+macro_rules! early_lint_passes {
+ ($macro:path, $args:tt) => {
+ $macro!(
+ $args,
+ [
+ UnusedParens: UnusedParens,
+ UnusedBraces: UnusedBraces,
+ UnusedImportBraces: UnusedImportBraces,
+ UnsafeCode: UnsafeCode,
+ AnonymousParameters: AnonymousParameters,
+ EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(),
+ NonCamelCaseTypes: NonCamelCaseTypes,
+ DeprecatedAttr: DeprecatedAttr::new(),
+ WhileTrue: WhileTrue,
+ NonAsciiIdents: NonAsciiIdents,
+ HiddenUnicodeCodepoints: HiddenUnicodeCodepoints,
+ IncompleteFeatures: IncompleteFeatures,
+ RedundantSemicolons: RedundantSemicolons,
+ UnusedDocComment: UnusedDocComment,
+ ]
+ );
+ };
+}
+
+macro_rules! declare_combined_early_pass {
+ ([$name:ident], $passes:tt) => (
+ early_lint_methods!(declare_combined_early_lint_pass, [pub $name, $passes]);
+ )
+}
+
+pre_expansion_lint_passes!(declare_combined_early_pass, [BuiltinCombinedPreExpansionLintPass]);
+early_lint_passes!(declare_combined_early_pass, [BuiltinCombinedEarlyLintPass]);
+
+macro_rules! late_lint_passes {
+ ($macro:path, $args:tt) => {
+ $macro!(
+ $args,
+ [
+ // Tracks state across modules
+ UnnameableTestItems: UnnameableTestItems::new(),
+ // Tracks attributes of parents
+ MissingDoc: MissingDoc::new(),
+ // Builds a global list of all impls of `Debug`.
+ // FIXME: Turn the computation of types which implement Debug into a query
+ // and change this to a module lint pass
+ MissingDebugImplementations: MissingDebugImplementations::default(),
+ // Keeps a global list of foreign declarations.
+ ClashingExternDeclarations: ClashingExternDeclarations::new(),
+ ]
+ );
+ };
+}
+
+macro_rules! late_lint_mod_passes {
+ ($macro:path, $args:tt) => {
+ $macro!(
+ $args,
+ [
+ HardwiredLints: HardwiredLints,
+ ImproperCTypesDeclarations: ImproperCTypesDeclarations,
+ ImproperCTypesDefinitions: ImproperCTypesDefinitions,
+ VariantSizeDifferences: VariantSizeDifferences,
+ BoxPointers: BoxPointers,
+ PathStatements: PathStatements,
+ // Depends on referenced function signatures in expressions
+ UnusedResults: UnusedResults,
+ NonUpperCaseGlobals: NonUpperCaseGlobals,
+ NonShorthandFieldPatterns: NonShorthandFieldPatterns,
+ UnusedAllocation: UnusedAllocation,
+ // Depends on types used in type definitions
+ MissingCopyImplementations: MissingCopyImplementations,
+ // Depends on referenced function signatures in expressions
+ MutableTransmutes: MutableTransmutes,
+ TypeAliasBounds: TypeAliasBounds,
+ TrivialConstraints: TrivialConstraints,
+ TypeLimits: TypeLimits::new(),
+ NonSnakeCase: NonSnakeCase,
+ InvalidNoMangleItems: InvalidNoMangleItems,
+ // Depends on access levels
+ UnreachablePub: UnreachablePub,
+ ExplicitOutlivesRequirements: ExplicitOutlivesRequirements,
+ InvalidValue: InvalidValue,
+ DerefNullPtr: DerefNullPtr,
+ // May Depend on constants elsewhere
+ UnusedBrokenConst: UnusedBrokenConst,
+ UnstableFeatures: UnstableFeatures,
+ ArrayIntoIter: ArrayIntoIter::default(),
+ DropTraitConstraints: DropTraitConstraints,
+ TemporaryCStringAsPtr: TemporaryCStringAsPtr,
+ NonPanicFmt: NonPanicFmt,
+ NoopMethodCall: NoopMethodCall,
+ EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
+ InvalidAtomicOrdering: InvalidAtomicOrdering,
+ NamedAsmLabels: NamedAsmLabels,
+ ]
+ );
+ };
+}
+
+macro_rules! declare_combined_late_pass {
+ ([$v:vis $name:ident], $passes:tt) => (
+ late_lint_methods!(declare_combined_late_lint_pass, [$v $name, $passes], ['tcx]);
+ )
+}
+
+// FIXME: Make a separate lint type which do not require typeck tables
+late_lint_passes!(declare_combined_late_pass, [pub BuiltinCombinedLateLintPass]);
+
+late_lint_mod_passes!(declare_combined_late_pass, [BuiltinCombinedModuleLateLintPass]);
+
+pub fn new_lint_store(no_interleave_lints: bool, internal_lints: bool) -> LintStore {
+ let mut lint_store = LintStore::new();
+
+ register_builtins(&mut lint_store, no_interleave_lints);
+ if internal_lints {
+ register_internals(&mut lint_store);
+ }
+
+ lint_store
+}
+
+/// Tell the `LintStore` about all the built-in lints (the ones
+/// defined in this crate and the ones defined in
+/// `rustc_session::lint::builtin`).
+fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
+ macro_rules! add_lint_group {
+ ($name:expr, $($lint:ident),*) => (
+ store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]);
+ )
+ }
+
+ macro_rules! register_pass {
+ ($method:ident, $ty:ident, $constructor:expr) => {
+ store.register_lints(&$ty::get_lints());
+ store.$method(|| Box::new($constructor));
+ };
+ }
+
+ macro_rules! register_passes {
+ ($method:ident, [$($passes:ident: $constructor:expr,)*]) => (
+ $(
+ register_pass!($method, $passes, $constructor);
+ )*
+ )
+ }
+
+ if no_interleave_lints {
+ pre_expansion_lint_passes!(register_passes, register_pre_expansion_pass);
+ early_lint_passes!(register_passes, register_early_pass);
+ late_lint_passes!(register_passes, register_late_pass);
+ late_lint_mod_passes!(register_passes, register_late_mod_pass);
+ } else {
+ store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints());
+ store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints());
+ store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints());
+ store.register_lints(&BuiltinCombinedLateLintPass::get_lints());
+ }
+
+ add_lint_group!(
+ "nonstandard_style",
+ NON_CAMEL_CASE_TYPES,
+ NON_SNAKE_CASE,
+ NON_UPPER_CASE_GLOBALS
+ );
+
+ add_lint_group!(
+ "unused",
+ UNUSED_IMPORTS,
+ UNUSED_VARIABLES,
+ UNUSED_ASSIGNMENTS,
+ DEAD_CODE,
+ UNUSED_MUT,
+ UNREACHABLE_CODE,
+ UNREACHABLE_PATTERNS,
+ UNUSED_MUST_USE,
+ UNUSED_UNSAFE,
+ PATH_STATEMENTS,
+ UNUSED_ATTRIBUTES,
+ UNUSED_MACROS,
+ UNUSED_MACRO_RULES,
+ UNUSED_ALLOCATION,
+ UNUSED_DOC_COMMENTS,
+ UNUSED_EXTERN_CRATES,
+ UNUSED_FEATURES,
+ UNUSED_LABELS,
+ UNUSED_PARENS,
+ UNUSED_BRACES,
+ REDUNDANT_SEMICOLONS
+ );
+
+ add_lint_group!(
+ "rust_2018_idioms",
+ BARE_TRAIT_OBJECTS,
+ UNUSED_EXTERN_CRATES,
+ ELLIPSIS_INCLUSIVE_RANGE_PATTERNS,
+ ELIDED_LIFETIMES_IN_PATHS,
+ EXPLICIT_OUTLIVES_REQUIREMENTS // FIXME(#52665, #47816) not always applicable and not all
+ // macros are ready for this yet.
+ // UNREACHABLE_PUB,
+
+ // FIXME macro crates are not up for this yet, too much
+ // breakage is seen if we try to encourage this lint.
+ // MACRO_USE_EXTERN_CRATE
+ );
+
+ // Register renamed and removed lints.
+ store.register_renamed("single_use_lifetime", "single_use_lifetimes");
+ store.register_renamed("elided_lifetime_in_path", "elided_lifetimes_in_paths");
+ store.register_renamed("bare_trait_object", "bare_trait_objects");
+ store.register_renamed("unstable_name_collision", "unstable_name_collisions");
+ store.register_renamed("unused_doc_comment", "unused_doc_comments");
+ store.register_renamed("async_idents", "keyword_idents");
+ store.register_renamed("exceeding_bitshifts", "arithmetic_overflow");
+ store.register_renamed("redundant_semicolon", "redundant_semicolons");
+ store.register_renamed("overlapping_patterns", "overlapping_range_endpoints");
+ store.register_renamed("safe_packed_borrows", "unaligned_references");
+ store.register_renamed("disjoint_capture_migration", "rust_2021_incompatible_closure_captures");
+ store.register_renamed("or_patterns_back_compat", "rust_2021_incompatible_or_patterns");
+ store.register_renamed("non_fmt_panic", "non_fmt_panics");
+
+ // These were moved to tool lints, but rustc still sees them when compiling normally, before
+ // tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use
+ // `register_removed` explicitly.
+ const RUSTDOC_LINTS: &[&str] = &[
+ "broken_intra_doc_links",
+ "private_intra_doc_links",
+ "missing_crate_level_docs",
+ "missing_doc_code_examples",
+ "private_doc_tests",
+ "invalid_codeblock_attributes",
+ "invalid_html_tags",
+ "non_autolinks",
+ ];
+ for rustdoc_lint in RUSTDOC_LINTS {
+ store.register_ignored(rustdoc_lint);
+ }
+ store.register_removed(
+ "intra_doc_link_resolution_failure",
+ "use `rustdoc::broken_intra_doc_links` instead",
+ );
+ store.register_removed("rustdoc", "use `rustdoc::all` instead");
+
+ store.register_removed("unknown_features", "replaced by an error");
+ store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
+ store.register_removed("negate_unsigned", "cast a signed value instead");
+ store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok");
+ // Register lint group aliases.
+ store.register_group_alias("nonstandard_style", "bad_style");
+ // This was renamed to `raw_pointer_derive`, which was then removed,
+ // so it is also considered removed.
+ store.register_removed("raw_pointer_deriving", "using derive with raw pointers is ok");
+ store.register_removed("drop_with_repr_extern", "drop flags have been removed");
+ store.register_removed("fat_ptr_transmutes", "was accidentally removed back in 2014");
+ store.register_removed("deprecated_attr", "use `deprecated` instead");
+ store.register_removed(
+ "transmute_from_fn_item_types",
+ "always cast functions before transmuting them",
+ );
+ store.register_removed(
+ "hr_lifetime_in_assoc_type",
+ "converted into hard error, see issue #33685 \
+ <https://github.com/rust-lang/rust/issues/33685> for more information",
+ );
+ store.register_removed(
+ "inaccessible_extern_crate",
+ "converted into hard error, see issue #36886 \
+ <https://github.com/rust-lang/rust/issues/36886> for more information",
+ );
+ store.register_removed(
+ "super_or_self_in_global_path",
+ "converted into hard error, see issue #36888 \
+ <https://github.com/rust-lang/rust/issues/36888> for more information",
+ );
+ store.register_removed(
+ "overlapping_inherent_impls",
+ "converted into hard error, see issue #36889 \
+ <https://github.com/rust-lang/rust/issues/36889> for more information",
+ );
+ store.register_removed(
+ "illegal_floating_point_constant_pattern",
+ "converted into hard error, see issue #36890 \
+ <https://github.com/rust-lang/rust/issues/36890> for more information",
+ );
+ store.register_removed(
+ "illegal_struct_or_enum_constant_pattern",
+ "converted into hard error, see issue #36891 \
+ <https://github.com/rust-lang/rust/issues/36891> for more information",
+ );
+ store.register_removed(
+ "lifetime_underscore",
+ "converted into hard error, see issue #36892 \
+ <https://github.com/rust-lang/rust/issues/36892> for more information",
+ );
+ store.register_removed(
+ "extra_requirement_in_impl",
+ "converted into hard error, see issue #37166 \
+ <https://github.com/rust-lang/rust/issues/37166> for more information",
+ );
+ store.register_removed(
+ "legacy_imports",
+ "converted into hard error, see issue #38260 \
+ <https://github.com/rust-lang/rust/issues/38260> for more information",
+ );
+ store.register_removed(
+ "coerce_never",
+ "converted into hard error, see issue #48950 \
+ <https://github.com/rust-lang/rust/issues/48950> for more information",
+ );
+ store.register_removed(
+ "resolve_trait_on_defaulted_unit",
+ "converted into hard error, see issue #48950 \
+ <https://github.com/rust-lang/rust/issues/48950> for more information",
+ );
+ store.register_removed(
+ "private_no_mangle_fns",
+ "no longer a warning, `#[no_mangle]` functions always exported",
+ );
+ store.register_removed(
+ "private_no_mangle_statics",
+ "no longer a warning, `#[no_mangle]` statics always exported",
+ );
+ store.register_removed("bad_repr", "replaced with a generic attribute input check");
+ store.register_removed(
+ "duplicate_matcher_binding_name",
+ "converted into hard error, see issue #57742 \
+ <https://github.com/rust-lang/rust/issues/57742> for more information",
+ );
+ store.register_removed(
+ "incoherent_fundamental_impls",
+ "converted into hard error, see issue #46205 \
+ <https://github.com/rust-lang/rust/issues/46205> for more information",
+ );
+ store.register_removed(
+ "legacy_constructor_visibility",
+ "converted into hard error, see issue #39207 \
+ <https://github.com/rust-lang/rust/issues/39207> for more information",
+ );
+ store.register_removed(
+ "legacy_directory_ownership",
+ "converted into hard error, see issue #37872 \
+ <https://github.com/rust-lang/rust/issues/37872> for more information",
+ );
+ store.register_removed(
+ "safe_extern_statics",
+ "converted into hard error, see issue #36247 \
+ <https://github.com/rust-lang/rust/issues/36247> for more information",
+ );
+ store.register_removed(
+ "parenthesized_params_in_types_and_modules",
+ "converted into hard error, see issue #42238 \
+ <https://github.com/rust-lang/rust/issues/42238> for more information",
+ );
+ store.register_removed(
+ "duplicate_macro_exports",
+ "converted into hard error, see issue #35896 \
+ <https://github.com/rust-lang/rust/issues/35896> for more information",
+ );
+ store.register_removed(
+ "nested_impl_trait",
+ "converted into hard error, see issue #59014 \
+ <https://github.com/rust-lang/rust/issues/59014> for more information",
+ );
+ store.register_removed("plugin_as_library", "plugins have been deprecated and retired");
+ store.register_removed(
+ "unsupported_naked_functions",
+ "converted into hard error, see RFC 2972 \
+ <https://github.com/rust-lang/rfcs/blob/master/text/2972-constrained-naked.md> for more information",
+ );
+ store.register_removed(
+ "mutable_borrow_reservation_conflict",
+ "now allowed, see issue #59159 \
+ <https://github.com/rust-lang/rust/issues/59159> for more information",
+ );
+}
+
+fn register_internals(store: &mut LintStore) {
+ store.register_lints(&LintPassImpl::get_lints());
+ store.register_early_pass(|| Box::new(LintPassImpl));
+ store.register_lints(&DefaultHashTypes::get_lints());
+ store.register_late_pass(|| Box::new(DefaultHashTypes));
+ store.register_lints(&QueryStability::get_lints());
+ store.register_late_pass(|| Box::new(QueryStability));
+ store.register_lints(&ExistingDocKeyword::get_lints());
+ store.register_late_pass(|| Box::new(ExistingDocKeyword));
+ store.register_lints(&TyTyKind::get_lints());
+ store.register_late_pass(|| Box::new(TyTyKind));
+ store.register_lints(&Diagnostics::get_lints());
+ store.register_late_pass(|| Box::new(Diagnostics));
+ store.register_lints(&BadOptAccess::get_lints());
+ store.register_late_pass(|| Box::new(BadOptAccess));
+ store.register_lints(&PassByValue::get_lints());
+ store.register_late_pass(|| Box::new(PassByValue));
+ // FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and
+ // `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and
+ // these lints will trigger all of the time - change this once migration to diagnostic structs
+ // and translation is completed
+ store.register_group(
+ false,
+ "rustc::internal",
+ None,
+ vec![
+ LintId::of(DEFAULT_HASH_TYPES),
+ LintId::of(POTENTIAL_QUERY_INSTABILITY),
+ LintId::of(USAGE_OF_TY_TYKIND),
+ LintId::of(PASS_BY_VALUE),
+ LintId::of(LINT_PASS_IMPL_WITHOUT_MACRO),
+ LintId::of(USAGE_OF_QUALIFIED_TY),
+ LintId::of(EXISTING_DOC_KEYWORD),
+ LintId::of(BAD_OPT_ACCESS),
+ ],
+ );
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs
new file mode 100644
index 000000000..ff5a01749
--- /dev/null
+++ b/compiler/rustc_lint/src/methods.rs
@@ -0,0 +1,103 @@
+use crate::LateContext;
+use crate::LateLintPass;
+use crate::LintContext;
+use rustc_errors::fluent;
+use rustc_hir::{Expr, ExprKind, PathSegment};
+use rustc_middle::ty;
+use rustc_span::{symbol::sym, ExpnKind, Span};
+
+declare_lint! {
+ /// The `temporary_cstring_as_ptr` lint detects getting the inner pointer of
+ /// a temporary `CString`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # #![allow(unused)]
+ /// # use std::ffi::CString;
+ /// let c_str = CString::new("foo").unwrap().as_ptr();
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The inner pointer of a `CString` lives only as long as the `CString` it
+ /// points to. Getting the inner pointer of a *temporary* `CString` allows the `CString`
+ /// to be dropped at the end of the statement, as it is not being referenced as far as the typesystem
+ /// is concerned. This means outside of the statement the pointer will point to freed memory, which
+ /// causes undefined behavior if the pointer is later dereferenced.
+ pub TEMPORARY_CSTRING_AS_PTR,
+ Warn,
+ "detects getting the inner pointer of a temporary `CString`"
+}
+
+declare_lint_pass!(TemporaryCStringAsPtr => [TEMPORARY_CSTRING_AS_PTR]);
+
+fn in_macro(span: Span) -> bool {
+ if span.from_expansion() {
+ !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
+ } else {
+ false
+ }
+}
+
+fn first_method_call<'tcx>(
+ expr: &'tcx Expr<'tcx>,
+) -> Option<(&'tcx PathSegment<'tcx>, &'tcx [Expr<'tcx>])> {
+ if let ExprKind::MethodCall(path, args, _) = &expr.kind {
+ if args.iter().any(|e| e.span.from_expansion()) { None } else { Some((path, *args)) }
+ } else {
+ None
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ if in_macro(expr.span) {
+ return;
+ }
+
+ match first_method_call(expr) {
+ Some((path, args)) if path.ident.name == sym::as_ptr => {
+ let unwrap_arg = &args[0];
+ let as_ptr_span = path.ident.span;
+ match first_method_call(unwrap_arg) {
+ Some((path, args))
+ if path.ident.name == sym::unwrap || path.ident.name == sym::expect =>
+ {
+ let source_arg = &args[0];
+ lint_cstring_as_ptr(cx, as_ptr_span, source_arg, unwrap_arg);
+ }
+ _ => return,
+ }
+ }
+ _ => return,
+ }
+ }
+}
+
+fn lint_cstring_as_ptr(
+ cx: &LateContext<'_>,
+ as_ptr_span: Span,
+ source: &rustc_hir::Expr<'_>,
+ unwrap: &rustc_hir::Expr<'_>,
+) {
+ let source_type = cx.typeck_results().expr_ty(source);
+ if let ty::Adt(def, substs) = source_type.kind() {
+ if cx.tcx.is_diagnostic_item(sym::Result, def.did()) {
+ if let ty::Adt(adt, _) = substs.type_at(0).kind() {
+ if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) {
+ cx.struct_span_lint(TEMPORARY_CSTRING_AS_PTR, as_ptr_span, |diag| {
+ diag.build(fluent::lint::cstring_ptr)
+ .span_label(as_ptr_span, fluent::lint::as_ptr_label)
+ .span_label(unwrap.span, fluent::lint::unwrap_label)
+ .note(fluent::lint::note)
+ .help(fluent::lint::help)
+ .emit();
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs
new file mode 100644
index 000000000..764003e61
--- /dev/null
+++ b/compiler/rustc_lint/src/non_ascii_idents.rs
@@ -0,0 +1,345 @@
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::fluent;
+use rustc_span::symbol::Symbol;
+
+declare_lint! {
+ /// The `non_ascii_idents` lint detects non-ASCII identifiers.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// # #![allow(unused)]
+ /// #![deny(non_ascii_idents)]
+ /// fn main() {
+ /// let föö = 1;
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint allows projects that wish to retain the limit of only using
+ /// ASCII characters to switch this lint to "forbid" (for example to ease
+ /// collaboration or for security reasons).
+ /// See [RFC 2457] for more details.
+ ///
+ /// [RFC 2457]: https://github.com/rust-lang/rfcs/blob/master/text/2457-non-ascii-idents.md
+ pub NON_ASCII_IDENTS,
+ Allow,
+ "detects non-ASCII identifiers",
+ crate_level_only
+}
+
+declare_lint! {
+ /// The `uncommon_codepoints` lint detects uncommon Unicode codepoints in
+ /// identifiers.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # #![allow(unused)]
+ /// const µ: f64 = 0.000001;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint warns about using characters which are not commonly used, and may
+ /// cause visual confusion.
+ ///
+ /// This lint is triggered by identifiers that contain a codepoint that is
+ /// not part of the set of "Allowed" codepoints as described by [Unicode®
+ /// Technical Standard #39 Unicode Security Mechanisms Section 3.1 General
+ /// Security Profile for Identifiers][TR39Allowed].
+ ///
+ /// Note that the set of uncommon codepoints may change over time. Beware
+ /// that if you "forbid" this lint that existing code may fail in the
+ /// future.
+ ///
+ /// [TR39Allowed]: https://www.unicode.org/reports/tr39/#General_Security_Profile
+ pub UNCOMMON_CODEPOINTS,
+ Warn,
+ "detects uncommon Unicode codepoints in identifiers",
+ crate_level_only
+}
+
+declare_lint! {
+ /// The `confusable_idents` lint detects visually confusable pairs between
+ /// identifiers.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// // Latin Capital Letter E With Caron
+ /// pub const Äš: i32 = 1;
+ /// // Latin Capital Letter E With Breve
+ /// pub const Ä”: i32 = 2;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint warns when different identifiers may appear visually similar,
+ /// which can cause confusion.
+ ///
+ /// The confusable detection algorithm is based on [Unicode® Technical
+ /// Standard #39 Unicode Security Mechanisms Section 4 Confusable
+ /// Detection][TR39Confusable]. For every distinct identifier X execute
+ /// the function `skeleton(X)`. If there exist two distinct identifiers X
+ /// and Y in the same crate where `skeleton(X) = skeleton(Y)` report it.
+ /// The compiler uses the same mechanism to check if an identifier is too
+ /// similar to a keyword.
+ ///
+ /// Note that the set of confusable characters may change over time.
+ /// Beware that if you "forbid" this lint that existing code may fail in
+ /// the future.
+ ///
+ /// [TR39Confusable]: https://www.unicode.org/reports/tr39/#Confusable_Detection
+ pub CONFUSABLE_IDENTS,
+ Warn,
+ "detects visually confusable pairs between identifiers",
+ crate_level_only
+}
+
+declare_lint! {
+ /// The `mixed_script_confusables` lint detects visually confusable
+ /// characters in identifiers between different [scripts].
+ ///
+ /// [scripts]: https://en.wikipedia.org/wiki/Script_(Unicode)
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// // The Japanese katakana character エ can be confused with the Han character 工.
+ /// const エ: &'static str = "アイウ";
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// This lint warns when characters between different scripts may appear
+ /// visually similar, which can cause confusion.
+ ///
+ /// If the crate contains other identifiers in the same script that have
+ /// non-confusable characters, then this lint will *not* be issued. For
+ /// example, if the example given above has another identifier with
+ /// katakana characters (such as `let カタカナ = 123;`), then this indicates
+ /// that you are intentionally using katakana, and it will not warn about
+ /// it.
+ ///
+ /// Note that the set of confusable characters may change over time.
+ /// Beware that if you "forbid" this lint that existing code may fail in
+ /// the future.
+ pub MIXED_SCRIPT_CONFUSABLES,
+ Warn,
+ "detects Unicode scripts whose mixed script confusables codepoints are solely used",
+ crate_level_only
+}
+
+declare_lint_pass!(NonAsciiIdents => [NON_ASCII_IDENTS, UNCOMMON_CODEPOINTS, CONFUSABLE_IDENTS, MIXED_SCRIPT_CONFUSABLES]);
+
+impl EarlyLintPass for NonAsciiIdents {
+ fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
+ use rustc_session::lint::Level;
+ use rustc_span::Span;
+ use std::collections::BTreeMap;
+ use unicode_security::GeneralSecurityProfile;
+
+ let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow;
+ let check_uncommon_codepoints =
+ cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow;
+ let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow;
+ let check_mixed_script_confusables =
+ cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow;
+
+ if !check_non_ascii_idents
+ && !check_uncommon_codepoints
+ && !check_confusable_idents
+ && !check_mixed_script_confusables
+ {
+ return;
+ }
+
+ let mut has_non_ascii_idents = false;
+ let symbols = cx.sess().parse_sess.symbol_gallery.symbols.lock();
+
+ // Sort by `Span` so that error messages make sense with respect to the
+ // order of identifier locations in the code.
+ let mut symbols: Vec<_> = symbols.iter().collect();
+ symbols.sort_by_key(|k| k.1);
+
+ for (symbol, &sp) in symbols.iter() {
+ let symbol_str = symbol.as_str();
+ if symbol_str.is_ascii() {
+ continue;
+ }
+ has_non_ascii_idents = true;
+ cx.struct_span_lint(NON_ASCII_IDENTS, sp, |lint| {
+ lint.build(fluent::lint::identifier_non_ascii_char).emit();
+ });
+ if check_uncommon_codepoints
+ && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed)
+ {
+ cx.struct_span_lint(UNCOMMON_CODEPOINTS, sp, |lint| {
+ lint.build(fluent::lint::identifier_uncommon_codepoints).emit();
+ })
+ }
+ }
+
+ if has_non_ascii_idents && check_confusable_idents {
+ let mut skeleton_map: FxHashMap<Symbol, (Symbol, Span, bool)> =
+ FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default());
+ let mut skeleton_buf = String::new();
+
+ for (&symbol, &sp) in symbols.iter() {
+ use unicode_security::confusable_detection::skeleton;
+
+ let symbol_str = symbol.as_str();
+ let is_ascii = symbol_str.is_ascii();
+
+ // Get the skeleton as a `Symbol`.
+ skeleton_buf.clear();
+ skeleton_buf.extend(skeleton(&symbol_str));
+ let skeleton_sym = if *symbol_str == *skeleton_buf {
+ symbol
+ } else {
+ Symbol::intern(&skeleton_buf)
+ };
+
+ skeleton_map
+ .entry(skeleton_sym)
+ .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| {
+ if !*existing_is_ascii || !is_ascii {
+ cx.struct_span_lint(CONFUSABLE_IDENTS, sp, |lint| {
+ lint.build(fluent::lint::confusable_identifier_pair)
+ .set_arg("existing_sym", *existing_symbol)
+ .set_arg("sym", symbol)
+ .span_label(*existing_span, fluent::lint::label)
+ .emit();
+ });
+ }
+ if *existing_is_ascii && !is_ascii {
+ *existing_symbol = symbol;
+ *existing_span = sp;
+ *existing_is_ascii = is_ascii;
+ }
+ })
+ .or_insert((symbol, sp, is_ascii));
+ }
+ }
+
+ if has_non_ascii_idents && check_mixed_script_confusables {
+ use unicode_security::is_potential_mixed_script_confusable_char;
+ use unicode_security::mixed_script::AugmentedScriptSet;
+
+ #[derive(Clone)]
+ enum ScriptSetUsage {
+ Suspicious(Vec<char>, Span),
+ Verified,
+ }
+
+ let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> =
+ FxHashMap::default();
+ let latin_augmented_script_set = AugmentedScriptSet::for_char('A');
+ script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified);
+
+ let mut has_suspicous = false;
+ for (symbol, &sp) in symbols.iter() {
+ let symbol_str = symbol.as_str();
+ for ch in symbol_str.chars() {
+ if ch.is_ascii() {
+ // all ascii characters are covered by exception.
+ continue;
+ }
+ if !GeneralSecurityProfile::identifier_allowed(ch) {
+ // this character is covered by `uncommon_codepoints` lint.
+ continue;
+ }
+ let augmented_script_set = AugmentedScriptSet::for_char(ch);
+ script_states
+ .entry(augmented_script_set)
+ .and_modify(|existing_state| {
+ if let ScriptSetUsage::Suspicious(ch_list, _) = existing_state {
+ if is_potential_mixed_script_confusable_char(ch) {
+ ch_list.push(ch);
+ } else {
+ *existing_state = ScriptSetUsage::Verified;
+ }
+ }
+ })
+ .or_insert_with(|| {
+ if !is_potential_mixed_script_confusable_char(ch) {
+ ScriptSetUsage::Verified
+ } else {
+ has_suspicous = true;
+ ScriptSetUsage::Suspicious(vec![ch], sp)
+ }
+ });
+ }
+ }
+
+ if has_suspicous {
+ let verified_augmented_script_sets = script_states
+ .iter()
+ .flat_map(|(k, v)| match v {
+ ScriptSetUsage::Verified => Some(*k),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+
+ // we're sorting the output here.
+ let mut lint_reports: BTreeMap<(Span, Vec<char>), AugmentedScriptSet> =
+ BTreeMap::new();
+
+ 'outerloop: for (augment_script_set, usage) in script_states {
+ let ScriptSetUsage::Suspicious(mut ch_list, sp) = usage else { continue };
+
+ if augment_script_set.is_all() {
+ continue;
+ }
+
+ for existing in verified_augmented_script_sets.iter() {
+ if existing.is_all() {
+ continue;
+ }
+ let mut intersect = *existing;
+ intersect.intersect_with(augment_script_set);
+ if !intersect.is_empty() && !intersect.is_all() {
+ continue 'outerloop;
+ }
+ }
+
+ // We sort primitive chars here and can use unstable sort
+ ch_list.sort_unstable();
+ ch_list.dedup();
+ lint_reports.insert((sp, ch_list), augment_script_set);
+ }
+
+ for ((sp, ch_list), script_set) in lint_reports {
+ cx.struct_span_lint(MIXED_SCRIPT_CONFUSABLES, sp, |lint| {
+ let mut includes = String::new();
+ for (idx, ch) in ch_list.into_iter().enumerate() {
+ if idx != 0 {
+ includes += ", ";
+ }
+ let char_info = format!("'{}' (U+{:04X})", ch, ch as u32);
+ includes += &char_info;
+ }
+ lint.build(fluent::lint::mixed_script_confusables)
+ .set_arg("set", script_set.to_string())
+ .set_arg("includes", includes)
+ .note(fluent::lint::includes_note)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs
new file mode 100644
index 000000000..cdad2d2e8
--- /dev/null
+++ b/compiler/rustc_lint/src/non_fmt_panic.rs
@@ -0,0 +1,357 @@
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_errors::{fluent, Applicability};
+use rustc_hir as hir;
+use rustc_infer::infer::TyCtxtInferExt;
+use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
+use rustc_middle::ty::subst::InternalSubsts;
+use rustc_parse_format::{ParseMode, Parser, Piece};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_span::edition::Edition;
+use rustc_span::{hygiene, sym, symbol::kw, InnerSpan, Span, Symbol};
+use rustc_trait_selection::infer::InferCtxtExt;
+
+declare_lint! {
+ /// The `non_fmt_panics` lint detects `panic!(..)` invocations where the first
+ /// argument is not a formatting string.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,no_run,edition2018
+ /// panic!("{}");
+ /// panic!(123);
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// In Rust 2018 and earlier, `panic!(x)` directly uses `x` as the message.
+ /// That means that `panic!("{}")` panics with the message `"{}"` instead
+ /// of using it as a formatting string, and `panic!(123)` will panic with
+ /// an `i32` as message.
+ ///
+ /// Rust 2021 always interprets the first argument as format string.
+ NON_FMT_PANICS,
+ Warn,
+ "detect single-argument panic!() invocations in which the argument is not a format string",
+ @future_incompatible = FutureIncompatibleInfo {
+ reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
+ explain_reason: false,
+ };
+ report_in_external_macro
+}
+
+declare_lint_pass!(NonPanicFmt => [NON_FMT_PANICS]);
+
+impl<'tcx> LateLintPass<'tcx> for NonPanicFmt {
+ 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() {
+ let f_diagnostic_name = cx.tcx.get_diagnostic_name(def_id);
+
+ if Some(def_id) == cx.tcx.lang_items().begin_panic_fn()
+ || Some(def_id) == cx.tcx.lang_items().panic_fn()
+ || f_diagnostic_name == Some(sym::panic_str)
+ {
+ if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
+ if matches!(
+ cx.tcx.get_diagnostic_name(id),
+ Some(sym::core_panic_2015_macro | sym::std_panic_2015_macro)
+ ) {
+ check_panic(cx, f, arg);
+ }
+ }
+ } else if f_diagnostic_name == Some(sym::unreachable_display) {
+ if let Some(id) = f.span.ctxt().outer_expn_data().macro_def_id {
+ if cx.tcx.is_diagnostic_item(sym::unreachable_2015_macro, id) {
+ check_panic(
+ cx,
+ f,
+ // This is safe because we checked above that the callee is indeed
+ // unreachable_display
+ match &arg.kind {
+ // Get the borrowed arg not the borrow
+ hir::ExprKind::AddrOf(ast::BorrowKind::Ref, _, arg) => arg,
+ _ => bug!("call to unreachable_display without borrow"),
+ },
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+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 {
+ // The argument is a string literal.
+ check_panic_str(cx, f, arg, sym.as_str());
+ return;
+ }
+ }
+
+ // The argument is *not* a string literal.
+
+ let (span, panic, symbol) = panic_call(cx, f);
+
+ if in_external_macro(cx.sess(), span) {
+ // Nothing that can be done about it in the current crate.
+ return;
+ }
+
+ // Find the span of the argument to `panic!()` or `unreachable!`, before expansion in the
+ // case of `panic!(some_macro!())` or `unreachable!(some_macro!())`.
+ // We don't use source_callsite(), because this `panic!(..)` might itself
+ // be expanded from another macro, in which case we want to stop at that
+ // expansion.
+ let mut arg_span = arg.span;
+ let mut arg_macro = None;
+ while !span.contains(arg_span) {
+ let expn = arg_span.ctxt().outer_expn_data();
+ if expn.is_root() {
+ break;
+ }
+ arg_macro = expn.macro_def_id;
+ arg_span = expn.call_site;
+ }
+
+ cx.struct_span_lint(NON_FMT_PANICS, arg_span, |lint| {
+ let mut l = lint.build(fluent::lint::non_fmt_panic);
+ l.set_arg("name", symbol);
+ l.note(fluent::lint::note);
+ l.note(fluent::lint::more_info_note);
+ if !is_arg_inside_call(arg_span, span) {
+ // No clue where this argument is coming from.
+ l.emit();
+ return;
+ }
+ if arg_macro.map_or(false, |id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) {
+ // A case of `panic!(format!(..))`.
+ l.note(fluent::lint::supports_fmt_note);
+ if let Some((open, close, _)) = find_delimiters(cx, arg_span) {
+ l.multipart_suggestion(
+ fluent::lint::supports_fmt_suggestion,
+ vec![
+ (arg_span.until(open.shrink_to_hi()), "".into()),
+ (close.until(arg_span.shrink_to_hi()), "".into()),
+ ],
+ Applicability::MachineApplicable,
+ );
+ }
+ } else {
+ let ty = cx.typeck_results().expr_ty(arg);
+ // If this is a &str or String, we can confidently give the `"{}", ` suggestion.
+ let is_str = matches!(
+ ty.kind(),
+ ty::Ref(_, r, _) if *r.kind() == ty::Str,
+ ) || matches!(
+ ty.ty_adt_def(),
+ Some(ty_def) if cx.tcx.is_diagnostic_item(sym::String, ty_def.did()),
+ );
+
+ let (suggest_display, suggest_debug) = cx.tcx.infer_ctxt().enter(|infcx| {
+ let display = is_str
+ || cx.tcx.get_diagnostic_item(sym::Display).map(|t| {
+ infcx
+ .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env)
+ .may_apply()
+ }) == Some(true);
+ let debug = !display
+ && cx.tcx.get_diagnostic_item(sym::Debug).map(|t| {
+ infcx
+ .type_implements_trait(t, ty, InternalSubsts::empty(), cx.param_env)
+ .may_apply()
+ }) == Some(true);
+ (display, debug)
+ });
+
+ let suggest_panic_any = !is_str && panic == sym::std_panic_macro;
+
+ let fmt_applicability = if suggest_panic_any {
+ // If we can use panic_any, use that as the MachineApplicable suggestion.
+ Applicability::MaybeIncorrect
+ } else {
+ // If we don't suggest panic_any, using a format string is our best bet.
+ Applicability::MachineApplicable
+ };
+
+ if suggest_display {
+ l.span_suggestion_verbose(
+ arg_span.shrink_to_lo(),
+ fluent::lint::display_suggestion,
+ "\"{}\", ",
+ fmt_applicability,
+ );
+ } else if suggest_debug {
+ l.set_arg("ty", ty);
+ l.span_suggestion_verbose(
+ arg_span.shrink_to_lo(),
+ fluent::lint::debug_suggestion,
+ "\"{:?}\", ",
+ fmt_applicability,
+ );
+ }
+
+ if suggest_panic_any {
+ if let Some((open, close, del)) = find_delimiters(cx, span) {
+ l.set_arg("already_suggested", suggest_display || suggest_debug);
+ l.multipart_suggestion(
+ fluent::lint::panic_suggestion,
+ if del == '(' {
+ vec![(span.until(open), "std::panic::panic_any".into())]
+ } else {
+ vec![
+ (span.until(open.shrink_to_hi()), "std::panic::panic_any(".into()),
+ (close, ")".into()),
+ ]
+ },
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+ l.emit();
+ });
+}
+
+fn check_panic_str<'tcx>(
+ cx: &LateContext<'tcx>,
+ f: &'tcx hir::Expr<'tcx>,
+ arg: &'tcx hir::Expr<'tcx>,
+ fmt: &str,
+) {
+ if !fmt.contains(&['{', '}']) {
+ // No brace, no problem.
+ return;
+ }
+
+ let (span, _, _) = panic_call(cx, f);
+
+ if in_external_macro(cx.sess(), span) && in_external_macro(cx.sess(), arg.span) {
+ // Nothing that can be done about it in the current crate.
+ 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, style, snippet.clone(), false, ParseMode::Format);
+ let n_arguments = (&mut fmt_parser).filter(|a| matches!(a, Piece::NextArgument(_))).count();
+
+ 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(InnerSpan::new(span.start, span.end)))
+ .collect(),
+ };
+ cx.struct_span_lint(NON_FMT_PANICS, arg_spans, |lint| {
+ let mut l = lint.build(fluent::lint::non_fmt_panic_unused);
+ l.set_arg("count", n_arguments);
+ l.note(fluent::lint::note);
+ if is_arg_inside_call(arg.span, span) {
+ l.span_suggestion(
+ arg.span.shrink_to_hi(),
+ fluent::lint::add_args_suggestion,
+ ", ...",
+ Applicability::HasPlaceholders,
+ );
+ l.span_suggestion(
+ arg.span.shrink_to_lo(),
+ fluent::lint::add_fmt_suggestion,
+ "\"{}\", ",
+ 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 count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2);
+ cx.struct_span_lint(NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), |lint| {
+ let mut l = lint.build(fluent::lint::non_fmt_panic_braces);
+ l.set_arg("count", count);
+ l.note(fluent::lint::note);
+ if is_arg_inside_call(arg.span, span) {
+ l.span_suggestion(
+ arg.span.shrink_to_lo(),
+ fluent::lint::suggestion,
+ "\"{}\", ",
+ Applicability::MachineApplicable,
+ );
+ }
+ l.emit();
+ });
+ }
+}
+
+/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`,
+/// and the type of (opening) delimiter used.
+fn find_delimiters<'tcx>(cx: &LateContext<'tcx>, span: Span) -> Option<(Span, Span, char)> {
+ let snippet = cx.sess().parse_sess.source_map().span_to_snippet(span).ok()?;
+ let (open, open_ch) = snippet.char_indices().find(|&(_, c)| "([{".contains(c))?;
+ let close = snippet.rfind(|c| ")]}".contains(c))?;
+ Some((
+ span.from_inner(InnerSpan { start: open, end: open + 1 }),
+ span.from_inner(InnerSpan { start: close, end: close + 1 }),
+ open_ch,
+ ))
+}
+
+fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, Symbol) {
+ let mut expn = f.span.ctxt().outer_expn_data();
+
+ let mut panic_macro = kw::Empty;
+
+ // Unwrap more levels of macro expansion, as panic_2015!()
+ // was likely expanded from panic!() and possibly from
+ // [debug_]assert!().
+ loop {
+ let parent = expn.call_site.ctxt().outer_expn_data();
+ let Some(id) = parent.macro_def_id else { break };
+ let Some(name) = cx.tcx.get_diagnostic_name(id) else { break };
+ if !matches!(
+ name,
+ sym::core_panic_macro
+ | sym::std_panic_macro
+ | sym::assert_macro
+ | sym::debug_assert_macro
+ | sym::unreachable_macro
+ ) {
+ break;
+ }
+ expn = parent;
+ panic_macro = name;
+ }
+
+ let macro_symbol =
+ if let hygiene::ExpnKind::Macro(_, symbol) = expn.kind { symbol } else { sym::panic };
+ (expn.call_site, panic_macro, macro_symbol)
+}
+
+fn is_arg_inside_call(arg: Span, call: Span) -> bool {
+ // We only add suggestions if the argument we're looking at appears inside the
+ // panic call in the source file, to avoid invalid suggestions when macros are involved.
+ // We specifically check for the spans to not be identical, as that happens sometimes when
+ // proc_macros lie about spans and apply the same span to all the tokens they produce.
+ call.contains(arg) && !call.source_equal(arg)
+}
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
new file mode 100644
index 000000000..8d04d68bf
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -0,0 +1,565 @@
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_errors::{fluent, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{GenericParamKind, PatKind};
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+use rustc_span::{symbol::Ident, BytePos, Span};
+use rustc_target::spec::abi::Abi;
+
+#[derive(PartialEq)]
+pub enum MethodLateContext {
+ TraitAutoImpl,
+ TraitImpl,
+ PlainImpl,
+}
+
+pub fn method_context(cx: &LateContext<'_>, id: hir::HirId) -> MethodLateContext {
+ let def_id = cx.tcx.hir().local_def_id(id);
+ let item = cx.tcx.associated_item(def_id);
+ match item.container {
+ ty::TraitContainer => MethodLateContext::TraitAutoImpl,
+ ty::ImplContainer => match cx.tcx.impl_trait_ref(item.container_id(cx.tcx)) {
+ Some(_) => MethodLateContext::TraitImpl,
+ None => MethodLateContext::PlainImpl,
+ },
+ }
+}
+
+declare_lint! {
+ /// The `non_camel_case_types` lint detects types, variants, traits and
+ /// type parameters that don't have camel case names.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// struct my_struct;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The preferred style for these identifiers is to use "camel case", such
+ /// as `MyStruct`, where the first letter should not be lowercase, and
+ /// should not use underscores between letters. Underscores are allowed at
+ /// the beginning and end of the identifier, as well as between
+ /// non-letters (such as `X86_64`).
+ pub NON_CAMEL_CASE_TYPES,
+ Warn,
+ "types, variants, traits and type parameters should have camel case names"
+}
+
+declare_lint_pass!(NonCamelCaseTypes => [NON_CAMEL_CASE_TYPES]);
+
+/// Some unicode characters *have* case, are considered upper case or lower case, but they *can't*
+/// be upper cased or lower cased. For the purposes of the lint suggestion, we care about being able
+/// to change the char's case.
+fn char_has_case(c: char) -> bool {
+ let mut l = c.to_lowercase();
+ let mut u = c.to_uppercase();
+ while let Some(l) = l.next() {
+ match u.next() {
+ Some(u) if l != u => return true,
+ _ => {}
+ }
+ }
+ u.next().is_some()
+}
+
+fn is_camel_case(name: &str) -> bool {
+ let name = name.trim_matches('_');
+ if name.is_empty() {
+ return true;
+ }
+
+ // start with a non-lowercase letter rather than non-uppercase
+ // ones (some scripts don't have a concept of upper/lowercase)
+ !name.chars().next().unwrap().is_lowercase()
+ && !name.contains("__")
+ && !name.chars().collect::<Vec<_>>().array_windows().any(|&[fst, snd]| {
+ // contains a capitalisable character followed by, or preceded by, an underscore
+ char_has_case(fst) && snd == '_' || char_has_case(snd) && fst == '_'
+ })
+}
+
+fn to_camel_case(s: &str) -> String {
+ s.trim_matches('_')
+ .split('_')
+ .filter(|component| !component.is_empty())
+ .map(|component| {
+ let mut camel_cased_component = String::new();
+
+ let mut new_word = true;
+ let mut prev_is_lower_case = true;
+
+ for c in component.chars() {
+ // Preserve the case if an uppercase letter follows a lowercase letter, so that
+ // `camelCase` is converted to `CamelCase`.
+ if prev_is_lower_case && c.is_uppercase() {
+ new_word = true;
+ }
+
+ if new_word {
+ camel_cased_component.extend(c.to_uppercase());
+ } else {
+ camel_cased_component.extend(c.to_lowercase());
+ }
+
+ prev_is_lower_case = c.is_lowercase();
+ new_word = false;
+ }
+
+ camel_cased_component
+ })
+ .fold((String::new(), None), |(acc, prev): (String, Option<String>), next| {
+ // separate two components with an underscore if their boundary cannot
+ // be distinguished using an uppercase/lowercase case distinction
+ let join = if let Some(prev) = prev {
+ let l = prev.chars().last().unwrap();
+ let f = next.chars().next().unwrap();
+ !char_has_case(l) && !char_has_case(f)
+ } else {
+ false
+ };
+ (acc + if join { "_" } else { "" } + &next, Some(next))
+ })
+ .0
+}
+
+impl NonCamelCaseTypes {
+ fn check_case(&self, cx: &EarlyContext<'_>, sort: &str, ident: &Ident) {
+ let name = ident.name.as_str();
+
+ if !is_camel_case(name) {
+ cx.struct_span_lint(NON_CAMEL_CASE_TYPES, ident.span, |lint| {
+ let mut err = lint.build(fluent::lint::non_camel_case_type);
+ 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,
+ fluent::lint::suggestion,
+ to_camel_case(name),
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ err.span_label(ident.span, fluent::lint::label);
+ }
+
+ err.set_arg("sort", sort);
+ err.set_arg("name", name);
+ err.emit();
+ })
+ }
+ }
+}
+
+impl EarlyLintPass for NonCamelCaseTypes {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) {
+ let has_repr_c = it
+ .attrs
+ .iter()
+ .any(|attr| attr::find_repr_attrs(cx.sess(), attr).contains(&attr::ReprC));
+
+ if has_repr_c {
+ return;
+ }
+
+ match it.kind {
+ ast::ItemKind::TyAlias(..)
+ | ast::ItemKind::Enum(..)
+ | ast::ItemKind::Struct(..)
+ | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident),
+ ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident),
+ ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident),
+ _ => (),
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) {
+ if let ast::AssocItemKind::TyAlias(..) = it.kind {
+ self.check_case(cx, "associated type", &it.ident);
+ }
+ }
+
+ fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
+ self.check_case(cx, "variant", &v.ident);
+ }
+
+ fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) {
+ if let ast::GenericParamKind::Type { .. } = param.kind {
+ self.check_case(cx, "type parameter", &param.ident);
+ }
+ }
+}
+
+declare_lint! {
+ /// The `non_snake_case` lint detects variables, methods, functions,
+ /// lifetime parameters and modules that don't have snake case names.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let MY_VALUE = 5;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The preferred style for these identifiers is to use "snake case",
+ /// where all the characters are in lowercase, with words separated with a
+ /// single underscore, such as `my_value`.
+ pub NON_SNAKE_CASE,
+ Warn,
+ "variables, methods, functions, lifetime parameters and modules should have snake case names"
+}
+
+declare_lint_pass!(NonSnakeCase => [NON_SNAKE_CASE]);
+
+impl NonSnakeCase {
+ fn to_snake_case(mut str: &str) -> String {
+ let mut words = vec![];
+ // Preserve leading underscores
+ str = str.trim_start_matches(|c: char| {
+ if c == '_' {
+ words.push(String::new());
+ true
+ } else {
+ false
+ }
+ });
+ for s in str.split('_') {
+ let mut last_upper = false;
+ let mut buf = String::new();
+ if s.is_empty() {
+ continue;
+ }
+ for ch in s.chars() {
+ if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
+ words.push(buf);
+ buf = String::new();
+ }
+ last_upper = ch.is_uppercase();
+ buf.extend(ch.to_lowercase());
+ }
+ words.push(buf);
+ }
+ words.join("_")
+ }
+
+ /// Checks if a given identifier is snake case, and reports a diagnostic if not.
+ fn check_snake_case(&self, cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+ fn is_snake_case(ident: &str) -> bool {
+ if ident.is_empty() {
+ return true;
+ }
+ let ident = ident.trim_start_matches('\'');
+ let ident = ident.trim_matches('_');
+
+ let mut allow_underscore = true;
+ ident.chars().all(|c| {
+ allow_underscore = match c {
+ '_' if !allow_underscore => return false,
+ '_' => false,
+ // It would be more obvious to use `c.is_lowercase()`,
+ // but some characters do not have a lowercase form
+ c if !c.is_uppercase() => true,
+ _ => return false,
+ };
+ true
+ })
+ }
+
+ let name = ident.name.as_str();
+
+ if !is_snake_case(name) {
+ cx.struct_span_lint(NON_SNAKE_CASE, ident.span, |lint| {
+ let sc = NonSnakeCase::to_snake_case(name);
+ let mut err = lint.build(fluent::lint::non_snake_case);
+ // 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() {
+ let sc_ident = Ident::from_str_and_span(&sc, ident.span);
+ let (message, suggestion) = if sc_ident.is_reserved() {
+ // We shouldn't suggest a reserved identifier to fix non-snake-case identifiers.
+ // Instead, recommend renaming the identifier entirely or, if permitted,
+ // escaping it to create a raw identifier.
+ if sc_ident.name.can_be_raw() {
+ (fluent::lint::rename_or_convert_suggestion, sc_ident.to_string())
+ } else {
+ err.note(fluent::lint::cannot_convert_note);
+ (fluent::lint::rename_suggestion, String::new())
+ }
+ } else {
+ (fluent::lint::convert_suggestion, sc.clone())
+ };
+
+ err.span_suggestion(
+ ident.span,
+ message,
+ suggestion,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ err.help(fluent::lint::help);
+ }
+ } else {
+ err.span_label(ident.span, fluent::lint::label);
+ }
+
+ err.set_arg("sort", sort);
+ err.set_arg("name", name);
+ err.set_arg("sc", sc);
+ err.emit();
+ });
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
+ fn check_mod(
+ &mut self,
+ cx: &LateContext<'_>,
+ _: &'tcx hir::Mod<'tcx>,
+ _: Span,
+ id: hir::HirId,
+ ) {
+ if id != hir::CRATE_HIR_ID {
+ return;
+ }
+
+ let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name {
+ Some(Ident::from_str(name))
+ } else {
+ cx.sess()
+ .find_by_name(&cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name)
+ .and_then(|attr| attr.meta())
+ .and_then(|meta| {
+ meta.name_value_literal().and_then(|lit| {
+ if let ast::LitKind::Str(name, ..) = lit.kind {
+ // Discard the double quotes surrounding the literal.
+ let sp = cx
+ .sess()
+ .source_map()
+ .span_to_snippet(lit.span)
+ .ok()
+ .and_then(|snippet| {
+ let left = snippet.find('"')?;
+ let right =
+ snippet.rfind('"').map(|pos| snippet.len() - pos)?;
+
+ Some(
+ lit.span
+ .with_lo(lit.span.lo() + BytePos(left as u32 + 1))
+ .with_hi(lit.span.hi() - BytePos(right as u32)),
+ )
+ })
+ .unwrap_or(lit.span);
+
+ Some(Ident::new(name, sp))
+ } else {
+ None
+ }
+ })
+ })
+ };
+
+ if let Some(ident) = &crate_ident {
+ self.check_snake_case(cx, "crate", ident);
+ }
+ }
+
+ fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+ if let GenericParamKind::Lifetime { .. } = param.kind {
+ self.check_snake_case(cx, "lifetime", &param.name.ident());
+ }
+ }
+
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'_>,
+ fk: FnKind<'_>,
+ _: &hir::FnDecl<'_>,
+ _: &hir::Body<'_>,
+ _: Span,
+ id: hir::HirId,
+ ) {
+ let attrs = cx.tcx.hir().attrs(id);
+ match &fk {
+ FnKind::Method(ident, sig, ..) => match method_context(cx, id) {
+ MethodLateContext::PlainImpl => {
+ if sig.header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle)
+ {
+ return;
+ }
+ self.check_snake_case(cx, "method", ident);
+ }
+ MethodLateContext::TraitAutoImpl => {
+ self.check_snake_case(cx, "trait method", ident);
+ }
+ _ => (),
+ },
+ FnKind::ItemFn(ident, _, header) => {
+ // Skip foreign-ABI #[no_mangle] functions (Issue #31924)
+ if header.abi != Abi::Rust && cx.sess().contains_name(attrs, sym::no_mangle) {
+ return;
+ }
+ self.check_snake_case(cx, "function", ident);
+ }
+ FnKind::Closure => (),
+ }
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ if let hir::ItemKind::Mod(_) = it.kind {
+ self.check_snake_case(cx, "module", &it.ident);
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
+ if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
+ self.check_snake_case(cx, "trait method", &item.ident);
+ for param_name in pnames {
+ self.check_snake_case(cx, "variable", param_name);
+ }
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
+ if let PatKind::Binding(_, hid, ident, _) = p.kind {
+ if let hir::Node::Pat(parent_pat) = cx.tcx.hir().get(cx.tcx.hir().get_parent_node(hid))
+ {
+ if let PatKind::Struct(_, field_pats, _) = &parent_pat.kind {
+ if field_pats
+ .iter()
+ .any(|field| !field.is_shorthand && field.pat.hir_id == p.hir_id)
+ {
+ // Only check if a new name has been introduced, to avoid warning
+ // on both the struct definition and this pattern.
+ self.check_snake_case(cx, "variable", &ident);
+ }
+ return;
+ }
+ }
+ self.check_snake_case(cx, "variable", &ident);
+ }
+ }
+
+ fn check_struct_def(&mut self, cx: &LateContext<'_>, s: &hir::VariantData<'_>) {
+ for sf in s.fields() {
+ self.check_snake_case(cx, "structure field", &sf.ident);
+ }
+ }
+}
+
+declare_lint! {
+ /// The `non_upper_case_globals` lint detects static items that don't have
+ /// uppercase identifiers.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// static max_points: i32 = 5;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The preferred style is for static item names to use all uppercase
+ /// letters such as `MAX_POINTS`.
+ pub NON_UPPER_CASE_GLOBALS,
+ Warn,
+ "static constants should have uppercase identifiers"
+}
+
+declare_lint_pass!(NonUpperCaseGlobals => [NON_UPPER_CASE_GLOBALS]);
+
+impl NonUpperCaseGlobals {
+ fn check_upper_case(cx: &LateContext<'_>, sort: &str, ident: &Ident) {
+ let name = ident.name.as_str();
+ 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();
+ let mut err = lint.build(fluent::lint::non_upper_case_global);
+ // We cannot provide meaningful suggestions
+ // if the characters are in the category of "Lowercase Letter".
+ if *name != uc {
+ err.span_suggestion(
+ ident.span,
+ fluent::lint::suggestion,
+ uc,
+ Applicability::MaybeIncorrect,
+ );
+ } else {
+ err.span_label(ident.span, fluent::lint::label);
+ }
+
+ err.set_arg("sort", sort);
+ err.set_arg("name", name);
+ err.emit();
+ })
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ let attrs = cx.tcx.hir().attrs(it.hir_id());
+ match it.kind {
+ hir::ItemKind::Static(..) if !cx.sess().contains_name(attrs, sym::no_mangle) => {
+ NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident);
+ }
+ hir::ItemKind::Const(..) => {
+ NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident);
+ }
+ _ => {}
+ }
+ }
+
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, ti: &hir::TraitItem<'_>) {
+ if let hir::TraitItemKind::Const(..) = ti.kind {
+ NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ti.ident);
+ }
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) {
+ if let hir::ImplItemKind::Const(..) = ii.kind {
+ NonUpperCaseGlobals::check_upper_case(cx, "associated constant", &ii.ident);
+ }
+ }
+
+ fn check_pat(&mut self, cx: &LateContext<'_>, p: &hir::Pat<'_>) {
+ // Lint for constants that look like binding identifiers (#7526)
+ if let PatKind::Path(hir::QPath::Resolved(None, ref path)) = p.kind {
+ if let Res::Def(DefKind::Const, _) = path.res {
+ if path.segments.len() == 1 {
+ NonUpperCaseGlobals::check_upper_case(
+ cx,
+ "constant in pattern",
+ &path.segments[0].ident,
+ );
+ }
+ }
+ }
+ }
+
+ fn check_generic_param(&mut self, cx: &LateContext<'_>, param: &hir::GenericParam<'_>) {
+ if let GenericParamKind::Const { .. } = param.kind {
+ NonUpperCaseGlobals::check_upper_case(cx, "const parameter", &param.name.ident());
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_lint/src/nonstandard_style/tests.rs b/compiler/rustc_lint/src/nonstandard_style/tests.rs
new file mode 100644
index 000000000..39c525b86
--- /dev/null
+++ b/compiler/rustc_lint/src/nonstandard_style/tests.rs
@@ -0,0 +1,21 @@
+use super::{is_camel_case, to_camel_case};
+
+#[test]
+fn camel_case() {
+ assert!(!is_camel_case("userData"));
+ assert_eq!(to_camel_case("userData"), "UserData");
+
+ assert!(is_camel_case("X86_64"));
+
+ assert!(!is_camel_case("X86__64"));
+ assert_eq!(to_camel_case("X86__64"), "X86_64");
+
+ assert!(!is_camel_case("Abc_123"));
+ assert_eq!(to_camel_case("Abc_123"), "Abc123");
+
+ assert!(!is_camel_case("A1_b2_c3"));
+ assert_eq!(to_camel_case("A1_b2_c3"), "A1B2C3");
+
+ assert!(!is_camel_case("ONE_TWO_THREE"));
+ assert_eq!(to_camel_case("ONE_TWO_THREE"), "OneTwoThree");
+}
diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs
new file mode 100644
index 000000000..11a752ff0
--- /dev/null
+++ b/compiler/rustc_lint/src/noop_method_call.rs
@@ -0,0 +1,103 @@
+use crate::context::LintContext;
+use crate::rustc_middle::ty::TypeVisitable;
+use crate::LateContext;
+use crate::LateLintPass;
+use rustc_errors::fluent;
+use rustc_hir::def::DefKind;
+use rustc_hir::{Expr, ExprKind};
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+declare_lint! {
+ /// The `noop_method_call` lint detects specific calls to noop methods
+ /// such as a calling `<&T as Clone>::clone` where `T: !Clone`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # #![allow(unused)]
+ /// #![warn(noop_method_call)]
+ /// struct Foo;
+ /// let foo = &Foo;
+ /// let clone: &Foo = foo.clone();
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Some method calls are noops meaning that they do nothing. Usually such methods
+ /// are the result of blanket implementations that happen to create some method invocations
+ /// that end up not doing anything. For instance, `Clone` is implemented on all `&T`, but
+ /// calling `clone` on a `&T` where `T` does not implement clone, actually doesn't do anything
+ /// as references are copy. This lint detects these calls and warns the user about them.
+ pub NOOP_METHOD_CALL,
+ Allow,
+ "detects the use of well-known noop methods"
+}
+
+declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL]);
+
+impl<'tcx> LateLintPass<'tcx> for NoopMethodCall {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ // We only care about method calls.
+ let ExprKind::MethodCall(call, elements, _) = &expr.kind else {
+ return
+ };
+ // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow`
+ // traits and ignore any other method call.
+ let (trait_id, did) = match cx.typeck_results().type_dependent_def(expr.hir_id) {
+ // Verify we are dealing with a method/associated function.
+ Some((DefKind::AssocFn, did)) => match cx.tcx.trait_of_item(did) {
+ // Check that we're dealing with a trait method for one of the traits we care about.
+ Some(trait_id)
+ if matches!(
+ cx.tcx.get_diagnostic_name(trait_id),
+ Some(sym::Borrow | sym::Clone | sym::Deref)
+ ) =>
+ {
+ (trait_id, did)
+ }
+ _ => return,
+ },
+ _ => return,
+ };
+ let substs = cx.typeck_results().node_substs(expr.hir_id);
+ if substs.needs_subst() {
+ // We can't resolve on types that require monomorphization, so we don't handle them if
+ // we need to perform substitution.
+ return;
+ }
+ let param_env = cx.tcx.param_env(trait_id);
+ // Resolve the trait method instance.
+ let Ok(Some(i)) = ty::Instance::resolve(cx.tcx, param_env, did, substs) else {
+ return
+ };
+ // (Re)check that it implements the noop diagnostic.
+ let Some(name) = cx.tcx.get_diagnostic_name(i.def_id()) else { return };
+ if !matches!(
+ name,
+ sym::noop_method_borrow | sym::noop_method_clone | sym::noop_method_deref
+ ) {
+ return;
+ }
+ let receiver = &elements[0];
+ let receiver_ty = cx.typeck_results().expr_ty(receiver);
+ let expr_ty = cx.typeck_results().expr_ty_adjusted(expr);
+ if receiver_ty != expr_ty {
+ // This lint will only trigger if the receiver type and resulting expression \
+ // type are the same, implying that the method call is unnecessary.
+ return;
+ }
+ let expr_span = expr.span;
+ let span = expr_span.with_lo(receiver.span.hi());
+ cx.struct_span_lint(NOOP_METHOD_CALL, span, |lint| {
+ lint.build(fluent::lint::noop_method_call)
+ .set_arg("method", call.ident.name)
+ .set_arg("receiver_ty", receiver_ty)
+ .span_label(span, fluent::lint::label)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+}
diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs
new file mode 100644
index 000000000..af5e5faf1
--- /dev/null
+++ b/compiler/rustc_lint/src/pass_by_value.rs
@@ -0,0 +1,96 @@
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_errors::{fluent, Applicability};
+use rustc_hir as hir;
+use rustc_hir::def::Res;
+use rustc_hir::{GenericArg, PathSegment, QPath, TyKind};
+use rustc_middle::ty;
+use rustc_span::symbol::sym;
+
+declare_tool_lint! {
+ /// The `rustc_pass_by_value` lint marks a type with `#[rustc_pass_by_value]` requiring it to
+ /// always be passed by value. This is usually used for types that are thin wrappers around
+ /// references, so there is no benefit to an extra layer of indirection. (Example: `Ty` which
+ /// is a reference to an `Interned<TyS>`)
+ pub rustc::PASS_BY_VALUE,
+ Warn,
+ "pass by reference of a type flagged as `#[rustc_pass_by_value]`",
+ report_in_external_macro: true
+}
+
+declare_lint_pass!(PassByValue => [PASS_BY_VALUE]);
+
+impl<'tcx> LateLintPass<'tcx> for PassByValue {
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) {
+ match &ty.kind {
+ TyKind::Rptr(_, hir::MutTy { ty: inner_ty, mutbl: hir::Mutability::Not }) => {
+ if let Some(impl_did) = cx.tcx.impl_of_method(ty.hir_id.owner.to_def_id()) {
+ if cx.tcx.impl_trait_ref(impl_did).is_some() {
+ return;
+ }
+ }
+ if let Some(t) = path_for_pass_by_value(cx, &inner_ty) {
+ cx.struct_span_lint(PASS_BY_VALUE, ty.span, |lint| {
+ lint.build(fluent::lint::pass_by_value)
+ .set_arg("ty", t.clone())
+ .span_suggestion(
+ ty.span,
+ fluent::lint::suggestion,
+ t,
+ // Changing type of function argument
+ Applicability::MaybeIncorrect,
+ )
+ .emit();
+ })
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+fn path_for_pass_by_value(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> Option<String> {
+ if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
+ match path.res {
+ Res::Def(_, def_id) if cx.tcx.has_attr(def_id, sym::rustc_pass_by_value) => {
+ let name = cx.tcx.item_name(def_id).to_ident_string();
+ let path_segment = path.segments.last().unwrap();
+ return Some(format!("{}{}", name, gen_args(cx, path_segment)));
+ }
+ Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
+ if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
+ if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) {
+ return Some(cx.tcx.def_path_str_with_substs(adt.did(), substs));
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+
+ None
+}
+
+fn gen_args(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> String {
+ if let Some(args) = &segment.args {
+ let params = args
+ .args
+ .iter()
+ .map(|arg| match arg {
+ GenericArg::Lifetime(lt) => lt.name.ident().to_string(),
+ GenericArg::Type(ty) => {
+ cx.tcx.sess.source_map().span_to_snippet(ty.span).unwrap_or_else(|_| "_".into())
+ }
+ GenericArg::Const(c) => {
+ cx.tcx.sess.source_map().span_to_snippet(c.span).unwrap_or_else(|_| "_".into())
+ }
+ GenericArg::Infer(_) => String::from("_"),
+ })
+ .collect::<Vec<_>>();
+
+ if !params.is_empty() {
+ return format!("<{}>", params.join(", "));
+ }
+ }
+
+ String::new()
+}
diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs
new file mode 100644
index 000000000..cb7bd407e
--- /dev/null
+++ b/compiler/rustc_lint/src/passes.rs
@@ -0,0 +1,249 @@
+use crate::context::{EarlyContext, LateContext};
+
+use rustc_ast as ast;
+use rustc_data_structures::sync;
+use rustc_hir as hir;
+use rustc_session::lint::builtin::HardwiredLints;
+use rustc_session::lint::LintPass;
+use rustc_span::symbol::Ident;
+use rustc_span::Span;
+
+#[macro_export]
+macro_rules! late_lint_methods {
+ ($macro:path, $args:tt, [$hir:tt]) => (
+ $macro!($args, [$hir], [
+ fn check_body(a: &$hir hir::Body<$hir>);
+ fn check_body_post(a: &$hir hir::Body<$hir>);
+ fn check_crate();
+ fn check_crate_post();
+ fn check_mod(a: &$hir hir::Mod<$hir>, b: Span, c: hir::HirId);
+ fn check_foreign_item(a: &$hir hir::ForeignItem<$hir>);
+ fn check_item(a: &$hir hir::Item<$hir>);
+ fn check_item_post(a: &$hir hir::Item<$hir>);
+ fn check_local(a: &$hir hir::Local<$hir>);
+ fn check_block(a: &$hir hir::Block<$hir>);
+ fn check_block_post(a: &$hir hir::Block<$hir>);
+ fn check_stmt(a: &$hir hir::Stmt<$hir>);
+ fn check_arm(a: &$hir hir::Arm<$hir>);
+ fn check_pat(a: &$hir hir::Pat<$hir>);
+ fn check_expr(a: &$hir hir::Expr<$hir>);
+ fn check_expr_post(a: &$hir hir::Expr<$hir>);
+ fn check_ty(a: &$hir hir::Ty<$hir>);
+ fn check_generic_param(a: &$hir hir::GenericParam<$hir>);
+ fn check_generics(a: &$hir hir::Generics<$hir>);
+ fn check_poly_trait_ref(a: &$hir hir::PolyTraitRef<$hir>, b: hir::TraitBoundModifier);
+ fn check_fn(
+ a: rustc_hir::intravisit::FnKind<$hir>,
+ b: &$hir hir::FnDecl<$hir>,
+ c: &$hir hir::Body<$hir>,
+ d: Span,
+ e: hir::HirId);
+ fn check_trait_item(a: &$hir hir::TraitItem<$hir>);
+ fn check_impl_item(a: &$hir hir::ImplItem<$hir>);
+ fn check_impl_item_post(a: &$hir hir::ImplItem<$hir>);
+ fn check_struct_def(a: &$hir hir::VariantData<$hir>);
+ fn check_field_def(a: &$hir hir::FieldDef<$hir>);
+ fn check_variant(a: &$hir hir::Variant<$hir>);
+ fn check_path(a: &$hir hir::Path<$hir>, b: hir::HirId);
+ fn check_attribute(a: &$hir ast::Attribute);
+
+ /// Called when entering a syntax node that can have lint attributes such
+ /// as `#[allow(...)]`. Called with *all* the attributes of that node.
+ fn enter_lint_attrs(a: &$hir [ast::Attribute]);
+
+ /// Counterpart to `enter_lint_attrs`.
+ fn exit_lint_attrs(a: &$hir [ast::Attribute]);
+ ]);
+ )
+}
+
+/// Trait for types providing lint checks.
+///
+/// Each `check` method checks a single syntax node, and should not
+/// invoke methods recursively (unlike `Visitor`). By default they
+/// do nothing.
+//
+// FIXME: eliminate the duplication with `Visitor`. But this also
+// contains a few lint-specific methods with no equivalent in `Visitor`.
+
+macro_rules! expand_lint_pass_methods {
+ ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})*
+ )
+}
+
+macro_rules! declare_late_lint_pass {
+ ([], [$hir:tt], [$($methods:tt)*]) => (
+ pub trait LateLintPass<$hir>: LintPass {
+ expand_lint_pass_methods!(&LateContext<$hir>, [$($methods)*]);
+ }
+ )
+}
+
+late_lint_methods!(declare_late_lint_pass, [], ['tcx]);
+
+impl LateLintPass<'_> for HardwiredLints {}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_method {
+ ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({
+ $($self.$passes.$name $params;)*
+ })
+}
+
+#[macro_export]
+macro_rules! expand_combined_late_lint_pass_methods {
+ ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(fn $name(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) {
+ expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*));
+ })*
+ )
+}
+
+#[macro_export]
+macro_rules! declare_combined_late_lint_pass {
+ ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], [$hir:tt], $methods:tt) => (
+ #[allow(non_snake_case)]
+ $v struct $name {
+ $($passes: $passes,)*
+ }
+
+ impl $name {
+ $v fn new() -> Self {
+ Self {
+ $($passes: $constructor,)*
+ }
+ }
+
+ $v fn get_lints() -> LintArray {
+ let mut lints = Vec::new();
+ $(lints.extend_from_slice(&$passes::get_lints());)*
+ lints
+ }
+ }
+
+ impl<'tcx> LateLintPass<'tcx> for $name {
+ expand_combined_late_lint_pass_methods!([$($passes),*], $methods);
+ }
+
+ #[allow(rustc::lint_pass_impl_without_macro)]
+ impl LintPass for $name {
+ fn name(&self) -> &'static str {
+ panic!()
+ }
+ }
+ )
+}
+
+#[macro_export]
+macro_rules! early_lint_methods {
+ ($macro:path, $args:tt) => (
+ $macro!($args, [
+ fn check_param(a: &ast::Param);
+ fn check_ident(a: Ident);
+ fn check_crate(a: &ast::Crate);
+ fn check_crate_post(a: &ast::Crate);
+ fn check_item(a: &ast::Item);
+ fn check_item_post(a: &ast::Item);
+ fn check_local(a: &ast::Local);
+ fn check_block(a: &ast::Block);
+ fn check_stmt(a: &ast::Stmt);
+ fn check_arm(a: &ast::Arm);
+ fn check_pat(a: &ast::Pat);
+ fn check_pat_post(a: &ast::Pat);
+ fn check_expr(a: &ast::Expr);
+ fn check_ty(a: &ast::Ty);
+ fn check_generic_arg(a: &ast::GenericArg);
+ fn check_generic_param(a: &ast::GenericParam);
+ fn check_generics(a: &ast::Generics);
+ fn check_poly_trait_ref(a: &ast::PolyTraitRef,
+ b: &ast::TraitBoundModifier);
+ fn check_fn(a: rustc_ast::visit::FnKind<'_>, c: Span, d_: ast::NodeId);
+ fn check_trait_item(a: &ast::AssocItem);
+ fn check_impl_item(a: &ast::AssocItem);
+ fn check_variant(a: &ast::Variant);
+ fn check_attribute(a: &ast::Attribute);
+ fn check_mac_def(a: &ast::MacroDef, b: ast::NodeId);
+ fn check_mac(a: &ast::MacCall);
+
+ /// Called when entering a syntax node that can have lint attributes such
+ /// as `#[allow(...)]`. Called with *all* the attributes of that node.
+ fn enter_lint_attrs(a: &[ast::Attribute]);
+
+ /// Counterpart to `enter_lint_attrs`.
+ fn exit_lint_attrs(a: &[ast::Attribute]);
+ ]);
+ )
+}
+
+macro_rules! expand_early_lint_pass_methods {
+ ($context:ty, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(#[inline(always)] fn $name(&mut self, _: $context, $(_: $arg),*) {})*
+ )
+}
+
+macro_rules! declare_early_lint_pass {
+ ([], [$($methods:tt)*]) => (
+ pub trait EarlyLintPass: LintPass {
+ expand_early_lint_pass_methods!(&EarlyContext<'_>, [$($methods)*]);
+ }
+ )
+}
+
+early_lint_methods!(declare_early_lint_pass, []);
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_method {
+ ([$($passes:ident),*], $self: ident, $name: ident, $params:tt) => ({
+ $($self.$passes.$name $params;)*
+ })
+}
+
+#[macro_export]
+macro_rules! expand_combined_early_lint_pass_methods {
+ ($passes:tt, [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => (
+ $(fn $name(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) {
+ expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*));
+ })*
+ )
+}
+
+#[macro_export]
+macro_rules! declare_combined_early_lint_pass {
+ ([$v:vis $name:ident, [$($passes:ident: $constructor:expr,)*]], $methods:tt) => (
+ #[allow(non_snake_case)]
+ $v struct $name {
+ $($passes: $passes,)*
+ }
+
+ impl $name {
+ $v fn new() -> Self {
+ Self {
+ $($passes: $constructor,)*
+ }
+ }
+
+ $v fn get_lints() -> LintArray {
+ let mut lints = Vec::new();
+ $(lints.extend_from_slice(&$passes::get_lints());)*
+ lints
+ }
+ }
+
+ impl EarlyLintPass for $name {
+ expand_combined_early_lint_pass_methods!([$($passes),*], $methods);
+ }
+
+ #[allow(rustc::lint_pass_impl_without_macro)]
+ impl LintPass for $name {
+ fn name(&self) -> &'static str {
+ panic!()
+ }
+ }
+ )
+}
+
+/// A lint pass boxed up as a trait object.
+pub type EarlyLintPassObject = Box<dyn EarlyLintPass + sync::Send + sync::Sync + 'static>;
+pub type LateLintPassObject =
+ Box<dyn for<'tcx> LateLintPass<'tcx> + sync::Send + sync::Sync + 'static>;
diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs
new file mode 100644
index 000000000..26f413453
--- /dev/null
+++ b/compiler/rustc_lint/src/redundant_semicolon.rs
@@ -0,0 +1,58 @@
+use crate::{EarlyContext, EarlyLintPass, LintContext};
+use rustc_ast::{Block, StmtKind};
+use rustc_errors::{fluent, Applicability};
+use rustc_span::Span;
+
+declare_lint! {
+ /// The `redundant_semicolons` lint detects unnecessary trailing
+ /// semicolons.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let _ = 123;;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Extra semicolons are not needed, and may be removed to avoid confusion
+ /// and visual clutter.
+ pub REDUNDANT_SEMICOLONS,
+ Warn,
+ "detects unnecessary trailing semicolons"
+}
+
+declare_lint_pass!(RedundantSemicolons => [REDUNDANT_SEMICOLONS]);
+
+impl EarlyLintPass for RedundantSemicolons {
+ fn check_block(&mut self, cx: &EarlyContext<'_>, block: &Block) {
+ 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),
+ }
+ }
+ maybe_lint_redundant_semis(cx, &mut seq);
+ }
+}
+
+fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, 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;
+ }
+
+ cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
+ lint.build(fluent::lint::redundant_semicolons)
+ .set_arg("multiple", multiple)
+ .span_suggestion(span, fluent::lint::suggestion, "", Applicability::MaybeIncorrect)
+ .emit();
+ });
+ }
+}
diff --git a/compiler/rustc_lint/src/tests.rs b/compiler/rustc_lint/src/tests.rs
new file mode 100644
index 000000000..fc9d6f636
--- /dev/null
+++ b/compiler/rustc_lint/src/tests.rs
@@ -0,0 +1,26 @@
+use crate::context::parse_lint_and_tool_name;
+use rustc_span::{create_default_session_globals_then, Symbol};
+
+#[test]
+fn parse_lint_no_tool() {
+ create_default_session_globals_then(|| {
+ assert_eq!(parse_lint_and_tool_name("foo"), (None, "foo"))
+ });
+}
+
+#[test]
+fn parse_lint_with_tool() {
+ create_default_session_globals_then(|| {
+ assert_eq!(parse_lint_and_tool_name("clippy::foo"), (Some(Symbol::intern("clippy")), "foo"))
+ });
+}
+
+#[test]
+fn parse_lint_multiple_path() {
+ create_default_session_globals_then(|| {
+ assert_eq!(
+ parse_lint_and_tool_name("clippy::foo::bar"),
+ (Some(Symbol::intern("clippy")), "foo::bar")
+ )
+ });
+}
diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs
new file mode 100644
index 000000000..df1587c59
--- /dev/null
+++ b/compiler/rustc_lint/src/traits.rs
@@ -0,0 +1,134 @@
+use crate::LateContext;
+use crate::LateLintPass;
+use crate::LintContext;
+use rustc_errors::fluent;
+use rustc_hir as hir;
+use rustc_span::symbol::sym;
+
+declare_lint! {
+ /// The `drop_bounds` lint checks for generics with `std::ops::Drop` as
+ /// bounds.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn foo<T: Drop>() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// A generic trait bound of the form `T: Drop` is most likely misleading
+ /// and not what the programmer intended (they probably should have used
+ /// `std::mem::needs_drop` instead).
+ ///
+ /// `Drop` bounds do not actually indicate whether a type can be trivially
+ /// dropped or not, because a composite type containing `Drop` types does
+ /// not necessarily implement `Drop` itself. Naïvely, one might be tempted
+ /// to write an implementation that assumes that a type can be trivially
+ /// dropped while also supplying a specialization for `T: Drop` that
+ /// actually calls the destructor. However, this breaks down e.g. when `T`
+ /// is `String`, which does not implement `Drop` itself but contains a
+ /// `Vec`, which does implement `Drop`, so assuming `T` can be trivially
+ /// dropped would lead to a memory leak here.
+ ///
+ /// Furthermore, the `Drop` trait only contains one method, `Drop::drop`,
+ /// which may not be called explicitly in user code (`E0040`), so there is
+ /// really no use case for using `Drop` in trait bounds, save perhaps for
+ /// some obscure corner cases, which can use `#[allow(drop_bounds)]`.
+ pub DROP_BOUNDS,
+ Warn,
+ "bounds of the form `T: Drop` are most likely incorrect"
+}
+
+declare_lint! {
+ /// The `dyn_drop` lint checks for trait objects with `std::ops::Drop`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn foo(_x: Box<dyn Drop>) {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// A trait object bound of the form `dyn Drop` is most likely misleading
+ /// and not what the programmer intended.
+ ///
+ /// `Drop` bounds do not actually indicate whether a type can be trivially
+ /// dropped or not, because a composite type containing `Drop` types does
+ /// not necessarily implement `Drop` itself. Naïvely, one might be tempted
+ /// to write a deferred drop system, to pull cleaning up memory out of a
+ /// latency-sensitive code path, using `dyn Drop` trait objects. However,
+ /// this breaks down e.g. when `T` is `String`, which does not implement
+ /// `Drop`, but should probably be accepted.
+ ///
+ /// To write a trait object bound that accepts anything, use a placeholder
+ /// trait with a blanket implementation.
+ ///
+ /// ```rust
+ /// trait Placeholder {}
+ /// impl<T> Placeholder for T {}
+ /// fn foo(_x: Box<dyn Placeholder>) {}
+ /// ```
+ pub DYN_DROP,
+ Warn,
+ "trait objects of the form `dyn Drop` are useless"
+}
+
+declare_lint_pass!(
+ /// Lint for bounds of the form `T: Drop`, which usually
+ /// indicate an attempt to emulate `std::mem::needs_drop`.
+ DropTraitConstraints => [DROP_BOUNDS, DYN_DROP]
+);
+
+impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints {
+ fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+ use rustc_middle::ty::PredicateKind::*;
+
+ let predicates = cx.tcx.explicit_predicates_of(item.def_id);
+ for &(predicate, span) in predicates.predicates {
+ let Trait(trait_predicate) = predicate.kind().skip_binder() else {
+ continue
+ };
+ let def_id = trait_predicate.trait_ref.def_id;
+ if cx.tcx.lang_items().drop_trait() == Some(def_id) {
+ // Explicitly allow `impl Drop`, a drop-guards-as-Voldemort-type pattern.
+ if trait_predicate.trait_ref.self_ty().is_impl_trait() {
+ continue;
+ }
+ cx.struct_span_lint(DROP_BOUNDS, span, |lint| {
+ let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
+ return
+ };
+ lint.build(fluent::lint::drop_trait_constraints)
+ .set_arg("predicate", predicate)
+ .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
+ .emit();
+ });
+ }
+ }
+ }
+
+ fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx>) {
+ let hir::TyKind::TraitObject(bounds, _lifetime, _syntax) = &ty.kind else {
+ return
+ };
+ for bound in &bounds[..] {
+ let def_id = bound.trait_ref.trait_def_id();
+ if cx.tcx.lang_items().drop_trait() == def_id {
+ cx.struct_span_lint(DYN_DROP, bound.span, |lint| {
+ let Some(needs_drop) = cx.tcx.get_diagnostic_item(sym::needs_drop) else {
+ return
+ };
+ lint.build(fluent::lint::drop_glue)
+ .set_arg("needs_drop", cx.tcx.def_path_str(needs_drop))
+ .emit();
+ });
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs
new file mode 100644
index 000000000..5c07afeb7
--- /dev/null
+++ b/compiler/rustc_lint/src/types.rs
@@ -0,0 +1,1576 @@
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_attr as attr;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{fluent, Applicability, DiagnosticMessage};
+use rustc_hir as hir;
+use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
+use rustc_macros::LintDiagnostic;
+use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton};
+use rustc_middle::ty::subst::SubstsRef;
+use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
+use rustc_span::source_map;
+use rustc_span::symbol::sym;
+use rustc_span::{Span, Symbol, DUMMY_SP};
+use rustc_target::abi::{Abi, WrappingRange};
+use rustc_target::abi::{Integer, TagEncoding, Variants};
+use rustc_target::spec::abi::Abi as SpecAbi;
+
+use std::cmp;
+use std::iter;
+use std::ops::ControlFlow;
+use tracing::debug;
+
+declare_lint! {
+ /// The `unused_comparisons` lint detects comparisons made useless by
+ /// limits of the types involved.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn foo(x: u8) {
+ /// x >= 0;
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// A useless comparison may indicate a mistake, and should be fixed or
+ /// removed.
+ UNUSED_COMPARISONS,
+ Warn,
+ "comparisons made useless by limits of the types involved"
+}
+
+declare_lint! {
+ /// The `overflowing_literals` lint detects literal out of range for its
+ /// type.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// let x: u8 = 1000;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// It is usually a mistake to use a literal that overflows the type where
+ /// it is used. Either use a literal that is within range, or change the
+ /// type to be within the range of the literal.
+ OVERFLOWING_LITERALS,
+ Deny,
+ "literal out of range for its type"
+}
+
+declare_lint! {
+ /// The `variant_size_differences` lint detects enums with widely varying
+ /// variant sizes.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(variant_size_differences)]
+ /// enum En {
+ /// V0(u8),
+ /// VBig([u8; 1024]),
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// It can be a mistake to add a variant to an enum that is much larger
+ /// than the other variants, bloating the overall size required for all
+ /// variants. This can impact performance and memory usage. This is
+ /// triggered if one variant is more than 3 times larger than the
+ /// second-largest variant.
+ ///
+ /// Consider placing the large variant's contents on the heap (for example
+ /// via [`Box`]) to keep the overall size of the enum itself down.
+ ///
+ /// This lint is "allow" by default because it can be noisy, and may not be
+ /// an actual problem. Decisions about this should be guided with
+ /// profiling and benchmarking.
+ ///
+ /// [`Box`]: https://doc.rust-lang.org/std/boxed/index.html
+ VARIANT_SIZE_DIFFERENCES,
+ Allow,
+ "detects enums with widely varying variant sizes"
+}
+
+#[derive(Copy, Clone)]
+pub struct TypeLimits {
+ /// Id of the last visited negated expression
+ negated_expr_id: Option<hir::HirId>,
+}
+
+impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]);
+
+impl TypeLimits {
+ pub fn new() -> TypeLimits {
+ TypeLimits { negated_expr_id: None }
+ }
+}
+
+/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint.
+/// Returns `true` iff the lint was overridden.
+fn lint_overflowing_range_endpoint<'tcx>(
+ cx: &LateContext<'tcx>,
+ lit: &hir::Lit,
+ lit_val: u128,
+ max: u128,
+ expr: &'tcx hir::Expr<'tcx>,
+ parent_expr: &'tcx hir::Expr<'tcx>,
+ ty: &str,
+) -> bool {
+ // We only want to handle exclusive (`..`) ranges,
+ // which are represented as `ExprKind::Struct`.
+ let mut overwritten = false;
+ if let ExprKind::Struct(_, eps, _) = &parent_expr.kind {
+ if eps.len() != 2 {
+ return false;
+ }
+ // We can suggest using an inclusive range
+ // (`..=`) instead only if it is the `end` that is
+ // overflowing and only by 1.
+ if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max {
+ cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| {
+ let mut err = lint.build(fluent::lint::range_endpoint_out_of_range);
+ err.set_arg("ty", ty);
+ if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) {
+ use ast::{LitIntType, LitKind};
+ // We need to preserve the literal's suffix,
+ // as it may determine typing information.
+ let suffix = match lit.node {
+ LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(),
+ LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(),
+ LitKind::Int(_, LitIntType::Unsuffixed) => "",
+ _ => bug!(),
+ };
+ let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix);
+ err.span_suggestion(
+ parent_expr.span,
+ fluent::lint::suggestion,
+ suggestion,
+ Applicability::MachineApplicable,
+ );
+ err.emit();
+ overwritten = true;
+ }
+ });
+ }
+ }
+ overwritten
+}
+
+// For `isize` & `usize`, be conservative with the warnings, so that the
+// warnings are consistent between 32- and 64-bit platforms.
+fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) {
+ match int_ty {
+ ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()),
+ ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()),
+ ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()),
+ ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()),
+ ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()),
+ ty::IntTy::I128 => (i128::MIN, i128::MAX),
+ }
+}
+
+fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) {
+ let max = match uint_ty {
+ ty::UintTy::Usize => u64::MAX.into(),
+ ty::UintTy::U8 => u8::MAX.into(),
+ ty::UintTy::U16 => u16::MAX.into(),
+ ty::UintTy::U32 => u32::MAX.into(),
+ ty::UintTy::U64 => u64::MAX.into(),
+ ty::UintTy::U128 => u128::MAX,
+ };
+ (0, max)
+}
+
+fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> {
+ let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?;
+ let firstch = src.chars().next()?;
+
+ if firstch == '0' {
+ match src.chars().nth(1) {
+ Some('x' | 'b') => return Some(src),
+ _ => return None,
+ }
+ }
+
+ None
+}
+
+fn report_bin_hex_error(
+ cx: &LateContext<'_>,
+ expr: &hir::Expr<'_>,
+ ty: attr::IntType,
+ repr_str: String,
+ val: u128,
+ negative: bool,
+) {
+ let size = Integer::from_attr(&cx.tcx, ty).size();
+ cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| {
+ let (t, actually) = match ty {
+ attr::IntType::SignedInt(t) => {
+ let actually = if negative {
+ -(size.sign_extend(val) as i128)
+ } else {
+ size.sign_extend(val) as i128
+ };
+ (t.name_str(), actually.to_string())
+ }
+ attr::IntType::UnsignedInt(t) => {
+ let actually = size.truncate(val);
+ (t.name_str(), actually.to_string())
+ }
+ };
+ let mut err = lint.build(fluent::lint::overflowing_bin_hex);
+ if negative {
+ // If the value is negative,
+ // emits a note about the value itself, apart from the literal.
+ err.note(fluent::lint::negative_note);
+ err.note(fluent::lint::negative_becomes_note);
+ } else {
+ err.note(fluent::lint::positive_note);
+ }
+ if let Some(sugg_ty) =
+ get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative)
+ {
+ err.set_arg("suggestion_ty", sugg_ty);
+ if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') {
+ let (sans_suffix, _) = repr_str.split_at(pos);
+ err.span_suggestion(
+ expr.span,
+ fluent::lint::suggestion,
+ format!("{}{}", sans_suffix, sugg_ty),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ err.help(fluent::lint::help);
+ }
+ }
+ err.set_arg("ty", t);
+ err.set_arg("lit", repr_str);
+ err.set_arg("dec", val);
+ err.set_arg("actually", actually);
+ err.emit();
+ });
+}
+
+// This function finds the next fitting type and generates a suggestion string.
+// It searches for fitting types in the following way (`X < Y`):
+// - `iX`: if literal fits in `uX` => `uX`, else => `iY`
+// - `-iX` => `iY`
+// - `uX` => `uY`
+//
+// No suggestion for: `isize`, `usize`.
+fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> {
+ use ty::IntTy::*;
+ use ty::UintTy::*;
+ macro_rules! find_fit {
+ ($ty:expr, $val:expr, $negative:expr,
+ $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => {
+ {
+ let _neg = if negative { 1 } else { 0 };
+ match $ty {
+ $($type => {
+ $(if !negative && val <= uint_ty_range($utypes).1 {
+ return Some($utypes.name_str())
+ })*
+ $(if val <= int_ty_range($itypes).1 as u128 + _neg {
+ return Some($itypes.name_str())
+ })*
+ None
+ },)+
+ _ => None
+ }
+ }
+ }
+ }
+ match t.kind() {
+ ty::Int(i) => find_fit!(i, val, negative,
+ I8 => [U8] => [I16, I32, I64, I128],
+ I16 => [U16] => [I32, I64, I128],
+ I32 => [U32] => [I64, I128],
+ I64 => [U64] => [I128],
+ I128 => [U128] => []),
+ ty::Uint(u) => find_fit!(u, val, negative,
+ U8 => [U8, U16, U32, U64, U128] => [],
+ U16 => [U16, U32, U64, U128] => [],
+ U32 => [U32, U64, U128] => [],
+ U64 => [U64, U128] => [],
+ U128 => [U128] => []),
+ _ => None,
+ }
+}
+
+fn lint_int_literal<'tcx>(
+ cx: &LateContext<'tcx>,
+ type_limits: &TypeLimits,
+ e: &'tcx hir::Expr<'tcx>,
+ lit: &hir::Lit,
+ t: ty::IntTy,
+ v: u128,
+) {
+ let int_type = t.normalize(cx.sess().target.pointer_width);
+ let (min, max) = int_ty_range(int_type);
+ let max = max as u128;
+ let negative = type_limits.negated_expr_id == Some(e.hir_id);
+
+ // Detect literal value out of range [min, max] inclusive
+ // avoiding use of -min to prevent overflow/panic
+ if (negative && v > max + 1) || (!negative && v > max) {
+ if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+ report_bin_hex_error(
+ cx,
+ e,
+ attr::IntType::SignedInt(ty::ast_int_ty(t)),
+ repr_str,
+ v,
+ negative,
+ );
+ return;
+ }
+
+ let par_id = cx.tcx.hir().get_parent_node(e.hir_id);
+ if let Node::Expr(par_e) = cx.tcx.hir().get(par_id) {
+ if let hir::ExprKind::Struct(..) = par_e.kind {
+ if is_range_literal(par_e)
+ && lint_overflowing_range_endpoint(cx, lit, v, max, e, par_e, t.name_str())
+ {
+ // The overflowing literal lint was overridden.
+ return;
+ }
+ }
+ }
+
+ cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+ let mut err = lint.build(fluent::lint::overflowing_int);
+ err.set_arg("ty", t.name_str());
+ err.set_arg(
+ "lit",
+ cx.sess()
+ .source_map()
+ .span_to_snippet(lit.span)
+ .expect("must get snippet from literal"),
+ );
+ err.set_arg("min", min);
+ err.set_arg("max", max);
+ err.note(fluent::lint::note);
+ if let Some(sugg_ty) =
+ get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative)
+ {
+ err.set_arg("suggestion_ty", sugg_ty);
+ err.help(fluent::lint::help);
+ }
+ err.emit();
+ });
+ }
+}
+
+fn lint_uint_literal<'tcx>(
+ cx: &LateContext<'tcx>,
+ e: &'tcx hir::Expr<'tcx>,
+ lit: &hir::Lit,
+ t: ty::UintTy,
+) {
+ let uint_type = t.normalize(cx.sess().target.pointer_width);
+ let (min, max) = uint_ty_range(uint_type);
+ let lit_val: u128 = match lit.node {
+ // _v is u8, within range by definition
+ ast::LitKind::Byte(_v) => return,
+ ast::LitKind::Int(v, _) => v,
+ _ => bug!(),
+ };
+ if lit_val < min || lit_val > max {
+ let parent_id = cx.tcx.hir().get_parent_node(e.hir_id);
+ if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) {
+ match par_e.kind {
+ hir::ExprKind::Cast(..) => {
+ if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() {
+ cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| {
+ lint.build(fluent::lint::only_cast_u8_to_char)
+ .span_suggestion(
+ par_e.span,
+ fluent::lint::suggestion,
+ format!("'\\u{{{:X}}}'", lit_val),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ return;
+ }
+ }
+ hir::ExprKind::Struct(..) if is_range_literal(par_e) => {
+ let t = t.name_str();
+ if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, par_e, t) {
+ // The overflowing literal lint was overridden.
+ return;
+ }
+ }
+ _ => {}
+ }
+ }
+ if let Some(repr_str) = get_bin_hex_repr(cx, lit) {
+ report_bin_hex_error(
+ cx,
+ e,
+ attr::IntType::UnsignedInt(ty::ast_uint_ty(t)),
+ repr_str,
+ lit_val,
+ false,
+ );
+ return;
+ }
+ cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+ lint.build(fluent::lint::overflowing_uint)
+ .set_arg("ty", t.name_str())
+ .set_arg(
+ "lit",
+ cx.sess()
+ .source_map()
+ .span_to_snippet(lit.span)
+ .expect("must get snippet from literal"),
+ )
+ .set_arg("min", min)
+ .set_arg("max", max)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+}
+
+fn lint_literal<'tcx>(
+ cx: &LateContext<'tcx>,
+ type_limits: &TypeLimits,
+ e: &'tcx hir::Expr<'tcx>,
+ lit: &hir::Lit,
+) {
+ match *cx.typeck_results().node_type(e.hir_id).kind() {
+ ty::Int(t) => {
+ match lit.node {
+ ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => {
+ lint_int_literal(cx, type_limits, e, lit, t, v)
+ }
+ _ => bug!(),
+ };
+ }
+ ty::Uint(t) => lint_uint_literal(cx, e, lit, t),
+ ty::Float(t) => {
+ let is_infinite = match lit.node {
+ ast::LitKind::Float(v, _) => match t {
+ ty::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite),
+ ty::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite),
+ },
+ _ => bug!(),
+ };
+ if is_infinite == Ok(true) {
+ cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| {
+ lint.build(fluent::lint::overflowing_literal)
+ .set_arg("ty", t.name_str())
+ .set_arg(
+ "lit",
+ cx.sess()
+ .source_map()
+ .span_to_snippet(lit.span)
+ .expect("must get snippet from literal"),
+ )
+ .note(fluent::lint::note)
+ .emit();
+ });
+ }
+ }
+ _ => {}
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for TypeLimits {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) {
+ match e.kind {
+ hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => {
+ // propagate negation, if the negation itself isn't negated
+ if self.negated_expr_id != Some(e.hir_id) {
+ self.negated_expr_id = Some(expr.hir_id);
+ }
+ }
+ hir::ExprKind::Binary(binop, ref l, ref r) => {
+ if is_comparison(binop) && !check_limits(cx, binop, &l, &r) {
+ cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| {
+ lint.build(fluent::lint::unused_comparisons).emit();
+ });
+ }
+ }
+ hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit),
+ _ => {}
+ };
+
+ fn is_valid<T: cmp::PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool {
+ match binop.node {
+ hir::BinOpKind::Lt => v > min && v <= max,
+ hir::BinOpKind::Le => v >= min && v < max,
+ hir::BinOpKind::Gt => v >= min && v < max,
+ hir::BinOpKind::Ge => v > min && v <= max,
+ hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max,
+ _ => bug!(),
+ }
+ }
+
+ fn rev_binop(binop: hir::BinOp) -> hir::BinOp {
+ source_map::respan(
+ binop.span,
+ match binop.node {
+ hir::BinOpKind::Lt => hir::BinOpKind::Gt,
+ hir::BinOpKind::Le => hir::BinOpKind::Ge,
+ hir::BinOpKind::Gt => hir::BinOpKind::Lt,
+ hir::BinOpKind::Ge => hir::BinOpKind::Le,
+ _ => return binop,
+ },
+ )
+ }
+
+ fn check_limits(
+ cx: &LateContext<'_>,
+ binop: hir::BinOp,
+ l: &hir::Expr<'_>,
+ r: &hir::Expr<'_>,
+ ) -> bool {
+ let (lit, expr, swap) = match (&l.kind, &r.kind) {
+ (&hir::ExprKind::Lit(_), _) => (l, r, true),
+ (_, &hir::ExprKind::Lit(_)) => (r, l, false),
+ _ => return true,
+ };
+ // Normalize the binop so that the literal is always on the RHS in
+ // the comparison
+ let norm_binop = if swap { rev_binop(binop) } else { binop };
+ match *cx.typeck_results().node_type(expr.hir_id).kind() {
+ ty::Int(int_ty) => {
+ let (min, max) = int_ty_range(int_ty);
+ let lit_val: i128 = match lit.kind {
+ hir::ExprKind::Lit(ref li) => match li.node {
+ ast::LitKind::Int(
+ v,
+ ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed,
+ ) => v as i128,
+ _ => return true,
+ },
+ _ => bug!(),
+ };
+ is_valid(norm_binop, lit_val, min, max)
+ }
+ ty::Uint(uint_ty) => {
+ let (min, max): (u128, u128) = uint_ty_range(uint_ty);
+ let lit_val: u128 = match lit.kind {
+ hir::ExprKind::Lit(ref li) => match li.node {
+ ast::LitKind::Int(v, _) => v,
+ _ => return true,
+ },
+ _ => bug!(),
+ };
+ is_valid(norm_binop, lit_val, min, max)
+ }
+ _ => true,
+ }
+ }
+
+ fn is_comparison(binop: hir::BinOp) -> bool {
+ matches!(
+ binop.node,
+ hir::BinOpKind::Eq
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt
+ )
+ }
+ }
+}
+
+declare_lint! {
+ /// The `improper_ctypes` lint detects incorrect use of types in foreign
+ /// modules.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// extern "C" {
+ /// static STATIC: String;
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The compiler has several checks to verify that types used in `extern`
+ /// blocks are safe and follow certain rules to ensure proper
+ /// compatibility with the foreign interfaces. This lint is issued when it
+ /// detects a probable mistake in a definition. The lint usually should
+ /// provide a description of the issue, along with possibly a hint on how
+ /// to resolve it.
+ IMPROPER_CTYPES,
+ Warn,
+ "proper use of libc types in foreign modules"
+}
+
+declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]);
+
+declare_lint! {
+ /// The `improper_ctypes_definitions` lint detects incorrect use of
+ /// [`extern` function] definitions.
+ ///
+ /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// # #![allow(unused)]
+ /// pub extern "C" fn str_type(p: &str) { }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// There are many parameter and return types that may be specified in an
+ /// `extern` function that are not compatible with the given ABI. This
+ /// lint is an alert that these types should not be used. The lint usually
+ /// should provide a description of the issue, along with possibly a hint
+ /// on how to resolve it.
+ IMPROPER_CTYPES_DEFINITIONS,
+ Warn,
+ "proper use of libc types in foreign item definitions"
+}
+
+declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]);
+
+#[derive(Clone, Copy)]
+pub(crate) enum CItemKind {
+ Declaration,
+ Definition,
+}
+
+struct ImproperCTypesVisitor<'a, 'tcx> {
+ cx: &'a LateContext<'tcx>,
+ mode: CItemKind,
+}
+
+enum FfiResult<'tcx> {
+ FfiSafe,
+ FfiPhantom(Ty<'tcx>),
+ FfiUnsafe { ty: Ty<'tcx>, reason: DiagnosticMessage, help: Option<DiagnosticMessage> },
+}
+
+pub(crate) fn nonnull_optimization_guaranteed<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ def: ty::AdtDef<'tcx>,
+) -> bool {
+ tcx.has_attr(def.did(), sym::rustc_nonnull_optimization_guaranteed)
+}
+
+/// `repr(transparent)` structs can have a single non-ZST field, this function returns that
+/// field.
+pub fn transparent_newtype_field<'a, 'tcx>(
+ tcx: TyCtxt<'tcx>,
+ variant: &'a ty::VariantDef,
+) -> Option<&'a ty::FieldDef> {
+ let param_env = tcx.param_env(variant.def_id);
+ variant.fields.iter().find(|field| {
+ let field_ty = tcx.type_of(field.did);
+ let is_zst = tcx.layout_of(param_env.and(field_ty)).map_or(false, |layout| layout.is_zst());
+ !is_zst
+ })
+}
+
+/// Is type known to be non-null?
+fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
+ let tcx = cx.tcx;
+ match ty.kind() {
+ ty::FnPtr(_) => true,
+ ty::Ref(..) => true,
+ ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true,
+ ty::Adt(def, substs) if def.repr().transparent() && !def.is_union() => {
+ let marked_non_null = nonnull_optimization_guaranteed(tcx, *def);
+
+ if marked_non_null {
+ return true;
+ }
+
+ // `UnsafeCell` has its niche hidden.
+ if def.is_unsafe_cell() {
+ return false;
+ }
+
+ def.variants()
+ .iter()
+ .filter_map(|variant| transparent_newtype_field(cx.tcx, variant))
+ .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, substs), mode))
+ }
+ _ => false,
+ }
+}
+
+/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type.
+/// If the type passed in was not scalar, returns None.
+fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
+ let tcx = cx.tcx;
+ Some(match *ty.kind() {
+ ty::Adt(field_def, field_substs) => {
+ let inner_field_ty = {
+ let first_non_zst_ty = field_def
+ .variants()
+ .iter()
+ .filter_map(|v| transparent_newtype_field(cx.tcx, v));
+ debug_assert_eq!(
+ first_non_zst_ty.clone().count(),
+ 1,
+ "Wrong number of fields for transparent type"
+ );
+ first_non_zst_ty
+ .last()
+ .expect("No non-zst fields in transparent type.")
+ .ty(tcx, field_substs)
+ };
+ return get_nullable_type(cx, inner_field_ty);
+ }
+ ty::Int(ty) => tcx.mk_mach_int(ty),
+ ty::Uint(ty) => tcx.mk_mach_uint(ty),
+ ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut),
+ // As these types are always non-null, the nullable equivalent of
+ // Option<T> of these types are their raw pointer counterparts.
+ ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }),
+ ty::FnPtr(..) => {
+ // There is no nullable equivalent for Rust's function pointers -- you
+ // must use an Option<fn(..) -> _> to represent it.
+ ty
+ }
+
+ // We should only ever reach this case if ty_is_known_nonnull is extended
+ // to other types.
+ ref unhandled => {
+ debug!(
+ "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}",
+ unhandled, ty
+ );
+ return None;
+ }
+ })
+}
+
+/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
+/// can, return the type that `ty` can be safely converted to, otherwise return `None`.
+/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`,
+/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
+/// FIXME: This duplicates code in codegen.
+pub(crate) fn repr_nullable_ptr<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ ckind: CItemKind,
+) -> Option<Ty<'tcx>> {
+ debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty);
+ if let ty::Adt(ty_def, substs) = ty.kind() {
+ let field_ty = match &ty_def.variants().raw[..] {
+ [var_one, var_two] => match (&var_one.fields[..], &var_two.fields[..]) {
+ ([], [field]) | ([field], []) => field.ty(cx.tcx, substs),
+ _ => return None,
+ },
+ _ => return None,
+ };
+
+ if !ty_is_known_nonnull(cx, field_ty, ckind) {
+ return None;
+ }
+
+ // At this point, the field's type is known to be nonnull and the parent enum is Option-like.
+ // If the computed size for the field and the enum are different, the nonnull optimization isn't
+ // being applied (and we've got a problem somewhere).
+ let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap();
+ if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) {
+ bug!("improper_ctypes: Option nonnull optimization not applied?");
+ }
+
+ // Return the nullable type this Option-like enum can be safely represented with.
+ let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi;
+ if let Abi::Scalar(field_ty_scalar) = field_ty_abi {
+ match field_ty_scalar.valid_range(cx) {
+ WrappingRange { start: 0, end }
+ if end == field_ty_scalar.size(&cx.tcx).unsigned_int_max() - 1 =>
+ {
+ return Some(get_nullable_type(cx, field_ty).unwrap());
+ }
+ WrappingRange { start: 1, .. } => {
+ return Some(get_nullable_type(cx, field_ty).unwrap());
+ }
+ WrappingRange { start, end } => {
+ unreachable!("Unhandled start and end range: ({}, {})", start, end)
+ }
+ };
+ }
+ }
+ None
+}
+
+impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
+ /// Check if the type is array and emit an unsafe type lint.
+ fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool {
+ if let ty::Array(..) = ty.kind() {
+ self.emit_ffi_unsafe_type_lint(
+ ty,
+ sp,
+ fluent::lint::improper_ctypes_array_reason,
+ Some(fluent::lint::improper_ctypes_array_help),
+ );
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Checks if the given field's type is "ffi-safe".
+ fn check_field_type_for_ffi(
+ &self,
+ cache: &mut FxHashSet<Ty<'tcx>>,
+ field: &ty::FieldDef,
+ substs: SubstsRef<'tcx>,
+ ) -> FfiResult<'tcx> {
+ let field_ty = field.ty(self.cx.tcx, substs);
+ if field_ty.has_opaque_types() {
+ self.check_type_for_ffi(cache, field_ty)
+ } else {
+ let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty);
+ self.check_type_for_ffi(cache, field_ty)
+ }
+ }
+
+ /// Checks if the given `VariantDef`'s field types are "ffi-safe".
+ fn check_variant_for_ffi(
+ &self,
+ cache: &mut FxHashSet<Ty<'tcx>>,
+ ty: Ty<'tcx>,
+ def: ty::AdtDef<'tcx>,
+ variant: &ty::VariantDef,
+ substs: SubstsRef<'tcx>,
+ ) -> FfiResult<'tcx> {
+ use FfiResult::*;
+
+ if def.repr().transparent() {
+ // Can assume that at most one field is not a ZST, so only check
+ // that field's type for FFI-safety.
+ if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) {
+ self.check_field_type_for_ffi(cache, field, substs)
+ } else {
+ // All fields are ZSTs; this means that the type should behave
+ // like (), which is FFI-unsafe
+ FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_struct_zst, help: None }
+ }
+ } else {
+ // We can't completely trust repr(C) markings; make sure the fields are
+ // actually safe.
+ let mut all_phantom = !variant.fields.is_empty();
+ for field in &variant.fields {
+ match self.check_field_type_for_ffi(cache, &field, substs) {
+ FfiSafe => {
+ all_phantom = false;
+ }
+ FfiPhantom(..) if def.is_enum() => {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_enum_phantomdata,
+ help: None,
+ };
+ }
+ FfiPhantom(..) => {}
+ r => return r,
+ }
+ }
+
+ if all_phantom { FfiPhantom(ty) } else { FfiSafe }
+ }
+ }
+
+ /// Checks if the given type is "ffi-safe" (has a stable, well-defined
+ /// representation which can be exported to C code).
+ fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> {
+ use FfiResult::*;
+
+ let tcx = self.cx.tcx;
+
+ // Protect against infinite recursion, for example
+ // `struct S(*mut S);`.
+ // FIXME: A recursion limit is necessary as well, for irregular
+ // recursive types.
+ if !cache.insert(ty) {
+ return FfiSafe;
+ }
+
+ match *ty.kind() {
+ ty::Adt(def, substs) => {
+ if def.is_box() && matches!(self.mode, CItemKind::Definition) {
+ if ty.boxed_ty().is_sized(tcx.at(DUMMY_SP), self.cx.param_env) {
+ return FfiSafe;
+ } else {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_box,
+ help: None,
+ };
+ }
+ }
+ if def.is_phantom_data() {
+ return FfiPhantom(ty);
+ }
+ match def.adt_kind() {
+ AdtKind::Struct | AdtKind::Union => {
+ if !def.repr().c() && !def.repr().transparent() {
+ return FfiUnsafe {
+ ty,
+ reason: if def.is_struct() {
+ fluent::lint::improper_ctypes_struct_layout_reason
+ } else {
+ fluent::lint::improper_ctypes_union_layout_reason
+ },
+ help: if def.is_struct() {
+ Some(fluent::lint::improper_ctypes_struct_layout_help)
+ } else {
+ Some(fluent::lint::improper_ctypes_union_layout_help)
+ },
+ };
+ }
+
+ let is_non_exhaustive =
+ def.non_enum_variant().is_field_list_non_exhaustive();
+ if is_non_exhaustive && !def.did().is_local() {
+ return FfiUnsafe {
+ ty,
+ reason: if def.is_struct() {
+ fluent::lint::improper_ctypes_struct_non_exhaustive
+ } else {
+ fluent::lint::improper_ctypes_union_non_exhaustive
+ },
+ help: None,
+ };
+ }
+
+ if def.non_enum_variant().fields.is_empty() {
+ return FfiUnsafe {
+ ty,
+ reason: if def.is_struct() {
+ fluent::lint::improper_ctypes_struct_fieldless_reason
+ } else {
+ fluent::lint::improper_ctypes_union_fieldless_reason
+ },
+ help: if def.is_struct() {
+ Some(fluent::lint::improper_ctypes_struct_fieldless_help)
+ } else {
+ Some(fluent::lint::improper_ctypes_union_fieldless_help)
+ },
+ };
+ }
+
+ self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs)
+ }
+ AdtKind::Enum => {
+ if def.variants().is_empty() {
+ // Empty enums are okay... although sort of useless.
+ return FfiSafe;
+ }
+
+ // Check for a repr() attribute to specify the size of the
+ // discriminant.
+ if !def.repr().c() && !def.repr().transparent() && def.repr().int.is_none()
+ {
+ // Special-case types like `Option<extern fn()>`.
+ if repr_nullable_ptr(self.cx, ty, self.mode).is_none() {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_enum_repr_reason,
+ help: Some(fluent::lint::improper_ctypes_enum_repr_help),
+ };
+ }
+ }
+
+ if def.is_variant_list_non_exhaustive() && !def.did().is_local() {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_non_exhaustive,
+ help: None,
+ };
+ }
+
+ // Check the contained variants.
+ for variant in def.variants() {
+ let is_non_exhaustive = variant.is_field_list_non_exhaustive();
+ if is_non_exhaustive && !variant.def_id.is_local() {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_non_exhaustive_variant,
+ help: None,
+ };
+ }
+
+ match self.check_variant_for_ffi(cache, ty, def, variant, substs) {
+ FfiSafe => (),
+ r => return r,
+ }
+ }
+
+ FfiSafe
+ }
+ }
+ }
+
+ ty::Char => FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_char_reason,
+ help: Some(fluent::lint::improper_ctypes_char_help),
+ },
+
+ ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => {
+ FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_128bit, help: None }
+ }
+
+ // Primitive types with a stable representation.
+ ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe,
+
+ ty::Slice(_) => FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_slice_reason,
+ help: Some(fluent::lint::improper_ctypes_slice_help),
+ },
+
+ ty::Dynamic(..) => {
+ FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_dyn, help: None }
+ }
+
+ ty::Str => FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_str_reason,
+ help: Some(fluent::lint::improper_ctypes_str_help),
+ },
+
+ ty::Tuple(..) => FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_tuple_reason,
+ help: Some(fluent::lint::improper_ctypes_tuple_help),
+ },
+
+ ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _)
+ if {
+ matches!(self.mode, CItemKind::Definition)
+ && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env)
+ } =>
+ {
+ FfiSafe
+ }
+
+ ty::RawPtr(ty::TypeAndMut { ty, .. })
+ if match ty.kind() {
+ ty::Tuple(tuple) => tuple.is_empty(),
+ _ => false,
+ } =>
+ {
+ FfiSafe
+ }
+
+ ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
+ self.check_type_for_ffi(cache, ty)
+ }
+
+ ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty),
+
+ ty::FnPtr(sig) => {
+ if self.is_internal_abi(sig.abi()) {
+ return FfiUnsafe {
+ ty,
+ reason: fluent::lint::improper_ctypes_fnptr_reason,
+ help: Some(fluent::lint::improper_ctypes_fnptr_help),
+ };
+ }
+
+ let sig = tcx.erase_late_bound_regions(sig);
+ if !sig.output().is_unit() {
+ let r = self.check_type_for_ffi(cache, sig.output());
+ match r {
+ FfiSafe => {}
+ _ => {
+ return r;
+ }
+ }
+ }
+ for arg in sig.inputs() {
+ let r = self.check_type_for_ffi(cache, *arg);
+ match r {
+ FfiSafe => {}
+ _ => {
+ return r;
+ }
+ }
+ }
+ FfiSafe
+ }
+
+ ty::Foreign(..) => FfiSafe,
+
+ // While opaque types are checked for earlier, if a projection in a struct field
+ // normalizes to an opaque type, then it will reach this branch.
+ ty::Opaque(..) => {
+ FfiUnsafe { ty, reason: fluent::lint::improper_ctypes_opaque, help: None }
+ }
+
+ // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe,
+ // so they are currently ignored for the purposes of this lint.
+ ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => {
+ FfiSafe
+ }
+
+ ty::Param(..)
+ | ty::Projection(..)
+ | ty::Infer(..)
+ | ty::Bound(..)
+ | ty::Error(_)
+ | ty::Closure(..)
+ | ty::Generator(..)
+ | ty::GeneratorWitness(..)
+ | ty::Placeholder(..)
+ | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty),
+ }
+ }
+
+ fn emit_ffi_unsafe_type_lint(
+ &mut self,
+ ty: Ty<'tcx>,
+ sp: Span,
+ note: DiagnosticMessage,
+ help: Option<DiagnosticMessage>,
+ ) {
+ let lint = match self.mode {
+ CItemKind::Declaration => IMPROPER_CTYPES,
+ CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS,
+ };
+
+ self.cx.struct_span_lint(lint, sp, |lint| {
+ let item_description = match self.mode {
+ CItemKind::Declaration => "block",
+ CItemKind::Definition => "fn",
+ };
+ let mut diag = lint.build(fluent::lint::improper_ctypes);
+ diag.set_arg("ty", ty);
+ diag.set_arg("desc", item_description);
+ diag.span_label(sp, fluent::lint::label);
+ if let Some(help) = help {
+ diag.help(help);
+ }
+ diag.note(note);
+ if let ty::Adt(def, _) = ty.kind() {
+ if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) {
+ diag.span_note(sp, fluent::lint::note);
+ }
+ }
+ diag.emit();
+ });
+ }
+
+ 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::visit::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> {
+ type BreakTy = Ty<'tcx>;
+
+ fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
+ match ty.kind() {
+ ty::Opaque(..) => ControlFlow::Break(ty),
+ // Consider opaque types within projections FFI-safe if they do not normalize
+ // to more opaque types.
+ ty::Projection(..) => {
+ let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty);
+
+ // If `ty` is an opaque type directly then `super_visit_with` won't invoke
+ // this function again.
+ if ty.has_opaque_types() {
+ self.visit_ty(ty)
+ } else {
+ ControlFlow::CONTINUE
+ }
+ }
+ _ => ty.super_visit_with(self),
+ }
+ }
+ }
+
+ if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes { cx: self.cx }).break_value() {
+ self.emit_ffi_unsafe_type_lint(ty, sp, fluent::lint::improper_ctypes_opaque, None);
+ true
+ } else {
+ false
+ }
+ }
+
+ fn check_type_for_ffi_and_report_errors(
+ &mut self,
+ sp: Span,
+ ty: Ty<'tcx>,
+ is_static: bool,
+ is_return_type: bool,
+ ) {
+ // We have to check for opaque types before `normalize_erasing_regions`,
+ // which will replace opaque types with their underlying concrete type.
+ if self.check_for_opaque_ty(sp, ty) {
+ // We've already emitted an error due to an opaque type.
+ return;
+ }
+
+ // it is only OK to use this function because extern fns cannot have
+ // any generic types right now:
+ let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty);
+
+ // C doesn't really support passing arrays by value - the only way to pass an array by value
+ // is through a struct. So, first test that the top level isn't an array, and then
+ // recursively check the types inside.
+ if !is_static && self.check_for_array_ty(sp, ty) {
+ return;
+ }
+
+ // Don't report FFI errors for unit return types. This check exists here, and not in
+ // `check_foreign_fn` (where it would make more sense) so that normalization has definitely
+ // happened.
+ if is_return_type && ty.is_unit() {
+ return;
+ }
+
+ match self.check_type_for_ffi(&mut FxHashSet::default(), ty) {
+ FfiResult::FfiSafe => {}
+ FfiResult::FfiPhantom(ty) => {
+ self.emit_ffi_unsafe_type_lint(
+ ty,
+ sp,
+ fluent::lint::improper_ctypes_only_phantomdata,
+ None,
+ );
+ }
+ // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic
+ // argument, which after substitution, is `()`, then this branch can be hit.
+ FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {}
+ FfiResult::FfiUnsafe { ty, reason, help } => {
+ self.emit_ffi_unsafe_type_lint(ty, sp, reason, help);
+ }
+ }
+ }
+
+ fn check_foreign_fn(&mut self, id: hir::HirId, decl: &hir::FnDecl<'_>) {
+ let def_id = self.cx.tcx.hir().local_def_id(id);
+ let sig = self.cx.tcx.fn_sig(def_id);
+ let sig = self.cx.tcx.erase_late_bound_regions(sig);
+
+ for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
+ self.check_type_for_ffi_and_report_errors(input_hir.span, *input_ty, false, false);
+ }
+
+ if let hir::FnRetTy::Return(ref ret_hir) = decl.output {
+ let ret_ty = sig.output();
+ self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true);
+ }
+ }
+
+ fn check_foreign_static(&mut self, id: hir::HirId, span: Span) {
+ let def_id = self.cx.tcx.hir().local_def_id(id);
+ let ty = self.cx.tcx.type_of(def_id);
+ self.check_type_for_ffi_and_report_errors(span, ty, true, false);
+ }
+
+ fn is_internal_abi(&self, abi: SpecAbi) -> bool {
+ matches!(
+ abi,
+ SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic
+ )
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations {
+ fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) {
+ let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration };
+ let abi = cx.tcx.hir().get_foreign_abi(it.hir_id());
+
+ if !vis.is_internal_abi(abi) {
+ match it.kind {
+ hir::ForeignItemKind::Fn(ref decl, _, _) => {
+ vis.check_foreign_fn(it.hir_id(), decl);
+ }
+ hir::ForeignItemKind::Static(ref ty, _) => {
+ vis.check_foreign_static(it.hir_id(), ty.span);
+ }
+ hir::ForeignItemKind::Type => (),
+ }
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'tcx>,
+ kind: hir::intravisit::FnKind<'tcx>,
+ decl: &'tcx hir::FnDecl<'_>,
+ _: &'tcx hir::Body<'_>,
+ _: Span,
+ hir_id: hir::HirId,
+ ) {
+ use hir::intravisit::FnKind;
+
+ let abi = match kind {
+ FnKind::ItemFn(_, _, header, ..) => header.abi,
+ FnKind::Method(_, sig, ..) => sig.header.abi,
+ _ => return,
+ };
+
+ let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition };
+ if !vis.is_internal_abi(abi) {
+ vis.check_foreign_fn(hir_id, decl);
+ }
+ }
+}
+
+declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]);
+
+impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
+ fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) {
+ if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind {
+ let t = cx.tcx.type_of(it.def_id);
+ let ty = cx.tcx.erase_regions(t);
+ let Ok(layout) = cx.layout_of(ty) else { return };
+ let Variants::Multiple {
+ tag_encoding: TagEncoding::Direct, tag, ref variants, ..
+ } = &layout.variants else {
+ return
+ };
+
+ let tag_size = tag.size(&cx.tcx).bytes();
+
+ debug!(
+ "enum `{}` is {} bytes large with layout:\n{:#?}",
+ t,
+ layout.size.bytes(),
+ layout
+ );
+
+ let (largest, slargest, largest_index) = iter::zip(enum_definition.variants, variants)
+ .map(|(variant, variant_layout)| {
+ // Subtract the size of the enum tag.
+ let bytes = variant_layout.size().bytes().saturating_sub(tag_size);
+
+ debug!("- variant `{}` is {} bytes large", variant.ident, bytes);
+ bytes
+ })
+ .enumerate()
+ .fold((0, 0, 0), |(l, s, li), (idx, size)| {
+ if size > l {
+ (size, l, idx)
+ } else if size > s {
+ (l, size, li)
+ } else {
+ (l, s, li)
+ }
+ });
+
+ // We only warn if the largest variant is at least thrice as large as
+ // the second-largest.
+ if largest > slargest * 3 && slargest > 0 {
+ cx.struct_span_lint(
+ VARIANT_SIZE_DIFFERENCES,
+ enum_definition.variants[largest_index].span,
+ |lint| {
+ lint.build(fluent::lint::variant_size_differences)
+ .set_arg("largest", largest)
+ .emit();
+ },
+ );
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `invalid_atomic_ordering` lint detects passing an `Ordering`
+ /// to an atomic operation that does not support that ordering.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// # use core::sync::atomic::{AtomicU8, Ordering};
+ /// let atom = AtomicU8::new(0);
+ /// let value = atom.load(Ordering::Release);
+ /// # let _ = value;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Some atomic operations are only supported for a subset of the
+ /// `atomic::Ordering` variants. Passing an unsupported variant will cause
+ /// an unconditional panic at runtime, which is detected by this lint.
+ ///
+ /// This lint will trigger in the following cases: (where `AtomicType` is an
+ /// atomic type from `core::sync::atomic`, such as `AtomicBool`,
+ /// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics).
+ ///
+ /// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to
+ /// `AtomicType::store`.
+ ///
+ /// - Passing `Ordering::Release` or `Ordering::AcqRel` to
+ /// `AtomicType::load`.
+ ///
+ /// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or
+ /// `core::sync::atomic::compiler_fence`.
+ ///
+ /// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure
+ /// ordering for any of `AtomicType::compare_exchange`,
+ /// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`.
+ INVALID_ATOMIC_ORDERING,
+ Deny,
+ "usage of invalid atomic ordering in atomic operations and memory fences"
+}
+
+declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]);
+
+impl InvalidAtomicOrdering {
+ fn inherent_atomic_method_call<'hir>(
+ cx: &LateContext<'_>,
+ expr: &Expr<'hir>,
+ recognized_names: &[Symbol], // used for fast path calculation
+ ) -> Option<(Symbol, &'hir [Expr<'hir>])> {
+ const ATOMIC_TYPES: &[Symbol] = &[
+ sym::AtomicBool,
+ sym::AtomicPtr,
+ sym::AtomicUsize,
+ sym::AtomicU8,
+ sym::AtomicU16,
+ sym::AtomicU32,
+ sym::AtomicU64,
+ sym::AtomicU128,
+ sym::AtomicIsize,
+ sym::AtomicI8,
+ sym::AtomicI16,
+ sym::AtomicI32,
+ sym::AtomicI64,
+ sym::AtomicI128,
+ ];
+ if let ExprKind::MethodCall(ref method_path, args, _) = &expr.kind
+ && recognized_names.contains(&method_path.ident.name)
+ && let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
+ && let Some(impl_did) = cx.tcx.impl_of_method(m_def_id)
+ && let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def()
+ // skip extension traits, only lint functions from the standard library
+ && cx.tcx.trait_id_of_impl(impl_did).is_none()
+ && let parent = cx.tcx.parent(adt.did())
+ && cx.tcx.is_diagnostic_item(sym::atomic_mod, parent)
+ && ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did()))
+ {
+ return Some((method_path.ident.name, args));
+ }
+ None
+ }
+
+ fn match_ordering(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<Symbol> {
+ let ExprKind::Path(ref ord_qpath) = ord_arg.kind else { return None };
+ let did = cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()?;
+ let tcx = cx.tcx;
+ let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering);
+ let name = tcx.item_name(did);
+ let parent = tcx.parent(did);
+ [sym::Relaxed, sym::Release, sym::Acquire, sym::AcqRel, sym::SeqCst].into_iter().find(
+ |&ordering| {
+ name == ordering
+ && (Some(parent) == atomic_ordering
+ // needed in case this is a ctor, not a variant
+ || tcx.opt_parent(parent) == atomic_ordering)
+ },
+ )
+ }
+
+ fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store])
+ && let Some((ordering_arg, invalid_ordering)) = match method {
+ sym::load => Some((&args[1], sym::Release)),
+ sym::store => Some((&args[2], sym::Acquire)),
+ _ => None,
+ }
+ && let Some(ordering) = Self::match_ordering(cx, ordering_arg)
+ && (ordering == invalid_ordering || ordering == sym::AcqRel)
+ {
+ cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
+ if method == sym::load {
+ diag.build(fluent::lint::atomic_ordering_load)
+ .help(fluent::lint::help)
+ .emit()
+ } else {
+ debug_assert_eq!(method, sym::store);
+ diag.build(fluent::lint::atomic_ordering_store)
+ .help(fluent::lint::help)
+ .emit();
+ }
+ });
+ }
+ }
+
+ fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Call(ref func, ref args) = expr.kind
+ && let ExprKind::Path(ref func_qpath) = func.kind
+ && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
+ && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::fence | sym::compiler_fence))
+ && Self::match_ordering(cx, &args[0]) == Some(sym::Relaxed)
+ {
+ cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| {
+ diag.build(fluent::lint::atomic_ordering_fence)
+ .help(fluent::lint::help)
+ .emit();
+ });
+ }
+ }
+
+ fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
+ let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak])
+ else {return };
+
+ let fail_order_arg = match method {
+ sym::fetch_update => &args[2],
+ sym::compare_exchange | sym::compare_exchange_weak => &args[4],
+ _ => return,
+ };
+
+ let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return };
+
+ if matches!(fail_ordering, sym::Release | sym::AcqRel) {
+ #[derive(LintDiagnostic)]
+ #[lint(lint::atomic_ordering_invalid)]
+ #[help]
+ struct InvalidAtomicOrderingDiag {
+ method: Symbol,
+ #[label]
+ fail_order_arg_span: Span,
+ }
+
+ cx.emit_spanned_lint(
+ INVALID_ATOMIC_ORDERING,
+ fail_order_arg.span,
+ InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span },
+ );
+ }
+ }
+}
+
+impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ Self::check_atomic_load_store(cx, expr);
+ Self::check_memory_fence(cx, expr);
+ Self::check_atomic_compare_exchange(cx, expr);
+ }
+}
diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs
new file mode 100644
index 000000000..b6cf18291
--- /dev/null
+++ b/compiler/rustc_lint/src/unused.rs
@@ -0,0 +1,1197 @@
+use crate::Lint;
+use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
+use rustc_ast as ast;
+use rustc_ast::util::{classify, parser};
+use rustc_ast::{ExprKind, StmtKind};
+use rustc_errors::{fluent, pluralize, Applicability, MultiSpan};
+use rustc_hir as hir;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::def_id::DefId;
+use rustc_middle::ty::adjustment;
+use rustc_middle::ty::{self, Ty};
+use rustc_span::symbol::Symbol;
+use rustc_span::symbol::{kw, sym};
+use rustc_span::{BytePos, Span, DUMMY_SP};
+
+declare_lint! {
+ /// The `unused_must_use` lint detects unused result of a type flagged as
+ /// `#[must_use]`.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// fn returns_result() -> Result<(), ()> {
+ /// Ok(())
+ /// }
+ ///
+ /// fn main() {
+ /// returns_result();
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The `#[must_use]` attribute is an indicator that it is a mistake to
+ /// ignore the value. See [the reference] for more details.
+ ///
+ /// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
+ pub UNUSED_MUST_USE,
+ Warn,
+ "unused result of a type flagged as `#[must_use]`",
+ report_in_external_macro
+}
+
+declare_lint! {
+ /// The `unused_results` lint checks for the unused result of an
+ /// expression in a statement.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(unused_results)]
+ /// fn foo<T>() -> T { panic!() }
+ ///
+ /// fn main() {
+ /// foo::<usize>();
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// Ignoring the return value of a function may indicate a mistake. In
+ /// cases were it is almost certain that the result should be used, it is
+ /// recommended to annotate the function with the [`must_use` attribute].
+ /// Failure to use such a return value will trigger the [`unused_must_use`
+ /// lint] which is warn-by-default. The `unused_results` lint is
+ /// essentially the same, but triggers for *all* return values.
+ ///
+ /// This lint is "allow" by default because it can be noisy, and may not be
+ /// an actual problem. For example, calling the `remove` method of a `Vec`
+ /// or `HashMap` returns the previous value, which you may not care about.
+ /// Using this lint would require explicitly ignoring or discarding such
+ /// values.
+ ///
+ /// [`must_use` attribute]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
+ /// [`unused_must_use` lint]: warn-by-default.html#unused-must-use
+ pub UNUSED_RESULTS,
+ Allow,
+ "unused result of an expression in a statement"
+}
+
+declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedResults {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+ let expr = match s.kind {
+ hir::StmtKind::Semi(ref expr) => &**expr,
+ _ => return,
+ };
+
+ if let hir::ExprKind::Ret(..) = expr.kind {
+ return;
+ }
+
+ let ty = cx.typeck_results().expr_ty(&expr);
+ let type_permits_lack_of_use = check_must_use_ty(cx, ty, &expr, s.span, "", "", 1);
+
+ let mut fn_warned = false;
+ let mut op_warned = false;
+ let maybe_def_id = match expr.kind {
+ hir::ExprKind::Call(ref callee, _) => {
+ match callee.kind {
+ hir::ExprKind::Path(ref qpath) => {
+ match cx.qpath_res(qpath, callee.hir_id) {
+ Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id),
+ // `Res::Local` if it was a closure, for which we
+ // do not currently support must-use linting
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ }
+ hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id),
+ _ => None,
+ };
+ if let Some(def_id) = maybe_def_id {
+ fn_warned = check_must_use_def(cx, def_id, s.span, "return value of ", "");
+ } else if type_permits_lack_of_use {
+ // We don't warn about unused unit or uninhabited types.
+ // (See https://github.com/rust-lang/rust/issues/43806 for details.)
+ return;
+ }
+
+ let must_use_op = match expr.kind {
+ // Hardcoding operators here seemed more expedient than the
+ // refactoring that would be needed to look up the `#[must_use]`
+ // attribute which does exist on the comparison trait methods
+ hir::ExprKind::Binary(bin_op, ..) => match bin_op.node {
+ hir::BinOpKind::Eq
+ | hir::BinOpKind::Lt
+ | hir::BinOpKind::Le
+ | hir::BinOpKind::Ne
+ | hir::BinOpKind::Ge
+ | hir::BinOpKind::Gt => Some("comparison"),
+ hir::BinOpKind::Add
+ | hir::BinOpKind::Sub
+ | hir::BinOpKind::Div
+ | hir::BinOpKind::Mul
+ | hir::BinOpKind::Rem => Some("arithmetic operation"),
+ hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"),
+ hir::BinOpKind::BitXor
+ | hir::BinOpKind::BitAnd
+ | hir::BinOpKind::BitOr
+ | hir::BinOpKind::Shl
+ | hir::BinOpKind::Shr => Some("bitwise operation"),
+ },
+ hir::ExprKind::AddrOf(..) => Some("borrow"),
+ hir::ExprKind::Unary(..) => Some("unary operation"),
+ _ => None,
+ };
+
+ if let Some(must_use_op) = must_use_op {
+ cx.struct_span_lint(UNUSED_MUST_USE, expr.span, |lint| {
+ lint.build(fluent::lint::unused_op)
+ .set_arg("op", must_use_op)
+ .span_label(expr.span, fluent::lint::label)
+ .span_suggestion_verbose(
+ expr.span.shrink_to_lo(),
+ fluent::lint::suggestion,
+ "let _ = ",
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ op_warned = true;
+ }
+
+ if !(type_permits_lack_of_use || fn_warned || op_warned) {
+ cx.struct_span_lint(UNUSED_RESULTS, s.span, |lint| {
+ lint.build(fluent::lint::unused_result).set_arg("ty", ty).emit();
+ });
+ }
+
+ // Returns whether an error has been emitted (and thus another does not need to be later).
+ fn check_must_use_ty<'tcx>(
+ cx: &LateContext<'tcx>,
+ ty: Ty<'tcx>,
+ expr: &hir::Expr<'_>,
+ span: Span,
+ descr_pre: &str,
+ descr_post: &str,
+ plural_len: usize,
+ ) -> bool {
+ if ty.is_unit()
+ || cx.tcx.is_ty_uninhabited_from(
+ cx.tcx.parent_module(expr.hir_id).to_def_id(),
+ ty,
+ cx.param_env,
+ )
+ {
+ return true;
+ }
+
+ let plural_suffix = pluralize!(plural_len);
+
+ match *ty.kind() {
+ ty::Adt(..) if ty.is_box() => {
+ let boxed_ty = ty.boxed_ty();
+ let descr_pre = &format!("{}boxed ", descr_pre);
+ check_must_use_ty(cx, boxed_ty, expr, span, descr_pre, descr_post, plural_len)
+ }
+ ty::Adt(def, _) => check_must_use_def(cx, def.did(), span, descr_pre, descr_post),
+ ty::Opaque(def, _) => {
+ let mut has_emitted = false;
+ for &(predicate, _) in cx.tcx.explicit_item_bounds(def) {
+ // We only look at the `DefId`, so it is safe to skip the binder here.
+ if let ty::PredicateKind::Trait(ref poly_trait_predicate) =
+ predicate.kind().skip_binder()
+ {
+ let def_id = poly_trait_predicate.trait_ref.def_id;
+ let descr_pre =
+ &format!("{}implementer{} of ", descr_pre, plural_suffix,);
+ if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
+ has_emitted = true;
+ break;
+ }
+ }
+ }
+ has_emitted
+ }
+ ty::Dynamic(binder, _) => {
+ let mut has_emitted = false;
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) =
+ predicate.skip_binder()
+ {
+ let def_id = trait_ref.def_id;
+ let descr_post =
+ &format!(" trait object{}{}", plural_suffix, descr_post,);
+ if check_must_use_def(cx, def_id, span, descr_pre, descr_post) {
+ has_emitted = true;
+ break;
+ }
+ }
+ }
+ has_emitted
+ }
+ ty::Tuple(ref tys) => {
+ let mut has_emitted = false;
+ let comps = if let hir::ExprKind::Tup(comps) = expr.kind {
+ debug_assert_eq!(comps.len(), tys.len());
+ comps
+ } else {
+ &[]
+ };
+ for (i, ty) in tys.iter().enumerate() {
+ let descr_post = &format!(" in tuple element {}", i);
+ let e = comps.get(i).unwrap_or(expr);
+ let span = e.span;
+ if check_must_use_ty(cx, ty, e, span, descr_pre, descr_post, plural_len) {
+ has_emitted = true;
+ }
+ }
+ has_emitted
+ }
+ ty::Array(ty, len) => match len.try_eval_usize(cx.tcx, cx.param_env) {
+ // If the array is empty we don't lint, to avoid false positives
+ Some(0) | None => false,
+ // If the array is definitely non-empty, we can do `#[must_use]` checking.
+ Some(n) => {
+ let descr_pre = &format!("{}array{} of ", descr_pre, plural_suffix,);
+ check_must_use_ty(cx, ty, expr, span, descr_pre, descr_post, n as usize + 1)
+ }
+ },
+ ty::Closure(..) => {
+ cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+ // FIXME(davidtwco): this isn't properly translatable becauses of the
+ // pre/post strings
+ lint.build(fluent::lint::unused_closure)
+ .set_arg("count", plural_len)
+ .set_arg("pre", descr_pre)
+ .set_arg("post", descr_post)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ true
+ }
+ ty::Generator(..) => {
+ cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+ // FIXME(davidtwco): this isn't properly translatable becauses of the
+ // pre/post strings
+ lint.build(fluent::lint::unused_generator)
+ .set_arg("count", plural_len)
+ .set_arg("pre", descr_pre)
+ .set_arg("post", descr_post)
+ .note(fluent::lint::note)
+ .emit();
+ });
+ true
+ }
+ _ => false,
+ }
+ }
+
+ // Returns whether an error has been emitted (and thus another does not need to be later).
+ // FIXME: Args desc_{pre,post}_path could be made lazy by taking Fn() -> &str, but this
+ // would make calling it a big awkward. Could also take String (so args are moved), but
+ // this would still require a copy into the format string, which would only be executed
+ // when needed.
+ fn check_must_use_def(
+ cx: &LateContext<'_>,
+ def_id: DefId,
+ span: Span,
+ descr_pre_path: &str,
+ descr_post_path: &str,
+ ) -> bool {
+ if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
+ cx.struct_span_lint(UNUSED_MUST_USE, span, |lint| {
+ // FIXME(davidtwco): this isn't properly translatable becauses of the pre/post
+ // strings
+ let mut err = lint.build(fluent::lint::unused_def);
+ err.set_arg("pre", descr_pre_path);
+ err.set_arg("post", descr_post_path);
+ err.set_arg("def", cx.tcx.def_path_str(def_id));
+ // check for #[must_use = "..."]
+ if let Some(note) = attr.value_str() {
+ err.note(note.as_str());
+ }
+ err.emit();
+ });
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
+
+declare_lint! {
+ /// The `path_statements` lint detects path statements with no effect.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// let x = 42;
+ ///
+ /// x;
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// It is usually a mistake to have a statement that has no effect.
+ pub PATH_STATEMENTS,
+ Warn,
+ "path statements with no effect"
+}
+
+declare_lint_pass!(PathStatements => [PATH_STATEMENTS]);
+
+impl<'tcx> LateLintPass<'tcx> for PathStatements {
+ fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
+ if let hir::StmtKind::Semi(expr) = s.kind {
+ if let hir::ExprKind::Path(_) = expr.kind {
+ cx.struct_span_lint(PATH_STATEMENTS, s.span, |lint| {
+ let ty = cx.typeck_results().expr_ty(expr);
+ if ty.needs_drop(cx.tcx, cx.param_env) {
+ let mut lint = lint.build(fluent::lint::path_statement_drop);
+ if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) {
+ lint.span_suggestion(
+ s.span,
+ fluent::lint::suggestion,
+ format!("drop({});", snippet),
+ Applicability::MachineApplicable,
+ );
+ } else {
+ lint.span_help(s.span, fluent::lint::suggestion);
+ }
+ lint.emit();
+ } else {
+ lint.build(fluent::lint::path_statement_no_effect).emit();
+ }
+ });
+ }
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum UnusedDelimsCtx {
+ FunctionArg,
+ MethodArg,
+ AssignedValue,
+ AssignedValueLetElse,
+ IfCond,
+ WhileCond,
+ ForIterExpr,
+ MatchScrutineeExpr,
+ ReturnValue,
+ BlockRetValue,
+ LetScrutineeExpr,
+ ArrayLenExpr,
+ AnonConst,
+ MatchArmExpr,
+}
+
+impl From<UnusedDelimsCtx> for &'static str {
+ fn from(ctx: UnusedDelimsCtx) -> &'static str {
+ match ctx {
+ UnusedDelimsCtx::FunctionArg => "function argument",
+ UnusedDelimsCtx::MethodArg => "method argument",
+ UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
+ "assigned value"
+ }
+ UnusedDelimsCtx::IfCond => "`if` condition",
+ UnusedDelimsCtx::WhileCond => "`while` condition",
+ UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
+ UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression",
+ UnusedDelimsCtx::ReturnValue => "`return` value",
+ UnusedDelimsCtx::BlockRetValue => "block return value",
+ UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression",
+ UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression",
+ UnusedDelimsCtx::MatchArmExpr => "match arm expression",
+ }
+ }
+}
+
+/// Used by both `UnusedParens` and `UnusedBraces` to prevent code duplication.
+trait UnusedDelimLint {
+ const DELIM_STR: &'static str;
+
+ /// Due to `ref` pattern, there can be a difference between using
+ /// `{ expr }` and `expr` in pattern-matching contexts. This means
+ /// that we should only lint `unused_parens` and not `unused_braces`
+ /// in this case.
+ ///
+ /// ```rust
+ /// let mut a = 7;
+ /// let ref b = { a }; // We actually borrow a copy of `a` here.
+ /// a += 1; // By mutating `a` we invalidate any borrows of `a`.
+ /// assert_eq!(b + 1, a); // `b` does not borrow `a`, so we can still use it here.
+ /// ```
+ const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool;
+
+ // this cannot be a constant is it refers to a static.
+ fn lint(&self) -> &'static Lint;
+
+ fn check_unused_delims_expr(
+ &self,
+ cx: &EarlyContext<'_>,
+ value: &ast::Expr,
+ ctx: UnusedDelimsCtx,
+ followed_by_block: bool,
+ left_pos: Option<BytePos>,
+ right_pos: Option<BytePos>,
+ );
+
+ fn is_expr_delims_necessary(
+ inner: &ast::Expr,
+ followed_by_block: bool,
+ followed_by_else: bool,
+ ) -> bool {
+ if followed_by_else {
+ match inner.kind {
+ ast::ExprKind::Binary(op, ..) if op.node.lazy() => return true,
+ _ if classify::expr_trailing_brace(inner).is_some() => return true,
+ _ => {}
+ }
+ }
+
+ // Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`
+ let lhs_needs_parens = {
+ let mut innermost = inner;
+ loop {
+ innermost = match &innermost.kind {
+ ExprKind::Binary(_, lhs, _rhs) => lhs,
+ ExprKind::Call(fn_, _params) => fn_,
+ ExprKind::Cast(expr, _ty) => expr,
+ ExprKind::Type(expr, _ty) => expr,
+ ExprKind::Index(base, _subscript) => base,
+ _ => break false,
+ };
+ if !classify::expr_requires_semi_to_be_stmt(innermost) {
+ break true;
+ }
+ }
+ };
+
+ lhs_needs_parens
+ || (followed_by_block
+ && match &inner.kind {
+ ExprKind::Ret(_) | ExprKind::Break(..) | ExprKind::Yield(..) => true,
+ ExprKind::Range(_lhs, Some(rhs), _limits) => {
+ matches!(rhs.kind, ExprKind::Block(..))
+ }
+ _ => parser::contains_exterior_struct_lit(&inner),
+ })
+ }
+
+ fn emit_unused_delims_expr(
+ &self,
+ cx: &EarlyContext<'_>,
+ value: &ast::Expr,
+ ctx: UnusedDelimsCtx,
+ left_pos: Option<BytePos>,
+ right_pos: Option<BytePos>,
+ ) {
+ let spans = match value.kind {
+ ast::ExprKind::Block(ref block, None) if block.stmts.len() > 0 => {
+ let start = block.stmts[0].span;
+ let end = block.stmts[block.stmts.len() - 1].span;
+ if value.span.from_expansion() || start.from_expansion() || end.from_expansion() {
+ (
+ value.span.with_hi(value.span.lo() + BytePos(1)),
+ value.span.with_lo(value.span.hi() - BytePos(1)),
+ )
+ } else {
+ (value.span.with_hi(start.lo()), value.span.with_lo(end.hi()))
+ }
+ }
+ ast::ExprKind::Paren(ref expr) => {
+ if value.span.from_expansion() || expr.span.from_expansion() {
+ (
+ value.span.with_hi(value.span.lo() + BytePos(1)),
+ value.span.with_lo(value.span.hi() - BytePos(1)),
+ )
+ } else {
+ (value.span.with_hi(expr.span.lo()), value.span.with_lo(expr.span.hi()))
+ }
+ }
+ _ => return,
+ };
+ let keep_space = (
+ left_pos.map_or(false, |s| s >= value.span.lo()),
+ right_pos.map_or(false, |s| s <= value.span.hi()),
+ );
+ self.emit_unused_delims(cx, spans, ctx.into(), keep_space);
+ }
+
+ fn emit_unused_delims(
+ &self,
+ cx: &EarlyContext<'_>,
+ spans: (Span, Span),
+ msg: &str,
+ keep_space: (bool, bool),
+ ) {
+ // FIXME(flip1995): Quick and dirty fix for #70814. This should be fixed in rustdoc
+ // properly.
+ if spans.0 == DUMMY_SP || spans.1 == DUMMY_SP {
+ return;
+ }
+
+ cx.struct_span_lint(self.lint(), MultiSpan::from(vec![spans.0, spans.1]), |lint| {
+ let replacement = vec![
+ (spans.0, if keep_space.0 { " ".into() } else { "".into() }),
+ (spans.1, if keep_space.1 { " ".into() } else { "".into() }),
+ ];
+ lint.build(fluent::lint::unused_delim)
+ .set_arg("delim", Self::DELIM_STR)
+ .set_arg("item", msg)
+ .multipart_suggestion(
+ fluent::lint::suggestion,
+ replacement,
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ });
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ use rustc_ast::ExprKind::*;
+ let (value, ctx, followed_by_block, left_pos, right_pos) = match e.kind {
+ // Do not lint `unused_braces` in `if let` expressions.
+ If(ref cond, ref block, _)
+ if !matches!(cond.kind, Let(_, _, _))
+ || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+ {
+ let left = e.span.lo() + rustc_span::BytePos(2);
+ let right = block.span.lo();
+ (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right))
+ }
+
+ // Do not lint `unused_braces` in `while let` expressions.
+ While(ref cond, ref block, ..)
+ if !matches!(cond.kind, Let(_, _, _))
+ || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
+ {
+ let left = e.span.lo() + rustc_span::BytePos(5);
+ let right = block.span.lo();
+ (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right))
+ }
+
+ ForLoop(_, ref cond, ref block, ..) => {
+ (cond, UnusedDelimsCtx::ForIterExpr, true, None, Some(block.span.lo()))
+ }
+
+ Match(ref head, _) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
+ let left = e.span.lo() + rustc_span::BytePos(5);
+ (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None)
+ }
+
+ Ret(Some(ref value)) => {
+ let left = e.span.lo() + rustc_span::BytePos(3);
+ (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None)
+ }
+
+ Assign(_, ref value, _) | AssignOp(.., ref value) => {
+ (value, UnusedDelimsCtx::AssignedValue, false, None, None)
+ }
+ // either function/method call, or something this lint doesn't care about
+ ref call_or_other => {
+ let (args_to_check, ctx) = match *call_or_other {
+ Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
+ // first "argument" is self (which sometimes needs delims)
+ MethodCall(_, ref args, _) => (&args[1..], UnusedDelimsCtx::MethodArg),
+ // actual catch-all arm
+ _ => {
+ return;
+ }
+ };
+ // Don't lint if this is a nested macro expansion: otherwise, the lint could
+ // trigger in situations that macro authors shouldn't have to care about, e.g.,
+ // when a parenthesized token tree matched in one macro expansion is matched as
+ // an expression in another and used as a fn/method argument (Issue #47775)
+ if e.span.ctxt().outer_expn_data().call_site.from_expansion() {
+ return;
+ }
+ for arg in args_to_check {
+ self.check_unused_delims_expr(cx, arg, ctx, false, None, None);
+ }
+ return;
+ }
+ };
+ self.check_unused_delims_expr(cx, &value, ctx, followed_by_block, left_pos, right_pos);
+ }
+
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+ match s.kind {
+ StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
+ if let Some((init, els)) = local.kind.init_else_opt() {
+ let ctx = match els {
+ None => UnusedDelimsCtx::AssignedValue,
+ Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
+ };
+ self.check_unused_delims_expr(cx, init, ctx, false, None, None);
+ }
+ }
+ StmtKind::Expr(ref expr) => {
+ self.check_unused_delims_expr(
+ cx,
+ &expr,
+ UnusedDelimsCtx::BlockRetValue,
+ false,
+ None,
+ None,
+ );
+ }
+ _ => {}
+ }
+ }
+
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ use ast::ItemKind::*;
+
+ if let Const(.., Some(expr)) | Static(.., Some(expr)) = &item.kind {
+ self.check_unused_delims_expr(
+ cx,
+ expr,
+ UnusedDelimsCtx::AssignedValue,
+ false,
+ None,
+ None,
+ );
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unused_parens` lint detects `if`, `match`, `while` and `return`
+ /// with parentheses; they do not need them.
+ ///
+ /// ### Examples
+ ///
+ /// ```rust
+ /// if(true) {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The parentheses are not needed, and should be removed. This is the
+ /// preferred style for writing these expressions.
+ pub(super) UNUSED_PARENS,
+ Warn,
+ "`if`, `match`, `while` and `return` do not need parentheses"
+}
+
+declare_lint_pass!(UnusedParens => [UNUSED_PARENS]);
+
+impl UnusedDelimLint for UnusedParens {
+ const DELIM_STR: &'static str = "parentheses";
+
+ const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true;
+
+ fn lint(&self) -> &'static Lint {
+ UNUSED_PARENS
+ }
+
+ fn check_unused_delims_expr(
+ &self,
+ cx: &EarlyContext<'_>,
+ value: &ast::Expr,
+ ctx: UnusedDelimsCtx,
+ followed_by_block: bool,
+ left_pos: Option<BytePos>,
+ right_pos: Option<BytePos>,
+ ) {
+ match value.kind {
+ ast::ExprKind::Paren(ref inner) => {
+ let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
+ if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else)
+ && value.attrs.is_empty()
+ && !value.span.from_expansion()
+ && (ctx != UnusedDelimsCtx::LetScrutineeExpr
+ || !matches!(inner.kind, ast::ExprKind::Binary(
+ rustc_span::source_map::Spanned { node, .. },
+ _,
+ _,
+ ) if node.lazy()))
+ {
+ self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos)
+ }
+ }
+ ast::ExprKind::Let(_, ref expr, _) => {
+ self.check_unused_delims_expr(
+ cx,
+ expr,
+ UnusedDelimsCtx::LetScrutineeExpr,
+ followed_by_block,
+ None,
+ None,
+ );
+ }
+ _ => {}
+ }
+ }
+}
+
+impl UnusedParens {
+ fn check_unused_parens_pat(
+ &self,
+ cx: &EarlyContext<'_>,
+ value: &ast::Pat,
+ avoid_or: bool,
+ avoid_mut: bool,
+ ) {
+ use ast::{BindingMode, Mutability, PatKind};
+
+ if let PatKind::Paren(inner) = &value.kind {
+ match inner.kind {
+ // The lint visitor will visit each subpattern of `p`. We do not want to lint
+ // any range pattern no matter where it occurs in the pattern. For something like
+ // `&(a..=b)`, there is a recursive `check_pat` on `a` and `b`, but we will assume
+ // that if there are unnecessary parens they serve a purpose of readability.
+ PatKind::Range(..) => return,
+ // Avoid `p0 | .. | pn` if we should.
+ PatKind::Or(..) if avoid_or => return,
+ // Avoid `mut x` and `mut x @ p` if we should:
+ PatKind::Ident(BindingMode::ByValue(Mutability::Mut), ..) if avoid_mut => return,
+ // Otherwise proceed with linting.
+ _ => {}
+ }
+ let spans = if value.span.from_expansion() || inner.span.from_expansion() {
+ (
+ value.span.with_hi(value.span.lo() + BytePos(1)),
+ value.span.with_lo(value.span.hi() - BytePos(1)),
+ )
+ } else {
+ (value.span.with_hi(inner.span.lo()), value.span.with_lo(inner.span.hi()))
+ };
+ self.emit_unused_delims(cx, spans, "pattern", (false, false));
+ }
+ }
+}
+
+impl EarlyLintPass for UnusedParens {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ match e.kind {
+ ExprKind::Let(ref pat, _, _) | ExprKind::ForLoop(ref pat, ..) => {
+ self.check_unused_parens_pat(cx, pat, false, false);
+ }
+ // We ignore parens in cases like `if (((let Some(0) = Some(1))))` because we already
+ // handle a hard error for them during AST lowering in `lower_expr_mut`, but we still
+ // want to complain about things like `if let 42 = (42)`.
+ ExprKind::If(ref cond, ref block, ref else_)
+ if matches!(cond.peel_parens().kind, ExprKind::Let(..)) =>
+ {
+ self.check_unused_delims_expr(
+ cx,
+ cond.peel_parens(),
+ UnusedDelimsCtx::LetScrutineeExpr,
+ true,
+ None,
+ None,
+ );
+ for stmt in &block.stmts {
+ <Self as UnusedDelimLint>::check_stmt(self, cx, stmt);
+ }
+ if let Some(e) = else_ {
+ <Self as UnusedDelimLint>::check_expr(self, cx, e);
+ }
+ return;
+ }
+ ExprKind::Match(ref _expr, ref arm) => {
+ for a in arm {
+ self.check_unused_delims_expr(
+ cx,
+ &a.body,
+ UnusedDelimsCtx::MatchArmExpr,
+ false,
+ None,
+ None,
+ );
+ }
+ }
+ _ => {}
+ }
+
+ <Self as UnusedDelimLint>::check_expr(self, cx, e)
+ }
+
+ fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) {
+ use ast::{Mutability, PatKind::*};
+ match &p.kind {
+ // Do not lint on `(..)` as that will result in the other arms being useless.
+ Paren(_)
+ // The other cases do not contain sub-patterns.
+ | Wild | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) => {},
+ // These are list-like patterns; parens can always be removed.
+ TupleStruct(_, _, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps {
+ self.check_unused_parens_pat(cx, p, false, false);
+ },
+ Struct(_, _, fps, _) => for f in fps {
+ self.check_unused_parens_pat(cx, &f.pat, false, false);
+ },
+ // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106.
+ Ident(.., Some(p)) | Box(p) => self.check_unused_parens_pat(cx, p, true, false),
+ // Avoid linting on `&(mut x)` as `&mut x` has a different meaning, #55342.
+ // Also avoid linting on `& mut? (p0 | .. | pn)`, #64106.
+ Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not),
+ }
+ }
+
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+ if let StmtKind::Local(ref local) = s.kind {
+ self.check_unused_parens_pat(cx, &local.pat, true, false);
+ }
+
+ <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+ }
+
+ fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) {
+ self.check_unused_parens_pat(cx, &param.pat, true, false);
+ }
+
+ fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
+ self.check_unused_parens_pat(cx, &arm.pat, false, false);
+ }
+
+ fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+ if let ast::TyKind::Paren(r) = &ty.kind {
+ match &r.kind {
+ ast::TyKind::TraitObject(..) => {}
+ ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {}
+ ast::TyKind::Array(_, len) => {
+ self.check_unused_delims_expr(
+ cx,
+ &len.value,
+ UnusedDelimsCtx::ArrayLenExpr,
+ false,
+ None,
+ None,
+ );
+ }
+ _ => {
+ let spans = if ty.span.from_expansion() || r.span.from_expansion() {
+ (
+ ty.span.with_hi(ty.span.lo() + BytePos(1)),
+ ty.span.with_lo(ty.span.hi() - BytePos(1)),
+ )
+ } else {
+ (ty.span.with_hi(r.span.lo()), ty.span.with_lo(r.span.hi()))
+ };
+ self.emit_unused_delims(cx, spans, "type", (false, false));
+ }
+ }
+ }
+ }
+
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ <Self as UnusedDelimLint>::check_item(self, cx, item)
+ }
+}
+
+declare_lint! {
+ /// The `unused_braces` lint detects unnecessary braces around an
+ /// expression.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// if { true } {
+ /// // ...
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The braces are not needed, and should be removed. This is the
+ /// preferred style for writing these expressions.
+ pub(super) UNUSED_BRACES,
+ Warn,
+ "unnecessary braces around an expression"
+}
+
+declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]);
+
+impl UnusedDelimLint for UnusedBraces {
+ const DELIM_STR: &'static str = "braces";
+
+ const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false;
+
+ fn lint(&self) -> &'static Lint {
+ UNUSED_BRACES
+ }
+
+ fn check_unused_delims_expr(
+ &self,
+ cx: &EarlyContext<'_>,
+ value: &ast::Expr,
+ ctx: UnusedDelimsCtx,
+ followed_by_block: bool,
+ left_pos: Option<BytePos>,
+ right_pos: Option<BytePos>,
+ ) {
+ match value.kind {
+ ast::ExprKind::Block(ref inner, None)
+ if inner.rules == ast::BlockCheckMode::Default =>
+ {
+ // emit a warning under the following conditions:
+ //
+ // - the block does not have a label
+ // - the block is not `unsafe`
+ // - the block contains exactly one expression (do not lint `{ expr; }`)
+ // - `followed_by_block` is true and the internal expr may contain a `{`
+ // - the block is not multiline (do not lint multiline match arms)
+ // ```
+ // match expr {
+ // Pattern => {
+ // somewhat_long_expression
+ // }
+ // // ...
+ // }
+ // ```
+ // - the block has no attribute and was not created inside a macro
+ // - if the block is an `anon_const`, the inner expr must be a literal
+ // (do not lint `struct A<const N: usize>; let _: A<{ 2 + 3 }>;`)
+ //
+ // FIXME(const_generics): handle paths when #67075 is fixed.
+ if let [stmt] = inner.stmts.as_slice() {
+ if let ast::StmtKind::Expr(ref expr) = stmt.kind {
+ if !Self::is_expr_delims_necessary(expr, followed_by_block, false)
+ && (ctx != UnusedDelimsCtx::AnonConst
+ || matches!(expr.kind, ast::ExprKind::Lit(_)))
+ && !cx.sess().source_map().is_multiline(value.span)
+ && value.attrs.is_empty()
+ && !value.span.from_expansion()
+ {
+ self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos)
+ }
+ }
+ }
+ }
+ ast::ExprKind::Let(_, ref expr, _) => {
+ self.check_unused_delims_expr(
+ cx,
+ expr,
+ UnusedDelimsCtx::LetScrutineeExpr,
+ followed_by_block,
+ None,
+ None,
+ );
+ }
+ _ => {}
+ }
+ }
+}
+
+impl EarlyLintPass for UnusedBraces {
+ fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
+ <Self as UnusedDelimLint>::check_stmt(self, cx, s)
+ }
+
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ <Self as UnusedDelimLint>::check_expr(self, cx, e);
+
+ if let ExprKind::Repeat(_, ref anon_const) = e.kind {
+ self.check_unused_delims_expr(
+ cx,
+ &anon_const.value,
+ UnusedDelimsCtx::AnonConst,
+ false,
+ None,
+ None,
+ );
+ }
+ }
+
+ fn check_generic_arg(&mut self, cx: &EarlyContext<'_>, arg: &ast::GenericArg) {
+ if let ast::GenericArg::Const(ct) = arg {
+ self.check_unused_delims_expr(
+ cx,
+ &ct.value,
+ UnusedDelimsCtx::AnonConst,
+ false,
+ None,
+ None,
+ );
+ }
+ }
+
+ fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
+ if let Some(anon_const) = &v.disr_expr {
+ self.check_unused_delims_expr(
+ cx,
+ &anon_const.value,
+ UnusedDelimsCtx::AnonConst,
+ false,
+ None,
+ None,
+ );
+ }
+ }
+
+ fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
+ match ty.kind {
+ ast::TyKind::Array(_, ref len) => {
+ self.check_unused_delims_expr(
+ cx,
+ &len.value,
+ UnusedDelimsCtx::ArrayLenExpr,
+ false,
+ None,
+ None,
+ );
+ }
+
+ ast::TyKind::Typeof(ref anon_const) => {
+ self.check_unused_delims_expr(
+ cx,
+ &anon_const.value,
+ UnusedDelimsCtx::AnonConst,
+ false,
+ None,
+ None,
+ );
+ }
+
+ _ => {}
+ }
+ }
+
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ <Self as UnusedDelimLint>::check_item(self, cx, item)
+ }
+}
+
+declare_lint! {
+ /// The `unused_import_braces` lint catches unnecessary braces around an
+ /// imported item.
+ ///
+ /// ### Example
+ ///
+ /// ```rust,compile_fail
+ /// #![deny(unused_import_braces)]
+ /// use test::{A};
+ ///
+ /// pub mod test {
+ /// pub struct A;
+ /// }
+ /// # fn main() {}
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// If there is only a single item, then remove the braces (`use test::A;`
+ /// for example).
+ ///
+ /// This lint is "allow" by default because it is only enforcing a
+ /// stylistic choice.
+ UNUSED_IMPORT_BRACES,
+ Allow,
+ "unnecessary braces around an imported item"
+}
+
+declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]);
+
+impl UnusedImportBraces {
+ fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) {
+ if let ast::UseTreeKind::Nested(ref items) = use_tree.kind {
+ // Recursively check nested UseTrees
+ for &(ref tree, _) in items {
+ self.check_use_tree(cx, tree, item);
+ }
+
+ // Trigger the lint only if there is one nested item
+ if items.len() != 1 {
+ return;
+ }
+
+ // Trigger the lint if the nested item is a non-self single item
+ let node_name = match items[0].0.kind {
+ ast::UseTreeKind::Simple(rename, ..) => {
+ let orig_ident = items[0].0.prefix.segments.last().unwrap().ident;
+ if orig_ident.name == kw::SelfLower {
+ return;
+ }
+ rename.unwrap_or(orig_ident).name
+ }
+ ast::UseTreeKind::Glob => Symbol::intern("*"),
+ ast::UseTreeKind::Nested(_) => return,
+ };
+
+ cx.struct_span_lint(UNUSED_IMPORT_BRACES, item.span, |lint| {
+ lint.build(fluent::lint::unused_import_braces).set_arg("node", node_name).emit();
+ });
+ }
+ }
+}
+
+impl EarlyLintPass for UnusedImportBraces {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
+ if let ast::ItemKind::Use(ref use_tree) = item.kind {
+ self.check_use_tree(cx, use_tree, item);
+ }
+ }
+}
+
+declare_lint! {
+ /// The `unused_allocation` lint detects unnecessary allocations that can
+ /// be eliminated.
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// #![feature(box_syntax)]
+ /// fn main() {
+ /// let a = (box [1, 2, 3]).len();
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// When a `box` expression is immediately coerced to a reference, then
+ /// the allocation is unnecessary, and a reference (using `&` or `&mut`)
+ /// should be used instead to avoid the allocation.
+ pub(super) UNUSED_ALLOCATION,
+ Warn,
+ "detects unnecessary allocations that can be eliminated"
+}
+
+declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]);
+
+impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
+ fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
+ match e.kind {
+ hir::ExprKind::Box(_) => {}
+ _ => return,
+ }
+
+ for adj in cx.typeck_results().expr_adjustments(e) {
+ if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind {
+ cx.struct_span_lint(UNUSED_ALLOCATION, e.span, |lint| {
+ lint.build(match m {
+ adjustment::AutoBorrowMutability::Not => fluent::lint::unused_allocation,
+ adjustment::AutoBorrowMutability::Mut { .. } => {
+ fluent::lint::unused_allocation_mut
+ }
+ })
+ .emit();
+ });
+ }
+ }
+ }
+}