diff options
Diffstat (limited to 'compiler/rustc_lint/src')
38 files changed, 2087 insertions, 1379 deletions
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs index 814991cd8c9..3a5c585366a 100644 --- a/compiler/rustc_lint/src/array_into_iter.rs +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -132,7 +132,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { } else { None }; - cx.emit_spanned_lint( + cx.emit_span_lint( ARRAY_INTO_ITER, call.ident.span, ArrayIntoIterDiag { target, suggestion: call.ident.span, sub }, diff --git a/compiler/rustc_lint/src/async_fn_in_trait.rs b/compiler/rustc_lint/src/async_fn_in_trait.rs index 51213647361..5f7a3137d52 100644 --- a/compiler/rustc_lint/src/async_fn_in_trait.rs +++ b/compiler/rustc_lint/src/async_fn_in_trait.rs @@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for AsyncFnInTrait { def.owner_id.def_id, " + Send", ); - cx.tcx.emit_spanned_lint( + cx.tcx.emit_node_span_lint( ASYNC_FN_IN_TRAIT, item.hir_id(), async_span, diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 8afb5f2d32e..d1343e3b4ba 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -44,7 +44,7 @@ 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, DecorateLint, MultiSpan}; +use rustc_errors::{Applicability, LintDiagnostic, MultiSpan}; use rustc_feature::{deprecated_attributes, AttributeGate, BuiltinAttribute, GateIssue, Stability}; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -58,7 +58,7 @@ use rustc_middle::ty::GenericArgKind; use rustc_middle::ty::ToPredicate; use rustc_middle::ty::TypeVisitableExt; use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; -use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason}; +use rustc_session::lint::{BuiltinLintDiag, FutureIncompatibilityReason}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -72,7 +72,7 @@ use crate::nonstandard_style::{method_context, MethodLateContext}; use std::fmt::Write; -// hardwired lints from librustc_middle +// hardwired lints from rustc_lint_defs pub use rustc_session::lint::builtin::*; declare_lint! { @@ -121,7 +121,7 @@ impl EarlyLintPass for WhileTrue { "{}loop", label.map_or_else(String::new, |label| format!("{}: ", label.ident,)) ); - cx.emit_spanned_lint( + cx.emit_span_lint( WHILE_TRUE, condition_span, BuiltinWhileTrue { suggestion: condition_span, replace }, @@ -162,7 +162,7 @@ impl BoxPointers { if let GenericArgKind::Type(leaf_ty) = leaf.unpack() && leaf_ty.is_box() { - cx.emit_spanned_lint(BOX_POINTERS, span, BuiltinBoxPointers { ty }); + cx.emit_span_lint(BOX_POINTERS, span, BuiltinBoxPointers { ty }); } } } @@ -265,7 +265,7 @@ impl<'tcx> LateLintPass<'tcx> for NonShorthandFieldPatterns { if cx.tcx.find_field_index(ident, variant) == Some(cx.typeck_results().field_index(fieldpat.hir_id)) { - cx.emit_spanned_lint( + cx.emit_span_lint( NON_SHORTHAND_FIELD_PATTERNS, fieldpat.span, BuiltinNonShorthandFieldPatterns { @@ -327,14 +327,14 @@ impl UnsafeCode { &self, cx: &EarlyContext<'_>, span: Span, - decorate: impl for<'a> DecorateLint<'a, ()>, + decorate: impl for<'a> LintDiagnostic<'a, ()>, ) { // This comes from a macro that has `#[allow_internal_unsafe]`. if span.allows_unsafe() { return; } - cx.emit_spanned_lint(UNSAFE_CODE, span, decorate); + cx.emit_span_lint(UNSAFE_CODE, span, decorate); } } @@ -393,6 +393,10 @@ impl EarlyLintPass for UnsafeCode { } } + ast::ItemKind::GlobalAsm(..) => { + self.report_unsafe(cx, it.span, BuiltinUnsafe::GlobalAsm); + } + _ => {} } } @@ -509,7 +513,7 @@ impl MissingDoc { 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_spanned_lint( + cx.emit_span_lint( MISSING_DOCS, cx.tcx.def_span(def_id), BuiltinMissingDoc { article, desc }, @@ -710,7 +714,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { ) .is_ok() { - cx.emit_spanned_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl); + cx.emit_span_lint(MISSING_COPY_IMPLEMENTATIONS, item.span, BuiltinMissingCopyImpl); } } } @@ -795,7 +799,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { .next() .is_some(); if !has_impl { - cx.emit_spanned_lint( + cx.emit_span_lint( MISSING_DEBUG_IMPLEMENTATIONS, item.span, BuiltinMissingDebugImpl { tcx: cx.tcx, def_id: debug }, @@ -874,7 +878,7 @@ impl EarlyLintPass for AnonymousParameters { } else { ("<type>", Applicability::HasPlaceholders) }; - cx.emit_spanned_lint( + cx.emit_span_lint( ANONYMOUS_PARAMETERS, arg.pat.span, BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip }, @@ -921,7 +925,7 @@ impl EarlyLintPass for DeprecatedAttr { BuiltinDeprecatedAttrLinkSuggestion::Default { suggestion: attr.span } } }; - cx.emit_spanned_lint( + cx.emit_span_lint( DEPRECATED, attr.span, BuiltinDeprecatedAttrLink { name, reason, link, suggestion }, @@ -931,7 +935,7 @@ impl EarlyLintPass for DeprecatedAttr { } } if attr.has_name(sym::no_start) || attr.has_name(sym::crate_id) { - cx.emit_spanned_lint( + cx.emit_span_lint( DEPRECATED, attr.span, BuiltinDeprecatedAttrUsed { @@ -973,7 +977,7 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: & BuiltinUnusedDocCommentSub::BlockHelp } }; - cx.emit_spanned_lint( + cx.emit_span_lint( UNUSED_DOC_COMMENTS, span, BuiltinUnusedDocComment { kind: node_kind, label: node_span, sub }, @@ -985,7 +989,7 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: & impl EarlyLintPass for UnusedDocComment { fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { let kind = match stmt.kind { - ast::StmtKind::Local(..) => "statements", + ast::StmtKind::Let(..) => "statements", // Disabled pending discussion in #78306 ast::StmtKind::Item(..) => return, // expressions will be reported by `check_expr`. @@ -1107,7 +1111,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { match param.kind { GenericParamKind::Lifetime { .. } => {} GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => { - cx.emit_spanned_lint( + cx.emit_span_lint( NO_MANGLE_GENERIC_ITEMS, span, BuiltinNoMangleGeneric { suggestion: no_mangle_attr.span }, @@ -1138,7 +1142,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { // Const items do not refer to a particular location in memory, and therefore // don't have anything to attach a symbol to - cx.emit_spanned_lint( + cx.emit_span_lint( NO_MANGLE_CONST_ITEMS, it.span, BuiltinConstNoMangle { suggestion }, @@ -1201,7 +1205,7 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { get_transmute_from_to(cx, expr).map(|(ty1, ty2)| (ty1.kind(), ty2.kind())) { if from_mutbl < to_mutbl { - cx.emit_spanned_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes); + cx.emit_span_lint(MUTABLE_TRANSMUTES, expr.span, BuiltinMutablesTransmutes); } } @@ -1227,16 +1231,36 @@ impl<'tcx> LateLintPass<'tcx> for MutableTransmutes { } fn def_id_is_transmute(cx: &LateContext<'_>, def_id: DefId) -> bool { - cx.tcx.is_intrinsic(def_id) && cx.tcx.item_name(def_id) == sym::transmute + cx.tcx.is_intrinsic(def_id, sym::transmute) } } } declare_lint! { - /// The `unstable_features` is deprecated and should no longer be used. + /// 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 (deprecated. do not use)" + "enabling unstable features" } declare_lint_pass!( @@ -1246,11 +1270,11 @@ declare_lint_pass!( impl<'tcx> LateLintPass<'tcx> for UnstableFeatures { fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { - if attr.has_name(sym::feature) { - if let Some(items) = attr.meta_item_list() { - for item in items { - cx.emit_spanned_lint(UNSTABLE_FEATURES, item.span(), BuiltinUnstableFeatures); - } + 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); } } } @@ -1303,13 +1327,10 @@ impl<'tcx> LateLintPass<'tcx> for UngatedAsyncFnTrackCaller { // Now, check if the function has the `#[track_caller]` attribute && let Some(attr) = cx.tcx.get_attr(def_id, sym::track_caller) { - cx.emit_spanned_lint( + cx.emit_span_lint( UNGATED_ASYNC_FN_TRACK_CALLER, attr.span, - BuiltinUngatedAsyncFnTrackCaller { - label: span, - parse_sess: &cx.tcx.sess.parse_sess, - }, + BuiltinUngatedAsyncFnTrackCaller { label: span, session: &cx.tcx.sess }, ); } } @@ -1372,7 +1393,7 @@ impl UnreachablePub { applicability = Applicability::MaybeIncorrect; } let def_span = cx.tcx.def_span(def_id); - cx.emit_spanned_lint( + cx.emit_span_lint( UNREACHABLE_PUB, def_span, BuiltinUnreachablePub { @@ -1399,8 +1420,7 @@ impl<'tcx> LateLintPass<'tcx> for UnreachablePub { } fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) { - let map = cx.tcx.hir(); - if matches!(map.get_parent(field.hir_id), Node::Variant(_)) { + if matches!(cx.tcx.parent_hir_node(field.hir_id), Node::Variant(_)) { return; } self.perform_lint(cx, "field", field.def_id, field.vis_span, false); @@ -1501,7 +1521,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { suggested_changing_assoc_types = true; SuggestChangingAssocTypes { ty: hir_ty } }); - cx.emit_spanned_lint( + cx.emit_span_lint( TYPE_ALIAS_BOUNDS, where_spans, BuiltinTypeAliasWhereClause { @@ -1517,7 +1537,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { suggested_changing_assoc_types = true; SuggestChangingAssocTypes { ty: hir_ty } }); - cx.emit_spanned_lint( + cx.emit_span_lint( TYPE_ALIAS_BOUNDS, inline_spans, BuiltinTypeAliasGenericBounds { suggestion, sub }, @@ -1526,32 +1546,6 @@ impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { } } -declare_lint_pass!( - /// Lint constants that are erroneous. - /// Without this lint, we might not get any diagnostic if the constant is - /// unused within this crate, even though downstream crates can't use it - /// without producing an error. - UnusedBrokenConst => [] -); - -impl<'tcx> LateLintPass<'tcx> for UnusedBrokenConst { - fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - match it.kind { - hir::ItemKind::Const(_, _, body_id) => { - let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); - // trigger the query once for all constants since that will already report the errors - // FIXME(generic_const_items): Does this work properly with generic const items? - cx.tcx.ensure().const_eval_poly(def_id); - } - hir::ItemKind::Static(_, _, body_id) => { - let def_id = cx.tcx.hir().body_owner_def_id(body_id).to_def_id(); - cx.tcx.ensure().eval_static_initializer(def_id); - } - _ => {} - } - } -} - declare_lint! { /// The `trivial_bounds` lint detects trait bounds that don't depend on /// any type parameters. @@ -1616,7 +1610,7 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints { | ClauseKind::ConstEvaluatable(..) => continue, }; if predicate.is_global() { - cx.emit_spanned_lint( + cx.emit_span_lint( TRIVIAL_BOUNDS, span, BuiltinTrivialBounds { predicate_kind_name, predicate }, @@ -1734,7 +1728,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { replace, }); } else { - cx.emit_spanned_lint( + cx.emit_span_lint( ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, BuiltinEllipsisInclusiveRangePatternsLint::Parenthesise { @@ -1752,7 +1746,7 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { replace: replace.to_string(), }); } else { - cx.emit_spanned_lint( + cx.emit_span_lint( ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, join, BuiltinEllipsisInclusiveRangePatternsLint::NonParenthesise { @@ -1831,7 +1825,7 @@ impl KeywordIdents { match tt { // Only report non-raw idents. TokenTree::Token(token, _) => { - if let Some((ident, false)) = token.ident() { + if let Some((ident, token::IdentIsRaw::No)) = token.ident() { self.check_ident_token(cx, UnderMacro(true), ident); } } @@ -1874,11 +1868,11 @@ impl KeywordIdents { }; // Don't lint `r#foo`. - if cx.sess().parse_sess.raw_identifier_spans.contains(ident.span) { + if cx.sess().psess.raw_identifier_spans.contains(ident.span) { return; } - cx.emit_spanned_lint( + cx.emit_span_lint( KEYWORD_IDENTS, ident.span, BuiltinKeywordIdents { kw: ident, next: next_edition, suggestion: ident.span }, @@ -2186,7 +2180,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { lint_spans.sort_unstable(); lint_spans.dedup(); - cx.emit_spanned_lint( + cx.emit_span_lint( EXPLICIT_OUTLIVES_REQUIREMENTS, lint_spans.clone(), BuiltinExplicitOutlives { @@ -2273,13 +2267,13 @@ impl EarlyLintPass for IncompleteInternalFeatures { let help = HAS_MIN_FEATURES.contains(&name).then_some(BuiltinIncompleteFeaturesHelp); - cx.emit_spanned_lint( + cx.emit_span_lint( INCOMPLETE_FEATURES, span, BuiltinIncompleteFeatures { name, note, help }, ); } else { - cx.emit_spanned_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name }); + cx.emit_span_lint(INTERNAL_FEATURES, span, BuiltinInternalFeatures { name }); } }); } @@ -2595,7 +2589,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { InitKind::Uninit => fluent::lint_builtin_unpermitted_type_init_uninit, }; let sub = BuiltinUnpermittedTypeInitSub { err }; - cx.emit_spanned_lint( + cx.emit_span_lint( INVALID_VALUE, expr.span, BuiltinUnpermittedTypeInit { @@ -2683,7 +2677,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefNullPtr { if let rustc_hir::ExprKind::Unary(rustc_hir::UnOp::Deref, expr_deref) = expr.kind { if is_null_ptr(cx, expr_deref) { - cx.emit_spanned_lint( + cx.emit_span_lint( DEREF_NULLPTR, expr.span, BuiltinDerefNullptr { label: expr.span }, @@ -2734,10 +2728,13 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { #[allow(rustc::diagnostic_outside_of_impl)] 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, .. }), + 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> { @@ -2763,24 +2760,57 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { 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; - for (idx, _) in statement.match_indices(':') { + 'label_loop: for (idx, _) in statement.match_indices(':') { let possible_label = statement[start_idx..idx].trim(); let mut chars = possible_label.chars(); - let Some(c) = chars.next() else { - // Empty string means a leading ':' in this section, which is not a label - break; + + let Some(start) = chars.next() else { + // Empty string means a leading ':' in this section, which is not a label. + break 'label_loop; }; - // A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $ - if (c.is_alphabetic() || matches!(c, '.' | '_')) - && chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$')) - { - found_labels.push(possible_label); - } else { - // If we encounter a non-label, there cannot be any further labels, so stop checking - break; + + // Whether a { bracket has been seen and its } hasn't been found yet. + let mut in_bracket = false; + + // A label starts with an ASCII alphabetic character or . or _ + // A label can also start with a format arg, if it's not a raw asm block. + if !raw && start == '{' { + in_bracket = true; + } else if !(start.is_ascii_alphabetic() || matches!(start, '.' | '_')) { + break 'label_loop; + } + + // Labels continue with ASCII alphanumeric characters, _, or $ + 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; + } else { + if !(c.is_ascii_alphanumeric() || matches!(c, '_' | '$')) { + // 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 likely a label. + found_labels.push(possible_label); start_idx = idx + 1; } } @@ -2796,12 +2826,12 @@ impl<'tcx> LateLintPass<'tcx> for NamedAsmLabels { let target_spans: MultiSpan = if spans.len() > 0 { spans.into() } else { (*template_span).into() }; - cx.lookup_with_diagnostics( + cx.span_lint_with_diagnostics( NAMED_ASM_LABELS, Some(target_spans), fluent::lint_builtin_asm_labels, |_| {}, - BuiltinLintDiagnostics::NamedAsmLabel( + BuiltinLintDiag::NamedAsmLabel( "only local labels of the form `<number>:` should be used in inline asm" .to_string(), ), @@ -2872,12 +2902,12 @@ impl EarlyLintPass for SpecialModuleName { } match item.ident.name.as_str() { - "lib" => cx.emit_spanned_lint( + "lib" => cx.emit_span_lint( SPECIAL_MODULE_NAME, item.span, BuiltinSpecialModuleNameUsed::Lib, ), - "main" => cx.emit_spanned_lint( + "main" => cx.emit_span_lint( SPECIAL_MODULE_NAME, item.span, BuiltinSpecialModuleNameUsed::Main, diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 40b70ba4e04..8c0e5b17fd8 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -18,34 +18,33 @@ use self::TargetLint::*; use crate::levels::LintLevelsBuilder; use crate::passes::{EarlyLintPassObject, LateLintPassObject}; -use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync; -use rustc_errors::{add_elided_lifetime_in_path_suggestion, DiagnosticBuilder, DiagnosticMessage}; -use rustc_errors::{Applicability, DecorateLint, MultiSpan, SuggestionStyle}; +use rustc_data_structures::unord::UnordMap; +use rustc_errors::{Diag, DiagMessage, 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::middle::privacy::EffectiveVisibilities; -use rustc_middle::middle::stability; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::{with_no_trimmed_paths, PrintError}; use rustc_middle::ty::{self, print::Printer, GenericArg, RegisteredTools, Ty, TyCtxt}; -use rustc_session::config::ExpectedValues; -use rustc_session::lint::{BuiltinLintDiagnostics, LintExpectationId}; +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_name; +use rustc_span::edit_distance::find_best_match_for_names; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_span::{BytePos, Span}; +use rustc_span::Span; use rustc_target::abi; use std::cell::Cell; use std::iter; use std::slice; +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; @@ -71,10 +70,10 @@ pub struct LintStore { pub late_module_passes: Vec<Box<LateLintPassFactory>>, /// Lints indexed by name. - by_name: FxHashMap<String, TargetLint>, + by_name: UnordMap<String, TargetLint>, /// Map of registered lint groups to what lints they expand to. - lint_groups: FxHashMap<&'static str, LintGroup>, + lint_groups: FxIndexMap<&'static str, LintGroup>, } impl LintStoreMarker for LintStore {} @@ -119,7 +118,7 @@ struct LintGroup { pub enum CheckLintNameResult<'a> { Ok(&'a [LintId]), /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. - NoLint(Option<Symbol>), + 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. @@ -154,8 +153,6 @@ impl LintStore { pub fn get_lint_groups<'t>( &'t self, ) -> impl Iterator<Item = (&'static str, Vec<LintId>, bool)> + 't { - // This function is not used in a way which observes the order of lints. - #[allow(rustc::potential_query_instability)] self.lint_groups .iter() .filter(|(_, LintGroup { depr, .. })| { @@ -341,7 +338,7 @@ impl LintStore { } /// Checks the name of a lint for its existence, and whether it was - /// renamed or removed. Generates a DiagnosticBuilder containing a + /// 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 @@ -376,10 +373,11 @@ impl LintStore { 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.keys().collect::<Vec<_>>()); + 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) + 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. @@ -421,17 +419,20 @@ impl LintStore { } } - fn no_lint_suggestion(&self, lint_name: &str) -> CheckLintNameResult<'_> { + 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))); + 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() @@ -441,7 +442,16 @@ impl LintStore { 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 suggestion = find_best_match_for_name(&names, Symbol::intern(&name_lower), None); + 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) } @@ -454,7 +464,7 @@ impl LintStore { 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), + 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 { @@ -520,458 +530,20 @@ pub trait LintContext { /// Emit a lint at the appropriate level, with an optional associated span and an existing /// diagnostic. /// - /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature #[rustc_lint_diagnostics] - fn lookup_with_diagnostics( + fn span_lint_with_diagnostics( &self, lint: &'static Lint, span: Option<impl Into<MultiSpan>>, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), - diagnostic: BuiltinLintDiagnostics, + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), + diagnostic: BuiltinLintDiag, ) { // We first generate a blank diagnostic. - self.lookup(lint, span, msg,|db| { + self.opt_span_lint(lint, span, msg, |db| { // Now, set up surrounding context. - let sess = self.sess(); - match diagnostic { - BuiltinLintDiagnostics::UnicodeTextFlow(span, content) => { - let spans: Vec<_> = content - .char_indices() - .filter_map(|(i, c)| { - TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { - let lo = span.lo() + BytePos(2 + i as u32); - (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) - }) - }) - .collect(); - let (an, s) = match spans.len() { - 1 => ("an ", ""), - _ => ("", "s"), - }; - db.span_label(span, format!( - "this comment contains {an}invisible unicode text flow control codepoint{s}", - )); - for (c, span) in &spans { - db.span_label(*span, format!("{c:?}")); - } - db.note( - "these kind of unicode codepoints change the way text flows on \ - applications that support them, but can cause confusion because they \ - change the order of characters on the screen", - ); - if !spans.is_empty() { - db.multipart_suggestion_with_style( - "if their presence wasn't intentional, you can remove them", - spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), - Applicability::MachineApplicable, - SuggestionStyle::HideCodeAlways, - ); - } - }, - BuiltinLintDiagnostics::Normal => (), - BuiltinLintDiagnostics::AbsPathWithModule(span) => { - let (sugg, app) = match sess.source_map().span_to_snippet(span) { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = - if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), - }; - db.span_suggestion(span, "use `crate`", sugg, app); - } - BuiltinLintDiagnostics::ProcMacroDeriveResolutionFallback(span) => { - db.span_label( - span, - "names from parent modules are not accessible without an explicit import", - ); - } - BuiltinLintDiagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths( - span_def, - ) => { - db.span_note(span_def, "the macro is defined here"); - } - BuiltinLintDiagnostics::ElidedLifetimesInPaths( - n, - path_span, - incl_angl_brckt, - insertion_span, - ) => { - add_elided_lifetime_in_path_suggestion( - sess.source_map(), - db, - n, - path_span, - incl_angl_brckt, - insertion_span, - ); - } - BuiltinLintDiagnostics::UnknownCrateTypes(span, note, sugg) => { - db.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect); - } - BuiltinLintDiagnostics::UnusedImports(message, replaces, in_test_module) => { - if !replaces.is_empty() { - db.tool_only_multipart_suggestion( - message, - replaces, - Applicability::MachineApplicable, - ); - } - - if let Some(span) = in_test_module { - db.span_help( - self.sess().source_map().guess_head_span(span), - "consider adding a `#[cfg(test)]` to the containing module", - ); - } - } - BuiltinLintDiagnostics::RedundantImport(spans, ident) => { - for (span, is_imported) in spans { - let introduced = if is_imported { "imported" } else { "defined" }; - db.span_label( - span, - format!("the item `{ident}` is already {introduced} here"), - ); - } - } - BuiltinLintDiagnostics::DeprecatedMacro(suggestion, span) => { - stability::deprecation_suggestion(db, "macro", suggestion, span) - } - BuiltinLintDiagnostics::UnusedDocComment(span) => { - db.span_label(span, "rustdoc does not generate documentation for macro invocations"); - db.help("to document an item produced by a macro, \ - the macro must produce the documentation as part of its expansion"); - } - BuiltinLintDiagnostics::PatternsInFnsWithoutBody(span, ident) => { - db.span_suggestion(span, "remove `mut` from the parameter", ident, Applicability::MachineApplicable); - } - BuiltinLintDiagnostics::MissingAbi(span, default_abi) => { - db.span_label(span, "ABI should be specified here"); - db.help(format!("the default ABI is {}", default_abi.name())); - } - BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => { - db.span_label(span, "the attribute is introduced here"); - } - BuiltinLintDiagnostics::ProcMacroBackCompat(note) => { - db.note(note); - } - BuiltinLintDiagnostics::OrPatternsBackCompat(span,suggestion) => { - db.span_suggestion(span, "use pat_param to preserve semantics", suggestion, Applicability::MachineApplicable); - } - BuiltinLintDiagnostics::ReservedPrefix(span) => { - db.span_label(span, "unknown prefix"); - db.span_suggestion_verbose( - span.shrink_to_hi(), - "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", - " ", - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::UnusedBuiltinAttribute { - attr_name, - macro_name, - invoc_span - } => { - db.span_note( - invoc_span, - format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") - ); - } - BuiltinLintDiagnostics::TrailingMacro(is_trailing, name) => { - if is_trailing { - db.note("macro invocations at the end of a block are treated as expressions"); - db.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); - } - } - BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => { - db.multipart_suggestion( - "wrap this expression in parentheses", - vec![(span.shrink_to_lo(), "(".to_string()), - (span.shrink_to_hi(), ")".to_string())], - Applicability::MachineApplicable - ); - } - BuiltinLintDiagnostics::NamedAsmLabel(help) => { - db.help(help); - db.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information"); - }, - BuiltinLintDiagnostics::UnexpectedCfgName((name, name_span), value) => { - let possibilities: Vec<Symbol> = sess.parse_sess.check_config.expecteds.keys().copied().collect(); - let is_from_cargo = std::env::var_os("CARGO").is_some(); - let mut is_feature_cfg = name == sym::feature; - - if is_feature_cfg && is_from_cargo { - db.help("consider defining some features in `Cargo.toml`"); - // Suggest the most probable if we found one - } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { - if let Some(ExpectedValues::Some(best_match_values)) = - sess.parse_sess.check_config.expecteds.get(&best_match) { - let mut possibilities = best_match_values.iter() - .flatten() - .map(Symbol::as_str) - .collect::<Vec<_>>(); - possibilities.sort(); - - let mut should_print_possibilities = true; - if let Some((value, value_span)) = value { - if best_match_values.contains(&Some(value)) { - db.span_suggestion(name_span, "there is a config with a similar name and value", best_match, Applicability::MaybeIncorrect); - should_print_possibilities = false; - } else if best_match_values.contains(&None) { - db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and no value", best_match, Applicability::MaybeIncorrect); - should_print_possibilities = false; - } else if let Some(first_value) = possibilities.first() { - db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", format!("{best_match} = \"{first_value}\""), Applicability::MaybeIncorrect); - } else { - db.span_suggestion(name_span.to(value_span), "there is a config with a similar name and different values", best_match, Applicability::MaybeIncorrect); - }; - } else { - db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect); - } - - if !possibilities.is_empty() && should_print_possibilities { - let possibilities = possibilities.join("`, `"); - db.help(format!("expected values for `{best_match}` are: `{possibilities}`")); - } - } else { - db.span_suggestion(name_span, "there is a config with a similar name", best_match, Applicability::MaybeIncorrect); - } - - is_feature_cfg |= best_match == sym::feature; - } else if !possibilities.is_empty() { - let mut possibilities = possibilities.iter() - .map(Symbol::as_str) - .collect::<Vec<_>>(); - possibilities.sort(); - let possibilities = possibilities.join("`, `"); - - // The list of expected names can be long (even by default) and - // so the diagnostic produced can take a lot of space. To avoid - // cloging the user output we only want to print that diagnostic - // once. - db.help_once(format!("expected names are: `{possibilities}`")); - } - - let inst = if let Some((value, _value_span)) = value { - let pre = if is_from_cargo { "\\" } else { "" }; - format!("cfg({name}, values({pre}\"{value}{pre}\"))") - } else { - format!("cfg({name})") - }; - - if is_from_cargo { - if !is_feature_cfg { - db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); - } - db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration"); - } else { - db.help(format!("to expect this configuration use `--check-cfg={inst}`")); - db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration"); - } - }, - BuiltinLintDiagnostics::UnexpectedCfgValue((name, name_span), value) => { - let Some(ExpectedValues::Some(values)) = &sess.parse_sess.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; - let possibilities: Vec<Symbol> = values.iter() - .inspect(|a| have_none_possibility |= a.is_none()) - .copied() - .flatten() - .collect(); - let is_from_cargo = std::env::var_os("CARGO").is_some(); - - // Show the full list if all possible values for a given name, but don't do it - // for names as the possibilities could be very long - if !possibilities.is_empty() { - { - let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>(); - possibilities.sort(); - - let possibilities = possibilities.join("`, `"); - let none = if have_none_possibility { "(none), " } else { "" }; - - db.note(format!("expected values for `{name}` are: {none}`{possibilities}`")); - } - - 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) { - db.span_suggestion(value_span, "there is a expected value with a similar name", format!("\"{best_match}\""), Applicability::MaybeIncorrect); - - } - } else if name == sym::feature && is_from_cargo { - db.help(format!("consider defining `{name}` as feature in `Cargo.toml`")); - } else if let &[first_possibility] = &possibilities[..] { - db.span_suggestion(name_span.shrink_to_hi(), "specify a config value", format!(" = \"{first_possibility}\""), Applicability::MaybeIncorrect); - } - } else if have_none_possibility { - db.note(format!("no expected value for `{name}`")); - if let Some((_value, value_span)) = value { - db.span_suggestion(name_span.shrink_to_hi().to(value_span), "remove the value", "", Applicability::MaybeIncorrect); - } - } - - let inst = if let Some((value, _value_span)) = value { - let pre = if is_from_cargo { "\\" } else { "" }; - format!("cfg({name}, values({pre}\"{value}{pre}\"))") - } else { - format!("cfg({name})") - }; - - if is_from_cargo { - if name == sym::feature { - if let Some((value, _value_span)) = value { - db.help(format!("consider adding `{value}` as a feature in `Cargo.toml`")); - } - } else { - db.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); - } - db.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration"); - } else { - db.help(format!("to expect this configuration use `--check-cfg={inst}`")); - db.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration"); - } - }, - BuiltinLintDiagnostics::DeprecatedWhereclauseLocation(new_span, suggestion) => { - db.multipart_suggestion( - "move it to the end of the type declaration", - vec![(db.span.primary_span().unwrap(), "".to_string()), (new_span, suggestion)], - Applicability::MachineApplicable, - ); - db.note( - "see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information", - ); - }, - BuiltinLintDiagnostics::SingleUseLifetime { - param_span, - use_span: Some((use_span, elide)), - deletion_span, - } => { - debug!(?param_span, ?use_span, ?deletion_span); - db.span_label(param_span, "this lifetime..."); - db.span_label(use_span, "...is used only here"); - if let Some(deletion_span) = deletion_span { - let msg = "elide the single-use lifetime"; - let (use_span, replace_lt) = if elide { - let use_span = sess.source_map().span_extend_while( - use_span, - char::is_whitespace, - ).unwrap_or(use_span); - (use_span, String::new()) - } else { - (use_span, "'_".to_owned()) - }; - 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 suggestions = if deletion_span.is_empty() { - vec![(use_span, replace_lt)] - } else { - vec![(deletion_span, String::new()), (use_span, replace_lt)] - }; - db.multipart_suggestion( - msg, - suggestions, - Applicability::MachineApplicable, - ); - } - }, - BuiltinLintDiagnostics::SingleUseLifetime { - param_span: _, - use_span: None, - deletion_span, - } => { - debug!(?deletion_span); - if let Some(deletion_span) = deletion_span { - db.span_suggestion( - deletion_span, - "elide the unused lifetime", - "", - Applicability::MachineApplicable, - ); - } - }, - BuiltinLintDiagnostics::NamedArgumentUsedPositionally{ position_sp_to_replace, position_sp_for_msg, named_arg_sp, named_arg_name, is_formatting_arg} => { - db.span_label(named_arg_sp, "this named argument is referred to by position in formatting string"); - if let Some(positional_arg_for_msg) = position_sp_for_msg { - let msg = format!("this formatting argument uses named argument `{named_arg_name}` by position"); - db.span_label(positional_arg_for_msg, msg); - } - - if let Some(positional_arg_to_replace) = position_sp_to_replace { - let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; - let span_to_replace = if let Ok(positional_arg_content) = - self.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 - }; - db.span_suggestion_verbose( - span_to_replace, - "use the named argument by name to avoid ambiguity", - name, - Applicability::MaybeIncorrect, - ); - } - } - BuiltinLintDiagnostics::ByteSliceInPackedStructWithDerive => { - db.help("consider implementing the trait by hand, or remove the `packed` attribute"); - } - BuiltinLintDiagnostics::UnusedExternCrate { removal_span }=> { - db.span_suggestion( - removal_span, - "remove it", - "", - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::ExternCrateNotIdiomatic { vis_span, ident_span }=> { - let suggestion_span = vis_span.between(ident_span); - db.span_suggestion_verbose( - suggestion_span, - "convert it to a `use`", - if vis_span.is_empty() { "use " } else { " use " }, - Applicability::MachineApplicable, - ); - } - BuiltinLintDiagnostics::AmbiguousGlobImports { diag } => { - rustc_errors::report_ambiguity_error(db, diag); - } - BuiltinLintDiagnostics::AmbiguousGlobReexports { name, namespace, first_reexport_span, duplicate_reexport_span } => { - db.span_label(first_reexport_span, format!("the name `{name}` in the {namespace} namespace is first re-exported here")); - db.span_label(duplicate_reexport_span, format!("but the name `{name}` in the {namespace} namespace is also re-exported here")); - } - BuiltinLintDiagnostics::HiddenGlobReexports { name, namespace, glob_reexport_span, private_item_span } => { - db.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); - db.span_note(private_item_span, "but the private item here shadows it".to_owned()); - } - BuiltinLintDiagnostics::UnusedQualifications { removal_span } => { - db.span_suggestion_verbose( - removal_span, - "remove the unnecessary path segments", - "", - Applicability::MachineApplicable - ); - } - BuiltinLintDiagnostics::AssociatedConstElidedLifetime { elided, span } => { - db.span_suggestion_verbose( - if elided { span.shrink_to_hi() } else { span }, - "use the `'static` lifetime", - if elided { "'static " } else { "'static" }, - Applicability::MachineApplicable - ); - }, - BuiltinLintDiagnostics::RedundantImportVisibility { max_vis, span } => { - db.span_note(span, format!("the most public imported item is `{max_vis}`")); - db.help("reduce the glob import's visibility or increase visibility of imported items"); - } - } + diagnostics::builtin(self.sess(), diagnostic, db); // Rewrap `db`, and pass control to the user. decorate(db) }); @@ -981,62 +553,62 @@ pub trait LintContext { // set the span in their `decorate` function (preferably using set_span). /// Emit a lint at the appropriate level, with an optional associated span. /// - /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature #[rustc_lint_diagnostics] - fn lookup<S: Into<MultiSpan>>( + fn opt_span_lint<S: Into<MultiSpan>>( &self, lint: &'static Lint, span: Option<S>, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ); - /// Emit a lint at `span` from a lint struct (some type that implements `DecorateLint`, + /// Emit a lint at `span` from a lint struct (some type that implements `LintDiagnostic`, /// typically generated by `#[derive(LintDiagnostic)]`). - fn emit_spanned_lint<S: Into<MultiSpan>>( + fn emit_span_lint<S: Into<MultiSpan>>( &self, lint: &'static Lint, span: S, - decorator: impl for<'a> DecorateLint<'a, ()>, + decorator: impl for<'a> LintDiagnostic<'a, ()>, ) { - self.lookup(lint, Some(span), decorator.msg(), |diag| { + self.opt_span_lint(lint, Some(span), decorator.msg(), |diag| { decorator.decorate_lint(diag); }); } /// Emit a lint at the appropriate level, with an associated span. /// - /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature #[rustc_lint_diagnostics] - fn struct_span_lint<S: Into<MultiSpan>>( + fn span_lint<S: Into<MultiSpan>>( &self, lint: &'static Lint, span: S, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { - self.lookup(lint, Some(span), msg, decorate); + self.opt_span_lint(lint, Some(span), msg, decorate); } - /// Emit a lint from a lint struct (some type that implements `DecorateLint`, typically + /// 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> DecorateLint<'a, ()>) { - self.lookup(lint, None as Option<Span>, decorator.msg(), |diag| { + fn emit_lint(&self, lint: &'static Lint, decorator: impl for<'a> LintDiagnostic<'a, ()>) { + self.opt_span_lint(lint, None as Option<Span>, decorator.msg(), |diag| { decorator.decorate_lint(diag); }); } /// Emit a lint at the appropriate level, with no associated span. /// - /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature #[rustc_lint_diagnostics] fn lint( &self, lint: &'static Lint, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { - self.lookup(lint, None as Option<Span>, msg, decorate); + self.opt_span_lint(lint, None as Option<Span>, msg, decorate); } /// This returns the lint level for the given lint at the current location. @@ -1049,12 +621,13 @@ pub trait LintContext { /// 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. - #[rustc_lint_diagnostics] fn fulfill_expectation(&self, expectation: LintExpectationId) { // We need to make sure that submitted expectation ids are correctly fulfilled suppressed // and stored between compilation sessions. To not manually do these steps, we simply create - // a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal - // expected lint diagnostic. + // 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( @@ -1069,7 +642,7 @@ impl<'a> EarlyContext<'a> { pub(crate) fn new( sess: &'a Session, features: &'a Features, - warn_about_weird_lints: bool, + lint_added_lints: bool, lint_store: &'a LintStore, registered_tools: &'a RegisteredTools, buffered: LintBuffer, @@ -1078,7 +651,7 @@ impl<'a> EarlyContext<'a> { builder: LintLevelsBuilder::new( sess, features, - warn_about_weird_lints, + lint_added_lints, lint_store, registered_tools, ), @@ -1094,18 +667,18 @@ impl<'tcx> LintContext for LateContext<'tcx> { } #[rustc_lint_diagnostics] - fn lookup<S: Into<MultiSpan>>( + fn opt_span_lint<S: Into<MultiSpan>>( &self, lint: &'static Lint, span: Option<S>, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + 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.struct_span_lint_hir(lint, hir_id, s, msg, decorate), - None => self.tcx.struct_lint_node(lint, hir_id, msg, decorate), + Some(s) => self.tcx.node_span_lint(lint, hir_id, s, msg, decorate), + None => self.tcx.node_lint(lint, hir_id, msg, decorate), } } @@ -1121,14 +694,14 @@ impl LintContext for EarlyContext<'_> { } #[rustc_lint_diagnostics] - fn lookup<S: Into<MultiSpan>>( + fn opt_span_lint<S: Into<MultiSpan>>( &self, lint: &'static Lint, span: Option<S>, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { - self.builder.struct_lint(lint, span.map(|s| s.into()), msg, decorate) + self.builder.opt_span_lint(lint, span.map(|s| s.into()), msg, decorate) } fn get_lint_level(&self, lint: &'static Lint) -> Level { @@ -1359,7 +932,7 @@ impl<'tcx> LateContext<'tcx> { 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) => self.tcx.hir().find_parent(hir_id), + Res::Local(hir_id) => Some(self.tcx.parent_hir_node(hir_id)), _ => None, } && let Some(init) = match parent_node { @@ -1403,7 +976,7 @@ impl<'tcx> LateContext<'tcx> { 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) => self.tcx.hir().find_parent(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, } diff --git a/compiler/rustc_lint/src/context/diagnostics.rs b/compiler/rustc_lint/src/context/diagnostics.rs new file mode 100644 index 00000000000..a0be1c09c9a --- /dev/null +++ b/compiler/rustc_lint/src/context/diagnostics.rs @@ -0,0 +1,621 @@ +#![allow(rustc::diagnostic_outside_of_impl)] +#![allow(rustc::untranslatable_diagnostic)] + +use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; +use rustc_errors::{add_elided_lifetime_in_path_suggestion, Diag}; +use rustc_errors::{Applicability, SuggestionStyle}; +use rustc_middle::middle::stability; +use rustc_session::config::ExpectedValues; +use rustc_session::lint::BuiltinLintDiag; +use rustc_session::Session; +use rustc_span::edit_distance::find_best_match_for_name; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::BytePos; + +const MAX_CHECK_CFG_NAMES_OR_VALUES: usize = 35; + +fn check_cfg_expected_note( + sess: &Session, + possibilities: &[Symbol], + type_: &str, + name: Option<Symbol>, + suffix: &str, +) -> String { + use std::fmt::Write; + + 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) + }; + + let mut possibilities = possibilities.iter().map(Symbol::as_str).collect::<Vec<_>>(); + possibilities.sort(); + + let and_more = possibilities.len().saturating_sub(n_possibilities); + let possibilities = possibilities[..n_possibilities].join("`, `"); + + let mut note = String::with_capacity(50 + possibilities.len()); + + write!(&mut note, "expected {type_}").unwrap(); + if let Some(name) = name { + write!(&mut note, " for `{name}`").unwrap(); + } + write!(&mut note, " are: {suffix}`{possibilities}`").unwrap(); + if and_more > 0 { + write!(&mut note, " and {and_more} more").unwrap(); + } + + note +} + +pub(super) fn builtin(sess: &Session, diagnostic: BuiltinLintDiag, diag: &mut Diag<'_, ()>) { + match diagnostic { + BuiltinLintDiag::UnicodeTextFlow(span, content) => { + let spans: Vec<_> = content + .char_indices() + .filter_map(|(i, c)| { + TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { + let lo = span.lo() + BytePos(2 + i as u32); + (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) + }) + }) + .collect(); + let (an, s) = match spans.len() { + 1 => ("an ", ""), + _ => ("", "s"), + }; + diag.span_label( + span, + format!( + "this comment contains {an}invisible unicode text flow control codepoint{s}", + ), + ); + for (c, span) in &spans { + diag.span_label(*span, format!("{c:?}")); + } + diag.note( + "these kind of unicode codepoints change the way text flows on \ + applications that support them, but can cause confusion because they \ + change the order of characters on the screen", + ); + if !spans.is_empty() { + diag.multipart_suggestion_with_style( + "if their presence wasn't intentional, you can remove them", + spans.into_iter().map(|(_, span)| (span, "".to_string())).collect(), + Applicability::MachineApplicable, + SuggestionStyle::HideCodeAlways, + ); + } + } + BuiltinLintDiag::Normal => (), + BuiltinLintDiag::AbsPathWithModule(span) => { + let (sugg, app) = match sess.source_map().span_to_snippet(span) { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::<path>".to_string(), Applicability::HasPlaceholders), + }; + diag.span_suggestion(span, "use `crate`", sugg, app); + } + BuiltinLintDiag::ProcMacroDeriveResolutionFallback(span) => { + diag.span_label( + span, + "names from parent modules are not accessible without an explicit import", + ); + } + BuiltinLintDiag::MacroExpandedMacroExportsAccessedByAbsolutePaths(span_def) => { + diag.span_note(span_def, "the macro is defined here"); + } + BuiltinLintDiag::ElidedLifetimesInPaths(n, path_span, incl_angl_brckt, insertion_span) => { + add_elided_lifetime_in_path_suggestion( + sess.source_map(), + diag, + n, + path_span, + incl_angl_brckt, + insertion_span, + ); + } + BuiltinLintDiag::UnknownCrateTypes(span, note, sugg) => { + diag.span_suggestion(span, note, sugg, Applicability::MaybeIncorrect); + } + BuiltinLintDiag::UnusedImports(message, replaces, in_test_module) => { + if !replaces.is_empty() { + diag.tool_only_multipart_suggestion( + message, + replaces, + Applicability::MachineApplicable, + ); + } + + if let Some(span) = in_test_module { + diag.span_help( + sess.source_map().guess_head_span(span), + "if this is a test module, consider adding a `#[cfg(test)]` to the containing module", + ); + } + } + BuiltinLintDiag::RedundantImport(spans, ident) => { + for (span, is_imported) in spans { + let introduced = if is_imported { "imported" } else { "defined" }; + let span_msg = if span.is_dummy() { "by prelude" } else { "here" }; + diag.span_label( + span, + format!("the item `{ident}` is already {introduced} {span_msg}"), + ); + } + } + BuiltinLintDiag::DeprecatedMacro(suggestion, span) => { + stability::deprecation_suggestion(diag, "macro", suggestion, span) + } + BuiltinLintDiag::UnusedDocComment(span) => { + diag.span_label(span, "rustdoc does not generate documentation for macro invocations"); + diag.help("to document an item produced by a macro, \ + the macro must produce the documentation as part of its expansion"); + } + BuiltinLintDiag::PatternsInFnsWithoutBody(span, ident) => { + diag.span_suggestion( + span, + "remove `mut` from the parameter", + ident, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::MissingAbi(span, default_abi) => { + diag.span_label(span, "ABI should be specified here"); + diag.help(format!("the default ABI is {}", default_abi.name())); + } + BuiltinLintDiag::LegacyDeriveHelpers(span) => { + diag.span_label(span, "the attribute is introduced here"); + } + BuiltinLintDiag::ProcMacroBackCompat(note) => { + diag.note(note); + } + BuiltinLintDiag::OrPatternsBackCompat(span, suggestion) => { + diag.span_suggestion( + span, + "use pat_param to preserve semantics", + suggestion, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::ReservedPrefix(span) => { + diag.span_label(span, "unknown prefix"); + diag.span_suggestion_verbose( + span.shrink_to_hi(), + "insert whitespace here to avoid this being parsed as a prefix in Rust 2021", + " ", + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => { + diag.span_note( + invoc_span, + format!("the built-in attribute `{attr_name}` will be ignored, since it's applied to the macro invocation `{macro_name}`") + ); + } + BuiltinLintDiag::TrailingMacro(is_trailing, name) => { + if is_trailing { + diag.note("macro invocations at the end of a block are treated as expressions"); + diag.note(format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`")); + } + } + BuiltinLintDiag::BreakWithLabelAndLoop(span) => { + diag.multipart_suggestion( + "wrap this expression in parentheses", + vec![ + (span.shrink_to_lo(), "(".to_string()), + (span.shrink_to_hi(), ")".to_string()), + ], + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::NamedAsmLabel(help) => { + diag.help(help); + diag.note("see the asm section of Rust By Example <https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html#labels> for more information"); + } + BuiltinLintDiag::UnexpectedCfgName((name, name_span), value) => { + #[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; + + if is_feature_cfg && is_from_cargo { + diag.help("consider defining some features in `Cargo.toml`"); + // Suggest the most probable if we found one + } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { + 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().map(Symbol::as_str).collect::<Vec<_>>(); + possibilities.sort(); + + let mut should_print_possibilities = true; + if let Some((value, value_span)) = value { + if best_match_values.contains(&Some(value)) { + diag.span_suggestion( + name_span, + "there is a config with a similar name and value", + best_match, + Applicability::MaybeIncorrect, + ); + should_print_possibilities = false; + } else if best_match_values.contains(&None) { + diag.span_suggestion( + name_span.to(value_span), + "there is a config with a similar name and no value", + best_match, + Applicability::MaybeIncorrect, + ); + should_print_possibilities = false; + } else if let Some(first_value) = possibilities.first() { + diag.span_suggestion( + name_span.to(value_span), + "there is a config with a similar name and different values", + format!("{best_match} = \"{first_value}\""), + Applicability::MaybeIncorrect, + ); + } else { + diag.span_suggestion( + name_span.to(value_span), + "there is a config with a similar name and different values", + best_match, + Applicability::MaybeIncorrect, + ); + }; + } else { + diag.span_suggestion( + name_span, + "there is a config with a similar name", + best_match, + Applicability::MaybeIncorrect, + ); + } + + if !possibilities.is_empty() && should_print_possibilities { + let possibilities = possibilities.join("`, `"); + diag.help(format!( + "expected values for `{best_match}` are: `{possibilities}`" + )); + } + } else { + diag.span_suggestion( + name_span, + "there is a config with a similar name", + best_match, + Applicability::MaybeIncorrect, + ); + } + + is_feature_cfg |= best_match == sym::feature; + } else { + if !names_possibilities.is_empty() && names_possibilities.len() <= 3 { + names_possibilities.sort(); + for cfg_name in names_possibilities.iter() { + diag.span_suggestion( + name_span, + "found config with similar value", + format!("{cfg_name} = \"{name}\""), + Applicability::MaybeIncorrect, + ); + } + } + if !possibilities.is_empty() { + diag.help_once(check_cfg_expected_note( + sess, + &possibilities, + "names", + None, + "", + )); + } + } + + let inst = if let Some((value, _value_span)) = value { + let pre = if is_from_cargo { "\\" } else { "" }; + format!("cfg({name}, values({pre}\"{value}{pre}\"))") + } else { + format!("cfg({name})") + }; + + if is_from_cargo { + if !is_feature_cfg { + diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); + } + diag.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration"); + } else { + diag.help(format!("to expect this configuration use `--check-cfg={inst}`")); + diag.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration"); + } + } + BuiltinLintDiag::UnexpectedCfgValue((name, name_span), value) => { + 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 + if !possibilities.is_empty() { + diag.note(check_cfg_expected_note( + sess, + &possibilities, + "values", + Some(name), + if have_none_possibility { "(none), " } else { "" }, + )); + + 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) + { + diag.span_suggestion( + value_span, + "there is a expected value with a similar name", + format!("\"{best_match}\""), + Applicability::MaybeIncorrect, + ); + } + } else if let &[first_possibility] = &possibilities[..] { + diag.span_suggestion( + name_span.shrink_to_hi(), + "specify a config value", + format!(" = \"{first_possibility}\""), + Applicability::MaybeIncorrect, + ); + } + } else if have_none_possibility { + diag.note(format!("no expected value for `{name}`")); + if let Some((_value, value_span)) = value { + diag.span_suggestion( + name_span.shrink_to_hi().to(value_span), + "remove the value", + "", + Applicability::MaybeIncorrect, + ); + } + } else { + diag.note(format!("no expected values for `{name}`")); + + let sp = if let Some((_value, value_span)) = value { + name_span.to(value_span) + } else { + name_span + }; + diag.span_suggestion(sp, "remove the condition", "", Applicability::MaybeIncorrect); + } + + // We don't want to suggest adding values to well known names + // since those are defined by rustc it-self. Users can still + // do it if they want, but should not encourage them. + let is_cfg_a_well_know_name = sess.psess.check_config.well_known_names.contains(&name); + + let inst = if let Some((value, _value_span)) = value { + let pre = if is_from_cargo { "\\" } else { "" }; + format!("cfg({name}, values({pre}\"{value}{pre}\"))") + } else { + format!("cfg({name})") + }; + + if is_from_cargo { + if name == sym::feature { + if let Some((value, _value_span)) = value { + diag.help(format!( + "consider adding `{value}` as a feature in `Cargo.toml`" + )); + } else { + diag.help("consider defining some features in `Cargo.toml`"); + } + } else if !is_cfg_a_well_know_name { + diag.help(format!("consider using a Cargo feature instead or adding `println!(\"cargo:rustc-check-cfg={inst}\");` to the top of a `build.rs`")); + } + diag.note("see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration"); + } else { + if !is_cfg_a_well_know_name { + diag.help(format!("to expect this configuration use `--check-cfg={inst}`")); + } + diag.note("see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration"); + } + } + BuiltinLintDiag::DeprecatedWhereclauseLocation(sugg) => { + let left_sp = diag.span.primary_span().unwrap(); + match sugg { + Some((right_sp, sugg)) => diag.multipart_suggestion( + "move it to the end of the type declaration", + vec![(left_sp, String::new()), (right_sp, sugg)], + Applicability::MachineApplicable, + ), + None => diag.span_suggestion( + left_sp, + "remove this `where`", + "", + Applicability::MachineApplicable, + ), + }; + diag.note("see issue #89122 <https://github.com/rust-lang/rust/issues/89122> for more information"); + } + BuiltinLintDiag::SingleUseLifetime { + param_span, + use_span: Some((use_span, elide)), + deletion_span, + } => { + debug!(?param_span, ?use_span, ?deletion_span); + diag.span_label(param_span, "this lifetime..."); + diag.span_label(use_span, "...is used only here"); + if let Some(deletion_span) = deletion_span { + let msg = "elide the single-use lifetime"; + let (use_span, replace_lt) = if elide { + let use_span = sess + .source_map() + .span_extend_while(use_span, char::is_whitespace) + .unwrap_or(use_span); + (use_span, String::new()) + } else { + (use_span, "'_".to_owned()) + }; + 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 suggestions = if deletion_span.is_empty() { + vec![(use_span, replace_lt)] + } else { + vec![(deletion_span, String::new()), (use_span, replace_lt)] + }; + diag.multipart_suggestion(msg, suggestions, Applicability::MachineApplicable); + } + } + BuiltinLintDiag::SingleUseLifetime { param_span: _, use_span: None, deletion_span } => { + debug!(?deletion_span); + if let Some(deletion_span) = deletion_span { + diag.span_suggestion( + deletion_span, + "elide the unused lifetime", + "", + Applicability::MachineApplicable, + ); + } + } + BuiltinLintDiag::NamedArgumentUsedPositionally { + position_sp_to_replace, + position_sp_for_msg, + named_arg_sp, + named_arg_name, + is_formatting_arg, + } => { + diag.span_label( + named_arg_sp, + "this named argument is referred to by position in formatting string", + ); + if let Some(positional_arg_for_msg) = position_sp_for_msg { + let msg = format!( + "this formatting argument uses named argument `{named_arg_name}` by position" + ); + diag.span_label(positional_arg_for_msg, msg); + } + + if let Some(positional_arg_to_replace) = position_sp_to_replace { + let name = if is_formatting_arg { named_arg_name + "$" } else { named_arg_name }; + 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 + }; + diag.span_suggestion_verbose( + span_to_replace, + "use the named argument by name to avoid ambiguity", + name, + Applicability::MaybeIncorrect, + ); + } + } + BuiltinLintDiag::ByteSliceInPackedStructWithDerive => { + diag.help("consider implementing the trait by hand, or remove the `packed` attribute"); + } + BuiltinLintDiag::UnusedExternCrate { removal_span } => { + diag.span_suggestion(removal_span, "remove it", "", Applicability::MachineApplicable); + } + BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => { + let suggestion_span = vis_span.between(ident_span); + diag.span_suggestion_verbose( + suggestion_span, + "convert it to a `use`", + if vis_span.is_empty() { "use " } else { " use " }, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::AmbiguousGlobImports { diag: ambiguity } => { + rustc_errors::report_ambiguity_error(diag, ambiguity); + } + BuiltinLintDiag::AmbiguousGlobReexports { + name, + namespace, + first_reexport_span, + duplicate_reexport_span, + } => { + diag.span_label( + first_reexport_span, + format!("the name `{name}` in the {namespace} namespace is first re-exported here"), + ); + diag.span_label( + duplicate_reexport_span, + format!( + "but the name `{name}` in the {namespace} namespace is also re-exported here" + ), + ); + } + BuiltinLintDiag::HiddenGlobReexports { + name, + namespace, + glob_reexport_span, + private_item_span, + } => { + diag.span_note(glob_reexport_span, format!("the name `{name}` in the {namespace} namespace is supposed to be publicly re-exported here")); + diag.span_note(private_item_span, "but the private item here shadows it".to_owned()); + } + BuiltinLintDiag::UnusedQualifications { removal_span } => { + diag.span_suggestion_verbose( + removal_span, + "remove the unnecessary path segments", + "", + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::AssociatedConstElidedLifetime { elided, span } => { + diag.span_suggestion_verbose( + if elided { span.shrink_to_hi() } else { span }, + "use the `'static` lifetime", + if elided { "'static " } else { "'static" }, + Applicability::MachineApplicable, + ); + } + BuiltinLintDiag::RedundantImportVisibility { max_vis, span } => { + diag.span_note(span, format!("the most public imported item is `{max_vis}`")); + diag.help( + "reduce the glob import's visibility or increase visibility of imported items", + ); + } + } +} diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs index 98bafc0f263..9bc81d2c46a 100644 --- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -5,6 +5,7 @@ use crate::{ use rustc_hir as hir; use rustc_middle::ty; +use rustc_session::lint::FutureIncompatibilityReason; use rustc_span::sym; use rustc_trait_selection::traits::supertraits; @@ -12,6 +13,9 @@ 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 @@ -40,10 +44,15 @@ declare_lint! { /// /// ### Explanation /// - /// The implicit dyn upcasting coercion take priority over those `Deref` impls. + /// 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 are shadow by implicit coercion", + "`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]); @@ -78,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { .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_spanned_lint( + cx.emit_span_lint( DEREF_INTO_DYN_SUPERTRAIT, span, SupertraitAsDerefTarget { diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs index 390a1620a2a..78ac7f9b235 100644 --- a/compiler/rustc_lint/src/drop_forget_useless.rs +++ b/compiler/rustc_lint/src/drop_forget_useless.rs @@ -149,28 +149,28 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { let drop_is_single_call_in_arm = is_single_call_in_arm(cx, arg, expr); match fn_name { sym::mem_drop if arg_ty.is_ref() && !drop_is_single_call_in_arm => { - cx.emit_spanned_lint( + cx.emit_span_lint( DROPPING_REFERENCES, expr.span, DropRefDiag { arg_ty, label: arg.span }, ); } sym::mem_forget if arg_ty.is_ref() => { - cx.emit_spanned_lint( + cx.emit_span_lint( FORGETTING_REFERENCES, expr.span, ForgetRefDiag { arg_ty, label: arg.span }, ); } sym::mem_drop if is_copy && !drop_is_single_call_in_arm => { - cx.emit_spanned_lint( + cx.emit_span_lint( DROPPING_COPY_TYPES, expr.span, DropCopyDiag { arg_ty, label: arg.span }, ); } sym::mem_forget if is_copy => { - cx.emit_spanned_lint( + cx.emit_span_lint( FORGETTING_COPY_TYPES, expr.span, ForgetCopyDiag { arg_ty, label: arg.span }, @@ -180,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { if let ty::Adt(adt, _) = arg_ty.kind() && adt.is_manually_drop() => { - cx.emit_spanned_lint( + cx.emit_span_lint( UNDROPPED_MANUALLY_DROPS, expr.span, UndroppedManuallyDropsDiag { @@ -214,8 +214,7 @@ fn is_single_call_in_arm<'tcx>( drop_expr: &'tcx Expr<'_>, ) -> bool { if arg.can_have_side_effects() { - let parent_node = cx.tcx.hir().find_parent(drop_expr.hir_id); - if let Some(Node::Arm(Arm { body, .. })) = &parent_node { + if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) { return body.hir_id == drop_expr.hir_id; } } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 70af6572246..d78ec8c0dd3 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -17,8 +17,8 @@ use crate::context::{EarlyContext, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; use rustc_ast::ptr::P; -use rustc_ast::visit::{self as ast_visit, Visitor}; -use rustc_ast::{self as ast, walk_list, HasAttrs}; +use rustc_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; @@ -45,7 +45,13 @@ impl<'a, T: EarlyLintPass> EarlyContextAndPass<'a, T> { fn inlined_check_id(&mut self, id: ast::NodeId) { for early_lint in self.context.buffered.take(id) { let BufferedEarlyLint { span, msg, node_id: _, lint_id, diagnostic } = early_lint; - self.context.lookup_with_diagnostics(lint_id.lint, Some(span), msg, |_| {}, diagnostic); + self.context.span_lint_with_diagnostics( + lint_id.lint, + Some(span), + msg, + |_| {}, + diagnostic, + ); } } @@ -425,14 +431,13 @@ pub fn check_ast_node_inner<'a, T: EarlyLintPass>( // 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 { - for early_lint in lints { - sess.dcx().span_delayed_bug( - early_lint.span, - format!( - "failed to process buffered lint here (dummy = {})", - id == ast::DUMMY_NODE_ID - ), + 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 index 05fe64830d1..a67f1b62fb0 100644 --- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -53,7 +53,7 @@ fn enforce_mem_discriminant( ) { let ty_param = cx.typeck_results().node_args(func_expr.hir_id).type_at(0); if is_non_enum(ty_param) { - cx.emit_spanned_lint( + cx.emit_span_lint( ENUM_INTRINSICS_NON_ENUMS, expr_span, EnumIntrinsicsMemDiscriminate { ty_param, note: args_span }, @@ -64,11 +64,7 @@ fn enforce_mem_discriminant( 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_spanned_lint( - ENUM_INTRINSICS_NON_ENUMS, - span, - EnumIntrinsicsMemVariant { ty_param }, - ); + cx.emit_span_lint(ENUM_INTRINSICS_NON_ENUMS, span, EnumIntrinsicsMemVariant { ty_param }); } } diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index eccea35c702..ee99e824a54 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -1,11 +1,11 @@ use crate::fluent_generated as fluent; -use rustc_errors::{AddToDiagnostic, Diagnostic, SubdiagnosticMessage}; +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")] +#[diag(lint_overruled_attribute, code = E0453)] pub struct OverruledAttribute<'a> { #[primary_span] pub span: Span, @@ -23,15 +23,16 @@ pub enum OverruledAttributeSub { CommandLineSource, } -impl AddToDiagnostic for OverruledAttributeSub { - fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F) - where - F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage, - { +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.set_arg("id", id); + diag.arg("id", id); } OverruledAttributeSub::NodeSource { span, reason } => { diag.span_label(span, fluent::lint_node_source); @@ -48,7 +49,7 @@ impl AddToDiagnostic for OverruledAttributeSub { } #[derive(Diagnostic)] -#[diag(lint_malformed_attribute, code = "E0452")] +#[diag(lint_malformed_attribute, code = E0452)] pub struct MalformedAttribute { #[primary_span] pub span: Span, @@ -67,7 +68,7 @@ pub enum MalformedAttributeSub { } #[derive(Diagnostic)] -#[diag(lint_unknown_tool_in_scoped_lint, code = "E0710")] +#[diag(lint_unknown_tool_in_scoped_lint, code = E0710)] pub struct UnknownToolInScopedLint { #[primary_span] pub span: Option<Span>, @@ -78,7 +79,7 @@ pub struct UnknownToolInScopedLint { } #[derive(Diagnostic)] -#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = "E0783")] +#[diag(lint_builtin_ellipsis_inclusive_range_patterns, code = E0783)] pub struct BuiltinEllipsisInclusiveRangePatterns { #[primary_span] pub span: Span, @@ -95,13 +96,13 @@ pub struct RequestedLevel<'a> { } #[derive(Diagnostic)] -#[diag(lint_unsupported_group, code = "E0602")] +#[diag(lint_unsupported_group, code = E0602)] pub struct UnsupportedGroup { pub lint_group: String, } #[derive(Diagnostic)] -#[diag(lint_check_name_unknown_tool, code = "E0602")] +#[diag(lint_check_name_unknown_tool, code = E0602)] pub struct CheckNameUnknownTool<'a> { pub tool_name: Symbol, #[subdiagnostic] diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 2b6290e8a00..40db765da53 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -29,7 +29,7 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) { { let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale }); let note = expectation.is_unfulfilled_lint_expectations.then_some(()); - tcx.emit_spanned_lint( + tcx.emit_node_span_lint( UNFULFILLED_LINT_EXPECTATIONS, *hir_id, expectation.emission_span, diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs index ea922785a95..cb7feea16b5 100644 --- a/compiler/rustc_lint/src/for_loops_over_fallibles.rs +++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { end_span: pat.span.between(arg.span), }; - cx.emit_spanned_lint( + cx.emit_span_lint( FOR_LOOPS_OVER_FALLIBLES, arg.span, ForLoopsOverFalliblesDiag { article, ty, sub, question_mark, suggestion }, diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs index 31d9c0d33fe..b995f38f23c 100644 --- a/compiler/rustc_lint/src/foreign_modules.rs +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -1,5 +1,5 @@ -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; 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; @@ -72,7 +72,7 @@ struct ClashingExternDeclarations { /// the symbol should be reported as a clashing declaration. // FIXME: Technically, we could just store a &'tcx str here without issue; however, the // `impl_lint_pass` macro doesn't currently support lints parametric over a lifetime. - seen_decls: FxHashMap<Symbol, hir::OwnerId>, + seen_decls: UnordMap<Symbol, hir::OwnerId>, } /// Differentiate between whether the name for an extern decl came from the link_name attribute or @@ -96,7 +96,7 @@ impl SymbolName { impl ClashingExternDeclarations { pub(crate) fn new() -> Self { - ClashingExternDeclarations { seen_decls: FxHashMap::default() } + ClashingExternDeclarations { seen_decls: Default::default() } } /// Insert a new foreign item into the seen set. If a symbol with the same name already exists @@ -161,7 +161,7 @@ impl ClashingExternDeclarations { sub, } }; - tcx.emit_spanned_lint( + tcx.emit_node_span_lint( CLASHING_EXTERN_DECLARATIONS, this_fi.hir_id(), mismatch_label, @@ -209,12 +209,12 @@ fn structurally_same_type<'tcx>( b: Ty<'tcx>, ckind: types::CItemKind, ) -> bool { - let mut seen_types = FxHashSet::default(); + 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 FxHashSet<(Ty<'tcx>, Ty<'tcx>)>, + seen_types: &mut UnordSet<(Ty<'tcx>, Ty<'tcx>)>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, a: Ty<'tcx>, diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs index 7c1af6bee1d..054cfe92a3a 100644 --- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs +++ b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs @@ -74,7 +74,7 @@ impl HiddenUnicodeCodepoints { HiddenUnicodeCodepointsDiagSub::NoEscape { spans } }; - cx.emit_spanned_lint( + cx.emit_span_lint( TEXT_DIRECTION_CODEPOINT_IN_LITERAL, span, HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub }, diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 53d99c7f7f3..153d91ce28c 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -4,14 +4,13 @@ use crate::lints::{ BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword, QueryInstability, SpanUseEqCtxtDiag, TyQualified, TykindDiag, TykindKind, UntranslatableDiag, - UntranslatableDiagnosticTrivial, }; 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; +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}; @@ -43,7 +42,7 @@ impl LateLintPass<'_> for DefaultHashTypes { Some(sym::HashSet) => "FxHashSet", _ => return, }; - cx.emit_spanned_lint( + cx.emit_span_lint( DEFAULT_HASH_TYPES, path.span, DefaultHashTypesDiag { preferred, used: cx.tcx.item_name(def_id) }, @@ -91,7 +90,7 @@ impl LateLintPass<'_> for QueryStability { if let Ok(Some(instance)) = ty::Instance::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_spanned_lint( + cx.emit_span_lint( POTENTIAL_QUERY_INSTABILITY, span, QueryInstability { query: cx.tcx.item_name(def_id) }, @@ -136,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { { let span = path.span.with_hi(segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()); - cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span }); + cx.emit_span_lint(USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span }); } } @@ -144,15 +143,14 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { match &ty.kind { TyKind::Path(QPath::Resolved(_, path)) => { if lint_ty_kind_usage(cx, &path.res) { - let hir = cx.tcx.hir(); - let span = match hir.find_parent(ty.hir_id) { - Some(Node::Pat(Pat { + 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 { @@ -161,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { None } } - Some(Node::Expr(Expr { kind: ExprKind::Path(qpath), .. })) => { + Node::Expr(Expr { kind: ExprKind::Path(qpath), .. }) => { if let QPath::TypeRelative(qpath_ty, ..) = qpath && qpath_ty.hir_id == ty.hir_id { @@ -172,7 +170,7 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { } // Can't unify these two branches because qpath below is `&&` and above is `&` // and `A | B` paths don't play well together with adjustments, apparently. - Some(Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. })) => { + Node::Expr(Expr { kind: ExprKind::Struct(qpath, ..), .. }) => { if let QPath::TypeRelative(qpath_ty, ..) = qpath && qpath_ty.hir_id == ty.hir_id { @@ -186,19 +184,19 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { match span { Some(span) => { - cx.emit_spanned_lint( + cx.emit_span_lint( USAGE_OF_TY_TYKIND, path.span, TykindKind { suggestion: span }, ); } - None => cx.emit_spanned_lint(USAGE_OF_TY_TYKIND, path.span, TykindDiag), + 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_spanned_lint( + cx.emit_span_lint( USAGE_OF_QUALIFIED_TY, path.span, TyQualified { ty, suggestion: path.span }, @@ -285,7 +283,7 @@ impl EarlyLintPass for LintPassImpl { && call_site.ctxt().outer_expn_data().kind != ExpnKind::Macro(MacroKind::Bang, sym::declare_lint_pass) { - cx.emit_spanned_lint( + cx.emit_span_lint( LINT_PASS_IMPL_WITHOUT_MACRO, lint_pass.path.span, LintPassByHand, @@ -327,7 +325,7 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword { if is_doc_keyword(keyword) { return; } - cx.emit_spanned_lint( + cx.emit_span_lint( EXISTING_DOC_KEYWORD, attr.span, NonExistentDocKeyword { keyword }, @@ -340,161 +338,151 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword { } declare_tool_lint! { - /// The `untranslatable_diagnostic` lint detects diagnostics created - /// without using translatable Fluent strings. + /// 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). + /// More details on translatable diagnostics can be found + /// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html). pub rustc::UNTRANSLATABLE_DIAGNOSTIC, - Allow, + Deny, "prevent creation of diagnostics which cannot be translated", report_in_external_macro: true } declare_tool_lint! { - /// The `diagnostic_outside_of_impl` lint detects diagnostics created manually, - /// and inside an `IntoDiagnostic`/`AddToDiagnostic` implementation, - /// or a `#[derive(Diagnostic)]`/`#[derive(Subdiagnostic)]` expansion. + /// 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). + /// 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, - Allow, - "prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls", - report_in_external_macro: true -} - -declare_tool_lint! { - /// The `untranslatable_diagnostic_trivial` lint detects diagnostics created using only static strings. - pub rustc::UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, Deny, - "prevent creation of diagnostics which cannot be translated, which use only static strings", + "prevent diagnostic creation outside of `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls", report_in_external_macro: true } -declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL, UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL ]); +declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]); impl LateLintPass<'_> for Diagnostics { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; - debug!(?span, ?def_id, ?args); - let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args) + // 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::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)); - if !has_attr { - return; - } - let mut found_parent_with_attr = false; - let mut found_impl = false; - for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) { - if let Some(owner_did) = hir_id.as_owner() { - found_parent_with_attr = found_parent_with_attr - || cx.tcx.has_attr(owner_did, 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 } + }; - 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::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint) - { - found_impl = true; - break; + // 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)); + } + } } } - debug!(?found_impl); - if !found_parent_with_attr && !found_impl { - cx.emit_spanned_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl); + + // Is the callee interesting? + if !has_attr && impl_into_diagnostic_message_params.is_empty() { + return; } - let mut found_diagnostic_message = false; - for ty in args.types() { - debug!(?ty); - if let Some(adt_def) = ty.ty_adt_def() - && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) - && matches!(name, sym::DiagnosticMessage | sym::SubdiagnosticMessage) + // 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) { - found_diagnostic_message = true; + parent_has_attr = true; break; } } - debug!(?found_diagnostic_message); - if !found_parent_with_attr && !found_diagnostic_message { - cx.emit_spanned_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag); - } - } -} -impl EarlyLintPass for Diagnostics { - #[allow(unused_must_use)] - fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { - // Looking for a straight chain of method calls from 'struct_span_err' to 'emit'. - let ast::StmtKind::Semi(expr) = &stmt.kind else { - return; - }; - let ast::ExprKind::MethodCall(meth) = &expr.kind else { - return; - }; - if meth.seg.ident.name != sym::emit || !meth.args.is_empty() { - return; - } - let mut segments = vec![]; - let mut cur = &meth.receiver; - let fake = &[].into(); - loop { - match &cur.kind { - ast::ExprKind::Call(func, args) => { - if let ast::ExprKind::Path(_, path) = &func.kind { - segments.push((path.segments.last().unwrap().ident.name, args)) - } - break; - } - ast::ExprKind::MethodCall(method) => { - segments.push((method.seg.ident.name, &method.args)); - cur = &method.receiver; - } - ast::ExprKind::MacCall(mac) => { - segments.push((mac.path.segments.last().unwrap().ident.name, fake)); - 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); + } } - segments.reverse(); - if segments.is_empty() { - return; - } - if segments[0].0.as_str() != "struct_span_err" { - return; - } - if !segments.iter().all(|(name, args)| { - let arg = match name.as_str() { - "struct_span_err" | "span_note" | "span_label" | "span_help" if args.len() == 2 => { - &args[1] - } - "note" | "help" if args.len() == 1 => &args[0], - _ => { - return false; - } - }; - if let ast::ExprKind::Lit(lit) = arg.kind - && let ast::token::LitKind::Str = lit.kind - { - true - } else { - false + + // 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); } - }) { - return; } - cx.emit_spanned_lint( - UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, - stmt.span, - UntranslatableDiagnosticTrivial, - ); } } @@ -507,7 +495,7 @@ declare_tool_lint! { report_in_external_macro: true } -declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]); +declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]); impl LateLintPass<'_> for BadOptAccess { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { @@ -528,7 +516,7 @@ impl LateLintPass<'_> for BadOptAccess { && let Some(lit) = item.lit() && let ast::LitKind::Str(val, _) = lit.kind { - cx.emit_spanned_lint( + cx.emit_span_lint( BAD_OPT_ACCESS, expr.span, BadOptAccessDiag { msg: val.as_str() }, @@ -549,9 +537,11 @@ 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, .. }, lhs, rhs) = expr.kind { + 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_spanned_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); + cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); } } } diff --git a/compiler/rustc_lint/src/invalid_from_utf8.rs b/compiler/rustc_lint/src/invalid_from_utf8.rs index 0b91b77a9f2..081e3e87530 100644 --- a/compiler/rustc_lint/src/invalid_from_utf8.rs +++ b/compiler/rustc_lint/src/invalid_from_utf8.rs @@ -78,7 +78,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 { let valid_up_to = utf8_error.valid_up_to(); let is_unchecked_variant = diag_item.as_str().contains("unchecked"); - cx.emit_spanned_lint( + cx.emit_span_lint( if is_unchecked_variant { INVALID_FROM_UTF8_UNCHECKED } else { @@ -111,7 +111,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidFromUtf8 { .map(|e| match &e.kind { ExprKind::Lit(Spanned { node: lit, .. }) => match lit { LitKind::Byte(b) => Some(*b), - LitKind::Int(b, _) => Some(*b as u8), + LitKind::Int(b, _) => Some(b.get() as u8), _ => None, }, _ => None, diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index caa01556591..506716e39a1 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -356,7 +356,7 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( 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.into()), + last_node_with_lint_attrs: tcx.local_def_id_to_hir_id(module_def_id), generics: None, only_module: true, }; @@ -364,14 +364,11 @@ pub fn late_lint_mod<'tcx, T: LateLintPass<'tcx> + 'tcx>( // 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 mut passes: Vec<_> = unerased_lint_store(tcx.sess) - .late_module_passes - .iter() - .map(|mk_pass| (mk_pass)(tcx)) - .collect(); - if passes.is_empty() { + 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); diff --git a/compiler/rustc_lint/src/let_underscore.rs b/compiler/rustc_lint/src/let_underscore.rs index 3eefd1b0e08..de642f373b5 100644 --- a/compiler/rustc_lint/src/let_underscore.rs +++ b/compiler/rustc_lint/src/let_underscore.rs @@ -5,7 +5,7 @@ use crate::{ use rustc_errors::MultiSpan; use rustc_hir as hir; use rustc_middle::ty; -use rustc_span::Symbol; +use rustc_span::{sym, Symbol}; declare_lint! { /// The `let_underscore_drop` lint checks for statements which don't bind @@ -104,47 +104,68 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [ ]; impl<'tcx> LateLintPass<'tcx> for LetUnderscore { + #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) { - if !matches!(local.pat.kind, hir::PatKind::Wild) { + if matches!(local.source, rustc_hir::LocalSource::AsyncFn) { return; } - if let Some(init) = local.init { - let init_ty = cx.typeck_results().expr_ty(init); + + 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 !init_ty.needs_drop(cx.tcx, cx.param_env) { + if !ty.needs_drop(cx.tcx, cx.param_env) { return; } - let is_sync_lock = match init_ty.kind() { + // 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: local.pat.span, - multi_suggestion_start: local.span.until(init.span), - multi_suggestion_end: init.span.shrink_to_hi(), + 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_spans(vec![local.pat.span, init.span]); + let mut span = MultiSpan::from_span(pat.span); span.push_span_label( - local.pat.span, + pat.span, "this lock is not assigned to a binding and is immediately dropped".to_string(), ); - span.push_span_label( - init.span, - "this binding will immediately drop the value assigned to it".to_string(), - ); - cx.emit_spanned_lint(LET_UNDERSCORE_LOCK, span, NonBindingLet::SyncLock { sub }); - } else { - cx.emit_spanned_lint( - LET_UNDERSCORE_DROP, - local.span, - NonBindingLet::DropType { sub }, - ); + 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 index 17c56f1ca58..95f312a31b3 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -15,8 +15,8 @@ use crate::{ }; use rustc_ast as ast; use rustc_ast_pretty::pprust; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DecorateLint, DiagnosticBuilder, DiagnosticMessage, MultiSpan}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::{Diag, DiagMessage, LintDiagnostic, MultiSpan}; use rustc_feature::{Features, GateIssue}; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; @@ -24,7 +24,7 @@ use rustc_hir::HirId; use rustc_index::IndexVec; use rustc_middle::hir::nested_filter; use rustc_middle::lint::{ - reveal_actual_level, struct_lint_level, LevelAndSource, LintExpectation, LintLevelSource, + lint_level, reveal_actual_level, LevelAndSource, LintExpectation, LintLevelSource, ShallowLintLevelMap, }; use rustc_middle::query::Providers; @@ -73,7 +73,7 @@ rustc_index::newtype_index! { struct LintSet { // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which // flag. - specs: FxHashMap<LintId, LevelAndSource>, + specs: FxIndexMap<LintId, LevelAndSource>, parent: LintStackIndex, } @@ -86,7 +86,7 @@ impl LintLevelSets { &self, lint: &'static Lint, idx: LintStackIndex, - aux: Option<&FxHashMap<LintId, LevelAndSource>>, + aux: Option<&FxIndexMap<LintId, LevelAndSource>>, sess: &Session, ) -> LevelAndSource { let lint = LintId::of(lint); @@ -101,13 +101,14 @@ impl LintLevelSets { &self, id: LintId, mut idx: LintStackIndex, - aux: Option<&FxHashMap<LintId, LevelAndSource>>, + aux: Option<&FxIndexMap<LintId, LevelAndSource>>, ) -> (Option<Level>, LintLevelSource) { - if let Some(specs) = aux { - if let Some(&(level, src)) = specs.get(&id) { - return (Some(level), src); - } + 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) { @@ -132,10 +133,10 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp cur: hir::CRATE_HIR_ID, specs: ShallowLintLevelMap::default(), expectations: Vec::new(), - unstable_to_stable_ids: FxHashMap::default(), - empty: FxHashMap::default(), + unstable_to_stable_ids: FxIndexMap::default(), + empty: FxIndexMap::default(), }, - warn_about_weird_lints: false, + lint_added_lints: false, store, registered_tools: tcx.registered_tools(()), }; @@ -161,10 +162,10 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe tcx, cur: owner.into(), specs: ShallowLintLevelMap::default(), - empty: FxHashMap::default(), + empty: FxIndexMap::default(), attrs, }, - warn_about_weird_lints: false, + lint_added_lints: false, store, registered_tools: tcx.registered_tools(()), }; @@ -177,11 +178,11 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe // 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: *local_id }), + &[(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(owner) { + _ => 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), @@ -190,6 +191,7 @@ fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLe levels.add_id(hir::CRATE_HIR_ID); levels.visit_mod(mod_, mod_.spans.inner_span, hir::CRATE_HIR_ID) } + hir::OwnerNode::AssocOpaqueTy(..) => unreachable!(), }, } @@ -209,14 +211,14 @@ pub struct TopDown { } pub trait LintLevelsProvider { - fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource>; + 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) -> &FxHashMap<LintId, LevelAndSource> { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { &self.sets.list[self.cur].specs } @@ -234,12 +236,12 @@ struct LintLevelQueryMap<'tcx> { cur: HirId, specs: ShallowLintLevelMap, /// Empty hash map to simplify code. - empty: FxHashMap<LintId, LevelAndSource>, + empty: FxIndexMap<LintId, LevelAndSource>, attrs: &'tcx hir::AttributeMap<'tcx>, } impl LintLevelsProvider for LintLevelQueryMap<'_> { - fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> { + 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) { @@ -257,13 +259,13 @@ struct QueryMapExpectationsWrapper<'tcx> { /// Level map for `cur`. specs: ShallowLintLevelMap, expectations: Vec<(LintExpectationId, LintExpectation)>, - unstable_to_stable_ids: FxHashMap<LintExpectationId, LintExpectationId>, + unstable_to_stable_ids: FxIndexMap<LintExpectationId, LintExpectationId>, /// Empty hash map to simplify code. - empty: FxHashMap<LintId, LevelAndSource>, + empty: FxIndexMap<LintId, LevelAndSource>, } impl LintLevelsProvider for QueryMapExpectationsWrapper<'_> { - fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> { + 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) { @@ -451,7 +453,7 @@ pub struct LintLevelsBuilder<'s, P> { sess: &'s Session, features: &'s Features, provider: P, - warn_about_weird_lints: bool, + lint_added_lints: bool, store: &'s LintStore, registered_tools: &'s RegisteredTools, } @@ -464,7 +466,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { pub(crate) fn new( sess: &'s Session, features: &'s Features, - warn_about_weird_lints: bool, + lint_added_lints: bool, store: &'s LintStore, registered_tools: &'s RegisteredTools, ) -> Self { @@ -472,7 +474,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { sess, features, provider: TopDown { sets: LintLevelSets::new(), cur: COMMAND_LINE }, - warn_about_weird_lints, + lint_added_lints, store, registered_tools, }; @@ -486,7 +488,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { .provider .sets .list - .push(LintSet { specs: FxHashMap::default(), parent: COMMAND_LINE }); + .push(LintSet { specs: FxIndexMap::default(), parent: COMMAND_LINE }); self.add_command_line(); } @@ -512,7 +514,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { ) -> BuilderPush { let prev = self.provider.cur; self.provider.cur = - self.provider.sets.list.push(LintSet { specs: FxHashMap::default(), parent: prev }); + self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); self.add(attrs, is_crate_node, source_hir_id); @@ -547,7 +549,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { self.features } - fn current_specs(&self) -> &FxHashMap<LintId, LevelAndSource> { + fn current_specs(&self) -> &FxIndexMap<LintId, LevelAndSource> { self.provider.current_specs() } @@ -582,8 +584,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } CheckLintNameResult::NoLint(suggestion) => { let name = lint_name.clone(); - let suggestion = - suggestion.map(|replace| UnknownLintSuggestion::WithoutSpan { replace }); + 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); @@ -642,63 +645,61 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { // // This means that this only errors if we're truly lowering the lint // level from forbid. - if level != Level::Forbid { - if let Level::Forbid = old_level { - // Backwards compatibility check: - // - // We used to not consider `forbid(lint_group)` - // as preventing `allow(lint)` for some lint `lint` in - // `lint_group`. For now, issue a future-compatibility - // warning for this case. - let id_name = id.lint.name_lower(); - let fcw_warning = match old_src { - LintLevelSource::Default => false, - LintLevelSource::Node { 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(), + 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, - }); - } else { - self.emit_spanned_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; - } + // 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; } } @@ -723,6 +724,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { }; } + #[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() { @@ -768,15 +770,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let Some(mut metas) = attr.meta_item_list() else { continue }; - if metas.is_empty() { + // 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; - let tail_li = &metas[metas.len() - 1]; if let Some(item) = tail_li.meta_item() { match item.kind { ast::MetaItemKind::Word => {} // actual lint names handled later @@ -785,7 +787,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { if let ast::LitKind::Str(rationale, _) = name_value.kind { if !self.features.lint_reasons { feature_err( - &self.sess.parse_sess, + &self.sess, sym::lint_reasons, item.span, "lint reasons are experimental", @@ -832,21 +834,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let meta_item = match li { ast::NestedMetaItem::MetaItem(meta_item) if meta_item.is_word() => meta_item, _ => { - if let Some(item) = li.meta_item() { - if let ast::MetaItemKind::NameValue(_) = item.kind { - if item.path == sym::reason { - sess.dcx().emit_err(MalformedAttribute { - span: sp, - sub: MalformedAttributeSub::ReasonMustComeLast(sp), - }); - continue; - } - } - } - sess.dcx().emit_err(MalformedAttribute { - span: sp, - sub: MalformedAttributeSub::BadAttributeArgument(sp), - }); + 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; } }; @@ -924,7 +921,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } Err((Some(ids), ref new_lint_name)) => { let lint = builtin::RENAMED_AND_REMOVED_LINTS; - self.emit_spanned_lint( + self.emit_span_lint( lint, sp.into(), DeprecatedLintName { @@ -968,33 +965,29 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; } - _ if !self.warn_about_weird_lints => {} + _ 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_spanned_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); + 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_spanned_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); + self.emit_span_lint(RENAMED_AND_REMOVED_LINTS, sp.into(), lint); } CheckLintNameResult::NoLint(suggestion) => { - let name = if let Some(tool_ident) = tool_ident { - format!("{}::{}", tool_ident.name, name) - } else { - name.to_string() - }; - let suggestion = suggestion.map(|replace| { - UnknownLintSuggestion::WithSpan { suggestion: sp, replace } + 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_spanned_lint(UNKNOWN_LINTS, sp.into(), lint); + self.emit_span_lint(UNKNOWN_LINTS, sp.into(), lint); } } // If this lint was renamed, apply the new lint instead of ignoring the attribute. @@ -1003,33 +996,30 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { 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. - if let CheckLintNameResult::Ok(ids) = + let CheckLintNameResult::Ok(ids) = self.store.check_lint_name(&new_name, None, self.registered_tools) - { - 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), - ); - } - } else { + 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 !is_crate_node { + 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; @@ -1040,7 +1030,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { continue; }; - self.emit_spanned_lint( + self.emit_span_lint( UNUSED_ATTRIBUTES, lint_attr_span.into(), IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name }, @@ -1054,37 +1044,46 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// Checks if the lint is gated on a feature that is not enabled. /// /// Returns `true` if the lint's feature is enabled. - // FIXME only emit this once for each attribute, instead of repeating it 4 times for - // pre-expansion lints, post-expansion lints, `shallow_lint_levels_on` and `lint_expectations`. #[track_caller] fn check_gated_lint(&self, lint_id: LintId, span: Span, lint_from_cli: bool) -> bool { - if let Some(feature) = lint_id.lint.feature_gate { - if !self.features.active(feature) { - let lint = builtin::UNKNOWN_LINTS; - let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); - struct_lint_level( - self.sess, - lint, - level, - src, - Some(span.into()), - fluent::lint_unknown_gated_lint, - |lint| { - lint.set_arg("name", lint_id.lint.name_lower()); - lint.note(fluent::lint_note); - rustc_session::parse::add_feature_diagnostics_for_issue( - lint, - &self.sess.parse_sess, - feature, - GateIssue::Language, - lint_from_cli, - ); - }, - ); - return false; - } + 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()), + fluent::lint_unknown_gated_lint, + |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, + ); + }, + ); } - true + + false } /// Find the lint level for a lint. @@ -1095,37 +1094,37 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// Used to emit a lint-related diagnostic based on the current state of /// this lint context. /// - /// [`struct_lint_level`]: rustc_middle::lint::struct_lint_level#decorate-signature + /// [`lint_level`]: rustc_middle::lint::lint_level#decorate-signature #[rustc_lint_diagnostics] #[track_caller] - pub(crate) fn struct_lint( + pub(crate) fn opt_span_lint( &self, lint: &'static Lint, span: Option<MultiSpan>, - msg: impl Into<DiagnosticMessage>, - decorate: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), + msg: impl Into<DiagMessage>, + decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { let (level, src) = self.lint_level(lint); - struct_lint_level(self.sess, lint, level, src, span, msg, decorate) + lint_level(self.sess, lint, level, src, span, msg, decorate) } #[track_caller] - pub fn emit_spanned_lint( + pub fn emit_span_lint( &self, lint: &'static Lint, span: MultiSpan, - decorate: impl for<'a> DecorateLint<'a, ()>, + decorate: impl for<'a> LintDiagnostic<'a, ()>, ) { let (level, src) = self.lint_level(lint); - struct_lint_level(self.sess, lint, level, src, Some(span), decorate.msg(), |lint| { + lint_level(self.sess, lint, level, src, Some(span), decorate.msg(), |lint| { decorate.decorate_lint(lint); }); } #[track_caller] - pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> DecorateLint<'a, ()>) { + pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) { let (level, src) = self.lint_level(lint); - struct_lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| { + lint_level(self.sess, lint, level, src, None, decorate.msg(), |lint| { decorate.decorate_lint(lint); }); } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 0fc24e88b3b..250c4adb948 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -25,23 +25,19 @@ //! //! This API is completely unstable and subject to change. -#![allow(rustc::potential_query_instability)] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(rust_logo)] #![feature(rustdoc_internals)] #![feature(array_windows)] #![feature(box_patterns)] #![feature(control_flow_enum)] +#![feature(extract_if)] +#![feature(generic_nonzero)] #![feature(if_let_guard)] -#![feature(iter_intersperse)] #![feature(iter_order_by)] #![feature(let_chains)] -#![feature(min_specialization)] -#![feature(never_type)] +#![feature(trait_upcasting)] #![feature(rustc_attrs)] -#![recursion_limit = "256"] -#![deny(rustc::untranslatable_diagnostic)] -#![deny(rustc::diagnostic_outside_of_impl)] #![allow(internal_features)] #[macro_use] @@ -75,6 +71,7 @@ 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; @@ -110,6 +107,7 @@ 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::*; @@ -218,8 +216,6 @@ late_lint_methods!( ExplicitOutlivesRequirements: ExplicitOutlivesRequirements, InvalidValue: InvalidValue, DerefNullPtr: DerefNullPtr, - // May Depend on constants elsewhere - UnusedBrokenConst: UnusedBrokenConst, UnstableFeatures: UnstableFeatures, UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller, ArrayIntoIter: ArrayIntoIter::default(), @@ -236,6 +232,7 @@ late_lint_methods!( MissingDebugImplementations: MissingDebugImplementations, MissingDoc: MissingDoc, AsyncFnInTrait: AsyncFnInTrait, + NonLocalDefinitions: NonLocalDefinitions::default(), ] ] ); @@ -329,6 +326,8 @@ fn register_builtins(store: &mut LintStore) { 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 @@ -513,6 +512,30 @@ fn register_builtins(store: &mut LintStore) { "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", + ); } fn register_internals(store: &mut LintStore) { @@ -527,7 +550,6 @@ fn register_internals(store: &mut LintStore) { store.register_lints(&TyTyKind::get_lints()); store.register_late_mod_pass(|_| Box::new(TyTyKind)); store.register_lints(&Diagnostics::get_lints()); - store.register_early_pass(|| Box::new(Diagnostics)); store.register_late_mod_pass(|_| Box::new(Diagnostics)); store.register_lints(&BadOptAccess::get_lints()); store.register_late_mod_pass(|_| Box::new(BadOptAccess)); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index ca6408bdf3d..1dac2d89c6b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1,19 +1,19 @@ -#![allow(rustc::untranslatable_diagnostic)] #![allow(rustc::diagnostic_outside_of_impl)] -use std::num::NonZeroU32; +#![allow(rustc::untranslatable_diagnostic)] +use std::num::NonZero; use crate::errors::RequestedLevel; use crate::fluent_generated as fluent; use rustc_errors::{ - AddToDiagnostic, Applicability, DecorateLint, DiagnosticMessage, DiagnosticStyledString, - SuggestionStyle, + codes::*, Applicability, Diag, DiagMessage, DiagStyledString, EmissionGuarantee, + LintDiagnostic, SubdiagMessageOp, Subdiagnostic, SuggestionStyle, }; use rustc_hir::def_id::DefId; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{ inhabitedness::InhabitedPredicate, Clause, PolyExistentialTraitRef, Ty, TyCtxt, }; -use rustc_session::parse::ParseSess; +use rustc_session::Session; use rustc_span::{edition::Edition, sym, symbol::Ident, Span, Symbol}; use crate::{ @@ -114,6 +114,9 @@ pub enum BuiltinUnsafe { DeclUnsafeMethod, #[diag(lint_builtin_impl_unsafe_method)] ImplUnsafeMethod, + #[diag(lint_builtin_global_asm)] + #[note(lint_builtin_global_macro_unsafety)] + GlobalAsm, } #[derive(LintDiagnostic)] @@ -133,12 +136,12 @@ pub struct BuiltinMissingDebugImpl<'a> { } // Needed for def_path_str -impl<'a> DecorateLint<'a, ()> for BuiltinMissingDebugImpl<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("debug", self.tcx.def_path_str(self.def_id)); +impl<'a> LintDiagnostic<'a, ()> for BuiltinMissingDebugImpl<'_> { + fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) { + diag.arg("debug", self.tcx.def_path_str(self.def_id)); } - fn msg(&self) -> DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_builtin_missing_debug_impl } } @@ -235,20 +238,20 @@ pub struct BuiltinUnstableFeatures; // lint_ungated_async_fn_track_caller pub struct BuiltinUngatedAsyncFnTrackCaller<'a> { pub label: Span, - pub parse_sess: &'a ParseSess, + pub session: &'a Session, } -impl<'a> DecorateLint<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { +impl<'a> LintDiagnostic<'a, ()> for BuiltinUngatedAsyncFnTrackCaller<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { diag.span_label(self.label, fluent::lint_label); rustc_session::parse::add_feature_diagnostics( diag, - self.parse_sess, + self.session, sym::async_fn_track_caller, ); } - fn msg(&self) -> DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_ungated_async_fn_track_caller } } @@ -267,23 +270,21 @@ pub struct SuggestChangingAssocTypes<'a, 'b> { pub ty: &'a rustc_hir::Ty<'b>, } -impl AddToDiagnostic for SuggestChangingAssocTypes<'_, '_> { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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> { - err: &'a mut rustc_errors::Diagnostic, + struct WalkAssocTypes<'a, 'b, G: EmissionGuarantee> { + err: &'a mut Diag<'b, G>, } - impl Visitor<'_> for WalkAssocTypes<'_> { + impl<'a, 'b, G: EmissionGuarantee> Visitor<'_> for WalkAssocTypes<'a, 'b, G> { fn visit_qpath( &mut self, qpath: &rustc_hir::QPath<'_>, @@ -325,14 +326,12 @@ pub struct BuiltinTypeAliasGenericBoundsSuggestion { pub suggestions: Vec<(Span, String)>, } -impl AddToDiagnostic for BuiltinTypeAliasGenericBoundsSuggestion { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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, @@ -413,20 +412,20 @@ pub struct BuiltinIncompleteFeaturesHelp; #[derive(Subdiagnostic)] #[note(lint_note)] pub struct BuiltinFeatureIssueNote { - pub n: NonZeroU32, + pub n: NonZero<u32>, } pub struct BuiltinUnpermittedTypeInit<'a> { - pub msg: DiagnosticMessage, + pub msg: DiagMessage, pub ty: Ty<'a>, pub label: Span, pub sub: BuiltinUnpermittedTypeInitSub, pub tcx: TyCtxt<'a>, } -impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("ty", self.ty); +impl<'a> LintDiagnostic<'a, ()> for BuiltinUnpermittedTypeInit<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + 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. @@ -435,10 +434,10 @@ impl<'a> DecorateLint<'a, ()> for BuiltinUnpermittedTypeInit<'_> { fluent::lint_builtin_unpermitted_type_init_label_suggestion, ); } - self.sub.add_to_diagnostic(diag); + self.sub.add_to_diag(diag); } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { self.msg.clone() } } @@ -448,14 +447,12 @@ pub struct BuiltinUnpermittedTypeInitSub { pub err: InitError, } -impl AddToDiagnostic for BuiltinUnpermittedTypeInitSub { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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 { @@ -505,17 +502,15 @@ pub struct BuiltinClashingExternSub<'a> { pub found: Ty<'a>, } -impl AddToDiagnostic for BuiltinClashingExternSub<'_> { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { - let mut expected_str = DiagnosticStyledString::new(); +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 = DiagnosticStyledString::new(); + 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); } @@ -544,7 +539,6 @@ pub enum BuiltinSpecialModuleNameUsed { // deref_into_dyn_supertrait.rs #[derive(LintDiagnostic)] #[diag(lint_supertrait_as_deref_target)] -#[help] pub struct SupertraitAsDerefTarget<'a> { pub self_ty: Ty<'a>, pub supertrait_principal: PolyExistentialTraitRef<'a>, @@ -740,7 +734,7 @@ pub enum InvalidFromUtf8Diag { // reference_casting.rs #[derive(LintDiagnostic)] -pub enum InvalidReferenceCastingDiag { +pub enum InvalidReferenceCastingDiag<'tcx> { #[diag(lint_invalid_reference_casting_borrow_as_mut)] #[note(lint_invalid_reference_casting_note_book)] BorrowAsMut { @@ -757,6 +751,18 @@ pub enum InvalidReferenceCastingDiag { #[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 @@ -778,14 +784,12 @@ pub struct HiddenUnicodeCodepointsDiagLabels { pub spans: Vec<(char, Span)>, } -impl AddToDiagnostic for HiddenUnicodeCodepointsDiagLabels { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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:?}")); } @@ -798,14 +802,12 @@ pub enum HiddenUnicodeCodepointsDiagSub { } // Used because of multiple multipart_suggestion and note -impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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( @@ -830,7 +832,7 @@ impl AddToDiagnostic for HiddenUnicodeCodepointsDiagSub { // 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.set_arg( + diag.arg( "escaped", spans .into_iter() @@ -922,10 +924,6 @@ pub struct DiagOutOfImpl; pub struct UntranslatableDiag; #[derive(LintDiagnostic)] -#[diag(lint_trivial_untranslatable_diag)] -pub struct UntranslatableDiagnosticTrivial; - -#[derive(LintDiagnostic)] #[diag(lint_bad_opt_access)] pub struct BadOptAccessDiag<'a> { pub msg: &'a str, @@ -948,32 +946,41 @@ pub enum NonBindingLet { pub struct NonBindingLetSub { pub suggestion: Span, - pub multi_suggestion_start: Span, - pub multi_suggestion_end: Span, -} - -impl AddToDiagnostic for NonBindingLetSub { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { - diag.span_suggestion_verbose( - self.suggestion, - fluent::lint_non_binding_let_suggestion, - "_unused", - Applicability::MachineApplicable, - ); - diag.multipart_suggestion( - fluent::lint_non_binding_let_multi_suggestion, - vec![ - (self.multi_suggestion_start, "drop(".to_string()), - (self.multi_suggestion_end, ")".to_string()), - ], - Applicability::MachineApplicable, - ); + 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); + } } } @@ -1069,13 +1076,14 @@ pub enum UnknownLintSuggestion { #[primary_span] suggestion: Span, replace: Symbol, + from_rustc: bool, }, #[help(lint_help)] - WithoutSpan { replace: Symbol }, + WithoutSpan { replace: Symbol, from_rustc: bool }, } #[derive(LintDiagnostic)] -#[diag(lint_unknown_lint, code = "E0602")] +#[diag(lint_unknown_lint, code = E0602)] pub struct UnknownLintFromCommandLine<'a> { pub name: String, #[subdiagnostic] @@ -1117,7 +1125,12 @@ pub struct IdentifierNonAsciiChar; #[derive(LintDiagnostic)] #[diag(lint_identifier_uncommon_codepoints)] -pub struct IdentifierUncommonCodepoints; +#[note] +pub struct IdentifierUncommonCodepoints { + pub codepoints: Vec<char>, + pub codepoints_len: usize, + pub identifier_type: &'static str, +} #[derive(LintDiagnostic)] #[diag(lint_confusable_identifier_pair)] @@ -1146,9 +1159,9 @@ pub struct NonFmtPanicUnused { } // Used because of two suggestions based on one Option<Span> -impl<'a> DecorateLint<'a, ()> for NonFmtPanicUnused { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("count", self.count); +impl<'a> LintDiagnostic<'a, ()> for NonFmtPanicUnused { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.arg("count", self.count); diag.note(fluent::lint_note); if let Some(span) = self.suggestion { diag.span_suggestion( @@ -1166,7 +1179,7 @@ impl<'a> DecorateLint<'a, ()> for NonFmtPanicUnused { } } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_non_fmt_panic_unused } } @@ -1223,14 +1236,12 @@ pub enum NonSnakeCaseDiagSub { SuggestionAndNote { span: Span }, } -impl AddToDiagnostic for NonSnakeCaseDiagSub { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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); @@ -1301,6 +1312,12 @@ pub struct NoopMethodCallDiag<'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)] @@ -1315,6 +1332,45 @@ pub struct SuspiciousDoubleRefCloneDiag<'a> { pub ty: Ty<'a>, } +// non_local_defs.rs +#[derive(LintDiagnostic)] +pub enum NonLocalDefinitionsDiag { + #[diag(lint_non_local_definitions_impl)] + #[help] + #[note(lint_non_local)] + #[note(lint_exception)] + #[note(lint_non_local_definitions_deprecation)] + Impl { + depth: u32, + body_kind_descr: &'static str, + body_name: String, + #[subdiagnostic] + cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>, + #[suggestion(lint_const_anon, code = "_", applicability = "machine-applicable")] + const_anon: Option<Span>, + }, + #[diag(lint_non_local_definitions_macro_rules)] + #[help] + #[note(lint_non_local)] + #[note(lint_exception)] + #[note(lint_non_local_definitions_deprecation)] + MacroRules { + depth: u32, + body_kind_descr: &'static str, + body_name: String, + #[subdiagnostic] + cargo_update: Option<NonLocalDefinitionsCargoUpdateNote>, + }, +} + +#[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)] @@ -1341,13 +1397,13 @@ pub struct DropTraitConstraintsDiag<'a> { } // Needed for def_path_str -impl<'a> DecorateLint<'a, ()> for DropTraitConstraintsDiag<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("predicate", self.predicate); - diag.set_arg("needs_drop", self.tcx.def_path_str(self.def_id)); +impl<'a> LintDiagnostic<'a, ()> for DropTraitConstraintsDiag<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.arg("predicate", self.predicate); + diag.arg("needs_drop", self.tcx.def_path_str(self.def_id)); } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_drop_trait_constraints } } @@ -1358,12 +1414,12 @@ pub struct DropGlue<'a> { } // Needed for def_path_str -impl<'a> DecorateLint<'a, ()> for DropGlue<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("needs_drop", self.tcx.def_path_str(self.def_id)); +impl<'a> LintDiagnostic<'a, ()> for DropGlue<'_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + diag.arg("needs_drop", self.tcx.def_path_str(self.def_id)); } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_drop_glue } } @@ -1422,14 +1478,12 @@ pub enum OverflowingBinHexSign { Negative, } -impl AddToDiagnostic for OverflowingBinHexSign { - fn add_to_diagnostic_with<F>(self, diag: &mut rustc_errors::Diagnostic, _: F) - where - F: Fn( - &mut rustc_errors::Diagnostic, - rustc_errors::SubdiagnosticMessage, - ) -> rustc_errors::SubdiagnosticMessage, - { +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); @@ -1571,7 +1625,8 @@ pub enum AmbiguousWidePointerComparisons<'a> { #[multipart_suggestion( lint_addr_metadata_suggestion, style = "verbose", - applicability = "machine-applicable" + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" )] pub struct AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a> { pub ne: &'a str, @@ -1590,7 +1645,8 @@ pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> { #[multipart_suggestion( lint_addr_suggestion, style = "verbose", - applicability = "machine-applicable" + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" )] AddrEq { ne: &'a str, @@ -1606,7 +1662,8 @@ pub enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> { #[multipart_suggestion( lint_addr_suggestion, style = "verbose", - applicability = "machine-applicable" + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" )] Cast { deref_left: &'a str, @@ -1626,16 +1683,16 @@ pub struct ImproperCTypes<'a> { pub ty: Ty<'a>, pub desc: &'a str, pub label: Span, - pub help: Option<DiagnosticMessage>, - pub note: DiagnosticMessage, + pub help: Option<DiagMessage>, + pub note: DiagMessage, pub span_note: Option<Span>, } -// Used because of the complexity of Option<DiagnosticMessage>, DiagnosticMessage, and Option<Span> -impl<'a> DecorateLint<'a, ()> for ImproperCTypes<'_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("ty", self.ty); - diag.set_arg("desc", self.desc); +// 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.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); @@ -1646,7 +1703,7 @@ impl<'a> DecorateLint<'a, ()> for ImproperCTypes<'_> { } } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_improper_ctypes } } @@ -1775,21 +1832,21 @@ pub enum UnusedDefSuggestion { } // Needed because of def_path_str -impl<'a> DecorateLint<'a, ()> for UnusedDef<'_, '_> { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { - diag.set_arg("pre", self.pre); - diag.set_arg("post", self.post); - diag.set_arg("def", self.cx.tcx.def_path_str(self.def_id)); +impl<'a> LintDiagnostic<'a, ()> for UnusedDef<'_, '_> { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { + 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); + diag.subdiagnostic(diag.dcx, sugg); } } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_unused_def } } @@ -1858,15 +1915,15 @@ pub struct AsyncFnInTraitDiag { pub sugg: Option<Vec<(Span, String)>>, } -impl<'a> DecorateLint<'a, ()> for AsyncFnInTraitDiag { - fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>) { +impl<'a> LintDiagnostic<'a, ()> for AsyncFnInTraitDiag { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) { diag.note(fluent::lint_note); if let Some(sugg) = self.sugg { diag.multipart_suggestion(fluent::lint_suggestion, sugg, Applicability::MaybeIncorrect); } } - fn msg(&self) -> rustc_errors::DiagnosticMessage { + fn msg(&self) -> DiagMessage { fluent::lint_async_fn_in_trait } } diff --git a/compiler/rustc_lint/src/map_unit_fn.rs b/compiler/rustc_lint/src/map_unit_fn.rs index 7c692fee333..07980cfb6fa 100644 --- a/compiler/rustc_lint/src/map_unit_fn.rs +++ b/compiler/rustc_lint/src/map_unit_fn.rs @@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for MapUnitFn { 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_spanned_lint( + cx.emit_span_lint( MAP_UNIT_FN, span, MappingToUnit { @@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for MapUnitFn { let cl_ty = subs.as_closure().sig(); let ret_ty = cl_ty.output().skip_binder(); if is_unit_type(ret_ty) { - cx.emit_spanned_lint( + cx.emit_span_lint( MAP_UNIT_FN, span, MappingToUnit { diff --git a/compiler/rustc_lint/src/methods.rs b/compiler/rustc_lint/src/methods.rs index 5b63b19c53c..9cfdaf0ce2f 100644 --- a/compiler/rustc_lint/src/methods.rs +++ b/compiler/rustc_lint/src/methods.rs @@ -57,7 +57,7 @@ fn lint_cstring_as_ptr( 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_spanned_lint( + 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 index dfefaf82fd7..69d623b547b 100644 --- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs +++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs @@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable { .into_iter() .filter_map(|(pred, _)| pred.as_trait_clause()); if direct_super_traits_iter.count() > 1 { - cx.emit_spanned_lint( + 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 index 4f92fcd71c6..79bc78ae55a 100644 --- a/compiler/rustc_lint/src/non_ascii_idents.rs +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -4,8 +4,10 @@ use crate::lints::{ }; use crate::{EarlyContext, EarlyLintPass, LintContext}; use rustc_ast as ast; -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::unord::UnordMap; use rustc_span::symbol::Symbol; +use unicode_security::general_security_profile::IdentifierType; declare_lint! { /// The `non_ascii_idents` lint detects non-ASCII identifiers. @@ -170,30 +172,71 @@ impl EarlyLintPass for NonAsciiIdents { } let mut has_non_ascii_idents = false; - let symbols = cx.sess().parse_sess.symbol_gallery.symbols.lock(); + 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_spanned_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar); + cx.emit_span_lint(NON_ASCII_IDENTS, sp, IdentifierNonAsciiChar); if check_uncommon_codepoints && !symbol_str.chars().all(GeneralSecurityProfile::identifier_allowed) { - cx.emit_spanned_lint(UNCOMMON_CODEPOINTS, sp, IdentifierUncommonCodepoints); + 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: FxHashMap<Symbol, (Symbol, Span, bool)> = - FxHashMap::with_capacity_and_hasher(symbols.len(), Default::default()); + 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() { @@ -215,7 +258,7 @@ impl EarlyLintPass for NonAsciiIdents { .entry(skeleton_sym) .and_modify(|(existing_symbol, existing_span, existing_is_ascii)| { if !*existing_is_ascii || !is_ascii { - cx.emit_spanned_lint( + cx.emit_span_lint( CONFUSABLE_IDENTS, sp, ConfusableIdentifierPair { @@ -246,8 +289,8 @@ impl EarlyLintPass for NonAsciiIdents { Verified, } - let mut script_states: FxHashMap<AugmentedScriptSet, ScriptSetUsage> = - FxHashMap::default(); + 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); @@ -287,6 +330,8 @@ impl EarlyLintPass for NonAsciiIdents { } 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 { @@ -299,6 +344,8 @@ impl EarlyLintPass for NonAsciiIdents { 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 }; @@ -332,7 +379,7 @@ impl EarlyLintPass for NonAsciiIdents { let char_info = format!("'{}' (U+{:04X})", ch, ch as u32); includes += &char_info; } - cx.emit_spanned_lint( + 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 index 9fcd70ba0b5..a2d07fff506 100644 --- a/compiler/rustc_lint/src/non_fmt_panic.rs +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -111,17 +111,18 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc let mut arg_span = arg.span; let mut arg_macro = None; while !span.contains(arg_span) { - let expn = arg_span.ctxt().outer_expn_data(); - if expn.is_root() { + 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.struct_span_lint(NON_FMT_PANICS, arg_span, fluent::lint_non_fmt_panic, |lint| { - lint.set_arg("name", symbol); + cx.span_lint(NON_FMT_PANICS, arg_span, fluent::lint_non_fmt_panic, |lint| { + lint.arg("name", symbol); lint.note(fluent::lint_note); lint.note(fluent::lint_more_info_note); if !is_arg_inside_call(arg_span, span) { @@ -180,7 +181,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc fmt_applicability, ); } else if suggest_debug { - lint.set_arg("ty", ty); + lint.arg("ty", ty); lint.span_suggestion_verbose( arg_span.shrink_to_lo(), fluent::lint_debug_suggestion, @@ -191,7 +192,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc if suggest_panic_any { if let Some((open, close, del)) = find_delimiters(cx, span) { - lint.set_arg("already_suggested", suggest_display || suggest_debug); + lint.arg("already_suggested", suggest_display || suggest_debug); lint.multipart_suggestion( fluent::lint_panic_suggestion, if del == '(' { @@ -230,7 +231,7 @@ fn check_panic_str<'tcx>( let fmt_span = arg.span.source_callsite(); - let (snippet, style) = match cx.sess().parse_sess.source_map().span_to_snippet(fmt_span) { + 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('"')); @@ -250,7 +251,7 @@ fn check_panic_str<'tcx>( .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end))) .collect(), }; - cx.emit_spanned_lint( + cx.emit_span_lint( NON_FMT_PANICS, arg_spans, NonFmtPanicUnused { @@ -267,7 +268,7 @@ fn check_panic_str<'tcx>( .collect() }); let count = brace_spans.as_ref().map(|v| v.len()).unwrap_or(/* any number >1 */ 2); - cx.emit_spanned_lint( + cx.emit_span_lint( NON_FMT_PANICS, brace_spans.unwrap_or_else(|| vec![span]), NonFmtPanicBraces { @@ -281,7 +282,7 @@ fn check_panic_str<'tcx>( /// 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().parse_sess.source_map().span_to_snippet(span).ok()?; + 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(( 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..7c4d92d3ce0 --- /dev/null +++ b/compiler/rustc_lint/src/non_local_def.rs @@ -0,0 +1,231 @@ +use rustc_hir::{def::DefKind, Body, Item, ItemKind, Node, Path, QPath, TyKind}; +use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::{sym, symbol::kw, ExpnKind, MacroKind}; + +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 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: &'tcx Body<'tcx>) { + self.body_depth += 1; + } + + fn check_body_post(&mut self, _cx: &LateContext<'tcx>, _body: &'tcx 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 parent = cx.tcx.parent(item.owner_id.def_id.into()); + 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 + } + }; + + 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 + }; + + let self_ty_has_local_parent = match impl_.self_ty.kind { + TyKind::Path(QPath::Resolved(_, ty_path)) => { + path_has_local_parent(ty_path, cx, parent, parent_parent) + } + TyKind::TraitObject([principle_poly_trait_ref, ..], _, _) => { + path_has_local_parent( + principle_poly_trait_ref.trait_ref.path, + cx, + parent, + parent_parent, + ) + } + TyKind::TraitObject([], _, _) + | TyKind::InferDelegation(_, _) + | TyKind::Slice(_) + | TyKind::Array(_, _) + | TyKind::Ptr(_) + | TyKind::Ref(_, _) + | TyKind::BareFn(_) + | TyKind::Never + | TyKind::Tup(_) + | TyKind::Path(_) + | TyKind::AnonAdt(_) + | TyKind::OpaqueDef(_, _, _) + | TyKind::Typeof(_) + | TyKind::Infer + | TyKind::Err(_) => false, + }; + + 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 none of them have a local parent (LOGICAL NOR) this means that + // this impl definition is a non-local definition and so we lint on it. + if !(self_ty_has_local_parent || of_trait_has_local_parent) { + let const_anon = if self.body_depth == 1 + && 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 + }; + + cx.emit_span_lint( + NON_LOCAL_DEFINITIONS, + item.span, + 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, + }, + ) + } + } + 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(), + }, + ) + } + _ => {} + } + } +} + +/// 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>> +/// ^^^^^^^^^^^^^^^^^^^^^^^ +/// ``` +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.is_local() && { + let res_parent = cx.tcx.parent(did); + res_parent == impl_parent || Some(res_parent) == impl_parent_parent + } + }) +} diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index 59f27a88aec..ee863672017 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -10,6 +10,7 @@ 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_span::def_id::LocalDefId; use rustc_span::symbol::{sym, Ident}; use rustc_span::{BytePos, Span}; @@ -150,7 +151,7 @@ impl NonCamelCaseTypes { } else { NonCamelCaseTypeSub::Label { span: ident.span } }; - cx.emit_spanned_lint( + cx.emit_span_lint( NON_CAMEL_CASE_TYPES, ident.span, NonCamelCaseType { sort, name, sub }, @@ -320,7 +321,7 @@ impl NonSnakeCase { } else { NonSnakeCaseDiagSub::Label { span } }; - cx.emit_spanned_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub }); + cx.emit_span_lint(NON_SNAKE_CASE, span, NonSnakeCaseDiag { sort, name, sc, sub }); } } } @@ -331,6 +332,10 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { 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 { @@ -427,7 +432,7 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { 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.hir().get_parent(hid) { + 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. @@ -481,7 +486,7 @@ impl NonUpperCaseGlobals { } else { NonUpperCaseGlobalSub::Label { span: ident.span } }; - cx.emit_spanned_lint( + cx.emit_span_lint( NON_UPPER_CASE_GLOBALS, ident.span, NonUpperCaseGlobal { sort, name, sub }, diff --git a/compiler/rustc_lint/src/noop_method_call.rs b/compiler/rustc_lint/src/noop_method_call.rs index cfbca6efbfa..970d411fb06 100644 --- a/compiler/rustc_lint/src/noop_method_call.rs +++ b/compiler/rustc_lint/src/noop_method_call.rs @@ -121,22 +121,32 @@ impl<'tcx> LateLintPass<'tcx> for NoopMethodCall { let orig_ty = expr_ty.peel_refs(); if receiver_ty == expr_ty { - cx.emit_spanned_lint( + 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 }, + 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_spanned_lint( + sym::noop_method_clone => cx.emit_span_lint( SUSPICIOUS_DOUBLE_REF_OP, span, SuspiciousDoubleRefCloneDiag { ty: expr_ty }, ), - sym::noop_method_deref => cx.emit_spanned_lint( + sym::noop_method_deref => cx.emit_span_lint( SUSPICIOUS_DOUBLE_REF_OP, span, SuspiciousDoubleRefDerefDiag { ty: expr_ty }, diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 44b23b8bdc0..cf825be7a55 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -4,7 +4,7 @@ use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{ self, fold::BottomUpFolder, print::TraitPredPrintModifiersAndPath, Ty, TypeFoldable, }; -use rustc_span::Span; +use rustc_span::{symbol::kw, Span}; use rustc_trait_selection::traits; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; @@ -77,90 +77,104 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied() { - let predicate = infcx.instantiate_binder_with_placeholders(pred.kind()); - let ty::ClauseKind::Projection(proj) = predicate else { - continue; - }; - // Only check types, since those are the only things that may - // have opaques in them anyways. - let Some(proj_term) = proj.term.ty() else { continue }; + 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.ty() 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: `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(_) - ) - { - continue; - } + // 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(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.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_ty.def_id) - .iter_instantiated_copied(cx.tcx, proj.projection_ty.args) - { - let assoc_pred = assoc_pred.fold_with(proj_replacer); - let Ok(assoc_pred) = traits::fully_normalize( - infcx, - traits::ObligationCause::dummy(), - cx.param_env, - assoc_pred, - ) else { - continue; + let proj_ty = + Ty::new_projection(cx.tcx, proj.projection_ty.def_id, proj.projection_ty.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, }; - // 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 !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new( - cx.tcx, - traits::ObligationCause::dummy(), - cx.param_env, - assoc_pred, - )) { - // 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, + // 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_ty.def_id) + .iter_instantiated_copied(cx.tcx, proj.projection_ty.args) + { + let assoc_pred = assoc_pred.fold_with(proj_replacer); + let Ok(assoc_pred) = traits::fully_normalize( + infcx, + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + ) else { + continue; }; - cx.emit_spanned_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, - }, - ); + + // 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 !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new( + cx.tcx, + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + )) { + // 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, + }, + ); + } } - } + }); } } } diff --git a/compiler/rustc_lint/src/pass_by_value.rs b/compiler/rustc_lint/src/pass_by_value.rs index fce750c9b55..160d42caa9e 100644 --- a/compiler/rustc_lint/src/pass_by_value.rs +++ b/compiler/rustc_lint/src/pass_by_value.rs @@ -29,7 +29,7 @@ impl<'tcx> LateLintPass<'tcx> for PassByValue { } } if let Some(t) = path_for_pass_by_value(cx, inner_ty) { - cx.emit_spanned_lint( + cx.emit_span_lint( PASS_BY_VALUE, ty.span, PassByValueDiag { ty: t, suggestion: ty.span }, diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs index 4ac8a5ceb85..8038115ef51 100644 --- a/compiler/rustc_lint/src/ptr_nulls.rs +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { ) && let Some(diag) = incorrect_check(cx, arg) => { - cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) } // Catching: @@ -110,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { ) && let Some(diag) = incorrect_check(cx, receiver) => { - cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) } ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { @@ -134,7 +134,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { && let LitKind::Int(v, _) = spanned.node && v == 0 => { - cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) } // Catching: @@ -145,7 +145,7 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) && (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut) => { - cx.emit_spanned_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) + 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 index 9a8b14b4907..1681ac2f1e5 100644 --- a/compiler/rustc_lint/src/redundant_semicolon.rs +++ b/compiler/rustc_lint/src/redundant_semicolon.rs @@ -47,7 +47,7 @@ fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, boo return; } - cx.emit_spanned_lint( + 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 index 96290288f07..f386db9d8db 100644 --- a/compiler/rustc_lint/src/reference_casting.rs +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -1,6 +1,7 @@ use rustc_ast::Mutability; use rustc_hir::{Expr, ExprKind, UnOp}; -use rustc_middle::ty::{self, TypeAndMut}; +use rustc_middle::ty::layout::LayoutOf as _; +use rustc_middle::ty::{self, layout::TyAndLayout, TypeAndMut}; use rustc_span::sym; use crate::{lints::InvalidReferenceCastingDiag, LateContext, LateLintPass, LintContext}; @@ -38,16 +39,22 @@ 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) { - if matches!(pat, PatternKind::Borrow { mutbl: Mutability::Mut } | PatternKind::Assign) { - let init = cx.expr_or_init(e); + 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)) + }; - let Some(ty_has_interior_mutability) = is_cast_from_ref_to_mut_ptr(cx, init) else { - return; - }; - let orig_cast = if init.span != e.span { Some(init.span) } else { None }; + 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_spanned_lint( + cx.emit_span_lint( INVALID_REFERENCE_CASTING, expr.span, if pat == PatternKind::Assign { @@ -63,6 +70,23 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { }, ); } + + 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(), + }, + ); + } } } } @@ -124,6 +148,7 @@ fn borrow_or_assign<'tcx>( 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); @@ -132,7 +157,7 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>( return None; } - let (e, need_check_freeze) = peel_casts(cx, orig_expr); + 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() { @@ -151,6 +176,56 @@ fn is_cast_from_ref_to_mut_ptr<'tcx>( } } +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(TypeAndMut { ty: inner_end_ty, mutbl: _ }) = 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 }; + 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; diff --git a/compiler/rustc_lint/src/traits.rs b/compiler/rustc_lint/src/traits.rs index e812493b3dd..789f154eac5 100644 --- a/compiler/rustc_lint/src/traits.rs +++ b/compiler/rustc_lint/src/traits.rs @@ -101,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { continue; } let Some(def_id) = cx.tcx.get_diagnostic_item(sym::needs_drop) else { return }; - cx.emit_spanned_lint( + cx.emit_span_lint( DROP_BOUNDS, span, DropTraitConstraintsDiag { predicate, tcx: cx.tcx, def_id }, @@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for DropTraitConstraints { 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_spanned_lint(DYN_DROP, bound.span, DropGlue { tcx: cx.tcx, def_id }); + 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 index 6dade43a183..51fe08d4af1 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -14,7 +14,7 @@ use crate::{LateContext, LateLintPass, LintContext}; use rustc_ast as ast; use rustc_attr as attr; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::DiagnosticMessage; +use rustc_errors::DiagMessage; use rustc_hir as hir; use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; use rustc_middle::ty::layout::{IntegerExt, LayoutOf, SizeSkeleton}; @@ -29,7 +29,6 @@ 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 rustc_type_ir::DynKind; use std::iter; use std::ops::ControlFlow; @@ -201,8 +200,7 @@ fn lint_overflowing_range_endpoint<'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.hir_node(cx.tcx.hir().parent_id(expr.hir_id)) + 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) @@ -212,9 +210,8 @@ fn lint_overflowing_range_endpoint<'tcx>( // We only want to handle exclusive (`..`) ranges, // which are represented as `ExprKind::Struct`. - let par_id = cx.tcx.hir().parent_id(expr.hir_id); - let Node::ExprField(field) = cx.tcx.hir_node(par_id) else { return false }; - let Node::Expr(struct_expr) = cx.tcx.hir().get_parent(field.hir_id) else { return false }; + 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; }; @@ -255,7 +252,7 @@ fn lint_overflowing_range_endpoint<'tcx>( } }; - cx.emit_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, struct_expr.span, RangeEndpointOutOfRange { ty, sub: sub_sugg }, @@ -372,7 +369,7 @@ fn report_bin_hex_error( }) .flatten(); - cx.emit_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, expr.span, OverflowingBinHex { @@ -474,7 +471,7 @@ fn lint_int_literal<'tcx>( let help = get_type_suggestion(cx.typeck_results().node_type(e.hir_id), v, negative) .map(|suggestion_ty| OverflowingIntHelp { suggestion_ty }); - cx.emit_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, span, OverflowingInt { ty: t.name_str(), lit, min, max, help }, @@ -493,16 +490,15 @@ fn lint_uint_literal<'tcx>( let lit_val: u128 = match lit.node { // _v is u8, within range by definition ast::LitKind::Byte(_v) => return, - ast::LitKind::Int(v, _) => v, + ast::LitKind::Int(v, _) => v.get(), _ => bug!(), }; if lit_val < min || lit_val > max { - let parent_id = cx.tcx.hir().parent_id(e.hir_id); - if let Node::Expr(par_e) = cx.tcx.hir_node(parent_id) { + 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_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, par_e.span, OnlyCastu8ToChar { span: par_e.span, literal: lit_val }, @@ -529,7 +525,7 @@ fn lint_uint_literal<'tcx>( ); return; } - cx.emit_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, e.span, OverflowingUInt { @@ -556,7 +552,7 @@ fn lint_literal<'tcx>( 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) + lint_int_literal(cx, type_limits, e, lit, t, v.get()) } _ => bug!(), }; @@ -565,13 +561,16 @@ fn lint_literal<'tcx>( ty::Float(t) => { let is_infinite = match lit.node { ast::LitKind::Float(v, _) => match t { + // FIXME(f16_f128): add this check once we have library support + 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_spanned_lint( + cx.emit_span_lint( OVERFLOWING_LITERALS, e.span, OverflowingLiteral { @@ -655,7 +654,7 @@ fn lint_nan<'tcx>( _ => return, }; - cx.emit_spanned_lint(INVALID_NAN_COMPARISONS, e.span, lint); + cx.emit_span_lint(INVALID_NAN_COMPARISONS, e.span, lint); } fn lint_wide_pointer<'tcx>( @@ -675,7 +674,7 @@ fn lint_wide_pointer<'tcx>( } match ty.kind() { ty::RawPtr(TypeAndMut { mutbl: _, ty }) => (!ty.is_sized(cx.tcx, cx.param_env)) - .then(|| (refs, matches!(ty.kind(), ty::Dynamic(_, _, DynKind::Dyn)))), + .then(|| (refs, matches!(ty.kind(), ty::Dynamic(_, _, ty::Dyn)))), _ => None, } }; @@ -701,7 +700,7 @@ fn lint_wide_pointer<'tcx>( 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_spanned_lint( + return cx.emit_span_lint( AMBIGUOUS_WIDE_POINTER_COMPARISONS, e.span, AmbiguousWidePointerComparisons::Spanless, @@ -719,7 +718,7 @@ fn lint_wide_pointer<'tcx>( let deref_left = &*"*".repeat(l_ty_refs); let deref_right = &*"*".repeat(r_ty_refs); - cx.emit_spanned_lint( + cx.emit_span_lint( AMBIGUOUS_WIDE_POINTER_COMPARISONS, e.span, AmbiguousWidePointerComparisons::Spanful { @@ -771,7 +770,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { hir::ExprKind::Binary(binop, ref l, ref r) => { if is_comparison(binop) { if !check_limits(cx, binop, l, r) { - cx.emit_spanned_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); + cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); } else { lint_nan(cx, e, binop, l, r); lint_wide_pointer(cx, e, binop.node, l, r); @@ -843,7 +842,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { ast::LitKind::Int( v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed, - ) => v as i128, + ) => v.get() as i128, _ => return true, }, _ => bug!(), @@ -854,7 +853,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { 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, + ast::LitKind::Int(v, _) => v.get(), _ => return true, }, _ => bug!(), @@ -961,7 +960,7 @@ struct ImproperCTypesVisitor<'a, 'tcx> { enum FfiResult<'tcx> { FfiSafe, FfiPhantom(Ty<'tcx>), - FfiUnsafe { ty: Ty<'tcx>, reason: DiagnosticMessage, help: Option<DiagnosticMessage> }, + FfiUnsafe { ty: Ty<'tcx>, reason: DiagMessage, help: Option<DiagMessage> }, } pub(crate) fn nonnull_optimization_guaranteed<'tcx>( @@ -1436,6 +1435,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { | ty::Bound(..) | ty::Error(_) | ty::Closure(..) + | ty::CoroutineClosure(..) | ty::Coroutine(..) | ty::CoroutineWitness(..) | ty::Placeholder(..) @@ -1447,8 +1447,8 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { &mut self, ty: Ty<'tcx>, sp: Span, - note: DiagnosticMessage, - help: Option<DiagnosticMessage>, + note: DiagMessage, + help: Option<DiagMessage>, ) { let lint = match self.mode { CItemKind::Declaration => IMPROPER_CTYPES, @@ -1465,7 +1465,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } else { None }; - self.cx.emit_spanned_lint( + self.cx.emit_span_lint( lint, sp, ImproperCTypes { ty, desc, label: sp, help, note, span_note }, @@ -1475,9 +1475,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { 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 BreakTy = Ty<'tcx>; + type Result = ControlFlow<Ty<'tcx>>; - fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { if !ty.has_opaque_types() { return ControlFlow::Continue(()); } @@ -1590,10 +1590,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } fn is_internal_abi(&self, abi: SpecAbi) -> bool { - matches!( - abi, - SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic - ) + matches!(abi, SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic) } /// Find any fn-ptr types with external ABIs in `ty`. @@ -1624,9 +1621,9 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } impl<'vis, 'a, 'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'vis, 'a, 'tcx> { - type BreakTy = Ty<'tcx>; + type Result = ControlFlow<Ty<'tcx>>; - fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { if let ty::FnPtr(sig) = ty.kind() && !self.visitor.is_internal_abi(sig.abi()) { @@ -1793,7 +1790,7 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { // 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_spanned_lint( + cx.emit_span_lint( VARIANT_SIZE_DIFFERENCES, enum_definition.variants[largest_index].span, VariantSizeDifferencesDiag { largest }, @@ -1914,17 +1911,9 @@ impl InvalidAtomicOrdering { && (ordering == invalid_ordering || ordering == sym::AcqRel) { if method == sym::load { - cx.emit_spanned_lint( - INVALID_ATOMIC_ORDERING, - ordering_arg.span, - AtomicOrderingLoad, - ); + cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingLoad); } else { - cx.emit_spanned_lint( - INVALID_ATOMIC_ORDERING, - ordering_arg.span, - AtomicOrderingStore, - ); + cx.emit_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, AtomicOrderingStore); }; } } @@ -1936,7 +1925,7 @@ impl InvalidAtomicOrdering { && 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_spanned_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence); + cx.emit_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, AtomicOrderingFence); } } @@ -1958,7 +1947,7 @@ impl InvalidAtomicOrdering { let Some(fail_ordering) = Self::match_ordering(cx, fail_order_arg) else { return }; if matches!(fail_ordering, sym::Release | sym::AcqRel) { - cx.emit_spanned_lint( + cx.emit_span_lint( INVALID_ATOMIC_ORDERING, fail_order_arg.span, InvalidAtomicOrderingDiag { method, fail_order_arg_span: fail_order_arg.span }, diff --git a/compiler/rustc_lint/src/unit_bindings.rs b/compiler/rustc_lint/src/unit_bindings.rs index c80889f3ae7..b74430d8fa0 100644 --- a/compiler/rustc_lint/src/unit_bindings.rs +++ b/compiler/rustc_lint/src/unit_bindings.rs @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for UnitBindings { && !matches!(init.kind, hir::ExprKind::Tup([])) && !matches!(local.pat.kind, hir::PatKind::Tuple([], ..)) { - cx.emit_spanned_lint( + 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 index 0386f2ec56c..3e10879e241 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -20,6 +20,7 @@ use rustc_span::symbol::Symbol; use rustc_span::symbol::{kw, sym}; use rustc_span::{BytePos, Span}; use std::iter; +use std::ops::ControlFlow; declare_lint! { /// The `unused_must_use` lint detects unused result of a type flagged as @@ -182,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let mut op_warned = false; if let Some(must_use_op) = must_use_op { - cx.emit_spanned_lint( + cx.emit_span_lint( UNUSED_MUST_USE, expr.span, UnusedOp { @@ -202,7 +203,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { } if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { - cx.emit_spanned_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); + cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); } fn check_fn_must_use( @@ -355,7 +356,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { Some(len) => is_ty_must_use(cx, ty, expr, span) .map(|inner| MustUsePath::Array(Box::new(inner), len)), }, - ty::Closure(..) => Some(MustUsePath::Closure(span)), + 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) { @@ -494,21 +495,21 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { ); } MustUsePath::Closure(span) => { - cx.emit_spanned_lint( + cx.emit_span_lint( UNUSED_MUST_USE, *span, UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, ); } MustUsePath::Coroutine(span) => { - cx.emit_spanned_lint( + 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_spanned_lint( + cx.emit_span_lint( UNUSED_MUST_USE, *span, UnusedDef { @@ -568,9 +569,9 @@ impl<'tcx> LateLintPass<'tcx> for PathStatements { } else { PathStatementDropSub::Help { span: s.span } }; - cx.emit_spanned_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub }) + cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub }) } else { - cx.emit_spanned_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect); + cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect); } } } @@ -651,9 +652,11 @@ trait UnusedDelimLint { fn is_expr_delims_necessary( inner: &ast::Expr, + ctx: UnusedDelimsCtx, followed_by_block: bool, - followed_by_else: 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, @@ -662,6 +665,13 @@ trait UnusedDelimLint { } } + // 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) }`. { let mut innermost = inner; @@ -744,21 +754,18 @@ trait UnusedDelimLint { // fn f(){(print!(รก // ``` use rustc_ast::visit::{walk_expr, Visitor}; - struct ErrExprVisitor { - has_error: bool, - } + struct ErrExprVisitor; impl<'ast> Visitor<'ast> for ErrExprVisitor { - fn visit_expr(&mut self, expr: &'ast ast::Expr) { - if let ExprKind::Err = expr.kind { - self.has_error = true; - return; + 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) } - walk_expr(self, expr) } } - let mut visitor = ErrExprVisitor { has_error: false }; - visitor.visit_expr(value); - if visitor.has_error { + if ErrExprVisitor.visit_expr(value).is_break() { return; } let spans = match value.kind { @@ -824,7 +831,7 @@ trait UnusedDelimLint { end_replace: hi_replace, } }); - cx.emit_spanned_lint( + cx.emit_span_lint( self.lint(), primary_span, UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion }, @@ -907,7 +914,7 @@ trait UnusedDelimLint { fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { match s.kind { - StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { + 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, @@ -1007,8 +1014,7 @@ impl UnusedDelimLint for UnusedParens { ) { match value.kind { ast::ExprKind::Paren(ref inner) => { - let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse; - if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else) + if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block) && value.attrs.is_empty() && !value.span.from_expansion() && (ctx != UnusedDelimsCtx::LetScrutineeExpr @@ -1071,17 +1077,31 @@ impl UnusedParens { 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 ExprKind::Binary(op, lhs, _rhs) = &e.kind - && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl) - && let ExprKind::Cast(_expr, ty) = &lhs.kind - && let ast::TyKind::Paren(_) = &ty.kind - { - self.parens_in_cast_in_lt.push(ty.id); + if let Some(ty_id) = self.cast_followed_by_lt(e) { + self.parens_in_cast_in_lt.push(ty_id); } match e.kind { @@ -1133,17 +1153,13 @@ impl EarlyLintPass for UnusedParens { } fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) { - if let ExprKind::Binary(op, lhs, _rhs) = &e.kind - && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl) - && let ExprKind::Cast(_expr, ty) = &lhs.kind - && let ast::TyKind::Paren(_) = &ty.kind - { + 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, + id, ty_id, "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor" ); } @@ -1156,7 +1172,7 @@ impl EarlyLintPass for UnusedParens { // 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(..) => {}, + | 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); @@ -1173,7 +1189,7 @@ impl EarlyLintPass for UnusedParens { } fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) { - if let StmtKind::Local(ref local) = s.kind { + if let StmtKind::Let(ref local) = s.kind { self.check_unused_parens_pat(cx, &local.pat, true, false, (true, false)); } @@ -1324,7 +1340,7 @@ impl UnusedDelimLint for UnusedBraces { // FIXME(const_generics): handle paths when #67075 is fixed. if let [stmt] = inner.stmts.as_slice() { if let ast::StmtKind::Expr(ref expr) = stmt.kind { - if !Self::is_expr_delims_necessary(expr, followed_by_block, false) + if !Self::is_expr_delims_necessary(expr, ctx, followed_by_block) && (ctx != UnusedDelimsCtx::AnonConst || (matches!(expr.kind, ast::ExprKind::Lit(_)) && !expr.span.from_expansion())) @@ -1497,7 +1513,7 @@ impl UnusedImportBraces { ast::UseTreeKind::Nested(_) => return, }; - cx.emit_spanned_lint( + cx.emit_span_lint( UNUSED_IMPORT_BRACES, item.span, UnusedImportBracesDiag { node: node_name }, @@ -1554,10 +1570,10 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAllocation { if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(_, m)) = adj.kind { match m { adjustment::AutoBorrowMutability::Not => { - cx.emit_spanned_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag); + cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag); } adjustment::AutoBorrowMutability::Mut { .. } => { - cx.emit_spanned_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag); + cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag); } }; } |
