diff options
Diffstat (limited to 'compiler/rustc_lint/src')
45 files changed, 20686 insertions, 0 deletions
diff --git a/compiler/rustc_lint/src/async_closures.rs b/compiler/rustc_lint/src/async_closures.rs new file mode 100644 index 00000000000..33cc5738262 --- /dev/null +++ b/compiler/rustc_lint/src/async_closures.rs @@ -0,0 +1,129 @@ +use rustc_hir as hir; +use rustc_macros::{LintDiagnostic, Subdiagnostic}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; + +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `closure_returning_async_block` lint detects cases where users + /// write a closure that returns an async block. + /// + /// ### Example + /// + /// ```rust + /// #![warn(closure_returning_async_block)] + /// let c = |x: &str| async {}; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Using an async closure is preferable over a closure that returns an + /// async block, since async closures are less restrictive in how its + /// captures are allowed to be used. + /// + /// For example, this code does not work with a closure returning an async + /// block: + /// + /// ```rust,compile_fail + /// async fn callback(x: &str) {} + /// + /// let captured_str = String::new(); + /// let c = move || async { + /// callback(&captured_str).await; + /// }; + /// ``` + /// + /// But it does work with async closures: + /// + /// ```rust + /// #![feature(async_closure)] + /// + /// async fn callback(x: &str) {} + /// + /// let captured_str = String::new(); + /// let c = async move || { + /// callback(&captured_str).await; + /// }; + /// ``` + pub CLOSURE_RETURNING_ASYNC_BLOCK, + Allow, + "closure that returns `async {}` could be rewritten as an async closure", + @feature_gate = async_closure; +} + +declare_lint_pass!( + /// Lint for potential usages of async closures and async fn trait bounds. + AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK] +); + +impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + let hir::ExprKind::Closure(&hir::Closure { + body, + kind: hir::ClosureKind::Closure, + fn_decl_span, + .. + }) = expr.kind + else { + return; + }; + + let mut body = cx.tcx.hir().body(body).value; + + // Only peel blocks that have no expressions. + while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) = + body.kind + { + body = tail; + } + + let hir::ExprKind::Closure(&hir::Closure { + kind: + hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared( + hir::CoroutineDesugaring::Async, + hir::CoroutineSource::Block, + )), + fn_decl_span: async_decl_span, + .. + }) = body.kind + else { + return; + }; + + let deletion_span = cx.tcx.sess.source_map().span_extend_while_whitespace(async_decl_span); + + cx.tcx.emit_node_span_lint( + CLOSURE_RETURNING_ASYNC_BLOCK, + expr.hir_id, + fn_decl_span, + ClosureReturningAsyncBlock { + async_decl_span, + sugg: AsyncClosureSugg { + deletion_span, + insertion_span: fn_decl_span.shrink_to_lo(), + }, + }, + ); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_closure_returning_async_block)] +struct ClosureReturningAsyncBlock { + #[label] + async_decl_span: Span, + #[subdiagnostic] + sugg: AsyncClosureSugg, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")] +struct AsyncClosureSugg { + #[suggestion_part(code = "")] + deletion_span: Span, + #[suggestion_part(code = "async ")] + insertion_span: Span, +} diff --git a/compiler/rustc_lint/src/async_fn_in_trait.rs b/compiler/rustc_lint/src/async_fn_in_trait.rs new file mode 100644 index 00000000000..6daee95dda6 --- /dev/null +++ b/compiler/rustc_lint/src/async_fn_in_trait.rs @@ -0,0 +1,128 @@ +use crate::lints::AsyncFnInTraitDiag; +use crate::LateContext; +use crate::LateLintPass; +use rustc_hir as hir; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_trait_selection::error_reporting::traits::suggestions::suggest_desugaring_async_fn_to_impl_future_in_trait; + +declare_lint! { + /// The `async_fn_in_trait` lint detects use of `async fn` in the + /// definition of a publicly-reachable trait. + /// + /// ### Example + /// + /// ```rust + /// pub trait Trait { + /// async fn method(&self); + /// } + /// # fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// When `async fn` is used in a trait definition, the trait does not + /// promise that the opaque [`Future`] returned by the associated function + /// or method will implement any [auto traits] such as [`Send`]. This may + /// be surprising and may make the associated functions or methods on the + /// trait less useful than intended. On traits exposed publicly from a + /// crate, this may affect downstream crates whose authors cannot alter + /// the trait definition. + /// + /// For example, this code is invalid: + /// + /// ```rust,compile_fail + /// pub trait Trait { + /// async fn method(&self) {} + /// } + /// + /// fn test<T: Trait>(x: T) { + /// fn spawn<T: Send>(_: T) {} + /// spawn(x.method()); // Not OK. + /// } + /// ``` + /// + /// This lint exists to warn authors of publicly-reachable traits that + /// they may want to consider desugaring the `async fn` to a normal `fn` + /// that returns an opaque `impl Future<..> + Send` type. + /// + /// For example, instead of: + /// + /// ```rust + /// pub trait Trait { + /// async fn method(&self) {} + /// } + /// ``` + /// + /// The author of the trait may want to write: + /// + /// + /// ```rust + /// use core::future::Future; + /// pub trait Trait { + /// fn method(&self) -> impl Future<Output = ()> + Send { async {} } + /// } + /// ``` + /// + /// This still allows the use of `async fn` within impls of the trait. + /// However, it also means that the trait will never be compatible with + /// impls where the returned [`Future`] of the method does not implement + /// `Send`. + /// + /// Conversely, if the trait is used only locally, if it is never used in + /// generic functions, or if it is only used in single-threaded contexts + /// that do not care whether the returned [`Future`] implements [`Send`], + /// then the lint may be suppressed. + /// + /// [`Future`]: https://doc.rust-lang.org/core/future/trait.Future.html + /// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html + /// [auto traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits + pub ASYNC_FN_IN_TRAIT, + Warn, + "use of `async fn` in definition of a publicly-reachable trait" +} + +declare_lint_pass!( + /// Lint for use of `async fn` in the definition of a publicly-reachable + /// trait. + AsyncFnInTrait => [ASYNC_FN_IN_TRAIT] +); + +impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'tcx>) { + if let hir::TraitItemKind::Fn(sig, body) = item.kind + && let hir::IsAsync::Async(async_span) = sig.header.asyncness + { + // RTN can be used to bound `async fn` in traits in a better way than "always" + if cx.tcx.features().return_type_notation { + return; + } + + // Only need to think about library implications of reachable traits + if !cx.tcx.effective_visibilities(()).is_reachable(item.owner_id.def_id) { + return; + } + + let hir::FnRetTy::Return(hir::Ty { kind: hir::TyKind::OpaqueDef(def, ..), .. }) = + sig.decl.output + else { + // This should never happen, but let's not ICE. + return; + }; + let sugg = suggest_desugaring_async_fn_to_impl_future_in_trait( + cx.tcx, + sig, + body, + def.owner_id.def_id, + " + Send", + ); + cx.tcx.emit_node_span_lint( + ASYNC_FN_IN_TRAIT, + item.hir_id(), + async_span, + AsyncFnInTraitDiag { sugg }, + ); + } + } +} diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs new file mode 100644 index 00000000000..485c214ac9d --- /dev/null +++ b/compiler/rustc_lint/src/builtin.rs @@ -0,0 +1,3003 @@ +//! 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::fluent_generated as fluent; +use crate::{ + errors::BuiltinEllipsisInclusiveRangePatterns, + lints::{ + BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDeprecatedAttrLink, + BuiltinDeprecatedAttrLinkSuggestion, BuiltinDeprecatedAttrUsed, BuiltinDerefNullptr, + BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, + BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, + BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, + BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, + BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, + BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasGenericBounds, + BuiltinTypeAliasGenericBoundsSuggestion, BuiltinTypeAliasWhereClause, + BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, + BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, BuiltinUnsafe, + BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, + BuiltinWhileTrue, InvalidAsmLabel, SuggestChangingAssocTypes, + }, + EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext, +}; +use ast::token::TokenKind; +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_errors::{Applicability, LintDiagnostic}; +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, CRATE_DEF_ID}; +use rustc_hir::intravisit::FnKind as HirFnKind; +use rustc_hir::{Body, FnDecl, GenericParamKind, PatKind, PredicateOrigin}; +use rustc_middle::bug; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::print::with_no_trimmed_paths; +use rustc_middle::ty::TypeVisitableExt; +use rustc_middle::ty::Upcast; +use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; +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::Abi; +use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; +use rustc_trait_selection::traits::{self, misc::type_allowed_to_implement_copy}; + +use crate::nonstandard_style::{method_context, MethodLateContext}; + +use std::fmt::Write; + +// hardwired lints from rustc_lint_defs +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 { + #[inline] + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let ast::ExprKind::While(cond, _, label) = &e.kind + && let ast::ExprKind::Lit(token_lit) = pierce_parens(cond).kind + && let token::Lit { kind: token::Bool, symbol: kw::True, .. } = token_lit + && !cond.span.from_expansion() + { + let condition_span = e.span.with_hi(cond.span.hi()); + let replace = format!( + "{}loop", + label.map_or_else(String::new, |label| format!("{}: ", label.ident,)) + ); + cx.emit_span_lint( + WHILE_TRUE, + condition_span, + BuiltinWhileTrue { suggestion: condition_span, replace }, + ); + } + } +} + +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.typeck_results().field_index(fieldpat.hir_id)) + { + cx.emit_span_lint( + NON_SHORTHAND_FIELD_PATTERNS, + fieldpat.span, + BuiltinNonShorthandFieldPatterns { + ident, + suggestion: fieldpat.span, + prefix: binding_annot.prefix_str(), + }, + ); + } + } + } + } + } +} + +declare_lint! { + /// The `unsafe_code` lint catches usage of `unsafe` code and other + /// potentially unsound constructs like `no_mangle`, `export_name`, + /// and `link_section`. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unsafe_code)] + /// fn main() { + /// unsafe { + /// + /// } + /// } + /// + /// #[no_mangle] + /// fn func_0() { } + /// + /// #[export_name = "exported_symbol_name"] + /// pub fn name_in_rust() { } + /// + /// #[no_mangle] + /// #[link_section = ".example_section"] + /// pub static VAR1: u32 = 1; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// This lint is intended to restrict the usage of `unsafe` blocks and other + /// constructs (including, but not limited to `no_mangle`, `link_section` + /// and `export_name` attributes) wrong usage of which causes undefined + /// behavior. + UNSAFE_CODE, + Allow, + "usage of `unsafe` code and other potentially unsound constructs" +} + +declare_lint_pass!(UnsafeCode => [UNSAFE_CODE]); + +impl UnsafeCode { + fn report_unsafe( + &self, + cx: &EarlyContext<'_>, + span: Span, + decorate: impl for<'a> LintDiagnostic<'a, ()>, + ) { + // This comes from a macro that has `#[allow_internal_unsafe]`. + if span.allows_unsafe() { + return; + } + + cx.emit_span_lint(UNSAFE_CODE, span, decorate); + } +} + +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, BuiltinUnsafe::AllowInternalUnsafe); + } + } + + #[inline] + 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, BuiltinUnsafe::UnsafeBlock); + } + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &ast::Item) { + match it.kind { + ast::ItemKind::Trait(box ast::Trait { safety: ast::Safety::Unsafe(_), .. }) => { + self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeTrait); + } + + ast::ItemKind::Impl(box ast::Impl { safety: ast::Safety::Unsafe(_), .. }) => { + self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeImpl); + } + + ast::ItemKind::Fn(..) => { + if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleFn); + } + + if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameFn); + } + + if let Some(attr) = attr::find_by_name(&it.attrs, sym::link_section) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::LinkSectionFn); + } + } + + ast::ItemKind::Static(..) => { + if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleStatic); + } + + if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameStatic); + } + + if let Some(attr) = attr::find_by_name(&it.attrs, sym::link_section) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::LinkSectionStatic); + } + } + + ast::ItemKind::GlobalAsm(..) => { + self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm); + } + + ast::ItemKind::ForeignMod(ForeignMod { safety, .. }) => { + if let Safety::Unsafe(_) = safety { + self.report_unsafe(cx, it.span, BuiltinUnsafe::UnsafeExternBlock); + } + } + + _ => {} + } + } + + fn check_impl_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::Fn(..) = it.kind { + if let Some(attr) = attr::find_by_name(&it.attrs, sym::no_mangle) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::NoMangleMethod); + } + if let Some(attr) = attr::find_by_name(&it.attrs, sym::export_name) { + self.report_unsafe(cx, attr.span, BuiltinUnsafe::ExportNameMethod); + } + } + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fk: FnKind<'_>, span: Span, _: ast::NodeId) { + if let FnKind::Fn( + ctxt, + _, + ast::FnSig { header: ast::FnHeader { safety: ast::Safety::Unsafe(_), .. }, .. }, + _, + _, + body, + ) = fk + { + let decorator = match ctxt { + FnCtxt::Foreign => return, + FnCtxt::Free => BuiltinUnsafe::DeclUnsafeFn, + FnCtxt::Assoc(_) if body.is_none() => BuiltinUnsafe::DeclUnsafeMethod, + FnCtxt::Assoc(_) => BuiltinUnsafe::ImplUnsafeMethod, + }; + self.report_unsafe(cx, span, decorator); + } + } +} + +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; + +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 { + 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; + } + + // 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.effective_visibilities.is_exported(def_id) { + return; + } + } + + let attrs = cx.tcx.hir().attrs(cx.tcx.local_def_id_to_hir_id(def_id)); + let has_doc = attrs.iter().any(has_doc); + if !has_doc { + cx.emit_span_lint( + MISSING_DOCS, + cx.tcx.def_span(def_id), + BuiltinMissingDoc { article, desc }, + ); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for MissingDoc { + 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<'_>) { + // Previously the Impl and Use types have been excluded from missing docs, + // so we will continue to exclude them for compatibility. + // + // The documentation on `ExternCrate` is not used at the moment so no need to warn for it. + if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(_) = + it.kind + { + return; + } + + let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id()); + self.check_missing_docs_attrs(cx, it.owner_id.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.owner_id.to_def_id()); + + self.check_missing_docs_attrs(cx, trait_item.owner_id.def_id, article, desc); + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + let context = method_context(cx, impl_item.owner_id.def_id); + + match context { + // If the method is an impl for a trait, don't doc. + MethodLateContext::TraitImpl => return, + MethodLateContext::TraitAutoImpl => {} + // If the method is an impl for an item with docs_hidden, don't doc. + MethodLateContext::PlainImpl => { + let parent = cx.tcx.hir().get_parent_item(impl_item.hir_id()); + let impl_ty = cx.tcx.type_of(parent).instantiate_identity(); + 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.owner_id.to_def_id()); + self.check_missing_docs_attrs(cx, impl_item.owner_id.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.owner_id.to_def_id()); + self.check_missing_docs_attrs(cx, foreign_item.owner_id.def_id, article, desc); + } + + fn check_field_def(&mut self, cx: &LateContext<'_>, sf: &hir::FieldDef<'_>) { + if !sf.is_positional() { + self.check_missing_docs_attrs(cx, sf.def_id, "a", "struct field") + } + } + + fn check_variant(&mut self, cx: &LateContext<'_>, v: &hir::Variant<'_>) { + self.check_missing_docs_attrs(cx, v.def_id, "a", "variant"); + } +} + +declare_lint! { + /// The `missing_copy_implementations` lint detects potentially-forgotten + /// implementations of [`Copy`] for public types. + /// + /// [`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.effective_visibilities.is_reachable(item.owner_id.def_id) { + return; + } + let (def, ty) = match item.kind { + hir::ItemKind::Struct(_, ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.owner_id); + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) + } + hir::ItemKind::Union(_, ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.owner_id); + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) + } + hir::ItemKind::Enum(_, ast_generics) => { + if !ast_generics.params.is_empty() { + return; + } + let def = cx.tcx.adt_def(item.owner_id); + (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) + } + _ => return, + }; + if def.has_dtor(cx.tcx) { + return; + } + + // If the type contains a raw pointer, it may represent something like a handle, + // and recommending Copy might be a bad idea. + for field in def.all_fields() { + let did = field.did; + if cx.tcx.type_of(did).instantiate_identity().is_unsafe_ptr() { + return; + } + } + if ty.is_copy_modulo_regions(cx.tcx, cx.param_env) { + return; + } + if type_implements_negative_copy_modulo_regions(cx.tcx, ty, cx.param_env) { + return; + } + if def.is_variant_list_non_exhaustive() + || def.variants().iter().any(|variant| variant.is_field_list_non_exhaustive()) + { + return; + } + + // We shouldn't recommend implementing `Copy` on stateful things, + // such as iterators. + if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator) + && cx + .tcx + .infer_ctxt() + .build() + .type_implements_trait(iter_trait, [ty], cx.param_env) + .must_apply_modulo_regions() + { + return; + } + + // Default value of clippy::trivially_copy_pass_by_ref + const MAX_SIZE: u64 = 256; + + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()) { + if size > MAX_SIZE { + return; + } + } + + if type_allowed_to_implement_copy( + cx.tcx, + cx.param_env, + ty, + traits::ObligationCause::misc(item.span, item.owner_id.def_id), + ) + .is_ok() + { + cx.emit_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl); + } + } +} + +/// Check whether a `ty` has a negative `Copy` implementation, ignoring outlives constraints. +fn type_implements_negative_copy_modulo_regions<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> bool { + let trait_ref = ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, None), [ty]); + let pred = ty::TraitPredicate { trait_ref, polarity: ty::PredicatePolarity::Negative }; + let obligation = traits::Obligation { + cause: traits::ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: pred.upcast(tcx), + }; + + tcx.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation) +} + +declare_lint! { + /// The `missing_debug_implementations` lint detects missing + /// implementations of [`fmt::Debug`] for public types. + /// + /// [`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(crate) struct MissingDebugImplementations; + +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.effective_visibilities.is_reachable(item.owner_id.def_id) { + return; + } + + match item.kind { + hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) | hir::ItemKind::Enum(..) => {} + _ => return, + } + + // Avoid listing trait impls if the trait is allowed. + let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()); + if level == Level::Allow { + return; + } + + let Some(debug) = cx.tcx.get_diagnostic_item(sym::Debug) else { return }; + + let has_impl = cx + .tcx + .non_blanket_impls_for_ty(debug, cx.tcx.type_of(item.owner_id).instantiate_identity()) + .next() + .is_some(); + if !has_impl { + cx.emit_span_lint( + MISSING_DEBUG_IMPLEMENTATIONS, + item.span, + BuiltinMissingDebugImpl { tcx: cx.tcx, def_id: debug }, + ); + } + } +} + +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 { + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018), + reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>", + }; +} + +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 { + 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) + }; + cx.emit_span_lint( + ANONYMOUS_PARAMETERS, + arg.pat.span, + BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip }, + ); + } + } + } + } + } +} + +/// 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 + { + let suggestion = match suggestion { + Some(msg) => { + BuiltinDeprecatedAttrLinkSuggestion::Msg { suggestion: attr.span, msg } + } + None => { + BuiltinDeprecatedAttrLinkSuggestion::Default { suggestion: attr.span } + } + }; + cx.emit_span_lint( + DEPRECATED, + attr.span, + BuiltinDeprecatedAttrLink { name, reason, link, suggestion }, + ); + } + return; + } + } + if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) { + cx.emit_span_lint( + DEPRECATED, + attr.span, + BuiltinDeprecatedAttrUsed { + name: pprust::path_to_string(&attr.get_normal_item().path), + suggestion: attr.span, + }, + ); + } + } +} + +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().is_some_and(|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) { + let sub = match attr.kind { + AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => { + BuiltinUnusedDocCommentSub::PlainHelp + } + AttrKind::DocComment(CommentKind::Block, _) => { + BuiltinUnusedDocCommentSub::BlockHelp + } + }; + cx.emit_span_lint( + UNUSED_DOC_COMMENTS, + span, + BuiltinUnusedDocComment { kind: node_kind, label: node_span, sub }, + ); + } + } +} + +impl EarlyLintPass for UnusedDocComment { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { + let kind = match stmt.kind { + ast::StmtKind::Let(..) => "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) { + if let Some(body) = &arm.body { + let arm_span = arm.pat.span.with_hi(body.span.hi()); + warn_if_doc(cx, arm_span, "match arms", &arm.attrs); + } + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &ast::Pat) { + if let ast::PatKind::Struct(_, _, fields, _) = &pat.kind { + for field in fields { + warn_if_doc(cx, field.span, "pattern fields", &field.attrs); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + warn_if_doc(cx, expr.span, "expressions", &expr.attrs); + + if let ExprKind::Struct(s) = &expr.kind { + for field in &s.fields { + warn_if_doc(cx, field.span, "expression fields", &field.attrs); + } + } + } + + fn check_generic_param(&mut self, cx: &EarlyContext<'_>, param: &ast::GenericParam) { + warn_if_doc(cx, param.ident.span, "generic parameters", ¶m.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.emit_span_lint( + NO_MANGLE_GENERIC_ITEMS, + span, + BuiltinNoMangleGeneric { suggestion: no_mangle_attr.span }, + ); + break; + } + } + } + }; + match it.kind { + hir::ItemKind::Fn(.., generics, _) => { + if let Some(no_mangle_attr) = attr::find_by_name(attrs, sym::no_mangle) { + check_no_mangle_on_generic_fn(no_mangle_attr, None, generics, it.span); + } + } + hir::ItemKind::Const(..) => { + if attr::contains_name(attrs, sym::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 suggestion = it.span.with_hi(BytePos(it.span.lo().0 + start + 5)); + + // Const items do not refer to a particular location in memory, and therefore + // don't have anything to attach a symbol to + cx.emit_span_lint( + NO_MANGLE_CONST_ITEMS, + it.span, + BuiltinConstNoMangle { suggestion }, + ); + } + } + hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => { + for it in *items { + if let hir::AssocItemKind::Fn { .. } = it.kind { + if let Some(no_mangle_attr) = + attr::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.owner_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_mutbl), &ty::Ref(_, _, to_mutbl))) = + get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind())) + { + if from_mutbl < to_mutbl { + cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes); + } + } + + 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, sym::transmute) + } + } +} + +declare_lint! { + /// The `unstable_features` lint detects uses of `#![feature]`. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unstable_features)] + /// #![feature(test)] + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In larger nightly-based projects which + /// + /// * consist of a multitude of crates where a subset of crates has to compile on + /// stable either unconditionally or depending on a `cfg` flag to for example + /// allow stable users to depend on them, + /// * don't use nightly for experimental features but for, e.g., unstable options only, + /// + /// this lint may come in handy to enforce policies of these kinds. + UNSTABLE_FEATURES, + Allow, + "enabling unstable features" +} + +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) + && let Some(items) = attr.meta_item_list() + { + for item in items { + cx.emit_span_lint(UNSTABLE_FEATURES, item.span(), BuiltinUnstableFeatures); + } + } + } +} + +declare_lint! { + /// The `ungated_async_fn_track_caller` lint warns when the + /// `#[track_caller]` attribute is used on an async function + /// without enabling the corresponding unstable feature flag. + /// + /// ### Example + /// + /// ```rust + /// #[track_caller] + /// async fn foo() {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The attribute must be used in conjunction with the + /// [`async_fn_track_caller` feature flag]. Otherwise, the `#[track_caller]` + /// annotation will function as a no-op. + /// + /// [`async_fn_track_caller` feature flag]: https://doc.rust-lang.org/beta/unstable-book/language-features/async-fn-track-caller.html + UNGATED_ASYNC_FN_TRACK_CALLER, + Warn, + "enabling track_caller on an async fn is a no-op unless the async_fn_track_caller feature is enabled" +} + +declare_lint_pass!( + /// Explains corresponding feature flag must be enabled for the `#[track_caller]` attribute to + /// do anything + UngatedAsyncFnTrackCaller => [UNGATED_ASYNC_FN_TRACK_CALLER] +); + +impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fn_kind: HirFnKind<'_>, + _: &'tcx FnDecl<'_>, + _: &'tcx Body<'_>, + span: Span, + def_id: LocalDefId, + ) { + if fn_kind.asyncness().is_async() + && !cx.tcx.features().async_fn_track_caller + // Now, check if the function has the `#[track_caller]` attribute + && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller) + { + cx.emit_span_lint( + UNGATED_ASYNC_FN_TRACK_CALLER, + attr.span, + BuiltinUngatedAsyncFnTrackCaller { label: span, session: &cx.tcx.sess }, + ); + } + } +} + +declare_lint! { + /// The `unreachable_pub` lint triggers for `pub` items not reachable from other crates - that + /// means neither directly accessible, nor reexported, nor leaked through things like return + /// types. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unreachable_pub)] + /// mod foo { + /// pub mod bar { + /// + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The `pub` keyword both expresses an intent for an item to be publicly available, and also + /// signals to the compiler to make the item publicly accessible. The intent can only be + /// satisfied, however, if all items which contain this item are *also* publicly accessible. + /// Thus, this lint serves to identify situations where the intent does not match the reality. + /// + /// If you wish the item to be accessible elsewhere within the crate, but not outside it, the + /// `pub(crate)` visibility is recommended to be used instead. This 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.effective_visibilities.is_reachable(def_id) + { + if vis_span.from_expansion() { + applicability = Applicability::MaybeIncorrect; + } + let def_span = cx.tcx.def_span(def_id); + cx.emit_span_lint( + UNREACHABLE_PUB, + def_span, + BuiltinUnreachablePub { + what, + suggestion: (vis_span, applicability), + help: exportable.then_some(()), + }, + ); + } + } +} + +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.owner_id.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.owner_id.def_id, foreign_item.vis_span, true); + } + + fn check_field_def(&mut self, _cx: &LateContext<'_>, _field: &hir::FieldDef<'_>) { + // - If an ADT definition is reported then we don't need to check fields + // (as it would add unnecessary complexity to the source code, the struct + // definition is in the immediate proximity to give the "real" visibility). + // - If an ADT is not reported because it's not `pub` - we don't need to + // check fields. + // - If an ADT is not reported because it's reachable - we also don't need + // to check fields because then they are reachable by construction if they + // are pub. + // + // Therefore in no case we check the fields. + // + // cf. https://github.com/rust-lang/rust/pull/126013#issuecomment-2152839205 + // cf. https://github.com/rust-lang/rust/pull/126040#issuecomment-2152944506 + } + + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) { + // Only lint inherent impl items. + if cx.tcx.associated_item(impl_item.owner_id).trait_item_def_id.is_none() { + self.perform_lint(cx, "item", impl_item.owner_id.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 { + pub(crate) fn is_type_variable_assoc(qpath: &hir::QPath<'_>) -> bool { + match *qpath { + hir::QPath::TypeRelative(ty, _) => { + // If this is a type variable, we found a `T::Assoc`. + match ty.kind { + hir::TyKind::Path(hir::QPath::Resolved(None, path)) => { + matches!(path.res, Res::Def(DefKind::TyParam, _)) + } + _ => false, + } + } + hir::QPath::Resolved(..) | hir::QPath::LangItem(..) => false, + } + } +} + +impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { + fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { + let hir::ItemKind::TyAlias(hir_ty, type_alias_generics) = &item.kind else { return }; + + // Bounds of lazy type aliases and TAITs are respected. + if cx.tcx.type_alias_is_lazy(item.owner_id) { + return; + } + + let ty = cx.tcx.type_of(item.owner_id).skip_binder(); + if ty.has_inherent_projections() { + // Bounds of type aliases that contain opaque types or inherent projections are + // respected. E.g: `type X = impl Trait;`, `type X = (impl Trait, Y);`, `type X = + // Type::Inherent;`. + 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() { + let sub = (!suggested_changing_assoc_types).then(|| { + suggested_changing_assoc_types = true; + SuggestChangingAssocTypes { ty: hir_ty } + }); + cx.emit_span_lint( + TYPE_ALIAS_BOUNDS, + where_spans, + BuiltinTypeAliasWhereClause { + suggestion: type_alias_generics.where_clause_span, + sub, + }, + ); + } + + if !inline_spans.is_empty() { + let suggestion = BuiltinTypeAliasGenericBoundsSuggestion { suggestions: inline_sugg }; + let sub = (!suggested_changing_assoc_types).then(|| { + suggested_changing_assoc_types = true; + SuggestChangingAssocTypes { ty: hir_ty } + }); + cx.emit_span_lint( + TYPE_ALIAS_BOUNDS, + inline_spans, + BuiltinTypeAliasGenericBounds { suggestion, sub }, + ); + } + } +} + +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::ClauseKind; + + if cx.tcx.features().trivial_bounds { + let predicates = cx.tcx.predicates_of(item.owner_id); + for &(predicate, span) in predicates.predicates { + let predicate_kind_name = match predicate.kind().skip_binder() { + ClauseKind::Trait(..) => "trait", + ClauseKind::TypeOutlives(..) | + ClauseKind::RegionOutlives(..) => "lifetime", + + // `ConstArgHasType` is never global as `ct` is always a param + ClauseKind::ConstArgHasType(..) + // Ignore projections, as they can only be global + // if the trait bound is global + | ClauseKind::Projection(..) + // Ignore bounds that a user can't type + | ClauseKind::WellFormed(..) + // FIXME(generic_const_exprs): `ConstEvaluatable` can be written + | ClauseKind::ConstEvaluatable(..) => continue, + }; + if predicate.is_global() { + cx.emit_span_lint( + TRIVIAL_BOUNDS, + span, + BuiltinTrivialBounds { predicate_kind_name, predicate }, + ); + } + } + } + } +} + +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, + 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 { + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2021), + reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/warnings-promoted-to-error.html>", + }; +} + +#[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 (parentheses, endpoints) = match &pat.kind { + PatKind::Ref(subpat, _) => (true, matches_ellipsis_pat(subpat)), + _ => (false, matches_ellipsis_pat(pat)), + }; + + if let Some((start, end, join)) = endpoints { + if parentheses { + 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 { + cx.sess().dcx().emit_err(BuiltinEllipsisInclusiveRangePatterns { + span: pat.span, + suggestion: pat.span, + replace, + }); + } else { + cx.emit_span_lint( + ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + pat.span, + BuiltinEllipsisInclusiveRangePatternsLint::Parenthesise { + suggestion: pat.span, + replace, + }, + ); + } + } else { + let replace = "..="; + if join.edition() >= Edition::Edition2021 { + cx.sess().dcx().emit_err(BuiltinEllipsisInclusiveRangePatterns { + span: pat.span, + suggestion: join, + replace: replace.to_string(), + }); + } else { + cx.emit_span_lint( + ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, + join, + BuiltinEllipsisInclusiveRangePatternsLint::NonParenthesise { + suggestion: join, + }, + ); + } + }; + } + } + + 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 `keyword_idents_2018` lint detects edition keywords being used as an + /// identifier. + /// + /// ### Example + /// + /// ```rust,edition2015,compile_fail + /// #![deny(keyword_idents_2018)] + /// // 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_2018, + Allow, + "detects edition keywords being used as an identifier", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018), + reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>", + }; +} + +declare_lint! { + /// The `keyword_idents_2024` lint detects edition keywords being used as an + /// identifier. + /// + /// ### Example + /// + /// ```rust,edition2015,compile_fail + /// #![deny(keyword_idents_2024)] + /// // edition 2015 + /// fn gen() {} + /// ``` + /// + /// {{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#gen`, 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_2024, + Allow, + "detects edition keywords being used as an identifier", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024), + reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>", + }; +} + +declare_lint_pass!( + /// Check for uses of edition keywords used as an identifier. + KeywordIdents => [KEYWORD_IDENTS_2018, KEYWORD_IDENTS_2024] +); + +struct UnderMacro(bool); + +impl KeywordIdents { + fn check_tokens(&mut self, cx: &EarlyContext<'_>, tokens: &TokenStream) { + // Check if the preceding token is `$`, because we want to allow `$async`, etc. + let mut prev_dollar = false; + for tt in tokens.trees() { + match tt { + // Only report non-raw idents. + TokenTree::Token(token, _) => { + if let Some((ident, token::IdentIsRaw::No)) = token.ident() { + if !prev_dollar { + self.check_ident_token(cx, UnderMacro(true), ident); + } + } else if token.kind == TokenKind::Dollar { + prev_dollar = true; + continue; + } + } + TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), + } + prev_dollar = false; + } + } + + fn check_ident_token( + &mut self, + cx: &EarlyContext<'_>, + UnderMacro(under_macro): UnderMacro, + ident: Ident, + ) { + let (lint, edition) = match ident.name { + kw::Async | kw::Await | kw::Try => (KEYWORD_IDENTS_2018, 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 => (KEYWORD_IDENTS_2018, Edition::Edition2018), + + kw::Gen => (KEYWORD_IDENTS_2024, Edition::Edition2024), + + _ => return, + }; + + // Don't lint `r#foo`. + if ident.span.edition() >= edition + || cx.sess().psess.raw_identifier_spans.contains(ident.span) + { + return; + } + + cx.emit_span_lint( + lint, + ident.span, + BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span }, + ); + } +} + +impl EarlyLintPass for KeywordIdents { + fn check_mac_def(&mut self, cx: &EarlyContext<'_>, mac_def: &ast::MacroDef) { + self.check_tokens(cx, &mac_def.body.tokens); + } + fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { + self.check_tokens(cx, &mac.args.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>( + tcx: TyCtxt<'tcx>, + inferred_outlives: &'tcx [(ty::Clause<'tcx>, Span)], + item: DefId, + lifetime: DefId, + ) -> Vec<ty::Region<'tcx>> { + let item_generics = tcx.generics_of(item); + + inferred_outlives + .iter() + .filter_map(|(clause, _)| match clause.kind().skip_binder() { + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { + ty::ReEarlyParam(ebr) + if item_generics.region_param(ebr, tcx).def_id == lifetime => + { + Some(b) + } + _ => None, + }, + _ => None, + }) + .collect() + } + + fn lifetimes_outliving_type<'tcx>( + inferred_outlives: &'tcx [(ty::Clause<'tcx>, Span)], + index: u32, + ) -> Vec<ty::Region<'tcx>> { + inferred_outlives + .iter() + .filter_map(|(clause, _)| match clause.kind().skip_binder() { + ty::ClauseKind::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>], + predicate_span: Span, + item: DefId, + ) -> Vec<(usize, Span)> { + use rustc_middle::middle::resolve_bound_vars::ResolvedArg; + + let item_generics = tcx.generics_of(item); + + bounds + .iter() + .enumerate() + .filter_map(|(i, bound)| { + let hir::GenericBound::Outlives(lifetime) = bound else { + return None; + }; + + let is_inferred = match tcx.named_bound_var(lifetime.hir_id) { + Some(ResolvedArg::EarlyBound(def_id)) => inferred_outlives + .iter() + .any(|r| matches!(**r, ty::ReEarlyParam(ebr) if { item_generics.region_param(ebr, tcx).def_id == def_id })), + _ => false, + }; + + if !is_inferred { + return None; + } + + let span = bound.span().find_ancestor_inside(predicate_span)?; + if in_external_macro(tcx.sess, span) { + return None; + } + + Some((i, 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_bound_vars::ResolvedArg; + + let def_id = item.owner_id.def_id; + if let hir::ItemKind::Struct(_, hir_generics) + | hir::ItemKind::Enum(_, hir_generics) + | hir::ItemKind::Union(_, 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 num_where_predicates = hir_generics + .predicates + .iter() + .filter(|predicate| predicate.in_where_clause()) + .count(); + + let mut bound_count = 0; + let mut lint_spans = Vec::new(); + let mut where_lint_spans = Vec::new(); + let mut dropped_where_predicate_count = 0; + for (i, where_predicate) in hir_generics.predicates.iter().enumerate() { + let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = + match where_predicate { + hir::WherePredicate::RegionPredicate(predicate) => { + if let Some(ResolvedArg::EarlyBound(region_def_id)) = + cx.tcx.named_bound_var(predicate.lifetime.hir_id) + { + ( + Self::lifetimes_outliving_lifetime( + cx.tcx, + inferred_outlives, + item.owner_id.to_def_id(), + region_def_id, + ), + &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, 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, + predicate_span, + item.owner_id.to_def_id(), + ); + bound_count += bound_spans.len(); + + let drop_predicate = bound_spans.len() == bounds.len(); + if drop_predicate && in_where_clause { + dropped_where_predicate_count += 1; + } + + if drop_predicate { + if !in_where_clause { + lint_spans.push(predicate_span); + } else if predicate_span.from_expansion() { + // Don't try to extend the span if it comes from a macro expansion. + where_lint_spans.push(predicate_span); + } else if i + 1 < num_where_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(); + if next_predicate_span.from_expansion() { + where_lint_spans.push(predicate_span); + } else { + where_lint_spans + .push(predicate_span.to(next_predicate_span.shrink_to_lo())); + } + } else { + // Eat the optional trailing comma after the last predicate. + let where_span = hir_generics.where_clause_span; + if where_span.from_expansion() { + where_lint_spans.push(predicate_span); + } else { + where_lint_spans.push(predicate_span.to(where_span.shrink_to_hi())); + } + } + } else { + where_lint_spans.extend(self.consolidate_outlives_bound_spans( + predicate_span.shrink_to_lo(), + bounds, + bound_spans, + )); + } + } + + // If all predicates in where clause are inferable, drop the entire clause + // (including the `where`) + if hir_generics.has_where_clause_predicates + && dropped_where_predicate_count == num_where_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) + }; + + // Due to macro expansions, the `full_where_span` might not actually contain all + // predicates. + if where_lint_spans.iter().all(|&sp| full_where_span.contains(sp)) { + lint_spans.push(full_where_span); + } else { + lint_spans.extend(where_lint_spans); + } + } else { + lint_spans.extend(where_lint_spans); + } + + if !lint_spans.is_empty() { + // Do not automatically delete outlives requirements from macros. + let applicability = if lint_spans.iter().all(|sp| sp.can_be_used_for_suggestions()) + { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + + // Due to macros, there might be several predicates with the same span + // and we only want to suggest removing them once. + lint_spans.sort_unstable(); + lint_spans.dedup(); + + cx.emit_span_lint( + EXPLICIT_OUTLIVES_REQUIREMENTS, + lint_spans.clone(), + BuiltinExplicitOutlives { + count: bound_count, + suggestion: BuiltinExplicitOutlivesSuggestion { + spans: lint_spans, + applicability, + }, + }, + ); + } + } + } +} + +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! { + /// The `internal_features` lint detects unstable features enabled with + /// the [`feature` attribute] that are internal to the compiler or standard + /// library. + /// + /// [`feature` attribute]: https://doc.rust-lang.org/nightly/unstable-book/ + /// + /// ### Example + /// + /// ```rust + /// #![feature(rustc_attrs)] + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// These features are an implementation detail of the compiler and standard + /// library and are not supposed to be used in user code. + pub INTERNAL_FEATURES, + Warn, + "internal features are not supposed to be used" +} + +declare_lint_pass!( + /// Check for used feature gates in `INCOMPLETE_FEATURES` in `rustc_feature/src/unstable.rs`. + IncompleteInternalFeatures => [INCOMPLETE_FEATURES, INTERNAL_FEATURES] +); + +impl EarlyLintPass for IncompleteInternalFeatures { + fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) { + let features = cx.builder.features(); + 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) || features.internal(name)) + .for_each(|(&name, &span)| { + if features.incomplete(name) { + let note = rustc_feature::find_feature_issue(name, GateIssue::Language) + .map(|n| BuiltinFeatureIssueNote { n }); + let help = + HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); + + cx.emit_span_lint( + INCOMPLETE_FEATURES, + span, + BuiltinIncompleteFeatures { name, note, help }, + ); + } else { + cx.emit_span_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name }); + } + }); + } +} + +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]); + +/// Information about why a type cannot be initialized this way. +pub struct InitError { + pub(crate) message: String, + /// Spans from struct fields and similar that can be obtained from just the type. + pub(crate) span: Option<Span>, + /// Used to report a trace through adts. + pub(crate) nested: Option<Box<InitError>>, +} +impl InitError { + fn spanned(self, span: Span) -> InitError { + Self { span: Some(span), ..self } + } + + fn nested(self, nested: impl Into<Option<InitError>>) -> InitError { + assert!(self.nested.is_none()); + Self { nested: nested.into().map(Box::new), ..self } + } +} + +impl<'a> From<&'a str> for InitError { + fn from(s: &'a str) -> Self { + s.to_owned().into() + } +} +impl From<String> for InitError { + fn from(message: String) -> Self { + Self { message, span: None, nested: None } + } +} + +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, + } + + /// 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(path_expr, 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(_, receiver, ..) = 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(path_expr, _) = receiver.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 + } + + fn variant_find_init_error<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + variant: &VariantDef, + args: ty::GenericArgsRef<'tcx>, + descr: &str, + init: InitKind, + ) -> Option<InitError> { + let mut field_err = variant.fields.iter().find_map(|field| { + ty_find_init_error(cx, field.ty(cx.tcx, args), init).map(|mut err| { + if !field.did.is_local() { + err + } else if err.span.is_none() { + err.span = Some(cx.tcx.def_span(field.did)); + write!(&mut err.message, " (in this {descr})").unwrap(); + err + } else { + InitError::from(format!("in this {descr}")) + .spanned(cx.tcx.def_span(field.did)) + .nested(err) + } + }) + }); + + // Check if this ADT has a constrained layout (like `NonNull` and friends). + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) { + if let Abi::Scalar(scalar) | Abi::ScalarPair(scalar, _) = &layout.abi { + let range = scalar.valid_range(cx); + let msg = if !range.contains(0) { + "must be non-null" + } else if init == InitKind::Uninit && !scalar.is_always_valid(cx) { + // Prefer reporting on the fields over the entire struct for uninit, + // as the information bubbles out and it may be unclear why the type can't + // be null from just its outside signature. + + "must be initialized inside its custom valid range" + } else { + return field_err; + }; + if let Some(field_err) = &mut field_err { + // Most of the time, if the field error is the same as the struct error, + // the struct error only happens because of the field error. + if field_err.message.contains(msg) { + field_err.message = format!("because {}", field_err.message); + } + } + return Some(InitError::from(format!("`{ty}` {msg}")).nested(field_err)); + } + } + field_err + } + + /// 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> { + let ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty); + + use rustc_type_ir::TyKind::*; + match ty.kind() { + // Primitive types that don't like 0 as a value. + Ref(..) => Some("references must be non-null".into()), + Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()), + FnPtr(..) => Some("function pointers must be non-null".into()), + Never => Some("the `!` type has no valid value".into()), + RawPtr(ty, _) if matches!(ty.kind(), Dynamic(..)) => + // raw ptr to dyn Trait + { + Some("the vtable of a wide raw pointer must be non-null".into()) + } + // Primitive types with other constraints. + Bool if init == InitKind::Uninit => { + Some("booleans must be either `true` or `false`".into()) + } + Char if init == InitKind::Uninit => { + Some("characters must be a valid Unicode codepoint".into()) + } + Int(_) | Uint(_) if init == InitKind::Uninit => { + Some("integers must be initialized".into()) + } + Float(_) if init == InitKind::Uninit => Some("floats must be initialized".into()), + RawPtr(_, _) if init == InitKind::Uninit => { + Some("raw pointers must be initialized".into()) + } + // Recurse and checks for some compound types. (but not unions) + Adt(adt_def, args) if !adt_def.is_union() => { + // Handle structs. + if adt_def.is_struct() { + return variant_find_init_error( + cx, + ty, + adt_def.non_enum_variant(), + args, + "struct field", + init, + ); + } + // And now, enums. + let span = cx.tcx.def_span(adt_def.did()); + let mut potential_variants = adt_def.variants().iter().filter_map(|variant| { + let definitely_inhabited = match variant + .inhabited_predicate(cx.tcx, *adt_def) + .instantiate(cx.tcx, args) + .apply_any_module(cx.tcx, cx.param_env) + { + // Entirely skip uninhabited variants. + Some(false) => return None, + // Forward the others, but remember which ones are definitely inhabited. + Some(true) => true, + None => false, + }; + Some((variant, definitely_inhabited)) + }); + let Some(first_variant) = potential_variants.next() else { + return Some( + InitError::from("enums with no inhabited variants have no valid value") + .spanned(span), + ); + }; + // So we have at least one potentially inhabited variant. Might we have two? + let Some(second_variant) = potential_variants.next() else { + // There is only one potentially inhabited variant. So we can recursively + // check that variant! + return variant_find_init_error( + cx, + ty, + first_variant.0, + args, + "field of the only potentially inhabited enum variant", + init, + ); + }; + // So we have at least two potentially inhabited variants. If we can prove that + // we have at least two *definitely* inhabited variants, then we have a tag and + // hence leaving this uninit is definitely disallowed. (Leaving it zeroed could + // be okay, depending on which variant is encoded as zero tag.) + if init == InitKind::Uninit { + let definitely_inhabited = (first_variant.1 as usize) + + (second_variant.1 as usize) + + potential_variants + .filter(|(_variant, definitely_inhabited)| *definitely_inhabited) + .count(); + if definitely_inhabited > 1 { + return Some(InitError::from( + "enums with multiple inhabited variants have to be initialized to a variant", + ).spanned(span)); + } + } + // We couldn't find anything wrong here. + 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_target_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(err) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) { + let msg = match init { + InitKind::Zeroed => fluent::lint_builtin_unpermitted_type_init_zeroed, + InitKind::Uninit => fluent::lint_builtin_unpermitted_type_init_uninit, + }; + let sub = BuiltinUnpermittedTypeInitSub { err }; + cx.emit_span_lint( + INVALID_VALUE, + expr.span, + BuiltinUnpermittedTypeInit { + msg, + ty: conjured_ty, + label: expr.span, + sub, + tcx: cx.tcx, + }, + ); + } + } + } +} + +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(expr, 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(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(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.emit_span_lint( + DEREF_NULLPTR, + expr.span, + BuiltinDerefNullptr { label: expr.span }, + ); + } + } + } +} + +declare_lint! { + /// The `named_asm_labels` lint detects the use of named labels in the + /// inline `asm!` macro. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(asm_experimental_arch)] + /// 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! { + /// The `binary_asm_labels` lint detects the use of numeric labels containing only binary + /// digits in the inline `asm!` macro. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(asm_experimental_arch)] + /// use std::arch::asm; + /// + /// fn main() { + /// unsafe { + /// asm!("0: jmp 0b"); + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// A [LLVM bug] causes this code to fail to compile because it interprets the `0b` as a binary + /// literal instead of a reference to the previous local label `0`. Note that even though the + /// bug is marked as fixed, it only fixes a specific usage of intel syntax within standalone + /// files, not inline assembly. To work around this bug, don't use labels that could be + /// confused with a binary literal. + /// + /// See the explanation in [Rust By Example] for more details. + /// + /// [LLVM bug]: https://bugs.llvm.org/show_bug.cgi?id=36144 + /// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels + pub BINARY_ASM_LABELS, + Deny, + "labels in inline assembly containing only 0 or 1 digits", +} + +declare_lint_pass!(AsmLabels => [NAMED_ASM_LABELS, BINARY_ASM_LABELS]); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AsmLabelKind { + Named, + FormatArg, + Binary, +} + +impl<'tcx> LateLintPass<'tcx> for AsmLabels { + 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, options, .. }), + .. + } = expr + { + // asm with `options(raw)` does not do replacement with `{` and `}`. + let raw = options.contains(InlineAsmOptions::RAW); + + 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 + }; + + // diagnostics are emitted per-template, so this is created here as opposed to the outer loop + let mut spans = 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]); + + // In this loop, if there is ever a non-label, no labels can come after it. + let mut start_idx = 0; + 'label_loop: for (idx, _) in statement.match_indices(':') { + let possible_label = statement[start_idx..idx].trim(); + let mut chars = possible_label.chars(); + + let Some(start) = chars.next() else { + // Empty string means a leading ':' in this section, which is not a + // label. + break 'label_loop; + }; + + // Whether a { bracket has been seen and its } hasn't been found yet. + let mut in_bracket = false; + let mut label_kind = AsmLabelKind::Named; + + // A label can also start with a format arg, if it's not a raw asm block. + if !raw && start == '{' { + in_bracket = true; + label_kind = AsmLabelKind::FormatArg; + } else if matches!(start, '0' | '1') { + // Binary labels have only the characters `0` or `1`. + label_kind = AsmLabelKind::Binary; + } else if !(start.is_ascii_alphabetic() || matches!(start, '.' | '_')) { + // Named labels start with ASCII letters, `.` or `_`. + // anything else is not a label + break 'label_loop; + } + + for c in chars { + // Inside a template format arg, any character is permitted for the + // puproses of label detection because we assume that it can be + // replaced with some other valid label string later. `options(raw)` + // asm blocks cannot have format args, so they are excluded from this + // special case. + if !raw && in_bracket { + if c == '{' { + // Nested brackets are not allowed in format args, this cannot + // be a label. + break 'label_loop; + } + + if c == '}' { + // The end of the format arg. + in_bracket = false; + } + } else if !raw && c == '{' { + // Start of a format arg. + in_bracket = true; + label_kind = AsmLabelKind::FormatArg; + } else { + let can_continue = match label_kind { + // Format arg labels are considered to be named labels for the purposes + // of continuing outside of their {} pair. + AsmLabelKind::Named | AsmLabelKind::FormatArg => { + c.is_ascii_alphanumeric() || matches!(c, '_' | '$') + } + AsmLabelKind::Binary => matches!(c, '0' | '1'), + }; + + if !can_continue { + // The potential label had an invalid character inside it, it + // cannot be a label. + break 'label_loop; + } + } + } + + // If all characters passed the label checks, this is a label. + spans.push((find_label_span(possible_label), label_kind)); + start_idx = idx + 1; + } + } + + for (span, label_kind) in spans { + let missing_precise_span = span.is_none(); + let span = span.unwrap_or(*template_span); + match label_kind { + AsmLabelKind::Named => { + cx.emit_span_lint( + NAMED_ASM_LABELS, + span, + InvalidAsmLabel::Named { missing_precise_span }, + ); + } + AsmLabelKind::FormatArg => { + cx.emit_span_lint( + NAMED_ASM_LABELS, + span, + InvalidAsmLabel::FormatArg { missing_precise_span }, + ); + } + AsmLabelKind::Binary => { + // the binary asm issue only occurs when using intel syntax + if !options.contains(InlineAsmOptions::ATT_SYNTAX) { + cx.emit_span_lint( + BINARY_ASM_LABELS, + span, + InvalidAsmLabel::Binary { missing_precise_span, span }, + ) + } + } + }; + } + } + } + } +} + +declare_lint! { + /// The `special_module_name` lint detects module + /// declarations for files that have a special meaning. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// mod lib; + /// + /// fn main() { + /// lib::run(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Cargo recognizes `lib.rs` and `main.rs` as the root of a + /// library or binary crate, so declaring them as modules + /// will lead to miscompilation of the crate unless configured + /// explicitly. + /// + /// To access a library from a binary target within the same crate, + /// use `your_crate_name::` as the path instead of `lib::`: + /// + /// ```rust,compile_fail + /// // bar/src/lib.rs + /// fn run() { + /// // ... + /// } + /// + /// // bar/src/main.rs + /// fn main() { + /// bar::run(); + /// } + /// ``` + /// + /// Binary targets cannot be used as libraries and so declaring + /// one as a module is not allowed. + pub SPECIAL_MODULE_NAME, + Warn, + "module declarations for files with a special meaning", +} + +declare_lint_pass!(SpecialModuleName => [SPECIAL_MODULE_NAME]); + +impl EarlyLintPass for SpecialModuleName { + fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) { + for item in &krate.items { + if let ast::ItemKind::Mod( + _, + ast::ModKind::Unloaded | ast::ModKind::Loaded(_, ast::Inline::No, _), + ) = item.kind + { + if item.attrs.iter().any(|a| a.has_name(sym::path)) { + continue; + } + + match item.ident.name.as_str() { + "lib" => cx.emit_span_lint( + SPECIAL_MODULE_NAME, + item.span, + BuiltinSpecialModuleNameUsed::Lib, + ), + "main" => cx.emit_span_lint( + SPECIAL_MODULE_NAME, + item.span, + BuiltinSpecialModuleNameUsed::Main, + ), + _ => continue, + } + } + } + } +} diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs new file mode 100644 index 00000000000..9f0f116cbd0 --- /dev/null +++ b/compiler/rustc_lint/src/context.rs @@ -0,0 +1,1023 @@ +//! 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_data_structures::fx::FxIndexMap; +use rustc_data_structures::sync; +use rustc_data_structures::unord::UnordMap; +use rustc_errors::{Diag, LintDiagnostic, MultiSpan}; +use rustc_feature::Features; +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::bug; +use rustc_middle::middle::privacy::EffectiveVisibilities; +use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; +use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError, PrintTraitRefExt as _}; +use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt}; +use rustc_session::lint::{BuiltinLintDiag, LintExpectationId}; +use rustc_session::lint::{FutureIncompatibleInfo, Level, Lint, LintBuffer, LintId}; +use rustc_session::{LintStoreMarker, Session}; +use rustc_span::edit_distance::find_best_match_for_names; +use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::Span; +use rustc_target::abi; +use std::cell::Cell; +use std::iter; +use std::slice; +use tracing::debug; + +mod diagnostics; + +type EarlyLintPassFactory = dyn Fn() -> EarlyLintPassObject + sync::DynSend + sync::DynSync; +type LateLintPassFactory = + dyn for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + sync::DynSend + sync::DynSync; + +/// 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<EarlyLintPassFactory>>, + pub early_passes: Vec<Box<EarlyLintPassFactory>>, + pub late_passes: Vec<Box<LateLintPassFactory>>, + /// This is unique in that we construct them per-module, so not once. + pub late_module_passes: Vec<Box<LateLintPassFactory>>, + + /// Lints indexed by name. + by_name: UnordMap<String, TargetLint>, + + /// Map of registered lint groups to what lints they expand to. + lint_groups: FxIndexMap<&'static str, LintGroup>, +} + +impl LintStoreMarker for LintStore {} + +/// 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>, + is_externally_loaded: 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, bool)>), + /// The lint refers to a tool that has not been registered. + NoTool, + /// The lint has been renamed to a new name. + Renamed(String), + /// The lint has been removed due to the given reason. + Removed(String), + + /// The lint is from a tool. The `LintId` will be returned as if it were a + /// rustc lint. The `Option<String>` indicates if the lint has been + /// renamed. + Tool(&'a [LintId], Option<String>), + + /// The lint is from a tool. 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`. + MissingTool, +} + +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 { + self.lint_groups + .iter() + .filter(|(_, LintGroup { depr, .. })| { + // Don't display deprecated lint groups. + depr.is_none() + }) + .map(|(k, LintGroup { lint_ids, is_externally_loaded, .. })| { + (*k, lint_ids.clone(), *is_externally_loaded) + }) + } + + pub fn register_early_pass( + &mut self, + pass: impl Fn() -> EarlyLintPassObject + 'static + sync::DynSend + sync::DynSync, + ) { + 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::DynSend + sync::DynSync, + ) { + self.pre_expansion_passes.push(Box::new(pass)); + } + + pub fn register_late_pass( + &mut self, + pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + + 'static + + sync::DynSend + + sync::DynSync, + ) { + self.late_passes.push(Box::new(pass)); + } + + pub fn register_late_mod_pass( + &mut self, + pass: impl for<'tcx> Fn(TyCtxt<'tcx>) -> LateLintPassObject<'tcx> + + 'static + + sync::DynSend + + sync::DynSync, + ) { + 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![], + is_externally_loaded: lint.is_externally_loaded, + 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![], + is_externally_loaded: lint.is_externally_loaded, + 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![], + is_externally_loaded: false, + depr: Some(LintAlias { name: lint_name, silent: true }), + }, + ); + } + + pub fn register_group( + &mut self, + is_externally_loaded: bool, + name: &'static str, + deprecated_name: Option<&'static str>, + to: Vec<LintId>, + ) { + let new = self + .lint_groups + .insert(name, LintGroup { lint_ids: to, is_externally_loaded, depr: None }) + .is_none(); + if let Some(deprecated) = deprecated_name { + self.lint_groups.insert( + deprecated, + LintGroup { + lint_ids: vec![], + is_externally_loaded, + 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), + }; + }, + } + } + + /// 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 `Diag` 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? + debug!("lints={:?}", self.by_name); + 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, tool_name.as_str()) + } 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::MissingTool + }; + } + Some(LintGroup { lint_ids, .. }) => { + return CheckLintNameResult::Tool(lint_ids, None); + } + }, + Some(Id(id)) => return CheckLintNameResult::Tool(slice::from_ref(id), None), + // 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(new_name, _)) => CheckLintNameResult::Renamed(new_name.to_string()), + Some(Removed(reason)) => CheckLintNameResult::Removed(reason.to_string()), + 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(lint_ids, Some((*name).to_string())) + }; + } + CheckLintNameResult::Ok(lint_ids) + } + }, + Some(Id(id)) => CheckLintNameResult::Ok(slice::from_ref(id)), + Some(&Ignored) => CheckLintNameResult::Ok(&[]), + } + } + + fn no_lint_suggestion(&self, lint_name: &str, tool_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), false))); + } + + // ...if not, search for lints with a similar name + // Note: find_best_match_for_name depends on the sort order of its input vector. + // To ensure deterministic output, sort elements of the lint_groups hash map. + // Also, never suggest deprecated lint groups. + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + let mut groups: Vec<_> = self + .lint_groups + .iter() + .filter_map(|(k, LintGroup { depr, .. })| depr.is_none().then_some(k)) + .collect(); + groups.sort(); + let groups = groups.iter().map(|k| Symbol::intern(k)); + let lints = self.lints.iter().map(|l| Symbol::intern(&l.name_lower())); + let names: Vec<Symbol> = groups.chain(lints).collect(); + let mut lookups = vec![Symbol::intern(&name_lower)]; + if let Some(stripped) = name_lower.split("::").last() { + lookups.push(Symbol::intern(stripped)); + } + let res = find_best_match_for_names(&names, &lookups, None); + let is_rustc = res.map_or_else( + || false, + |s| name_lower.contains("::") && !s.as_str().starts_with(tool_name), + ); + let suggestion = res.map(|s| (s, is_rustc)); + 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, tool_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(); + if *silent { + CheckLintNameResult::Tool(lint_ids, Some(complete_name)) + } else { + CheckLintNameResult::Tool(lint_ids, Some((*name).to_string())) + } + } else { + CheckLintNameResult::Tool(lint_ids, Some(complete_name)) + } + } + }, + Some(Id(id)) => CheckLintNameResult::Tool(slice::from_ref(id), Some(complete_name)), + Some(other) => { + 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 effective_visibilities: &'tcx EffectiveVisibilities, + + 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, crate::levels::TopDown>, + pub buffered: LintBuffer, +} + +impl EarlyContext<'_> { + /// Emit a lint at the appropriate level, with an optional associated span and an existing + /// diagnostic. + /// + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature + #[rustc_lint_diagnostics] + pub fn span_lint_with_diagnostics( + &self, + lint: &'static Lint, + span: MultiSpan, + diagnostic: BuiltinLintDiag, + ) { + self.opt_span_lint(lint, Some(span), |diag| { + diagnostics::decorate_lint(self.sess(), diagnostic, diag); + }); + } +} + +pub trait LintContext { + fn sess(&self) -> &Session; + + // 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). + /// Emit a lint at the appropriate level, with an optional associated span. + /// + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature + #[rustc_lint_diagnostics] + fn opt_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ); + + /// Emit a lint at `span` from a lint struct (some type that implements `LintDiagnostic`, + /// typically generated by `#[derive(LintDiagnostic)]`). + fn emit_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: S, + decorator: impl for<'a> LintDiagnostic<'a, ()>, + ) { + self.opt_span_lint(lint, Some(span), |lint| { + decorator.decorate_lint(lint); + }); + } + + /// Emit a lint at the appropriate level, with an associated span. + /// + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature + #[rustc_lint_diagnostics] + fn span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: S, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ) { + self.opt_span_lint(lint, Some(span), decorate); + } + + /// Emit a lint from a lint struct (some type that implements `LintDiagnostic`, typically + /// generated by `#[derive(LintDiagnostic)]`). + fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) { + self.opt_span_lint(lint, None as Option<Span>, |lint| { + decorator.decorate_lint(lint); + }); + } + + /// Emit a lint at the appropriate level, with no associated span. + /// + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature + #[rustc_lint_diagnostics] + fn lint(&self, lint: &'static Lint, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)) { + self.opt_span_lint(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 it as usual, which will be suppressed and stored like a + // normal expected lint diagnostic. + #[allow(rustc::diagnostic_outside_of_impl)] + #[allow(rustc::untranslatable_diagnostic)] + self.sess() + .dcx() + .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, + features: &'a Features, + lint_added_lints: bool, + lint_store: &'a LintStore, + registered_tools: &'a RegisteredTools, + buffered: LintBuffer, + ) -> EarlyContext<'a> { + EarlyContext { + builder: LintLevelsBuilder::new( + sess, + features, + lint_added_lints, + lint_store, + registered_tools, + ), + buffered, + } + } +} + +impl<'tcx> LintContext for LateContext<'tcx> { + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + self.tcx.sess + } + + #[rustc_lint_diagnostics] + fn opt_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ) { + let hir_id = self.last_node_with_lint_attrs; + + match span { + Some(s) => self.tcx.node_span_lint(lint, hir_id, s, decorate), + None => self.tcx.node_lint(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<'_> { + /// Gets the overall compiler `Session` object. + fn sess(&self) -> &Session { + self.builder.sess() + } + + #[rustc_lint_diagnostics] + fn opt_span_lint<S: Into<MultiSpan>>( + &self, + lint: &'static Lint, + span: Option<S>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ) { + self.builder.opt_span_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(_, path) => path.res, + hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => self + .maybe_typeck_results() + .filter(|typeck_results| typeck_results.hir_owner == id.owner) + .or_else(|| { + self.tcx + .has_typeck_results(id.owner.def_id) + .then(|| self.tcx.typeck(id.owner.def_id)) + }) + .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> { + struct AbsolutePathPrinter<'tcx> { + tcx: TyCtxt<'tcx>, + path: Vec<Symbol>, + } + + impl<'tcx> Printer<'tcx> for AbsolutePathPrinter<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> { + Ok(()) + } + + fn print_type(&mut self, _ty: Ty<'tcx>) -> Result<(), PrintError> { + Ok(()) + } + + fn print_dyn_existential( + &mut self, + _predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>, + ) -> Result<(), PrintError> { + Ok(()) + } + + fn print_const(&mut self, _ct: ty::Const<'tcx>) -> Result<(), PrintError> { + Ok(()) + } + + fn path_crate(&mut self, cnum: CrateNum) -> Result<(), PrintError> { + self.path = vec![self.tcx.crate_name(cnum)]; + Ok(()) + } + + fn path_qualified( + &mut self, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<(), PrintError> { + if trait_ref.is_none() { + if let ty::Adt(def, args) = self_ty.kind() { + return self.print_def_path(def.did(), args); + } + } + + // This shouldn't ever be needed, but just in case: + with_no_trimmed_paths!({ + self.path = vec![match trait_ref { + Some(trait_ref) => Symbol::intern(&format!("{trait_ref:?}")), + None => Symbol::intern(&format!("<{self_ty}>")), + }]; + Ok(()) + }) + } + + fn path_append_impl( + &mut self, + print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, + _disambiguated_data: &DisambiguatedDefPathData, + self_ty: Ty<'tcx>, + trait_ref: Option<ty::TraitRef<'tcx>>, + ) -> Result<(), PrintError> { + print_prefix(self)?; + + // This shouldn't ever be needed, but just in case: + self.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(()) + } + + fn path_append( + &mut self, + print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, + disambiguated_data: &DisambiguatedDefPathData, + ) -> Result<(), PrintError> { + print_prefix(self)?; + + // Skip `::{{extern}}` blocks and `::{{constructor}}` on tuple/unit structs. + if let DefPathData::ForeignMod | DefPathData::Ctor = disambiguated_data.data { + return Ok(()); + } + + self.path.push(Symbol::intern(&disambiguated_data.data.to_string())); + Ok(()) + } + + fn path_generic_args( + &mut self, + print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>, + _args: &[GenericArg<'tcx>], + ) -> Result<(), PrintError> { + print_prefix(self) + } + } + + let mut printer = AbsolutePathPrinter { tcx: self.tcx, path: vec![] }; + printer.print_def_path(def_id, &[]).unwrap(); + printer.path + } + + /// Returns the associated type `name` for `self_ty` as an implementation of `trait_id`. + /// Do not invoke without first verifying that the type implements the trait. + pub fn get_associated_type( + &self, + self_ty: Ty<'tcx>, + trait_id: DefId, + name: &str, + ) -> Option<Ty<'tcx>> { + let tcx = self.tcx; + tcx.associated_items(trait_id) + .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) + .and_then(|assoc| { + let proj = Ty::new_projection(tcx, assoc.def_id, [self_ty]); + tcx.try_normalize_erasing_regions(self.param_env, proj).ok() + }) + } + + /// If the given expression is a local binding, find the initializer expression. + /// If that initializer expression is another local binding, find its initializer again. + /// + /// This process repeats as long as possible (but usually no more than once). + /// Type-check adjustments are not taken in account in this function. + /// + /// Examples: + /// ``` + /// let abc = 1; + /// let def = abc + 2; + /// // ^^^^^^^ output + /// let def = def; + /// dbg!(def); + /// // ^^^ input + /// ``` + pub fn expr_or_init<'a>(&self, mut expr: &'a hir::Expr<'tcx>) -> &'a hir::Expr<'tcx> { + expr = expr.peel_blocks(); + + while let hir::ExprKind::Path(ref qpath) = expr.kind + && let Some(parent_node) = match self.qpath_res(qpath, expr.hir_id) { + Res::Local(hir_id) => Some(self.tcx.parent_hir_node(hir_id)), + _ => None, + } + && let Some(init) = match parent_node { + hir::Node::Expr(expr) => Some(expr), + hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init, + _ => None, + } + { + expr = init.peel_blocks(); + } + expr + } + + /// If the given expression is a local binding, find the initializer expression. + /// If that initializer expression is another local or **outside** (`const`/`static`) + /// binding, find its initializer again. + /// + /// This process repeats as long as possible (but usually no more than once). + /// Type-check adjustments are not taken in account in this function. + /// + /// Examples: + /// ``` + /// const ABC: i32 = 1; + /// // ^ output + /// let def = ABC; + /// dbg!(def); + /// // ^^^ input + /// + /// // or... + /// let abc = 1; + /// let def = abc + 2; + /// // ^^^^^^^ output + /// dbg!(def); + /// // ^^^ input + /// ``` + pub fn expr_or_init_with_outside_body<'a>( + &self, + mut expr: &'a hir::Expr<'tcx>, + ) -> &'a hir::Expr<'tcx> { + expr = expr.peel_blocks(); + + while let hir::ExprKind::Path(ref qpath) = expr.kind + && let Some(parent_node) = match self.qpath_res(qpath, expr.hir_id) { + Res::Local(hir_id) => Some(self.tcx.parent_hir_node(hir_id)), + Res::Def(_, def_id) => self.tcx.hir().get_if_local(def_id), + _ => None, + } + && let Some(init) = match parent_node { + hir::Node::Expr(expr) => Some(expr), + hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init, + hir::Node::Item(item) => match item.kind { + hir::ItemKind::Const(.., body_id) | hir::ItemKind::Static(.., body_id) => { + Some(self.tcx.hir().body(body_id).value) + } + _ => None, + }, + _ => None, + } + { + expr = init.peel_blocks(); + } + expr + } +} + +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 + } +} diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs new file mode 100644 index 00000000000..05e075205c4 --- /dev/null +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -0,0 +1,441 @@ +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] + +use std::borrow::Cow; + +use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; +use rustc_errors::elided_lifetime_in_path_suggestion; +use rustc_errors::{Applicability, Diag, DiagArgValue, LintDiagnostic}; +use rustc_middle::middle::stability; +use rustc_session::lint::BuiltinLintDiag; +use rustc_session::Session; +use rustc_span::BytePos; +use tracing::debug; + +use crate::lints; + +mod check_cfg; + +pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) { + match diagnostic { + BuiltinLintDiag::UnicodeTextFlow(comment_span, content) => { + let spans: Vec<_> = content + .char_indices() + .filter_map(|(i, c)| { + TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { + let lo = comment_span.lo() + BytePos(2 + i as u32); + (c, comment_span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) + }) + }) + .collect(); + let characters = spans + .iter() + .map(|&(c, span)| lints::UnicodeCharNoteSub { span, c_debug: format!("{c:?}") }) + .collect(); + let suggestions = (!spans.is_empty()).then_some(lints::UnicodeTextFlowSuggestion { + spans: spans.iter().map(|(_c, span)| *span).collect(), + }); + + lints::UnicodeTextFlow { + comment_span, + characters, + suggestions, + num_codepoints: spans.len(), + } + .decorate_lint(diag); + } + BuiltinLintDiag::AbsPathWithModule(mod_span) => { + let (replacement, applicability) = match sess.source_map().span_to_snippet(mod_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), + }; + lints::AbsPathWithModule { + sugg: lints::AbsPathWithModuleSugg { span: mod_span, applicability, replacement }, + } + .decorate_lint(diag); + } + BuiltinLintDiag::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident } => { + lints::ProcMacroDeriveResolutionFallback { span: macro_span, ns, ident } + .decorate_lint(diag) + } + BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => { + lints::MacroExpandedMacroExportsAccessedByAbsolutePaths { definition: span_def } + .decorate_lint(diag) + } + + BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => { + lints::ElidedLifetimesInPaths { + subdiag: elided_lifetime_in_path_suggestion( + sess.source_map(), + n, + path_span, + incl_angl_brckt, + insertion_span, + ), + } + .decorate_lint(diag); + } + BuiltinLintDiag::UnknownCrateTypes { span, candidate } => { + let sugg = candidate.map(|candidate| lints::UnknownCrateTypesSub { span, candidate }); + lints::UnknownCrateTypes { sugg }.decorate_lint(diag); + } + BuiltinLintDiag::UnusedImports { + remove_whole_use, + num_to_remove, + remove_spans, + test_module_span, + span_snippets, + } => { + let sugg = if remove_whole_use { + lints::UnusedImportsSugg::RemoveWholeUse { span: remove_spans[0] } + } else { + lints::UnusedImportsSugg::RemoveImports { remove_spans, num_to_remove } + }; + let test_module_span = + test_module_span.map(|span| sess.source_map().guess_head_span(span)); + + lints::UnusedImports { + sugg, + test_module_span, + num_snippets: span_snippets.len(), + span_snippets: DiagArgValue::StrListSepByAnd( + span_snippets.into_iter().map(Cow::Owned).collect(), + ), + } + .decorate_lint(diag); + } + BuiltinLintDiag::RedundantImport(spans, ident) => { + let subs = spans + .into_iter() + .map(|(span, is_imported)| { + (match (span.is_dummy(), is_imported) { + (false, true) => lints::RedundantImportSub::ImportedHere, + (false, false) => lints::RedundantImportSub::DefinedHere, + (true, true) => lints::RedundantImportSub::ImportedPrelude, + (true, false) => lints::RedundantImportSub::DefinedPrelude, + })(span) + }) + .collect(); + lints::RedundantImport { subs, ident }.decorate_lint(diag); + } + BuiltinLintDiag::DeprecatedMacro { + suggestion, + suggestion_span, + note, + path, + since_kind, + } => { + let sub = suggestion.map(|suggestion| stability::DeprecationSuggestion { + span: suggestion_span, + kind: "macro".to_owned(), + suggestion, + }); + + stability::Deprecated { sub, kind: "macro".to_owned(), path, note, since_kind } + .decorate_lint(diag); + } + BuiltinLintDiag::UnusedDocComment(attr_span) => { + lints::UnusedDocComment { span: attr_span }.decorate_lint(diag); + } + BuiltinLintDiag::PatternsInFnsWithoutBody { span: remove_span, ident, is_foreign } => { + let sub = lints::PatternsInFnsWithoutBodySub { ident, span: remove_span }; + if is_foreign { + lints::PatternsInFnsWithoutBody::Foreign { sub } + } else { + lints::PatternsInFnsWithoutBody::Bodiless { sub } + } + .decorate_lint(diag); + } + BuiltinLintDiag::MissingAbi(label_span, default_abi) => { + lints::MissingAbi { span: label_span, default_abi: default_abi.name() } + .decorate_lint(diag); + } + BuiltinLintDiag::LegacyDeriveHelpers(label_span) => { + lints::LegacyDeriveHelpers { span: label_span }.decorate_lint(diag); + } + BuiltinLintDiag::OrPatternsBackCompat(suggestion_span, suggestion) => { + lints::OrPatternsBackCompat { span: suggestion_span, suggestion }.decorate_lint(diag); + } + BuiltinLintDiag::ReservedPrefix(label_span, prefix) => { + lints::ReservedPrefix { + label: label_span, + suggestion: label_span.shrink_to_hi(), + prefix, + } + .decorate_lint(diag); + } + BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => { + lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag); + } + BuiltinLintDiag::TrailingMacro(is_trailing, name) => { + lints::TrailingMacro { is_trailing, name }.decorate_lint(diag); + } + BuiltinLintDiag::BreakWithLabelAndLoop(sugg_span) => { + lints::BreakWithLabelAndLoop { + sub: lints::BreakWithLabelAndLoopSub { + left: sugg_span.shrink_to_lo(), + right: sugg_span.shrink_to_hi(), + }, + } + .decorate_lint(diag); + } + BuiltinLintDiag::UnexpectedCfgName(name, value) => { + check_cfg::unexpected_cfg_name(sess, name, value).decorate_lint(diag); + } + BuiltinLintDiag::UnexpectedCfgValue(name, value) => { + check_cfg::unexpected_cfg_value(sess, name, value).decorate_lint(diag); + } + BuiltinLintDiag::DeprecatedWhereclauseLocation(left_sp, sugg) => { + let suggestion = match sugg { + Some((right_sp, sugg)) => lints::DeprecatedWhereClauseLocationSugg::MoveToEnd { + left: left_sp, + right: right_sp, + sugg, + }, + None => lints::DeprecatedWhereClauseLocationSugg::RemoveWhere { span: left_sp }, + }; + lints::DeprecatedWhereClauseLocation { suggestion }.decorate_lint(diag); + } + BuiltinLintDiag::MissingUnsafeOnExtern { suggestion } => { + lints::MissingUnsafeOnExtern { suggestion }.decorate_lint(diag); + } + BuiltinLintDiag::SingleUseLifetime { + param_span, + use_span: Some((use_span, elide)), + deletion_span, + ident, + } => { + debug!(?param_span, ?use_span, ?deletion_span); + let suggestion = if let Some(deletion_span) = deletion_span { + let (use_span, replace_lt) = if elide { + let use_span = sess.source_map().span_extend_while_whitespace(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + debug!(?deletion_span, ?use_span); + + // issue 107998 for the case such as a wrong function pointer type + // `deletion_span` is empty and there is no need to report lifetime uses here + let deletion_span = + if deletion_span.is_empty() { None } else { Some(deletion_span) }; + Some(lints::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt }) + } else { + None + }; + + lints::SingleUseLifetime { suggestion, param_span, use_span, ident } + .decorate_lint(diag); + } + BuiltinLintDiag::SingleUseLifetime { use_span: None, deletion_span, ident, .. } => { + debug!(?deletion_span); + lints::UnusedLifetime { deletion_span, ident }.decorate_lint(diag); + } + BuiltinLintDiag::NamedArgumentUsedPositionally { + position_sp_to_replace, + position_sp_for_msg, + named_arg_sp, + named_arg_name, + is_formatting_arg, + } => { + let (suggestion, name) = if let Some(positional_arg_to_replace) = position_sp_to_replace + { + let mut name = named_arg_name.clone(); + if is_formatting_arg { + name.push('$') + }; + let span_to_replace = if let Ok(positional_arg_content) = + sess.source_map().span_to_snippet(positional_arg_to_replace) + && positional_arg_content.starts_with(':') + { + positional_arg_to_replace.shrink_to_lo() + } else { + positional_arg_to_replace + }; + (Some(span_to_replace), name) + } else { + (None, String::new()) + }; + + lints::NamedArgumentUsedPositionally { + named_arg_sp, + position_label_sp: position_sp_for_msg, + suggestion, + name, + named_arg_name, + } + .decorate_lint(diag); + } + BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => { + lints::ByteSliceInPackedStructWithDerive { ty }.decorate_lint(diag); + } + BuiltinLintDiag::UnusedExternCrate { removal_span } => { + lints::UnusedExternCrate { removal_span }.decorate_lint(diag); + } + BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => { + let suggestion_span = vis_span.between(ident_span); + let code = if vis_span.is_empty() { "use " } else { " use " }; + + lints::ExternCrateNotIdiomatic { span: suggestion_span, code }.decorate_lint(diag); + } + BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => { + lints::AmbiguousGlobImports { ambiguity }.decorate_lint(diag); + } + BuiltinLintDiag::AmbiguousGlobReexports { + name, + namespace, + first_reexport_span, + duplicate_reexport_span, + } => { + lints::AmbiguousGlobReexports { + first_reexport: first_reexport_span, + duplicate_reexport: duplicate_reexport_span, + name, + namespace, + } + .decorate_lint(diag); + } + BuiltinLintDiag::HiddenGlobReexports { + name, + namespace, + glob_reexport_span, + private_item_span, + } => { + lints::HiddenGlobReexports { + glob_reexport: glob_reexport_span, + private_item: private_item_span, + + name, + namespace, + } + .decorate_lint(diag); + } + BuiltinLintDiag::UnusedQualifications { removal_span } => { + lints::UnusedQualifications { removal_span }.decorate_lint(diag); + } + BuiltinLintDiag::UnsafeAttrOutsideUnsafe { + attribute_name_span, + sugg_spans: (left, right), + } => { + lints::UnsafeAttrOutsideUnsafe { + span: attribute_name_span, + suggestion: lints::UnsafeAttrOutsideUnsafeSuggestion { left, right }, + } + .decorate_lint(diag); + } + BuiltinLintDiag::AssociatedConstElidedLifetime { + elided, + span: lt_span, + lifetimes_in_scope, + } => { + let lt_span = if elided { lt_span.shrink_to_hi() } else { lt_span }; + let code = if elided { "'static " } else { "'static" }; + lints::AssociatedConstElidedLifetime { + span: lt_span, + code, + elided, + lifetimes_in_scope, + } + .decorate_lint(diag); + } + BuiltinLintDiag::RedundantImportVisibility { max_vis, span: vis_span, import_vis } => { + lints::RedundantImportVisibility { span: vis_span, help: (), max_vis, import_vis } + .decorate_lint(diag); + } + BuiltinLintDiag::UnknownDiagnosticAttribute { span: typo_span, typo_name } => { + let typo = typo_name.map(|typo_name| lints::UnknownDiagnosticAttributeTypoSugg { + span: typo_span, + typo_name, + }); + lints::UnknownDiagnosticAttribute { typo }.decorate_lint(diag); + } + BuiltinLintDiag::MacroUseDeprecated => { + lints::MacroUseDeprecated.decorate_lint(diag); + } + BuiltinLintDiag::UnusedMacroUse => lints::UnusedMacroUse.decorate_lint(diag), + BuiltinLintDiag::PrivateExternCrateReexport { source: ident, extern_crate_span } => { + lints::PrivateExternCrateReexport { ident, sugg: extern_crate_span.shrink_to_lo() } + .decorate_lint(diag); + } + BuiltinLintDiag::UnusedLabel => lints::UnusedLabel.decorate_lint(diag), + BuiltinLintDiag::MacroIsPrivate(ident) => { + lints::MacroIsPrivate { ident }.decorate_lint(diag); + } + BuiltinLintDiag::UnusedMacroDefinition(name) => { + lints::UnusedMacroDefinition { name }.decorate_lint(diag); + } + BuiltinLintDiag::MacroRuleNeverUsed(n, name) => { + lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag); + } + BuiltinLintDiag::UnstableFeature(msg) => { + lints::UnstableFeature { msg }.decorate_lint(diag); + } + BuiltinLintDiag::AvoidUsingIntelSyntax => { + lints::AvoidIntelSyntax.decorate_lint(diag); + } + BuiltinLintDiag::AvoidUsingAttSyntax => { + lints::AvoidAttSyntax.decorate_lint(diag); + } + BuiltinLintDiag::IncompleteInclude => { + lints::IncompleteInclude.decorate_lint(diag); + } + BuiltinLintDiag::UnnameableTestItems => { + lints::UnnameableTestItems.decorate_lint(diag); + } + BuiltinLintDiag::DuplicateMacroAttribute => { + lints::DuplicateMacroAttribute.decorate_lint(diag); + } + BuiltinLintDiag::CfgAttrNoAttributes => { + lints::CfgAttrNoAttributes.decorate_lint(diag); + } + BuiltinLintDiag::CrateTypeInCfgAttr => { + lints::CrateTypeInCfgAttr.decorate_lint(diag); + } + BuiltinLintDiag::CrateNameInCfgAttr => { + lints::CrateNameInCfgAttr.decorate_lint(diag); + } + BuiltinLintDiag::MissingFragmentSpecifier => { + lints::MissingFragmentSpecifier.decorate_lint(diag); + } + BuiltinLintDiag::MetaVariableStillRepeating(name) => { + lints::MetaVariableStillRepeating { name }.decorate_lint(diag); + } + BuiltinLintDiag::MetaVariableWrongOperator => { + lints::MetaVariableWrongOperator.decorate_lint(diag); + } + BuiltinLintDiag::DuplicateMatcherBinding => { + lints::DuplicateMatcherBinding.decorate_lint(diag); + } + BuiltinLintDiag::UnknownMacroVariable(name) => { + lints::UnknownMacroVariable { name }.decorate_lint(diag); + } + BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => { + lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag) + } + BuiltinLintDiag::WasmCAbi => lints::WasmCAbi.decorate_lint(diag), + BuiltinLintDiag::IllFormedAttributeInput { suggestions } => { + lints::IllFormedAttributeInput { + num_suggestions: suggestions.len(), + suggestions: DiagArgValue::StrListSepByAnd( + suggestions.into_iter().map(|s| format!("`{s}`").into()).collect(), + ), + } + .decorate_lint(diag) + } + BuiltinLintDiag::InnerAttributeUnstable { is_macro } => if is_macro { + lints::InnerAttributeUnstable::InnerMacroAttribute + } else { + lints::InnerAttributeUnstable::CustomInnerAttribute + } + .decorate_lint(diag), + BuiltinLintDiag::OutOfScopeMacroCalls { path } => { + lints::OutOfScopeMacroCalls { path }.decorate_lint(diag) + } + } +} diff --git a/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs new file mode 100644 index 00000000000..da36f68fca9 --- /dev/null +++ b/compiler/rustc_lint/src/context/diagnostics/check_cfg.rs @@ -0,0 +1,305 @@ +use rustc_middle::bug; +use rustc_session::{config::ExpectedValues, Session}; +use rustc_span::edit_distance::find_best_match_for_name; +use rustc_span::{sym, Span, Symbol}; + +use crate::lints; + +const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35; + +fn sort_and_truncate_possibilities( + sess: &Session, + mut possibilities: Vec<Symbol>, +) -> (Vec<Symbol>, usize) { + let n_possibilities = if sess.opts.unstable_opts.check_cfg_all_expected { + possibilities.len() + } else { + std::cmp::min(possibilities.len(), MAX_CHECK_CFG_NAMES_OR_VALUES) + }; + + possibilities.sort_by(|s1, s2| s1.as_str().cmp(s2.as_str())); + + let and_more = possibilities.len().saturating_sub(n_possibilities); + possibilities.truncate(n_possibilities); + (possibilities, and_more) +} + +enum EscapeQuotes { + Yes, + No, +} + +fn to_check_cfg_arg(name: Symbol, value: Option<Symbol>, quotes: EscapeQuotes) -> String { + if let Some(value) = value { + let value = str::escape_debug(value.as_str()).to_string(); + let values = match quotes { + EscapeQuotes::Yes => format!("\\\"{}\\\"", value.replace("\"", "\\\\\\\\\"")), + EscapeQuotes::No => format!("\"{value}\""), + }; + format!("cfg({name}, values({values}))") + } else { + format!("cfg({name})") + } +} + +fn cargo_help_sub( + sess: &Session, + inst: &impl Fn(EscapeQuotes) -> String, +) -> lints::UnexpectedCfgCargoHelp { + // We don't want to suggest the `build.rs` way to expected cfgs if we are already in a + // `build.rs`. We therefor do a best effort check (looking if the `--crate-name` is + // `build_script_build`) to try to figure out if we are building a Cargo build script + + let unescaped = &inst(EscapeQuotes::No); + if matches!(&sess.opts.crate_name, Some(crate_name) if crate_name == "build_script_build") { + lints::UnexpectedCfgCargoHelp::lint_cfg(unescaped) + } else { + lints::UnexpectedCfgCargoHelp::lint_cfg_and_build_rs(unescaped, &inst(EscapeQuotes::Yes)) + } +} + +pub(super) fn unexpected_cfg_name( + sess: &Session, + (name, name_span): (Symbol, Span), + value: Option<(Symbol, Span)>, +) -> lints::UnexpectedCfgName { + #[allow(rustc::potential_query_instability)] + let possibilities: Vec<Symbol> = sess.psess.check_config.expecteds.keys().copied().collect(); + + let mut names_possibilities: Vec<_> = if value.is_none() { + // We later sort and display all the possibilities, so the order here does not matter. + #[allow(rustc::potential_query_instability)] + sess.psess + .check_config + .expecteds + .iter() + .filter_map(|(k, v)| match v { + ExpectedValues::Some(v) if v.contains(&Some(name)) => Some(k), + _ => None, + }) + .collect() + } else { + Vec::new() + }; + + let is_from_cargo = rustc_session::utils::was_invoked_from_cargo(); + let mut is_feature_cfg = name == sym::feature; + + let code_sugg = if is_feature_cfg && is_from_cargo { + lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures + // Suggest the most probable if we found one + } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { + is_feature_cfg |= best_match == sym::feature; + + if let Some(ExpectedValues::Some(best_match_values)) = + sess.psess.check_config.expecteds.get(&best_match) + { + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + let mut possibilities = best_match_values.iter().flatten().collect::<Vec<_>>(); + possibilities.sort_by_key(|s| s.as_str()); + + let get_possibilities_sub = || { + if !possibilities.is_empty() { + let possibilities = + possibilities.iter().copied().cloned().collect::<Vec<_>>().into(); + Some(lints::unexpected_cfg_name::ExpectedValues { best_match, possibilities }) + } else { + None + } + }; + + if let Some((value, value_span)) = value { + if best_match_values.contains(&Some(value)) { + lints::unexpected_cfg_name::CodeSuggestion::SimilarNameAndValue { + span: name_span, + code: best_match.to_string(), + } + } else if best_match_values.contains(&None) { + lints::unexpected_cfg_name::CodeSuggestion::SimilarNameNoValue { + span: name_span.to(value_span), + code: best_match.to_string(), + } + } else if let Some(first_value) = possibilities.first() { + lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues { + span: name_span.to(value_span), + code: format!("{best_match} = \"{first_value}\""), + expected: get_possibilities_sub(), + } + } else { + lints::unexpected_cfg_name::CodeSuggestion::SimilarNameDifferentValues { + span: name_span.to(value_span), + code: best_match.to_string(), + expected: get_possibilities_sub(), + } + } + } else { + lints::unexpected_cfg_name::CodeSuggestion::SimilarName { + span: name_span, + code: best_match.to_string(), + expected: get_possibilities_sub(), + } + } + } else { + lints::unexpected_cfg_name::CodeSuggestion::SimilarName { + span: name_span, + code: best_match.to_string(), + expected: None, + } + } + } else { + let similar_values = if !names_possibilities.is_empty() && names_possibilities.len() <= 3 { + names_possibilities.sort(); + names_possibilities + .iter() + .map(|cfg_name| lints::unexpected_cfg_name::FoundWithSimilarValue { + span: name_span, + code: format!("{cfg_name} = \"{name}\""), + }) + .collect() + } else { + vec![] + }; + let expected_names = if !possibilities.is_empty() { + let (possibilities, and_more) = sort_and_truncate_possibilities(sess, possibilities); + Some(lints::unexpected_cfg_name::ExpectedNames { + possibilities: possibilities.into(), + and_more, + }) + } else { + None + }; + lints::unexpected_cfg_name::CodeSuggestion::SimilarValues { + with_similar_values: similar_values, + expected_names, + } + }; + + let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes); + + let invocation_help = if is_from_cargo { + let sub = if !is_feature_cfg { Some(cargo_help_sub(sess, &inst)) } else { None }; + lints::unexpected_cfg_name::InvocationHelp::Cargo { sub } + } else { + lints::unexpected_cfg_name::InvocationHelp::Rustc(lints::UnexpectedCfgRustcHelp::new( + &inst(EscapeQuotes::No), + )) + }; + + lints::UnexpectedCfgName { code_sugg, invocation_help, name } +} + +pub(super) fn unexpected_cfg_value( + sess: &Session, + (name, name_span): (Symbol, Span), + value: Option<(Symbol, Span)>, +) -> lints::UnexpectedCfgValue { + let Some(ExpectedValues::Some(values)) = &sess.psess.check_config.expecteds.get(&name) else { + bug!( + "it shouldn't be possible to have a diagnostic on a value whose name is not in values" + ); + }; + let mut have_none_possibility = false; + // We later sort possibilities if it is not empty, so the + // order here does not matter. + #[allow(rustc::potential_query_instability)] + let possibilities: Vec<Symbol> = values + .iter() + .inspect(|a| have_none_possibility |= a.is_none()) + .copied() + .flatten() + .collect(); + let is_from_cargo = rustc_session::utils::was_invoked_from_cargo(); + + // 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 + let code_sugg = if !possibilities.is_empty() { + let expected_values = { + let (possibilities, and_more) = + sort_and_truncate_possibilities(sess, possibilities.clone()); + lints::unexpected_cfg_value::ExpectedValues { + name, + have_none_possibility, + possibilities: possibilities.into(), + and_more, + } + }; + + let suggestion = if let Some((value, value_span)) = value { + // Suggest the most probable if we found one + if let Some(best_match) = find_best_match_for_name(&possibilities, value, None) { + Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SimilarName { + span: value_span, + best_match, + }) + } else { + None + } + } else if let &[first_possibility] = &possibilities[..] { + Some(lints::unexpected_cfg_value::ChangeValueSuggestion::SpecifyValue { + span: name_span.shrink_to_hi(), + first_possibility, + }) + } else { + None + }; + + lints::unexpected_cfg_value::CodeSuggestion::ChangeValue { expected_values, suggestion } + } else if have_none_possibility { + let suggestion = + value.map(|(_value, value_span)| lints::unexpected_cfg_value::RemoveValueSuggestion { + span: name_span.shrink_to_hi().to(value_span), + }); + lints::unexpected_cfg_value::CodeSuggestion::RemoveValue { suggestion, name } + } else { + let span = if let Some((_value, value_span)) = value { + name_span.to(value_span) + } else { + name_span + }; + let suggestion = lints::unexpected_cfg_value::RemoveConditionSuggestion { span }; + lints::unexpected_cfg_value::CodeSuggestion::RemoveCondition { suggestion, name } + }; + + // We don't want to encourage people to add values to a well-known names, as these are + // defined by rustc/Rust itself. Users can still do this if they wish, but should not be + // encouraged to do so. + let can_suggest_adding_value = !sess.psess.check_config.well_known_names.contains(&name) + // Except when working on rustc or the standard library itself, in which case we want to + // suggest adding these cfgs to the "normal" place because of bootstraping reasons. As a + // basic heuristic, we use the "cheat" unstable feature enable method and the + // non-ui-testing enabled option. + || (matches!(sess.psess.unstable_features, rustc_feature::UnstableFeatures::Cheat) + && !sess.opts.unstable_opts.ui_testing); + + let inst = |escape_quotes| to_check_cfg_arg(name, value.map(|(v, _s)| v), escape_quotes); + + let invocation_help = if is_from_cargo { + let help = if name == sym::feature { + if let Some((value, _value_span)) = value { + Some(lints::unexpected_cfg_value::CargoHelp::AddFeature { value }) + } else { + Some(lints::unexpected_cfg_value::CargoHelp::DefineFeatures) + } + } else if can_suggest_adding_value { + Some(lints::unexpected_cfg_value::CargoHelp::Other(cargo_help_sub(sess, &inst))) + } else { + None + }; + lints::unexpected_cfg_value::InvocationHelp::Cargo(help) + } else { + let help = if can_suggest_adding_value { + Some(lints::UnexpectedCfgRustcHelp::new(&inst(EscapeQuotes::No))) + } else { + None + }; + lints::unexpected_cfg_value::InvocationHelp::Rustc(help) + }; + + lints::UnexpectedCfgValue { + code_sugg, + invocation_help, + has_value: value.is_some(), + value: value.map_or_else(String::new, |(v, _span)| v.to_string()), + } +} diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs new file mode 100644 index 00000000000..911975f6179 --- /dev/null +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -0,0 +1,106 @@ +use crate::{ + lints::{SupertraitAsDerefTarget, SupertraitAsDerefTargetLabel}, + LateContext, LateLintPass, LintContext, +}; + +use rustc_hir::{self as hir, LangItem}; +use rustc_middle::ty; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; +use rustc_trait_selection::traits::supertraits; + +declare_lint! { + /// The `deref_into_dyn_supertrait` lint is output whenever there is a use of the + /// `Deref` implementation with a `dyn SuperTrait` type as `Output`. + /// + /// These implementations will become shadowed when the `trait_upcasting` feature is stabilized. + /// The `deref` functions will no longer be called implicitly, so there might be behavior change. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(deref_into_dyn_supertrait)] + /// #![allow(dead_code)] + /// + /// use core::ops::Deref; + /// + /// trait A {} + /// trait B: A {} + /// impl<'a> Deref for dyn 'a + B { + /// type Target = dyn A; + /// fn deref(&self) -> &Self::Target { + /// todo!() + /// } + /// } + /// + /// fn take_a(_: &dyn A) { } + /// + /// fn take_b(b: &dyn B) { + /// take_a(b); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The dyn upcasting coercion feature adds new coercion rules, taking priority + /// over certain other coercion rules, which will cause some behavior change. + pub DEREF_INTO_DYN_SUPERTRAIT, + Warn, + "`Deref` implementation usage with a supertrait trait object for output might be shadowed in the future", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseSemanticsChange, + reference: "issue #89460 <https://github.com/rust-lang/rust/issues/89460>", + }; +} + +declare_lint_pass!(DerefIntoDynSupertrait => [DEREF_INTO_DYN_SUPERTRAIT]); + +impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + let tcx = cx.tcx; + // `Deref` is being implemented for `t` + if let hir::ItemKind::Impl(impl_) = item.kind + // the trait is a `Deref` implementation + && let Some(trait_) = &impl_.of_trait + && let Some(did) = trait_.trait_def_id() + && tcx.is_lang_item(did, LangItem::Deref) + // the self type is `dyn t_principal` + && let self_ty = tcx.type_of(item.owner_id).instantiate_identity() + && let ty::Dynamic(data, _, ty::Dyn) = self_ty.kind() + && let Some(self_principal) = data.principal() + // `<T as Deref>::Target` is `dyn target_principal` + && let Some(target) = cx.get_associated_type(self_ty, did, "Target") + && let ty::Dynamic(data, _, ty::Dyn) = target.kind() + && let Some(target_principal) = data.principal() + // `target_principal` is a supertrait of `t_principal` + && let Some(supertrait_principal) = supertraits(tcx, self_principal.with_self_ty(tcx, self_ty)) + .find(|supertrait| supertrait.def_id() == target_principal.def_id()) + { + // erase regions in self type for better diagnostic presentation + let (self_ty, target_principal, supertrait_principal) = + tcx.erase_regions((self_ty, target_principal, supertrait_principal)); + let label2 = impl_ + .items + .iter() + .find_map(|i| (i.ident.name == sym::Target).then_some(i.span)) + .map(|label| SupertraitAsDerefTargetLabel { label }); + let span = tcx.def_span(item.owner_id.def_id); + cx.emit_span_lint( + DEREF_INTO_DYN_SUPERTRAIT, + span, + SupertraitAsDerefTarget { + self_ty, + supertrait_principal: supertrait_principal.map_bound(|trait_ref| { + ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) + }), + target_principal, + label: span, + label2, + }, + ); + } + } +} diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs new file mode 100644 index 00000000000..eea0898d83f --- /dev/null +++ b/compiler/rustc_lint/src/drop_forget_useless.rs @@ -0,0 +1,249 @@ +use rustc_hir::{Arm, Expr, ExprKind, Node, StmtKind}; +use rustc_middle::ty; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +use crate::{ + lints::{ + DropCopyDiag, DropRefDiag, ForgetCopyDiag, ForgetRefDiag, UndroppedManuallyDropsDiag, + UndroppedManuallyDropsSuggestion, UseLetUnderscoreIgnoreSuggestion, + }, + LateContext, LateLintPass, LintContext, +}; + +declare_lint! { + /// The `dropping_references` lint checks for calls to `std::mem::drop` with a reference + /// instead of an owned value. + /// + /// ### Example + /// + /// ```rust + /// # fn operation_that_requires_mutex_to_be_unlocked() {} // just to make it compile + /// # let mutex = std::sync::Mutex::new(1); // just to make it compile + /// let mut lock_guard = mutex.lock(); + /// std::mem::drop(&lock_guard); // Should have been drop(lock_guard), mutex + /// // still locked + /// operation_that_requires_mutex_to_be_unlocked(); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Calling `drop` on a reference will only drop the + /// reference itself, which is a no-op. It will not call the `drop` method (from + /// the `Drop` trait implementation) on the underlying referenced value, which + /// is likely what was intended. + pub DROPPING_REFERENCES, + Warn, + "calls to `std::mem::drop` with a reference instead of an owned value" +} + +declare_lint! { + /// The `forgetting_references` lint checks for calls to `std::mem::forget` with a reference + /// instead of an owned value. + /// + /// ### Example + /// + /// ```rust + /// let x = Box::new(1); + /// std::mem::forget(&x); // Should have been forget(x), x will still be dropped + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Calling `forget` on a reference will only forget the + /// reference itself, which is a no-op. It will not forget the underlying + /// referenced value, which is likely what was intended. + pub FORGETTING_REFERENCES, + Warn, + "calls to `std::mem::forget` with a reference instead of an owned value" +} + +declare_lint! { + /// The `dropping_copy_types` lint checks for calls to `std::mem::drop` with a value + /// that derives the Copy trait. + /// + /// ### Example + /// + /// ```rust + /// let x: i32 = 42; // i32 implements Copy + /// std::mem::drop(x); // A copy of x is passed to the function, leaving the + /// // original unaffected + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Calling `std::mem::drop` [does nothing for types that + /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html), since the + /// value will be copied and moved into the function on invocation. + pub DROPPING_COPY_TYPES, + Warn, + "calls to `std::mem::drop` with a value that implements Copy" +} + +declare_lint! { + /// The `forgetting_copy_types` lint checks for calls to `std::mem::forget` with a value + /// that derives the Copy trait. + /// + /// ### Example + /// + /// ```rust + /// let x: i32 = 42; // i32 implements Copy + /// std::mem::forget(x); // A copy of x is passed to the function, leaving the + /// // original unaffected + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Calling `std::mem::forget` [does nothing for types that + /// implement Copy](https://doc.rust-lang.org/std/mem/fn.drop.html) since the + /// value will be copied and moved into the function on invocation. + /// + /// An alternative, but also valid, explanation is that Copy types do not + /// implement the Drop trait, which means they have no destructors. Without a + /// destructor, there is nothing for `std::mem::forget` to ignore. + pub FORGETTING_COPY_TYPES, + Warn, + "calls to `std::mem::forget` with a value that implements Copy" +} + +declare_lint! { + /// The `undropped_manually_drops` lint check for calls to `std::mem::drop` with + /// a value of `std::mem::ManuallyDrop` which doesn't drop. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// struct S; + /// drop(std::mem::ManuallyDrop::new(S)); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `ManuallyDrop` does not drop it's inner value so calling `std::mem::drop` will + /// not drop the inner value of the `ManuallyDrop` either. + pub UNDROPPED_MANUALLY_DROPS, + Deny, + "calls to `std::mem::drop` with `std::mem::ManuallyDrop` instead of it's inner value" +} + +declare_lint_pass!(DropForgetUseless => [DROPPING_REFERENCES, FORGETTING_REFERENCES, DROPPING_COPY_TYPES, FORGETTING_COPY_TYPES, UNDROPPED_MANUALLY_DROPS]); + +impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Call(path, [arg]) = expr.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id) + { + let arg_ty = cx.typeck_results().expr_ty(arg); + let is_copy = arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env); + let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr); + let let_underscore_ignore_sugg = || { + if let Some((_, node)) = cx.tcx.hir().parent_iter(expr.hir_id).nth(0) + && let Node::Stmt(stmt) = node + && let StmtKind::Semi(e) = stmt.kind + && e.hir_id == expr.hir_id + { + UseLetUnderscoreIgnoreSuggestion::Suggestion { + start_span: expr.span.shrink_to_lo().until(arg.span), + end_span: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()), + } + } else { + UseLetUnderscoreIgnoreSuggestion::Note + } + }; + match fn_name { + sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => { + cx.emit_span_lint( + DROPPING_REFERENCES, + expr.span, + DropRefDiag { arg_ty, label: arg.span, sugg: let_underscore_ignore_sugg() }, + ); + } + sym::mem_forget if arg_ty.is_ref() => { + cx.emit_span_lint( + FORGETTING_REFERENCES, + expr.span, + ForgetRefDiag { + arg_ty, + label: arg.span, + sugg: let_underscore_ignore_sugg(), + }, + ); + } + sym::mem_drop if is_copy && !drop_is_single_call_in_arm => { + cx.emit_span_lint( + DROPPING_COPY_TYPES, + expr.span, + DropCopyDiag { + arg_ty, + label: arg.span, + sugg: let_underscore_ignore_sugg(), + }, + ); + } + sym::mem_forget if is_copy => { + cx.emit_span_lint( + FORGETTING_COPY_TYPES, + expr.span, + ForgetCopyDiag { + arg_ty, + label: arg.span, + sugg: let_underscore_ignore_sugg(), + }, + ); + } + sym::mem_drop + if let ty::Adt(adt, _) = arg_ty.kind() + && adt.is_manually_drop() => + { + cx.emit_span_lint( + UNDROPPED_MANUALLY_DROPS, + expr.span, + UndroppedManuallyDropsDiag { + arg_ty, + label: arg.span, + suggestion: UndroppedManuallyDropsSuggestion { + start_span: arg.span.shrink_to_lo(), + end_span: arg.span.shrink_to_hi(), + }, + }, + ); + } + _ => return, + }; + } + } +} + +// Dropping returned value of a function, as in the following snippet is considered idiomatic, see +// rust-lang/rust-clippy#9482 for examples. +// +// ``` +// match <var> { +// <pat> => drop(fn_with_side_effect_and_returning_some_value()), +// .. +// } +// ``` +fn is_single_call_in_arm<'tcx>( + cx: &LateContext<'tcx>, + arg: &'tcx Expr<'_>, + drop_expr: &'tcx Expr<'_>, +) -> bool { + if arg.can_have_side_effects() { + if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) { + return body.hir_id == drop_expr.hir_id; + } + } + false +} diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs new file mode 100644 index 00000000000..329221612b5 --- /dev/null +++ b/compiler/rustc_lint/src/early.rs @@ -0,0 +1,438 @@ +//! 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, LintStore}; +use crate::passes::{EarlyLintPass, EarlyLintPassObject}; +use rustc_ast::ptr::P; +use rustc_ast::visit::{self as ast_visit, walk_list, Visitor}; +use rustc_ast::{self as ast, HasAttrs}; +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_feature::Features; +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 tracing::debug; + +macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +/// Implements the AST traversal for early lint passes. `T` provides the +/// `check_*` methods. +pub struct EarlyContextAndPass<'a, T: EarlyLintPass> { + context: EarlyContext<'a>, + pass: T, +} + +impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { + // This always-inlined function is for the hot call site. + #[inline(always)] + #[allow(rustc::diagnostic_outside_of_impl)] + fn inlined_check_id(&mut self, id: ast::NodeId) { + for early_lint in self.context.buffered.take(id) { + let BufferedEarlyLint { span, node_id: _, lint_id, diagnostic } = early_lint; + self.context.span_lint_with_diagnostics(lint_id.lint, span, diagnostic); + } + } + + // This non-inlined function is for the cold call sites. + fn check_id(&mut self, id: ast::NodeId) { + self.inlined_check_id(id) + } + + /// 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; + debug!(?id); + let push = self.context.builder.push(attrs, is_crate_node, None); + + self.inlined_check_id(id); + debug!("early context: enter_attrs({:?})", attrs); + lint_callback!(self, check_attributes, attrs); + ensure_sufficient_stack(|| f(self)); + debug!("early context: exit_attrs({:?})", attrs); + lint_callback!(self, check_attributes_post, 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, ¶m.attrs, |cx| { + lint_callback!(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| { + lint_callback!(cx, check_item, it); + ast_visit::walk_item(cx, it); + lint_callback!(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_item(cx, it); + }) + } + + fn visit_pat(&mut self, p: &'a ast::Pat) { + lint_callback!(self, check_pat, p); + self.check_id(p.id); + ast_visit::walk_pat(self, p); + lint_callback!(self, check_pat_post, p); + } + + fn visit_pat_field(&mut self, field: &'a ast::PatField) { + self.with_lint_attrs(field.id, &field.attrs, |cx| { + ast_visit::walk_pat_field(cx, field); + }); + } + + 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| { + lint_callback!(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| { + lint_callback!(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) { + lint_callback!(self, check_fn, fk, span, id); + self.check_id(id); + ast_visit::walk_fn(self, fk); + + // 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 Some(coroutine_kind) = sig.header.coroutine_kind { + self.check_id(coroutine_kind.closure_id()); + } + } + } + + fn visit_variant_data(&mut self, s: &'a ast::VariantData) { + if let Some(ctor_node_id) = s.ctor_node_id() { + self.check_id(ctor_node_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| { + lint_callback!(cx, check_variant, v); + ast_visit::walk_variant(cx, v); + }) + } + + fn visit_ty(&mut self, t: &'a ast::Ty) { + lint_callback!(self, check_ty, t); + self.check_id(t.id); + ast_visit::walk_ty(self, t); + } + + fn visit_ident(&mut self, ident: Ident) { + lint_callback!(self, check_ident, ident); + } + + fn visit_local(&mut self, l: &'a ast::Local) { + self.with_lint_attrs(l.id, &l.attrs, |cx| { + lint_callback!(cx, check_local, l); + ast_visit::walk_local(cx, l); + }) + } + + fn visit_block(&mut self, b: &'a ast::Block) { + lint_callback!(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| { + lint_callback!(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(box ast::Closure { + coroutine_kind: Some(coroutine_kind), + .. + }) => { + self.check_id(coroutine_kind.closure_id()); + } + _ => {} + } + lint_callback!(self, check_expr_post, e); + } + + fn visit_generic_arg(&mut self, arg: &'a ast::GenericArg) { + lint_callback!(self, check_generic_arg, arg); + ast_visit::walk_generic_arg(self, arg); + } + + fn visit_generic_param(&mut self, param: &'a ast::GenericParam) { + self.with_lint_attrs(param.id, ¶m.attrs, |cx| { + lint_callback!(cx, check_generic_param, param); + ast_visit::walk_generic_param(cx, param); + }); + } + + fn visit_generics(&mut self, g: &'a ast::Generics) { + lint_callback!(self, check_generics, g); + ast_visit::walk_generics(self, g); + } + + fn visit_where_predicate(&mut self, p: &'a ast::WherePredicate) { + lint_callback!(self, enter_where_predicate, p); + ast_visit::walk_where_predicate(self, p); + lint_callback!(self, exit_where_predicate, p); + } + + fn visit_poly_trait_ref(&mut self, t: &'a ast::PolyTraitRef) { + lint_callback!(self, check_poly_trait_ref, t); + ast_visit::walk_poly_trait_ref(self, t); + } + + 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 => { + lint_callback!(cx, check_trait_item, item); + ast_visit::walk_assoc_item(cx, item, ctxt); + } + ast_visit::AssocCtxt::Impl => { + lint_callback!(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, s: &'a ast::PathSegment) { + self.check_id(s.id); + ast_visit::walk_path_segment(self, s); + } + + fn visit_attribute(&mut self, attr: &'a ast::Attribute) { + lint_callback!(self, check_attribute, attr); + } + + fn visit_mac_def(&mut self, mac: &'a ast::MacroDef, id: ast::NodeId) { + lint_callback!(self, check_mac_def, mac); + self.check_id(id); + } + + fn visit_mac_call(&mut self, mac: &'a ast::MacCall) { + lint_callback!(self, check_mac, mac); + ast_visit::walk_mac(self, mac); + } +} + +// Combines multiple lint passes into a single pass, at runtime. Each +// `check_foo` method in `$methods` within this pass simply calls `check_foo` +// once per `$pass`. Compare with `declare_combined_early_lint_pass`, which is +// similar, but combines lint passes at compile time. +struct RuntimeCombinedEarlyLintPass<'a> { + passes: &'a mut [EarlyLintPassObject], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for RuntimeCombinedEarlyLintPass<'_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! impl_early_lint_pass { + ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => ( + impl EarlyLintPass for RuntimeCombinedEarlyLintPass<'_> { + $(fn $f(&mut self, context: &EarlyContext<'_>, $($param: $arg),*) { + for pass in self.passes.iter_mut() { + pass.$f(context, $($param),*); + } + })* + } + ) +} + +crate::early_lint_methods!(impl_early_lint_pass, []); + +/// 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, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) + where + 'a: 'b; +} + +impl<'a> EarlyCheckNode<'a> for (&'a ast::Crate, &'a [ast::Attribute]) { + fn id(self) -> ast::NodeId { + ast::CRATE_NODE_ID + } + fn attrs<'b>(self) -> &'b [ast::Attribute] + where + 'a: 'b, + { + self.1 + } + fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) + where + 'a: 'b, + { + lint_callback!(cx, check_crate, self.0); + ast_visit::walk_crate(cx, self.0); + lint_callback!(cx, check_crate_post, self.0); + } +} + +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, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>) + where + 'a: 'b, + { + walk_list!(cx, visit_attribute, self.1); + walk_list!(cx, visit_item, self.2); + } +} + +pub fn check_ast_node<'a>( + sess: &Session, + features: &Features, + pre_expansion: bool, + lint_store: &LintStore, + registered_tools: &RegisteredTools, + lint_buffer: Option<LintBuffer>, + builtin_lints: impl EarlyLintPass + 'static, + check_node: impl EarlyCheckNode<'a>, +) { + let context = EarlyContext::new( + sess, + features, + !pre_expansion, + lint_store, + registered_tools, + lint_buffer.unwrap_or_default(), + ); + + // Note: `passes` is often empty. In that case, it's faster to run + // `builtin_lints` directly rather than bundling it up into the + // `RuntimeCombinedEarlyLintPass`. + let passes = + if pre_expansion { &lint_store.pre_expansion_passes } else { &lint_store.early_passes }; + if passes.is_empty() { + check_ast_node_inner(sess, check_node, context, builtin_lints); + } else { + let mut passes: Vec<_> = passes.iter().map(|mk_pass| (mk_pass)()).collect(); + passes.push(Box::new(builtin_lints)); + let pass = RuntimeCombinedEarlyLintPass { passes: &mut passes[..] }; + check_ast_node_inner(sess, check_node, context, pass); + } +} + +pub fn check_ast_node_inner<'a, T: EarlyLintPass>( + sess: &Session, + check_node: impl EarlyCheckNode<'a>, + context: EarlyContext<'_>, + pass: T, +) { + let mut cx = EarlyContextAndPass { context, pass }; + + cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx)); + + // 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 cx.context.buffered.map { + if !lints.is_empty() { + assert!( + sess.dcx().has_errors().is_some(), + "failed to process buffered lint here (dummy = {})", + id == ast::DUMMY_NODE_ID + ); + break; + } + } +} 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 00000000000..958da177eda --- /dev/null +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -0,0 +1,84 @@ +use crate::{ + context::LintContext, + lints::{EnumIntrinsicsMemDiscriminate, EnumIntrinsicsMemVariant}, + LateContext, LateLintPass, +}; +use rustc_hir as hir; +use rustc_middle::ty::{visit::TypeVisitableExt, Ty}; +use rustc_session::{declare_lint, declare_lint_pass}; +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.has_param() +} + +fn enforce_mem_discriminant( + cx: &LateContext<'_>, + func_expr: &hir::Expr<'_>, + expr_span: Span, + args_span: Span, +) { + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.emit_span_lint( + ENUM_INTRINSICS_NON_ENUMS, + expr_span, + EnumIntrinsicsMemDiscriminate { ty_param, note: args_span }, + ); + } +} + +fn enforce_mem_variant_count(cx: &LateContext<'_>, func_expr: &hir::Expr<'_>, span: Span) { + let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); + if is_non_enum(ty_param) { + cx.emit_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, EnumIntrinsicsMemVariant { ty_param }); + } +} + +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/errors.rs b/compiler/rustc_lint/src/errors.rs new file mode 100644 index 00000000000..46dfaf0b83f --- /dev/null +++ b/compiler/rustc_lint/src/errors.rs @@ -0,0 +1,110 @@ +use crate::fluent_generated as fluent; +use rustc_errors::{codes::*, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic}; +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_session::lint::Level; +use rustc_span::{Span, Symbol}; + +#[derive(Diagnostic)] +#[diag(lint_overruled_attribute, code = E0453)] +pub struct OverruledAttribute<'a> { + #[primary_span] + pub span: Span, + #[label] + pub overruled: Span, + pub lint_level: &'a str, + pub lint_source: Symbol, + #[subdiagnostic] + pub sub: OverruledAttributeSub, +} + +pub enum OverruledAttributeSub { + DefaultSource { id: String }, + NodeSource { span: Span, reason: Option<Symbol> }, + CommandLineSource, +} + +impl Subdiagnostic for OverruledAttributeSub { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + match self { + OverruledAttributeSub::DefaultSource { id } => { + diag.note(fluent::lint_default_source); + diag.arg("id", id); + } + OverruledAttributeSub::NodeSource { span, reason } => { + diag.span_label(span, fluent::lint_node_source); + if let Some(rationale) = reason { + #[allow(rustc::untranslatable_diagnostic)] + diag.note(rationale.to_string()); + } + } + OverruledAttributeSub::CommandLineSource => { + diag.note(fluent::lint_command_line_source); + } + } + } +} + +#[derive(Diagnostic)] +#[diag(lint_malformed_attribute, code = E0452)] +pub struct MalformedAttribute { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sub: MalformedAttributeSub, +} + +#[derive(Subdiagnostic)] +pub enum MalformedAttributeSub { + #[label(lint_bad_attribute_argument)] + BadAttributeArgument(#[primary_span] Span), + #[label(lint_reason_must_be_string_literal)] + ReasonMustBeStringLiteral(#[primary_span] Span), + #[label(lint_reason_must_come_last)] + ReasonMustComeLast(#[primary_span] Span), +} + +#[derive(Diagnostic)] +#[diag(lint_unknown_tool_in_scoped_lint, code = E0710)] +pub struct UnknownToolInScopedLint { + #[primary_span] + pub span: Option<Span>, + pub tool_name: Symbol, + pub lint_name: String, + #[help] + pub is_nightly_build: Option<()>, +} + +#[derive(Diagnostic)] +#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = E0783)] +pub struct BuiltinEllipsisInclusiveRangePatterns { + #[primary_span] + pub span: Span, + #[suggestion(style = "short", code = "{replace}", applicability = "machine-applicable")] + pub suggestion: Span, + pub replace: String, +} + +#[derive(Subdiagnostic)] +#[note(lint_requested_level)] +pub struct RequestedLevel<'a> { + pub level: Level, + pub lint_name: &'a str, +} + +#[derive(Diagnostic)] +#[diag(lint_unsupported_group, code = E0602)] +pub struct UnsupportedGroup { + pub lint_group: String, +} + +#[derive(Diagnostic)] +#[diag(lint_check_name_unknown_tool, code = E0602)] +pub struct CheckNameUnknownTool<'a> { + pub tool_name: Symbol, + #[subdiagnostic] + pub sub: RequestedLevel<'a>, +} diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs new file mode 100644 index 00000000000..04c2ebf189f --- /dev/null +++ b/compiler/rustc_lint/src/expect.rs @@ -0,0 +1,38 @@ +use crate::lints::{Expectation, ExpectationNote}; +use rustc_middle::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::UNFULFILLED_LINT_EXPECTATIONS; +use rustc_session::lint::LintExpectationId; +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>) { + let lint_expectations = tcx.lint_expectations(()); + let fulfilled_expectations = tcx.dcx().steal_fulfilled_expectation_ids(); + + tracing::debug!(?lint_expectations, ?fulfilled_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)) + { + let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale }); + let note = expectation.is_unfulfilled_lint_expectations.then_some(()); + tcx.emit_node_span_lint( + UNFULFILLED_LINT_EXPECTATIONS, + *hir_id, + expectation.emission_span, + Expectation { rationale, note }, + ); + } + } else { + unreachable!("at this stage all `LintExpectationId`s are stable"); + } + } +} diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs new file mode 100644 index 00000000000..aa00fb4573d --- /dev/null +++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs @@ -0,0 +1,180 @@ +use crate::{ + lints::{ + ForLoopsOverFalliblesDiag, ForLoopsOverFalliblesLoopSub, ForLoopsOverFalliblesQuestionMark, + ForLoopsOverFalliblesSuggestion, + }, + LateContext, LateLintPass, LintContext, +}; + +use hir::{Expr, Pat}; +use rustc_hir as hir; +use rustc_infer::{infer::TyCtxtInferExt, traits::ObligationCause}; +use rustc_middle::ty; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{sym, Span}; +use rustc_trait_selection::traits::ObligationCtxt; + +declare_lint! { + /// The `for_loops_over_fallibles` lint checks for `for` loops over `Option` or `Result` values. + /// + /// ### Example + /// + /// ```rust + /// let opt = Some(1); + /// for x in opt { /* ... */} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Both `Option` and `Result` implement `IntoIterator` trait, which allows using them in a `for` loop. + /// `for` loop over `Option` or `Result` will iterate either 0 (if the value is `None`/`Err(_)`) + /// or 1 time (if the value is `Some(_)`/`Ok(_)`). This is not very useful and is more clearly expressed + /// via `if let`. + /// + /// `for` loop can also be accidentally written with the intention to call a function multiple times, + /// while the function returns `Some(_)`, in these cases `while let` loop should be used instead. + /// + /// The "intended" use of `IntoIterator` implementations for `Option` and `Result` is passing them to + /// generic code that expects something implementing `IntoIterator`. For example using `.chain(option)` + /// to optionally add a value to an iterator. + pub FOR_LOOPS_OVER_FALLIBLES, + Warn, + "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" +} + +declare_lint_pass!(ForLoopsOverFallibles => [FOR_LOOPS_OVER_FALLIBLES]); + +impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let Some((pat, arg)) = extract_for_loop(expr) else { return }; + + let ty = cx.typeck_results().expr_ty(arg); + + let (adt, args, ref_mutability) = match ty.kind() { + &ty::Adt(adt, args) => (adt, args, None), + &ty::Ref(_, ty, mutability) => match ty.kind() { + &ty::Adt(adt, args) => (adt, args, Some(mutability)), + _ => return, + }, + _ => return, + }; + + let (article, ty, var) = match adt.did() { + did if cx.tcx.is_diagnostic_item(sym::Option, did) && ref_mutability.is_some() => { + ("a", "Option", "Some") + } + did if cx.tcx.is_diagnostic_item(sym::Option, did) => ("an", "Option", "Some"), + did if cx.tcx.is_diagnostic_item(sym::Result, did) => ("a", "Result", "Ok"), + _ => return, + }; + + let ref_prefix = match ref_mutability { + None => "", + Some(ref_mutability) => ref_mutability.ref_prefix_str(), + }; + + let sub = if let Some(recv) = extract_iterator_next_call(cx, arg) + && let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span) + { + ForLoopsOverFalliblesLoopSub::RemoveNext { + suggestion: recv.span.between(arg.span.shrink_to_hi()), + recv_snip, + } + } else { + ForLoopsOverFalliblesLoopSub::UseWhileLet { + start_span: expr.span.with_hi(pat.span.lo()), + end_span: pat.span.between(arg.span), + var, + } + }; + let question_mark = suggest_question_mark(cx, adt, args, expr.span) + .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() }); + let suggestion = ForLoopsOverFalliblesSuggestion { + var, + start_span: expr.span.with_hi(pat.span.lo()), + end_span: pat.span.between(arg.span), + }; + + cx.emit_span_lint( + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + ForLoopsOverFalliblesDiag { article, ref_prefix, ty, sub, question_mark, suggestion }, + ); + } +} + +fn extract_for_loop<'tcx>(expr: &Expr<'tcx>) -> Option<(&'tcx Pat<'tcx>, &'tcx Expr<'tcx>)> { + if let hir::ExprKind::DropTemps(e) = expr.kind + && let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind + && let hir::ExprKind::Call(_, [arg]) = iterexpr.kind + && let hir::ExprKind::Loop(block, ..) = arm.body.kind + && let [stmt] = block.stmts + && let hir::StmtKind::Expr(e) = stmt.kind + && let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind + && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind + { + Some((field.pat, arg)) + } else { + None + } +} + +fn extract_iterator_next_call<'tcx>( + cx: &LateContext<'_>, + expr: &Expr<'tcx>, +) -> Option<&'tcx Expr<'tcx>> { + // This won't work for `Iterator::next(iter)`, is this an issue? + if let hir::ExprKind::MethodCall(_, recv, _, _) = expr.kind + && cx.typeck_results().type_dependent_def_id(expr.hir_id) == cx.tcx.lang_items().next_fn() + { + Some(recv) + } else { + return None; + } +} + +fn suggest_question_mark<'tcx>( + cx: &LateContext<'tcx>, + adt: ty::AdtDef<'tcx>, + args: ty::GenericArgsRef<'tcx>, + span: Span, +) -> bool { + let Some(body_id) = cx.enclosing_body else { return false }; + let Some(into_iterator_did) = cx.tcx.get_diagnostic_item(sym::IntoIterator) else { + return false; + }; + + if !cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { + return false; + } + + // Check that the function/closure/constant we are in has a `Result` type. + // Otherwise suggesting using `?` may not be a good idea. + { + let ty = cx.typeck_results().expr_ty(cx.tcx.hir().body(body_id).value); + let ty::Adt(ret_adt, ..) = ty.kind() else { return false }; + if !cx.tcx.is_diagnostic_item(sym::Result, ret_adt.did()) { + return false; + } + } + + let ty = args.type_at(0); + let infcx = cx.tcx.infer_ctxt().build(); + let ocx = ObligationCtxt::new(&infcx); + + let body_def_id = cx.tcx.hir().body_owner_def_id(body_id); + let cause = + ObligationCause::new(span, body_def_id, rustc_infer::traits::ObligationCauseCode::Misc); + + ocx.register_bound( + cause, + cx.param_env, + // Erase any region vids from the type, which may not be resolved + infcx.tcx.erase_regions(ty), + into_iterator_did, + ); + + ocx.select_all_or_error().is_empty() +} diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs new file mode 100644 index 00000000000..5da1cbc2283 --- /dev/null +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -0,0 +1,403 @@ +use rustc_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::unord::{UnordMap, UnordSet}; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_middle::query::Providers; +use rustc_middle::ty::layout::LayoutError; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_session::declare_lint; +use rustc_span::{sym, Span, Symbol}; +use rustc_target::abi::FIRST_VARIANT; +use tracing::{debug, instrument}; + +use crate::lints::{BuiltinClashingExtern, BuiltinClashingExternSub}; +use crate::{types, LintVec}; + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { clashing_extern_declarations, ..*providers }; +} + +pub(crate) fn get_lints() -> LintVec { + vec![CLASHING_EXTERN_DECLARATIONS] +} + +fn clashing_extern_declarations(tcx: TyCtxt<'_>, (): ()) { + let mut lint = ClashingExternDeclarations::new(); + for id in tcx.hir_crate_items(()).foreign_items() { + lint.check_foreign_item(tcx, id); + } +} + +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" +} + +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: UnordMap<Symbol, hir::OwnerId>, +} + +/// 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: Default::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::ForeignItemId) -> Option<hir::OwnerId> { + let did = fi.owner_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(&existing_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(existing_id) + } else { + self.seen_decls.insert(name, fi.owner_id) + } + } + + #[instrument(level = "trace", skip(self, tcx))] + fn check_foreign_item<'tcx>(&mut self, tcx: TyCtxt<'tcx>, this_fi: hir::ForeignItemId) { + let DefKind::Fn = tcx.def_kind(this_fi.owner_id) else { return }; + let Some(existing_did) = self.insert(tcx, this_fi) else { return }; + + let existing_decl_ty = tcx.type_of(existing_did).skip_binder(); + let this_decl_ty = tcx.type_of(this_fi.owner_id).instantiate_identity(); + debug!( + "ClashingExternDeclarations: Comparing existing {:?}: {:?} to this {:?}: {:?}", + existing_did, existing_decl_ty, this_fi.owner_id, this_decl_ty + ); + + // Check that the declarations match. + if !structurally_same_type( + tcx, + tcx.param_env(this_fi.owner_id), + existing_decl_ty, + this_decl_ty, + types::CItemKind::Declaration, + ) { + let orig = name_of_extern_decl(tcx, existing_did); + + // Finally, emit the diagnostic. + let this = tcx.item_name(this_fi.owner_id.to_def_id()); + let orig = orig.get_name(); + let previous_decl_label = get_relevant_span(tcx, existing_did); + let mismatch_label = get_relevant_span(tcx, this_fi.owner_id); + let sub = + BuiltinClashingExternSub { tcx, expected: existing_decl_ty, found: this_decl_ty }; + let decorator = if orig == this { + BuiltinClashingExtern::SameName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + } else { + BuiltinClashingExtern::DiffName { + this, + orig, + previous_decl_label, + mismatch_label, + sub, + } + }; + tcx.emit_node_span_lint( + CLASHING_EXTERN_DECLARATIONS, + this_fi.hir_id(), + mismatch_label, + decorator, + ); + } + } +} + +/// 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::OwnerId) -> SymbolName { + if let Some((overridden_link_name, overridden_link_name_span)) = + tcx.codegen_fn_attrs(fi).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, sym::link_name).unwrap().span) + }) + { + SymbolName::Link(overridden_link_name, overridden_link_name_span) + } else { + SymbolName::Normal(tcx.item_name(fi.to_def_id())) + } +} + +/// 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. +fn get_relevant_span(tcx: TyCtxt<'_>, fi: hir::OwnerId) -> Span { + match name_of_extern_decl(tcx, fi) { + SymbolName::Normal(_) => tcx.def_span(fi), + SymbolName::Link(_, annot_span) => annot_span, + } +} + +/// 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>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + let mut seen_types = UnordSet::default(); + structurally_same_type_impl(&mut seen_types, tcx, param_env, a, b, ckind) +} + +fn structurally_same_type_impl<'tcx>( + seen_types: &mut UnordSet<(Ty<'tcx>, Ty<'tcx>)>, + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + a: Ty<'tcx>, + b: Ty<'tcx>, + ckind: types::CItemKind, +) -> bool { + debug!("structurally_same_type_impl(tcx, a = {:?}, b = {:?})", a, b); + + // Given a transparent newtype, reach through and grab the inner + // type unless the newtype makes the type non-null. + let non_transparent_ty = |mut ty: Ty<'tcx>| -> Ty<'tcx> { + loop { + if let ty::Adt(def, args) = *ty.kind() { + let is_transparent = def.repr().transparent(); + let is_non_null = 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_eq!(def.variants().len(), 1); + let v = &def.variant(FIRST_VARIANT); + // continue with `ty`'s non-ZST field, + // otherwise `ty` is a ZST and we can return + if let Some(field) = types::transparent_newtype_field(tcx, v) { + ty = field.ty(tcx, args); + 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. + true + } else 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::TyKind::*; + let a_kind = a.kind(); + let b_kind = b.kind(); + + let compare_layouts = |a, b| -> Result<bool, &'tcx LayoutError<'tcx>> { + debug!("compare_layouts({:?}, {:?})", a, b); + let a_layout = &tcx.layout_of(param_env.and(a))?.layout.abi(); + let b_layout = &tcx.layout_of(param_env.and(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, + tcx, + param_env, + tcx.type_of(a_did).instantiate_identity(), + tcx.type_of(b_did).instantiate_identity(), + 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, tcx, param_env, *a_ty, *b_ty, ckind, + ) + } + (Slice(a_ty), Slice(b_ty)) => { + structurally_same_type_impl(seen_types, tcx, param_env, *a_ty, *b_ty, ckind) + } + (RawPtr(a_ty, a_mutbl), RawPtr(b_ty, b_mutbl)) => { + a_mutbl == b_mutbl + && structurally_same_type_impl( + seen_types, tcx, param_env, *a_ty, *b_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, tcx, param_env, *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.instantiate_bound_regions_with_erased(a_poly_sig); + let b_sig = tcx.instantiate_bound_regions_with_erased(b_poly_sig); + + (a_sig.abi, a_sig.safety, a_sig.c_variadic) + == (b_sig.abi, b_sig.safety, b_sig.c_variadic) + && a_sig.inputs().iter().eq_by(b_sig.inputs().iter(), |a, b| { + structurally_same_type_impl(seen_types, tcx, param_env, *a, *b, ckind) + }) + && structurally_same_type_impl( + seen_types, + tcx, + param_env, + a_sig.output(), + b_sig.output(), + ckind, + ) + } + (Tuple(a_args), Tuple(b_args)) => { + a_args.iter().eq_by(b_args.iter(), |a_ty, b_ty| { + structurally_same_type_impl(seen_types, tcx, param_env, 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(..)) + | (Coroutine(..), Coroutine(..)) + | (CoroutineWitness(..), CoroutineWitness(..)) + | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) + | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) + | (Alias(ty::Opaque, ..), Alias(ty::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) = types::repr_nullable_ptr(tcx, param_env, 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), + } + }) + } +} 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 00000000000..aa8ca1776dc --- /dev/null +++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs @@ -0,0 +1,115 @@ +use crate::{ + lints::{ + HiddenUnicodeCodepointsDiag, HiddenUnicodeCodepointsDiagLabels, + HiddenUnicodeCodepointsDiagSub, + }, + EarlyContext, EarlyLintPass, LintContext, +}; +use ast::util::unicode::{contains_text_flow_control_chars, TEXT_FLOW_CONTROL_CHARS}; +use rustc_ast as ast; +use rustc_session::{declare_lint, declare_lint_pass}; +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(); + + let count = spans.len(); + let labels = point_at_inner_spans + .then_some(HiddenUnicodeCodepointsDiagLabels { spans: spans.clone() }); + let sub = if point_at_inner_spans && !spans.is_empty() { + HiddenUnicodeCodepointsDiagSub::Escape { spans } + } else { + HiddenUnicodeCodepointsDiagSub::NoEscape { spans } + }; + + cx.emit_span_lint( + TEXT_DIRECTION_CODEPOINT_IN_LITERAL, + span, + HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub }, + ); + } +} +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"); + } + } + } + + #[inline] + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { + // byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString` + match &expr.kind { + ast::ExprKind::Lit(token_lit) => { + let text = token_lit.symbol; + if !contains_text_flow_control_chars(text.as_str()) { + return; + } + let padding = match token_lit.kind { + // account for `"` or `'` + ast::token::LitKind::Str | ast::token::LitKind::Char => 1, + // account for `r###"` + ast::token::LitKind::StrRaw(n) => n as u32 + 2, + _ => return, + }; + self.lint_text_direction_codepoint(cx, text, expr.span, padding, true, "literal"); + } + _ => {} + }; + } +} diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs new file mode 100644 index 00000000000..0860413190c --- /dev/null +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -0,0 +1,418 @@ +use rustc_data_structures::fx::FxIndexSet; +use rustc_data_structures::unord::UnordSet; +use rustc_errors::{Applicability, LintDiagnostic}; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_macros::LintDiagnostic; +use rustc_middle::bug; +use rustc_middle::middle::resolve_bound_vars::ResolvedArg; +use rustc_middle::ty::{ + self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; + +use crate::fluent_generated as fluent; +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `impl_trait_overcaptures` lint warns against cases where lifetime + /// capture behavior will differ in edition 2024. + /// + /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope, + /// rather than just the lifetimes that are mentioned in the bounds of the type. + /// Often these sets are equal, but if not, it means that the `impl Trait` may + /// cause erroneous borrow-checker errors. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(precise_capturing)] + /// # #![allow(incomplete_features)] + /// # #![deny(impl_trait_overcaptures)] + /// # use std::fmt::Display; + /// let mut x = vec![]; + /// x.push(1); + /// + /// fn test(x: &Vec<i32>) -> impl Display { + /// x[0] + /// } + /// + /// let element = test(&x); + /// x.push(2); + /// println!("{element}"); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In edition < 2024, the returned `impl Display` doesn't capture the + /// lifetime from the `&Vec<i32>`, so the vector can be mutably borrowed + /// while the `impl Display` is live. + /// + /// To fix this, we can explicitly state that the `impl Display` doesn't + /// capture any lifetimes, using `impl Display + use<>`. + pub IMPL_TRAIT_OVERCAPTURES, + Allow, + "`impl Trait` will capture more lifetimes than possibly intended in edition 2024", + @feature_gate = precise_capturing; + //@future_incompatible = FutureIncompatibleInfo { + // reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + // reference: "<FIXME>", + //}; +} + +declare_lint! { + /// The `impl_trait_redundant_captures` lint warns against cases where use of the + /// precise capturing `use<...>` syntax is not needed. + /// + /// In the 2024 edition, `impl Trait`s will capture all lifetimes in scope. + /// If precise-capturing `use<...>` syntax is used, and the set of parameters + /// that are captures are *equal* to the set of parameters in scope, then + /// the syntax is redundant, and can be removed. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #![feature(precise_capturing, lifetime_capture_rules_2024)] + /// # #![allow(incomplete_features)] + /// # #![deny(impl_trait_redundant_captures)] + /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { x } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// To fix this, remove the `use<'a>`, since the lifetime is already captured + /// since it is in scope. + pub IMPL_TRAIT_REDUNDANT_CAPTURES, + Warn, + "redundant precise-capturing `use<...>` syntax on an `impl Trait`", + @feature_gate = precise_capturing; +} + +declare_lint_pass!( + /// Lint for opaque types that will begin capturing in-scope but unmentioned lifetimes + /// in edition 2024. + ImplTraitOvercaptures => [IMPL_TRAIT_OVERCAPTURES, IMPL_TRAIT_REDUNDANT_CAPTURES] +); + +impl<'tcx> LateLintPass<'tcx> for ImplTraitOvercaptures { + fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'tcx>) { + match &it.kind { + hir::ItemKind::Fn(..) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::ImplItem<'tcx>) { + match &it.kind { + hir::ImplItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::TraitItem<'tcx>) { + match &it.kind { + hir::TraitItemKind::Fn(_, _) => check_fn(cx.tcx, it.owner_id.def_id), + _ => {} + } + } +} + +fn check_fn(tcx: TyCtxt<'_>, parent_def_id: LocalDefId) { + let sig = tcx.fn_sig(parent_def_id).instantiate_identity(); + + let mut in_scope_parameters = FxIndexSet::default(); + // Populate the in_scope_parameters list first with all of the generics in scope + let mut current_def_id = Some(parent_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = tcx.generics_of(def_id); + for param in &generics.own_params { + in_scope_parameters.insert(param.def_id); + } + current_def_id = generics.parent; + } + + // Then visit the signature to walk through all the binders (incl. the late-bound + // vars on the function itself, which we need to count too). + sig.visit_with(&mut VisitOpaqueTypes { + tcx, + parent_def_id, + in_scope_parameters, + seen: Default::default(), + }); +} + +struct VisitOpaqueTypes<'tcx> { + tcx: TyCtxt<'tcx>, + parent_def_id: LocalDefId, + in_scope_parameters: FxIndexSet<DefId>, + seen: FxIndexSet<LocalDefId>, +} + +impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> { + fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>( + &mut self, + t: &ty::Binder<'tcx, T>, + ) -> Self::Result { + // When we get into a binder, we need to add its own bound vars to the scope. + let mut added = vec![]; + for arg in t.bound_vars() { + let arg: ty::BoundVariableKind = arg; + match arg { + ty::BoundVariableKind::Region(ty::BoundRegionKind::BrNamed(def_id, ..)) + | ty::BoundVariableKind::Ty(ty::BoundTyKind::Param(def_id, _)) => { + added.push(def_id); + let unique = self.in_scope_parameters.insert(def_id); + assert!(unique); + } + _ => { + self.tcx.dcx().span_delayed_bug( + self.tcx.def_span(self.parent_def_id), + format!("unsupported bound variable kind: {arg:?}"), + ); + } + } + } + + t.super_visit_with(self); + + // And remove them. The `shift_remove` should be `O(1)` since we're popping + // them off from the end. + for arg in added.into_iter().rev() { + self.in_scope_parameters.shift_remove(&arg); + } + } + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + if !t.has_aliases() { + return; + } + + if let ty::Alias(ty::Projection, opaque_ty) = *t.kind() + && self.tcx.is_impl_trait_in_trait(opaque_ty.def_id) + { + // visit the opaque of the RPITIT + self.tcx + .type_of(opaque_ty.def_id) + .instantiate(self.tcx, opaque_ty.args) + .visit_with(self) + } else if let ty::Alias(ty::Opaque, opaque_ty) = *t.kind() + && let Some(opaque_def_id) = opaque_ty.def_id.as_local() + // Don't recurse infinitely on an opaque + && self.seen.insert(opaque_def_id) + // If it's owned by this function + && let opaque = + self.tcx.hir_node_by_def_id(opaque_def_id).expect_item().expect_opaque_ty() + && let hir::OpaqueTyOrigin::FnReturn(parent_def_id) = opaque.origin + && parent_def_id == self.parent_def_id + { + // Compute the set of args that are captured by the opaque... + let mut captured = FxIndexSet::default(); + let variances = self.tcx.variances_of(opaque_def_id); + let mut current_def_id = Some(opaque_def_id.to_def_id()); + while let Some(def_id) = current_def_id { + let generics = self.tcx.generics_of(def_id); + for param in &generics.own_params { + // A param is captured if it's invariant. + if variances[param.index as usize] != ty::Invariant { + continue; + } + // We need to turn all `ty::Param`/`ConstKind::Param` and + // `ReEarlyParam`/`ReBound` into def ids. + captured.insert(extract_def_id_from_arg( + self.tcx, + generics, + opaque_ty.args[param.index as usize], + )); + } + current_def_id = generics.parent; + } + + // Compute the set of in scope params that are not captured. Get their spans, + // since that's all we really care about them for emitting the diagnostic. + let uncaptured_spans: Vec<_> = self + .in_scope_parameters + .iter() + .filter(|def_id| !captured.contains(*def_id)) + .map(|def_id| self.tcx.def_span(def_id)) + .collect(); + + let opaque_span = self.tcx.def_span(opaque_def_id); + let new_capture_rules = + opaque_span.at_least_rust_2024() || self.tcx.features().lifetime_capture_rules_2024; + + // If we have uncaptured args, and if the opaque doesn't already have + // `use<>` syntax on it, and we're < edition 2024, then warn the user. + if !new_capture_rules + && !opaque.bounds.iter().any(|bound| matches!(bound, hir::GenericBound::Use(..))) + && !uncaptured_spans.is_empty() + { + let suggestion = if let Ok(snippet) = + self.tcx.sess.source_map().span_to_snippet(opaque_span) + && snippet.starts_with("impl ") + { + let (lifetimes, others): (Vec<_>, Vec<_>) = captured + .into_iter() + .partition(|def_id| self.tcx.def_kind(*def_id) == DefKind::LifetimeParam); + // Take all lifetime params first, then all others (ty/ct). + let generics: Vec<_> = lifetimes + .into_iter() + .chain(others) + .map(|def_id| self.tcx.item_name(def_id).to_string()) + .collect(); + // Make sure that we're not trying to name any APITs + if generics.iter().all(|name| !name.starts_with("impl ")) { + Some(( + format!(" + use<{}>", generics.join(", ")), + opaque_span.shrink_to_hi(), + )) + } else { + None + } + } else { + None + }; + + self.tcx.emit_node_span_lint( + IMPL_TRAIT_OVERCAPTURES, + self.tcx.local_def_id_to_hir_id(opaque_def_id), + opaque_span, + ImplTraitOvercapturesLint { + self_ty: t, + num_captured: uncaptured_spans.len(), + uncaptured_spans, + suggestion, + }, + ); + } + // Otherwise, if we are edition 2024, have `use<>` syntax, and + // have no uncaptured args, then we should warn to the user that + // it's redundant to capture all args explicitly. + else if new_capture_rules + && let Some((captured_args, capturing_span)) = + opaque.bounds.iter().find_map(|bound| match *bound { + hir::GenericBound::Use(a, s) => Some((a, s)), + _ => None, + }) + { + let mut explicitly_captured = UnordSet::default(); + for arg in captured_args { + match self.tcx.named_bound_var(arg.hir_id()) { + Some( + ResolvedArg::EarlyBound(def_id) | ResolvedArg::LateBound(_, _, def_id), + ) => { + if self.tcx.def_kind(self.tcx.parent(def_id)) == DefKind::OpaqueTy { + let def_id = self + .tcx + .map_opaque_lifetime_to_parent_lifetime(def_id.expect_local()) + .opt_param_def_id(self.tcx, self.parent_def_id.to_def_id()) + .expect("variable should have been duplicated from parent"); + + explicitly_captured.insert(def_id); + } else { + explicitly_captured.insert(def_id); + } + } + _ => { + self.tcx.dcx().span_delayed_bug( + self.tcx.hir().span(arg.hir_id()), + "no valid for captured arg", + ); + } + } + } + + if self + .in_scope_parameters + .iter() + .all(|def_id| explicitly_captured.contains(def_id)) + { + self.tcx.emit_node_span_lint( + IMPL_TRAIT_REDUNDANT_CAPTURES, + self.tcx.local_def_id_to_hir_id(opaque_def_id), + opaque_span, + ImplTraitRedundantCapturesLint { capturing_span }, + ); + } + } + + // Walk into the bounds of the opaque, too, since we want to get nested opaques + // in this lint as well. Interestingly, one place that I expect this lint to fire + // is for `impl for<'a> Bound<Out = impl Other>`, since `impl Other` will begin + // to capture `'a` in e2024 (even though late-bound vars in opaques are not allowed). + for clause in + self.tcx.item_bounds(opaque_ty.def_id).iter_instantiated(self.tcx, opaque_ty.args) + { + clause.visit_with(self) + } + } + + t.super_visit_with(self); + } +} + +struct ImplTraitOvercapturesLint<'tcx> { + uncaptured_spans: Vec<Span>, + self_ty: Ty<'tcx>, + num_captured: usize, + suggestion: Option<(String, Span)>, +} + +impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> { + fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { + diag.primary_message(fluent::lint_impl_trait_overcaptures); + diag.arg("self_ty", self.self_ty.to_string()) + .arg("num_captured", self.num_captured) + .span_note(self.uncaptured_spans, fluent::lint_note) + .note(fluent::lint_note2); + if let Some((suggestion, span)) = self.suggestion { + diag.span_suggestion( + span, + fluent::lint_suggestion, + suggestion, + Applicability::MachineApplicable, + ); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_impl_trait_redundant_captures)] +struct ImplTraitRedundantCapturesLint { + #[suggestion(lint_suggestion, code = "", applicability = "machine-applicable")] + capturing_span: Span, +} + +fn extract_def_id_from_arg<'tcx>( + tcx: TyCtxt<'tcx>, + generics: &'tcx ty::Generics, + arg: ty::GenericArg<'tcx>, +) -> DefId { + match arg.unpack() { + ty::GenericArgKind::Lifetime(re) => match *re { + ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id, + ty::ReBound( + _, + ty::BoundRegion { kind: ty::BoundRegionKind::BrNamed(def_id, ..), .. }, + ) => def_id, + _ => unreachable!(), + }, + ty::GenericArgKind::Type(ty) => { + let ty::Param(param_ty) = *ty.kind() else { + bug!(); + }; + generics.type_param(param_ty, tcx).def_id + } + ty::GenericArgKind::Const(ct) => { + let ty::ConstKind::Param(param_ct) = ct.kind() else { + bug!(); + }; + generics.const_param(param_ct, tcx).def_id + } + } +} diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs new file mode 100644 index 00000000000..772cc2ff8b9 --- /dev/null +++ b/compiler/rustc_lint/src/internal.rs @@ -0,0 +1,560 @@ +//! Some lints that are only useful in the compiler or crates that use compiler internals, such as +//! Clippy. + +use crate::lints::{ + BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword, + QueryInstability, SpanUseEqCtxtDiag, TyQualified, TykindDiag, TykindKind, UntranslatableDiag, +}; +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_hir::def::Res; +use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath}; +use rustc_hir::{BinOp, BinOpKind, HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind}; +use rustc_middle::ty::{self, Ty as MiddleTy}; +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! { + /// The `default_hash_type` lint detects use of [`std::collections::HashMap`] and + /// [`std::collections::HashSet`], suggesting the use of `FxHashMap`/`FxHashSet`. + /// + /// This can help as `FxHasher` can perform better than the default hasher. DOS protection is + /// not required as input is assumed to be trusted. + 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_node(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) { + // Don't lint imports, only actual usages. + return; + } + let preferred = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::HashMap) => "FxHashMap", + Some(sym::HashSet) => "FxHashSet", + _ => return, + }; + cx.emit_span_lint( + DEFAULT_HASH_TYPES, + path.span, + DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) }, + ); + } +} + +/// Helper function for lints that check for expressions with calls and use typeck results to +/// get the `DefId` and `GenericArgsRef` of the function. +fn typeck_results_of_method_fn<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<(Span, DefId, ty::GenericArgsRef<'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_args(expr.hir_id))) + } + _ => match cx.typeck_results().node_type(expr.hir_id).kind() { + &ty::FnDef(def_id, args) => Some((expr.span, def_id, args)), + _ => None, + }, + } +} + +declare_tool_lint! { + /// The `potential_query_instability` lint detects use of methods which can lead to + /// potential query instability, such as iterating over a `HashMap`. + /// + /// Due to the [incremental compilation](https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html) model, + /// queries must return deterministic, stable results. `HashMap` iteration order can change + /// between compilations, and will introduce instability if query results expose the order. + 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, args)) = typeck_results_of_method_fn(cx, expr) else { return }; + if let Ok(Some(instance)) = ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, args) { + let def_id = instance.def_id(); + if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) { + cx.emit_span_lint( + POTENTIAL_QUERY_INSTABILITY, + span, + QueryInstability { query: cx.tcx.item_name(def_id) }, + ); + } + } + } +} + +declare_tool_lint! { + /// The `usage_of_ty_tykind` lint detects usages of `ty::TyKind::<kind>`, + /// where `ty::<kind>` would suffice. + 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! { + /// The `usage_of_qualified_ty` lint detects usages of `ty::TyKind`, + /// where `Ty` should be used instead. + 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: &rustc_hir::Path<'tcx>, + _: rustc_hir::HirId, + ) { + if let Some(segment) = path.segments.iter().nth_back(1) + && lint_ty_kind_usage(cx, &segment.res) + { + let span = + path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()); + cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span }); + } + } + + 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) { + let span = match cx.tcx.parent_hir_node(ty.hir_id) { + 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 + { + Some(path.span) + } else { + None + } + } + Node::Expr(Expr { kind: ExprKind::Path(qpath), .. }) => { + if let QPath::TypeRelative(qpath_ty, ..) = qpath + && qpath_ty.hir_id == ty.hir_id + { + Some(path.span) + } else { + None + } + } + // 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. + Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. }) => { + if let QPath::TypeRelative(qpath_ty, ..) = qpath + && qpath_ty.hir_id == ty.hir_id + { + Some(path.span) + } else { + None + } + } + _ => None, + }; + + match span { + Some(span) => { + cx.emit_span_lint( + USAGE_OF_TY_TYKIND, + path.span, + TykindKind { suggestion: span }, + ); + } + None => cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag), + } + } else if !ty.span.from_expansion() + && path.segments.len() > 1 + && let Some(ty) = is_ty_or_ty_ctxt(cx, path) + { + cx.emit_span_lint( + USAGE_OF_QUALIFIED_TY, + path.span, + TyQualified { ty, suggestion: path.span }, + ); + } + } + _ => {} + } + } +} + +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::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().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, args[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.ident.to_string()) } else { None } + }) + .collect::<Vec<_>>(); + + if !lifetimes.is_empty() { + return format!("<{}>", lifetimes.join(", ")); + } + } + + String::new() +} + +declare_tool_lint! { + /// The `lint_pass_impl_without_macro` detects manual implementations of a lint + /// pass, without using [`declare_lint_pass`] or [`impl_lint_pass`]. + 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.emit_span_lint( + LINT_PASS_IMPL_WITHOUT_MACRO, + lint_pass.path.span, + LintPassByHand, + ); + } + } + } + } + } +} + +declare_tool_lint! { + /// The `existing_doc_keyword` lint detects use `#[doc()]` keywords + /// that don't exist, e.g. `#[doc(keyword = "..")]`. + 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 keyword = nested + .value_str() + .expect("#[doc(keyword = \"...\")] expected a value!"); + if is_doc_keyword(keyword) { + return; + } + cx.emit_span_lint( + EXISTING_DOC_KEYWORD, + attr.span, + NonExistentDocKeyword { keyword }, + ); + } + } + } + } + } +} + +declare_tool_lint! { + /// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl + /// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings. + /// + /// More details on translatable diagnostics can be found + /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html). + pub rustc::UNTRANSLATABLE_DIAGNOSTIC, + Deny, + "prevent creation of diagnostics which cannot be translated", + report_in_external_macro: true +} + +declare_tool_lint! { + /// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with + /// `#[rustc_lint_diagnostics]` that are outside an `Diagnostic`, `Subdiagnostic`, or + /// `LintDiagnostic` impl (either hand-written or derived). + /// + /// More details on diagnostics implementations can be found + /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html). + pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL, + Deny, + "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` 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<'_>) { + // Only check function calls and method calls. + let (span, def_id, fn_gen_args, call_tys) = match expr.kind { + ExprKind::Call(callee, args) => { + match cx.typeck_results().node_type(callee.hir_id).kind() { + &ty::FnDef(def_id, fn_gen_args) => { + let call_tys: Vec<_> = + args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect(); + (callee.span, def_id, fn_gen_args, call_tys) + } + _ => return, // occurs for fns passed as args + } + } + ExprKind::MethodCall(_segment, _recv, args, _span) => { + let Some((span, def_id, fn_gen_args)) = typeck_results_of_method_fn(cx, expr) + else { + return; + }; + let mut call_tys: Vec<_> = + args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect(); + call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self` + (span, def_id, fn_gen_args, call_tys) + } + _ => return, + }; + + // Is the callee marked with `#[rustc_lint_diagnostics]`? + let has_attr = ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, fn_gen_args) + .ok() + .flatten() + .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics)); + + // Closure: is the type `{D,Subd}iagMessage`? + let is_diag_message = |ty: MiddleTy<'_>| { + if let Some(adt_def) = ty.ty_adt_def() + && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) + && matches!(name, sym::DiagMessage | sym::SubdiagMessage) + { + true + } else { + false + } + }; + + // Does the callee have one or more `impl Into<{D,Subd}iagMessage>` parameters? + let mut impl_into_diagnostic_message_params = vec![]; + let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates; + for (i, ¶m_ty) in fn_sig.inputs().iter().enumerate() { + if let ty::Param(p) = param_ty.kind() { + // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`. + for pred in predicates.iter() { + if let Some(trait_pred) = pred.as_trait_clause() + && let trait_ref = trait_pred.skip_binder().trait_ref + && trait_ref.self_ty() == param_ty // correct predicate for the param? + && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id) + && let ty1 = trait_ref.args.type_at(1) + && is_diag_message(ty1) + { + impl_into_diagnostic_message_params.push((i, p.name)); + } + } + } + } + + // Is the callee interesting? + if !has_attr && impl_into_diagnostic_message_params.is_empty() { + return; + } + + // Is the parent method marked with `#[rustc_lint_diagnostics]`? + let mut parent_has_attr = false; + for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) { + if let Some(owner_did) = hir_id.as_owner() + && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics) + { + parent_has_attr = true; + break; + } + } + + // Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur: + // - inside an impl of `Diagnostic`, `Subdiagnostic`, or `LintDiagnostic`, or + // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`. + // + // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint. + if has_attr && !parent_has_attr { + let mut is_inside_appropriate_impl = false; + for (_hir_id, 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::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic) + { + is_inside_appropriate_impl = true; + break; + } + } + debug!(?is_inside_appropriate_impl); + if !is_inside_appropriate_impl { + cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl); + } + } + + // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg + // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an + // `UNTRANSLATABLE_DIAGNOSTIC` lint. + for (param_i, param_i_p_name) in impl_into_diagnostic_message_params { + // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`? + let arg_ty = call_tys[param_i]; + let is_translatable = is_diag_message(arg_ty) + || matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name); + if !is_translatable { + cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag); + } + } + } +} + +declare_tool_lint! { + /// The `bad_opt_access` lint detects accessing options by field instead of + /// the wrapper function. + 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(lit) = item.lit() + && let ast::LitKind::Str(val, _) = lit.kind + { + cx.emit_span_lint( + BAD_OPT_ACCESS, + expr.span, + BadOptAccessDiag { msg: val.as_str() }, + ); + } + } + } +} + +declare_tool_lint! { + pub rustc::SPAN_USE_EQ_CTXT, + Allow, + "forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead", + report_in_external_macro: true +} + +declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]); + +impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if let ExprKind::Binary(BinOp { node: BinOpKind::Eq | BinOpKind::Ne, .. }, lhs, rhs) = + expr.kind + { + if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) { + cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); + } + } + } +} + +fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match &expr.kind { + ExprKind::MethodCall(..) => cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)), + + _ => false, + } +} diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs new file mode 100644 index 00000000000..0081374922e --- /dev/null +++ b/compiler/rustc_lint/src/invalid_from_utf8.rs @@ -0,0 +1,132 @@ +use std::str::Utf8Error; + +use rustc_ast::LitKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::source_map::Spanned; +use rustc_span::sym; + +use crate::lints::InvalidFromUtf8Diag; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `invalid_from_utf8_unchecked` lint checks for calls to + /// `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut` + /// with a known invalid UTF-8 value. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # #[allow(unused)] + /// unsafe { + /// std::str::from_utf8_unchecked(b"Ru\x82st"); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Creating such a `str` would result in undefined behavior as per documentation + /// for `std::str::from_utf8_unchecked` and `std::str::from_utf8_unchecked_mut`. + pub INVALID_FROM_UTF8_UNCHECKED, + Deny, + "using a non UTF-8 literal in `std::str::from_utf8_unchecked`" +} + +declare_lint! { + /// The `invalid_from_utf8` lint checks for calls to + /// `std::str::from_utf8` and `std::str::from_utf8_mut` + /// with a known invalid UTF-8 value. + /// + /// ### Example + /// + /// ```rust + /// # #[allow(unused)] + /// std::str::from_utf8(b"Ru\x82st"); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Trying to create such a `str` would always return an error as per documentation + /// for `std::str::from_utf8` and `std::str::from_utf8_mut`. + pub INVALID_FROM_UTF8, + Warn, + "using a non UTF-8 literal in `std::str::from_utf8`" +} + +declare_lint_pass!(InvalidFromUtf8 => [INVALID_FROM_UTF8_UNCHECKED, INVALID_FROM_UTF8]); + +impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::Call(path, [arg]) = expr.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && [ + sym::str_from_utf8, + sym::str_from_utf8_mut, + sym::str_from_utf8_unchecked, + sym::str_from_utf8_unchecked_mut, + ] + .contains(&diag_item) + { + let lint = |label, utf8_error: Utf8Error| { + let method = diag_item.as_str().strip_prefix("str_").unwrap(); + let method = format!("std::str::{method}"); + let valid_up_to = utf8_error.valid_up_to(); + let is_unchecked_variant = diag_item.as_str().contains("unchecked"); + + cx.emit_span_lint( + if is_unchecked_variant { + INVALID_FROM_UTF8_UNCHECKED + } else { + INVALID_FROM_UTF8 + }, + expr.span, + if is_unchecked_variant { + InvalidFromUtf8Diag::Unchecked { method, valid_up_to, label } + } else { + InvalidFromUtf8Diag::Checked { method, valid_up_to, label } + }, + ) + }; + + let mut init = cx.expr_or_init_with_outside_body(arg); + while let ExprKind::AddrOf(.., inner) = init.kind { + init = cx.expr_or_init_with_outside_body(inner); + } + match init.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => { + if let LitKind::ByteStr(bytes, _) = &lit + && let Err(utf8_error) = std::str::from_utf8(bytes) + { + lint(init.span, utf8_error); + } + } + ExprKind::Array(args) => { + let elements = args + .iter() + .map(|e| match &e.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => match lit { + LitKind::Byte(b) => Some(*b), + LitKind::Int(b, _) => Some(b.get() as u8), + _ => None, + }, + _ => None, + }) + .collect::<Option<Vec<_>>>(); + + if let Some(elements) = elements + && let Err(utf8_error) = std::str::from_utf8(&elements) + { + lint(init.span, utf8_error); + } + } + _ => {} + } + } + } +} diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs new file mode 100644 index 00000000000..aa328fb87b2 --- /dev/null +++ b/compiler/rustc_lint/src/late.rs @@ -0,0 +1,455 @@ +//! 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_data_structures::stack::ensure_sufficient_stack; +use rustc_data_structures::sync::{join, Lrc}; +use rustc_hir as hir; +use rustc_hir::def_id::{LocalDefId, LocalModDefId}; +use rustc_hir::intravisit as hir_visit; +use rustc_hir::HirId; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, TyCtxt}; +use rustc_session::lint::LintPass; +use rustc_session::Session; +use rustc_span::Span; +use std::any::Any; +use std::cell::Cell; +use tracing::debug; + +/// Extract the [`LintStore`] from [`Session`]. +/// +/// This function exists because [`Session::lint_store`] is type-erased. +pub fn unerased_lint_store(sess: &Session) -> &LintStore { + let store: &Lrc<_> = sess.lint_store.as_ref().unwrap(); + let store: &dyn Any = &**store; + store.downcast_ref().unwrap() +} + +macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ + $cx.pass.$f(&$cx.context, $($args),*); +}) } + +/// Implements the AST traversal for late lint passes. `T` provides the +/// `check_*` methods. +pub 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: 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, check_attributes, attrs); + for attr in attrs { + lint_callback!(self, check_attribute, attr); + } + f(self); + debug!("late context: exit_attrs({:?})", attrs); + lint_callback!(self, check_attributes_post, attrs); + self.context.last_node_with_lint_attrs = prev; + } + + fn with_param_env<F>(&mut self, id: hir::OwnerId, f: F) + where + F: FnOnce(&mut Self), + { + let old_param_env = self.context.param_env; + self.context.param_env = self.context.tcx.param_env(id); + f(self); + self.context.param_env = old_param_env; + } + + fn process_mod(&mut self, m: &'tcx hir::Mod<'tcx>, n: HirId) { + lint_callback!(self, check_mod, m, 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: &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.owner_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.owner_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_field(&mut self, field: &'tcx hir::ExprField<'tcx>) { + self.with_lint_attrs(field.hir_id, |cx| hir_visit::walk_expr_field(cx, field)) + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + ensure_sufficient_stack(|| { + 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: LocalDefId, + ) { + // 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, 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>) { + 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>) { + self.with_lint_attrs(v.hir_id, |cx| { + lint_callback!(cx, check_variant, v); + hir_visit::walk_variant(cx, v); + }) + } + + 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>, _: Span, n: HirId) { + if !self.context.only_module { + self.process_mod(m, n); + } + } + + fn visit_local(&mut self, l: &'tcx hir::LetStmt<'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>) { + self.with_lint_attrs(a.hir_id, |cx| { + lint_callback!(cx, check_arm, a); + hir_visit::walk_arm(cx, 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>) { + lint_callback!(self, check_poly_trait_ref, t); + hir_visit::walk_poly_trait_ref(self, t); + } + + 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.owner_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.owner_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: &hir::Path<'tcx>, id: HirId) { + lint_callback!(self, check_path, p, id); + hir_visit::walk_path(self, p); + } +} + +// Combines multiple lint passes into a single pass, at runtime. Each +// `check_foo` method in `$methods` within this pass simply calls `check_foo` +// once per `$pass`. Compare with `declare_combined_late_lint_pass`, which is +// similar, but combines lint passes at compile time. +struct RuntimeCombinedLateLintPass<'a, 'tcx> { + passes: &'a mut [LateLintPassObject<'tcx>], +} + +#[allow(rustc::lint_pass_impl_without_macro)] +impl LintPass for RuntimeCombinedLateLintPass<'_, '_> { + fn name(&self) -> &'static str { + panic!() + } +} + +macro_rules! impl_late_lint_pass { + ([], [$($(#[$attr:meta])* fn $f:ident($($param:ident: $arg:ty),*);)*]) => { + impl<'tcx> LateLintPass<'tcx> for RuntimeCombinedLateLintPass<'_, 'tcx> { + $(fn $f(&mut self, context: &LateContext<'tcx>, $($param: $arg),*) { + for pass in self.passes.iter_mut() { + pass.$f(context, $($param),*); + } + })* + } + }; +} + +crate::late_lint_methods!(impl_late_lint_pass, []); + +pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalModDefId, + builtin_lints: T, +) { + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + effective_visibilities: tcx.effective_visibilities(()), + last_node_with_lint_attrs: tcx.local_def_id_to_hir_id(module_def_id), + generics: None, + only_module: true, + }; + + // Note: `passes` is often empty. In that case, it's faster to run + // `builtin_lints` directly rather than bundling it up into the + // `RuntimeCombinedLateLintPass`. + let late_module_passes = &unerased_lint_store(tcx.sess).late_module_passes; + if late_module_passes.is_empty() { + late_lint_mod_inner(tcx, module_def_id, context, builtin_lints); + } else { + let mut passes: Vec<_> = late_module_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + passes.push(Box::new(builtin_lints)); + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_mod_inner(tcx, module_def_id, context, pass); + } +} + +fn late_lint_mod_inner<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + module_def_id: LocalModDefId, + context: LateContext<'tcx>, + pass: T, +) { + let mut cx = LateContextAndPass { context, pass }; + + let (module, _span, hir_id) = tcx.hir().get_module(module_def_id); + + cx.with_lint_attrs(hir_id, |cx| { + // There is no module lint that will have the crate itself as an item, so check it here. + if hir_id == hir::CRATE_HIR_ID { + lint_callback!(cx, check_crate,); + } + + cx.process_mod(module, hir_id); + + if hir_id == hir::CRATE_HIR_ID { + lint_callback!(cx, check_crate_post,); + } + }); +} + +fn late_lint_crate<'tcx>(tcx: TyCtxt<'tcx>) { + // Note: `passes` is often empty. + let mut passes: Vec<_> = + unerased_lint_store(tcx.sess).late_passes.iter().map(|mk_pass| (mk_pass)(tcx)).collect(); + + if passes.is_empty() { + return; + } + + let context = LateContext { + tcx, + enclosing_body: None, + cached_typeck_results: Cell::new(None), + param_env: ty::ParamEnv::empty(), + effective_visibilities: tcx.effective_visibilities(()), + last_node_with_lint_attrs: hir::CRATE_HIR_ID, + generics: None, + only_module: false, + }; + + let pass = RuntimeCombinedLateLintPass { passes: &mut passes[..] }; + late_lint_crate_inner(tcx, context, pass); +} + +fn late_lint_crate_inner<'tcx, T: LateLintPass<'tcx>>( + tcx: TyCtxt<'tcx>, + context: LateContext<'tcx>, + pass: T, +) { + 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); + lint_callback!(cx, check_crate_post,); + }) +} + +/// Performs lint checking on a crate. +pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>) { + join( + || { + tcx.sess.time("crate_lints", || { + // Run whole crate non-incremental lints + late_lint_crate(tcx); + }); + }, + || { + 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/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs new file mode 100644 index 00000000000..e6c274ec09a --- /dev/null +++ b/compiler/rustc_lint/src/let_underscore.rs @@ -0,0 +1,172 @@ +use crate::{ + lints::{NonBindingLet, NonBindingLetSub}, + LateContext, LateLintPass, LintContext, +}; +use rustc_errors::MultiSpan; +use rustc_hir as hir; +use rustc_middle::ty; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{sym, Symbol}; + +declare_lint! { + /// The `let_underscore_drop` lint checks for statements which don't bind + /// an expression which has a non-trivial Drop implementation to anything, + /// causing the expression to be dropped immediately instead of at end of + /// scope. + /// + /// ### Example + /// + /// ```rust + /// struct SomeStruct; + /// impl Drop for SomeStruct { + /// fn drop(&mut self) { + /// println!("Dropping SomeStruct"); + /// } + /// } + /// + /// fn main() { + /// #[warn(let_underscore_drop)] + /// // SomeStruct is dropped immediately instead of at end of scope, + /// // so "Dropping SomeStruct" is printed before "end of main". + /// // The order of prints would be reversed if SomeStruct was bound to + /// // a name (such as "_foo"). + /// let _ = SomeStruct; + /// println!("end of main"); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Statements which assign an expression to an underscore causes the + /// expression to immediately drop instead of extending the expression's + /// lifetime to the end of the scope. This is usually unintended, + /// especially for types like `MutexGuard`, which are typically used to + /// lock a mutex for the duration of an entire scope. + /// + /// If you want to extend the expression's lifetime to the end of the scope, + /// assign an underscore-prefixed name (such as `_foo`) to the expression. + /// If you do actually want to drop the expression immediately, then + /// calling `std::mem::drop` on the expression is clearer and helps convey + /// intent. + pub LET_UNDERSCORE_DROP, + Allow, + "non-binding let on a type that implements `Drop`" +} + +declare_lint! { + /// The `let_underscore_lock` lint checks for statements which don't bind + /// a mutex to anything, causing the lock to be released immediately instead + /// of at end of scope, which is typically incorrect. + /// + /// ### Example + /// ```rust,compile_fail + /// use std::sync::{Arc, Mutex}; + /// use std::thread; + /// let data = Arc::new(Mutex::new(0)); + /// + /// thread::spawn(move || { + /// // The lock is immediately released instead of at the end of the + /// // scope, which is probably not intended. + /// let _ = data.lock().unwrap(); + /// println!("doing some work"); + /// let mut lock = data.lock().unwrap(); + /// *lock += 1; + /// }); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Statements which assign an expression to an underscore causes the + /// expression to immediately drop instead of extending the expression's + /// lifetime to the end of the scope. This is usually unintended, + /// especially for types like `MutexGuard`, which are typically used to + /// lock a mutex for the duration of an entire scope. + /// + /// If you want to extend the expression's lifetime to the end of the scope, + /// assign an underscore-prefixed name (such as `_foo`) to the expression. + /// If you do actually want to drop the expression immediately, then + /// calling `std::mem::drop` on the expression is clearer and helps convey + /// intent. + pub LET_UNDERSCORE_LOCK, + Deny, + "non-binding let on a synchronization lock" +} + +declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]); + +const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [ + rustc_span::sym::MutexGuard, + rustc_span::sym::RwLockReadGuard, + rustc_span::sym::RwLockWriteGuard, +]; + +impl<'tcx> LateLintPass<'tcx> for LetUnderscore { + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::LetStmt<'_>) { + if matches!(local.source, rustc_hir::LocalSource::AsyncFn) { + return; + } + + let mut top_level = true; + + // We recursively walk through all patterns, so that we can catch cases where the lock is + // nested in a pattern. For the basic `let_underscore_drop` lint, we only look at the top + // level, since there are many legitimate reasons to bind a sub-pattern to an `_`, if we're + // only interested in the rest. But with locks, we prefer having the chance of "false + // positives" over missing cases, since the effects can be quite catastrophic. + local.pat.walk_always(|pat| { + let is_top_level = top_level; + top_level = false; + + if !matches!(pat.kind, hir::PatKind::Wild) { + return; + } + + let ty = cx.typeck_results().pat_ty(pat); + + // If the type has a trivial Drop implementation, then it doesn't + // matter that we drop the value immediately. + if !ty.needs_drop(cx.tcx, cx.param_env) { + return; + } + // Lint for patterns like `mutex.lock()`, which returns `Result<MutexGuard, _>` as well. + let potential_lock_type = match ty.kind() { + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { + args.type_at(0) + } + _ => ty, + }; + let is_sync_lock = match potential_lock_type.kind() { + ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS + .iter() + .any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())), + _ => false, + }; + + let can_use_init = is_top_level.then_some(local.init).flatten(); + + let sub = NonBindingLetSub { + suggestion: pat.span, + // We can't suggest `drop()` when we're on the top level. + drop_fn_start_end: can_use_init + .map(|init| (local.span.until(init.span), init.span.shrink_to_hi())), + is_assign_desugar: matches!(local.source, rustc_hir::LocalSource::AssignDesugar(_)), + }; + if is_sync_lock { + let mut span = MultiSpan::from_span(pat.span); + span.push_span_label( + pat.span, + "this lock is not assigned to a binding and is immediately dropped".to_string(), + ); + cx.emit_span_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub }); + // Only emit let_underscore_drop for top-level `_` patterns. + } else if can_use_init.is_some() { + cx.emit_span_lint(LET_UNDERSCORE_DROP, local.span, NonBindingLet::DropType { sub }); + } + }); + } +} diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs new file mode 100644 index 00000000000..0df34c32e38 --- /dev/null +++ b/compiler/rustc_lint/src/levels.rs @@ -0,0 +1,1125 @@ +use crate::errors::{CheckNameUnknownTool, RequestedLevel, UnsupportedGroup}; +use crate::lints::{ + DeprecatedLintNameFromCommandLine, RemovedLintFromCommandLine, RenamedLintFromCommandLine, + UnknownLintFromCommandLine, +}; +use crate::{ + builtin::MISSING_DOCS, + context::{CheckLintNameResult, LintStore}, + fluent_generated as fluent, + late::unerased_lint_store, + lints::{ + DeprecatedLintName, IgnoredUnlessCrateSpecified, OverruledAttributeLint, RemovedLint, + RenamedLint, RenamedLintSuggestion, UnknownLint, UnknownLintSuggestion, + }, +}; +use rustc_ast as ast; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::{Diag, LintDiagnostic, MultiSpan}; +use rustc_feature::{Features, GateIssue}; +use rustc_hir as hir; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::HirId; +use rustc_index::IndexVec; +use rustc_middle::bug; +use rustc_middle::hir::nested_filter; +use rustc_middle::lint::{ + lint_level, reveal_actual_level, LevelAndSource, LintExpectation, LintLevelSource, + ShallowLintLevelMap, +}; +use rustc_middle::query::Providers; +use rustc_middle::ty::{RegisteredTools, TyCtxt}; +use rustc_session::lint::{ + builtin::{ + self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES, + UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, + }, + Level, Lint, LintExpectationId, LintId, +}; +use rustc_session::Session; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use tracing::{debug, instrument}; + +use crate::errors::{ + MalformedAttribute, MalformedAttributeSub, OverruledAttribute, OverruledAttributeSub, + UnknownToolInScopedLint, +}; + +/// Collection of lint levels for the whole crate. +/// This is used by AST-based lints, which do not +/// wait until we have built HIR to be emitted. +#[derive(Debug)] +struct LintLevelSets { + /// Linked list of specifications. + list: IndexVec<LintStackIndex, LintSet>, +} + +rustc_index::newtype_index! { + struct LintStackIndex { + const COMMAND_LINE = 0; + } +} + +/// Specifications found at this position in the stack. This map only represents the lints +/// found for one set of attributes (like `shallow_lint_levels_on` does). +/// +/// We store the level specifications as a linked list. +/// Each `LintSet` represents a set of attributes on the same AST node. +/// The `parent` forms a linked list that matches the AST tree. +/// This way, walking the linked list is equivalent to walking the AST bottom-up +/// to find the specifications for a given lint. +#[derive(Debug)] +struct LintSet { + // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which + // flag. + specs: FxIndexMap<LintId, LevelAndSource>, + parent: LintStackIndex, +} + +impl LintLevelSets { + fn new() -> Self { + LintLevelSets { list: IndexVec::new() } + } + + fn get_lint_level( + &self, + lint: &'static Lint, + idx: LintStackIndex, + aux: Option<&FxIndexMap<LintId, LevelAndSource>>, + sess: &Session, + ) -> LevelAndSource { + let lint = LintId::of(lint); + let (level, mut src) = self.raw_lint_id_level(lint, idx, aux); + let level = reveal_actual_level(level, &mut src, sess, lint, |id| { + self.raw_lint_id_level(id, idx, aux) + }); + (level, src) + } + + fn raw_lint_id_level( + &self, + id: LintId, + mut idx: LintStackIndex, + aux: Option<&FxIndexMap<LintId, LevelAndSource>>, + ) -> (Option<Level>, LintLevelSource) { + if let Some(specs) = aux + && let Some(&(level, src)) = specs.get(&id) + { + return (Some(level), src); + } + + loop { + let LintSet { ref specs, parent } = self.list[idx]; + if let Some(&(level, src)) = specs.get(&id) { + return (Some(level), src); + } + if idx == COMMAND_LINE { + return (None, LintLevelSource::Default); + } + idx = parent; + } + } +} + +fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExpectation)> { + let store = unerased_lint_store(tcx.sess); + + let mut builder = LintLevelsBuilder { + sess: tcx.sess, + features: tcx.features(), + provider: QueryMapExpectationsWrapper { + tcx, + cur: hir::CRATE_HIR_ID, + specs: ShallowLintLevelMap::default(), + expectations: Vec::new(), + unstable_to_stable_ids: FxIndexMap::default(), + empty: FxIndexMap::default(), + }, + lint_added_lints: false, + store, + registered_tools: tcx.registered_tools(()), + }; + + builder.add_command_line(); + builder.add_id(hir::CRATE_HIR_ID); + tcx.hir().walk_toplevel_module(&mut builder); + + tcx.dcx().update_unstable_expectation_id(&builder.provider.unstable_to_stable_ids); + + builder.provider.expectations +} + +#[instrument(level = "trace", skip(tcx), ret)] +fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap { + let store = unerased_lint_store(tcx.sess); + let attrs = tcx.hir_attrs(owner); + + let mut levels = LintLevelsBuilder { + sess: tcx.sess, + features: tcx.features(), + provider: LintLevelQueryMap { + tcx, + cur: owner.into(), + specs: ShallowLintLevelMap::default(), + empty: FxIndexMap::default(), + attrs, + }, + lint_added_lints: false, + store, + registered_tools: tcx.registered_tools(()), + }; + + if owner == hir::CRATE_OWNER_ID { + levels.add_command_line(); + } + + match attrs.map.range(..) { + // There is only something to do if there are attributes at all. + [] => {} + // Most of the time, there is only one attribute. Avoid fetching HIR in that case. + &[(local_id, _)] => levels.add_id(HirId { owner, local_id }), + // Otherwise, we need to visit the attributes in source code order, so we fetch HIR and do + // a standard visit. + // FIXME(#102522) Just iterate on attrs once that iteration order matches HIR's. + _ => match tcx.hir_owner_node(owner) { + hir::OwnerNode::Item(item) => levels.visit_item(item), + hir::OwnerNode::ForeignItem(item) => levels.visit_foreign_item(item), + hir::OwnerNode::TraitItem(item) => levels.visit_trait_item(item), + hir::OwnerNode::ImplItem(item) => levels.visit_impl_item(item), + hir::OwnerNode::Crate(mod_) => { + levels.add_id(hir::CRATE_HIR_ID); + levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID) + } + hir::OwnerNode::Synthetic => unreachable!(), + }, + } + + let specs = levels.provider.specs; + + #[cfg(debug_assertions)] + for (_, v) in specs.specs.iter() { + debug_assert!(!v.is_empty()); + } + + specs +} + +pub struct TopDown { + sets: LintLevelSets, + cur: LintStackIndex, +} + +pub trait LintLevelsProvider { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource>; + fn insert(&mut self, id: LintId, lvl: LevelAndSource); + fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource; + fn push_expectation(&mut self, _id: LintExpectationId, _expectation: LintExpectation) {} +} + +impl LintLevelsProvider for TopDown { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { + &self.sets.list[self.cur].specs + } + + fn insert(&mut self, id: LintId, lvl: LevelAndSource) { + self.sets.list[self.cur].specs.insert(id, lvl); + } + + fn get_lint_level(&self, lint: &'static Lint, sess: &Session) -> LevelAndSource { + self.sets.get_lint_level(lint, self.cur, Some(self.current_specs()), sess) + } +} + +struct LintLevelQueryMap<'tcx> { + tcx: TyCtxt<'tcx>, + cur: HirId, + specs: ShallowLintLevelMap, + /// Empty hash map to simplify code. + empty: FxIndexMap<LintId, LevelAndSource>, + attrs: &'tcx hir::AttributeMap<'tcx>, +} + +impl LintLevelsProvider for LintLevelQueryMap<'_> { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { + self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty) + } + fn insert(&mut self, id: LintId, lvl: LevelAndSource) { + self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); + } + fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) + } +} + +struct QueryMapExpectationsWrapper<'tcx> { + tcx: TyCtxt<'tcx>, + /// HirId of the currently investigated element. + cur: HirId, + /// Level map for `cur`. + specs: ShallowLintLevelMap, + expectations: Vec<(LintExpectationId, LintExpectation)>, + unstable_to_stable_ids: FxIndexMap<LintExpectationId, LintExpectationId>, + /// Empty hash map to simplify code. + empty: FxIndexMap<LintId, LevelAndSource>, +} + +impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { + self.specs.specs.get(&self.cur.local_id).unwrap_or(&self.empty) + } + fn insert(&mut self, id: LintId, lvl: LevelAndSource) { + self.specs.specs.get_mut_or_insert_default(self.cur.local_id).insert(id, lvl); + } + fn get_lint_level(&self, lint: &'static Lint, _: &Session) -> LevelAndSource { + // We cannot use `tcx.lint_level_at_node` because we want to know in which order the + // attributes have been inserted, in particular whether an `expect` follows a `forbid`. + self.specs.lint_level_id_at_node(self.tcx, LintId::of(lint), self.cur) + } + fn push_expectation(&mut self, id: LintExpectationId, expectation: LintExpectation) { + let LintExpectationId::Stable { attr_id: Some(attr_id), hir_id, attr_index, .. } = id + else { + bug!("unstable expectation id should already be mapped") + }; + let key = LintExpectationId::Unstable { attr_id, lint_index: None }; + + self.unstable_to_stable_ids.entry(key).or_insert(LintExpectationId::Stable { + hir_id, + attr_index, + lint_index: None, + attr_id: None, + }); + + self.expectations.push((id.normalize(), expectation)); + } +} + +impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { + fn add_id(&mut self, hir_id: HirId) { + self.provider.cur = hir_id; + self.add( + self.provider.attrs.get(hir_id.local_id), + hir_id == hir::CRATE_HIR_ID, + Some(hir_id), + ); + } +} + +impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.provider.tcx.hir() + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.add_id(param.hir_id); + intravisit::walk_param(self, param); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + self.add_id(it.hir_id()); + intravisit::walk_item(self, it); + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.add_id(it.hir_id()); + intravisit::walk_foreign_item(self, it); + } + + fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) { + // We will call `add_id` 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.add_id(e.hir_id); + intravisit::walk_expr(self, e); + } + + fn visit_expr_field(&mut self, f: &'tcx hir::ExprField<'tcx>) { + self.add_id(f.hir_id); + intravisit::walk_expr_field(self, f); + } + + fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { + self.add_id(s.hir_id); + intravisit::walk_field_def(self, s); + } + + fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) { + self.add_id(v.hir_id); + intravisit::walk_variant(self, v); + } + + fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) { + self.add_id(l.hir_id); + intravisit::walk_local(self, l); + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + self.add_id(a.hir_id); + intravisit::walk_arm(self, a); + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + self.add_id(trait_item.hir_id()); + intravisit::walk_trait_item(self, trait_item); + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + self.add_id(impl_item.hir_id()); + intravisit::walk_impl_item(self, impl_item); + } +} + +impl<'tcx> LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> { + fn add_id(&mut self, hir_id: HirId) { + // Change both the `HirId` and the associated specs. + self.provider.cur = hir_id; + self.provider.specs.specs.clear(); + self.add(self.provider.tcx.hir().attrs(hir_id), hir_id == hir::CRATE_HIR_ID, Some(hir_id)); + } +} + +impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, QueryMapExpectationsWrapper<'tcx>> { + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.provider.tcx.hir() + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.add_id(param.hir_id); + intravisit::walk_param(self, param); + } + + fn visit_item(&mut self, it: &'tcx hir::Item<'tcx>) { + self.add_id(it.hir_id()); + intravisit::walk_item(self, it); + } + + fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) { + self.add_id(it.hir_id()); + intravisit::walk_foreign_item(self, it); + } + + fn visit_stmt(&mut self, e: &'tcx hir::Stmt<'tcx>) { + // We will call `add_id` 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.add_id(e.hir_id); + intravisit::walk_expr(self, e); + } + + fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) { + self.add_id(s.hir_id); + intravisit::walk_field_def(self, s); + } + + fn visit_variant(&mut self, v: &'tcx hir::Variant<'tcx>) { + self.add_id(v.hir_id); + intravisit::walk_variant(self, v); + } + + fn visit_local(&mut self, l: &'tcx hir::LetStmt<'tcx>) { + self.add_id(l.hir_id); + intravisit::walk_local(self, l); + } + + fn visit_arm(&mut self, a: &'tcx hir::Arm<'tcx>) { + self.add_id(a.hir_id); + intravisit::walk_arm(self, a); + } + + fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) { + self.add_id(trait_item.hir_id()); + intravisit::walk_trait_item(self, trait_item); + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + self.add_id(impl_item.hir_id()); + intravisit::walk_impl_item(self, impl_item); + } +} + +pub struct LintLevelsBuilder<'s, P> { + sess: &'s Session, + features: &'s Features, + provider: P, + lint_added_lints: bool, + store: &'s LintStore, + registered_tools: &'s RegisteredTools, +} + +pub(crate) struct BuilderPush { + prev: LintStackIndex, +} + +impl<'s> LintLevelsBuilder<'s, TopDown> { + pub(crate) fn new( + sess: &'s Session, + features: &'s Features, + lint_added_lints: bool, + store: &'s LintStore, + registered_tools: &'s RegisteredTools, + ) -> Self { + let mut builder = LintLevelsBuilder { + sess, + features, + provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, + lint_added_lints, + store, + registered_tools, + }; + builder.process_command_line(); + assert_eq!(builder.provider.sets.list.len(), 1); + builder + } + + fn process_command_line(&mut self) { + self.provider.cur = self + .provider + .sets + .list + .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE }); + self.add_command_line(); + } + + /// 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.provider.cur; + self.provider.cur = + self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); + + self.add(attrs, is_crate_node, source_hir_id); + + if self.provider.current_specs().is_empty() { + self.provider.sets.list.pop(); + self.provider.cur = prev; + } + + BuilderPush { prev } + } + + /// Called after `push` when the scope of a set of attributes are exited. + pub(crate) fn pop(&mut self, push: BuilderPush) { + self.provider.cur = push.prev; + std::mem::forget(push); + } +} + +#[cfg(debug_assertions)] +impl Drop for BuilderPush { + fn drop(&mut self) { + panic!("Found a `push` without a `pop`."); + } +} + +impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { + pub(crate) fn sess(&self) -> &Session { + self.sess + } + + pub(crate) fn features(&self) -> &Features { + self.features + } + + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { + self.provider.current_specs() + } + + fn insert(&mut self, id: LintId, lvl: LevelAndSource) { + self.provider.insert(id, lvl) + } + + fn add_command_line(&mut self) { + for &(ref lint_name, level) in &self.sess.opts.lint_opts { + // Checks the validity of lint names derived from the command line. + 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(_)) + { + self.sess + .dcx() + .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); + } + match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) { + CheckLintNameResult::Renamed(ref replace) => { + let name = lint_name.as_str(); + let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; + let requested_level = RequestedLevel { level, lint_name }; + let lint = RenamedLintFromCommandLine { name, suggestion, requested_level }; + self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); + } + CheckLintNameResult::Removed(ref reason) => { + let name = lint_name.as_str(); + let requested_level = RequestedLevel { level, lint_name }; + let lint = RemovedLintFromCommandLine { name, reason, requested_level }; + self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); + } + CheckLintNameResult::NoLint(suggestion) => { + let name = lint_name.clone(); + let suggestion = suggestion.map(|(replace, from_rustc)| { + UnknownLintSuggestion::WithoutSpan { replace, from_rustc } + }); + let requested_level = RequestedLevel { level, lint_name }; + let lint = UnknownLintFromCommandLine { name, suggestion, requested_level }; + self.emit_lint(UNKNOWN_LINTS, lint); + } + CheckLintNameResult::Tool(_, Some(ref replace)) => { + let name = lint_name.clone(); + let requested_level = RequestedLevel { level, lint_name }; + let lint = DeprecatedLintNameFromCommandLine { name, replace, requested_level }; + self.emit_lint(RENAMED_AND_REMOVED_LINTS, lint); + } + CheckLintNameResult::NoTool => { + self.sess.dcx().emit_err(CheckNameUnknownTool { + tool_name: tool_name.unwrap(), + sub: RequestedLevel { level, lint_name }, + }); + } + _ => {} + }; + + let orig_level = level; + let lint_flag_val = Symbol::intern(lint_name); + + let Ok(ids) = self.store.find_lints(lint_name) else { + // errors already handled 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, true) { + let src = LintLevelSource::CommandLine(lint_flag_val, orig_level); + self.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, (mut level, src): LevelAndSource) { + let (old_level, old_src) = self.provider.get_lint_level(id.lint, self.sess); + if let Level::Expect(id) = &mut level + && let LintExpectationId::Stable { .. } = id + { + *id = id.normalize(); + } + // 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 self.lint_added_lints && level != Level::Forbid && old_level == Level::Forbid { + // 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 { name, .. } => self.store.is_lint_group(name), + 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 sub = match old_src { + LintLevelSource::Default => { + OverruledAttributeSub::DefaultSource { id: id.to_string() } + } + LintLevelSource::Node { span, reason, .. } => { + OverruledAttributeSub::NodeSource { span, reason } + } + LintLevelSource::CommandLine(_, _) => OverruledAttributeSub::CommandLineSource, + }; + if !fcw_warning { + self.sess.dcx().emit_err(OverruledAttribute { + span: src.span(), + overruled: src.span(), + lint_level: level.as_str(), + lint_source: src.name(), + sub, + }); + } else { + self.emit_span_lint( + FORBIDDEN_LINT_GROUPS, + src.span().into(), + OverruledAttributeLint { + overruled: src.span(), + lint_level: level.as_str(), + lint_source: src.name(), + sub, + }, + ); + } + + // 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.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)) + } + // Keep `ForceWarn` level but drop the expectation + (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)), + // Set the lint level as normal + _ => self.insert(id, (level, src)), + }; + } + + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable + fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) { + let sess = self.sess; + for (attr_index, attr) in attrs.iter().enumerate() { + if attr.has_name(sym::automatically_derived) { + self.insert( + LintId::of(SINGLE_USE_LIFETIMES), + (Level::Allow, LintLevelSource::Default), + ); + continue; + } + + // `#[doc(hidden)]` disables missing_docs check. + if attr.has_name(sym::doc) + && attr + .meta_item_list() + .is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden)) + { + self.insert(LintId::of(MISSING_DOCS), (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 LintExpectationId::Unstable { attr_id, lint_index } = unstable_id else { + bug!("stable id Level::from_attr") + }; + + let stable_id = LintExpectationId::Stable { + hir_id, + attr_index: attr_index.try_into().unwrap(), + lint_index, + // We pass the previous unstable attr_id such that we can trace the ast id + // when building a map to go from unstable to stable id. + attr_id: Some(attr_id), + }; + + Level::Expect(stable_id) + } + Some(lvl) => lvl, + }; + + let Some(mut metas) = attr.meta_item_list() else { continue }; + + // Check whether `metas` is empty, and get its last element. + let Some(tail_li) = metas.last() else { + // 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; + 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 { + reason = Some(rationale); + } else { + sess.dcx().emit_err(MalformedAttribute { + span: name_value.span, + sub: MalformedAttributeSub::ReasonMustBeStringLiteral( + name_value.span, + ), + }); + } + // found reason, reslice meta list to exclude it + metas.pop().unwrap(); + } else { + sess.dcx().emit_err(MalformedAttribute { + span: item.span, + sub: MalformedAttributeSub::BadAttributeArgument(item.span), + }); + } + } + ast::MetaItemKind::List(_) => { + sess.dcx().emit_err(MalformedAttribute { + span: item.span, + sub: MalformedAttributeSub::BadAttributeArgument(item.span), + }); + } + } + } + + 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 sub = if let Some(item) = li.meta_item() + && let ast::MetaItemKind::NameValue(_) = item.kind + && item.path == sym::reason + { + MalformedAttributeSub::ReasonMustComeLast(sp) + } else { + MalformedAttributeSub::BadAttributeArgument(sp) + }; + + sess.dcx().emit_err(MalformedAttribute { span: sp, sub }); + 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.provider.push_expectation( + expect_id, + LintExpectation::new( + reason, + sp, + is_unfulfilled_lint_expectations, + tool_name, + ), + ); + } + let src = LintLevelSource::Node { + name: meta_item + .path + .segments + .last() + .expect("empty lint name") + .ident + .name, + span: sp, + reason, + }; + for &id in *ids { + if self.check_gated_lint(id, attr.span, false) { + self.insert_spec(id, (level, src)); + } + } + } + + CheckLintNameResult::Tool(ids, new_lint_name) => { + let src = match new_lint_name { + None => { + let complete_name = + &format!("{}::{}", tool_ident.unwrap().name, name); + LintLevelSource::Node { + name: Symbol::intern(complete_name), + span: sp, + reason, + } + } + Some(new_lint_name) => { + self.emit_span_lint( + builtin::RENAMED_AND_REMOVED_LINTS, + sp.into(), + DeprecatedLintName { + name, + suggestion: sp, + replace: new_lint_name, + }, + ); + LintLevelSource::Node { + name: Symbol::intern(new_lint_name), + span: sp, + reason, + } + } + }; + for &id in *ids { + if self.check_gated_lint(id, attr.span, false) { + self.insert_spec(id, (level, src)); + } + } + if let Level::Expect(expect_id) = level { + self.provider.push_expectation( + expect_id, + LintExpectation::new(reason, sp, false, tool_name), + ); + } + } + + CheckLintNameResult::MissingTool => { + // If `MissingTool` 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 => { + sess.dcx().emit_err(UnknownToolInScopedLint { + span: tool_ident.map(|ident| ident.span), + tool_name: tool_name.unwrap(), + lint_name: pprust::path_to_string(&meta_item.path), + is_nightly_build: sess.is_nightly_build().then_some(()), + }); + continue; + } + + _ if !self.lint_added_lints => {} + + CheckLintNameResult::Renamed(ref replace) => { + let suggestion = + RenamedLintSuggestion::WithSpan { suggestion: sp, replace }; + let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + let lint = RenamedLint { name: name.as_str(), suggestion }; + self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); + } + + CheckLintNameResult::Removed(ref reason) => { + let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + let lint = RemovedLint { name: name.as_str(), reason }; + self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); + } + + CheckLintNameResult::NoLint(suggestion) => { + let name = tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + let suggestion = suggestion.map(|(replace, from_rustc)| { + UnknownLintSuggestion::WithSpan { suggestion: sp, replace, from_rustc } + }); + let lint = UnknownLint { name, suggestion }; + self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint); + } + } + // 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::Renamed(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. + let CheckLintNameResult::Ok(ids) = + self.store.check_lint_name(&new_name, None, self.registered_tools) + else { + panic!("renamed lint does not exist: {new_name}"); + }; + + let src = + LintLevelSource::Node { name: Symbol::intern(&new_name), span: sp, reason }; + for &id in ids { + if self.check_gated_lint(id, attr.span, false) { + self.insert_spec(id, (level, src)); + } + } + if let Level::Expect(expect_id) = level { + self.provider.push_expectation( + expect_id, + LintExpectation::new(reason, sp, false, tool_name), + ); + } + } + } + } + + if self.lint_added_lints && !is_crate_node { + for (id, &(level, ref src)) in self.current_specs().iter() { + if !id.lint.crate_level_only { + continue; + } + + let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src + else { + continue; + }; + + self.emit_span_lint( + UNUSED_ATTRIBUTES, + lint_attr_span.into(), + IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name }, + ); + // don't set a separate error for every lint in the group + break; + } + } + } + + /// Checks if the lint is gated on a feature that is not enabled. + /// + /// Returns `true` if the lint's feature is enabled. + #[track_caller] + fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool { + let feature = if let Some(feature) = lint_id.lint.feature_gate + && !self.features.active(feature) + { + // Lint is behind a feature that is not enabled; eventually return false. + feature + } else { + // Lint is ungated or its feature is enabled; exit early. + return true; + }; + + if self.lint_added_lints { + let lint = builtin::UNKNOWN_LINTS; + let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); + // FIXME: make this translatable + #[allow(rustc::diagnostic_outside_of_impl)] + #[allow(rustc::untranslatable_diagnostic)] + lint_level(self.sess, lint, level, src, Some(span.into()), |lint| { + lint.primary_message(fluent::lint_unknown_gated_lint); + lint.arg("name", lint_id.lint.name_lower()); + lint.note(fluent::lint_note); + rustc_session::parse::add_feature_diagnostics_for_issue( + lint, + &self.sess, + feature, + GateIssue::Language, + lint_from_cli, + None, + ); + }); + } + + false + } + + /// Find the lint level for a lint. + pub fn lint_level(&self, lint: &'static Lint) -> LevelAndSource { + self.provider.get_lint_level(lint, self.sess) + } + + /// Used to emit a lint-related diagnostic based on the current state of + /// this lint context. + /// + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature + #[rustc_lint_diagnostics] + #[track_caller] + pub(crate) fn opt_span_lint( + &self, + lint: &'static Lint, + span: Option<MultiSpan>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + ) { + let (level, src) = self.lint_level(lint); + lint_level(self.sess, lint, level, src, span, decorate) + } + + #[track_caller] + pub fn emit_span_lint( + &self, + lint: &'static Lint, + span: MultiSpan, + decorate: impl for<'a> LintDiagnostic<'a, ()>, + ) { + let (level, src) = self.lint_level(lint); + lint_level(self.sess, lint, level, src, Some(span), |lint| { + decorate.decorate_lint(lint); + }); + } + + #[track_caller] + pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) { + let (level, src) = self.lint_level(lint); + lint_level(self.sess, lint, level, src, None, |lint| { + decorate.decorate_lint(lint); + }); + } +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { shallow_lint_levels_on, lint_expectations, ..*providers }; +} + +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/lib.rs b/compiler/rustc_lint/src/lib.rs new file mode 100644 index 00000000000..8be8996e4c8 --- /dev/null +++ b/compiler/rustc_lint/src/lib.rs @@ -0,0 +1,607 @@ +//! 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 external lint plugins, as is done for Clippy. +//! +//! 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. + +// tidy-alphabetical-start +#![allow(internal_features)] +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] +#![doc(rust_logo)] +#![feature(array_windows)] +#![feature(box_patterns)] +#![feature(control_flow_enum)] +#![feature(extract_if)] +#![feature(if_let_guard)] +#![feature(iter_order_by)] +#![feature(let_chains)] +#![feature(rustc_attrs)] +#![feature(rustdoc_internals)] +#![feature(trait_upcasting)] +// tidy-alphabetical-end + +mod async_closures; +mod async_fn_in_trait; +pub mod builtin; +mod context; +mod deref_into_dyn_supertrait; +mod drop_forget_useless; +mod early; +mod enum_intrinsics_non_enums; +mod errors; +mod expect; +mod for_loops_over_fallibles; +mod foreign_modules; +pub mod hidden_unicode_codepoints; +mod impl_trait_overcaptures; +mod internal; +mod invalid_from_utf8; +mod late; +mod let_underscore; +mod levels; +mod lints; +mod macro_expr_fragment_specifier_2024_migration; +mod map_unit_fn; +mod methods; +mod multiple_supertrait_upcastable; +mod non_ascii_idents; +mod non_fmt_panic; +mod non_local_def; +mod nonstandard_style; +mod noop_method_call; +mod opaque_hidden_inferred_bound; +mod pass_by_value; +mod passes; +mod ptr_nulls; +mod redundant_semicolon; +mod reference_casting; +mod shadowed_into_iter; +mod traits; +mod types; +mod unit_bindings; +mod unused; + +pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER}; + +use rustc_hir::def_id::LocalModDefId; +use rustc_middle::query::Providers; +use rustc_middle::ty::TyCtxt; + +use async_closures::AsyncClosureUsage; +use async_fn_in_trait::AsyncFnInTrait; +use builtin::*; +use deref_into_dyn_supertrait::*; +use drop_forget_useless::*; +use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; +use for_loops_over_fallibles::*; +use hidden_unicode_codepoints::*; +use impl_trait_overcaptures::ImplTraitOvercaptures; +use internal::*; +use invalid_from_utf8::*; +use let_underscore::*; +use macro_expr_fragment_specifier_2024_migration::*; +use map_unit_fn::*; +use methods::*; +use multiple_supertrait_upcastable::*; +use non_ascii_idents::*; +use non_fmt_panic::NonPanicFmt; +use non_local_def::*; +use nonstandard_style::*; +use noop_method_call::*; +use opaque_hidden_inferred_bound::*; +use pass_by_value::*; +use ptr_nulls::*; +use redundant_semicolon::*; +use reference_casting::*; +use shadowed_into_iter::ShadowedIntoIter; +use traits::*; +use types::*; +use unit_bindings::*; +use unused::*; + +/// Useful for other parts of the compiler / Clippy. +pub use builtin::{MissingDoc, 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, late_lint_mod, 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::{LintPass, LintVec}; + +rustc_fluent_macro::fluent_messages! { "../messages.ftl" } + +pub fn provide(providers: &mut Providers) { + levels::provide(providers); + expect::provide(providers); + foreign_modules::provide(providers); + *providers = Providers { lint_mod, ..*providers }; +} + +fn lint_mod(tcx: TyCtxt<'_>, module_def_id: LocalModDefId) { + late_lint_mod(tcx, module_def_id, BuiltinCombinedModuleLateLintPass::new()); +} + +early_lint_methods!( + declare_combined_early_lint_pass, + [ + pub BuiltinCombinedPreExpansionLintPass, + [ + KeywordIdents: KeywordIdents, + ] + ] +); + +early_lint_methods!( + declare_combined_early_lint_pass, + [ + pub BuiltinCombinedEarlyLintPass, + [ + UnusedParens: UnusedParens::new(), + UnusedBraces: UnusedBraces, + UnusedImportBraces: UnusedImportBraces, + UnsafeCode: UnsafeCode, + SpecialModuleName: SpecialModuleName, + AnonymousParameters: AnonymousParameters, + EllipsisInclusiveRangePatterns: EllipsisInclusiveRangePatterns::default(), + NonCamelCaseTypes: NonCamelCaseTypes, + DeprecatedAttr: DeprecatedAttr::new(), + WhileTrue: WhileTrue, + NonAsciiIdents: NonAsciiIdents, + HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, + IncompleteInternalFeatures: IncompleteInternalFeatures, + RedundantSemicolons: RedundantSemicolons, + UnusedDocComment: UnusedDocComment, + Expr2024: Expr2024, + ] + ] +); + +late_lint_methods!( + declare_combined_late_lint_pass, + [ + BuiltinCombinedModuleLateLintPass, + [ + ForLoopsOverFallibles: ForLoopsOverFallibles, + DerefIntoDynSupertrait: DerefIntoDynSupertrait, + DropForgetUseless: DropForgetUseless, + HardwiredLints: HardwiredLints, + ImproperCTypesDeclarations: ImproperCTypesDeclarations, + ImproperCTypesDefinitions: ImproperCTypesDefinitions, + InvalidFromUtf8: InvalidFromUtf8, + VariantSizeDifferences: VariantSizeDifferences, + PathStatements: PathStatements, + LetUnderscore: LetUnderscore, + InvalidReferenceCasting: InvalidReferenceCasting, + // Depends on referenced function signatures in expressions + UnusedResults: UnusedResults, + UnitBindings: UnitBindings, + NonUpperCaseGlobals: NonUpperCaseGlobals, + NonShorthandFieldPatterns: NonShorthandFieldPatterns, + UnusedAllocation: UnusedAllocation, + // Depends on types used in type definitions + MissingCopyImplementations: MissingCopyImplementations, + // Depends on referenced function signatures in expressions + PtrNullChecks: PtrNullChecks, + MutableTransmutes: MutableTransmutes, + TypeAliasBounds: TypeAliasBounds, + TrivialConstraints: TrivialConstraints, + TypeLimits: TypeLimits::new(), + NonSnakeCase: NonSnakeCase, + InvalidNoMangleItems: InvalidNoMangleItems, + // Depends on effective visibilities + UnreachablePub: UnreachablePub, + ExplicitOutlivesRequirements: ExplicitOutlivesRequirements, + InvalidValue: InvalidValue, + DerefNullPtr: DerefNullPtr, + UnstableFeatures: UnstableFeatures, + UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller, + ShadowedIntoIter: ShadowedIntoIter, + DropTraitConstraints: DropTraitConstraints, + TemporaryCStringAsPtr: TemporaryCStringAsPtr, + NonPanicFmt: NonPanicFmt, + NoopMethodCall: NoopMethodCall, + EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums, + InvalidAtomicOrdering: InvalidAtomicOrdering, + AsmLabels: AsmLabels, + OpaqueHiddenInferredBound: OpaqueHiddenInferredBound, + MultipleSupertraitUpcastable: MultipleSupertraitUpcastable, + MapUnitFn: MapUnitFn, + MissingDebugImplementations: MissingDebugImplementations, + MissingDoc: MissingDoc, + AsyncClosureUsage: AsyncClosureUsage, + AsyncFnInTrait: AsyncFnInTrait, + NonLocalDefinitions: NonLocalDefinitions::default(), + ImplTraitOvercaptures: ImplTraitOvercaptures, + ] + ] +); + +pub fn new_lint_store(internal_lints: bool) -> LintStore { + let mut lint_store = LintStore::new(); + + register_builtins(&mut lint_store); + 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) { + macro_rules! add_lint_group { + ($name:expr, $($lint:ident),*) => ( + store.register_group(false, $name, None, vec![$(LintId::of($lint)),*]); + ) + } + + store.register_lints(&BuiltinCombinedPreExpansionLintPass::get_lints()); + store.register_lints(&BuiltinCombinedEarlyLintPass::get_lints()); + store.register_lints(&BuiltinCombinedModuleLateLintPass::get_lints()); + store.register_lints(&foreign_modules::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, + MAP_UNIT_FN + ); + + add_lint_group!("let_underscore", LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK); + + 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 + ); + + add_lint_group!("keyword_idents", KEYWORD_IDENTS_2018, KEYWORD_IDENTS_2024); + + add_lint_group!( + "refining_impl_trait", + REFINING_IMPL_TRAIT_REACHABLE, + REFINING_IMPL_TRAIT_INTERNAL + ); + + // 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_2018"); + 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("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"); + store.register_renamed("unused_tuple_struct_fields", "dead_code"); + store.register_renamed("static_mut_ref", "static_mut_refs"); + + // 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", + ); + store.register_removed( + "const_err", + "converted into hard error, see issue #71800 \ + <https://github.com/rust-lang/rust/issues/71800> for more information", + ); + store.register_removed( + "safe_packed_borrows", + "converted into hard error, see issue #82523 \ + <https://github.com/rust-lang/rust/issues/82523> for more information", + ); + store.register_removed( + "unaligned_references", + "converted into hard error, see issue #82523 \ + <https://github.com/rust-lang/rust/issues/82523> for more information", + ); + store.register_removed( + "private_in_public", + "replaced with another group of lints, see RFC \ + <https://rust-lang.github.io/rfcs/2145-type-privacy.html> for more information", + ); + store.register_removed( + "invalid_alignment", + "converted into hard error, see PR #104616 \ + <https://github.com/rust-lang/rust/pull/104616> for more information", + ); + store.register_removed( + "implied_bounds_entailment", + "converted into hard error, see PR #117984 \ + <https://github.com/rust-lang/rust/pull/117984> for more information", + ); + store.register_removed( + "coinductive_overlap_in_coherence", + "converted into hard error, see PR #118649 \ + <https://github.com/rust-lang/rust/pull/118649> for more information", + ); + store.register_removed( + "illegal_floating_point_literal_pattern", + "no longer a warning, float patterns behave the same as `==`", + ); + store.register_removed( + "nontrivial_structural_match", + "no longer needed, see RFC #3535 \ + <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information", + ); + store.register_removed( + "suspicious_auto_trait_impls", + "no longer needed, see #93367 \ + <https://github.com/rust-lang/rust/issues/93367> for more information", + ); + store.register_removed( + "const_patterns_without_partial_eq", + "converted into hard error, see RFC #3535 \ + <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information", + ); + store.register_removed( + "indirect_structural_match", + "converted into hard error, see RFC #3535 \ + <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information", + ); + store.register_removed( + "pointer_structural_match", + "converted into hard error, see RFC #3535 \ + <https://rust-lang.github.io/rfcs/3535-constants-in-patterns.html> for more information", + ); + store.register_removed( + "box_pointers", + "it does not detect other kinds of allocations, and existed only for historical reasons", + ); +} + +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_mod_pass(|_| Box::new(DefaultHashTypes)); + store.register_lints(&QueryStability::get_lints()); + store.register_late_mod_pass(|_| Box::new(QueryStability)); + store.register_lints(&ExistingDocKeyword::get_lints()); + store.register_late_mod_pass(|_| Box::new(ExistingDocKeyword)); + store.register_lints(&TyTyKind::get_lints()); + store.register_late_mod_pass(|_| Box::new(TyTyKind)); + store.register_lints(&Diagnostics::get_lints()); + store.register_late_mod_pass(|_| Box::new(Diagnostics)); + store.register_lints(&BadOptAccess::get_lints()); + store.register_late_mod_pass(|_| Box::new(BadOptAccess)); + store.register_lints(&PassByValue::get_lints()); + store.register_late_mod_pass(|_| Box::new(PassByValue)); + store.register_lints(&SpanUseEqCtxt::get_lints()); + store.register_late_mod_pass(|_| Box::new(SpanUseEqCtxt)); + // 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), + LintId::of(SPAN_USE_EQ_CTXT), + ], + ); +} + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs new file mode 100644 index 00000000000..308bb73f4ce --- /dev/null +++ b/compiler/rustc_lint/src/lints.rs @@ -0,0 +1,2945 @@ +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] +use std::num::NonZero; + +use crate::errors::RequestedLevel; +use crate::fluent_generated as fluent; +use rustc_errors::{ + codes::*, Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString, + ElidedLifetimeInPathSubdiag, EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp, + Subdiagnostic, SuggestionStyle, +}; +use rustc_hir::{def::Namespace, def_id::DefId}; +use rustc_macros::{LintDiagnostic, Subdiagnostic}; +use rustc_middle::ty::{ + inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt, +}; +use rustc_session::{lint::AmbiguityErrorDiag, Session}; +use rustc_span::{ + edition::Edition, + sym, + symbol::{Ident, MacroRulesNormalizedIdent}, + Span, Symbol, +}; + +use crate::{ + builtin::InitError, builtin::TypeAliasBounds, errors::OverruledAttributeSub, LateContext, +}; + +// array_into_iter.rs +#[derive(LintDiagnostic)] +#[diag(lint_shadowed_into_iter)] +pub struct ShadowedIntoIterDiag { + pub target: &'static str, + pub edition: &'static str, + #[suggestion(lint_use_iter_suggestion, code = "iter", applicability = "machine-applicable")] + pub suggestion: Span, + #[subdiagnostic] + pub sub: Option<ShadowedIntoIterDiagSub>, +} + +#[derive(Subdiagnostic)] +pub enum ShadowedIntoIterDiagSub { + #[suggestion(lint_remove_into_iter_suggestion, code = "", applicability = "maybe-incorrect")] + RemoveIntoIter { + #[primary_span] + span: Span, + }, + #[multipart_suggestion( + lint_use_explicit_into_iter_suggestion, + applicability = "maybe-incorrect" + )] + UseExplicitIntoIter { + #[suggestion_part(code = "IntoIterator::into_iter(")] + start_span: Span, + #[suggestion_part(code = ")")] + end_span: Span, + }, +} + +// builtin.rs +#[derive(LintDiagnostic)] +#[diag(lint_builtin_while_true)] +pub struct BuiltinWhileTrue { + #[suggestion(style = "short", code = "{replace}", applicability = "machine-applicable")] + pub suggestion: Span, + pub replace: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_non_shorthand_field_patterns)] +pub struct BuiltinNonShorthandFieldPatterns { + pub ident: Ident, + #[suggestion(code = "{prefix}{ident}", applicability = "machine-applicable")] + pub suggestion: Span, + pub prefix: &'static str, +} + +#[derive(LintDiagnostic)] +pub enum BuiltinUnsafe { + #[diag(lint_builtin_allow_internal_unsafe)] + AllowInternalUnsafe, + #[diag(lint_builtin_unsafe_block)] + UnsafeBlock, + #[diag(lint_builtin_unsafe_extern_block)] + UnsafeExternBlock, + #[diag(lint_builtin_unsafe_trait)] + UnsafeTrait, + #[diag(lint_builtin_unsafe_impl)] + UnsafeImpl, + #[diag(lint_builtin_no_mangle_fn)] + #[note(lint_builtin_overridden_symbol_name)] + NoMangleFn, + #[diag(lint_builtin_export_name_fn)] + #[note(lint_builtin_overridden_symbol_name)] + ExportNameFn, + #[diag(lint_builtin_link_section_fn)] + #[note(lint_builtin_overridden_symbol_section)] + LinkSectionFn, + #[diag(lint_builtin_no_mangle_static)] + #[note(lint_builtin_overridden_symbol_name)] + NoMangleStatic, + #[diag(lint_builtin_export_name_static)] + #[note(lint_builtin_overridden_symbol_name)] + ExportNameStatic, + #[diag(lint_builtin_link_section_static)] + #[note(lint_builtin_overridden_symbol_section)] + LinkSectionStatic, + #[diag(lint_builtin_no_mangle_method)] + #[note(lint_builtin_overridden_symbol_name)] + NoMangleMethod, + #[diag(lint_builtin_export_name_method)] + #[note(lint_builtin_overridden_symbol_name)] + ExportNameMethod, + #[diag(lint_builtin_decl_unsafe_fn)] + DeclUnsafeFn, + #[diag(lint_builtin_decl_unsafe_method)] + DeclUnsafeMethod, + #[diag(lint_builtin_impl_unsafe_method)] + ImplUnsafeMethod, + #[diag(lint_builtin_global_asm)] + #[note(lint_builtin_global_macro_unsafety)] + GlobalAsm, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_missing_doc)] +pub struct BuiltinMissingDoc<'a> { + pub article: &'a str, + pub desc: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_missing_copy_impl)] +pub struct BuiltinMissingCopyImpl; + +pub struct BuiltinMissingDebugImpl<'a> { + pub tcx: TyCtxt<'a>, + pub def_id: DefId, +} + +// Needed for def_path_str +impl<'a> LintDiagnostic<'a, ()> for BuiltinMissingDebugImpl<'_> { + fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { + diag.primary_message(fluent::lint_builtin_missing_debug_impl); + diag.arg("debug", self.tcx.def_path_str(self.def_id)); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_anonymous_params)] +pub struct BuiltinAnonymousParams<'a> { + #[suggestion(code = "_: {ty_snip}")] + pub suggestion: (Span, Applicability), + pub ty_snip: &'a str, +} + +// FIXME(davidtwco) translatable deprecated attr +#[derive(LintDiagnostic)] +#[diag(lint_builtin_deprecated_attr_link)] +pub struct BuiltinDeprecatedAttrLink<'a> { + pub name: Symbol, + pub reason: &'a str, + pub link: &'a str, + #[subdiagnostic] + pub suggestion: BuiltinDeprecatedAttrLinkSuggestion<'a>, +} + +#[derive(Subdiagnostic)] +pub enum BuiltinDeprecatedAttrLinkSuggestion<'a> { + #[suggestion(lint_msg_suggestion, code = "", applicability = "machine-applicable")] + Msg { + #[primary_span] + suggestion: Span, + msg: &'a str, + }, + #[suggestion(lint_default_suggestion, code = "", applicability = "machine-applicable")] + Default { + #[primary_span] + suggestion: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_deprecated_attr_used)] +pub struct BuiltinDeprecatedAttrUsed { + pub name: String, + #[suggestion( + lint_builtin_deprecated_attr_default_suggestion, + style = "short", + code = "", + applicability = "machine-applicable" + )] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_unused_doc_comment)] +pub struct BuiltinUnusedDocComment<'a> { + pub kind: &'a str, + #[label] + pub label: Span, + #[subdiagnostic] + pub sub: BuiltinUnusedDocCommentSub, +} + +#[derive(Subdiagnostic)] +pub enum BuiltinUnusedDocCommentSub { + #[help(lint_plain_help)] + PlainHelp, + #[help(lint_block_help)] + BlockHelp, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_no_mangle_generic)] +pub struct BuiltinNoMangleGeneric { + // Use of `#[no_mangle]` suggests FFI intent; correct + // fix may be to monomorphize source by hand + #[suggestion(style = "short", code = "", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_const_no_mangle)] +pub struct BuiltinConstNoMangle { + #[suggestion(code = "pub static", applicability = "machine-applicable")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_mutable_transmutes)] +pub struct BuiltinMutablesTransmutes; + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_unstable_features)] +pub struct BuiltinUnstableFeatures; + +// lint_ungated_async_fn_track_caller +pub struct BuiltinUngatedAsyncFnTrackCaller<'a> { + pub label: Span, + pub session: &'a Session, +} + +impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_ungated_async_fn_track_caller); + diag.span_label(self.label, fluent::lint_label); + rustc_session::parse::add_feature_diagnostics( + diag, + self.session, + sym::async_fn_track_caller, + ); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_unreachable_pub)] +pub struct BuiltinUnreachablePub<'a> { + pub what: &'a str, + #[suggestion(code = "pub(crate)")] + pub suggestion: (Span, Applicability), + #[help] + pub help: Option<()>, +} + +pub struct SuggestChangingAssocTypes<'a, 'b> { + pub ty: &'a rustc_hir::Ty<'b>, +} + +impl<'a, 'b> Subdiagnostic for SuggestChangingAssocTypes<'a, 'b> { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + // 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, 'b, G: EmissionGuarantee> { + err: &'a mut Diag<'b, G>, + } + impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> { + fn visit_qpath( + &mut self, + qpath: &rustc_hir::QPath<'_>, + id: rustc_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) + } + } + + // Let's go for a walk! + let mut visitor = WalkAssocTypes { err: diag }; + visitor.visit_ty(self.ty); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_type_alias_where_clause)] +pub struct BuiltinTypeAliasWhereClause<'a, 'b> { + #[suggestion(code = "", applicability = "machine-applicable")] + pub suggestion: Span, + #[subdiagnostic] + pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_type_alias_generic_bounds)] +pub struct BuiltinTypeAliasGenericBounds<'a, 'b> { + #[subdiagnostic] + pub suggestion: BuiltinTypeAliasGenericBoundsSuggestion, + #[subdiagnostic] + pub sub: Option<SuggestChangingAssocTypes<'a, 'b>>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_macro_expr_fragment_specifier_2024_migration)] +pub struct MacroExprFragment2024 { + #[suggestion(code = "expr_2021", applicability = "machine-applicable")] + pub suggestion: Span, +} + +pub struct BuiltinTypeAliasGenericBoundsSuggestion { + pub suggestions: Vec<(Span, String)>, +} + +impl Subdiagnostic for BuiltinTypeAliasGenericBoundsSuggestion { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + diag.multipart_suggestion( + fluent::lint_suggestion, + self.suggestions, + Applicability::MachineApplicable, + ); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_trivial_bounds)] +pub struct BuiltinTrivialBounds<'a> { + pub predicate_kind_name: &'a str, + pub predicate: Clause<'a>, +} + +#[derive(LintDiagnostic)] +pub enum BuiltinEllipsisInclusiveRangePatternsLint { + #[diag(lint_builtin_ellipsis_inclusive_range_patterns)] + Parenthesise { + #[suggestion(code = "{replace}", applicability = "machine-applicable")] + suggestion: Span, + replace: String, + }, + #[diag(lint_builtin_ellipsis_inclusive_range_patterns)] + NonParenthesise { + #[suggestion(style = "short", code = "..=", applicability = "machine-applicable")] + suggestion: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_keyword_idents)] +pub struct BuiltinKeywordIdents { + pub kw: Ident, + pub next: Edition, + #[suggestion(code = "r#{kw}", applicability = "machine-applicable")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_explicit_outlives)] +pub struct BuiltinExplicitOutlives { + pub count: usize, + #[subdiagnostic] + pub suggestion: BuiltinExplicitOutlivesSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion)] +pub struct BuiltinExplicitOutlivesSuggestion { + #[suggestion_part(code = "")] + pub spans: Vec<Span>, + #[applicability] + pub applicability: Applicability, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_incomplete_features)] +pub struct BuiltinIncompleteFeatures { + pub name: Symbol, + #[subdiagnostic] + pub note: Option<BuiltinFeatureIssueNote>, + #[subdiagnostic] + pub help: Option<BuiltinIncompleteFeaturesHelp>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_internal_features)] +#[note] +pub struct BuiltinInternalFeatures { + pub name: Symbol, +} + +#[derive(Subdiagnostic)] +#[help(lint_help)] +pub struct BuiltinIncompleteFeaturesHelp; + +#[derive(Subdiagnostic)] +#[note(lint_note)] +pub struct BuiltinFeatureIssueNote { + pub n: NonZero<u32>, +} + +pub struct BuiltinUnpermittedTypeInit<'a> { + pub msg: DiagMessage, + pub ty: Ty<'a>, + pub label: Span, + pub sub: BuiltinUnpermittedTypeInitSub, + pub tcx: TyCtxt<'a>, +} + +impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(self.msg); + diag.arg("ty", self.ty); + diag.span_label(self.label, fluent::lint_builtin_unpermitted_type_init_label); + if let InhabitedPredicate::True = self.ty.inhabited_predicate(self.tcx) { + // Only suggest late `MaybeUninit::assume_init` initialization if the type is inhabited. + diag.span_label( + self.label, + fluent::lint_builtin_unpermitted_type_init_label_suggestion, + ); + } + self.sub.add_to_diag(diag); + } +} + +// FIXME(davidtwco): make translatable +pub struct BuiltinUnpermittedTypeInitSub { + pub err: InitError, +} + +impl Subdiagnostic for BuiltinUnpermittedTypeInitSub { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + let mut err = self.err; + loop { + if let Some(span) = err.span { + diag.span_note(span, err.message); + } else { + diag.note(err.message); + } + if let Some(e) = err.nested { + err = *e; + } else { + break; + } + } + } +} + +#[derive(LintDiagnostic)] +pub enum BuiltinClashingExtern<'a> { + #[diag(lint_builtin_clashing_extern_same_name)] + SameName { + this: Symbol, + orig: Symbol, + #[label(lint_previous_decl_label)] + previous_decl_label: Span, + #[label(lint_mismatch_label)] + mismatch_label: Span, + #[subdiagnostic] + sub: BuiltinClashingExternSub<'a>, + }, + #[diag(lint_builtin_clashing_extern_diff_name)] + DiffName { + this: Symbol, + orig: Symbol, + #[label(lint_previous_decl_label)] + previous_decl_label: Span, + #[label(lint_mismatch_label)] + mismatch_label: Span, + #[subdiagnostic] + sub: BuiltinClashingExternSub<'a>, + }, +} + +// FIXME(davidtwco): translatable expected/found +pub struct BuiltinClashingExternSub<'a> { + pub tcx: TyCtxt<'a>, + pub expected: Ty<'a>, + pub found: Ty<'a>, +} + +impl Subdiagnostic for BuiltinClashingExternSub<'_> { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + let mut expected_str = DiagStyledString::new(); + expected_str.push(self.expected.fn_sig(self.tcx).to_string(), false); + let mut found_str = DiagStyledString::new(); + found_str.push(self.found.fn_sig(self.tcx).to_string(), true); + diag.note_expected_found(&"", expected_str, &"", found_str); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_builtin_deref_nullptr)] +pub struct BuiltinDerefNullptr { + #[label] + pub label: Span, +} + +// FIXME: migrate fluent::lint::builtin_asm_labels + +#[derive(LintDiagnostic)] +pub enum BuiltinSpecialModuleNameUsed { + #[diag(lint_builtin_special_module_name_used_lib)] + #[note] + #[help] + Lib, + #[diag(lint_builtin_special_module_name_used_main)] + #[note] + Main, +} + +// deref_into_dyn_supertrait.rs +#[derive(LintDiagnostic)] +#[diag(lint_supertrait_as_deref_target)] +pub struct SupertraitAsDerefTarget<'a> { + pub self_ty: Ty<'a>, + pub supertrait_principal: PolyExistentialTraitRef<'a>, + pub target_principal: PolyExistentialTraitRef<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub label2: Option<SupertraitAsDerefTargetLabel>, +} + +#[derive(Subdiagnostic)] +#[label(lint_label2)] +pub struct SupertraitAsDerefTargetLabel { + #[primary_span] + pub label: Span, +} + +// enum_intrinsics_non_enums.rs +#[derive(LintDiagnostic)] +#[diag(lint_enum_intrinsics_mem_discriminant)] +pub struct EnumIntrinsicsMemDiscriminate<'a> { + pub ty_param: Ty<'a>, + #[note] + pub note: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_enum_intrinsics_mem_variant)] +#[note] +pub struct EnumIntrinsicsMemVariant<'a> { + pub ty_param: Ty<'a>, +} + +// expect.rs +#[derive(LintDiagnostic)] +#[diag(lint_expectation)] +pub struct Expectation { + #[subdiagnostic] + pub rationale: Option<ExpectationNote>, + #[note] + pub note: Option<()>, +} + +#[derive(Subdiagnostic)] +#[note(lint_rationale)] +pub struct ExpectationNote { + pub rationale: Symbol, +} + +// ptr_nulls.rs +#[derive(LintDiagnostic)] +pub enum PtrNullChecksDiag<'a> { + #[diag(lint_ptr_null_checks_fn_ptr)] + #[help(lint_help)] + FnPtr { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, + #[diag(lint_ptr_null_checks_ref)] + Ref { + orig_ty: Ty<'a>, + #[label] + label: Span, + }, + #[diag(lint_ptr_null_checks_fn_ret)] + FnRet { fn_name: Ident }, +} + +// for_loops_over_fallibles.rs +#[derive(LintDiagnostic)] +#[diag(lint_for_loops_over_fallibles)] +pub struct ForLoopsOverFalliblesDiag<'a> { + pub article: &'static str, + pub ref_prefix: &'static str, + pub ty: &'static str, + #[subdiagnostic] + pub sub: ForLoopsOverFalliblesLoopSub<'a>, + #[subdiagnostic] + pub question_mark: Option<ForLoopsOverFalliblesQuestionMark>, + #[subdiagnostic] + pub suggestion: ForLoopsOverFalliblesSuggestion<'a>, +} + +#[derive(Subdiagnostic)] +pub enum ForLoopsOverFalliblesLoopSub<'a> { + #[suggestion(lint_remove_next, code = ".by_ref()", applicability = "maybe-incorrect")] + RemoveNext { + #[primary_span] + suggestion: Span, + recv_snip: String, + }, + #[multipart_suggestion(lint_use_while_let, applicability = "maybe-incorrect")] + UseWhileLet { + #[suggestion_part(code = "while let {var}(")] + start_span: Span, + #[suggestion_part(code = ") = ")] + end_span: Span, + var: &'a str, + }, +} + +#[derive(Subdiagnostic)] +#[suggestion(lint_use_question_mark, code = "?", applicability = "maybe-incorrect")] +pub struct ForLoopsOverFalliblesQuestionMark { + #[primary_span] + pub suggestion: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")] +pub struct ForLoopsOverFalliblesSuggestion<'a> { + pub var: &'a str, + #[suggestion_part(code = "if let {var}(")] + pub start_span: Span, + #[suggestion_part(code = ") = ")] + pub end_span: Span, +} + +#[derive(Subdiagnostic)] +pub enum UseLetUnderscoreIgnoreSuggestion { + #[note(lint_use_let_underscore_ignore_suggestion)] + Note, + #[multipart_suggestion( + lint_use_let_underscore_ignore_suggestion, + style = "verbose", + applicability = "maybe-incorrect" + )] + Suggestion { + #[suggestion_part(code = "let _ = ")] + start_span: Span, + #[suggestion_part(code = "")] + end_span: Span, + }, +} + +// drop_forget_useless.rs +#[derive(LintDiagnostic)] +#[diag(lint_dropping_references)] +pub struct DropRefDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub sugg: UseLetUnderscoreIgnoreSuggestion, +} + +#[derive(LintDiagnostic)] +#[diag(lint_dropping_copy_types)] +pub struct DropCopyDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub sugg: UseLetUnderscoreIgnoreSuggestion, +} + +#[derive(LintDiagnostic)] +#[diag(lint_forgetting_references)] +pub struct ForgetRefDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub sugg: UseLetUnderscoreIgnoreSuggestion, +} + +#[derive(LintDiagnostic)] +#[diag(lint_forgetting_copy_types)] +pub struct ForgetCopyDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub sugg: UseLetUnderscoreIgnoreSuggestion, +} + +#[derive(LintDiagnostic)] +#[diag(lint_undropped_manually_drops)] +pub struct UndroppedManuallyDropsDiag<'a> { + pub arg_ty: Ty<'a>, + #[label] + pub label: Span, + #[subdiagnostic] + pub suggestion: UndroppedManuallyDropsSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")] +pub struct UndroppedManuallyDropsSuggestion { + #[suggestion_part(code = "std::mem::ManuallyDrop::into_inner(")] + pub start_span: Span, + #[suggestion_part(code = ")")] + pub end_span: Span, +} + +// invalid_from_utf8.rs +#[derive(LintDiagnostic)] +pub enum InvalidFromUtf8Diag { + #[diag(lint_invalid_from_utf8_unchecked)] + Unchecked { + method: String, + valid_up_to: usize, + #[label] + label: Span, + }, + #[diag(lint_invalid_from_utf8_checked)] + Checked { + method: String, + valid_up_to: usize, + #[label] + label: Span, + }, +} + +// reference_casting.rs +#[derive(LintDiagnostic)] +pub enum InvalidReferenceCastingDiag<'tcx> { + #[diag(lint_invalid_reference_casting_borrow_as_mut)] + #[note(lint_invalid_reference_casting_note_book)] + BorrowAsMut { + #[label] + orig_cast: Option<Span>, + #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)] + ty_has_interior_mutability: Option<()>, + }, + #[diag(lint_invalid_reference_casting_assign_to_ref)] + #[note(lint_invalid_reference_casting_note_book)] + AssignToRef { + #[label] + orig_cast: Option<Span>, + #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)] + ty_has_interior_mutability: Option<()>, + }, + #[diag(lint_invalid_reference_casting_bigger_layout)] + #[note(lint_layout)] + BiggerLayout { + #[label] + orig_cast: Option<Span>, + #[label(lint_alloc)] + alloc: Span, + from_ty: Ty<'tcx>, + from_size: u64, + to_ty: Ty<'tcx>, + to_size: u64, + }, +} + +// hidden_unicode_codepoints.rs +#[derive(LintDiagnostic)] +#[diag(lint_hidden_unicode_codepoints)] +#[note] +pub struct HiddenUnicodeCodepointsDiag<'a> { + pub label: &'a str, + pub count: usize, + #[label] + pub span_label: Span, + #[subdiagnostic] + pub labels: Option<HiddenUnicodeCodepointsDiagLabels>, + #[subdiagnostic] + pub sub: HiddenUnicodeCodepointsDiagSub, +} + +pub struct HiddenUnicodeCodepointsDiagLabels { + pub spans: Vec<(char, Span)>, +} + +impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + for (c, span) in self.spans { + diag.span_label(span, format!("{c:?}")); + } + } +} + +pub enum HiddenUnicodeCodepointsDiagSub { + Escape { spans: Vec<(char, Span)> }, + NoEscape { spans: Vec<(char, Span)> }, +} + +// Used because of multiple multipart_suggestion and note +impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + match self { + HiddenUnicodeCodepointsDiagSub::Escape { spans } => { + diag.multipart_suggestion_with_style( + fluent::lint_suggestion_remove, + spans.iter().map(|(_, span)| (*span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + diag.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, + ); + } + HiddenUnicodeCodepointsDiagSub::NoEscape { spans } => { + // 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. + diag.arg( + "escaped", + spans + .into_iter() + .map(|(c, _)| format!("{c:?}")) + .collect::<Vec<String>>() + .join(", "), + ); + diag.note(fluent::lint_suggestion_remove); + diag.note(fluent::lint_no_suggestion_note_escape); + } + } + } +} + +// map_unit_fn.rs +#[derive(LintDiagnostic)] +#[diag(lint_map_unit_fn)] +#[note] +pub struct MappingToUnit { + #[label(lint_function_label)] + pub function_label: Span, + #[label(lint_argument_label)] + pub argument_label: Span, + #[label(lint_map_label)] + pub map_label: Span, + #[suggestion(style = "verbose", code = "{replace}", applicability = "maybe-incorrect")] + pub suggestion: Span, + pub replace: String, +} + +// internal.rs +#[derive(LintDiagnostic)] +#[diag(lint_default_hash_types)] +#[note] +pub struct DefaultHashTypesDiag<'a> { + pub preferred: &'a str, + pub used: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_query_instability)] +#[note] +pub struct QueryInstability { + pub query: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_span_use_eq_ctxt)] +pub struct SpanUseEqCtxtDiag; + +#[derive(LintDiagnostic)] +#[diag(lint_tykind_kind)] +pub struct TykindKind { + #[suggestion(code = "ty", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_tykind)] +#[help] +pub struct TykindDiag; + +#[derive(LintDiagnostic)] +#[diag(lint_ty_qualified)] +pub struct TyQualified { + pub ty: String, + #[suggestion(code = "{ty}", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_lintpass_by_hand)] +#[help] +pub struct LintPassByHand; + +#[derive(LintDiagnostic)] +#[diag(lint_non_existent_doc_keyword)] +#[help] +pub struct NonExistentDocKeyword { + pub keyword: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_diag_out_of_impl)] +pub struct DiagOutOfImpl; + +#[derive(LintDiagnostic)] +#[diag(lint_untranslatable_diag)] +pub struct UntranslatableDiag; + +#[derive(LintDiagnostic)] +#[diag(lint_bad_opt_access)] +pub struct BadOptAccessDiag<'a> { + pub msg: &'a str, +} + +// let_underscore.rs +#[derive(LintDiagnostic)] +pub enum NonBindingLet { + #[diag(lint_non_binding_let_on_sync_lock)] + SyncLock { + #[subdiagnostic] + sub: NonBindingLetSub, + }, + #[diag(lint_non_binding_let_on_drop_type)] + DropType { + #[subdiagnostic] + sub: NonBindingLetSub, + }, +} + +pub struct NonBindingLetSub { + pub suggestion: Span, + pub drop_fn_start_end: Option<(Span, Span)>, + pub is_assign_desugar: bool, +} + +impl Subdiagnostic for NonBindingLetSub { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + let can_suggest_binding = self.drop_fn_start_end.is_some() || !self.is_assign_desugar; + + if can_suggest_binding { + let prefix = if self.is_assign_desugar { "let " } else { "" }; + diag.span_suggestion_verbose( + self.suggestion, + fluent::lint_non_binding_let_suggestion, + format!("{prefix}_unused"), + Applicability::MachineApplicable, + ); + } else { + diag.span_help(self.suggestion, fluent::lint_non_binding_let_suggestion); + } + if let Some(drop_fn_start_end) = self.drop_fn_start_end { + diag.multipart_suggestion( + fluent::lint_non_binding_let_multi_suggestion, + vec![ + (drop_fn_start_end.0, "drop(".to_string()), + (drop_fn_start_end.1, ")".to_string()), + ], + Applicability::MachineApplicable, + ); + } else { + diag.help(fluent::lint_non_binding_let_multi_drop_fn); + } + } +} + +// levels.rs +#[derive(LintDiagnostic)] +#[diag(lint_overruled_attribute)] +pub struct OverruledAttributeLint<'a> { + #[label] + pub overruled: Span, + pub lint_level: &'a str, + pub lint_source: Symbol, + #[subdiagnostic] + pub sub: OverruledAttributeSub, +} + +#[derive(LintDiagnostic)] +#[diag(lint_deprecated_lint_name)] +pub struct DeprecatedLintName<'a> { + pub name: String, + #[suggestion(code = "{replace}", applicability = "machine-applicable")] + pub suggestion: Span, + pub replace: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_deprecated_lint_name)] +#[help] +pub struct DeprecatedLintNameFromCommandLine<'a> { + pub name: String, + pub replace: &'a str, + #[subdiagnostic] + pub requested_level: RequestedLevel<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_renamed_lint)] +pub struct RenamedLint<'a> { + pub name: &'a str, + #[subdiagnostic] + pub suggestion: RenamedLintSuggestion<'a>, +} + +#[derive(Subdiagnostic)] +pub enum RenamedLintSuggestion<'a> { + #[suggestion(lint_suggestion, code = "{replace}", applicability = "machine-applicable")] + WithSpan { + #[primary_span] + suggestion: Span, + replace: &'a str, + }, + #[help(lint_help)] + WithoutSpan { replace: &'a str }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_renamed_lint)] +pub struct RenamedLintFromCommandLine<'a> { + pub name: &'a str, + #[subdiagnostic] + pub suggestion: RenamedLintSuggestion<'a>, + #[subdiagnostic] + pub requested_level: RequestedLevel<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_removed_lint)] +pub struct RemovedLint<'a> { + pub name: &'a str, + pub reason: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_removed_lint)] +pub struct RemovedLintFromCommandLine<'a> { + pub name: &'a str, + pub reason: &'a str, + #[subdiagnostic] + pub requested_level: RequestedLevel<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unknown_lint)] +pub struct UnknownLint { + pub name: String, + #[subdiagnostic] + pub suggestion: Option<UnknownLintSuggestion>, +} + +#[derive(Subdiagnostic)] +pub enum UnknownLintSuggestion { + #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")] + WithSpan { + #[primary_span] + suggestion: Span, + replace: Symbol, + from_rustc: bool, + }, + #[help(lint_help)] + WithoutSpan { replace: Symbol, from_rustc: bool }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unknown_lint, code = E0602)] +pub struct UnknownLintFromCommandLine<'a> { + pub name: String, + #[subdiagnostic] + pub suggestion: Option<UnknownLintSuggestion>, + #[subdiagnostic] + pub requested_level: RequestedLevel<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_ignored_unless_crate_specified)] +pub struct IgnoredUnlessCrateSpecified<'a> { + pub level: &'a str, + pub name: Symbol, +} + +// methods.rs +#[derive(LintDiagnostic)] +#[diag(lint_cstring_ptr)] +#[note] +#[help] +pub struct CStringPtr { + #[label(lint_as_ptr_label)] + pub as_ptr: Span, + #[label(lint_unwrap_label)] + pub unwrap: Span, +} + +// multiple_supertrait_upcastable.rs +#[derive(LintDiagnostic)] +#[diag(lint_multiple_supertrait_upcastable)] +pub struct MultipleSupertraitUpcastable { + pub ident: Ident, +} + +// non_ascii_idents.rs +#[derive(LintDiagnostic)] +#[diag(lint_identifier_non_ascii_char)] +pub struct IdentifierNonAsciiChar; + +#[derive(LintDiagnostic)] +#[diag(lint_identifier_uncommon_codepoints)] +#[note] +pub struct IdentifierUncommonCodepoints { + pub codepoints: Vec<char>, + pub codepoints_len: usize, + pub identifier_type: &'static str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_confusable_identifier_pair)] +pub struct ConfusableIdentifierPair { + pub existing_sym: Symbol, + pub sym: Symbol, + #[label(lint_other_use)] + pub label: Span, + #[label(lint_current_use)] + pub main_label: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_mixed_script_confusables)] +#[note(lint_includes_note)] +#[note] +pub struct MixedScriptConfusables { + pub set: String, + pub includes: String, +} + +// non_fmt_panic.rs +pub struct NonFmtPanicUnused { + pub count: usize, + pub suggestion: Option<Span>, +} + +// Used because of two suggestions based on one Option<Span> +impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_non_fmt_panic_unused); + diag.arg("count", self.count); + diag.note(fluent::lint_note); + if let Some(span) = self.suggestion { + diag.span_suggestion( + span.shrink_to_hi(), + fluent::lint_add_args_suggestion, + ", ...", + Applicability::HasPlaceholders, + ); + diag.span_suggestion( + span.shrink_to_lo(), + fluent::lint_add_fmt_suggestion, + "\"{}\", ", + Applicability::MachineApplicable, + ); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_non_fmt_panic_braces)] +#[note] +pub struct NonFmtPanicBraces { + pub count: usize, + #[suggestion(code = "\"{{}}\", ", applicability = "machine-applicable")] + pub suggestion: Option<Span>, +} + +// nonstandard_style.rs +#[derive(LintDiagnostic)] +#[diag(lint_non_camel_case_type)] +pub struct NonCamelCaseType<'a> { + pub sort: &'a str, + pub name: &'a str, + #[subdiagnostic] + pub sub: NonCamelCaseTypeSub, +} + +#[derive(Subdiagnostic)] +pub enum NonCamelCaseTypeSub { + #[label(lint_label)] + Label { + #[primary_span] + span: Span, + }, + #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")] + Suggestion { + #[primary_span] + span: Span, + replace: String, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_non_snake_case)] +pub struct NonSnakeCaseDiag<'a> { + pub sort: &'a str, + pub name: &'a str, + pub sc: String, + #[subdiagnostic] + pub sub: NonSnakeCaseDiagSub, +} + +pub enum NonSnakeCaseDiagSub { + Label { span: Span }, + Help, + RenameOrConvertSuggestion { span: Span, suggestion: Ident }, + ConvertSuggestion { span: Span, suggestion: String }, + SuggestionAndNote { span: Span }, +} + +impl Subdiagnostic for NonSnakeCaseDiagSub { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + match self { + NonSnakeCaseDiagSub::Label { span } => { + diag.span_label(span, fluent::lint_label); + } + NonSnakeCaseDiagSub::Help => { + diag.help(fluent::lint_help); + } + NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion } => { + diag.span_suggestion( + span, + fluent::lint_convert_suggestion, + suggestion, + Applicability::MaybeIncorrect, + ); + } + NonSnakeCaseDiagSub::RenameOrConvertSuggestion { span, suggestion } => { + diag.span_suggestion( + span, + fluent::lint_rename_or_convert_suggestion, + suggestion, + Applicability::MaybeIncorrect, + ); + } + NonSnakeCaseDiagSub::SuggestionAndNote { span } => { + diag.note(fluent::lint_cannot_convert_note); + diag.span_suggestion( + span, + fluent::lint_rename_suggestion, + "", + Applicability::MaybeIncorrect, + ); + } + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_non_upper_case_global)] +pub struct NonUpperCaseGlobal<'a> { + pub sort: &'a str, + pub name: &'a str, + #[subdiagnostic] + pub sub: NonUpperCaseGlobalSub, +} + +#[derive(Subdiagnostic)] +pub enum NonUpperCaseGlobalSub { + #[label(lint_label)] + Label { + #[primary_span] + span: Span, + }, + #[suggestion(lint_suggestion, code = "{replace}", applicability = "maybe-incorrect")] + Suggestion { + #[primary_span] + span: Span, + replace: String, + }, +} + +// noop_method_call.rs +#[derive(LintDiagnostic)] +#[diag(lint_noop_method_call)] +#[note] +pub struct NoopMethodCallDiag<'a> { + pub method: Symbol, + pub orig_ty: Ty<'a>, + pub trait_: Symbol, + #[suggestion(code = "", applicability = "machine-applicable")] + pub label: Span, + #[suggestion( + lint_derive_suggestion, + code = "#[derive(Clone)]\n", + applicability = "maybe-incorrect" + )] + pub suggest_derive: Option<Span>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_suspicious_double_ref_deref)] +pub struct SuspiciousDoubleRefDerefDiag<'a> { + pub ty: Ty<'a>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_suspicious_double_ref_clone)] +pub struct SuspiciousDoubleRefCloneDiag<'a> { + pub ty: Ty<'a>, +} + +// non_local_defs.rs +pub enum NonLocalDefinitionsDiag { + Impl { + depth: u32, + body_kind_descr: &'static str, + body_name: String, + cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>, + const_anon: Option<Option<Span>>, + move_to: Option<(Span, Vec<Span>)>, + doctest: bool, + may_remove: Option<(Span, String)>, + has_trait: bool, + self_ty_str: String, + of_trait_str: Option<String>, + macro_to_change: Option<(String, &'static str)>, + }, + MacroRules { + depth: u32, + body_kind_descr: &'static str, + body_name: String, + doctest: bool, + cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>, + }, +} + +impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + match self { + NonLocalDefinitionsDiag::Impl { + depth, + body_kind_descr, + body_name, + cargo_update, + const_anon, + move_to, + doctest, + may_remove, + has_trait, + self_ty_str, + of_trait_str, + macro_to_change, + } => { + diag.primary_message(fluent::lint_non_local_definitions_impl); + diag.arg("depth", depth); + diag.arg("body_kind_descr", body_kind_descr); + diag.arg("body_name", body_name); + diag.arg("self_ty_str", self_ty_str); + if let Some(of_trait_str) = of_trait_str { + diag.arg("of_trait_str", of_trait_str); + } + + if let Some((macro_to_change, macro_kind)) = macro_to_change { + diag.arg("macro_to_change", macro_to_change); + diag.arg("macro_kind", macro_kind); + diag.note(fluent::lint_macro_to_change); + } + if let Some(cargo_update) = cargo_update { + diag.subdiagnostic(cargo_update); + } + + if has_trait { + diag.note(fluent::lint_bounds); + diag.note(fluent::lint_with_trait); + } else { + diag.note(fluent::lint_without_trait); + } + + if let Some((move_help, may_move)) = move_to { + let mut ms = MultiSpan::from_span(move_help); + for sp in may_move { + ms.push_span_label(sp, fluent::lint_non_local_definitions_may_move); + } + diag.span_help(ms, fluent::lint_non_local_definitions_impl_move_help); + } + if doctest { + diag.help(fluent::lint_doctest); + } + + if let Some((span, part)) = may_remove { + diag.arg("may_remove_part", part); + diag.span_suggestion( + span, + fluent::lint_remove_help, + "", + Applicability::MaybeIncorrect, + ); + } + + if let Some(const_anon) = const_anon { + diag.note(fluent::lint_exception); + if let Some(const_anon) = const_anon { + diag.span_suggestion( + const_anon, + fluent::lint_const_anon, + "_", + Applicability::MachineApplicable, + ); + } + } + + diag.note(fluent::lint_non_local_definitions_deprecation); + } + NonLocalDefinitionsDiag::MacroRules { + depth, + body_kind_descr, + body_name, + doctest, + cargo_update, + } => { + diag.primary_message(fluent::lint_non_local_definitions_macro_rules); + diag.arg("depth", depth); + diag.arg("body_kind_descr", body_kind_descr); + diag.arg("body_name", body_name); + + if doctest { + diag.help(fluent::lint_help_doctest); + } else { + diag.help(fluent::lint_help); + } + + diag.note(fluent::lint_non_local); + diag.note(fluent::lint_non_local_definitions_deprecation); + + if let Some(cargo_update) = cargo_update { + diag.subdiagnostic(cargo_update); + } + } + } + } +} + +#[derive(Subdiagnostic)] +#[note(lint_non_local_definitions_cargo_update)] +pub struct NonLocalDefinitionsCargoUpdateNote { + pub macro_kind: &'static str, + pub macro_name: Symbol, + pub crate_name: Symbol, +} + +// pass_by_value.rs +#[derive(LintDiagnostic)] +#[diag(lint_pass_by_value)] +pub struct PassByValueDiag { + pub ty: String, + #[suggestion(code = "{ty}", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + +// redundant_semicolon.rs +#[derive(LintDiagnostic)] +#[diag(lint_redundant_semicolons)] +pub struct RedundantSemicolonsDiag { + pub multiple: bool, + #[suggestion(code = "", applicability = "maybe-incorrect")] + pub suggestion: Span, +} + +// traits.rs +pub struct DropTraitConstraintsDiag<'a> { + pub predicate: Clause<'a>, + pub tcx: TyCtxt<'a>, + pub def_id: DefId, +} + +// Needed for def_path_str +impl<'a> LintDiagnostic<'a, ()> for DropTraitConstraintsDiag<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_drop_trait_constraints); + diag.arg("predicate", self.predicate); + diag.arg("needs_drop", self.tcx.def_path_str(self.def_id)); + } +} + +pub struct DropGlue<'a> { + pub tcx: TyCtxt<'a>, + pub def_id: DefId, +} + +// Needed for def_path_str +impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_drop_glue); + diag.arg("needs_drop", self.tcx.def_path_str(self.def_id)); + } +} + +// types.rs +#[derive(LintDiagnostic)] +#[diag(lint_range_endpoint_out_of_range)] +pub struct RangeEndpointOutOfRange<'a> { + pub ty: &'a str, + #[subdiagnostic] + pub sub: UseInclusiveRange<'a>, +} + +#[derive(Subdiagnostic)] +pub enum UseInclusiveRange<'a> { + #[suggestion( + lint_range_use_inclusive_range, + code = "{start}..={literal}{suffix}", + applicability = "machine-applicable" + )] + WithoutParen { + #[primary_span] + sugg: Span, + start: String, + literal: u128, + suffix: &'a str, + }, + #[multipart_suggestion(lint_range_use_inclusive_range, applicability = "machine-applicable")] + WithParen { + #[suggestion_part(code = "=")] + eq_sugg: Span, + #[suggestion_part(code = "{literal}{suffix}")] + lit_sugg: Span, + literal: u128, + suffix: &'a str, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_overflowing_bin_hex)] +pub struct OverflowingBinHex<'a> { + pub ty: &'a str, + pub lit: String, + pub dec: u128, + pub actually: String, + #[subdiagnostic] + pub sign: OverflowingBinHexSign, + #[subdiagnostic] + pub sub: Option<OverflowingBinHexSub<'a>>, + #[subdiagnostic] + pub sign_bit_sub: Option<OverflowingBinHexSignBitSub<'a>>, +} + +pub enum OverflowingBinHexSign { + Positive, + Negative, +} + +impl Subdiagnostic for OverflowingBinHexSign { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + match self { + OverflowingBinHexSign::Positive => { + diag.note(fluent::lint_positive_note); + } + OverflowingBinHexSign::Negative => { + diag.note(fluent::lint_negative_note); + diag.note(fluent::lint_negative_becomes_note); + } + } + } +} + +#[derive(Subdiagnostic)] +pub enum OverflowingBinHexSub<'a> { + #[suggestion( + lint_suggestion, + code = "{sans_suffix}{suggestion_ty}", + applicability = "machine-applicable" + )] + Suggestion { + #[primary_span] + span: Span, + suggestion_ty: &'a str, + sans_suffix: &'a str, + }, + #[help(lint_help)] + Help { suggestion_ty: &'a str }, +} + +#[derive(Subdiagnostic)] +#[suggestion( + lint_sign_bit_suggestion, + code = "{lit_no_suffix}{uint_ty} as {int_ty}", + applicability = "maybe-incorrect" +)] +pub struct OverflowingBinHexSignBitSub<'a> { + #[primary_span] + pub span: Span, + pub lit_no_suffix: &'a str, + pub negative_val: String, + pub uint_ty: &'a str, + pub int_ty: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_overflowing_int)] +#[note] +pub struct OverflowingInt<'a> { + pub ty: &'a str, + pub lit: String, + pub min: i128, + pub max: u128, + #[subdiagnostic] + pub help: Option<OverflowingIntHelp<'a>>, +} + +#[derive(Subdiagnostic)] +#[help(lint_help)] +pub struct OverflowingIntHelp<'a> { + pub suggestion_ty: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_only_cast_u8_to_char)] +pub struct OnlyCastu8ToChar { + #[suggestion(code = "'\\u{{{literal:X}}}'", applicability = "machine-applicable")] + pub span: Span, + pub literal: u128, +} + +#[derive(LintDiagnostic)] +#[diag(lint_overflowing_uint)] +#[note] +pub struct OverflowingUInt<'a> { + pub ty: &'a str, + pub lit: String, + pub min: u128, + pub max: u128, +} + +#[derive(LintDiagnostic)] +#[diag(lint_overflowing_literal)] +#[note] +pub struct OverflowingLiteral<'a> { + pub ty: &'a str, + pub lit: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_comparisons)] +pub struct UnusedComparisons; + +#[derive(LintDiagnostic)] +pub enum InvalidNanComparisons { + #[diag(lint_invalid_nan_comparisons_eq_ne)] + EqNe { + #[subdiagnostic] + suggestion: Option<InvalidNanComparisonsSuggestion>, + }, + #[diag(lint_invalid_nan_comparisons_lt_le_gt_ge)] + LtLeGtGe, +} + +#[derive(Subdiagnostic)] +pub enum InvalidNanComparisonsSuggestion { + #[multipart_suggestion( + lint_suggestion, + style = "verbose", + applicability = "machine-applicable" + )] + Spanful { + #[suggestion_part(code = "!")] + neg: Option<Span>, + #[suggestion_part(code = ".is_nan()")] + float: Span, + #[suggestion_part(code = "")] + nan_plus_binop: Span, + }, + #[help(lint_suggestion)] + Spanless, +} + +#[derive(LintDiagnostic)] +pub enum AmbiguousWidePointerComparisons<'a> { + #[diag(lint_ambiguous_wide_pointer_comparisons)] + Spanful { + #[subdiagnostic] + addr_suggestion: AmbiguousWidePointerComparisonsAddrSuggestion<'a>, + #[subdiagnostic] + addr_metadata_suggestion: Option<AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a>>, + }, + #[diag(lint_ambiguous_wide_pointer_comparisons)] + #[help(lint_addr_metadata_suggestion)] + #[help(lint_addr_suggestion)] + Spanless, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + lint_addr_metadata_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" +)] +pub struct AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a> { + pub ne: &'a str, + pub deref_left: &'a str, + pub deref_right: &'a str, + pub l_modifiers: &'a str, + pub r_modifiers: &'a str, + #[suggestion_part(code = "{ne}std::ptr::eq({deref_left}")] + pub left: Span, + #[suggestion_part(code = "{l_modifiers}, {deref_right}")] + pub middle: Span, + #[suggestion_part(code = "{r_modifiers})")] + pub right: Span, +} + +#[derive(Subdiagnostic)] +pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> { + #[multipart_suggestion( + lint_addr_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" + )] + AddrEq { + ne: &'a str, + deref_left: &'a str, + deref_right: &'a str, + l_modifiers: &'a str, + r_modifiers: &'a str, + #[suggestion_part(code = "{ne}std::ptr::addr_eq({deref_left}")] + left: Span, + #[suggestion_part(code = "{l_modifiers}, {deref_right}")] + middle: Span, + #[suggestion_part(code = "{r_modifiers})")] + right: Span, + }, + #[multipart_suggestion( + lint_addr_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" + )] + Cast { + deref_left: &'a str, + deref_right: &'a str, + paren_left: &'a str, + paren_right: &'a str, + l_modifiers: &'a str, + r_modifiers: &'a str, + #[suggestion_part(code = "({deref_left}")] + left_before: Option<Span>, + #[suggestion_part(code = "{l_modifiers}{paren_left}.cast::<()>()")] + left_after: Span, + #[suggestion_part(code = "({deref_right}")] + right_before: Option<Span>, + #[suggestion_part(code = "{r_modifiers}{paren_right}.cast::<()>()")] + right_after: Span, + }, +} + +pub struct ImproperCTypes<'a> { + pub ty: Ty<'a>, + pub desc: &'a str, + pub label: Span, + pub help: Option<DiagMessage>, + pub note: DiagMessage, + pub span_note: Option<Span>, +} + +// Used because of the complexity of Option<DiagMessage>, DiagMessage, and Option<Span> +impl<'a> LintDiagnostic<'a, ()> for ImproperCTypes<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_improper_ctypes); + diag.arg("ty", self.ty); + diag.arg("desc", self.desc); + diag.span_label(self.label, fluent::lint_label); + if let Some(help) = self.help { + diag.help(help); + } + diag.note(self.note); + if let Some(note) = self.span_note { + diag.span_note(note, fluent::lint_note); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_variant_size_differences)] +pub struct VariantSizeDifferencesDiag { + pub largest: u64, +} + +#[derive(LintDiagnostic)] +#[diag(lint_atomic_ordering_load)] +#[help] +pub struct AtomicOrderingLoad; + +#[derive(LintDiagnostic)] +#[diag(lint_atomic_ordering_store)] +#[help] +pub struct AtomicOrderingStore; + +#[derive(LintDiagnostic)] +#[diag(lint_atomic_ordering_fence)] +#[help] +pub struct AtomicOrderingFence; + +#[derive(LintDiagnostic)] +#[diag(lint_atomic_ordering_invalid)] +#[help] +pub struct InvalidAtomicOrderingDiag { + pub method: Symbol, + #[label] + pub fail_order_arg_span: Span, +} + +// unused.rs +#[derive(LintDiagnostic)] +#[diag(lint_unused_op)] +pub struct UnusedOp<'a> { + pub op: &'a str, + #[label] + pub label: Span, + #[subdiagnostic] + pub suggestion: UnusedOpSuggestion, +} + +#[derive(Subdiagnostic)] +pub enum UnusedOpSuggestion { + #[suggestion( + lint_suggestion, + style = "verbose", + code = "let _ = ", + applicability = "maybe-incorrect" + )] + NormalExpr { + #[primary_span] + span: Span, + }, + #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")] + BlockTailExpr { + #[suggestion_part(code = "let _ = ")] + before_span: Span, + #[suggestion_part(code = ";")] + after_span: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_result)] +pub struct UnusedResult<'a> { + pub ty: Ty<'a>, +} + +// FIXME(davidtwco): this isn't properly translatable because of the +// pre/post strings +#[derive(LintDiagnostic)] +#[diag(lint_unused_closure)] +#[note] +pub struct UnusedClosure<'a> { + pub count: usize, + pub pre: &'a str, + pub post: &'a str, +} + +// FIXME(davidtwco): this isn't properly translatable because of the +// pre/post strings +#[derive(LintDiagnostic)] +#[diag(lint_unused_coroutine)] +#[note] +pub struct UnusedCoroutine<'a> { + pub count: usize, + pub pre: &'a str, + pub post: &'a str, +} + +// FIXME(davidtwco): this isn't properly translatable because of the pre/post +// strings +pub struct UnusedDef<'a, 'b> { + pub pre: &'a str, + pub post: &'a str, + pub cx: &'a LateContext<'b>, + pub def_id: DefId, + pub note: Option<Symbol>, + pub suggestion: Option<UnusedDefSuggestion>, +} + +#[derive(Subdiagnostic)] + +pub enum UnusedDefSuggestion { + #[suggestion( + lint_suggestion, + style = "verbose", + code = "let _ = ", + applicability = "maybe-incorrect" + )] + NormalExpr { + #[primary_span] + span: Span, + }, + #[multipart_suggestion(lint_suggestion, style = "verbose", applicability = "maybe-incorrect")] + BlockTailExpr { + #[suggestion_part(code = "let _ = ")] + before_span: Span, + #[suggestion_part(code = ";")] + after_span: Span, + }, +} + +// Needed because of def_path_str +impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_unused_def); + diag.arg("pre", self.pre); + diag.arg("post", self.post); + diag.arg("def", self.cx.tcx.def_path_str(self.def_id)); + // check for #[must_use = "..."] + if let Some(note) = self.note { + diag.note(note.to_string()); + } + if let Some(sugg) = self.suggestion { + diag.subdiagnostic(sugg); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_path_statement_drop)] +pub struct PathStatementDrop { + #[subdiagnostic] + pub sub: PathStatementDropSub, +} + +#[derive(Subdiagnostic)] +pub enum PathStatementDropSub { + #[suggestion(lint_suggestion, code = "drop({snippet});", applicability = "machine-applicable")] + Suggestion { + #[primary_span] + span: Span, + snippet: String, + }, + #[help(lint_help)] + Help { + #[primary_span] + span: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_path_statement_no_effect)] +pub struct PathStatementNoEffect; + +#[derive(LintDiagnostic)] +#[diag(lint_unused_delim)] +pub struct UnusedDelim<'a> { + pub delim: &'static str, + pub item: &'a str, + #[subdiagnostic] + pub suggestion: Option<UnusedDelimSuggestion>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")] +pub struct UnusedDelimSuggestion { + #[suggestion_part(code = "{start_replace}")] + pub start_span: Span, + pub start_replace: &'static str, + #[suggestion_part(code = "{end_replace}")] + pub end_span: Span, + pub end_replace: &'static str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_import_braces)] +pub struct UnusedImportBracesDiag { + pub node: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_allocation)] +pub struct UnusedAllocationDiag; + +#[derive(LintDiagnostic)] +#[diag(lint_unused_allocation_mut)] +pub struct UnusedAllocationMutDiag; + +pub struct AsyncFnInTraitDiag { + pub sugg: Option<Vec<(Span, String)>>, +} + +impl<'a> LintDiagnostic<'a, ()> for AsyncFnInTraitDiag { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(fluent::lint_async_fn_in_trait); + diag.note(fluent::lint_note); + if let Some(sugg) = self.sugg { + diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_unit_bindings)] +pub struct UnitBindingsDiag { + #[label] + pub label: Span, +} + +#[derive(LintDiagnostic)] +pub enum InvalidAsmLabel { + #[diag(lint_invalid_asm_label_named)] + #[help] + #[note] + Named { + #[note(lint_invalid_asm_label_no_span)] + missing_precise_span: bool, + }, + #[diag(lint_invalid_asm_label_format_arg)] + #[help] + #[note(lint_note1)] + #[note(lint_note2)] + FormatArg { + #[note(lint_invalid_asm_label_no_span)] + missing_precise_span: bool, + }, + #[diag(lint_invalid_asm_label_binary)] + #[note] + Binary { + #[note(lint_invalid_asm_label_no_span)] + missing_precise_span: bool, + // hack to get a label on the whole span, must match the emitted span + #[label] + span: Span, + }, +} + +#[derive(Subdiagnostic)] +pub enum UnexpectedCfgCargoHelp { + #[help(lint_unexpected_cfg_add_cargo_feature)] + #[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)] + LintCfg { cargo_toml_lint_cfg: String }, + #[help(lint_unexpected_cfg_add_cargo_feature)] + #[help(lint_unexpected_cfg_add_cargo_toml_lint_cfg)] + #[help(lint_unexpected_cfg_add_build_rs_println)] + LintCfgAndBuildRs { cargo_toml_lint_cfg: String, build_rs_println: String }, +} + +impl UnexpectedCfgCargoHelp { + fn cargo_toml_lint_cfg(unescaped: &str) -> String { + format!( + "\n [lints.rust]\n unexpected_cfgs = {{ level = \"warn\", check-cfg = ['{unescaped}'] }}" + ) + } + + pub fn lint_cfg(unescaped: &str) -> Self { + UnexpectedCfgCargoHelp::LintCfg { + cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped), + } + } + + pub fn lint_cfg_and_build_rs(unescaped: &str, escaped: &str) -> Self { + UnexpectedCfgCargoHelp::LintCfgAndBuildRs { + cargo_toml_lint_cfg: Self::cargo_toml_lint_cfg(unescaped), + build_rs_println: format!("println!(\"cargo::rustc-check-cfg={escaped}\");"), + } + } +} + +#[derive(Subdiagnostic)] +#[help(lint_unexpected_cfg_add_cmdline_arg)] +pub struct UnexpectedCfgRustcHelp { + pub cmdline_arg: String, +} + +impl UnexpectedCfgRustcHelp { + pub fn new(unescaped: &str) -> Self { + Self { cmdline_arg: format!("--check-cfg={unescaped}") } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_unexpected_cfg_name)] +pub struct UnexpectedCfgName { + #[subdiagnostic] + pub code_sugg: unexpected_cfg_name::CodeSuggestion, + #[subdiagnostic] + pub invocation_help: unexpected_cfg_name::InvocationHelp, + + pub name: Symbol, +} + +pub mod unexpected_cfg_name { + use rustc_errors::DiagSymbolList; + use rustc_macros::Subdiagnostic; + use rustc_span::{Span, Symbol}; + + #[derive(Subdiagnostic)] + pub enum CodeSuggestion { + #[help(lint_unexpected_cfg_define_features)] + DefineFeatures, + #[suggestion( + lint_unexpected_cfg_name_similar_name_value, + applicability = "maybe-incorrect", + code = "{code}" + )] + SimilarNameAndValue { + #[primary_span] + span: Span, + code: String, + }, + #[suggestion( + lint_unexpected_cfg_name_similar_name_no_value, + applicability = "maybe-incorrect", + code = "{code}" + )] + SimilarNameNoValue { + #[primary_span] + span: Span, + code: String, + }, + #[suggestion( + lint_unexpected_cfg_name_similar_name_different_values, + applicability = "maybe-incorrect", + code = "{code}" + )] + SimilarNameDifferentValues { + #[primary_span] + span: Span, + code: String, + #[subdiagnostic] + expected: Option<ExpectedValues>, + }, + #[suggestion( + lint_unexpected_cfg_name_similar_name, + applicability = "maybe-incorrect", + code = "{code}" + )] + SimilarName { + #[primary_span] + span: Span, + code: String, + #[subdiagnostic] + expected: Option<ExpectedValues>, + }, + SimilarValues { + #[subdiagnostic] + with_similar_values: Vec<FoundWithSimilarValue>, + #[subdiagnostic] + expected_names: Option<ExpectedNames>, + }, + } + + #[derive(Subdiagnostic)] + #[help(lint_unexpected_cfg_name_expected_values)] + pub struct ExpectedValues { + pub best_match: Symbol, + pub possibilities: DiagSymbolList, + } + + #[derive(Subdiagnostic)] + #[suggestion( + lint_unexpected_cfg_name_with_similar_value, + applicability = "maybe-incorrect", + code = "{code}" + )] + pub struct FoundWithSimilarValue { + #[primary_span] + pub span: Span, + pub code: String, + } + + #[derive(Subdiagnostic)] + #[help_once(lint_unexpected_cfg_name_expected_names)] + pub struct ExpectedNames { + pub possibilities: DiagSymbolList, + pub and_more: usize, + } + + #[derive(Subdiagnostic)] + pub enum InvocationHelp { + #[note(lint_unexpected_cfg_doc_cargo)] + Cargo { + #[subdiagnostic] + sub: Option<super::UnexpectedCfgCargoHelp>, + }, + #[note(lint_unexpected_cfg_doc_rustc)] + Rustc(#[subdiagnostic] super::UnexpectedCfgRustcHelp), + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_unexpected_cfg_value)] +pub struct UnexpectedCfgValue { + #[subdiagnostic] + pub code_sugg: unexpected_cfg_value::CodeSuggestion, + #[subdiagnostic] + pub invocation_help: unexpected_cfg_value::InvocationHelp, + + pub has_value: bool, + pub value: String, +} + +pub mod unexpected_cfg_value { + use rustc_errors::DiagSymbolList; + use rustc_macros::Subdiagnostic; + use rustc_span::{Span, Symbol}; + + #[derive(Subdiagnostic)] + pub enum CodeSuggestion { + ChangeValue { + #[subdiagnostic] + expected_values: ExpectedValues, + #[subdiagnostic] + suggestion: Option<ChangeValueSuggestion>, + }, + #[note(lint_unexpected_cfg_value_no_expected_value)] + RemoveValue { + #[subdiagnostic] + suggestion: Option<RemoveValueSuggestion>, + + name: Symbol, + }, + #[note(lint_unexpected_cfg_value_no_expected_values)] + RemoveCondition { + #[subdiagnostic] + suggestion: RemoveConditionSuggestion, + + name: Symbol, + }, + } + + #[derive(Subdiagnostic)] + pub enum ChangeValueSuggestion { + #[suggestion( + lint_unexpected_cfg_value_similar_name, + code = r#""{best_match}""#, + applicability = "maybe-incorrect" + )] + SimilarName { + #[primary_span] + span: Span, + best_match: Symbol, + }, + #[suggestion( + lint_unexpected_cfg_value_specify_value, + code = r#" = "{first_possibility}""#, + applicability = "maybe-incorrect" + )] + SpecifyValue { + #[primary_span] + span: Span, + first_possibility: Symbol, + }, + } + + #[derive(Subdiagnostic)] + #[suggestion( + lint_unexpected_cfg_value_remove_value, + code = "", + applicability = "maybe-incorrect" + )] + pub struct RemoveValueSuggestion { + #[primary_span] + pub span: Span, + } + + #[derive(Subdiagnostic)] + #[suggestion( + lint_unexpected_cfg_value_remove_condition, + code = "", + applicability = "maybe-incorrect" + )] + pub struct RemoveConditionSuggestion { + #[primary_span] + pub span: Span, + } + + #[derive(Subdiagnostic)] + #[note(lint_unexpected_cfg_value_expected_values)] + pub struct ExpectedValues { + pub name: Symbol, + pub have_none_possibility: bool, + pub possibilities: DiagSymbolList, + pub and_more: usize, + } + + #[derive(Subdiagnostic)] + pub enum InvocationHelp { + #[note(lint_unexpected_cfg_doc_cargo)] + Cargo(#[subdiagnostic] Option<CargoHelp>), + #[note(lint_unexpected_cfg_doc_rustc)] + Rustc(#[subdiagnostic] Option<super::UnexpectedCfgRustcHelp>), + } + + #[derive(Subdiagnostic)] + pub enum CargoHelp { + #[help(lint_unexpected_cfg_value_add_feature)] + AddFeature { + value: Symbol, + }, + #[help(lint_unexpected_cfg_define_features)] + DefineFeatures, + Other(#[subdiagnostic] super::UnexpectedCfgCargoHelp), + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_macro_use_deprecated)] +#[help] +pub struct MacroUseDeprecated; + +#[derive(LintDiagnostic)] +#[diag(lint_unused_macro_use)] +pub struct UnusedMacroUse; + +#[derive(LintDiagnostic)] +#[diag(lint_private_extern_crate_reexport, code = E0365)] +pub struct PrivateExternCrateReexport { + pub ident: Ident, + #[suggestion(code = "pub ", style = "verbose", applicability = "maybe-incorrect")] + pub sugg: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_label)] +pub struct UnusedLabel; + +#[derive(LintDiagnostic)] +#[diag(lint_macro_is_private)] +pub struct MacroIsPrivate { + pub ident: Ident, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_macro_definition)] +pub struct UnusedMacroDefinition { + pub name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_macro_rule_never_used)] +pub struct MacroRuleNeverUsed { + pub n: usize, + pub name: Symbol, +} + +pub struct UnstableFeature { + pub msg: DiagMessage, +} + +impl<'a> LintDiagnostic<'a, ()> for UnstableFeature { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.primary_message(self.msg); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_avoid_intel_syntax)] +pub struct AvoidIntelSyntax; + +#[derive(LintDiagnostic)] +#[diag(lint_avoid_att_syntax)] +pub struct AvoidAttSyntax; + +#[derive(LintDiagnostic)] +#[diag(lint_incomplete_include)] +pub struct IncompleteInclude; + +#[derive(LintDiagnostic)] +#[diag(lint_unnameable_test_items)] +pub struct UnnameableTestItems; + +#[derive(LintDiagnostic)] +#[diag(lint_duplicate_macro_attribute)] +pub struct DuplicateMacroAttribute; + +#[derive(LintDiagnostic)] +#[diag(lint_cfg_attr_no_attributes)] +pub struct CfgAttrNoAttributes; + +#[derive(LintDiagnostic)] +#[diag(lint_crate_type_in_cfg_attr_deprecated)] +pub struct CrateTypeInCfgAttr; + +#[derive(LintDiagnostic)] +#[diag(lint_crate_name_in_cfg_attr_deprecated)] +pub struct CrateNameInCfgAttr; + +#[derive(LintDiagnostic)] +#[diag(lint_missing_fragment_specifier)] +pub struct MissingFragmentSpecifier; + +#[derive(LintDiagnostic)] +#[diag(lint_metavariable_still_repeating)] +pub struct MetaVariableStillRepeating { + pub name: MacroRulesNormalizedIdent, +} + +#[derive(LintDiagnostic)] +#[diag(lint_metavariable_wrong_operator)] +pub struct MetaVariableWrongOperator; + +#[derive(LintDiagnostic)] +#[diag(lint_duplicate_matcher_binding)] +pub struct DuplicateMatcherBinding; + +#[derive(LintDiagnostic)] +#[diag(lint_unknown_macro_variable)] +pub struct UnknownMacroVariable { + pub name: MacroRulesNormalizedIdent, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_crate_dependency)] +#[help] +pub struct UnusedCrateDependency { + pub extern_crate: Symbol, + pub local_crate: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_wasm_c_abi)] +pub struct WasmCAbi; + +#[derive(LintDiagnostic)] +#[diag(lint_ill_formed_attribute_input)] +pub struct IllFormedAttributeInput { + pub num_suggestions: usize, + pub suggestions: DiagArgValue, +} + +#[derive(LintDiagnostic)] +pub enum InnerAttributeUnstable { + #[diag(lint_inner_macro_attribute_unstable)] + InnerMacroAttribute, + #[diag(lint_custom_inner_attribute_unstable)] + CustomInnerAttribute, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unknown_diagnostic_attribute)] +pub struct UnknownDiagnosticAttribute { + #[subdiagnostic] + pub typo: Option<UnknownDiagnosticAttributeTypoSugg>, +} + +#[derive(Subdiagnostic)] +#[suggestion( + lint_unknown_diagnostic_attribute_typo_sugg, + style = "verbose", + code = "{typo_name}", + applicability = "machine-applicable" +)] +pub struct UnknownDiagnosticAttributeTypoSugg { + #[primary_span] + pub span: Span, + pub typo_name: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unicode_text_flow)] +#[note] +pub struct UnicodeTextFlow { + #[label] + pub comment_span: Span, + #[subdiagnostic] + pub characters: Vec<UnicodeCharNoteSub>, + #[subdiagnostic] + pub suggestions: Option<UnicodeTextFlowSuggestion>, + + pub num_codepoints: usize, +} + +#[derive(Subdiagnostic)] +#[label(lint_label_comment_char)] +pub struct UnicodeCharNoteSub { + #[primary_span] + pub span: Span, + pub c_debug: String, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable", style = "hidden")] +pub struct UnicodeTextFlowSuggestion { + #[suggestion_part(code = "")] + pub spans: Vec<Span>, +} + +#[derive(LintDiagnostic)] +#[diag(lint_abs_path_with_module)] +pub struct AbsPathWithModule { + #[subdiagnostic] + pub sugg: AbsPathWithModuleSugg, +} + +#[derive(Subdiagnostic)] +#[suggestion(lint_suggestion, code = "{replacement}")] +pub struct AbsPathWithModuleSugg { + #[primary_span] + pub span: Span, + #[applicability] + pub applicability: Applicability, + pub replacement: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_proc_macro_derive_resolution_fallback)] +pub struct ProcMacroDeriveResolutionFallback { + #[label] + pub span: Span, + pub ns: Namespace, + pub ident: Ident, +} + +#[derive(LintDiagnostic)] +#[diag(lint_macro_expanded_macro_exports_accessed_by_absolute_paths)] +pub struct MacroExpandedMacroExportsAccessedByAbsolutePaths { + #[note] + pub definition: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_hidden_lifetime_parameters)] +pub struct ElidedLifetimesInPaths { + #[subdiagnostic] + pub subdiag: ElidedLifetimeInPathSubdiag, +} + +#[derive(LintDiagnostic)] +#[diag(lint_invalid_crate_type_value)] +pub struct UnknownCrateTypes { + #[subdiagnostic] + pub sugg: Option<UnknownCrateTypesSub>, +} + +#[derive(Subdiagnostic)] +#[suggestion(lint_suggestion, code = r#""{candidate}""#, applicability = "maybe-incorrect")] +pub struct UnknownCrateTypesSub { + #[primary_span] + pub span: Span, + pub candidate: Symbol, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_imports)] +pub struct UnusedImports { + #[subdiagnostic] + pub sugg: UnusedImportsSugg, + #[help] + pub test_module_span: Option<Span>, + + pub span_snippets: DiagArgValue, + pub num_snippets: usize, +} + +#[derive(Subdiagnostic)] +pub enum UnusedImportsSugg { + #[suggestion( + lint_suggestion_remove_whole_use, + applicability = "machine-applicable", + code = "", + style = "tool-only" + )] + RemoveWholeUse { + #[primary_span] + span: Span, + }, + #[multipart_suggestion( + lint_suggestion_remove_imports, + applicability = "machine-applicable", + style = "tool-only" + )] + RemoveImports { + #[suggestion_part(code = "")] + remove_spans: Vec<Span>, + num_to_remove: usize, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_redundant_import)] +pub struct RedundantImport { + #[subdiagnostic] + pub subs: Vec<RedundantImportSub>, + + pub ident: Ident, +} + +#[derive(Subdiagnostic)] +pub enum RedundantImportSub { + #[label(lint_label_imported_here)] + ImportedHere(#[primary_span] Span), + #[label(lint_label_defined_here)] + DefinedHere(#[primary_span] Span), + #[label(lint_label_imported_prelude)] + ImportedPrelude(#[primary_span] Span), + #[label(lint_label_defined_prelude)] + DefinedPrelude(#[primary_span] Span), +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_doc_comment)] +#[help] +pub struct UnusedDocComment { + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +pub enum PatternsInFnsWithoutBody { + #[diag(lint_pattern_in_foreign)] + Foreign { + #[subdiagnostic] + sub: PatternsInFnsWithoutBodySub, + }, + #[diag(lint_pattern_in_bodiless)] + Bodiless { + #[subdiagnostic] + sub: PatternsInFnsWithoutBodySub, + }, +} + +#[derive(Subdiagnostic)] +#[suggestion(lint_remove_mut_from_pattern, code = "{ident}", applicability = "machine-applicable")] +pub struct PatternsInFnsWithoutBodySub { + #[primary_span] + pub span: Span, + + pub ident: Ident, +} + +#[derive(LintDiagnostic)] +#[diag(lint_extern_without_abi)] +#[help] +pub struct MissingAbi { + #[label] + pub span: Span, + + pub default_abi: &'static str, +} + +#[derive(LintDiagnostic)] +#[diag(lint_legacy_derive_helpers)] +pub struct LegacyDeriveHelpers { + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_or_patterns_back_compat)] +pub struct OrPatternsBackCompat { + #[suggestion(code = "{suggestion}", applicability = "machine-applicable")] + pub span: Span, + pub suggestion: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_reserved_prefix)] +pub struct ReservedPrefix { + #[label] + pub label: Span, + #[suggestion(code = " ", applicability = "machine-applicable")] + pub suggestion: Span, + + pub prefix: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_builtin_attribute)] +pub struct UnusedBuiltinAttribute { + #[note] + pub invoc_span: Span, + + pub attr_name: Symbol, + pub macro_name: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_trailing_semi_macro)] +pub struct TrailingMacro { + #[note(lint_note1)] + #[note(lint_note2)] + pub is_trailing: bool, + + pub name: Ident, +} + +#[derive(LintDiagnostic)] +#[diag(lint_break_with_label_and_loop)] +pub struct BreakWithLabelAndLoop { + #[subdiagnostic] + pub sub: BreakWithLabelAndLoopSub, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")] +pub struct BreakWithLabelAndLoopSub { + #[suggestion_part(code = "(")] + pub left: Span, + #[suggestion_part(code = ")")] + pub right: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_deprecated_where_clause_location)] +#[note] +pub struct DeprecatedWhereClauseLocation { + #[subdiagnostic] + pub suggestion: DeprecatedWhereClauseLocationSugg, +} + +#[derive(Subdiagnostic)] +pub enum DeprecatedWhereClauseLocationSugg { + #[multipart_suggestion(lint_suggestion_move_to_end, applicability = "machine-applicable")] + MoveToEnd { + #[suggestion_part(code = "")] + left: Span, + #[suggestion_part(code = "{sugg}")] + right: Span, + + sugg: String, + }, + #[suggestion(lint_suggestion_remove_where, code = "", applicability = "machine-applicable")] + RemoveWhere { + #[primary_span] + span: Span, + }, +} + +#[derive(LintDiagnostic)] +#[diag(lint_missing_unsafe_on_extern)] +pub struct MissingUnsafeOnExtern { + #[suggestion(code = "unsafe ", applicability = "machine-applicable")] + pub suggestion: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_single_use_lifetime)] +pub struct SingleUseLifetime { + #[label(lint_label_param)] + pub param_span: Span, + #[label(lint_label_use)] + pub use_span: Span, + #[subdiagnostic] + pub suggestion: Option<SingleUseLifetimeSugg>, + + pub ident: Ident, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "machine-applicable")] +pub struct SingleUseLifetimeSugg { + #[suggestion_part(code = "")] + pub deletion_span: Option<Span>, + #[suggestion_part(code = "{replace_lt}")] + pub use_span: Span, + + pub replace_lt: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_lifetime)] +pub struct UnusedLifetime { + #[suggestion(code = "", applicability = "machine-applicable")] + pub deletion_span: Option<Span>, + + pub ident: Ident, +} + +#[derive(LintDiagnostic)] +#[diag(lint_named_argument_used_positionally)] +pub struct NamedArgumentUsedPositionally { + #[label(lint_label_named_arg)] + pub named_arg_sp: Span, + #[label(lint_label_position_arg)] + pub position_label_sp: Option<Span>, + #[suggestion(style = "verbose", code = "{name}", applicability = "maybe-incorrect")] + pub suggestion: Option<Span>, + + pub name: String, + pub named_arg_name: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_byte_slice_in_packed_struct_with_derive)] +#[help] +pub struct ByteSliceInPackedStructWithDerive { + // FIXME: make this translatable + pub ty: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unused_extern_crate)] +pub struct UnusedExternCrate { + #[suggestion(code = "", applicability = "machine-applicable")] + pub removal_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_extern_crate_not_idiomatic)] +pub struct ExternCrateNotIdiomatic { + #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")] + pub span: Span, + + pub code: &'static str, +} + +// FIXME: make this translatable +pub struct AmbiguousGlobImports { + pub ambiguity: AmbiguityErrorDiag, +} + +impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for AmbiguousGlobImports { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) { + diag.primary_message(self.ambiguity.msg.clone()); + rustc_errors::report_ambiguity_error(diag, self.ambiguity); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_ambiguous_glob_reexport)] +pub struct AmbiguousGlobReexports { + #[label(lint_label_first_reexport)] + pub first_reexport: Span, + #[label(lint_label_duplicate_reexport)] + pub duplicate_reexport: Span, + + pub name: String, + // FIXME: make this translatable + pub namespace: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_hidden_glob_reexport)] +pub struct HiddenGlobReexports { + #[note(lint_note_glob_reexport)] + pub glob_reexport: Span, + #[note(lint_note_private_item)] + pub private_item: Span, + + pub name: String, + // FIXME: make this translatable + pub namespace: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unnecessary_qualification)] +pub struct UnusedQualifications { + #[suggestion(style = "verbose", code = "", applicability = "machine-applicable")] + pub removal_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_associated_const_elided_lifetime)] +pub struct AssociatedConstElidedLifetime { + #[suggestion(style = "verbose", code = "{code}", applicability = "machine-applicable")] + pub span: Span, + + pub code: &'static str, + pub elided: bool, + #[note] + pub lifetimes_in_scope: MultiSpan, +} + +#[derive(LintDiagnostic)] +#[diag(lint_redundant_import_visibility)] +pub struct RedundantImportVisibility { + #[note] + pub span: Span, + #[help] + pub help: (), + + pub import_vis: String, + pub max_vis: String, +} + +#[derive(LintDiagnostic)] +#[diag(lint_unsafe_attr_outside_unsafe)] +pub struct UnsafeAttrOutsideUnsafe { + #[label] + pub span: Span, + #[subdiagnostic] + pub suggestion: UnsafeAttrOutsideUnsafeSuggestion, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + lint_unsafe_attr_outside_unsafe_suggestion, + applicability = "machine-applicable" +)] +pub struct UnsafeAttrOutsideUnsafeSuggestion { + #[suggestion_part(code = "unsafe(")] + pub left: Span, + #[suggestion_part(code = ")")] + pub right: Span, +} + +#[derive(LintDiagnostic)] +#[diag(lint_out_of_scope_macro_calls)] +#[help] +pub struct OutOfScopeMacroCalls { + pub path: String, +} diff --git a/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs new file mode 100644 index 00000000000..867e132b106 --- /dev/null +++ b/compiler/rustc_lint/src/macro_expr_fragment_specifier_2024_migration.rs @@ -0,0 +1,155 @@ +//! Migration code for the `expr_fragment_specifier_2024` +//! rule. +use tracing::debug; + +use rustc_ast::token::Token; +use rustc_ast::token::TokenKind; +use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::TokenTree; +use rustc_session::declare_lint; +use rustc_session::declare_lint_pass; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_span::edition::Edition; +use rustc_span::sym; + +use crate::lints::MacroExprFragment2024; +use crate::EarlyLintPass; + +declare_lint! { + /// The `edition_2024_expr_fragment_specifier` lint detects the use of + /// `expr` fragments in macros during migration to the 2024 edition. + /// + /// The `expr` fragment specifier will accept more expressions in the 2024 + /// edition. To maintain the behavior from the 2021 edition and earlier, use + /// the `expr_2021` fragment specifier. + /// + /// ### Example + /// + /// ```rust,edition2021,compile_fail + /// #![deny(edition_2024_expr_fragment_specifier)] + /// macro_rules! m { + /// ($e:expr) => { + /// $e + /// } + /// } + /// + /// fn main() { + /// m!(1); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Rust [editions] allow the language to evolve without breaking backwards + /// compatibility. This lint catches code that uses [macro matcher fragment + /// specifiers] that have changed meaning in the 2024 edition. If you switch + /// to the new edition without updating the code, your macros may behave + /// differently. + /// + /// In the 2024 edition, the `expr` fragment specifier `expr` will also + /// match `const { ... }` blocks. This means if a macro had a pattern that + /// matched `$e:expr` and another that matches `const { $e: expr }`, for + /// example, that under the 2024 edition the first pattern would match while + /// in the 2021 and earlier editions the second pattern would match. To keep + /// the old behavior, use the `expr_2021` fragment specifier. + /// + /// This lint detects macros whose behavior might change due to the changing + /// meaning of the `expr` fragment specifier. 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. This provides a + /// completely automated way to update old code for a new edition. + /// + /// Using `cargo fix --edition` with this lint will ensure that your code + /// retains the same behavior. This may not be the desired, as macro authors + /// often will want their macros to use the latest grammar for matching + /// expressions. Be sure to carefully review changes introduced by this lint + /// to ensure the macros implement the desired behavior. + /// + /// [editions]: https://doc.rust-lang.org/edition-guide/ + /// [macro matcher fragment specifiers]: https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html + /// [`cargo fix`]: https://doc.rust-lang.org/cargo/commands/cargo-fix.html + pub EDITION_2024_EXPR_FRAGMENT_SPECIFIER, + Allow, + "The `expr` fragment specifier will accept more expressions in the 2024 edition. \ + To keep the existing behavior, use the `expr_2021` fragment specifier.", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + reference: "Migration Guide <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/macro-fragment-specifiers.html>", + }; +} + +declare_lint_pass!(Expr2024 => [EDITION_2024_EXPR_FRAGMENT_SPECIFIER,]); + +impl Expr2024 { + fn check_tokens(&mut self, cx: &crate::EarlyContext<'_>, tokens: &TokenStream) { + let mut prev_colon = false; + let mut prev_identifier = false; + let mut prev_dollar = false; + for tt in tokens.trees() { + debug!( + "check_tokens: {:?} - colon {prev_dollar} - ident {prev_identifier} - colon {prev_colon}", + tt + ); + match tt { + TokenTree::Token(token, _) => match token.kind { + TokenKind::Dollar => { + prev_dollar = true; + continue; + } + TokenKind::Ident(..) | TokenKind::NtIdent(..) => { + if prev_colon && prev_identifier && prev_dollar { + self.check_ident_token(cx, token); + } else if prev_dollar { + prev_identifier = true; + continue; + } + } + TokenKind::Colon => { + if prev_dollar && prev_identifier { + prev_colon = true; + continue; + } + } + _ => {} + }, + TokenTree::Delimited(.., tts) => self.check_tokens(cx, tts), + } + prev_colon = false; + prev_identifier = false; + prev_dollar = false; + } + } + + fn check_ident_token(&mut self, cx: &crate::EarlyContext<'_>, token: &Token) { + debug!("check_ident_token: {:?}", token); + let (sym, edition) = match token.kind { + TokenKind::Ident(sym, _) => (sym, Edition::Edition2024), + _ => return, + }; + + debug!("token.span.edition(): {:?}", token.span.edition()); + if token.span.edition() >= edition { + return; + } + + if sym != sym::expr { + return; + } + + debug!("emitting lint"); + cx.builder.emit_span_lint( + &EDITION_2024_EXPR_FRAGMENT_SPECIFIER, + token.span.into(), + MacroExprFragment2024 { suggestion: token.span }, + ); + } +} + +impl EarlyLintPass for Expr2024 { + fn check_mac_def(&mut self, cx: &crate::EarlyContext<'_>, mc: &rustc_ast::MacroDef) { + self.check_tokens(cx, &mc.body.tokens); + } +} diff --git a/compiler/rustc_lint/src/map_unit_fn.rs b/compiler/rustc_lint/src/map_unit_fn.rs new file mode 100644 index 00000000000..e355604e206 --- /dev/null +++ b/compiler/rustc_lint/src/map_unit_fn.rs @@ -0,0 +1,128 @@ +use crate::lints::MappingToUnit; +use crate::{LateContext, LateLintPass, LintContext}; + +use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind}; +use rustc_middle::{ + query::Key, + ty::{self, Ty}, +}; +use rustc_session::{declare_lint, declare_lint_pass}; + +declare_lint! { + /// The `map_unit_fn` lint checks for `Iterator::map` receive + /// a callable that returns `()`. + /// + /// ### Example + /// + /// ```rust + /// fn foo(items: &mut Vec<u8>) { + /// items.sort(); + /// } + /// + /// fn main() { + /// let mut x: Vec<Vec<u8>> = vec![ + /// vec![0, 2, 1], + /// vec![5, 4, 3], + /// ]; + /// x.iter_mut().map(foo); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Mapping to `()` is almost always a mistake. + pub MAP_UNIT_FN, + Warn, + "`Iterator::map` call that discard the iterator's values" +} + +declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]); + +impl<'tcx> LateLintPass<'tcx> for MapUnitFn { + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) { + if stmt.span.from_expansion() { + return; + } + + if let StmtKind::Semi(expr) = stmt.kind { + if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind { + if path.ident.name.as_str() == "map" { + if receiver.span.from_expansion() + || args.iter().any(|e| e.span.from_expansion()) + || !is_impl_slice(cx, receiver) + || !is_diagnostic_name(cx, expr.hir_id, "IteratorMap") + { + return; + } + let arg_ty = cx.typeck_results().expr_ty(&args[0]); + let default_span = args[0].span; + if let ty::FnDef(id, _) = arg_ty.kind() { + let fn_ty = cx.tcx.fn_sig(id).skip_binder(); + let ret_ty = fn_ty.output().skip_binder(); + if is_unit_type(ret_ty) { + cx.emit_span_lint( + MAP_UNIT_FN, + span, + MappingToUnit { + function_label: cx + .tcx + .span_of_impl(*id) + .unwrap_or(default_span), + argument_label: args[0].span, + map_label: arg_ty.default_span(cx.tcx), + suggestion: path.ident.span, + replace: "for_each".to_string(), + }, + ) + } + } else if let ty::Closure(id, subs) = arg_ty.kind() { + let cl_ty = subs.as_closure().sig(); + let ret_ty = cl_ty.output().skip_binder(); + if is_unit_type(ret_ty) { + cx.emit_span_lint( + MAP_UNIT_FN, + span, + MappingToUnit { + function_label: cx + .tcx + .span_of_impl(*id) + .unwrap_or(default_span), + argument_label: args[0].span, + map_label: arg_ty.default_span(cx.tcx), + suggestion: path.ident.span, + replace: "for_each".to_string(), + }, + ) + } + } + } + } + } + } +} + +fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { + if let Some(impl_id) = cx.tcx.impl_of_method(method_id) { + return cx.tcx.type_of(impl_id).skip_binder().is_slice(); + } + } + false +} + +fn is_unit_type(ty: Ty<'_>) -> bool { + ty.is_unit() || ty.is_never() +} + +fn is_diagnostic_name(cx: &LateContext<'_>, id: HirId, name: &str) -> bool { + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(id) { + if let Some(item) = cx.tcx.get_diagnostic_name(def_id) { + if item.as_str() == name { + return true; + } + } + } + false +} diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs new file mode 100644 index 00000000000..7a71fec769f --- /dev/null +++ b/compiler/rustc_lint/src/methods.rs @@ -0,0 +1,70 @@ +use crate::lints::CStringPtr; +use crate::LateContext; +use crate::LateLintPass; +use crate::LintContext; +use rustc_hir::{Expr, ExprKind}; +use rustc_middle::ty; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{symbol::sym, 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]); + +impl<'tcx> LateLintPass<'tcx> for TemporaryCStringAsPtr { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::MethodCall(as_ptr_path, as_ptr_receiver, ..) = expr.kind + && as_ptr_path.ident.name == sym::as_ptr + && let ExprKind::MethodCall(unwrap_path, unwrap_receiver, ..) = as_ptr_receiver.kind + && (unwrap_path.ident.name == sym::unwrap || unwrap_path.ident.name == sym::expect) + { + lint_cstring_as_ptr(cx, as_ptr_path.ident.span, unwrap_receiver, as_ptr_receiver); + } + } +} + +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, args) = source_type.kind() { + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) { + if let ty::Adt(adt, _) = args.type_at(0).kind() { + if cx.tcx.is_diagnostic_item(sym::cstring_type, adt.did()) { + cx.emit_span_lint( + TEMPORARY_CSTRING_AS_PTR, + as_ptr_span, + CStringPtr { as_ptr: as_ptr_span, unwrap: unwrap.span }, + ); + } + } + } + } +} diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs new file mode 100644 index 00000000000..93dd5e764c6 --- /dev/null +++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs @@ -0,0 +1,60 @@ +use crate::{LateContext, LateLintPass, LintContext}; + +use rustc_hir as hir; +use rustc_session::{declare_lint, declare_lint_pass}; + +declare_lint! { + /// The `multiple_supertrait_upcastable` lint detects when an object-safe trait has multiple + /// supertraits. + /// + /// ### Example + /// + /// ```rust + /// #![feature(multiple_supertrait_upcastable)] + /// trait A {} + /// trait B {} + /// + /// #[warn(multiple_supertrait_upcastable)] + /// trait C: A + B {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// To support upcasting with multiple supertraits, we need to store multiple vtables and this + /// can result in extra space overhead, even if no code actually uses upcasting. + /// This lint allows users to identify when such scenarios occur and to decide whether the + /// additional overhead is justified. + pub MULTIPLE_SUPERTRAIT_UPCASTABLE, + Allow, + "detect when an object-safe trait has multiple supertraits", + @feature_gate = multiple_supertrait_upcastable; +} + +declare_lint_pass!(MultipleSupertraitUpcastable => [MULTIPLE_SUPERTRAIT_UPCASTABLE]); + +impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + let def_id = item.owner_id.to_def_id(); + // NOTE(nbdd0121): use `object_safety_violations` instead of `is_object_safe` because + // the latter will report `where_clause_object_safety` lint. + if let hir::ItemKind::Trait(_, _, _, _, _) = item.kind + && cx.tcx.is_object_safe(def_id) + { + let direct_super_traits_iter = cx + .tcx + .explicit_super_predicates_of(def_id) + .predicates + .into_iter() + .filter_map(|(pred, _)| pred.as_trait_clause()); + if direct_super_traits_iter.count() > 1 { + cx.emit_span_lint( + MULTIPLE_SUPERTRAIT_UPCASTABLE, + cx.tcx.def_span(def_id), + crate::lints::MultipleSupertraitUpcastable { ident: item.ident }, + ); + } + } + } +} 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 00000000000..9f298a6071c --- /dev/null +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -0,0 +1,392 @@ +use crate::lints::{ + ConfusableIdentifierPair, IdentifierNonAsciiChar, IdentifierUncommonCodepoints, + MixedScriptConfusables, +}; +use crate::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast as ast; +use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::unord::UnordMap; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::symbol::Symbol; +use unicode_security::general_security_profile::IdentifierType; + +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().psess.symbol_gallery.symbols.lock(); + + // Sort by `Span` so that error messages make sense with respect to the + // order of identifier locations in the code. + // We will soon sort, so the initial order does not matter. + #[allow(rustc::potential_query_instability)] + 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.emit_span_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar); + if check_uncommon_codepoints + && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed) + { + let mut chars: Vec<_> = symbol_str + .chars() + .map(|c| (c, GeneralSecurityProfile::identifier_type(c))) + .collect(); + + for (id_ty, id_ty_descr) in [ + (IdentifierType::Exclusion, "Exclusion"), + (IdentifierType::Technical, "Technical"), + (IdentifierType::Limited_Use, "Limited_Use"), + (IdentifierType::Not_NFKC, "Not_NFKC"), + ] { + let codepoints: Vec<_> = + chars.extract_if(|(_, ty)| *ty == Some(id_ty)).collect(); + if codepoints.is_empty() { + continue; + } + cx.emit_span_lint( + UNCOMMON_CODEPOINTS, + sp, + IdentifierUncommonCodepoints { + codepoints_len: codepoints.len(), + codepoints: codepoints.into_iter().map(|(c, _)| c).collect(), + identifier_type: id_ty_descr, + }, + ); + } + + let remaining = chars + .extract_if(|(c, _)| !GeneralSecurityProfile::identifier_allowed(*c)) + .collect::<Vec<_>>(); + if !remaining.is_empty() { + cx.emit_span_lint( + UNCOMMON_CODEPOINTS, + sp, + IdentifierUncommonCodepoints { + codepoints_len: remaining.len(), + codepoints: remaining.into_iter().map(|(c, _)| c).collect(), + identifier_type: "Restricted", + }, + ); + } + } + } + + if has_non_ascii_idents && check_confusable_idents { + let mut skeleton_map: UnordMap<Symbol, (Symbol, Span, bool)> = + UnordMap::with_capacity(symbols.len()); + 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.emit_span_lint( + CONFUSABLE_IDENTS, + sp, + ConfusableIdentifierPair { + existing_sym: *existing_symbol, + sym: symbol, + label: *existing_span, + main_label: sp, + }, + ); + } + 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: FxIndexMap<AugmentedScriptSet, ScriptSetUsage> = + Default::default(); + let latin_augmented_script_set = AugmentedScriptSet::for_char('A'); + script_states.insert(latin_augmented_script_set, ScriptSetUsage::Verified); + + let mut has_suspicious = 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_suspicious = true; + ScriptSetUsage::Suspicious(vec![ch], sp) + } + }); + } + } + + if has_suspicious { + // The end result is put in `lint_reports` which is sorted. + #[allow(rustc::potential_query_instability)] + 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(); + + // The end result is put in `lint_reports` which is sorted. + #[allow(rustc::potential_query_instability)] + '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 { + 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; + } + cx.emit_span_lint( + MIXED_SCRIPT_CONFUSABLES, + sp, + MixedScriptConfusables { set: script_set.to_string(), includes }, + ); + } + } + } + } +} 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 00000000000..2dc2a0efdf0 --- /dev/null +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -0,0 +1,335 @@ +use crate::lints::{NonFmtPanicBraces, NonFmtPanicUnused}; +use crate::{fluent_generated as fluent, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, LangItem}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_middle::bug; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; +use rustc_parse_format::{ParseMode, Parser, Piece}; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_session::{declare_lint, declare_lint_pass}; +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 cx.tcx.is_lang_item(def_id, LangItem::BeginPanic) + || cx.tcx.is_lang_item(def_id, LangItem::Panic) + || f_diagnostic_name == Some(sym::panic_str_2015) + { + 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 ctxt = arg_span.ctxt(); + if ctxt.is_root() { + break; + } + let expn = ctxt.outer_expn_data(); + arg_macro = expn.macro_def_id; + arg_span = expn.call_site; + } + + #[allow(rustc::diagnostic_outside_of_impl)] + cx.span_lint(NON_FMT_PANICS, arg_span, |lint| { + lint.primary_message(fluent::lint_non_fmt_panic); + lint.arg("name", symbol); + lint.note(fluent::lint_note); + lint.note(fluent::lint_more_info_note); + if !is_arg_inside_call(arg_span, span) { + // No clue where this argument is coming from. + return; + } + if arg_macro.is_some_and(|id| cx.tcx.is_diagnostic_item(sym::format_macro, id)) { + // A case of `panic!(format!(..))`. + lint.note(fluent::lint_supports_fmt_note); + if let Some((open, close, _)) = find_delimiters(cx, arg_span) { + lint.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.is_str(), + ) || matches!( + ty.ty_adt_def(), + Some(ty_def) if cx.tcx.is_lang_item(ty_def.did(), LangItem::String), + ); + + let infcx = cx.tcx.infer_ctxt().build(); + let suggest_display = is_str + || cx.tcx.get_diagnostic_item(sym::Display).is_some_and(|t| { + infcx.type_implements_trait(t, [ty], cx.param_env).may_apply() + }); + let suggest_debug = !suggest_display + && cx.tcx.get_diagnostic_item(sym::Debug).is_some_and(|t| { + infcx.type_implements_trait(t, [ty], cx.param_env).may_apply() + }); + + 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 { + lint.span_suggestion_verbose( + arg_span.shrink_to_lo(), + fluent::lint_display_suggestion, + "\"{}\", ", + fmt_applicability, + ); + } else if suggest_debug { + lint.arg("ty", ty); + lint.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) { + lint.arg("already_suggested", suggest_display || suggest_debug); + lint.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, + ); + } + } + } + }); +} + +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().psess.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.emit_span_lint( + NON_FMT_PANICS, + arg_spans, + NonFmtPanicUnused { + count: n_arguments, + suggestion: is_arg_inside_call(arg.span, span).then_some(arg.span), + }, + ); + } 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.emit_span_lint( + NON_FMT_PANICS, + brace_spans.unwrap_or_else(|| vec![span]), + NonFmtPanicBraces { + count, + suggestion: is_arg_inside_call(arg.span, span).then_some(arg.span.shrink_to_lo()), + }, + ); + } +} + +/// Given the span of `some_macro!(args);`, gives the span of `(` and `)`, +/// and the type of (opening) delimiter used. +fn find_delimiters(cx: &LateContext<'_>, span: Span) -> Option<(Span, Span, char)> { + let snippet = cx.sess().psess.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/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs new file mode 100644 index 00000000000..2f8eea6cd18 --- /dev/null +++ b/compiler/rustc_lint/src/non_local_def.rs @@ -0,0 +1,545 @@ +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::HirId; +use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, TyKind}; +use rustc_hir::{Path, QPath}; +use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::{Obligation, ObligationCause}; +use rustc_middle::ty::{self, Binder, Ty, TyCtxt, TypeFoldable, TypeFolder}; +use rustc_middle::ty::{EarlyBinder, TraitRef, TypeSuperFoldable}; +use rustc_session::{declare_lint, impl_lint_pass}; +use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::Span; +use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind, Symbol}; +use rustc_trait_selection::error_reporting::traits::ambiguity::{ + compute_applicable_impls_for_diagnostics, CandidateSource, +}; +use rustc_trait_selection::infer::TyCtxtInferExt; + +use crate::fluent_generated as fluent; +use crate::lints::{NonLocalDefinitionsCargoUpdateNote, NonLocalDefinitionsDiag}; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `non_local_definitions` lint checks for `impl` blocks and `#[macro_export]` + /// macro inside bodies (functions, enum discriminant, ...). + /// + /// ### Example + /// + /// ```rust + /// #![warn(non_local_definitions)] + /// trait MyTrait {} + /// struct MyStruct; + /// + /// fn foo() { + /// impl MyTrait for MyStruct {} + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Creating non-local definitions go against expectation and can create discrepancies + /// in tooling. It should be avoided. It may become deny-by-default in edition 2024 + /// and higher, see the tracking issue <https://github.com/rust-lang/rust/issues/120363>. + /// + /// An `impl` definition is non-local if it is nested inside an item and neither + /// the type nor the trait are at the same nesting level as the `impl` block. + /// + /// All nested bodies (functions, enum discriminant, array length, consts) (expect for + /// `const _: Ty = { ... }` in top-level module, which is still undecided) are checked. + pub NON_LOCAL_DEFINITIONS, + Allow, + "checks for non-local definitions", + report_in_external_macro +} + +#[derive(Default)] +pub struct NonLocalDefinitions { + body_depth: u32, +} + +impl_lint_pass!(NonLocalDefinitions => [NON_LOCAL_DEFINITIONS]); + +// FIXME(Urgau): Figure out how to handle modules nested in bodies. +// It's currently not handled by the current logic because modules are not bodies. +// They don't even follow the correct order (check_body -> check_mod -> check_body_post) +// instead check_mod is called after every body has been handled. + +impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { + fn check_body(&mut self, _cx: &LateContext<'tcx>, _body: &Body<'tcx>) { + self.body_depth += 1; + } + + fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &Body<'tcx>) { + self.body_depth -= 1; + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if self.body_depth == 0 { + return; + } + + let def_id = item.owner_id.def_id.into(); + let parent = cx.tcx.parent(def_id); + let parent_def_kind = cx.tcx.def_kind(parent); + let parent_opt_item_name = cx.tcx.opt_item_name(parent); + + // Per RFC we (currently) ignore anon-const (`const _: Ty = ...`) in top-level module. + if self.body_depth == 1 + && parent_def_kind == DefKind::Const + && parent_opt_item_name == Some(kw::Underscore) + { + return; + } + + let cargo_update = || { + let oexpn = item.span.ctxt().outer_expn_data(); + if let Some(def_id) = oexpn.macro_def_id + && let ExpnKind::Macro(macro_kind, macro_name) = oexpn.kind + && def_id.krate != LOCAL_CRATE + && rustc_session::utils::was_invoked_from_cargo() + { + Some(NonLocalDefinitionsCargoUpdateNote { + macro_kind: macro_kind.descr(), + macro_name, + crate_name: cx.tcx.crate_name(def_id.krate), + }) + } else { + None + } + }; + + // determining if we are in a doctest context can't currently be determined + // by the code itself (there are no specific attributes), but fortunately rustdoc + // sets a perma-unstable env var for libtest so we just reuse that for now + let is_at_toplevel_doctest = + || self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok(); + + match item.kind { + ItemKind::Impl(impl_) => { + // The RFC states: + // + // > An item nested inside an expression-containing item (through any + // > level of nesting) may not define an impl Trait for Type unless + // > either the **Trait** or the **Type** is also nested inside the + // > same expression-containing item. + // + // To achieve this we get try to get the paths of the _Trait_ and + // _Type_, and we look inside thoses paths to try a find in one + // of them a type whose parent is the same as the impl definition. + // + // If that's the case this means that this impl block declaration + // is using local items and so we don't lint on it. + + // We also ignore anon-const in item by including the anon-const + // parent as well. + let parent_parent = if parent_def_kind == DefKind::Const + && parent_opt_item_name == Some(kw::Underscore) + { + Some(cx.tcx.parent(parent)) + } else { + None + }; + + // Part 1: Is the Self type local? + let self_ty_has_local_parent = + ty_has_local_parent(&impl_.self_ty.kind, cx, parent, parent_parent); + + if self_ty_has_local_parent { + return; + } + + // Part 2: Is the Trait local? + let of_trait_has_local_parent = impl_ + .of_trait + .map(|of_trait| path_has_local_parent(of_trait.path, cx, parent, parent_parent)) + .unwrap_or(false); + + if of_trait_has_local_parent { + return; + } + + // Part 3: Is the impl definition leaking outside it's defining scope? + // + // We always consider inherent impls to be leaking. + let impl_has_enough_non_local_candidates = cx + .tcx + .impl_trait_ref(def_id) + .map(|binder| { + impl_trait_ref_has_enough_non_local_candidates( + cx.tcx, + item.span, + def_id, + binder, + |did| did_has_local_parent(did, cx.tcx, parent, parent_parent), + ) + }) + .unwrap_or(false); + + if impl_has_enough_non_local_candidates { + return; + } + + // Get the span of the parent const item ident (if it's a not a const anon). + // + // Used to suggest changing the const item to a const anon. + let span_for_const_anon_suggestion = if parent_def_kind == DefKind::Const + && parent_opt_item_name != Some(kw::Underscore) + && let Some(parent) = parent.as_local() + && let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) + && let ItemKind::Const(ty, _, _) = item.kind + && let TyKind::Tup(&[]) = ty.kind + { + Some(item.ident.span) + } else { + None + }; + + let const_anon = matches!(parent_def_kind, DefKind::Const | DefKind::Static { .. }) + .then_some(span_for_const_anon_suggestion); + + let may_remove = match &impl_.self_ty.kind { + TyKind::Ptr(mut_ty) | TyKind::Ref(_, mut_ty) + if ty_has_local_parent(&mut_ty.ty.kind, cx, parent, parent_parent) => + { + let type_ = + if matches!(impl_.self_ty.kind, TyKind::Ptr(_)) { "*" } else { "&" }; + let part = format!("{}{}", type_, mut_ty.mutbl.prefix_str()); + Some((impl_.self_ty.span.shrink_to_lo().until(mut_ty.ty.span), part)) + } + _ => None, + }; + + let impl_span = item.span.shrink_to_lo().to(impl_.self_ty.span); + let mut ms = MultiSpan::from_span(impl_span); + + let (self_ty_span, self_ty_str) = + self_ty_kind_for_diagnostic(&impl_.self_ty, cx.tcx); + + ms.push_span_label( + self_ty_span, + fluent::lint_non_local_definitions_self_ty_not_local, + ); + let of_trait_str = if let Some(of_trait) = &impl_.of_trait { + ms.push_span_label( + path_span_without_args(&of_trait.path), + fluent::lint_non_local_definitions_of_trait_not_local, + ); + Some(path_name_to_string(&of_trait.path)) + } else { + None + }; + + let (doctest, move_to) = if is_at_toplevel_doctest() { + (true, None) + } else { + let mut collector = PathCollector { paths: Vec::new() }; + collector.visit_ty(&impl_.self_ty); + if let Some(of_trait) = &impl_.of_trait { + collector.visit_trait_ref(of_trait); + } + collector.visit_generics(&impl_.generics); + + let mut may_move: Vec<Span> = collector + .paths + .into_iter() + .filter_map(|path| { + if let Some(did) = path.res.opt_def_id() + && did_has_local_parent(did, cx.tcx, parent, parent_parent) + { + Some(cx.tcx.def_span(did)) + } else { + None + } + }) + .collect(); + may_move.sort(); + may_move.dedup(); + + let move_to = if may_move.is_empty() { + ms.push_span_label( + cx.tcx.def_span(parent), + fluent::lint_non_local_definitions_impl_move_help, + ); + None + } else { + Some((cx.tcx.def_span(parent), may_move)) + }; + + (false, move_to) + }; + + let macro_to_change = + if let ExpnKind::Macro(kind, name) = item.span.ctxt().outer_expn_data().kind { + Some((name.to_string(), kind.descr())) + } else { + None + }; + + cx.emit_span_lint( + NON_LOCAL_DEFINITIONS, + ms, + NonLocalDefinitionsDiag::Impl { + depth: self.body_depth, + body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent), + body_name: parent_opt_item_name + .map(|s| s.to_ident_string()) + .unwrap_or_else(|| "<unnameable>".to_string()), + cargo_update: cargo_update(), + const_anon, + self_ty_str, + of_trait_str, + move_to, + doctest, + may_remove, + has_trait: impl_.of_trait.is_some(), + macro_to_change, + }, + ) + } + ItemKind::Macro(_macro, MacroKind::Bang) + if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) => + { + cx.emit_span_lint( + NON_LOCAL_DEFINITIONS, + item.span, + NonLocalDefinitionsDiag::MacroRules { + depth: self.body_depth, + body_kind_descr: cx.tcx.def_kind_descr(parent_def_kind, parent), + body_name: parent_opt_item_name + .map(|s| s.to_ident_string()) + .unwrap_or_else(|| "<unnameable>".to_string()), + cargo_update: cargo_update(), + doctest: is_at_toplevel_doctest(), + }, + ) + } + _ => {} + } + } +} + +// Detecting if the impl definition is leaking outside of its defining scope. +// +// Rule: for each impl, instantiate all local types with inference vars and +// then assemble candidates for that goal, if there are more than 1 (non-private +// impls), it does not leak. +// +// https://github.com/rust-lang/rust/issues/121621#issuecomment-1976826895 +fn impl_trait_ref_has_enough_non_local_candidates<'tcx>( + tcx: TyCtxt<'tcx>, + infer_span: Span, + trait_def_id: DefId, + binder: EarlyBinder<'tcx, TraitRef<'tcx>>, + mut did_has_local_parent: impl FnMut(DefId) -> bool, +) -> bool { + let infcx = tcx + .infer_ctxt() + // We use the new trait solver since the obligation we are trying to + // prove here may overflow and those are fatal in the old trait solver. + // Which is unacceptable for a lint. + // + // Thanksfully the part we use here are very similar to the + // new-trait-solver-as-coherence, which is in stabilization. + // + // https://github.com/rust-lang/rust/issues/123573 + .with_next_trait_solver(true) + .build(); + + let trait_ref = binder.instantiate(tcx, infcx.fresh_args_for_item(infer_span, trait_def_id)); + + let trait_ref = trait_ref.fold_with(&mut ReplaceLocalTypesWithInfer { + infcx: &infcx, + infer_span, + did_has_local_parent: &mut did_has_local_parent, + }); + + let poly_trait_obligation = Obligation::new( + tcx, + ObligationCause::dummy(), + ty::ParamEnv::empty(), + Binder::dummy(trait_ref), + ); + + let ambiguities = compute_applicable_impls_for_diagnostics(&infcx, &poly_trait_obligation); + + let mut it = ambiguities.iter().filter(|ambi| match ambi { + CandidateSource::DefId(did) => !did_has_local_parent(*did), + CandidateSource::ParamEnv(_) => unreachable!(), + }); + + let _ = it.next(); + it.next().is_some() +} + +/// Replace every local type by inference variable. +/// +/// ```text +/// <Global<Local> as std::cmp::PartialEq<Global<Local>>> +/// to +/// <Global<_> as std::cmp::PartialEq<Global<_>>> +/// ``` +struct ReplaceLocalTypesWithInfer<'a, 'tcx, F: FnMut(DefId) -> bool> { + infcx: &'a InferCtxt<'tcx>, + did_has_local_parent: F, + infer_span: Span, +} + +impl<'a, 'tcx, F: FnMut(DefId) -> bool> TypeFolder<TyCtxt<'tcx>> + for ReplaceLocalTypesWithInfer<'a, 'tcx, F> +{ + fn cx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { + if let Some(def) = t.ty_adt_def() + && (self.did_has_local_parent)(def.did()) + { + self.infcx.next_ty_var(self.infer_span) + } else { + t.super_fold_with(self) + } + } +} + +/// Simple hir::Path collector +struct PathCollector<'tcx> { + paths: Vec<Path<'tcx>>, +} + +impl<'tcx> Visitor<'tcx> for PathCollector<'tcx> { + fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { + self.paths.push(path.clone()); // need to clone, bc of the restricted lifetime + intravisit::walk_path(self, path) + } +} + +/// Given a `Ty` we check if the (outermost) type is local. +fn ty_has_local_parent( + ty_kind: &TyKind<'_>, + cx: &LateContext<'_>, + impl_parent: DefId, + impl_parent_parent: Option<DefId>, +) -> bool { + match ty_kind { + TyKind::Path(QPath::Resolved(_, ty_path)) => { + path_has_local_parent(ty_path, cx, impl_parent, impl_parent_parent) + } + TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => path_has_local_parent( + principle_poly_trait_ref.trait_ref.path, + cx, + impl_parent, + impl_parent_parent, + ), + TyKind::TraitObject([], _, _) + | TyKind::InferDelegation(_, _) + | TyKind::Slice(_) + | TyKind::Array(_, _) + | TyKind::Ptr(_) + | TyKind::Ref(_, _) + | TyKind::BareFn(_) + | TyKind::Never + | TyKind::Tup(_) + | TyKind::Path(_) + | TyKind::Pat(..) + | TyKind::AnonAdt(_) + | TyKind::OpaqueDef(_, _, _) + | TyKind::Typeof(_) + | TyKind::Infer + | TyKind::Err(_) => false, + } +} + +/// Given a path and a parent impl def id, this checks if the if parent resolution +/// def id correspond to the def id of the parent impl definition. +/// +/// Given this path, we will look at the path (and ignore any generic args): +/// +/// ```text +/// std::convert::PartialEq<Foo<Bar>> +/// ^^^^^^^^^^^^^^^^^^^^^^^ +/// ``` +#[inline] +fn path_has_local_parent( + path: &Path<'_>, + cx: &LateContext<'_>, + impl_parent: DefId, + impl_parent_parent: Option<DefId>, +) -> bool { + path.res + .opt_def_id() + .is_some_and(|did| did_has_local_parent(did, cx.tcx, impl_parent, impl_parent_parent)) +} + +/// Given a def id and a parent impl def id, this checks if the parent +/// def id (modulo modules) correspond to the def id of the parent impl definition. +#[inline] +fn did_has_local_parent( + did: DefId, + tcx: TyCtxt<'_>, + impl_parent: DefId, + impl_parent_parent: Option<DefId>, +) -> bool { + did.is_local() + && if let Some(did_parent) = tcx.opt_parent(did) { + did_parent == impl_parent + || Some(did_parent) == impl_parent_parent + || !did_parent.is_crate_root() + && tcx.def_kind(did_parent) == DefKind::Mod + && did_has_local_parent(did_parent, tcx, impl_parent, impl_parent_parent) + } else { + false + } +} + +/// Return for a given `Path` the span until the last args +fn path_span_without_args(path: &Path<'_>) -> Span { + if let Some(args) = &path.segments.last().unwrap().args { + path.span.until(args.span_ext) + } else { + path.span + } +} + +/// Return a "error message-able" ident for the last segment of the `Path` +fn path_name_to_string(path: &Path<'_>) -> String { + path.segments.last().unwrap().ident.name.to_ident_string() +} + +/// Compute the `Span` and visual representation for the `Self` we want to point at; +/// It follows part of the actual logic of non-local, and if possible return the least +/// amount possible for the span and representation. +fn self_ty_kind_for_diagnostic(ty: &rustc_hir::Ty<'_>, tcx: TyCtxt<'_>) -> (Span, String) { + match ty.kind { + TyKind::Path(QPath::Resolved(_, ty_path)) => ( + path_span_without_args(ty_path), + ty_path + .res + .opt_def_id() + .map(|did| tcx.opt_item_name(did)) + .flatten() + .as_ref() + .map(|s| Symbol::as_str(s)) + .unwrap_or("<unnameable>") + .to_string(), + ), + TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => { + let path = &principle_poly_trait_ref.trait_ref.path; + ( + path_span_without_args(path), + path.res + .opt_def_id() + .map(|did| tcx.opt_item_name(did)) + .flatten() + .as_ref() + .map(|s| Symbol::as_str(s)) + .unwrap_or("<unnameable>") + .to_string(), + ) + } + _ => (ty.span, rustc_hir_pretty::ty_to_string(&tcx, ty)), + } +} diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs new file mode 100644 index 00000000000..d64f4447162 --- /dev/null +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -0,0 +1,554 @@ +use crate::lints::{ + NonCamelCaseType, NonCamelCaseTypeSub, NonSnakeCaseDiag, NonSnakeCaseDiagSub, + NonUpperCaseGlobal, NonUpperCaseGlobalSub, +}; +use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +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_session::config::CrateType; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::def_id::LocalDefId; +use rustc_span::symbol::{sym, Ident}; +use rustc_span::{BytePos, Span}; +use rustc_target::spec::abi::Abi; + +#[derive(PartialEq)] +pub enum MethodLateContext { + TraitAutoImpl, + TraitImpl, + PlainImpl, +} + +pub fn method_context(cx: &LateContext<'_>, id: LocalDefId) -> MethodLateContext { + let item = cx.tcx.associated_item(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, + }, + } +} + +fn assoc_item_in_trait_impl(cx: &LateContext<'_>, ii: &hir::ImplItem<'_>) -> bool { + let item = cx.tcx.associated_item(ii.owner_id); + item.trait_item_def_id.is_some() +} + +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) { + let cc = to_camel_case(name); + let sub = if *name != cc { + NonCamelCaseTypeSub::Suggestion { span: ident.span, replace: cc } + } else { + NonCamelCaseTypeSub::Label { span: ident.span } + }; + cx.emit_span_lint( + NON_CAMEL_CASE_TYPES, + ident.span, + NonCamelCaseType { sort, name, sub }, + ); + } + } +} + +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), + + // N.B. This check is only for inherent associated types, so that we don't lint against + // trait impls where we should have warned for the trait definition already. + ast::ItemKind::Impl(box ast::Impl { of_trait: None, items, .. }) => { + for it in items { + // FIXME: this doesn't respect `#[allow(..)]` on the item itself. + if let ast::AssocItemKind::Type(..) = it.kind { + self.check_case(cx, "associated type", &it.ident); + } + } + } + _ => (), + } + } + + fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { + if let ast::AssocItemKind::Type(..) = 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", ¶m.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) { + let span = ident.span; + let sc = NonSnakeCase::to_snake_case(name); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Uppercase Letter". + let sub = 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 !span.is_dummy() { + let sc_ident = Ident::from_str_and_span(&sc, span); + 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() { + NonSnakeCaseDiagSub::RenameOrConvertSuggestion { + span, + suggestion: sc_ident, + } + } else { + NonSnakeCaseDiagSub::SuggestionAndNote { span } + } + } else { + NonSnakeCaseDiagSub::ConvertSuggestion { span, suggestion: sc.clone() } + } + } else { + NonSnakeCaseDiagSub::Help + } + } else { + NonSnakeCaseDiagSub::Label { span } + }; + cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { + fn check_mod(&mut self, cx: &LateContext<'_>, _: &'tcx hir::Mod<'tcx>, id: hir::HirId) { + if id != hir::CRATE_HIR_ID { + return; + } + + if cx.tcx.crate_types().iter().all(|&crate_type| crate_type == CrateType::Executable) { + return; + } + + let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name { + Some(Ident::from_str(name)) + } else { + attr::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", ¶m.name.ident()); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'_>, + fk: FnKind<'_>, + _: &hir::FnDecl<'_>, + _: &hir::Body<'_>, + _: Span, + id: LocalDefId, + ) { + match &fk { + FnKind::Method(ident, sig, ..) => match method_context(cx, id) { + MethodLateContext::PlainImpl => { + if sig.header.abi != Abi::Rust && cx.tcx.has_attr(id, 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.tcx.has_attr(id, 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::PatField(field) = cx.tcx.parent_hir_node(hid) { + if !field.is_shorthand { + // 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()) { + let uc = NonSnakeCase::to_snake_case(name).to_uppercase(); + // We cannot provide meaningful suggestions + // if the characters are in the category of "Lowercase Letter". + let sub = if *name != uc { + NonUpperCaseGlobalSub::Suggestion { span: ident.span, replace: uc } + } else { + NonUpperCaseGlobalSub::Label { span: ident.span } + }; + cx.emit_span_lint( + NON_UPPER_CASE_GLOBALS, + ident.span, + NonUpperCaseGlobal { sort, name, sub }, + ); + } + } +} + +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 !attr::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 + && !assoc_item_in_trait_impl(cx, ii) + { + 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, 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 { is_host_effect, .. } = param.kind { + // `host` params are explicitly allowed to be lowercase. + if is_host_effect { + return; + } + NonUpperCaseGlobals::check_upper_case(cx, "const parameter", ¶m.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 00000000000..39c525b8623 --- /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 00000000000..307e4bebe9a --- /dev/null +++ b/compiler/rustc_lint/src/noop_method_call.rs @@ -0,0 +1,161 @@ +use crate::context::LintContext; +use crate::lints::{ + NoopMethodCallDiag, SuspiciousDoubleRefCloneDiag, SuspiciousDoubleRefDerefDiag, +}; +use crate::LateContext; +use crate::LateLintPass; +use rustc_hir::def::DefKind; +use rustc_hir::{Expr, ExprKind}; +use rustc_middle::ty; +use rustc_middle::ty::adjustment::Adjust; +use rustc_session::{declare_lint, declare_lint_pass}; +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)] + /// 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, + Warn, + "detects the use of well-known noop methods" +} + +declare_lint! { + /// The `suspicious_double_ref_op` lint checks for usage of `.clone()`/`.borrow()`/`.deref()` + /// on an `&&T` when `T: !Deref/Borrow/Clone`, which means the call will return the inner `&T`, + /// instead of performing the operation on the underlying `T` and can be confusing. + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// struct Foo; + /// let foo = &&Foo; + /// let clone: &Foo = foo.clone(); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Since `Foo` doesn't implement `Clone`, running `.clone()` only dereferences the double + /// reference, instead of cloning the inner type which should be what was intended. + pub SUSPICIOUS_DOUBLE_REF_OP, + Warn, + "suspicious call of trait method on `&&T`" +} + +declare_lint_pass!(NoopMethodCall => [NOOP_METHOD_CALL, SUSPICIOUS_DOUBLE_REF_OP]); + +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, receiver, _, call_span) = &expr.kind else { + return; + }; + + if call_span.from_expansion() { + return; + } + + // We only care about method calls corresponding to the `Clone`, `Deref` and `Borrow` + // traits and ignore any other method call. + + let Some((DefKind::AssocFn, did)) = cx.typeck_results().type_dependent_def(expr.hir_id) + else { + return; + }; + + let Some(trait_id) = cx.tcx.trait_of_item(did) else { return }; + + let Some(trait_) = cx.tcx.get_diagnostic_name(trait_id) else { return }; + + if !matches!(trait_, sym::Borrow | sym::Clone | sym::Deref) { + return; + }; + + let args = cx + .tcx + .normalize_erasing_regions(cx.param_env, cx.typeck_results().node_args(expr.hir_id)); + // Resolve the trait method instance. + let Ok(Some(i)) = ty::Instance::try_resolve(cx.tcx, cx.param_env, did, args) 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_ty = cx.typeck_results().expr_ty(receiver); + let expr_ty = cx.typeck_results().expr_ty_adjusted(expr); + let arg_adjustments = cx.typeck_results().expr_adjustments(receiver); + + // If there is any user defined auto-deref step, then we don't want to warn. + // https://github.com/rust-lang/rust-clippy/issues/9272 + if arg_adjustments.iter().any(|adj| matches!(adj.kind, Adjust::Deref(Some(_)))) { + return; + } + + let expr_span = expr.span; + let span = expr_span.with_lo(receiver.span.hi()); + + let orig_ty = expr_ty.peel_refs(); + + if receiver_ty == expr_ty { + let suggest_derive = match orig_ty.kind() { + ty::Adt(def, _) => Some(cx.tcx.def_span(def.did()).shrink_to_lo()), + _ => None, + }; + cx.emit_span_lint( + NOOP_METHOD_CALL, + span, + NoopMethodCallDiag { + method: call.ident.name, + orig_ty, + trait_, + label: span, + suggest_derive, + }, + ); + } else { + match name { + // If `type_of(x) == T` and `x.borrow()` is used to get `&T`, + // then that should be allowed + sym::noop_method_borrow => return, + sym::noop_method_clone => cx.emit_span_lint( + SUSPICIOUS_DOUBLE_REF_OP, + span, + SuspiciousDoubleRefCloneDiag { ty: expr_ty }, + ), + sym::noop_method_deref => cx.emit_span_lint( + SUSPICIOUS_DOUBLE_REF_OP, + span, + SuspiciousDoubleRefDerefDiag { ty: expr_ty }, + ), + _ => return, + } + } + } +} diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs new file mode 100644 index 00000000000..fdb71ad41a7 --- /dev/null +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -0,0 +1,209 @@ +use rustc_hir as hir; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_macros::{LintDiagnostic, Subdiagnostic}; +use rustc_middle::ty::print::{PrintTraitPredicateExt as _, TraitPredPrintModifiersAndPath}; +use rustc_middle::ty::{self, fold::BottomUpFolder, Ty, TypeFoldable}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{symbol::kw, Span}; +use rustc_trait_selection::traits::{self, ObligationCtxt}; + +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `opaque_hidden_inferred_bound` lint detects cases in which nested + /// `impl Trait` in associated type bounds are not written generally enough + /// to satisfy the bounds of the associated type. + /// + /// ### Explanation + /// + /// This functionality was removed in #97346, but then rolled back in #99860 + /// because it caused regressions. + /// + /// We plan on reintroducing this as a hard error, but in the meantime, + /// this lint serves to warn and suggest fixes for any use-cases which rely + /// on this behavior. + /// + /// ### Example + /// + /// ```rust + /// #![feature(type_alias_impl_trait)] + /// + /// trait Duh {} + /// + /// impl Duh for i32 {} + /// + /// trait Trait { + /// type Assoc: Duh; + /// } + /// + /// impl<F: Duh> Trait for F { + /// type Assoc = F; + /// } + /// + /// type Tait = impl Sized; + /// + /// fn test() -> impl Trait<Assoc = Tait> { + /// 42 + /// } + /// + /// fn main() {} + /// ``` + /// + /// {{produces}} + /// + /// In this example, `test` declares that the associated type `Assoc` for + /// `impl Trait` is `impl Sized`, which does not satisfy the bound `Duh` + /// on the associated type. + /// + /// Although the hidden type, `i32` does satisfy this bound, we do not + /// consider the return type to be well-formed with this lint. It can be + /// fixed by changing `Tait = impl Sized` into `Tait = impl Sized + Duh`. + pub OPAQUE_HIDDEN_INFERRED_BOUND, + Warn, + "detects the use of nested `impl Trait` types in associated type bounds that are not general enough" +} + +declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]); + +impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + let hir::ItemKind::OpaqueTy(opaque) = &item.kind else { + return; + }; + let def_id = item.owner_id.def_id.to_def_id(); + let infcx = &cx.tcx.infer_ctxt().build(); + // For every projection predicate in the opaque type's explicit bounds, + // check that the type that we're assigning actually satisfies the bounds + // of the associated type. + for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).iter_identity_copied() { + infcx.enter_forall(pred.kind(), |predicate| { + let ty::ClauseKind::Projection(proj) = predicate else { + return; + }; + // Only check types, since those are the only things that may + // have opaques in them anyways. + let Some(proj_term) = proj.term.as_type() else { return }; + + // HACK: `impl Trait<Assoc = impl Trait2>` from an RPIT is "ok"... + if let ty::Alias(ty::Opaque, opaque_ty) = *proj_term.kind() + && cx.tcx.parent(opaque_ty.def_id) == def_id + && matches!( + opaque.origin, + hir::OpaqueTyOrigin::FnReturn(_) | hir::OpaqueTyOrigin::AsyncFn(_) + ) + { + return; + } + + // HACK: `async fn() -> Self` in traits is "ok"... + // This is not really that great, but it's similar to why the `-> Self` + // return type is well-formed in traits even when `Self` isn't sized. + if let ty::Param(param_ty) = *proj_term.kind() + && param_ty.name == kw::SelfUpper + && matches!(opaque.origin, hir::OpaqueTyOrigin::AsyncFn(_)) + && opaque.in_trait + { + return; + } + + let proj_ty = Ty::new_projection_from_args( + cx.tcx, + proj.projection_term.def_id, + proj.projection_term.args, + ); + // For every instance of the projection type in the bounds, + // replace them with the term we're assigning to the associated + // type in our opaque type. + let proj_replacer = &mut BottomUpFolder { + tcx: cx.tcx, + ty_op: |ty| if ty == proj_ty { proj_term } else { ty }, + lt_op: |lt| lt, + ct_op: |ct| ct, + }; + // For example, in `impl Trait<Assoc = impl Send>`, for all of the bounds on `Assoc`, + // e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::Assoc: OtherTrait` + // with `impl Send: OtherTrait`. + for (assoc_pred, assoc_pred_span) in cx + .tcx + .explicit_item_bounds(proj.projection_term.def_id) + .iter_instantiated_copied(cx.tcx, proj.projection_term.args) + { + let assoc_pred = assoc_pred.fold_with(proj_replacer); + + let ocx = ObligationCtxt::new(infcx); + let assoc_pred = + ocx.normalize(&traits::ObligationCause::dummy(), cx.param_env, assoc_pred); + if !ocx.select_all_or_error().is_empty() { + // Can't normalize for some reason...? + continue; + } + + ocx.register_obligation(traits::Obligation::new( + cx.tcx, + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + )); + + // If that predicate doesn't hold modulo regions (but passed during type-check), + // then we must've taken advantage of the hack in `project_and_unify_types` where + // we replace opaques with inference vars. Emit a warning! + if !ocx.select_all_or_error().is_empty() { + // If it's a trait bound and an opaque that doesn't satisfy it, + // then we can emit a suggestion to add the bound. + let add_bound = match (proj_term.kind(), assoc_pred.kind().skip_binder()) { + ( + ty::Alias(ty::Opaque, ty::AliasTy { def_id, .. }), + ty::ClauseKind::Trait(trait_pred), + ) => Some(AddBound { + suggest_span: cx.tcx.def_span(*def_id).shrink_to_hi(), + trait_ref: trait_pred.print_modifiers_and_trait_path(), + }), + _ => None, + }; + + cx.emit_span_lint( + OPAQUE_HIDDEN_INFERRED_BOUND, + pred_span, + OpaqueHiddenInferredBoundLint { + ty: Ty::new_opaque( + cx.tcx, + def_id, + ty::GenericArgs::identity_for_item(cx.tcx, def_id), + ), + proj_ty: proj_term, + assoc_pred_span, + add_bound, + }, + ); + } + } + }); + } + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_opaque_hidden_inferred_bound)] +struct OpaqueHiddenInferredBoundLint<'tcx> { + ty: Ty<'tcx>, + proj_ty: Ty<'tcx>, + #[label(lint_specifically)] + assoc_pred_span: Span, + #[subdiagnostic] + add_bound: Option<AddBound<'tcx>>, +} + +#[derive(Subdiagnostic)] +#[suggestion( + lint_opaque_hidden_inferred_bound_sugg, + style = "verbose", + applicability = "machine-applicable", + code = " + {trait_ref}" +)] +struct AddBound<'tcx> { + #[primary_span] + suggest_span: Span, + #[skip_arg] + trait_ref: TraitPredPrintModifiersAndPath<'tcx>, +} 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 00000000000..c1f5cd45dc8 --- /dev/null +++ b/compiler/rustc_lint/src/pass_by_value.rs @@ -0,0 +1,93 @@ +use crate::lints::PassByValueDiag; +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::{GenericArg, PathSegment, QPath, TyKind}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +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<TyKind>`) + 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::Ref(_, 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.emit_span_lint( + PASS_BY_VALUE, + ty.span, + PassByValueDiag { ty: t, suggestion: ty.span }, + ); + } + } + _ => {} + } + } +} + +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::SelfTyAlias { alias_to: did, is_trait_impl: false, .. } => { + if let ty::Adt(adt, args) = cx.tcx.type_of(did).instantiate_identity().kind() { + if cx.tcx.has_attr(adt.did(), sym::rustc_pass_by_value) { + return Some(cx.tcx.def_path_str_with_args(adt.did(), args)); + } + } + } + _ => (), + } + } + + 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.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.value.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 00000000000..2a843977990 --- /dev/null +++ b/compiler/rustc_lint/src/passes.rs @@ -0,0 +1,241 @@ +use crate::context::{EarlyContext, LateContext}; + +use rustc_session::lint::builtin::HardwiredLints; +use rustc_session::lint::LintPass; + +#[macro_export] +macro_rules! late_lint_methods { + ($macro:path, $args:tt) => ( + $macro!($args, [ + fn check_body(a: &rustc_hir::Body<'tcx>); + fn check_body_post(a: &rustc_hir::Body<'tcx>); + fn check_crate(); + fn check_crate_post(); + fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_hir::HirId); + fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>); + fn check_item(a: &'tcx rustc_hir::Item<'tcx>); + fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>); + fn check_local(a: &'tcx rustc_hir::LetStmt<'tcx>); + fn check_block(a: &'tcx rustc_hir::Block<'tcx>); + fn check_block_post(a: &'tcx rustc_hir::Block<'tcx>); + fn check_stmt(a: &'tcx rustc_hir::Stmt<'tcx>); + fn check_arm(a: &'tcx rustc_hir::Arm<'tcx>); + fn check_pat(a: &'tcx rustc_hir::Pat<'tcx>); + fn check_expr(a: &'tcx rustc_hir::Expr<'tcx>); + fn check_expr_post(a: &'tcx rustc_hir::Expr<'tcx>); + fn check_ty(a: &'tcx rustc_hir::Ty<'tcx>); + fn check_generic_param(a: &'tcx rustc_hir::GenericParam<'tcx>); + fn check_generics(a: &'tcx rustc_hir::Generics<'tcx>); + fn check_poly_trait_ref(a: &'tcx rustc_hir::PolyTraitRef<'tcx>); + fn check_fn( + a: rustc_hir::intravisit::FnKind<'tcx>, + b: &'tcx rustc_hir::FnDecl<'tcx>, + c: &'tcx rustc_hir::Body<'tcx>, + d: rustc_span::Span, + e: rustc_span::def_id::LocalDefId); + fn check_trait_item(a: &'tcx rustc_hir::TraitItem<'tcx>); + fn check_impl_item(a: &'tcx rustc_hir::ImplItem<'tcx>); + fn check_impl_item_post(a: &'tcx rustc_hir::ImplItem<'tcx>); + fn check_struct_def(a: &'tcx rustc_hir::VariantData<'tcx>); + fn check_field_def(a: &'tcx rustc_hir::FieldDef<'tcx>); + fn check_variant(a: &'tcx rustc_hir::Variant<'tcx>); + fn check_path(a: &rustc_hir::Path<'tcx>, b: rustc_hir::HirId); + fn check_attribute(a: &'tcx rustc_ast::Attribute); + fn check_attributes(a: &'tcx [rustc_ast::Attribute]); + fn check_attributes_post(a: &'tcx [rustc_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! declare_late_lint_pass { + ([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + pub trait LateLintPass<'tcx>: LintPass { + $(#[inline(always)] fn $name(&mut self, _: &LateContext<'tcx>, $(_: $arg),*) {})* + } + ) +} + +// Declare the `LateLintPass` trait, which contains empty default definitions +// for all the `check_*` methods. +late_lint_methods!(declare_late_lint_pass, []); + +impl LateLintPass<'_> for HardwiredLints {} + +#[macro_export] +macro_rules! expand_combined_late_lint_pass_method { + ([$($pass:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$pass.$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: &$crate::LateContext<'tcx>, $($param: $arg),*) { + $crate::expand_combined_late_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +/// Combines multiple lints passes into a single lint pass, at compile time, +/// for maximum speed. Each `check_foo` method in `$methods` within this pass +/// simply calls `check_foo` once per `$pass`. Compare with +/// `LateLintPassObjects`, which is similar, but combines lint passes at +/// runtime. +#[macro_export] +macro_rules! declare_combined_late_lint_pass { + ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($pass: $pass,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($pass: $constructor,)* + } + } + + $v fn get_lints() -> $crate::LintVec { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$pass::get_lints());)* + lints + } + } + + impl<'tcx> $crate::LateLintPass<'tcx> for $name { + $crate::expand_combined_late_lint_pass_methods!([$($pass),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl $crate::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: &rustc_ast::Param); + fn check_ident(a: rustc_span::symbol::Ident); + fn check_crate(a: &rustc_ast::Crate); + fn check_crate_post(a: &rustc_ast::Crate); + fn check_item(a: &rustc_ast::Item); + fn check_item_post(a: &rustc_ast::Item); + fn check_local(a: &rustc_ast::Local); + fn check_block(a: &rustc_ast::Block); + fn check_stmt(a: &rustc_ast::Stmt); + fn check_arm(a: &rustc_ast::Arm); + fn check_pat(a: &rustc_ast::Pat); + fn check_pat_post(a: &rustc_ast::Pat); + fn check_expr(a: &rustc_ast::Expr); + fn check_expr_post(a: &rustc_ast::Expr); + fn check_ty(a: &rustc_ast::Ty); + fn check_generic_arg(a: &rustc_ast::GenericArg); + fn check_generic_param(a: &rustc_ast::GenericParam); + fn check_generics(a: &rustc_ast::Generics); + fn check_poly_trait_ref(a: &rustc_ast::PolyTraitRef); + fn check_fn( + a: rustc_ast::visit::FnKind<'_>, + c: rustc_span::Span, + d_: rustc_ast::NodeId); + fn check_trait_item(a: &rustc_ast::AssocItem); + fn check_impl_item(a: &rustc_ast::AssocItem); + fn check_variant(a: &rustc_ast::Variant); + fn check_attribute(a: &rustc_ast::Attribute); + fn check_attributes(a: &[rustc_ast::Attribute]); + fn check_attributes_post(a: &[rustc_ast::Attribute]); + fn check_mac_def(a: &rustc_ast::MacroDef); + fn check_mac(a: &rustc_ast::MacCall); + + fn enter_where_predicate(a: &rustc_ast::WherePredicate); + fn exit_where_predicate(a: &rustc_ast::WherePredicate); + ]); + ) +} + +macro_rules! declare_early_lint_pass { + ([], [$($(#[$attr:meta])* fn $name:ident($($param:ident: $arg:ty),*);)*]) => ( + pub trait EarlyLintPass: LintPass { + $(#[inline(always)] fn $name(&mut self, _: &EarlyContext<'_>, $(_: $arg),*) {})* + } + ) +} + +// Declare the `EarlyLintPass` trait, which contains empty default definitions +// for all the `check_*` methods. +early_lint_methods!(declare_early_lint_pass, []); + +#[macro_export] +macro_rules! expand_combined_early_lint_pass_method { + ([$($pass:ident),*], $self: ident, $name: ident, $params:tt) => ({ + $($self.$pass.$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: &$crate::EarlyContext<'_>, $($param: $arg),*) { + $crate::expand_combined_early_lint_pass_method!($passes, self, $name, (context, $($param),*)); + })* + ) +} + +/// Combines multiple lints passes into a single lint pass, at compile time, +/// for maximum speed. Each `check_foo` method in `$methods` within this pass +/// simply calls `check_foo` once per `$pass`. Compare with +/// `EarlyLintPassObjects`, which is similar, but combines lint passes at +/// runtime. +#[macro_export] +macro_rules! declare_combined_early_lint_pass { + ([$v:vis $name:ident, [$($pass:ident: $constructor:expr,)*]], $methods:tt) => ( + #[allow(non_snake_case)] + $v struct $name { + $($pass: $pass,)* + } + + impl $name { + $v fn new() -> Self { + Self { + $($pass: $constructor,)* + } + } + + $v fn get_lints() -> $crate::LintVec { + let mut lints = Vec::new(); + $(lints.extend_from_slice(&$pass::get_lints());)* + lints + } + } + + impl $crate::EarlyLintPass for $name { + $crate::expand_combined_early_lint_pass_methods!([$($pass),*], $methods); + } + + #[allow(rustc::lint_pass_impl_without_macro)] + impl $crate::LintPass for $name { + fn name(&self) -> &'static str { + panic!() + } + } + ) +} + +/// A lint pass boxed up as a trait object. +pub(crate) type EarlyLintPassObject = Box<dyn EarlyLintPass + 'static>; +pub(crate) type LateLintPassObject<'tcx> = Box<dyn LateLintPass<'tcx> + 'tcx>; diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs new file mode 100644 index 00000000000..8038115ef51 --- /dev/null +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -0,0 +1,157 @@ +use crate::{lints::PtrNullChecksDiag, LateContext, LateLintPass, LintContext}; +use rustc_ast::LitKind; +use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +declare_lint! { + /// The `useless_ptr_null_checks` lint checks for useless null checks against pointers + /// obtained from non-null types. + /// + /// ### Example + /// + /// ```rust + /// # fn test() {} + /// let fn_ptr: fn() = /* somehow obtained nullable function pointer */ + /// # test; + /// + /// if (fn_ptr as *const ()).is_null() { /* ... */ } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Function pointers and references are assumed to be non-null, checking them for null + /// will always return false. + USELESS_PTR_NULL_CHECKS, + Warn, + "useless checking of non-null-typed pointer" +} + +declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]); + +/// This function checks if the expression is from a series of consecutive casts, +/// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either +/// a fn ptr, a reference, or a function call whose definition is +/// annotated with `#![rustc_never_returns_null_ptr]`. +/// If this situation is present, the function returns the appropriate diagnostic. +fn incorrect_check<'a, 'tcx: 'a>( + cx: &'a LateContext<'tcx>, + mut e: &'a Expr<'a>, +) -> Option<PtrNullChecksDiag<'tcx>> { + let mut had_at_least_one_cast = false; + loop { + e = e.peel_blocks(); + if let ExprKind::MethodCall(_, _expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr) + && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) + { + return Some(PtrNullChecksDiag::FnRet { fn_name }); + } else if let ExprKind::Call(path, _args) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr) + && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) + { + return Some(PtrNullChecksDiag::FnRet { fn_name }); + } + e = if let ExprKind::Cast(expr, t) = e.kind + && let TyKind::Ptr(_) = t.kind + { + had_at_least_one_cast = true; + expr + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_cast | sym::ptr_cast_mut)) + { + had_at_least_one_cast = true; + expr + } else if had_at_least_one_cast { + let orig_ty = cx.typeck_results().expr_ty(e); + return if orig_ty.is_fn() { + Some(PtrNullChecksDiag::FnPtr { orig_ty, label: e.span }) + } else if orig_ty.is_ref() { + Some(PtrNullChecksDiag::Ref { orig_ty, label: e.span }) + } else { + None + }; + } else { + return None; + }; + } +} + +impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match expr.kind { + // Catching: + // <*<const/mut> <ty>>::is_null(fn_ptr as *<const/mut> <ty>) + ExprKind::Call(path, [arg]) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, arg) => + { + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + // Catching: + // (fn_ptr as *<const/mut> <ty>).is_null() + ExprKind::MethodCall(_, receiver, _, _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_const_is_null | sym::ptr_is_null) + ) + && let Some(diag) = incorrect_check(cx, receiver) => + { + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { + let to_check: &Expr<'_>; + let diag: PtrNullChecksDiag<'_>; + if let Some(ddiag) = incorrect_check(cx, left) { + to_check = right; + diag = ddiag; + } else if let Some(ddiag) = incorrect_check(cx, right) { + to_check = left; + diag = ddiag; + } else { + return; + } + + match to_check.kind { + // Catching: + // (fn_ptr as *<const/mut> <ty>) == (0 as <ty>) + ExprKind::Cast(cast_expr, _) + if let ExprKind::Lit(spanned) = cast_expr.kind + && let LitKind::Int(v, _) = spanned.node + && v == 0 => + { + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + // Catching: + // (fn_ptr as *<const/mut> <ty>) == std::ptr::null() + ExprKind::Call(path, []) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) => + { + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + } + + _ => {} + } + } + _ => {} + } + } +} diff --git a/compiler/rustc_lint/src/redundant_semicolon.rs b/compiler/rustc_lint/src/redundant_semicolon.rs new file mode 100644 index 00000000000..ef08e79e24a --- /dev/null +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -0,0 +1,57 @@ +use crate::{lints::RedundantSemicolonsDiag, EarlyContext, EarlyLintPass, LintContext}; +use rustc_ast::{Block, StmtKind}; +use rustc_session::{declare_lint, declare_lint_pass}; +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.emit_span_lint( + REDUNDANT_SEMICOLONS, + span, + RedundantSemicolonsDiag { multiple, suggestion: span }, + ); + } +} diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs new file mode 100644 index 00000000000..34153e3a220 --- /dev/null +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -0,0 +1,281 @@ +use rustc_ast::Mutability; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_middle::ty::layout::LayoutOf as _; +use rustc_middle::ty::{self, layout::TyAndLayout}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `invalid_reference_casting` lint checks for casts of `&T` to `&mut T` + /// without using interior mutability. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// fn x(r: &i32) { + /// unsafe { + /// *(r as *const i32 as *mut i32) += 1; + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Casting `&T` to `&mut T` without using interior mutability is undefined behavior, + /// as it's a violation of Rust reference aliasing requirements. + /// + /// `UnsafeCell` is the only way to obtain aliasable data that is considered + /// mutable. + INVALID_REFERENCE_CASTING, + Deny, + "casts of `&T` to `&mut T` without interior mutability" +} + +declare_lint_pass!(InvalidReferenceCasting => [INVALID_REFERENCE_CASTING]); + +impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let Some((e, pat)) = borrow_or_assign(cx, expr) { + let init = cx.expr_or_init(e); + let orig_cast = if init.span != e.span { Some(init.span) } else { None }; + + // small cache to avoid recomputing needlesly computing peel_casts of init + let mut peel_casts = { + let mut peel_casts_cache = None; + move || *peel_casts_cache.get_or_insert_with(|| peel_casts(cx, init)) + }; + + if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign) + && let Some(ty_has_interior_mutability) = + is_cast_from_ref_to_mut_ptr(cx, init, &mut peel_casts) + { + let ty_has_interior_mutability = ty_has_interior_mutability.then_some(()); + + cx.emit_span_lint( + INVALID_REFERENCE_CASTING, + expr.span, + if pat == PatternKind::Assign { + InvalidReferenceCastingDiag::AssignToRef { + orig_cast, + ty_has_interior_mutability, + } + } else { + InvalidReferenceCastingDiag::BorrowAsMut { + orig_cast, + ty_has_interior_mutability, + } + }, + ); + } + + if let Some((from_ty_layout, to_ty_layout, e_alloc)) = + is_cast_to_bigger_memory_layout(cx, init, &mut peel_casts) + { + cx.emit_span_lint( + INVALID_REFERENCE_CASTING, + expr.span, + InvalidReferenceCastingDiag::BiggerLayout { + orig_cast, + alloc: e_alloc.span, + from_ty: from_ty_layout.ty, + from_size: from_ty_layout.layout.size().bytes(), + to_ty: to_ty_layout.ty, + to_size: to_ty_layout.layout.size().bytes(), + }, + ); + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PatternKind { + Borrow { mutbl: Mutability }, + Assign, +} + +fn borrow_or_assign<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, +) -> Option<(&'tcx Expr<'tcx>, PatternKind)> { + fn deref_assign_or_addr_of<'tcx>( + expr: &'tcx Expr<'tcx>, + ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> { + // &(mut) <expr> + let (inner, pat) = if let ExprKind::AddrOf(_, mutbl, expr) = expr.kind { + (expr, PatternKind::Borrow { mutbl }) + // <expr> = ... + } else if let ExprKind::Assign(expr, _, _) = expr.kind { + (expr, PatternKind::Assign) + // <expr> += ... + } else if let ExprKind::AssignOp(_, expr, _) = expr.kind { + (expr, PatternKind::Assign) + } else { + return None; + }; + + // *<inner> + let ExprKind::Unary(UnOp::Deref, e) = &inner.kind else { + return None; + }; + Some((e, pat)) + } + + fn ptr_write<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + ) -> Option<(&'tcx Expr<'tcx>, PatternKind)> { + if let ExprKind::Call(path, [arg_ptr, _arg_val]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_write | sym::ptr_write_volatile | sym::ptr_write_unaligned) + ) + { + Some((arg_ptr, PatternKind::Assign)) + } else { + None + } + } + + deref_assign_or_addr_of(e).or_else(|| ptr_write(cx, e)) +} + +fn is_cast_from_ref_to_mut_ptr<'tcx>( + cx: &LateContext<'tcx>, + orig_expr: &'tcx Expr<'tcx>, + mut peel_casts: impl FnMut() -> (&'tcx Expr<'tcx>, bool), +) -> Option<bool> { + let end_ty = cx.typeck_results().node_type(orig_expr.hir_id); + + // Bail out early if the end type is **not** a mutable pointer. + if !matches!(end_ty.kind(), ty::RawPtr(_, Mutability::Mut)) { + return None; + } + + let (e, need_check_freeze) = peel_casts(); + + let start_ty = cx.typeck_results().node_type(e.hir_id); + if let ty::Ref(_, inner_ty, Mutability::Not) = start_ty.kind() { + // If an UnsafeCell method is involved, we need to additionally check the + // inner type for the presence of the Freeze trait (ie does NOT contain + // an UnsafeCell), since in that case we would incorrectly lint on valid casts. + // + // Except on the presence of non concrete skeleton types (ie generics) + // since there is no way to make it safe for arbitrary types. + let inner_ty_has_interior_mutability = + !inner_ty.is_freeze(cx.tcx, cx.param_env) && inner_ty.has_concrete_skeleton(); + (!need_check_freeze || !inner_ty_has_interior_mutability) + .then_some(inner_ty_has_interior_mutability) + } else { + None + } +} + +fn is_cast_to_bigger_memory_layout<'tcx>( + cx: &LateContext<'tcx>, + orig_expr: &'tcx Expr<'tcx>, + mut peel_casts: impl FnMut() -> (&'tcx Expr<'tcx>, bool), +) -> Option<(TyAndLayout<'tcx>, TyAndLayout<'tcx>, Expr<'tcx>)> { + let end_ty = cx.typeck_results().node_type(orig_expr.hir_id); + + let ty::RawPtr(inner_end_ty, _) = end_ty.kind() else { + return None; + }; + + let (e, _) = peel_casts(); + let start_ty = cx.typeck_results().node_type(e.hir_id); + + let ty::Ref(_, inner_start_ty, _) = start_ty.kind() else { + return None; + }; + + // try to find the underlying allocation + let e_alloc = cx.expr_or_init(e); + let e_alloc = + if let ExprKind::AddrOf(_, _, inner_expr) = e_alloc.kind { inner_expr } else { e_alloc }; + + // if the current expr looks like this `&mut expr[index]` then just looking + // at `expr[index]` won't give us the underlying allocation, so we just skip it + // the same logic applies field access `&mut expr.field` and reborrows `&mut *expr`. + if let ExprKind::Index(..) | ExprKind::Field(..) | ExprKind::Unary(UnOp::Deref, ..) = + e_alloc.kind + { + return None; + } + + let alloc_ty = cx.typeck_results().node_type(e_alloc.hir_id); + + // if we do not find it we bail out, as this may not be UB + // see https://github.com/rust-lang/unsafe-code-guidelines/issues/256 + if alloc_ty.is_any_ptr() { + return None; + } + + let from_layout = cx.layout_of(*inner_start_ty).ok()?; + + // if the type isn't sized, we bail out, instead of potentially giving + // the user a meaningless warning. + if from_layout.is_unsized() { + return None; + } + + let alloc_layout = cx.layout_of(alloc_ty).ok()?; + let to_layout = cx.layout_of(*inner_end_ty).ok()?; + + if to_layout.layout.size() > from_layout.layout.size() + && to_layout.layout.size() > alloc_layout.layout.size() + { + Some((from_layout, to_layout, *e_alloc)) + } else { + None + } +} + +fn peel_casts<'tcx>(cx: &LateContext<'tcx>, mut e: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, bool) { + let mut gone_trough_unsafe_cell_raw_get = false; + + loop { + e = e.peel_blocks(); + // <expr> as ... + e = if let ExprKind::Cast(expr, _) = e.kind { + expr + // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) + ) + { + expr + // ptr::from_ref(<expr>), UnsafeCell::raw_get(<expr>) or mem::transmute<_, _>(<expr>) + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get | sym::transmute) + ) + { + if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) { + gone_trough_unsafe_cell_raw_get = true; + } + arg + } else { + let init = cx.expr_or_init(e); + if init.hir_id != e.hir_id { + init + } else { + break; + } + }; + } + + (e, gone_trough_unsafe_cell_raw_get) +} diff --git a/compiler/rustc_lint/src/shadowed_into_iter.rs b/compiler/rustc_lint/src/shadowed_into_iter.rs new file mode 100644 index 00000000000..da2b5878b19 --- /dev/null +++ b/compiler/rustc_lint/src/shadowed_into_iter.rs @@ -0,0 +1,157 @@ +use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub}; +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_hir as hir; +use rustc_middle::ty::{self, Ty}; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_session::{declare_lint, impl_lint_pass}; +use rustc_span::edition::Edition; + +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 { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021), + reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>", + }; +} + +declare_lint! { + /// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices. + /// + /// ### Example + /// + /// ```rust,edition2021 + /// # #![allow(unused)] + /// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; }); + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Since Rust 1.80.0, boxed slices implement `IntoIterator`. However, to avoid + /// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still + /// behave as `(&boxed_slice).into_iter()`, returning an iterator over + /// references, just like in Rust 1.79.0 and earlier. + /// This only applies to the method call syntax `boxed_slice.into_iter()`, not to + /// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`. + pub BOXED_SLICE_INTO_ITER, + Warn, + "detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + }; +} + +#[derive(Copy, Clone)] +pub struct ShadowedIntoIter; + +impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]); + +impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else { + return; + }; + + // Check if the method call actually calls the libcore + // `IntoIterator::into_iter`. + let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { + return; + }; + if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() { + return; + } + + // As this is a method call expression, we have at least one argument. + let receiver_ty = cx.typeck_results().expr_ty(receiver_arg); + let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); + + let adjusted_receiver_tys: Vec<_> = + [receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect(); + + fn is_ref_to_array(ty: Ty<'_>) -> bool { + if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false } + } + fn is_boxed_slice(ty: Ty<'_>) -> bool { + ty.is_box() && ty.boxed_ty().is_slice() + } + fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool { + if let ty::Ref(_, pointee_ty, _) = *ty.kind() { + is_boxed_slice(pointee_ty) + } else { + false + } + } + + let (lint, target, edition, can_suggest_ufcs) = + if is_ref_to_array(*adjusted_receiver_tys.last().unwrap()) + && let Some(idx) = adjusted_receiver_tys + .iter() + .copied() + .take_while(|ty| !is_ref_to_array(*ty)) + .position(|ty| ty.is_array()) + { + (ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0) + } else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap()) + && let Some(idx) = adjusted_receiver_tys + .iter() + .copied() + .take_while(|ty| !is_ref_to_boxed_slice(*ty)) + .position(|ty| is_boxed_slice(ty)) + { + (BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0) + } else { + return; + }; + + // If this expression comes from the `IntoIter::into_iter` inside of a for loop, + // we should just suggest removing the `.into_iter()` or changing it to `.iter()` + // to disambiguate if we want to iterate by-value or by-ref. + let sub = if let Some((_, hir::Node::Expr(parent_expr))) = + cx.tcx.hir().parent_iter(expr.hir_id).nth(1) + && let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = + &parent_expr.kind + && let hir::ExprKind::Call(path, [_]) = &arg.kind + && let hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoIterIntoIter, ..)) = + &path.kind + { + Some(ShadowedIntoIterDiagSub::RemoveIntoIter { + span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), + }) + } else if can_suggest_ufcs { + Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter { + start_span: expr.span.shrink_to_lo(), + end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()), + }) + } else { + None + }; + + cx.emit_span_lint( + lint, + call.ident.span, + ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub }, + ); + } +} diff --git a/compiler/rustc_lint/src/tests.rs b/compiler/rustc_lint/src/tests.rs new file mode 100644 index 00000000000..4fd054cb717 --- /dev/null +++ b/compiler/rustc_lint/src/tests.rs @@ -0,0 +1,26 @@ +use crate::levels::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 00000000000..6983e7abbd6 --- /dev/null +++ b/compiler/rustc_lint/src/traits.rs @@ -0,0 +1,124 @@ +use crate::lints::{DropGlue, DropTraitConstraintsDiag}; +use crate::LateContext; +use crate::LateLintPass; +use crate::LintContext; +use rustc_hir::{self as hir, LangItem}; +use rustc_session::{declare_lint, declare_lint_pass}; +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::ClauseKind; + + let predicates = cx.tcx.explicit_predicates_of(item.owner_id); + for &(predicate, span) in predicates.predicates { + let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() else { + continue; + }; + let def_id = trait_predicate.trait_ref.def_id; + if cx.tcx.is_lang_item(def_id, LangItem::Drop) { + // Explicitly allow `impl Drop`, a drop-guards-as-unnameable-type pattern. + if trait_predicate.trait_ref.self_ty().is_impl_trait() { + continue; + } + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; + cx.emit_span_lint( + DROP_BOUNDS, + span, + DropTraitConstraintsDiag { predicate, tcx: cx.tcx, def_id }, + ); + } + } + } + + 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 { + let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; + cx.emit_span_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id }); + } + } + } +} diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs new file mode 100644 index 00000000000..c0364b35716 --- /dev/null +++ b/compiler/rustc_lint/src/types.rs @@ -0,0 +1,2059 @@ +use crate::{ + fluent_generated as fluent, + lints::{ + AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, + AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad, + AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons, + InvalidNanComparisonsSuggestion, OnlyCastu8ToChar, OverflowingBinHex, + OverflowingBinHexSign, OverflowingBinHexSignBitSub, OverflowingBinHexSub, OverflowingInt, + OverflowingIntHelp, OverflowingLiteral, OverflowingUInt, RangeEndpointOutOfRange, + UnusedComparisons, UseInclusiveRange, VariantSizeDifferencesDiag, + }, +}; +use crate::{LateContext, LateLintPass, LintContext}; +use rustc_ast as ast; +use rustc_attr as attr; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::DiagMessage; +use rustc_hir as hir; +use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; +use rustc_middle::bug; +use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; +use rustc_middle::ty::GenericArgsRef; +use rustc_middle::ty::{ + self, AdtKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, +}; +use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; +use rustc_span::def_id::LocalDefId; +use rustc_span::source_map; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol}; +use rustc_target::abi::{Abi, Size, WrappingRange}; +use rustc_target::abi::{Integer, TagEncoding, Variants}; +use rustc_target::spec::abi::Abi as SpecAbi; +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" +} + +declare_lint! { + /// The `invalid_nan_comparisons` lint checks comparison with `f32::NAN` or `f64::NAN` + /// as one of the operand. + /// + /// ### Example + /// + /// ```rust + /// let a = 2.3f32; + /// if a == f32::NAN {} + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// NaN does not compare meaningfully to anything โ not + /// even itself โ so those comparisons are always false. + INVALID_NAN_COMPARISONS, + Warn, + "detects invalid floating point NaN comparisons" +} + +declare_lint! { + /// The `ambiguous_wide_pointer_comparisons` lint checks comparison + /// of `*const/*mut ?Sized` as the operands. + /// + /// ### Example + /// + /// ```rust + /// # struct A; + /// # struct B; + /// + /// # trait T {} + /// # impl T for A {} + /// # impl T for B {} + /// + /// let ab = (A, B); + /// let a = &ab.0 as *const dyn T; + /// let b = &ab.1 as *const dyn T; + /// + /// let _ = a == b; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The comparison includes metadata which may not be expected. + AMBIGUOUS_WIDE_POINTER_COMPARISONS, + Warn, + "detects ambiguous wide pointer comparisons" +} + +#[derive(Copy, Clone)] +pub struct TypeLimits { + /// Id of the last visited negated expression + negated_expr_id: Option<hir::HirId>, + /// Span of the last visited negated expression + negated_expr_span: Option<Span>, +} + +impl_lint_pass!(TypeLimits => [ + UNUSED_COMPARISONS, + OVERFLOWING_LITERALS, + INVALID_NAN_COMPARISONS, + AMBIGUOUS_WIDE_POINTER_COMPARISONS +]); + +impl TypeLimits { + pub fn new() -> TypeLimits { + TypeLimits { negated_expr_id: None, negated_expr_span: None } + } +} + +/// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint (`expr..MAX+1`). +/// Returns `true` iff the lint was emitted. +fn lint_overflowing_range_endpoint<'tcx>( + cx: &LateContext<'tcx>, + lit: &hir::Lit, + lit_val: u128, + max: u128, + expr: &'tcx hir::Expr<'tcx>, + ty: &str, +) -> bool { + // Look past casts to support cases like `0..256 as u8` + let (expr, lit_span) = if let Node::Expr(par_expr) = cx.tcx.parent_hir_node(expr.hir_id) + && let ExprKind::Cast(_, _) = par_expr.kind + { + (par_expr, expr.span) + } else { + (expr, expr.span) + }; + + // We only want to handle exclusive (`..`) ranges, + // which are represented as `ExprKind::Struct`. + let Node::ExprField(field) = cx.tcx.parent_hir_node(expr.hir_id) else { return false }; + let Node::Expr(struct_expr) = cx.tcx.parent_hir_node(field.hir_id) else { return false }; + if !is_range_literal(struct_expr) { + return false; + }; + let ExprKind::Struct(_, eps, _) = &struct_expr.kind else { return false }; + 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) { + return false; + }; + + use rustc_ast::{LitIntType, LitKind}; + 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 sub_sugg = if expr.span.lo() == lit_span.lo() { + let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) else { return false }; + UseInclusiveRange::WithoutParen { + sugg: struct_expr.span.shrink_to_lo().to(lit_span.shrink_to_hi()), + start, + literal: lit_val - 1, + suffix, + } + } else { + UseInclusiveRange::WithParen { + eq_sugg: expr.span.shrink_to_lo(), + lit_sugg: lit_span, + literal: lit_val - 1, + suffix, + } + }; + + cx.emit_span_lint( + OVERFLOWING_LITERALS, + struct_expr.span, + RangeEndpointOutOfRange { ty, sub: sub_sugg }, + ); + + // We've just emitted a lint, special cased for `(...)..MAX+1` ranges, + // return `true` so the callers don't also emit a lint + true +} + +// 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, + size: Size, + repr_str: String, + val: u128, + negative: bool, +) { + 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 sign = + if negative { OverflowingBinHexSign::Negative } else { OverflowingBinHexSign::Positive }; + let sub = get_type_suggestion(cx.typeck_results().node_type(expr.hir_id), val, negative).map( + |suggestion_ty| { + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + let (sans_suffix, _) = repr_str.split_at(pos); + OverflowingBinHexSub::Suggestion { span: expr.span, suggestion_ty, sans_suffix } + } else { + OverflowingBinHexSub::Help { suggestion_ty } + } + }, + ); + let sign_bit_sub = (!negative) + .then(|| { + let ty::Int(int_ty) = cx.typeck_results().node_type(expr.hir_id).kind() else { + return None; + }; + + let Some(bit_width) = int_ty.bit_width() else { + return None; // isize case + }; + + // Skip if sign bit is not set + if (val & (1 << (bit_width - 1))) == 0 { + return None; + } + + let lit_no_suffix = + if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { + repr_str.split_at(pos).0 + } else { + &repr_str + }; + + Some(OverflowingBinHexSignBitSub { + span: expr.span, + lit_no_suffix, + negative_val: actually.clone(), + int_ty: int_ty.name_str(), + uint_ty: int_ty.to_unsigned().name_str(), + }) + }) + .flatten(); + + cx.emit_span_lint( + OVERFLOWING_LITERALS, + expr.span, + OverflowingBinHex { + ty: t, + lit: repr_str.clone(), + dec: val, + actually, + sign, + sub, + sign_bit_sub, + }, + ) +} + +// 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)), + Integer::from_int_ty(cx, t).size(), + repr_str, + v, + negative, + ); + return; + } + + if lint_overflowing_range_endpoint(cx, lit, v, max, e, t.name_str()) { + // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`. + return; + } + + let span = if negative { type_limits.negated_expr_span.unwrap() } else { e.span }; + let lit = + cx.sess().source_map().span_to_snippet(span).expect("must get snippet from literal"); + let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative) + .map(|suggestion_ty| OverflowingIntHelp { suggestion_ty }); + + cx.emit_span_lint( + OVERFLOWING_LITERALS, + span, + OverflowingInt { ty: t.name_str(), lit, min, max, help }, + ); + } +} + +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.get(), + _ => bug!(), + }; + if lit_val < min || lit_val > max { + if let Node::Expr(par_e) = cx.tcx.parent_hir_node(e.hir_id) { + match par_e.kind { + hir::ExprKind::Cast(..) => { + if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() { + cx.emit_span_lint( + OVERFLOWING_LITERALS, + par_e.span, + OnlyCastu8ToChar { span: par_e.span, literal: lit_val }, + ); + return; + } + } + _ => {} + } + } + if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, t.name_str()) { + // The overflowing literal lint was emitted by `lint_overflowing_range_endpoint`. + 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)), + Integer::from_uint_ty(cx, t).size(), + repr_str, + lit_val, + false, + ); + return; + } + cx.emit_span_lint( + OVERFLOWING_LITERALS, + e.span, + OverflowingUInt { + ty: t.name_str(), + lit: cx + .sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + min, + max, + }, + ); + } +} + +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.get()) + } + _ => 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 { + // FIXME(f16_f128): add this check once `is_infinite` is reliable (ABI + // issues resolved). + ty::FloatTy::F16 => Ok(false), + ty::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), + ty::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), + ty::FloatTy::F128 => Ok(false), + }, + _ => bug!(), + }; + if is_infinite == Ok(true) { + cx.emit_span_lint( + OVERFLOWING_LITERALS, + e.span, + OverflowingLiteral { + ty: t.name_str(), + lit: cx + .sess() + .source_map() + .span_to_snippet(lit.span) + .expect("must get snippet from literal"), + }, + ); + } + } + _ => {} + } +} + +fn lint_nan<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx hir::Expr<'tcx>, + binop: hir::BinOp, + l: &'tcx hir::Expr<'tcx>, + r: &'tcx hir::Expr<'tcx>, +) { + fn is_nan(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + let expr = expr.peel_blocks().peel_borrows(); + match expr.kind { + ExprKind::Path(qpath) => { + let Some(def_id) = cx.typeck_results().qpath_res(&qpath, expr.hir_id).opt_def_id() + else { + return false; + }; + + matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::f32_nan | sym::f64_nan)) + } + _ => false, + } + } + + fn eq_ne( + cx: &LateContext<'_>, + e: &hir::Expr<'_>, + l: &hir::Expr<'_>, + r: &hir::Expr<'_>, + f: impl FnOnce(Span, Span) -> InvalidNanComparisonsSuggestion, + ) -> InvalidNanComparisons { + // FIXME(#72505): This suggestion can be restored if `f{32,64}::is_nan` is made const. + let suggestion = (!cx.tcx.hir().is_inside_const_context(e.hir_id)).then(|| { + if let Some(l_span) = l.span.find_ancestor_inside(e.span) + && let Some(r_span) = r.span.find_ancestor_inside(e.span) + { + f(l_span, r_span) + } else { + InvalidNanComparisonsSuggestion::Spanless + } + }); + + InvalidNanComparisons::EqNe { suggestion } + } + + let lint = match binop.node { + hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => { + eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { + nan_plus_binop: l_span.until(r_span), + float: r_span.shrink_to_hi(), + neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()), + }) + } + hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => { + eq_ne(cx, e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { + nan_plus_binop: l_span.shrink_to_hi().to(r_span), + float: l_span.shrink_to_hi(), + neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()), + }) + } + hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge + if is_nan(cx, l) || is_nan(cx, r) => + { + InvalidNanComparisons::LtLeGtGe + } + _ => return, + }; + + cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint); +} + +#[derive(Debug, PartialEq)] +enum ComparisonOp { + BinOp(hir::BinOpKind), + Other, +} + +fn lint_wide_pointer<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx hir::Expr<'tcx>, + cmpop: ComparisonOp, + l: &'tcx hir::Expr<'tcx>, + r: &'tcx hir::Expr<'tcx>, +) { + let ptr_unsized = |mut ty: Ty<'tcx>| -> Option<( + /* number of refs */ usize, + /* modifiers */ String, + /* is dyn */ bool, + )> { + let mut refs = 0; + // here we remove any "implicit" references and count the number + // of them to correctly suggest the right number of deref + while let ty::Ref(_, inner_ty, _) = ty.kind() { + ty = *inner_ty; + refs += 1; + } + + // get the inner type of a pointer (or akin) + let mut modifiers = String::new(); + ty = match ty.kind() { + ty::RawPtr(ty, _) => *ty, + ty::Adt(def, args) if cx.tcx.is_diagnostic_item(sym::NonNull, def.did()) => { + modifiers.push_str(".as_ptr()"); + args.type_at(0) + } + _ => return None, + }; + + (!ty.is_sized(cx.tcx, cx.param_env)) + .then(|| (refs, modifiers, matches!(ty.kind(), ty::Dynamic(_, _, ty::Dyn)))) + }; + + // the left and right operands can have references, remove any explicit references + let l = l.peel_borrows(); + let r = r.peel_borrows(); + + let Some(l_ty) = cx.typeck_results().expr_ty_opt(l) else { + return; + }; + let Some(r_ty) = cx.typeck_results().expr_ty_opt(r) else { + return; + }; + + let Some((l_ty_refs, l_modifiers, l_inner_ty_is_dyn)) = ptr_unsized(l_ty) else { + return; + }; + let Some((r_ty_refs, r_modifiers, r_inner_ty_is_dyn)) = ptr_unsized(r_ty) else { + return; + }; + + let (Some(l_span), Some(r_span)) = + (l.span.find_ancestor_inside(e.span), r.span.find_ancestor_inside(e.span)) + else { + return cx.emit_span_lint( + AMBIGUOUS_WIDE_POINTER_COMPARISONS, + e.span, + AmbiguousWidePointerComparisons::Spanless, + ); + }; + + let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" }; + let is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne)); + let is_dyn_comparison = l_inner_ty_is_dyn && r_inner_ty_is_dyn; + + let left = e.span.shrink_to_lo().until(l_span.shrink_to_lo()); + let middle = l_span.shrink_to_hi().until(r_span.shrink_to_lo()); + let right = r_span.shrink_to_hi().until(e.span.shrink_to_hi()); + + let deref_left = &*"*".repeat(l_ty_refs); + let deref_right = &*"*".repeat(r_ty_refs); + + let l_modifiers = &*l_modifiers; + let r_modifiers = &*r_modifiers; + + cx.emit_span_lint( + AMBIGUOUS_WIDE_POINTER_COMPARISONS, + e.span, + AmbiguousWidePointerComparisons::Spanful { + addr_metadata_suggestion: (is_eq_ne && !is_dyn_comparison).then(|| { + AmbiguousWidePointerComparisonsAddrMetadataSuggestion { + ne, + deref_left, + deref_right, + l_modifiers, + r_modifiers, + left, + middle, + right, + } + }), + addr_suggestion: if is_eq_ne { + AmbiguousWidePointerComparisonsAddrSuggestion::AddrEq { + ne, + deref_left, + deref_right, + l_modifiers, + r_modifiers, + left, + middle, + right, + } + } else { + AmbiguousWidePointerComparisonsAddrSuggestion::Cast { + deref_left, + deref_right, + l_modifiers, + r_modifiers, + paren_left: if l_ty_refs != 0 { ")" } else { "" }, + paren_right: if r_ty_refs != 0 { ")" } else { "" }, + left_before: (l_ty_refs != 0).then_some(l_span.shrink_to_lo()), + left_after: l_span.shrink_to_hi(), + right_before: (r_ty_refs != 0).then_some(r_span.shrink_to_lo()), + right_after: r_span.shrink_to_hi(), + } + }, + }, + ); +} + +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, 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); + self.negated_expr_span = Some(e.span); + } + } + hir::ExprKind::Binary(binop, ref l, ref r) => { + if is_comparison(binop) { + if !check_limits(cx, binop, l, r) { + cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); + } else { + lint_nan(cx, e, binop, l, r); + lint_wide_pointer(cx, e, ComparisonOp::BinOp(binop.node), l, r); + } + } + } + hir::ExprKind::Lit(lit) => lint_literal(cx, self, e, lit), + hir::ExprKind::Call(path, [l, r]) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && let Some(cmpop) = diag_item_cmpop(diag_item) => + { + lint_wide_pointer(cx, e, cmpop, l, r); + } + hir::ExprKind::MethodCall(_, l, [r], _) + if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + && let Some(cmpop) = diag_item_cmpop(diag_item) => + { + lint_wide_pointer(cx, e, cmpop, l, r); + } + _ => {} + }; + + fn is_valid<T: 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(li) => match li.node { + ast::LitKind::Int( + v, + ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed, + ) => v.get() 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(li) => match li.node { + ast::LitKind::Int(v, _) => v.get(), + _ => 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 + ) + } + + fn diag_item_cmpop(diag_item: Symbol) -> Option<ComparisonOp> { + Some(match diag_item { + sym::cmp_ord_max => ComparisonOp::Other, + sym::cmp_ord_min => ComparisonOp::Other, + sym::ord_cmp_method => ComparisonOp::Other, + sym::cmp_partialeq_eq => ComparisonOp::BinOp(hir::BinOpKind::Eq), + sym::cmp_partialeq_ne => ComparisonOp::BinOp(hir::BinOpKind::Ne), + sym::cmp_partialord_cmp => ComparisonOp::Other, + sym::cmp_partialord_ge => ComparisonOp::BinOp(hir::BinOpKind::Ge), + sym::cmp_partialord_gt => ComparisonOp::BinOp(hir::BinOpKind::Gt), + sym::cmp_partialord_le => ComparisonOp::BinOp(hir::BinOpKind::Le), + sym::cmp_partialord_lt => ComparisonOp::BinOp(hir::BinOpKind::Lt), + _ => return None, + }) + } + } +} + +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: DiagMessage, help: Option<DiagMessage> }, +} + +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-1-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).instantiate_identity(); + let is_1zst = tcx.layout_of(param_env.and(field_ty)).is_ok_and(|layout| layout.is_1zst()); + !is_1zst + }) +} + +/// Is type known to be non-null? +fn ty_is_known_nonnull<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + mode: CItemKind, +) -> bool { + let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + + match ty.kind() { + ty::FnPtr(_) => true, + ty::Ref(..) => true, + ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, + ty::Adt(def, args) 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(tcx, variant)) + .any(|field| ty_is_known_nonnull(tcx, param_env, field.ty(tcx, args), 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>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, +) -> Option<Ty<'tcx>> { + let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty); + + Some(match *ty.kind() { + ty::Adt(field_def, field_args) => { + let inner_field_ty = { + let mut first_non_zst_ty = + field_def.variants().iter().filter_map(|v| transparent_newtype_field(tcx, v)); + debug_assert_eq!( + first_non_zst_ty.clone().count(), + 1, + "Wrong number of fields for transparent type" + ); + first_non_zst_ty + .next_back() + .expect("No non-zst fields in transparent type.") + .ty(tcx, field_args) + }; + return get_nullable_type(tcx, param_env, inner_field_ty); + } + ty::Int(ty) => Ty::new_int(tcx, ty), + ty::Uint(ty) => Ty::new_uint(tcx, ty), + ty::RawPtr(ty, mutbl) => Ty::new_ptr(tcx, ty, mutbl), + // 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) => Ty::new_ptr(tcx, ty, mutbl), + // There is no nullable equivalent for Rust's function pointers, + // you must use an `Option<fn(..) -> _>` to represent it. + ty::FnPtr(..) => 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; + } + }) +} + +/// A type is niche-optimization candidate iff: +/// - Is a zero-sized type with alignment 1 (a โ1-ZSTโ). +/// - Has no fields. +/// - Does not have the `#[non_exhaustive]` attribute. +fn is_niche_optimization_candidate<'tcx>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, +) -> bool { + if tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| !layout.is_1zst()) { + return false; + } + + match ty.kind() { + ty::Adt(ty_def, _) => { + let non_exhaustive = ty_def.is_variant_list_non_exhaustive(); + let empty = (ty_def.is_struct() && ty_def.all_fields().next().is_none()) + || (ty_def.is_enum() && ty_def.variants().is_empty()); + + !non_exhaustive && empty + } + ty::Tuple(tys) => tys.is_empty(), + _ => false, + } +} + +/// 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>( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, + ckind: CItemKind, +) -> Option<Ty<'tcx>> { + debug!("is_repr_nullable_ptr(tcx, ty = {:?})", ty); + if let ty::Adt(ty_def, args) = ty.kind() { + let field_ty = match &ty_def.variants().raw[..] { + [var_one, var_two] => match (&var_one.fields.raw[..], &var_two.fields.raw[..]) { + ([], [field]) | ([field], []) => field.ty(tcx, args), + ([field1], [field2]) => { + if !tcx.features().result_ffi_guarantees { + return None; + } + + let ty1 = field1.ty(tcx, args); + let ty2 = field2.ty(tcx, args); + + if is_niche_optimization_candidate(tcx, param_env, ty1) { + ty2 + } else if is_niche_optimization_candidate(tcx, param_env, ty2) { + ty1 + } else { + return None; + } + } + _ => return None, + }, + _ => return None, + }; + + if !ty_is_known_nonnull(tcx, param_env, 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, tcx, param_env).ok(); + 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_layout = tcx.layout_of(param_env.and(field_ty)); + if field_ty_layout.is_err() && !field_ty.has_non_region_param() { + bug!("should be able to compute the layout of non-polymorphic type"); + } + + let field_ty_abi = &field_ty_layout.ok()?.abi; + if let Abi::Scalar(field_ty_scalar) = field_ty_abi { + match field_ty_scalar.valid_range(&tcx) { + WrappingRange { start: 0, end } + if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 => + { + return Some(get_nullable_type(tcx, param_env, field_ty).unwrap()); + } + WrappingRange { start: 1, .. } => { + return Some(get_nullable_type(tcx, param_env, 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, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + let field_ty = field.ty(self.cx.tcx, args); + let field_ty = self + .cx + .tcx + .try_normalize_erasing_regions(self.cx.param_env, field_ty) + .unwrap_or(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, + args: GenericArgsRef<'tcx>, + ) -> FfiResult<'tcx> { + use FfiResult::*; + let transparent_with_all_zst_fields = if def.repr().transparent() { + if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { + // Transparent newtypes have at most one non-ZST field which needs to be checked.. + match self.check_field_type_for_ffi(cache, field, args) { + FfiUnsafe { ty, .. } if ty.is_unit() => (), + r => return r, + } + + false + } else { + // ..or have only ZST fields, which is FFI-unsafe (unless those fields are all + // `PhantomData`). + true + } + } else { + false + }; + + // We can't completely trust `repr(C)` markings, so make sure the fields are actually safe. + let mut all_phantom = !variant.fields.is_empty(); + for field in &variant.fields { + all_phantom &= match self.check_field_type_for_ffi(cache, field, args) { + FfiSafe => false, + // `()` fields are FFI-safe! + FfiUnsafe { ty, .. } if ty.is_unit() => false, + FfiPhantom(..) => true, + r @ FfiUnsafe { .. } => return r, + } + } + + if all_phantom { + FfiPhantom(ty) + } else if transparent_with_all_zst_fields { + FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_struct_zst, help: None } + } 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, args) => { + if def.is_box() && matches!(self.mode, CItemKind::Definition) { + if ty.boxed_ty().is_sized(tcx, 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(), args) + } + AdtKind::Enum => { + if def.variants().is_empty() { + // Empty enums are okay... although sort of useless. + return FfiSafe; + } + + if def.is_variant_list_non_exhaustive() && !def.did().is_local() { + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_non_exhaustive, + help: None, + }; + } + + // 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()>` and `Result<extern fn(), ()>` + if let Some(ty) = + repr_nullable_ptr(self.cx.tcx, self.cx.param_env, ty, self.mode) + { + return self.check_type_for_ffi(cache, ty); + } + + return FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_enum_repr_reason, + help: Some(fluent::lint_improper_ctypes_enum_repr_help), + }; + } + + // 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, args) { + 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::Pat(..) => FfiUnsafe { + ty, + reason: fluent::lint_improper_ctypes_pat_reason, + help: Some(fluent::lint_improper_ctypes_pat_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, _) | ty::Ref(_, ty, _) + if { + matches!(self.mode, CItemKind::Definition) + && ty.is_sized(self.cx.tcx, self.cx.param_env) + } => + { + FfiSafe + } + + ty::RawPtr(ty, _) + if match ty.kind() { + ty::Tuple(tuple) => tuple.is_empty(), + _ => false, + } => + { + FfiSafe + } + + ty::RawPtr(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.instantiate_bound_regions_with_erased(sig); + for arg in sig.inputs() { + match self.check_type_for_ffi(cache, *arg) { + FfiSafe => {} + r => return r, + } + } + + let ret_ty = sig.output(); + if ret_ty.is_unit() { + return FfiSafe; + } + + self.check_type_for_ffi(cache, ret_ty) + } + + 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::Alias(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::Alias(ty::Projection | ty::Inherent, ..) + if matches!(self.mode, CItemKind::Definition) => + { + FfiSafe + } + + ty::Param(..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) + | ty::Infer(..) + | ty::Bound(..) + | ty::Error(_) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | 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: DiagMessage, + help: Option<DiagMessage>, + ) { + let lint = match self.mode { + CItemKind::Declaration => IMPROPER_CTYPES, + CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, + }; + let desc = match self.mode { + CItemKind::Declaration => "block", + CItemKind::Definition => "fn", + }; + let span_note = if let ty::Adt(def, _) = ty.kind() + && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) + { + Some(sp) + } else { + None + }; + self.cx.emit_span_lint( + lint, + sp, + ImproperCTypes { ty, desc, label: sp, help, note, span_note }, + ); + } + + fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { + struct ProhibitOpaqueTypes; + impl<'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for ProhibitOpaqueTypes { + type Result = ControlFlow<Ty<'tcx>>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + if !ty.has_opaque_types() { + return ControlFlow::Continue(()); + } + + if let ty::Alias(ty::Opaque, ..) = ty.kind() { + ControlFlow::Break(ty) + } else { + ty.super_visit_with(self) + } + } + } + + if let Some(ty) = self + .cx + .tcx + .try_normalize_erasing_regions(self.cx.param_env, ty) + .unwrap_or(ty) + .visit_with(&mut ProhibitOpaqueTypes) + .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, + ) { + if self.check_for_opaque_ty(sp, ty) { + // We've already emitted an error due to an opaque type. + return; + } + + let ty = self.cx.tcx.try_normalize_erasing_regions(self.cx.param_env, ty).unwrap_or(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 + // the caller (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, + ); + } + FfiResult::FfiUnsafe { ty, reason, help } => { + self.emit_ffi_unsafe_type_lint(ty, sp, reason, help); + } + } + } + + /// Check if a function's argument types and result type are "ffi-safe". + /// + /// For a external ABI function, argument types and the result type are walked to find fn-ptr + /// types that have external ABIs, as these still need checked. + fn check_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(sig); + + for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(input_hir, *input_ty) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, false); + } + } + + if let hir::FnRetTy::Return(ret_hir) = decl.output { + for (fn_ptr_ty, span) in self.find_fn_ptr_ty_with_external_abi(ret_hir, sig.output()) { + self.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, false, true); + } + } + } + + /// Check if a function's argument types and result type are "ffi-safe". + fn check_foreign_fn(&mut self, def_id: LocalDefId, decl: &'tcx hir::FnDecl<'_>) { + let sig = self.cx.tcx.fn_sig(def_id).instantiate_identity(); + let sig = self.cx.tcx.instantiate_bound_regions_with_erased(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(ret_hir) = decl.output { + self.check_type_for_ffi_and_report_errors(ret_hir.span, sig.output(), false, true); + } + } + + fn check_foreign_static(&mut self, id: hir::OwnerId, span: Span) { + let ty = self.cx.tcx.type_of(id).instantiate_identity(); + 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) + } + + /// Find any fn-ptr types with external ABIs in `ty`. + /// + /// For example, `Option<extern "C" fn()>` returns `extern "C" fn()` + fn find_fn_ptr_ty_with_external_abi( + &self, + hir_ty: &hir::Ty<'tcx>, + ty: Ty<'tcx>, + ) -> Vec<(Ty<'tcx>, Span)> { + struct FnPtrFinder<'parent, 'a, 'tcx> { + visitor: &'parent ImproperCTypesVisitor<'a, 'tcx>, + spans: Vec<Span>, + tys: Vec<Ty<'tcx>>, + } + + impl<'parent, 'a, 'tcx> hir::intravisit::Visitor<'_> for FnPtrFinder<'parent, 'a, 'tcx> { + fn visit_ty(&mut self, ty: &'_ hir::Ty<'_>) { + debug!(?ty); + if let hir::TyKind::BareFn(hir::BareFnTy { abi, .. }) = ty.kind + && !self.visitor.is_internal_abi(*abi) + { + self.spans.push(ty.span); + } + + hir::intravisit::walk_ty(self, ty) + } + } + + impl<'vis, 'a, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'vis, 'a, 'tcx> { + type Result = ControlFlow<Ty<'tcx>>; + + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { + if let ty::FnPtr(sig) = ty.kind() + && !self.visitor.is_internal_abi(sig.abi()) + { + self.tys.push(ty); + } + + ty.super_visit_with(self) + } + } + + let mut visitor = FnPtrFinder { visitor: &*self, spans: Vec::new(), tys: Vec::new() }; + ty.visit_with(&mut visitor); + hir::intravisit::Visitor::visit_ty(&mut visitor, hir_ty); + + iter::zip(visitor.tys.drain(..), visitor.spans.drain(..)).collect() + } +} + +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { + fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; + let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); + + match it.kind { + hir::ForeignItemKind::Fn(decl, _, _, _) if !vis.is_internal_abi(abi) => { + vis.check_foreign_fn(it.owner_id.def_id, decl); + } + hir::ForeignItemKind::Static(ty, _, _) if !vis.is_internal_abi(abi) => { + vis.check_foreign_static(it.owner_id, ty.span); + } + hir::ForeignItemKind::Fn(decl, _, _, _) => vis.check_fn(it.owner_id.def_id, decl), + hir::ForeignItemKind::Static(..) | hir::ForeignItemKind::Type => (), + } + } +} + +impl ImproperCTypesDefinitions { + fn check_ty_maybe_containing_foreign_fnptr<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + hir_ty: &'tcx hir::Ty<'_>, + ty: Ty<'tcx>, + ) { + let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; + for (fn_ptr_ty, span) in vis.find_fn_ptr_ty_with_external_abi(hir_ty, ty) { + vis.check_type_for_ffi_and_report_errors(span, fn_ptr_ty, true, false); + } + } +} + +/// `ImproperCTypesDefinitions` checks items outside of foreign items (e.g. stuff that isn't in +/// `extern "C" { }` blocks): +/// +/// - `extern "<abi>" fn` definitions are checked in the same way as the +/// `ImproperCtypesDeclarations` visitor checks functions if `<abi>` is external (e.g. "C"). +/// - All other items which contain types (e.g. other functions, struct definitions, etc) are +/// checked for extern fn-ptrs with external ABIs. +impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + match item.kind { + hir::ItemKind::Static(ty, ..) + | hir::ItemKind::Const(ty, ..) + | hir::ItemKind::TyAlias(ty, ..) => { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + ty, + cx.tcx.type_of(item.owner_id).instantiate_identity(), + ); + } + // See `check_fn`.. + hir::ItemKind::Fn(..) => {} + // See `check_field_def`.. + hir::ItemKind::Union(..) | hir::ItemKind::Struct(..) | hir::ItemKind::Enum(..) => {} + // Doesn't define something that can contain a external type to be checked. + hir::ItemKind::Impl(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::Trait(..) + | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::GlobalAsm(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Mod(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Use(..) + | hir::ItemKind::ExternCrate(..) => {} + } + } + + fn check_field_def(&mut self, cx: &LateContext<'tcx>, field: &'tcx hir::FieldDef<'tcx>) { + self.check_ty_maybe_containing_foreign_fnptr( + cx, + field.ty, + cx.tcx.type_of(field.def_id).instantiate_identity(), + ); + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: hir::intravisit::FnKind<'tcx>, + decl: &'tcx hir::FnDecl<'_>, + _: &'tcx hir::Body<'_>, + _: Span, + id: LocalDefId, + ) { + 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_fn(id, decl); + } else { + vis.check_foreign_fn(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.owner_id).instantiate_identity(); + 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.emit_span_lint( + VARIANT_SIZE_DIFFERENCES, + enum_definition.variants[largest_index].span, + VariantSizeDifferencesDiag { largest }, + ); + } + } + } +} + +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(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).instantiate_identity().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[0], sym::Release)), + sym::store => Some((&args[1], sym::Acquire)), + _ => None, + } + && let Some(ordering) = Self::match_ordering(cx, ordering_arg) + && (ordering == invalid_ordering || ordering == sym::AcqRel) + { + if method == sym::load { + cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingLoad); + } else { + cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingStore); + }; + } + } + + fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Call(func, 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.emit_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence); + } + } + + 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[1], + sym::compare_exchange | sym::compare_exchange_weak => &args[3], + _ => return, + }; + + let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return }; + + if matches!(fail_ordering, sym::Release | sym::AcqRel) { + cx.emit_span_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/unit_bindings.rs b/compiler/rustc_lint/src/unit_bindings.rs new file mode 100644 index 00000000000..8202bfa805a --- /dev/null +++ b/compiler/rustc_lint/src/unit_bindings.rs @@ -0,0 +1,72 @@ +use crate::lints::UnitBindingsDiag; +use crate::{LateLintPass, LintContext}; +use rustc_hir as hir; +use rustc_session::{declare_lint, declare_lint_pass}; + +declare_lint! { + /// The `unit_bindings` lint detects cases where bindings are useless because they have + /// the unit type `()` as their inferred type. The lint is suppressed if the user explicitly + /// annotates the let binding with the unit type `()`, or if the let binding uses an underscore + /// wildcard pattern, i.e. `let _ = expr`, or if the binding is produced from macro expansions. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unit_bindings)] + /// + /// fn foo() { + /// println!("do work"); + /// } + /// + /// pub fn main() { + /// let x = foo(); // useless binding + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Creating a local binding with the unit type `()` does not do much and can be a sign of a + /// user error, such as in this example: + /// + /// ```rust,no_run + /// fn main() { + /// let mut x = [1, 2, 3]; + /// x[0] = 5; + /// let y = x.sort(); // useless binding as `sort` returns `()` and not the sorted array. + /// println!("{:?}", y); // prints "()" + /// } + /// ``` + pub UNIT_BINDINGS, + Allow, + "binding is useless because it has the unit `()` type" +} + +declare_lint_pass!(UnitBindings => [UNIT_BINDINGS]); + +impl<'tcx> LateLintPass<'tcx> for UnitBindings { + fn check_local(&mut self, cx: &crate::LateContext<'tcx>, local: &'tcx hir::LetStmt<'tcx>) { + // Suppress warning if user: + // - explicitly ascribes a type to the pattern + // - explicitly wrote `let pat = ();` + // - explicitly wrote `let () = init;`. + if !local.span.from_expansion() + && let Some(tyck_results) = cx.maybe_typeck_results() + && let Some(init) = local.init + && let init_ty = tyck_results.expr_ty(init) + && let local_ty = tyck_results.node_type(local.hir_id) + && init_ty == cx.tcx.types.unit + && local_ty == cx.tcx.types.unit + && local.ty.is_none() + && !matches!(init.kind, hir::ExprKind::Tup([])) + && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..)) + { + cx.emit_span_lint( + UNIT_BINDINGS, + local.span, + UnitBindingsDiag { label: local.pat.span }, + ); + } + } +} diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs new file mode 100644 index 00000000000..65d42ed8054 --- /dev/null +++ b/compiler/rustc_lint/src/unused.rs @@ -0,0 +1,1623 @@ +use crate::lints::{ + PathStatementDrop, PathStatementDropSub, PathStatementNoEffect, UnusedAllocationDiag, + UnusedAllocationMutDiag, UnusedClosure, UnusedCoroutine, UnusedDef, UnusedDefSuggestion, + UnusedDelim, UnusedDelimSuggestion, UnusedImportBracesDiag, UnusedOp, UnusedOpSuggestion, + UnusedResult, +}; +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::{pluralize, MultiSpan}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, LangItem}; +use rustc_infer::traits::util::elaborate; +use rustc_middle::ty::adjustment; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; +use rustc_span::symbol::Symbol; +use rustc_span::symbol::{kw, sym}; +use rustc_span::{BytePos, Span}; +use std::iter; +use std::ops::ControlFlow; +use tracing::instrument; + +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 hir::StmtKind::Semi(mut expr) = s.kind else { + return; + }; + + let mut expr_is_from_block = false; + while let hir::ExprKind::Block(blk, ..) = expr.kind + && let hir::Block { expr: Some(e), .. } = blk + { + expr = e; + expr_is_from_block = true; + } + + if let hir::ExprKind::Ret(..) = expr.kind { + return; + } + + if let hir::ExprKind::Match(await_expr, _arms, hir::MatchSource::AwaitDesugar) = expr.kind + && let ty = cx.typeck_results().expr_ty(await_expr) + && let ty::Alias(ty::Opaque, ty::AliasTy { def_id: future_def_id, .. }) = ty.kind() + && cx.tcx.ty_is_opaque_future(ty) + && let async_fn_def_id = cx.tcx.parent(*future_def_id) + && matches!(cx.tcx.def_kind(async_fn_def_id), DefKind::Fn | DefKind::AssocFn) + // Check that this `impl Future` actually comes from an `async fn` + && cx.tcx.asyncness(async_fn_def_id).is_async() + && check_must_use_def( + cx, + async_fn_def_id, + expr.span, + "output of future returned by ", + "", + expr_is_from_block, + ) + { + // We have a bare `foo().await;` on an opaque type from an async function that was + // annotated with `#[must_use]`. + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + + let must_use_result = is_ty_must_use(cx, ty, expr, expr.span); + let type_lint_emitted_or_suppressed = match must_use_result { + Some(path) => { + emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); + true + } + None => false, + }; + + let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block); + + if !fn_warned && type_lint_emitted_or_suppressed { + // 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::OffsetOf(..) => Some("`offset_of` call"), + hir::ExprKind::Unary(..) => Some("unary operation"), + _ => None, + }; + + let mut op_warned = false; + + if let Some(must_use_op) = must_use_op { + cx.emit_span_lint( + UNUSED_MUST_USE, + expr.span, + UnusedOp { + op: must_use_op, + label: expr.span, + suggestion: if expr_is_from_block { + UnusedOpSuggestion::BlockTailExpr { + before_span: expr.span.shrink_to_lo(), + after_span: expr.span.shrink_to_hi(), + } + } else { + UnusedOpSuggestion::NormalExpr { span: expr.span.shrink_to_lo() } + }, + }, + ); + op_warned = true; + } + + if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { + cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); + } + + fn check_fn_must_use( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + expr_is_from_block: bool, + ) -> bool { + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(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 { + check_must_use_def( + cx, + def_id, + expr.span, + "return value of ", + "", + expr_is_from_block, + ) + } else { + false + } + } + + /// A path through a type to a must_use source. Contains useful info for the lint. + #[derive(Debug)] + enum MustUsePath { + /// Suppress must_use checking. + Suppressed, + /// The root of the normal must_use lint with an optional message. + Def(Span, DefId, Option<Symbol>), + Boxed(Box<Self>), + Pinned(Box<Self>), + Opaque(Box<Self>), + TraitObject(Box<Self>), + TupleElement(Vec<(usize, Self)>), + Array(Box<Self>, u64), + /// The root of the unused_closures lint. + Closure(Span), + /// The root of the unused_coroutines lint. + Coroutine(Span), + } + + #[instrument(skip(cx, expr), level = "debug", ret)] + fn is_ty_must_use<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, + ) -> Option<MustUsePath> { + if ty.is_unit() + || !ty.is_inhabited_from( + cx.tcx, + cx.tcx.parent_module(expr.hir_id).to_def_id(), + cx.param_env, + ) + { + return Some(MustUsePath::Suppressed); + } + + match *ty.kind() { + ty::Adt(..) if ty.is_box() => { + let boxed_ty = ty.boxed_ty(); + is_ty_must_use(cx, boxed_ty, expr, span) + .map(|inner| MustUsePath::Boxed(Box::new(inner))) + } + ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { + let pinned_ty = args.type_at(0); + is_ty_must_use(cx, pinned_ty, expr, span) + .map(|inner| MustUsePath::Pinned(Box::new(inner))) + } + ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), + ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { + elaborate( + cx.tcx, + cx.tcx.explicit_item_super_predicates(def).iter_identity_copied(), + ) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = + pred.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) + } + ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() + { + let def_id = trait_ref.def_id; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::TraitObject(Box::new(inner))) + } else { + None + } + }), + ty::Tuple(tys) => { + let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { + debug_assert_eq!(elem_exprs.len(), tys.len()); + elem_exprs + } else { + &[] + }; + + // Default to `expr`. + let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); + + let nested_must_use = tys + .iter() + .zip(elem_exprs) + .enumerate() + .filter_map(|(i, (ty, expr))| { + is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) + }) + .collect::<Vec<_>>(); + + if !nested_must_use.is_empty() { + Some(MustUsePath::TupleElement(nested_must_use)) + } else { + None + } + } + ty::Array(ty, len) => match len.try_eval_target_usize(cx.tcx, cx.param_env) { + // If the array is empty we don't lint, to avoid false positives + Some(0) | None => None, + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(len) => is_ty_must_use(cx, ty, expr, span) + .map(|inner| MustUsePath::Array(Box::new(inner), len)), + }, + ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), + ty::Coroutine(def_id, ..) => { + // async fn should be treated as "implementor of `Future`" + let must_use = if cx.tcx.coroutine_is_async(def_id) { + let def_id = cx.tcx.lang_items().future_trait()?; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) + } else { + None + }; + must_use.or(Some(MustUsePath::Coroutine(span))) + } + _ => None, + } + } + + fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option<MustUsePath> { + if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) { + // check for #[must_use = "..."] + let reason = attr.value_str(); + Some(MustUsePath::Def(span, def_id, reason)) + } else { + None + } + } + + // Returns whether further errors should be suppressed because either a lint has been + // emitted or the type should be ignored. + fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + expr_is_from_block: bool, + ) -> bool { + is_def_must_use(cx, def_id, span) + .map(|must_use_path| { + emit_must_use_untranslated( + cx, + &must_use_path, + descr_pre_path, + descr_post_path, + 1, + false, + expr_is_from_block, + ) + }) + .is_some() + } + + #[instrument(skip(cx), level = "debug")] + fn emit_must_use_untranslated( + cx: &LateContext<'_>, + path: &MustUsePath, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + is_inner: bool, + expr_is_from_block: bool, + ) { + let plural_suffix = pluralize!(plural_len); + + match path { + MustUsePath::Suppressed => {} + MustUsePath::Boxed(path) => { + let descr_pre = &format!("{descr_pre}boxed "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Pinned(path) => { + let descr_pre = &format!("{descr_pre}pinned "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Opaque(path) => { + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TraitObject(path) => { + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TupleElement(elems) => { + for (index, path) in elems { + let descr_post = &format!(" in tuple element {index}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + } + MustUsePath::Array(path, len) => { + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), + true, + expr_is_from_block, + ); + } + MustUsePath::Closure(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Coroutine(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Def(span, def_id, reason) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedDef { + pre: descr_pre, + post: descr_post, + cx, + def_id: *def_id, + note: *reason, + suggestion: (!is_inner).then_some(if expr_is_from_block { + UnusedDefSuggestion::BlockTailExpr { + before_span: span.shrink_to_lo(), + after_span: span.shrink_to_hi(), + } + } else { + UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() } + }), + }, + ); + } + } + } + } +} + +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 { + let ty = cx.typeck_results().expr_ty(expr); + if ty.needs_drop(cx.tcx, cx.param_env) { + let sub = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span) + { + PathStatementDropSub::Suggestion { span: s.span, snippet } + } else { + PathStatementDropSub::Help { span: s.span } + }; + cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub }) + } else { + cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect); + } + } + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum UnusedDelimsCtx { + FunctionArg, + MethodArg, + AssignedValue, + AssignedValueLetElse, + IfCond, + WhileCond, + ForIterExpr, + MatchScrutineeExpr, + ReturnValue, + BlockRetValue, + LetScrutineeExpr, + ArrayLenExpr, + AnonConst, + MatchArmExpr, + IndexExpr, +} + +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", + UnusedDelimsCtx::IndexExpr => "index 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>, + is_kw: bool, + ); + + fn is_expr_delims_necessary( + inner: &ast::Expr, + ctx: UnusedDelimsCtx, + followed_by_block: bool, + ) -> bool { + let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse; + + if followed_by_else { + match inner.kind { + ast::ExprKind::Binary(op, ..) if op.node.is_lazy() => return true, + _ if classify::expr_trailing_brace(inner).is_some() => return true, + _ => {} + } + } + + // Check it's range in LetScrutineeExpr + if let ast::ExprKind::Range(..) = inner.kind + && matches!(ctx, UnusedDelimsCtx::LetScrutineeExpr) + { + return true; + } + + // Check if LHS needs parens to prevent false-positives in cases like + // `fn x() -> u8 { ({ 0 } + 1) }`. + // + // FIXME: https://github.com/rust-lang/rust/issues/119426 + // The syntax tree in this code is from after macro expansion, so the + // current implementation has both false negatives and false positives + // related to expressions containing macros. + // + // macro_rules! m1 { + // () => { + // 1 + // }; + // } + // + // fn f1() -> u8 { + // // Lint says parens are not needed, but they are. + // (m1! {} + 1) + // } + // + // macro_rules! m2 { + // () => { + // loop { break 1; } + // }; + // } + // + // fn f2() -> u8 { + // // Lint says parens are needed, but they are not. + // (m2!() + 1) + // } + { + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::Binary(_op, lhs, _rhs) => lhs, + ExprKind::Call(fn_, _params) => fn_, + ExprKind::Cast(expr, _ty) => expr, + ExprKind::Type(expr, _ty) => expr, + ExprKind::Index(base, _subscript, _) => base, + _ => break, + }; + if !classify::expr_requires_semi_to_be_stmt(innermost) { + return true; + } + } + } + + // Check if RHS needs parens to prevent false-positives in cases like `if (() == return) + // {}`. + if !followed_by_block { + return false; + } + + // Check if we need parens for `match &( Struct { feild: }) {}`. + { + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::AddrOf(_, _, expr) => expr, + _ => { + if parser::contains_exterior_struct_lit(innermost) { + return true; + } else { + break; + } + } + } + } + } + + let mut innermost = inner; + loop { + innermost = match &innermost.kind { + ExprKind::Unary(_op, expr) => expr, + ExprKind::Binary(_op, _lhs, rhs) => rhs, + ExprKind::AssignOp(_op, _lhs, rhs) => rhs, + ExprKind::Assign(_lhs, rhs, _span) => rhs, + + ExprKind::Ret(_) | ExprKind::Yield(..) | ExprKind::Yeet(..) => return true, + + ExprKind::Break(_label, None) => return false, + ExprKind::Break(_label, Some(break_expr)) => { + return matches!(break_expr.kind, ExprKind::Block(..)); + } + + ExprKind::Range(_lhs, Some(rhs), _limits) => { + return matches!(rhs.kind, ExprKind::Block(..)); + } + + _ => return 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>, + is_kw: bool, + ) { + // If `value` has `ExprKind::Err`, unused delim lint can be broken. + // For example, the following code caused ICE. + // This is because the `ExprKind::Call` in `value` has `ExprKind::Err` as its argument + // and this leads to wrong spans. #104897 + // + // ``` + // fn f(){(print!(รก + // ``` + use rustc_ast::visit::{walk_expr, Visitor}; + struct ErrExprVisitor; + impl<'ast> Visitor<'ast> for ErrExprVisitor { + type Result = ControlFlow<()>; + fn visit_expr(&mut self, expr: &'ast ast::Expr) -> ControlFlow<()> { + if let ExprKind::Err(_) = expr.kind { + ControlFlow::Break(()) + } else { + walk_expr(self, expr) + } + } + } + if ErrExprVisitor.visit_expr(value).is_break() { + return; + } + let spans = match value.kind { + ast::ExprKind::Block(ref block, None) if block.stmts.len() == 1 => block.stmts[0] + .span + .find_ancestor_inside(value.span) + .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi()))), + ast::ExprKind::Paren(ref expr) => { + expr.span.find_ancestor_inside(value.span).map(|expr_span| { + (value.span.with_hi(expr_span.lo()), value.span.with_lo(expr_span.hi())) + }) + } + _ => return, + }; + let keep_space = ( + left_pos.is_some_and(|s| s >= value.span.lo()), + right_pos.is_some_and(|s| s <= value.span.hi()), + ); + self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space, is_kw); + } + + fn emit_unused_delims( + &self, + cx: &EarlyContext<'_>, + value_span: Span, + spans: Option<(Span, Span)>, + msg: &str, + keep_space: (bool, bool), + is_kw: bool, + ) { + let primary_span = if let Some((lo, hi)) = spans { + if hi.is_empty() { + // do not point at delims that do not exist + return; + } + MultiSpan::from(vec![lo, hi]) + } else { + MultiSpan::from(value_span) + }; + let suggestion = spans.map(|(lo, hi)| { + let sm = cx.sess().source_map(); + let lo_replace = if (keep_space.0 || is_kw) + && let Ok(snip) = sm.span_to_prev_source(lo) + && !snip.ends_with(' ') + { + " " + } else { + "" + }; + + let hi_replace = if keep_space.1 + && let Ok(snip) = sm.span_to_next_source(hi) + && !snip.starts_with(' ') + { + " " + } else { + "" + }; + UnusedDelimSuggestion { + start_span: lo, + start_replace: lo_replace, + end_span: hi, + end_replace: hi_replace, + } + }); + cx.emit_span_lint( + self.lint(), + primary_span, + UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion }, + ); + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + use rustc_ast::ExprKind::*; + let (value, ctx, followed_by_block, left_pos, right_pos, is_kw) = 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), true) + } + + // 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), true) + } + + ForLoop { ref iter, ref body, .. } => { + (iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true) + } + + Match(ref head, _, ast::MatchKind::Prefix) + if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => + { + let left = e.span.lo() + rustc_span::BytePos(5); + (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true) + } + + Ret(Some(ref value)) => { + let left = e.span.lo() + rustc_span::BytePos(3); + (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true) + } + + Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false), + + Assign(_, ref value, _) | AssignOp(.., ref value) => { + (value, UnusedDelimsCtx::AssignedValue, false, None, None, false) + } + // 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), + MethodCall(ref call) => (&call.args[..], 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, false); + } + return; + } + }; + self.check_unused_delims_expr( + cx, + value, + ctx, + followed_by_block, + left_pos, + right_pos, + is_kw, + ); + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + match s.kind { + StmtKind::Let(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, false); + } + } + StmtKind::Expr(ref expr) => { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::BlockRetValue, + false, + None, + None, + false, + ); + } + _ => {} + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + use ast::ItemKind::*; + + if let Const(box ast::ConstItem { expr: Some(expr), .. }) + | Static(box ast::StaticItem { expr: Some(expr), .. }) = &item.kind + { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::AssignedValue, + false, + None, + None, + false, + ); + } + } +} + +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" +} + +pub struct UnusedParens { + with_self_ty_parens: bool, + /// `1 as (i32) < 2` parses to ExprKind::Lt + /// `1 as i32 < 2` parses to i32::<2[missing angle bracket] + parens_in_cast_in_lt: Vec<ast::NodeId>, +} + +impl UnusedParens { + pub fn new() -> Self { + Self { with_self_ty_parens: false, parens_in_cast_in_lt: Vec::new() } + } +} + +impl_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>, + is_kw: bool, + ) { + match value.kind { + ast::ExprKind::Paren(ref inner) => { + if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block) + && 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.is_lazy())) + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw) + } + } + ast::ExprKind::Let(_, ref expr, _, _) => { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + false, + ); + } + _ => {} + } + } +} + +impl UnusedParens { + fn check_unused_parens_pat( + &self, + cx: &EarlyContext<'_>, + value: &ast::Pat, + avoid_or: bool, + avoid_mut: bool, + keep_space: (bool, bool), + ) { + use ast::{BindingMode, 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::MUT, ..) if avoid_mut => { + return; + } + // Otherwise proceed with linting. + _ => {} + } + let spans = if !value.span.from_expansion() { + inner + .span + .find_ancestor_inside(value.span) + .map(|inner| (value.span.with_hi(inner.lo()), value.span.with_lo(inner.hi()))) + } else { + None + }; + self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space, false); + } + } + + fn cast_followed_by_lt(&self, expr: &ast::Expr) -> Option<ast::NodeId> { + if let ExprKind::Binary(op, lhs, _rhs) = &expr.kind + && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl) + { + let mut cur = lhs; + while let ExprKind::Binary(_, _, rhs) = &cur.kind { + cur = rhs; + } + + if let ExprKind::Cast(_, ty) = &cur.kind + && let ast::TyKind::Paren(_) = &ty.kind + { + return Some(ty.id); + } + } + None + } +} + +impl EarlyLintPass for UnusedParens { + #[inline] + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + if let Some(ty_id) = self.cast_followed_by_lt(e) { + self.parens_in_cast_in_lt.push(ty_id); + } + + match e.kind { + ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop { ref pat, .. } => { + self.check_unused_parens_pat(cx, pat, false, false, (true, true)); + } + // 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, + true, + ); + 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 { + if let Some(body) = &a.body { + self.check_unused_delims_expr( + cx, + body, + UnusedDelimsCtx::MatchArmExpr, + false, + None, + None, + true, + ); + } + } + } + _ => {} + } + + <Self as UnusedDelimLint>::check_expr(self, cx, e) + } + + fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) { + if let Some(ty_id) = self.cast_followed_by_lt(e) { + let id = self + .parens_in_cast_in_lt + .pop() + .expect("check_expr and check_expr_post must balance"); + assert_eq!( + id, ty_id, + "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor" + ); + } + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) { + use ast::{Mutability, PatKind::*}; + let keep_space = (false, false); + 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 | Never | Rest | Lit(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) | Err(_) => {}, + // 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, keep_space); + }, + Struct(_, _, fps, _) => for f in fps { + self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space); + }, + // Avoid linting on `i @ (p0 | .. | pn)` and `box (p0 | .. | pn)`, #64106. + Ident(.., Some(p)) | Box(p) | Deref(p) => self.check_unused_parens_pat(cx, p, true, false, keep_space), + // 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, keep_space), + } + } + + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + if let StmtKind::Let(ref local) = s.kind { + self.check_unused_parens_pat(cx, &local.pat, true, false, (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, ¶m.pat, true, false, (false, false)); + } + + fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) { + self.check_unused_parens_pat(cx, &arm.pat, false, false, (false, false)); + } + + fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) { + if let ast::TyKind::Paren(_) = ty.kind + && Some(&ty.id) == self.parens_in_cast_in_lt.last() + { + return; + } + match &ty.kind { + ast::TyKind::Array(_, len) => { + self.check_unused_delims_expr( + cx, + &len.value, + UnusedDelimsCtx::ArrayLenExpr, + false, + None, + None, + false, + ); + } + ast::TyKind::Paren(r) => { + match &r.kind { + ast::TyKind::TraitObject(..) => {} + ast::TyKind::BareFn(b) + if self.with_self_ty_parens && b.generic_params.len() > 0 => {} + ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {} + _ => { + let spans = if !ty.span.from_expansion() { + r.span + .find_ancestor_inside(ty.span) + .map(|r| (ty.span.with_hi(r.lo()), ty.span.with_lo(r.hi()))) + } else { + None + }; + self.emit_unused_delims(cx, ty.span, spans, "type", (false, false), false); + } + } + self.with_self_ty_parens = false; + } + _ => {} + } + } + + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { + <Self as UnusedDelimLint>::check_item(self, cx, item) + } + + fn enter_where_predicate(&mut self, _: &EarlyContext<'_>, pred: &ast::WherePredicate) { + use rustc_ast::{WhereBoundPredicate, WherePredicate}; + if let WherePredicate::BoundPredicate(WhereBoundPredicate { + bounded_ty, + bound_generic_params, + .. + }) = pred + && let ast::TyKind::Paren(_) = &bounded_ty.kind + && bound_generic_params.is_empty() + { + self.with_self_ty_parens = true; + } + } + + fn exit_where_predicate(&mut self, _: &EarlyContext<'_>, _: &ast::WherePredicate) { + assert!(!self.with_self_ty_parens); + } +} + +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>, + is_kw: bool, + ) { + 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 + // not created by a macro, i.e. do not lint on: + // ``` + // struct A<const N: usize>; + // let _: A<{ 2 + 3 }>; + // let _: A<{produces_literal!()}>; + // ``` + // 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, ctx, followed_by_block) + && (ctx != UnusedDelimsCtx::AnonConst + || (matches!(expr.kind, ast::ExprKind::Lit(_)) + && !expr.span.from_expansion())) + && !cx.sess().source_map().is_multiline(value.span) + && value.attrs.is_empty() + && !value.span.from_expansion() + && !inner.span.from_expansion() + { + self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw) + } + } + } + } + ast::ExprKind::Let(_, ref expr, _, _) => { + self.check_unused_delims_expr( + cx, + expr, + UnusedDelimsCtx::LetScrutineeExpr, + followed_by_block, + None, + None, + false, + ); + } + _ => {} + } + } +} + +impl EarlyLintPass for UnusedBraces { + fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { + <Self as UnusedDelimLint>::check_stmt(self, cx, s) + } + + #[inline] + 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, + false, + ); + } + } + + 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, + false, + ); + } + } + + 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, + false, + ); + } + } + + 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, + false, + ); + } + + ast::TyKind::Typeof(ref anon_const) => { + self.check_unused_delims_expr( + cx, + &anon_const.value, + UnusedDelimsCtx::AnonConst, + false, + None, + None, + false, + ); + } + + _ => {} + } + } + + 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 (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.emit_span_lint( + UNUSED_IMPORT_BRACES, + item.span, + UnusedImportBracesDiag { node: node_name }, + ); + } + } +} + +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 + /// fn main() { + /// let a = Box::new([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::Call(path_expr, [_]) + if let hir::ExprKind::Path(qpath) = &path_expr.kind + && let Some(did) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() + && cx.tcx.is_diagnostic_item(sym::box_new, did) => {} + _ => return, + } + + for adj in cx.typeck_results().expr_adjustments(e) { + if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind { + match m { + adjustment::AutoBorrowMutability::Not => { + cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag); + } + adjustment::AutoBorrowMutability::Mut { .. } => { + cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag); + } + }; + } + } + } +} |
