diff options
Diffstat (limited to 'compiler/rustc_lint/src')
39 files changed, 2147 insertions, 1002 deletions
diff --git a/compiler/rustc_lint/src/autorefs.rs b/compiler/rustc_lint/src/autorefs.rs new file mode 100644 index 00000000000..845a1f1b81f --- /dev/null +++ b/compiler/rustc_lint/src/autorefs.rs @@ -0,0 +1,176 @@ +use rustc_ast::{BorrowKind, UnOp}; +use rustc_hir::{Expr, ExprKind, Mutability}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, OverloadedDeref}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::sym; + +use crate::lints::{ + ImplicitUnsafeAutorefsDiag, ImplicitUnsafeAutorefsMethodNote, ImplicitUnsafeAutorefsOrigin, + ImplicitUnsafeAutorefsSuggestion, +}; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `dangerous_implicit_autorefs` lint checks for implicitly taken references + /// to dereferences of raw pointers. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] { + /// unsafe { &raw mut (*ptr)[..16] } + /// // ^^^^^^ this calls `IndexMut::index_mut(&mut ..., ..16)`, + /// // implicitly creating a reference + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// When working with raw pointers it's usually undesirable to create references, + /// since they inflict additional safety requirements. Unfortunately, it's possible + /// to take a reference to the dereference of a raw pointer implicitly, which inflicts + /// the usual reference requirements. + /// + /// If you are sure that you can soundly take a reference, then you can take it explicitly: + /// + /// ```rust + /// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] { + /// unsafe { &raw mut (&mut *ptr)[..16] } + /// } + /// ``` + /// + /// Otherwise try to find an alternative way to achive your goals using only raw pointers: + /// + /// ```rust + /// use std::ptr; + /// + /// fn fun(ptr: *mut [u8]) -> *mut [u8] { + /// ptr::slice_from_raw_parts_mut(ptr.cast(), 16) + /// } + /// ``` + pub DANGEROUS_IMPLICIT_AUTOREFS, + Deny, + "implicit reference to a dereference of a raw pointer", + report_in_external_macro +} + +declare_lint_pass!(ImplicitAutorefs => [DANGEROUS_IMPLICIT_AUTOREFS]); + +impl<'tcx> LateLintPass<'tcx> for ImplicitAutorefs { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + // This logic has mostly been taken from + // <https://github.com/rust-lang/rust/pull/103735#issuecomment-1370420305> + + // 5. Either of the following: + // a. A deref followed by any non-deref place projection (that intermediate + // deref will typically be auto-inserted). + // b. A method call annotated with `#[rustc_no_implicit_refs]`. + // c. A deref followed by a `&raw const` or `&raw mut`. + let mut is_coming_from_deref = false; + let inner = match expr.kind { + ExprKind::AddrOf(BorrowKind::Raw, _, inner) => match inner.kind { + ExprKind::Unary(UnOp::Deref, inner) => { + is_coming_from_deref = true; + inner + } + _ => return, + }, + ExprKind::Index(base, _, _) => base, + ExprKind::MethodCall(_, inner, _, _) => { + // PERF: Checking of `#[rustc_no_implicit_refs]` is deferred below + // because checking for attribute is a bit costly. + inner + } + ExprKind::Field(inner, _) => inner, + _ => return, + }; + + let typeck = cx.typeck_results(); + let adjustments_table = typeck.adjustments(); + + if let Some(adjustments) = adjustments_table.get(inner.hir_id) + // 4. Any number of automatically inserted deref/derefmut calls. + && let adjustments = peel_derefs_adjustments(&**adjustments) + // 3. An automatically inserted reference (might come from a deref). + && let [adjustment] = adjustments + && let Some((borrow_mutbl, through_overloaded_deref)) = has_implicit_borrow(adjustment) + && let ExprKind::Unary(UnOp::Deref, dereferenced) = + // 2. Any number of place projections. + peel_place_mappers(inner).kind + // 1. Deref of a raw pointer. + && typeck.expr_ty(dereferenced).is_raw_ptr() + && let method_did = match expr.kind { + // PERF: 5. b. A method call annotated with `#[rustc_no_implicit_refs]` + ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + } + && method_did.map(|did| cx.tcx.has_attr(did, sym::rustc_no_implicit_autorefs)).unwrap_or(true) + { + cx.emit_span_lint( + DANGEROUS_IMPLICIT_AUTOREFS, + expr.span.source_callsite(), + ImplicitUnsafeAutorefsDiag { + raw_ptr_span: dereferenced.span, + raw_ptr_ty: typeck.expr_ty(dereferenced), + origin: if through_overloaded_deref { + ImplicitUnsafeAutorefsOrigin::OverloadedDeref + } else { + ImplicitUnsafeAutorefsOrigin::Autoref { + autoref_span: inner.span, + autoref_ty: typeck.expr_ty_adjusted(inner), + } + }, + method: method_did.map(|did| ImplicitUnsafeAutorefsMethodNote { + def_span: cx.tcx.def_span(did), + method_name: cx.tcx.item_name(did), + }), + suggestion: ImplicitUnsafeAutorefsSuggestion { + mutbl: borrow_mutbl.ref_prefix_str(), + deref: if is_coming_from_deref { "*" } else { "" }, + start_span: inner.span.shrink_to_lo(), + end_span: inner.span.shrink_to_hi(), + }, + }, + ) + } + } +} + +/// Peels expressions from `expr` that can map a place. +fn peel_place_mappers<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + loop { + match expr.kind { + ExprKind::Index(base, _idx, _) => expr = &base, + ExprKind::Field(e, _) => expr = &e, + _ => break expr, + } + } +} + +/// Peel derefs adjustments until the last last element. +fn peel_derefs_adjustments<'a>(mut adjs: &'a [Adjustment<'a>]) -> &'a [Adjustment<'a>] { + while let [Adjustment { kind: Adjust::Deref(_), .. }, end @ ..] = adjs + && !end.is_empty() + { + adjs = end; + } + adjs +} + +/// Test if some adjustment has some implicit borrow. +/// +/// Returns `Some((mutability, was_an_overloaded_deref))` if the argument adjustment is +/// an implicit borrow (or has an implicit borrow via an overloaded deref). +fn has_implicit_borrow(Adjustment { kind, .. }: &Adjustment<'_>) -> Option<(Mutability, bool)> { + match kind { + &Adjust::Deref(Some(OverloadedDeref { mutbl, .. })) => Some((mutbl, true)), + &Adjust::Borrow(AutoBorrow::Ref(mutbl)) => Some((mutbl.into(), false)), + Adjust::NeverToAny + | Adjust::Pointer(..) + | Adjust::ReborrowPin(..) + | Adjust::Deref(None) + | Adjust::Borrow(AutoBorrow::RawPtr(..)) => None, + } +} diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index f7be37dc4a2..69e9f8e1b2c 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -29,6 +29,7 @@ use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId}; use rustc_hir::intravisit::FnKind as HirFnKind; use rustc_hir::{Body, FnDecl, GenericParamKind, PatKind, PredicateOrigin}; use rustc_middle::bug; +use rustc_middle::lint::LevelAndSource; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Upcast, VariantDef}; @@ -38,7 +39,7 @@ pub use rustc_session::lint::builtin::*; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::edition::Edition; use rustc_span::source_map::Spanned; -use rustc_span::{BytePos, Ident, InnerSpan, Span, Symbol, kw, sym}; +use rustc_span::{BytePos, DUMMY_SP, Ident, InnerSpan, Span, Symbol, kw, sym}; use rustc_target::asm::InlineAsmArch; use rustc_trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; use rustc_trait_selection::traits::misc::type_allowed_to_implement_copy; @@ -330,7 +331,6 @@ impl EarlyLintPass for UnsafeCode { if let FnKind::Fn( ctxt, _, - _, ast::Fn { sig: ast::FnSig { header: ast::FnHeader { safety: ast::Safety::Unsafe(_), .. }, .. }, body, @@ -419,7 +419,7 @@ impl MissingDoc { return; } - let attrs = cx.tcx.hir().attrs(cx.tcx.local_def_id_to_hir_id(def_id)); + let attrs = cx.tcx.hir_attrs(cx.tcx.local_def_id_to_hir_id(def_id)); let has_doc = attrs.iter().any(has_doc); if !has_doc { cx.emit_span_lint( @@ -441,7 +441,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { // so we will continue to exclude them for compatibility. // // The documentation on `ExternCrate` is not used at the moment so no need to warn for it. - if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(_) = + if let hir::ItemKind::Impl(..) | hir::ItemKind::Use(..) | hir::ItemKind::ExternCrate(..) = it.kind { return; @@ -545,22 +545,22 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations { return; } let (def, ty) = match item.kind { - hir::ItemKind::Struct(_, ast_generics) => { - if !ast_generics.params.is_empty() { + hir::ItemKind::Struct(_, generics, _) => { + if !generics.params.is_empty() { return; } let def = cx.tcx.adt_def(item.owner_id); (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) } - hir::ItemKind::Union(_, ast_generics) => { - if !ast_generics.params.is_empty() { + hir::ItemKind::Union(_, generics, _) => { + if !generics.params.is_empty() { return; } let def = cx.tcx.adt_def(item.owner_id); (def, Ty::new_adt(cx.tcx, def, ty::List::empty())) } - hir::ItemKind::Enum(_, ast_generics) => { - if !ast_generics.params.is_empty() { + hir::ItemKind::Enum(_, generics, _) => { + if !generics.params.is_empty() { return; } let def = cx.tcx.adt_def(item.owner_id); @@ -635,7 +635,8 @@ fn type_implements_negative_copy_modulo_regions<'tcx>( typing_env: ty::TypingEnv<'tcx>, ) -> bool { let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); - let trait_ref = ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, None), [ty]); + let trait_ref = + ty::TraitRef::new(tcx, tcx.require_lang_item(hir::LangItem::Copy, DUMMY_SP), [ty]); let pred = ty::TraitPredicate { trait_ref, polarity: ty::PredicatePolarity::Negative }; let obligation = traits::Obligation { cause: traits::ObligationCause::dummy(), @@ -695,7 +696,8 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations { } // Avoid listing trait impls if the trait is allowed. - let (level, _) = cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()); + let LevelAndSource { level, .. } = + cx.tcx.lint_level_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()); if level == Level::Allow { return; } @@ -778,21 +780,19 @@ impl EarlyLintPass for AnonymousParameters { } if let ast::AssocItemKind::Fn(box Fn { ref sig, .. }) = it.kind { for arg in sig.decl.inputs.iter() { - if let ast::PatKind::Ident(_, ident, None) = arg.pat.kind { - if ident.name == kw::Empty { - let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span); + if let ast::PatKind::Missing = arg.pat.kind { + let ty_snip = cx.sess().source_map().span_to_snippet(arg.ty.span); - let (ty_snip, appl) = if let Ok(ref snip) = ty_snip { - (snip.as_str(), Applicability::MachineApplicable) - } else { - ("<type>", Applicability::HasPlaceholders) - }; - cx.emit_span_lint( - ANONYMOUS_PARAMETERS, - arg.pat.span, - BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip }, - ); - } + let (ty_snip, appl) = if let Ok(ref snip) = ty_snip { + (snip.as_str(), Applicability::MachineApplicable) + } else { + ("<type>", Applicability::HasPlaceholders) + }; + cx.emit_span_lint( + ANONYMOUS_PARAMETERS, + arg.pat.span, + BuiltinAnonymousParams { suggestion: (arg.pat.span, appl), ty_snip }, + ); } } } @@ -949,7 +949,7 @@ declare_lint! { /// /// ### Example /// - /// ```rust,compile_fail + /// ```rust,compile_fail,edition2021 /// #[no_mangle] /// const FOO: i32 = 5; /// ``` @@ -977,6 +977,9 @@ declare_lint! { /// ```rust /// #[unsafe(no_mangle)] /// fn foo<T>(t: T) {} + /// + /// #[unsafe(export_name = "bar")] + /// fn bar<T>(t: T) {} /// ``` /// /// {{produces}} @@ -984,10 +987,11 @@ declare_lint! { /// ### Explanation /// /// A function with generics must have its symbol mangled to accommodate - /// the generic parameter. The [`no_mangle` attribute] has no effect in - /// this situation, and should be removed. + /// the generic parameter. The [`no_mangle`] and [`export_name`] attributes + /// have no effect in this situation, and should be removed. /// - /// [`no_mangle` attribute]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute + /// [`no_mangle`]: https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute + /// [`export_name`]: https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute NO_MANGLE_GENERIC_ITEMS, Warn, "generic items must be mangled" @@ -997,8 +1001,8 @@ declare_lint_pass!(InvalidNoMangleItems => [NO_MANGLE_CONST_ITEMS, NO_MANGLE_GEN impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - let attrs = cx.tcx.hir().attrs(it.hir_id()); - let check_no_mangle_on_generic_fn = |no_mangle_attr: &hir::Attribute, + let attrs = cx.tcx.hir_attrs(it.hir_id()); + let check_no_mangle_on_generic_fn = |attr: &hir::Attribute, impl_generics: Option<&hir::Generics<'_>>, generics: &hir::Generics<'_>, span| { @@ -1011,7 +1015,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { cx.emit_span_lint( NO_MANGLE_GENERIC_ITEMS, span, - BuiltinNoMangleGeneric { suggestion: no_mangle_attr.span() }, + BuiltinNoMangleGeneric { suggestion: attr.span() }, ); break; } @@ -1020,8 +1024,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { }; match it.kind { hir::ItemKind::Fn { generics, .. } => { - if let Some(no_mangle_attr) = attr::find_by_name(attrs, sym::no_mangle) { - check_no_mangle_on_generic_fn(no_mangle_attr, None, generics, it.span); + if let Some(attr) = attr::find_by_name(attrs, sym::export_name) + .or_else(|| attr::find_by_name(attrs, sym::no_mangle)) + { + check_no_mangle_on_generic_fn(attr, None, generics, it.span); } } hir::ItemKind::Const(..) => { @@ -1049,11 +1055,12 @@ impl<'tcx> LateLintPass<'tcx> for InvalidNoMangleItems { hir::ItemKind::Impl(hir::Impl { generics, items, .. }) => { for it in *items { if let hir::AssocItemKind::Fn { .. } = it.kind { - if let Some(no_mangle_attr) = - attr::find_by_name(cx.tcx.hir().attrs(it.id.hir_id()), sym::no_mangle) + let attrs = cx.tcx.hir_attrs(it.id.hir_id()); + if let Some(attr) = attr::find_by_name(attrs, sym::export_name) + .or_else(|| attr::find_by_name(attrs, sym::no_mangle)) { check_no_mangle_on_generic_fn( - no_mangle_attr, + attr, Some(generics), cx.tcx.hir_get_generics(it.id.owner_id.def_id).unwrap(), it.span, @@ -1416,7 +1423,7 @@ impl TypeAliasBounds { impl<'tcx> LateLintPass<'tcx> for TypeAliasBounds { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - let hir::ItemKind::TyAlias(hir_ty, generics) = item.kind else { return }; + let hir::ItemKind::TyAlias(_, generics, hir_ty) = item.kind else { return }; // There must not be a where clause. if generics.predicates.is_empty() { @@ -1989,7 +1996,7 @@ impl ExplicitOutlivesRequirements { inferred_outlives .filter_map(|(clause, _)| match clause.kind().skip_binder() { - ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { + ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match a.kind() { ty::ReEarlyParam(ebr) if item_generics.region_param(ebr, tcx).def_id == lifetime.to_def_id() => { @@ -2039,7 +2046,7 @@ impl ExplicitOutlivesRequirements { let is_inferred = match tcx.named_bound_var(lifetime.hir_id) { Some(ResolvedArg::EarlyBound(def_id)) => inferred_outlives .iter() - .any(|r| matches!(**r, ty::ReEarlyParam(ebr) if { item_generics.region_param(ebr, tcx).def_id == def_id.to_def_id() })), + .any(|r| matches!(r.kind(), ty::ReEarlyParam(ebr) if { item_generics.region_param(ebr, tcx).def_id == def_id.to_def_id() })), _ => false, }; @@ -2119,9 +2126,9 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { use rustc_middle::middle::resolve_bound_vars::ResolvedArg; let def_id = item.owner_id.def_id; - if let hir::ItemKind::Struct(_, hir_generics) - | hir::ItemKind::Enum(_, hir_generics) - | hir::ItemKind::Union(_, hir_generics) = item.kind + if let hir::ItemKind::Struct(_, generics, _) + | hir::ItemKind::Enum(_, generics, _) + | hir::ItemKind::Union(_, generics, _) = item.kind { let inferred_outlives = cx.tcx.inferred_outlives_of(def_id); if inferred_outlives.is_empty() { @@ -2129,7 +2136,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } let ty_generics = cx.tcx.generics_of(def_id); - let num_where_predicates = hir_generics + let num_where_predicates = generics .predicates .iter() .filter(|predicate| predicate.kind.in_where_clause()) @@ -2139,7 +2146,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { let mut lint_spans = Vec::new(); let mut where_lint_spans = Vec::new(); let mut dropped_where_predicate_count = 0; - for (i, where_predicate) in hir_generics.predicates.iter().enumerate() { + for (i, where_predicate) in generics.predicates.iter().enumerate() { let (relevant_lifetimes, bounds, predicate_span, in_where_clause) = match where_predicate.kind { hir::WherePredicateKind::RegionPredicate(predicate) => { @@ -2222,7 +2229,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } else if i + 1 < num_where_predicates { // If all the bounds on a predicate were inferable and there are // further predicates, we want to eat the trailing comma. - let next_predicate_span = hir_generics.predicates[i + 1].span; + let next_predicate_span = generics.predicates[i + 1].span; if next_predicate_span.from_expansion() { where_lint_spans.push(predicate_span); } else { @@ -2231,7 +2238,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { } } else { // Eat the optional trailing comma after the last predicate. - let where_span = hir_generics.where_clause_span; + let where_span = generics.where_clause_span; if where_span.from_expansion() { where_lint_spans.push(predicate_span); } else { @@ -2249,18 +2256,18 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { // If all predicates in where clause are inferable, drop the entire clause // (including the `where`) - if hir_generics.has_where_clause_predicates + if generics.has_where_clause_predicates && dropped_where_predicate_count == num_where_predicates { - let where_span = hir_generics.where_clause_span; + let where_span = generics.where_clause_span; // Extend the where clause back to the closing `>` of the // generics, except for tuple struct, which have the `where` // after the fields of the struct. let full_where_span = - if let hir::ItemKind::Struct(hir::VariantData::Tuple(..), _) = item.kind { + if let hir::ItemKind::Struct(_, _, hir::VariantData::Tuple(..)) = item.kind { where_span } else { - hir_generics.span.shrink_to_hi().to(where_span) + generics.span.shrink_to_hi().to(where_span) }; // Due to macro expansions, the `full_where_span` might not actually contain all @@ -2582,34 +2589,35 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { ) -> Option<InitError> { let ty = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), ty).unwrap_or(ty); - use rustc_type_ir::TyKind::*; match ty.kind() { // Primitive types that don't like 0 as a value. - Ref(..) => Some("references must be non-null".into()), - Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()), - FnPtr(..) => Some("function pointers must be non-null".into()), - Never => Some("the `!` type has no valid value".into()), - RawPtr(ty, _) if matches!(ty.kind(), Dynamic(..)) => + ty::Ref(..) => Some("references must be non-null".into()), + ty::Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()), + ty::FnPtr(..) => Some("function pointers must be non-null".into()), + ty::Never => Some("the `!` type has no valid value".into()), + ty::RawPtr(ty, _) if matches!(ty.kind(), ty::Dynamic(..)) => // raw ptr to dyn Trait { Some("the vtable of a wide raw pointer must be non-null".into()) } // Primitive types with other constraints. - Bool if init == InitKind::Uninit => { + ty::Bool if init == InitKind::Uninit => { Some("booleans must be either `true` or `false`".into()) } - Char if init == InitKind::Uninit => { + ty::Char if init == InitKind::Uninit => { Some("characters must be a valid Unicode codepoint".into()) } - Int(_) | Uint(_) if init == InitKind::Uninit => { + ty::Int(_) | ty::Uint(_) if init == InitKind::Uninit => { Some("integers must be initialized".into()) } - Float(_) if init == InitKind::Uninit => Some("floats must be initialized".into()), - RawPtr(_, _) if init == InitKind::Uninit => { + ty::Float(_) if init == InitKind::Uninit => { + Some("floats must be initialized".into()) + } + ty::RawPtr(_, _) if init == InitKind::Uninit => { Some("raw pointers must be initialized".into()) } // Recurse and checks for some compound types. (but not unions) - Adt(adt_def, args) if !adt_def.is_union() => { + ty::Adt(adt_def, args) if !adt_def.is_union() => { // Handle structs. if adt_def.is_struct() { return variant_find_init_error( @@ -2675,11 +2683,11 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // We couldn't find anything wrong here. None } - Tuple(..) => { + ty::Tuple(..) => { // Proceed recursively, check all fields. ty.tuple_fields().iter().find_map(|field| ty_find_init_error(cx, field, init)) } - Array(ty, len) => { + ty::Array(ty, len) => { if matches!(len.try_to_target_usize(cx.tcx), Some(v) if v > 0) { // Array length known at array non-empty -- recurse. ty_find_init_error(cx, *ty, init) @@ -3115,6 +3123,7 @@ impl EarlyLintPass for SpecialModuleName { for item in &krate.items { if let ast::ItemKind::Mod( _, + ident, ast::ModKind::Unloaded | ast::ModKind::Loaded(_, ast::Inline::No, _, _), ) = item.kind { @@ -3122,7 +3131,7 @@ impl EarlyLintPass for SpecialModuleName { continue; } - match item.ident.name.as_str() { + match ident.name.as_str() { "lib" => cx.emit_span_lint( SPECIAL_MODULE_NAME, item.span, diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index cd4106ebf83..5679d4566dc 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use std::slice; +use rustc_ast::BindingMode; use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::sync; use rustc_data_structures::unord::UnordMap; @@ -14,14 +15,14 @@ use rustc_feature::Features; use rustc_hir::def::Res; use rustc_hir::def_id::{CrateNum, DefId}; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; +use rustc_hir::{Pat, PatKind}; use rustc_middle::bug; +use rustc_middle::lint::LevelAndSource; use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, Printer, with_no_trimmed_paths}; use rustc_middle::ty::{self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode}; -use rustc_session::lint::{ - FutureIncompatibleInfo, Level, Lint, LintBuffer, LintExpectationId, LintId, -}; +use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintBuffer, LintExpectationId, LintId}; use rustc_session::{LintStoreMarker, Session}; use rustc_span::edit_distance::find_best_match_for_names; use rustc_span::{Ident, Span, Symbol, sym}; @@ -82,11 +83,6 @@ enum TargetLint { Ignored, } -pub enum FindLintError { - NotFound, - Removed, -} - struct LintAlias { name: &'static str, /// Whether deprecation warnings should be suppressed for this alias. @@ -230,13 +226,24 @@ impl LintStore { } } - pub fn register_group_alias(&mut self, lint_name: &'static str, alias: &'static str) { - self.lint_groups.insert( + fn insert_group(&mut self, name: &'static str, group: LintGroup) { + let previous = self.lint_groups.insert(name, group); + if previous.is_some() { + bug!("group {name:?} already exists"); + } + } + + pub fn register_group_alias(&mut self, group_name: &'static str, alias: &'static str) { + let Some(LintGroup { lint_ids, .. }) = self.lint_groups.get(group_name) else { + bug!("group alias {alias:?} points to unregistered group {group_name:?}") + }; + + self.insert_group( alias, LintGroup { - lint_ids: vec![], + lint_ids: lint_ids.clone(), is_externally_loaded: false, - depr: Some(LintAlias { name: lint_name, silent: true }), + depr: Some(LintAlias { name: group_name, silent: true }), }, ); } @@ -248,24 +255,17 @@ impl LintStore { deprecated_name: Option<&'static str>, to: Vec<LintId>, ) { - let new = self - .lint_groups - .insert(name, LintGroup { lint_ids: to, is_externally_loaded, depr: None }) - .is_none(); if let Some(deprecated) = deprecated_name { - self.lint_groups.insert( + self.insert_group( deprecated, LintGroup { - lint_ids: vec![], + lint_ids: to.clone(), is_externally_loaded, depr: Some(LintAlias { name, silent: false }), }, ); } - - if !new { - bug!("duplicate specification of lint group {}", name); - } + self.insert_group(name, LintGroup { lint_ids: to, is_externally_loaded, depr: None }); } /// This lint should give no warning and have no effect. @@ -291,23 +291,15 @@ impl LintStore { self.by_name.insert(name.into(), Removed(reason.into())); } - pub fn find_lints(&self, mut lint_name: &str) -> Result<Vec<LintId>, FindLintError> { + pub fn find_lints(&self, lint_name: &str) -> Option<&[LintId]> { match self.by_name.get(lint_name) { - Some(&Id(lint_id)) => Ok(vec![lint_id]), - Some(&Renamed(_, lint_id)) => Ok(vec![lint_id]), - Some(&Removed(_)) => Err(FindLintError::Removed), - Some(&Ignored) => Ok(vec![]), - None => loop { - return match self.lint_groups.get(lint_name) { - Some(LintGroup { lint_ids, depr, .. }) => { - if let Some(LintAlias { name, .. }) = depr { - lint_name = name; - continue; - } - Ok(lint_ids.clone()) - } - None => Err(FindLintError::Removed), - }; + Some(Id(lint_id)) => Some(slice::from_ref(lint_id)), + Some(Renamed(_, lint_id)) => Some(slice::from_ref(lint_id)), + Some(Removed(_)) => None, + Some(Ignored) => Some(&[]), + None => match self.lint_groups.get(lint_name) { + Some(LintGroup { lint_ids, .. }) => Some(lint_ids), + None => None, }, } } @@ -373,8 +365,12 @@ impl LintStore { CheckLintNameResult::MissingTool }; } - Some(LintGroup { lint_ids, .. }) => { - return CheckLintNameResult::Tool(lint_ids, None); + Some(LintGroup { lint_ids, depr, .. }) => { + return if let &Some(LintAlias { name, silent: false }) = depr { + CheckLintNameResult::Tool(lint_ids, Some(name.to_string())) + } else { + CheckLintNameResult::Tool(lint_ids, None) + }; } }, Some(Id(id)) => return CheckLintNameResult::Tool(slice::from_ref(id), None), @@ -392,15 +388,11 @@ impl LintStore { None => self.check_tool_name_for_backwards_compat(&complete_name, "clippy"), Some(LintGroup { lint_ids, depr, .. }) => { // Check if the lint group name is deprecated - if let Some(LintAlias { name, silent }) = depr { - let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); - return if *silent { - CheckLintNameResult::Ok(lint_ids) - } else { - CheckLintNameResult::Tool(lint_ids, Some((*name).to_string())) - }; + if let &Some(LintAlias { name, silent: false }) = depr { + CheckLintNameResult::Tool(lint_ids, Some(name.to_string())) + } else { + CheckLintNameResult::Ok(lint_ids) } - CheckLintNameResult::Ok(lint_ids) } }, Some(Id(id)) => CheckLintNameResult::Ok(slice::from_ref(id)), @@ -411,7 +403,7 @@ impl LintStore { 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() { + if lint_name.chars().any(char::is_uppercase) && self.find_lints(&name_lower).is_some() { // First check if the lint name is (partly) in upper case instead of lower case... return CheckLintNameResult::NoLint(Some((Symbol::intern(&name_lower), false))); } @@ -454,18 +446,8 @@ impl LintStore { None => match self.lint_groups.get(&*complete_name) { // Now we are sure, that this lint exists nowhere None => self.no_lint_suggestion(lint_name, tool_name), - Some(LintGroup { lint_ids, depr, .. }) => { - // Reaching this would be weird, but let's cover this case anyway - if let Some(LintAlias { name, silent }) = depr { - let LintGroup { lint_ids, .. } = self.lint_groups.get(name).unwrap(); - if *silent { - CheckLintNameResult::Tool(lint_ids, Some(complete_name)) - } else { - CheckLintNameResult::Tool(lint_ids, Some((*name).to_string())) - } - } else { - CheckLintNameResult::Tool(lint_ids, Some(complete_name)) - } + Some(LintGroup { lint_ids, .. }) => { + CheckLintNameResult::Tool(lint_ids, Some(complete_name)) } }, Some(Id(id)) => CheckLintNameResult::Tool(slice::from_ref(id), Some(complete_name)), @@ -571,7 +553,7 @@ pub trait LintContext { } /// This returns the lint level for the given lint at the current location. - fn get_lint_level(&self, lint: &'static Lint) -> Level; + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource; /// This function can be used to manually fulfill an expectation. This can /// be used for lints which contain several spans, and should be suppressed, @@ -640,8 +622,8 @@ impl<'tcx> LintContext for LateContext<'tcx> { } } - fn get_lint_level(&self, lint: &'static Lint) -> Level { - self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs).0 + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs) } } @@ -661,8 +643,8 @@ impl LintContext for EarlyContext<'_> { self.builder.opt_span_lint(lint, span.map(|s| s.into()), decorate) } - fn get_lint_level(&self, lint: &'static Lint) -> Level { - self.builder.lint_level(lint).0 + fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { + self.builder.lint_level(lint) } } @@ -830,7 +812,10 @@ impl<'tcx> LateContext<'tcx> { return Ok(()); } - self.path.push(Symbol::intern(&disambiguated_data.data.to_string())); + self.path.push(match disambiguated_data.data.get_opt_name() { + Some(sym) => sym, + None => Symbol::intern(&disambiguated_data.data.to_string()), + }); Ok(()) } @@ -854,11 +839,11 @@ impl<'tcx> LateContext<'tcx> { &self, self_ty: Ty<'tcx>, trait_id: DefId, - name: &str, + name: Symbol, ) -> Option<Ty<'tcx>> { let tcx = self.tcx; tcx.associated_items(trait_id) - .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) + .find_by_ident_and_kind(tcx, Ident::with_dummy_span(name), ty::AssocTag::Type, trait_id) .and_then(|assoc| { let proj = Ty::new_projection(tcx, assoc.def_id, [self_ty]); tcx.try_normalize_erasing_regions(self.typing_env(), proj).ok() @@ -890,7 +875,12 @@ impl<'tcx> LateContext<'tcx> { } && let Some(init) = match parent_node { hir::Node::Expr(expr) => Some(expr), - hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init, + hir::Node::LetStmt(hir::LetStmt { + init, + // Binding is immutable, init cannot be re-assigned + pat: Pat { kind: PatKind::Binding(BindingMode::NONE, ..), .. }, + .. + }) => *init, _ => None, } { @@ -935,7 +925,12 @@ impl<'tcx> LateContext<'tcx> { } && let Some(init) = match parent_node { hir::Node::Expr(expr) => Some(expr), - hir::Node::LetStmt(hir::LetStmt { init, .. }) => *init, + hir::Node::LetStmt(hir::LetStmt { + init, + // Binding is immutable, init cannot be re-assigned + pat: Pat { kind: PatKind::Binding(BindingMode::NONE, ..), .. }, + .. + }) => *init, hir::Node::Item(item) => match item.kind { hir::ItemKind::Const(.., body_id) | hir::ItemKind::Static(.., body_id) => { Some(self.tcx.hir_body(body_id).value) diff --git a/compiler/rustc_lint/src/default_could_be_derived.rs b/compiler/rustc_lint/src/default_could_be_derived.rs index 59e38a882dd..7734f441df2 100644 --- a/compiler/rustc_lint/src/default_could_be_derived.rs +++ b/compiler/rustc_lint/src/default_could_be_derived.rs @@ -93,7 +93,11 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived { let orig_fields = match cx.tcx.hir_get_if_local(type_def_id) { Some(hir::Node::Item(hir::Item { kind: - hir::ItemKind::Struct(hir::VariantData::Struct { fields, recovered: _ }, _generics), + hir::ItemKind::Struct( + _, + _generics, + hir::VariantData::Struct { fields, recovered: _ }, + ), .. })) => fields.iter().map(|f| (f.ident.name, f)).collect::<FxHashMap<_, _>>(), _ => return, @@ -133,7 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for DefaultCouldBeDerived { return; } - // At least one of the fields with a default value have been overriden in + // At least one of the fields with a default value have been overridden in // the `Default` implementation. We suggest removing it and relying on `..` // instead. let any_default_field_given = diff --git a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs index ec8f8441575..5989ef9519c 100644 --- a/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs +++ b/compiler/rustc_lint/src/deref_into_dyn_supertrait.rs @@ -69,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for DerefIntoDynSupertrait { && let ty::Dynamic(data, _, ty::Dyn) = self_ty.kind() && let Some(self_principal) = data.principal() // `<T as Deref>::Target` is `dyn target_principal` - && let Some(target) = cx.get_associated_type(self_ty, did, "Target") + && let Some(target) = cx.get_associated_type(self_ty, did, sym::Target) && let ty::Dynamic(data, _, ty::Dyn) = target.kind() && let Some(target_principal) = data.principal() // `target_principal` is a supertrait of `t_principal` diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index 723b894c43b..12666d383f9 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -18,7 +18,7 @@ use tracing::debug; use crate::context::{EarlyContext, LintContext, LintStore}; use crate::passes::{EarlyLintPass, EarlyLintPassObject}; -mod diagnostics; +pub(super) mod diagnostics; macro_rules! lint_callback { ($cx:expr, $f:ident, $($args:expr),*) => ({ $cx.pass.$f(&$cx.context, $($args),*); @@ -40,7 +40,7 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { for early_lint in self.context.buffered.take(id) { let BufferedEarlyLint { span, node_id: _, lint_id, diagnostic } = early_lint; self.context.opt_span_lint(lint_id.lint, span, |diag| { - diagnostics::decorate_lint(self.context.sess(), self.tcx, diagnostic, diag); + diagnostics::decorate_builtin_lint(self.context.sess(), self.tcx, diagnostic, diag); }); } } @@ -74,8 +74,8 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> for EarlyContextAndPass<'ecx, 'tcx, T> { - fn visit_coroutine_kind(&mut self, coroutine_kind: &'ast ast::CoroutineKind) -> Self::Result { - self.check_id(coroutine_kind.closure_id()); + fn visit_id(&mut self, id: rustc_ast::NodeId) { + self.check_id(id); } fn visit_param(&mut self, param: &'ast ast::Param) { @@ -101,7 +101,6 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> fn visit_pat(&mut self, p: &'ast ast::Pat) { lint_callback!(self, check_pat, p); - self.check_id(p.id); ast_visit::walk_pat(self, p); lint_callback!(self, check_pat_post, p); } @@ -112,11 +111,6 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> }); } - fn visit_anon_const(&mut self, c: &'ast ast::AnonConst) { - self.check_id(c.id); - ast_visit::walk_anon_const(self, c); - } - fn visit_expr(&mut self, e: &'ast ast::Expr) { self.with_lint_attrs(e.id, &e.attrs, |cx| { lint_callback!(cx, check_expr, e); @@ -142,7 +136,6 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> // the AST struct that they wrap (e.g. an item) self.with_lint_attrs(s.id, s.attrs(), |cx| { lint_callback!(cx, check_stmt, s); - cx.check_id(s.id); }); // The visitor for the AST struct wrapped // by the statement (e.g. `Item`) will call @@ -153,17 +146,9 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> fn visit_fn(&mut self, fk: ast_visit::FnKind<'ast>, span: Span, id: ast::NodeId) { lint_callback!(self, check_fn, fk, span, id); - self.check_id(id); ast_visit::walk_fn(self, fk); } - fn visit_variant_data(&mut self, s: &'ast ast::VariantData) { - if let Some(ctor_node_id) = s.ctor_node_id() { - self.check_id(ctor_node_id); - } - ast_visit::walk_struct_def(self, s); - } - fn visit_field_def(&mut self, s: &'ast ast::FieldDef) { self.with_lint_attrs(s.id, &s.attrs, |cx| { ast_visit::walk_field_def(cx, s); @@ -179,7 +164,6 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> fn visit_ty(&mut self, t: &'ast ast::Ty) { lint_callback!(self, check_ty, t); - self.check_id(t.id); ast_visit::walk_ty(self, t); } @@ -196,7 +180,6 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> fn visit_block(&mut self, b: &'ast ast::Block) { lint_callback!(self, check_block, b); - self.check_id(b.id); ast_visit::walk_block(self, b); } @@ -241,7 +224,7 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> ast_visit::AssocCtxt::Trait => { lint_callback!(cx, check_trait_item, item); } - ast_visit::AssocCtxt::Impl => { + ast_visit::AssocCtxt::Impl { .. } => { lint_callback!(cx, check_impl_item, item); } } @@ -250,36 +233,20 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> ast_visit::AssocCtxt::Trait => { lint_callback!(cx, check_trait_item_post, item); } - ast_visit::AssocCtxt::Impl => { + ast_visit::AssocCtxt::Impl { .. } => { lint_callback!(cx, check_impl_item_post, item); } } }); } - fn visit_lifetime(&mut self, lt: &'ast ast::Lifetime, _: ast_visit::LifetimeCtxt) { - self.check_id(lt.id); - ast_visit::walk_lifetime(self, lt); - } - - fn visit_path(&mut self, p: &'ast ast::Path, id: ast::NodeId) { - self.check_id(id); - ast_visit::walk_path(self, p); - } - - fn visit_path_segment(&mut self, s: &'ast ast::PathSegment) { - self.check_id(s.id); - ast_visit::walk_path_segment(self, s); - } - fn visit_attribute(&mut self, attr: &'ast ast::Attribute) { lint_callback!(self, check_attribute, attr); ast_visit::walk_attribute(self, attr); } - fn visit_mac_def(&mut self, mac: &'ast ast::MacroDef, id: ast::NodeId) { + fn visit_macro_def(&mut self, mac: &'ast ast::MacroDef) { lint_callback!(self, check_mac_def, mac); - self.check_id(id); } fn visit_mac_call(&mut self, mac: &'ast ast::MacCall) { diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 40ca9e05d95..60c477dd6c7 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -10,15 +10,15 @@ use rustc_errors::{ use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; use rustc_session::Session; -use rustc_session::lint::{BuiltinLintDiag, ElidedLifetimeResolution}; -use rustc_span::{BytePos, kw}; +use rustc_session::lint::BuiltinLintDiag; +use rustc_span::BytePos; use tracing::debug; -use crate::lints::{self, ElidedNamedLifetime}; +use crate::lints; mod check_cfg; -pub(super) fn decorate_lint( +pub fn decorate_builtin_lint( sess: &Session, tcx: Option<TyCtxt<'_>>, diagnostic: BuiltinLintDiag, @@ -187,6 +187,27 @@ pub(super) fn decorate_lint( lints::ReservedMultihash { suggestion }.decorate_lint(diag); } } + BuiltinLintDiag::HiddenUnicodeCodepoints { + label, + count, + span_label, + labels, + escape, + spans, + } => { + lints::HiddenUnicodeCodepointsDiag { + label: &label, + count, + span_label, + labels: labels.map(|spans| lints::HiddenUnicodeCodepointsDiagLabels { spans }), + sub: if escape { + lints::HiddenUnicodeCodepointsDiagSub::Escape { spans } + } else { + lints::HiddenUnicodeCodepointsDiagSub::NoEscape { spans } + }, + } + .decorate_lint(diag); + } BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => { lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag); } @@ -292,8 +313,8 @@ pub(super) fn decorate_lint( BuiltinLintDiag::ByteSliceInPackedStructWithDerive { ty } => { lints::ByteSliceInPackedStructWithDerive { ty }.decorate_lint(diag); } - BuiltinLintDiag::UnusedExternCrate { removal_span } => { - lints::UnusedExternCrate { removal_span }.decorate_lint(diag); + BuiltinLintDiag::UnusedExternCrate { span, removal_span } => { + lints::UnusedExternCrate { span, removal_span }.decorate_lint(diag); } BuiltinLintDiag::ExternCrateNotIdiomatic { vis_span, ident_span } => { let suggestion_span = vis_span.between(ident_span); @@ -450,16 +471,5 @@ pub(super) fn decorate_lint( BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by } => { lints::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by }.decorate_lint(diag) } - BuiltinLintDiag::ElidedNamedLifetimes { elided: (span, kind), resolution } => { - match resolution { - ElidedLifetimeResolution::Static => { - ElidedNamedLifetime { span, kind, name: kw::StaticLifetime, declaration: None } - } - ElidedLifetimeResolution::Param(name, declaration) => { - ElidedNamedLifetime { span, kind, name, declaration: Some(declaration) } - } - } - .decorate_lint(diag) - } } } diff --git a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs index 946dbc34f71..e2f5dd315d5 100644 --- a/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs +++ b/compiler/rustc_lint/src/early/diagnostics/check_cfg.rs @@ -140,6 +140,14 @@ pub(super) fn unexpected_cfg_name( let code_sugg = if is_feature_cfg && is_from_cargo { lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures + // Suggest correct `version("..")` predicate syntax + } else if let Some((_value, value_span)) = value + && name == sym::version + { + lints::unexpected_cfg_name::CodeSuggestion::VersionSyntax { + between_name_and_value: name_span.between(value_span), + after_value: value_span.shrink_to_hi(), + } // Suggest the most probable if we found one } else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) { is_feature_cfg |= best_match == sym::feature; diff --git a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs index 7ead8eafbd5..179f2bcf07f 100644 --- a/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs +++ b/compiler/rustc_lint/src/enum_intrinsics_non_enums.rs @@ -1,6 +1,5 @@ use rustc_hir as hir; -use rustc_middle::ty::Ty; -use rustc_middle::ty::visit::TypeVisitableExt; +use rustc_middle::ty::{Ty, TypeVisitableExt}; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::{Span, sym}; diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index d109a5c9030..586e55c8055 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -1,5 +1,5 @@ use rustc_errors::codes::*; -use rustc_errors::{Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic}; +use rustc_errors::{Diag, EmissionGuarantee, Subdiagnostic}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_session::lint::Level; use rustc_span::{Span, Symbol}; @@ -26,11 +26,7 @@ pub(crate) enum OverruledAttributeSub { } impl Subdiagnostic for OverruledAttributeSub { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { match self { OverruledAttributeSub::DefaultSource { id } => { diag.note(fluent::lint_default_source); diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 9ca148e1f25..4c2b82a9a23 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -38,7 +38,7 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) { } LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { // We are an `eval_always` query, so looking at the attribute's `AttrId` is ok. - let attr_id = tcx.hir().attrs(hir_id)[attr_index as usize].id(); + let attr_id = tcx.hir_attrs(hir_id)[attr_index as usize].id(); (attr_id, lint_index) } diff --git a/compiler/rustc_lint/src/for_loops_over_fallibles.rs b/compiler/rustc_lint/src/for_loops_over_fallibles.rs index 757fc1f58bd..a56b753bda7 100644 --- a/compiler/rustc_lint/src/for_loops_over_fallibles.rs +++ b/compiler/rustc_lint/src/for_loops_over_fallibles.rs @@ -49,6 +49,8 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let Some((pat, arg)) = extract_for_loop(expr) else { return }; + let arg_span = arg.span.source_callsite(); + let ty = cx.typeck_results().expr_ty(arg); let (adt, args, ref_mutability) = match ty.kind() { @@ -78,27 +80,27 @@ impl<'tcx> LateLintPass<'tcx> for ForLoopsOverFallibles { && let Ok(recv_snip) = cx.sess().source_map().span_to_snippet(recv.span) { ForLoopsOverFalliblesLoopSub::RemoveNext { - suggestion: recv.span.between(arg.span.shrink_to_hi()), + suggestion: recv.span.between(arg_span.shrink_to_hi()), recv_snip, } } else { ForLoopsOverFalliblesLoopSub::UseWhileLet { start_span: expr.span.with_hi(pat.span.lo()), - end_span: pat.span.between(arg.span), + end_span: pat.span.between(arg_span), var, } }; let question_mark = suggest_question_mark(cx, adt, args, expr.span) - .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg.span.shrink_to_hi() }); + .then(|| ForLoopsOverFalliblesQuestionMark { suggestion: arg_span.shrink_to_hi() }); let suggestion = ForLoopsOverFalliblesSuggestion { var, start_span: expr.span.with_hi(pat.span.lo()), - end_span: pat.span.between(arg.span), + end_span: pat.span.between(arg_span), }; cx.emit_span_lint( FOR_LOOPS_OVER_FALLIBLES, - arg.span, + arg_span, ForLoopsOverFalliblesDiag { article, ref_prefix, ty, sub, question_mark, suggestion }, ); } diff --git a/compiler/rustc_lint/src/foreign_modules.rs b/compiler/rustc_lint/src/foreign_modules.rs index 6175415c31f..d0668794198 100644 --- a/compiler/rustc_lint/src/foreign_modules.rs +++ b/compiler/rustc_lint/src/foreign_modules.rs @@ -104,7 +104,7 @@ impl ClashingExternDeclarations { /// for the item, return its HirId without updating the set. fn insert(&mut self, tcx: TyCtxt<'_>, fi: hir::ForeignItemId) -> Option<hir::OwnerId> { let did = fi.owner_id.to_def_id(); - let instance = Instance::new(did, ty::List::identity_for_item(tcx, did)); + let instance = Instance::new_raw(did, ty::List::identity_for_item(tcx, did)); let name = Symbol::intern(tcx.symbol_name(instance).name); if let Some(&existing_id) = self.seen_decls.get(&name) { // Avoid updating the map with the new entry when we do find a collision. We want to @@ -270,14 +270,12 @@ fn structurally_same_type_impl<'tcx>( true } else { // Do a full, depth-first comparison between the two. - use rustc_type_ir::TyKind::*; - let is_primitive_or_pointer = - |ty: Ty<'tcx>| ty.is_primitive() || matches!(ty.kind(), RawPtr(..) | Ref(..)); + |ty: Ty<'tcx>| ty.is_primitive() || matches!(ty.kind(), ty::RawPtr(..) | ty::Ref(..)); ensure_sufficient_stack(|| { match (a.kind(), b.kind()) { - (&Adt(a_def, a_gen_args), &Adt(b_def, b_gen_args)) => { + (&ty::Adt(a_def, a_gen_args), &ty::Adt(b_def, b_gen_args)) => { // Only `repr(C)` types can be compared structurally. if !(a_def.repr().c() && b_def.repr().c()) { return false; @@ -308,30 +306,30 @@ fn structurally_same_type_impl<'tcx>( }, ) } - (Array(a_ty, a_len), Array(b_ty, b_len)) => { + (ty::Array(a_ty, a_len), ty::Array(b_ty, b_len)) => { // For arrays, we also check the length. a_len == b_len && structurally_same_type_impl( seen_types, tcx, typing_env, *a_ty, *b_ty, ckind, ) } - (Slice(a_ty), Slice(b_ty)) => { + (ty::Slice(a_ty), ty::Slice(b_ty)) => { structurally_same_type_impl(seen_types, tcx, typing_env, *a_ty, *b_ty, ckind) } - (RawPtr(a_ty, a_mutbl), RawPtr(b_ty, b_mutbl)) => { + (ty::RawPtr(a_ty, a_mutbl), ty::RawPtr(b_ty, b_mutbl)) => { a_mutbl == b_mutbl && structurally_same_type_impl( seen_types, tcx, typing_env, *a_ty, *b_ty, ckind, ) } - (Ref(_a_region, a_ty, a_mut), Ref(_b_region, b_ty, b_mut)) => { + (ty::Ref(_a_region, a_ty, a_mut), ty::Ref(_b_region, b_ty, b_mut)) => { // For structural sameness, we don't need the region to be same. a_mut == b_mut && structurally_same_type_impl( seen_types, tcx, typing_env, *a_ty, *b_ty, ckind, ) } - (FnDef(..), FnDef(..)) => { + (ty::FnDef(..), ty::FnDef(..)) => { let a_poly_sig = a.fn_sig(tcx); let b_poly_sig = b.fn_sig(tcx); @@ -354,35 +352,38 @@ fn structurally_same_type_impl<'tcx>( ckind, ) } - (Tuple(..), Tuple(..)) => { + (ty::Tuple(..), ty::Tuple(..)) => { // Tuples are not `repr(C)` so these cannot be compared structurally. false } // For these, it's not quite as easy to define structural-sameness quite so easily. // For the purposes of this lint, take the conservative approach and mark them as // not structurally same. - (Dynamic(..), Dynamic(..)) - | (Error(..), Error(..)) - | (Closure(..), Closure(..)) - | (Coroutine(..), Coroutine(..)) - | (CoroutineWitness(..), CoroutineWitness(..)) - | (Alias(ty::Projection, ..), Alias(ty::Projection, ..)) - | (Alias(ty::Inherent, ..), Alias(ty::Inherent, ..)) - | (Alias(ty::Opaque, ..), Alias(ty::Opaque, ..)) => false, + (ty::Dynamic(..), ty::Dynamic(..)) + | (ty::Error(..), ty::Error(..)) + | (ty::Closure(..), ty::Closure(..)) + | (ty::Coroutine(..), ty::Coroutine(..)) + | (ty::CoroutineWitness(..), ty::CoroutineWitness(..)) + | (ty::Alias(ty::Projection, ..), ty::Alias(ty::Projection, ..)) + | (ty::Alias(ty::Inherent, ..), ty::Alias(ty::Inherent, ..)) + | (ty::Alias(ty::Opaque, ..), ty::Alias(ty::Opaque, ..)) => false, // These definitely should have been caught above. - (Bool, Bool) | (Char, Char) | (Never, Never) | (Str, Str) => unreachable!(), + (ty::Bool, ty::Bool) + | (ty::Char, ty::Char) + | (ty::Never, ty::Never) + | (ty::Str, ty::Str) => unreachable!(), // An Adt and a primitive or pointer type. This can be FFI-safe if non-null // enum layout optimisation is being applied. - (Adt(..) | Pat(..), _) if is_primitive_or_pointer(b) => { + (ty::Adt(..) | ty::Pat(..), _) if is_primitive_or_pointer(b) => { if let Some(a_inner) = types::repr_nullable_ptr(tcx, typing_env, a, ckind) { a_inner == b } else { false } } - (_, Adt(..) | Pat(..)) if is_primitive_or_pointer(a) => { + (_, ty::Adt(..) | ty::Pat(..)) if is_primitive_or_pointer(a) => { if let Some(b_inner) = types::repr_nullable_ptr(tcx, typing_env, b, ckind) { b_inner == a } else { diff --git a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs b/compiler/rustc_lint/src/hidden_unicode_codepoints.rs deleted file mode 100644 index 491c2826baa..00000000000 --- a/compiler/rustc_lint/src/hidden_unicode_codepoints.rs +++ /dev/null @@ -1,136 +0,0 @@ -use ast::util::unicode::{TEXT_FLOW_CONTROL_CHARS, contains_text_flow_control_chars}; -use rustc_ast as ast; -use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::{BytePos, Span, Symbol}; - -use crate::lints::{ - HiddenUnicodeCodepointsDiag, HiddenUnicodeCodepointsDiagLabels, HiddenUnicodeCodepointsDiagSub, -}; -use crate::{EarlyContext, EarlyLintPass, LintContext}; - -declare_lint! { - #[allow(text_direction_codepoint_in_literal)] - /// The `text_direction_codepoint_in_literal` lint detects Unicode codepoints that change the - /// visual representation of text on screen in a way that does not correspond to their on - /// memory representation. - /// - /// ### Explanation - /// - /// The unicode characters `\u{202A}`, `\u{202B}`, `\u{202D}`, `\u{202E}`, `\u{2066}`, - /// `\u{2067}`, `\u{2068}`, `\u{202C}` and `\u{2069}` make the flow of text on screen change - /// its direction on software that supports these codepoints. This makes the text "abc" display - /// as "cba" on screen. By leveraging software that supports these, people can write specially - /// crafted literals that make the surrounding code seem like it's performing one action, when - /// in reality it is performing another. Because of this, we proactively lint against their - /// presence to avoid surprises. - /// - /// ### Example - /// - /// ```rust,compile_fail - /// #![deny(text_direction_codepoint_in_literal)] - /// fn main() { - /// println!("{:?}", ''); - /// } - /// ``` - /// - /// {{produces}} - /// - pub TEXT_DIRECTION_CODEPOINT_IN_LITERAL, - Deny, - "detect special Unicode codepoints that affect the visual representation of text on screen, \ - changing the direction in which text flows", -} - -declare_lint_pass!(HiddenUnicodeCodepoints => [TEXT_DIRECTION_CODEPOINT_IN_LITERAL]); - -impl HiddenUnicodeCodepoints { - fn lint_text_direction_codepoint( - &self, - cx: &EarlyContext<'_>, - text: Symbol, - span: Span, - padding: u32, - point_at_inner_spans: bool, - label: &str, - ) { - // Obtain the `Span`s for each of the forbidden chars. - let spans: Vec<_> = text - .as_str() - .char_indices() - .filter_map(|(i, c)| { - TEXT_FLOW_CONTROL_CHARS.contains(&c).then(|| { - let lo = span.lo() + BytePos(i as u32 + padding); - (c, span.with_lo(lo).with_hi(lo + BytePos(c.len_utf8() as u32))) - }) - }) - .collect(); - - let count = spans.len(); - let labels = point_at_inner_spans - .then_some(HiddenUnicodeCodepointsDiagLabels { spans: spans.clone() }); - let sub = if point_at_inner_spans && !spans.is_empty() { - HiddenUnicodeCodepointsDiagSub::Escape { spans } - } else { - HiddenUnicodeCodepointsDiagSub::NoEscape { spans } - }; - - cx.emit_span_lint( - TEXT_DIRECTION_CODEPOINT_IN_LITERAL, - span, - HiddenUnicodeCodepointsDiag { label, count, span_label: span, labels, sub }, - ); - } - - fn check_literal( - &mut self, - cx: &EarlyContext<'_>, - text: Symbol, - lit_kind: ast::token::LitKind, - span: Span, - label: &'static str, - ) { - if !contains_text_flow_control_chars(text.as_str()) { - return; - } - let (padding, point_at_inner_spans) = match lit_kind { - // account for `"` or `'` - ast::token::LitKind::Str | ast::token::LitKind::Char => (1, true), - // account for `c"` - ast::token::LitKind::CStr => (2, true), - // account for `r###"` - ast::token::LitKind::StrRaw(n) => (n as u32 + 2, true), - // account for `cr###"` - ast::token::LitKind::CStrRaw(n) => (n as u32 + 3, true), - // suppress bad literals. - ast::token::LitKind::Err(_) => return, - // Be conservative just in case new literals do support these. - _ => (0, false), - }; - self.lint_text_direction_codepoint(cx, text, span, padding, point_at_inner_spans, label); - } -} - -impl EarlyLintPass for HiddenUnicodeCodepoints { - fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &ast::Attribute) { - if let ast::AttrKind::DocComment(_, comment) = attr.kind { - if contains_text_flow_control_chars(comment.as_str()) { - self.lint_text_direction_codepoint(cx, comment, attr.span, 0, false, "doc comment"); - } - } - } - - #[inline] - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { - // byte strings are already handled well enough by `EscapeError::NonAsciiCharInByteString` - match &expr.kind { - ast::ExprKind::Lit(token_lit) => { - self.check_literal(cx, token_lit.symbol, token_lit.kind, expr.span, "literal"); - } - ast::ExprKind::FormatArgs(args) => { - let (lit_kind, text) = args.uncooked_fmt_str; - self.check_literal(cx, text, lit_kind, args.span, "format string"); - } - _ => {} - }; - } -} diff --git a/compiler/rustc_lint/src/if_let_rescope.rs b/compiler/rustc_lint/src/if_let_rescope.rs index 39ea8d8e324..a9b04511c6b 100644 --- a/compiler/rustc_lint/src/if_let_rescope.rs +++ b/compiler/rustc_lint/src/if_let_rescope.rs @@ -3,9 +3,7 @@ use std::ops::ControlFlow; use hir::intravisit::{self, Visitor}; use rustc_ast::Recovered; -use rustc_errors::{ - Applicability, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic, SuggestionStyle, -}; +use rustc_errors::{Applicability, Diag, EmissionGuarantee, Subdiagnostic, SuggestionStyle}; use rustc_hir::{self as hir, HirIdSet}; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::adjustment::Adjust; @@ -327,11 +325,7 @@ struct IfLetRescopeRewrite { } impl Subdiagnostic for IfLetRescopeRewrite { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { let mut suggestions = vec![]; for match_head in self.match_heads { match match_head { @@ -360,7 +354,7 @@ impl Subdiagnostic for IfLetRescopeRewrite { .chain(repeat('}').take(closing_brackets.count)) .collect(), )); - let msg = f(diag, crate::fluent_generated::lint_suggestion); + let msg = diag.eagerly_translate(crate::fluent_generated::lint_suggestion); diag.multipart_suggestion_with_style( msg, suggestions, diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs index 0b3af7d6aba..8124d7f7c86 100644 --- a/compiler/rustc_lint/src/impl_trait_overcaptures.rs +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -15,7 +15,8 @@ use rustc_middle::ty::relate::{ Relate, RelateResult, TypeRelation, structurally_relate_consts, structurally_relate_tys, }; use rustc_middle::ty::{ - self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, + self, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, + TypeVisitor, }; use rustc_middle::{bug, span_bug}; use rustc_session::lint::FutureIncompatibilityReason; @@ -41,7 +42,7 @@ declare_lint! { /// /// ### Example /// - /// ```rust,compile_fail + /// ```rust,compile_fail,edition2021 /// # #![deny(impl_trait_overcaptures)] /// # use std::fmt::Display; /// let mut x = vec![]; @@ -209,7 +210,7 @@ where VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>, OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>, { - fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) { + fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) { // When we get into a binder, we need to add its own bound vars to the scope. let mut added = vec![]; for arg in t.bound_vars() { @@ -392,7 +393,7 @@ where } _ => { self.tcx.dcx().span_delayed_bug( - self.tcx.hir().span(arg.hir_id()), + self.tcx.hir_span(arg.hir_id()), "no valid for captured arg", ); } @@ -460,8 +461,8 @@ fn extract_def_id_from_arg<'tcx>( generics: &'tcx ty::Generics, arg: ty::GenericArg<'tcx>, ) -> DefId { - match arg.unpack() { - ty::GenericArgKind::Lifetime(re) => match *re { + match arg.kind() { + ty::GenericArgKind::Lifetime(re) => match re.kind() { ty::ReEarlyParam(ebr) => generics.region_param(ebr, tcx).def_id, ty::ReBound( _, @@ -506,9 +507,9 @@ impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> { self.tcx } - fn relate_with_variance<T: ty::relate::Relate<TyCtxt<'tcx>>>( + fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>( &mut self, - variance: rustc_type_ir::Variance, + variance: ty::Variance, _: ty::VarianceDiagInfo<TyCtxt<'tcx>>, a: T, b: T, @@ -530,7 +531,7 @@ impl<'tcx> TypeRelation<TyCtxt<'tcx>> for FunctionalVariances<'tcx> { a: ty::Region<'tcx>, _: ty::Region<'tcx>, ) -> RelateResult<'tcx, ty::Region<'tcx>> { - let def_id = match *a { + let def_id = match a.kind() { ty::ReEarlyParam(ebr) => self.generics.region_param(ebr, self.tcx).def_id, ty::ReBound( _, diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 1f999bbea5f..1805a674d68 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -1,24 +1,21 @@ //! Some lints that are only useful in the compiler or crates that use compiler internals, such as //! Clippy. -use rustc_ast as ast; +use rustc_hir::HirId; use rustc_hir::def::Res; use rustc_hir::def_id::DefId; -use rustc_hir::{ - AmbigArg, BinOp, BinOpKind, Expr, ExprKind, GenericArg, HirId, Impl, Item, ItemKind, Node, Pat, - PatExpr, PatExprKind, PatKind, Path, PathSegment, QPath, Ty, TyKind, -}; use rustc_middle::ty::{self, GenericArgsRef, Ty as MiddleTy}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::{Span, sym}; use tracing::debug; +use {rustc_ast as ast, rustc_hir as hir}; use crate::lints::{ BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonGlobImportTypeIrInherent, QueryInstability, QueryUntracked, SpanUseEqCtxtDiag, SymbolInternStringLiteralDiag, TyQualified, TykindDiag, TykindKind, TypeIrInherentUsage, - UntranslatableDiag, + TypeIrTraitUsage, UntranslatableDiag, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; @@ -37,9 +34,12 @@ declare_tool_lint! { declare_lint_pass!(DefaultHashTypes => [DEFAULT_HASH_TYPES]); impl LateLintPass<'_> for DefaultHashTypes { - fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) { + fn check_path(&mut self, cx: &LateContext<'_>, path: &hir::Path<'_>, hir_id: HirId) { let Res::Def(rustc_hir::def::DefKind::Struct, def_id) = path.res else { return }; - if matches!(cx.tcx.hir_node(hir_id), Node::Item(Item { kind: ItemKind::Use(..), .. })) { + if matches!( + cx.tcx.hir_node(hir_id), + hir::Node::Item(hir::Item { kind: hir::ItemKind::Use(..), .. }) + ) { // Don't lint imports, only actual usages. return; } @@ -60,10 +60,10 @@ impl LateLintPass<'_> for DefaultHashTypes { /// get the `DefId` and `GenericArgsRef` of the function. fn typeck_results_of_method_fn<'tcx>( cx: &LateContext<'tcx>, - expr: &Expr<'_>, + expr: &hir::Expr<'_>, ) -> Option<(Span, DefId, ty::GenericArgsRef<'tcx>)> { match expr.kind { - ExprKind::MethodCall(segment, ..) + hir::ExprKind::MethodCall(segment, ..) if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { Some((segment.ident.span, def_id, cx.typeck_results().node_args(expr.hir_id))) @@ -102,7 +102,7 @@ declare_tool_lint! { declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY, UNTRACKED_QUERY_INFORMATION]); impl LateLintPass<'_> for QueryStability { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return }; if let Ok(Some(instance)) = ty::Instance::try_resolve(cx.tcx, cx.typing_env(), def_id, args) { @@ -164,21 +164,25 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind { } } - fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx, AmbigArg>) { + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { match &ty.kind { - TyKind::Path(QPath::Resolved(_, path)) => { + hir::TyKind::Path(hir::QPath::Resolved(_, path)) => { if lint_ty_kind_usage(cx, &path.res) { let span = match cx.tcx.parent_hir_node(ty.hir_id) { - Node::PatExpr(PatExpr { kind: PatExprKind::Path(qpath), .. }) - | Node::Pat(Pat { - kind: PatKind::TupleStruct(qpath, ..) | PatKind::Struct(qpath, ..), + hir::Node::PatExpr(hir::PatExpr { + kind: hir::PatExprKind::Path(qpath), + .. + }) + | hir::Node::Pat(hir::Pat { + kind: + hir::PatKind::TupleStruct(qpath, ..) | hir::PatKind::Struct(qpath, ..), .. }) - | Node::Expr( - Expr { kind: ExprKind::Path(qpath), .. } - | &Expr { kind: ExprKind::Struct(qpath, ..), .. }, + | hir::Node::Expr( + hir::Expr { kind: hir::ExprKind::Path(qpath), .. } + | &hir::Expr { kind: hir::ExprKind::Struct(qpath, ..), .. }, ) => { - if let QPath::TypeRelative(qpath_ty, ..) = qpath + if let hir::QPath::TypeRelative(qpath_ty, ..) = qpath && qpath_ty.hir_id == ty.hir_id { Some(path.span) @@ -223,7 +227,7 @@ fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool { } } -fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> { +fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &hir::Path<'_>) -> Option<String> { match &path.res { Res::Def(_, def_id) => { if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) { @@ -244,13 +248,17 @@ fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> { None } -fn gen_args(segment: &PathSegment<'_>) -> String { +fn gen_args(segment: &hir::PathSegment<'_>) -> String { if let Some(args) = &segment.args { let lifetimes = args .args .iter() .filter_map(|arg| { - if let GenericArg::Lifetime(lt) = arg { Some(lt.ident.to_string()) } else { None } + if let hir::GenericArg::Lifetime(lt) = arg { + Some(lt.ident.to_string()) + } else { + None + } }) .collect::<Vec<_>>(); @@ -272,7 +280,7 @@ declare_tool_lint! { } declare_tool_lint! { - /// The `usage_of_type_ir_inherent` lint detects usage `rustc_type_ir::inherent`. + /// The `usage_of_type_ir_inherent` lint detects usage of `rustc_type_ir::inherent`. /// /// This module should only be used within the trait solver. pub rustc::USAGE_OF_TYPE_IR_INHERENT, @@ -281,22 +289,58 @@ declare_tool_lint! { report_in_external_macro: true } -declare_lint_pass!(TypeIr => [NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_INHERENT]); +declare_tool_lint! { + /// The `usage_of_type_ir_traits` lint detects usage of `rustc_type_ir::Interner`, + /// or `rustc_infer::InferCtxtLike`. + /// + /// Methods of this trait should only be used within the type system abstraction layer, + /// and in the generic next trait solver implementation. Look for an analogously named + /// method on `TyCtxt` or `InferCtxt` (respectively). + pub rustc::USAGE_OF_TYPE_IR_TRAITS, + Allow, + "usage `rustc_type_ir`-specific abstraction traits outside of trait system", + report_in_external_macro: true +} + +declare_lint_pass!(TypeIr => [NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_INHERENT, USAGE_OF_TYPE_IR_TRAITS]); impl<'tcx> LateLintPass<'tcx> for TypeIr { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + let res_def_id = match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => path.res.opt_def_id(), + hir::ExprKind::Path(hir::QPath::TypeRelative(..)) | hir::ExprKind::MethodCall(..) => { + cx.typeck_results().type_dependent_def_id(expr.hir_id) + } + _ => return, + }; + let Some(res_def_id) = res_def_id else { + return; + }; + if let Some(assoc_item) = cx.tcx.opt_associated_item(res_def_id) + && let Some(trait_def_id) = assoc_item.trait_container(cx.tcx) + && (cx.tcx.is_diagnostic_item(sym::type_ir_interner, trait_def_id) + | cx.tcx.is_diagnostic_item(sym::type_ir_infer_ctxt_like, trait_def_id)) + { + cx.emit_span_lint(USAGE_OF_TYPE_IR_TRAITS, expr.span, TypeIrTraitUsage); + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { let rustc_hir::ItemKind::Use(path, kind) = item.kind else { return }; - let is_mod_inherent = |def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id); + let is_mod_inherent = |res: Res| { + res.opt_def_id() + .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::type_ir_inherent, def_id)) + }; // Path segments except for the final. - if let Some(seg) = - path.segments.iter().find(|seg| seg.res.opt_def_id().is_some_and(is_mod_inherent)) - { + if let Some(seg) = path.segments.iter().find(|seg| is_mod_inherent(seg.res)) { cx.emit_span_lint(USAGE_OF_TYPE_IR_INHERENT, seg.ident.span, TypeIrInherentUsage); } // Final path resolutions, like `use rustc_type_ir::inherent` - else if path.res.iter().any(|res| res.opt_def_id().is_some_and(is_mod_inherent)) { + else if let Some(type_ns) = path.res.type_ns + && is_mod_inherent(type_ns) + { cx.emit_span_lint( USAGE_OF_TYPE_IR_INHERENT, path.segments.last().unwrap().ident.span, @@ -305,21 +349,20 @@ impl<'tcx> LateLintPass<'tcx> for TypeIr { } let (lo, hi, snippet) = match path.segments { - [.., penultimate, segment] - if penultimate.res.opt_def_id().is_some_and(is_mod_inherent) => - { - (segment.ident.span, item.ident.span, "*") + [.., penultimate, segment] if is_mod_inherent(penultimate.res) => { + (segment.ident.span, item.kind.ident().unwrap().span, "*") } [.., segment] - if path.res.iter().flat_map(Res::opt_def_id).any(is_mod_inherent) - && let rustc_hir::UseKind::Single = kind => + if let Some(type_ns) = path.res.type_ns + && is_mod_inherent(type_ns) + && let rustc_hir::UseKind::Single(ident) = kind => { let (lo, snippet) = match cx.tcx.sess.source_map().span_to_snippet(path.span).as_deref() { Ok("self") => (path.span, "*"), _ => (segment.ident.span.shrink_to_hi(), "::*"), }; - (lo, if segment.ident == item.ident { lo } else { item.ident.span }, snippet) + (lo, if segment.ident == ident { lo } else { ident.span }, snippet) } _ => return, }; @@ -394,15 +437,15 @@ declare_tool_lint! { declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL]); impl LateLintPass<'_> for Diagnostics { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let collect_args_tys_and_spans = |args: &[Expr<'_>], reserve_one_extra: bool| { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + let collect_args_tys_and_spans = |args: &[hir::Expr<'_>], reserve_one_extra: bool| { let mut result = Vec::with_capacity(args.len() + usize::from(reserve_one_extra)); result.extend(args.iter().map(|arg| (cx.typeck_results().expr_ty(arg), arg.span))); result }; // Only check function calls and method calls. let (span, def_id, fn_gen_args, arg_tys_and_spans) = match expr.kind { - ExprKind::Call(callee, args) => { + hir::ExprKind::Call(callee, args) => { match cx.typeck_results().node_type(callee.hir_id).kind() { &ty::FnDef(def_id, fn_gen_args) => { (callee.span, def_id, fn_gen_args, collect_args_tys_and_spans(args, false)) @@ -410,7 +453,7 @@ impl LateLintPass<'_> for Diagnostics { _ => return, // occurs for fns passed as args } } - ExprKind::MethodCall(_segment, _recv, args, _span) => { + hir::ExprKind::MethodCall(_segment, _recv, args, _span) => { let Some((span, def_id, fn_gen_args)) = typeck_results_of_method_fn(cx, expr) else { return; @@ -514,8 +557,8 @@ impl Diagnostics { let mut is_inside_appropriate_impl = false; for (_hir_id, parent) in cx.tcx.hir_parent_iter(current_id) { debug!(?parent); - if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent - && let Impl { of_trait: Some(of_trait), .. } = impl_ + if let hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(impl_), .. }) = parent + && let hir::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) @@ -543,8 +586,8 @@ declare_tool_lint! { declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]); impl LateLintPass<'_> for BadOptAccess { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let ExprKind::Field(base, target) = expr.kind else { return }; + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + let hir::ExprKind::Field(base, target) = expr.kind else { return }; let Some(adt_def) = cx.typeck_results().expr_ty(base).ty_adt_def() else { return }; // Skip types without `#[rustc_lint_opt_ty]` - only so that the rest of the lint can be // avoided. @@ -581,9 +624,12 @@ declare_tool_lint! { declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]); impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { - if let ExprKind::Binary(BinOp { node: BinOpKind::Eq | BinOpKind::Ne, .. }, lhs, rhs) = - expr.kind + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) { + if let hir::ExprKind::Binary( + hir::BinOp { node: hir::BinOpKind::Eq | hir::BinOpKind::Ne, .. }, + lhs, + rhs, + ) = expr.kind { if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) { cx.emit_span_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag); @@ -592,9 +638,9 @@ impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt { } } -fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { +fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { match &expr.kind { - ExprKind::MethodCall(..) => cx + hir::ExprKind::MethodCall(..) => cx .typeck_results() .type_dependent_def_id(expr.hir_id) .is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)), @@ -617,11 +663,11 @@ declare_lint_pass!(SymbolInternStringLiteral => [SYMBOL_INTERN_STRING_LITERAL]); impl<'tcx> LateLintPass<'tcx> for SymbolInternStringLiteral { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if let ExprKind::Call(path, [arg]) = expr.kind - && let ExprKind::Path(ref qpath) = path.kind + if let hir::ExprKind::Call(path, [arg]) = expr.kind + && let hir::ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::SymbolIntern, def_id) - && let ExprKind::Lit(kind) = arg.kind + && let hir::ExprKind::Lit(kind) = arg.kind && let rustc_ast::LitKind::Str(_, _) = kind.node { cx.emit_span_lint( diff --git a/compiler/rustc_lint/src/late.rs b/compiler/rustc_lint/src/late.rs index 23d6efa0508..852bb01c096 100644 --- a/compiler/rustc_lint/src/late.rs +++ b/compiler/rustc_lint/src/late.rs @@ -48,7 +48,7 @@ impl<'tcx, T: LateLintPass<'tcx>> LateContextAndPass<'tcx, T> { where F: FnOnce(&mut Self), { - let attrs = self.context.tcx.hir().attrs(id); + let attrs = self.context.tcx.hir_attrs(id); let prev = self.context.last_node_with_lint_attrs; self.context.last_node_with_lint_attrs = id; debug!("late context: enter_attrs({:?})", attrs); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 4ede9b44087..c52dbd892bf 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,10 +1,11 @@ use rustc_ast::attr::AttributeExt; use rustc_ast_pretty::pprust; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::unord::UnordSet; use rustc_errors::{Diag, LintDiagnostic, MultiSpan}; use rustc_feature::{Features, GateIssue}; +use rustc_hir::HirId; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{CRATE_HIR_ID, HirId}; use rustc_index::IndexVec; use rustc_middle::bug; use rustc_middle::hir::nested_filter; @@ -84,10 +85,10 @@ impl LintLevelSets { ) -> LevelAndSource { let lint = LintId::of(lint); let (level, mut src) = self.raw_lint_id_level(lint, idx, aux); - let level = reveal_actual_level(level, &mut src, sess, lint, |id| { + let (level, lint_id) = reveal_actual_level(level, &mut src, sess, lint, |id| { self.raw_lint_id_level(id, idx, aux) }); - (level, src) + LevelAndSource { level, lint_id, src } } fn raw_lint_id_level( @@ -95,17 +96,17 @@ impl LintLevelSets { id: LintId, mut idx: LintStackIndex, aux: Option<&FxIndexMap<LintId, LevelAndSource>>, - ) -> (Option<Level>, LintLevelSource) { + ) -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource) { if let Some(specs) = aux - && let Some(&(level, src)) = specs.get(&id) + && let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id) { - return (Some(level), src); + return (Some((level, lint_id)), src); } loop { let LintSet { ref specs, parent } = self.list[idx]; - if let Some(&(level, src)) = specs.get(&id) { - return (Some(level), src); + if let Some(&LevelAndSource { level, lint_id, src }) = specs.get(&id) { + return (Some((level, lint_id)), src); } if idx == COMMAND_LINE { return (None, LintLevelSource::Default); @@ -115,44 +116,50 @@ impl LintLevelSets { } } -fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LintId> { +fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> { let store = unerased_lint_store(&tcx.sess); + let root_map = tcx.shallow_lint_levels_on(hir::CRATE_OWNER_ID); - let map = tcx.shallow_lint_levels_on(rustc_hir::CRATE_OWNER_ID); - - let dont_need_to_run: FxIndexSet<LintId> = store + let mut dont_need_to_run: FxHashSet<LintId> = store .get_lints() .into_iter() .filter(|lint| { // Lints that show up in future-compat reports must always be run. let has_future_breakage = - lint.future_incompatible.is_some_and(|fut| fut.reason.has_future_breakage()); + lint.future_incompatible.is_some_and(|fut| fut.report_in_deps); !has_future_breakage && !lint.eval_always }) - .filter_map(|lint| { - let lint_level = map.lint_level_id_at_node(tcx, LintId::of(lint), CRATE_HIR_ID); - if matches!(lint_level, (Level::Allow, ..)) - || (matches!(lint_level, (.., LintLevelSource::Default))) - && lint.default_level(tcx.sess.edition()) == Level::Allow - { - Some(LintId::of(lint)) - } else { - None - } + .filter(|lint| { + let lint_level = + root_map.lint_level_id_at_node(tcx, LintId::of(lint), hir::CRATE_HIR_ID); + // Only include lints that are allowed at crate root or by default. + matches!(lint_level.level, Level::Allow) + || (matches!(lint_level.src, LintLevelSource::Default) + && lint.default_level(tcx.sess.edition()) == Level::Allow) }) + .map(|lint| LintId::of(*lint)) .collect(); - let mut visitor = LintLevelMaximum { tcx, dont_need_to_run }; - visitor.process_opts(); - tcx.hir_walk_attributes(&mut visitor); + for owner in tcx.hir_crate_items(()).owners() { + let map = tcx.shallow_lint_levels_on(owner); - visitor.dont_need_to_run + // All lints that appear with a non-allow level must be run. + for (_, specs) in map.specs.iter() { + for (lint, level_and_source) in specs.iter() { + if !matches!(level_and_source.level, Level::Allow) { + dont_need_to_run.remove(lint); + } + } + } + } + + dont_need_to_run.into() } #[instrument(level = "trace", skip(tcx), ret)] fn shallow_lint_levels_on(tcx: TyCtxt<'_>, owner: hir::OwnerId) -> ShallowLintLevelMap { let store = unerased_lint_store(tcx.sess); - let attrs = tcx.hir_attrs(owner); + let attrs = tcx.hir_attr_map(owner); let mut levels = LintLevelsBuilder { sess: tcx.sess, @@ -299,6 +306,11 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { intravisit::walk_expr(self, e); } + fn visit_pat_field(&mut self, f: &'tcx hir::PatField<'tcx>) -> Self::Result { + self.add_id(f.hir_id); + intravisit::walk_pat_field(self, f); + } + fn visit_expr_field(&mut self, f: &'tcx hir::ExprField<'tcx>) { self.add_id(f.hir_id); intravisit::walk_expr_field(self, f); @@ -335,82 +347,6 @@ impl<'tcx> Visitor<'tcx> for LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { } } -/// Visitor with the only function of visiting every item-like in a crate and -/// computing the highest level that every lint gets put to. -/// -/// E.g., if a crate has a global #![allow(lint)] attribute, but a single item -/// uses #[warn(lint)], this visitor will set that lint level as `Warn` -struct LintLevelMaximum<'tcx> { - tcx: TyCtxt<'tcx>, - /// The actual list of detected lints. - dont_need_to_run: FxIndexSet<LintId>, -} - -impl<'tcx> LintLevelMaximum<'tcx> { - fn process_opts(&mut self) { - let store = unerased_lint_store(self.tcx.sess); - for (lint_group, level) in &self.tcx.sess.opts.lint_opts { - if *level != Level::Allow { - let Ok(lints) = store.find_lints(lint_group) else { - return; - }; - for lint in lints { - self.dont_need_to_run.swap_remove(&lint); - } - } - } - } -} - -impl<'tcx> Visitor<'tcx> for LintLevelMaximum<'tcx> { - type NestedFilter = nested_filter::All; - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.tcx - } - - /// FIXME(blyxyas): In a future revision, we should also graph #![allow]s, - /// but that is handled with more care - fn visit_attribute(&mut self, attribute: &'tcx hir::Attribute) { - if matches!( - Level::from_attr(attribute), - Some( - Level::Warn - | Level::Deny - | Level::Forbid - | Level::Expect(..) - | Level::ForceWarn(..), - ) - ) { - let store = unerased_lint_store(self.tcx.sess); - // Lint attributes are always a metalist inside a - // metalist (even with just one lint). - let Some(meta_item_list) = attribute.meta_item_list() else { return }; - - for meta_list in meta_item_list { - // Convert Path to String - let Some(meta_item) = meta_list.meta_item() else { return }; - let ident: &str = &meta_item - .path - .segments - .iter() - .map(|segment| segment.ident.as_str()) - .collect::<Vec<&str>>() - .join("::"); - let Ok(lints) = store.find_lints( - // Lint attributes can only have literals - ident, - ) else { - return; - }; - for lint in lints { - self.dont_need_to_run.swap_remove(&lint); - } - } - } - } -} - pub struct LintLevelsBuilder<'s, P> { sess: &'s Session, features: &'s Features, @@ -445,6 +381,19 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { builder } + pub fn crate_root( + sess: &'s Session, + features: &'s Features, + lint_added_lints: bool, + store: &'s LintStore, + registered_tools: &'s RegisteredTools, + crate_attrs: &[ast::Attribute], + ) -> Self { + let mut builder = Self::new(sess, features, lint_added_lints, store, registered_tools); + builder.add(crate_attrs, true, None); + builder + } + fn process_command_line(&mut self) { self.provider.cur = self .provider @@ -523,9 +472,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { for &(ref lint_name, level) in &self.sess.opts.lint_opts { // Checks the validity of lint names derived from the command line. let (tool_name, lint_name_only) = parse_lint_and_tool_name(lint_name); - if lint_name_only == crate::WARNINGS.name_lower() - && matches!(level, Level::ForceWarn(_)) - { + if lint_name_only == crate::WARNINGS.name_lower() && matches!(level, Level::ForceWarn) { self.sess .dcx() .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); @@ -568,24 +515,23 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { _ => {} }; - let orig_level = level; let lint_flag_val = Symbol::intern(lint_name); - let Ok(ids) = self.store.find_lints(lint_name) else { + let Some(ids) = self.store.find_lints(lint_name) else { // errors already handled above continue; }; - for id in ids { + for &id in ids { // ForceWarn and Forbid cannot be overridden - if let Some((Level::ForceWarn(_) | Level::Forbid, _)) = + if let Some(LevelAndSource { level: Level::ForceWarn | Level::Forbid, .. }) = self.current_specs().get(&id) { continue; } if self.check_gated_lint(id, DUMMY_SP, true) { - let src = LintLevelSource::CommandLine(lint_flag_val, orig_level); - self.insert(id, (level, src)); + let src = LintLevelSource::CommandLine(lint_flag_val, level); + self.insert(id, LevelAndSource { level, lint_id: None, src }); } } } @@ -594,8 +540,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { /// Attempts to insert the `id` to `level_src` map entry. If unsuccessful /// (e.g. if a forbid was already inserted on the same scope), then emits a /// diagnostic with no change to `specs`. - fn insert_spec(&mut self, id: LintId, (level, src): LevelAndSource) { - let (old_level, old_src) = self.provider.get_lint_level(id.lint, self.sess); + fn insert_spec(&mut self, id: LintId, LevelAndSource { level, lint_id, src }: LevelAndSource) { + let LevelAndSource { level: old_level, src: old_src, .. } = + self.provider.get_lint_level(id.lint, self.sess); // Setting to a non-forbid level is an error if the lint previously had // a forbid level. Note that this is not necessarily true even with a @@ -667,7 +614,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { // The lint `unfulfilled_lint_expectations` can't be expected, as it would suppress itself. // Handling expectations of this lint would add additional complexity with little to no // benefit. The expect level for this lint will therefore be ignored. - if let Level::Expect(_) = level + if let Level::Expect = level && id == LintId::of(UNFULFILLED_LINT_EXPECTATIONS) { return; @@ -675,13 +622,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { match (old_level, level) { // If the new level is an expectation store it in `ForceWarn` - (Level::ForceWarn(_), Level::Expect(expectation_id)) => { - self.insert(id, (Level::ForceWarn(Some(expectation_id)), old_src)) + (Level::ForceWarn, Level::Expect) => { + self.insert(id, LevelAndSource { level: Level::ForceWarn, lint_id, src: old_src }) } // Keep `ForceWarn` level but drop the expectation - (Level::ForceWarn(_), _) => self.insert(id, (Level::ForceWarn(None), old_src)), + (Level::ForceWarn, _) => self.insert( + id, + LevelAndSource { level: Level::ForceWarn, lint_id: None, src: old_src }, + ), // Set the lint level as normal - _ => self.insert(id, (level, src)), + _ => self.insert(id, LevelAndSource { level, lint_id, src }), }; } @@ -696,7 +646,11 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { if attr.has_name(sym::automatically_derived) { self.insert( LintId::of(SINGLE_USE_LIFETIMES), - (Level::Allow, LintLevelSource::Default), + LevelAndSource { + level: Level::Allow, + lint_id: None, + src: LintLevelSource::Default, + }, ); continue; } @@ -707,15 +661,22 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { .meta_item_list() .is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden)) { - self.insert(LintId::of(MISSING_DOCS), (Level::Allow, LintLevelSource::Default)); + self.insert( + LintId::of(MISSING_DOCS), + LevelAndSource { + level: Level::Allow, + lint_id: None, + src: LintLevelSource::Default, + }, + ); continue; } - let level = match Level::from_attr(attr) { + let (level, lint_id) = match Level::from_attr(attr) { None => continue, // This is the only lint level with a `LintExpectationId` that can be created from // an attribute. - Some(Level::Expect(unstable_id)) if let Some(hir_id) = source_hir_id => { + Some((Level::Expect, Some(unstable_id))) if let Some(hir_id) = source_hir_id => { let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id else { bug!("stable id Level::from_attr") @@ -727,9 +688,9 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { lint_index: None, }; - Level::Expect(stable_id) + (Level::Expect, Some(stable_id)) } - Some(lvl) => lvl, + Some((lvl, id)) => (lvl, id), }; let Some(mut metas) = attr.meta_item_list() else { continue }; @@ -777,13 +738,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } for (lint_index, li) in metas.iter_mut().enumerate() { - let level = match level { - Level::Expect(mut id) => { - id.set_lint_index(Some(lint_index as u16)); - Level::Expect(id) - } - level => level, - }; + let mut lint_id = lint_id; + if let Some(id) = &mut lint_id { + id.set_lint_index(Some(lint_index as u16)); + } let sp = li.span(); let meta_item = match li { @@ -915,7 +873,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { let src = LintLevelSource::Node { name, span: sp, reason }; for &id in ids { if self.check_gated_lint(id, sp, false) { - self.insert_spec(id, (level, src)); + self.insert_spec(id, LevelAndSource { level, lint_id, src }); } } @@ -924,7 +882,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { // overriding the lint level but instead add an expectation that can't be // fulfilled. The lint message will include an explanation, that the // `unfulfilled_lint_expectations` lint can't be expected. - if let Level::Expect(expect_id) = level { + if let (Level::Expect, Some(expect_id)) = (level, lint_id) { // The `unfulfilled_lint_expectations` lint is not part of any lint // groups. Therefore. we only need to check the slice if it contains a // single lint. @@ -946,7 +904,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { } if self.lint_added_lints && !is_crate_node { - for (id, &(level, ref src)) in self.current_specs().iter() { + for (id, &LevelAndSource { level, ref src, .. }) in self.current_specs().iter() { if !id.lint.crate_level_only { continue; } @@ -984,10 +942,10 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { if self.lint_added_lints { let lint = builtin::UNKNOWN_LINTS; - let (level, src) = self.lint_level(builtin::UNKNOWN_LINTS); + let level = self.lint_level(builtin::UNKNOWN_LINTS); // FIXME: make this translatable #[allow(rustc::diagnostic_outside_of_impl)] - lint_level(self.sess, lint, level, src, Some(span.into()), |lint| { + lint_level(self.sess, lint, level, Some(span.into()), |lint| { lint.primary_message(fluent::lint_unknown_gated_lint); lint.arg("name", lint_id.lint.name_lower()); lint.note(fluent::lint_note); @@ -1022,8 +980,8 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { span: Option<MultiSpan>, decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>), ) { - let (level, src) = self.lint_level(lint); - lint_level(self.sess, lint, level, src, span, decorate) + let level = self.lint_level(lint); + lint_level(self.sess, lint, level, span, decorate) } #[track_caller] @@ -1033,16 +991,16 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { span: MultiSpan, decorate: impl for<'a> LintDiagnostic<'a, ()>, ) { - let (level, src) = self.lint_level(lint); - lint_level(self.sess, lint, level, src, Some(span), |lint| { + let level = self.lint_level(lint); + lint_level(self.sess, lint, level, Some(span), |lint| { decorate.decorate_lint(lint); }); } #[track_caller] pub fn emit_lint(&self, lint: &'static Lint, decorate: impl for<'a> LintDiagnostic<'a, ()>) { - let (level, src) = self.lint_level(lint); - lint_level(self.sess, lint, level, src, None, |lint| { + let level = self.lint_level(lint); + lint_level(self.sess, lint, level, None, |lint| { decorate.decorate_lint(lint); }); } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 35867d8c9ef..72bfeaddbf1 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -28,7 +28,6 @@ #![feature(box_patterns)] #![feature(if_let_guard)] #![feature(iter_order_by)] -#![feature(let_chains)] #![feature(rustc_attrs)] #![feature(rustdoc_internals)] #![feature(try_blocks)] @@ -36,6 +35,7 @@ mod async_closures; mod async_fn_in_trait; +mod autorefs; pub mod builtin; mod context; mod dangling; @@ -48,7 +48,6 @@ mod errors; mod expect; mod for_loops_over_fallibles; mod foreign_modules; -pub mod hidden_unicode_codepoints; mod if_let_rescope; mod impl_trait_overcaptures; mod internal; @@ -56,6 +55,7 @@ mod invalid_from_utf8; mod late; mod let_underscore; mod levels; +mod lifetime_syntax; mod lints; mod macro_expr_fragment_specifier_2024_migration; mod map_unit_fn; @@ -75,13 +75,16 @@ mod reference_casting; mod shadowed_into_iter; mod static_mut_refs; mod traits; +mod transmute; mod types; mod unit_bindings; mod unqualified_local_imports; mod unused; +mod utils; use async_closures::AsyncClosureUsage; use async_fn_in_trait::AsyncFnInTrait; +use autorefs::*; use builtin::*; use dangling::*; use default_could_be_derived::DefaultCouldBeDerived; @@ -89,12 +92,12 @@ use deref_into_dyn_supertrait::*; use drop_forget_useless::*; use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums; use for_loops_over_fallibles::*; -use hidden_unicode_codepoints::*; use if_let_rescope::IfLetRescope; use impl_trait_overcaptures::ImplTraitOvercaptures; use internal::*; use invalid_from_utf8::*; use let_underscore::*; +use lifetime_syntax::*; use macro_expr_fragment_specifier_2024_migration::*; use map_unit_fn::*; use multiple_supertrait_upcastable::*; @@ -116,6 +119,7 @@ use shadowed_into_iter::ShadowedIntoIter; pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER}; use static_mut_refs::*; use traits::*; +use transmute::CheckTransmutes; use types::*; use unit_bindings::*; use unqualified_local_imports::*; @@ -123,11 +127,11 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; -pub use context::{ - CheckLintNameResult, EarlyContext, FindLintError, LateContext, LintContext, LintStore, -}; +pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore}; +pub use early::diagnostics::decorate_builtin_lint; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; +pub use levels::LintLevelsBuilder; pub use passes::{EarlyLintPass, LateLintPass}; pub use rustc_session::lint::Level::{self, *}; pub use rustc_session::lint::{ @@ -173,7 +177,6 @@ early_lint_methods!( DeprecatedAttr: DeprecatedAttr::default(), WhileTrue: WhileTrue, NonAsciiIdents: NonAsciiIdents, - HiddenUnicodeCodepoints: HiddenUnicodeCodepoints, IncompleteInternalFeatures: IncompleteInternalFeatures, RedundantSemicolons: RedundantSemicolons, UnusedDocComment: UnusedDocComment, @@ -200,6 +203,7 @@ late_lint_methods!( PathStatements: PathStatements, LetUnderscore: LetUnderscore, InvalidReferenceCasting: InvalidReferenceCasting, + ImplicitAutorefs: ImplicitAutorefs, // Depends on referenced function signatures in expressions UnusedResults: UnusedResults, UnitBindings: UnitBindings, @@ -243,6 +247,8 @@ late_lint_methods!( IfLetRescope: IfLetRescope::default(), StaticMutRefs: StaticMutRefs, UnqualifiedLocalImports: UnqualifiedLocalImports, + CheckTransmutes: CheckTransmutes, + LifetimeSyntax: LifetimeSyntax, ] ] ); @@ -350,6 +356,7 @@ fn register_builtins(store: &mut LintStore) { store.register_renamed("unused_tuple_struct_fields", "dead_code"); store.register_renamed("static_mut_ref", "static_mut_refs"); store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries"); + store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes"); // 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 @@ -592,7 +599,6 @@ fn register_builtins(store: &mut LintStore) { "converted into hard error, see PR #125380 \ <https://github.com/rust-lang/rust/pull/125380> for more information", ); - store.register_removed("unsupported_calling_conventions", "converted into hard error"); store.register_removed( "cenum_impl_drop_cast", "converted into hard error, \ @@ -603,6 +609,16 @@ fn register_builtins(store: &mut LintStore) { "converted into hard error, see issue #127323 \ <https://github.com/rust-lang/rust/issues/127323> for more information", ); + store.register_removed( + "undefined_naked_function_abi", + "converted into hard error, see PR #139001 \ + <https://github.com/rust-lang/rust/issues/139001> for more information", + ); + store.register_removed( + "abi_unsupported_vector_types", + "converted into hard error, \ + see <https://github.com/rust-lang/rust/issues/116558> for more information", + ); } fn register_internals(store: &mut LintStore) { @@ -644,6 +660,7 @@ fn register_internals(store: &mut LintStore) { LintId::of(USAGE_OF_QUALIFIED_TY), LintId::of(NON_GLOB_IMPORT_OF_TYPE_IR_INHERENT), LintId::of(USAGE_OF_TYPE_IR_INHERENT), + LintId::of(USAGE_OF_TYPE_IR_TRAITS), LintId::of(BAD_OPT_ACCESS), LintId::of(SPAN_USE_EQ_CTXT), ], diff --git a/compiler/rustc_lint/src/lifetime_syntax.rs b/compiler/rustc_lint/src/lifetime_syntax.rs new file mode 100644 index 00000000000..31b038e6a46 --- /dev/null +++ b/compiler/rustc_lint/src/lifetime_syntax.rs @@ -0,0 +1,503 @@ +use rustc_data_structures::fx::FxIndexMap; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self as hir, LifetimeSource}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::Span; +use tracing::instrument; + +use crate::{LateContext, LateLintPass, LintContext, lints}; + +declare_lint! { + /// The `mismatched_lifetime_syntaxes` lint detects when the same + /// lifetime is referred to by different syntaxes between function + /// arguments and return values. + /// + /// The three kinds of syntaxes are: + /// + /// 1. Named lifetimes. These are references (`&'a str`) or paths + /// (`Person<'a>`) that use a lifetime with a name, such as + /// `'static` or `'a`. + /// + /// 2. Elided lifetimes. These are references with no explicit + /// lifetime (`&str`), references using the anonymous lifetime + /// (`&'_ str`), and paths using the anonymous lifetime + /// (`Person<'_>`). + /// + /// 3. Hidden lifetimes. These are paths that do not contain any + /// visual indication that it contains a lifetime (`Person`). + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(mismatched_lifetime_syntaxes)] + /// + /// pub fn mixing_named_with_elided(v: &'static u8) -> &u8 { + /// v + /// } + /// + /// struct Person<'a> { + /// name: &'a str, + /// } + /// + /// pub fn mixing_hidden_with_elided(v: Person) -> Person<'_> { + /// v + /// } + /// + /// struct Foo; + /// + /// impl Foo { + /// // Lifetime elision results in the output lifetime becoming + /// // `'static`, which is not what was intended. + /// pub fn get_mut(&'static self, x: &mut u8) -> &mut u8 { + /// unsafe { &mut *(x as *mut _) } + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Lifetime elision is useful because it frees you from having to + /// give each lifetime its own name and show the relation of input + /// and output lifetimes for common cases. However, a lifetime + /// that uses inconsistent syntax between related arguments and + /// return values is more confusing. + /// + /// In certain `unsafe` code, lifetime elision combined with + /// inconsistent lifetime syntax may result in unsound code. + pub MISMATCHED_LIFETIME_SYNTAXES, + Warn, + "detects when a lifetime uses different syntax between arguments and return values" +} + +declare_lint_pass!(LifetimeSyntax => [MISMATCHED_LIFETIME_SYNTAXES]); + +impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax { + #[instrument(skip_all)] + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + _: hir::intravisit::FnKind<'tcx>, + fd: &'tcx hir::FnDecl<'tcx>, + _: &'tcx hir::Body<'tcx>, + _: rustc_span::Span, + _: rustc_span::def_id::LocalDefId, + ) { + let mut input_map = Default::default(); + let mut output_map = Default::default(); + + for input in fd.inputs { + LifetimeInfoCollector::collect(input, &mut input_map); + } + + if let hir::FnRetTy::Return(output) = fd.output { + LifetimeInfoCollector::collect(output, &mut output_map); + } + + report_mismatches(cx, &input_map, &output_map); + } +} + +#[instrument(skip_all)] +fn report_mismatches<'tcx>( + cx: &LateContext<'tcx>, + inputs: &LifetimeInfoMap<'tcx>, + outputs: &LifetimeInfoMap<'tcx>, +) { + for (resolved_lifetime, output_info) in outputs { + if let Some(input_info) = inputs.get(resolved_lifetime) { + if !lifetimes_use_matched_syntax(input_info, output_info) { + emit_mismatch_diagnostic(cx, input_info, output_info); + } + } + } +} + +fn lifetimes_use_matched_syntax(input_info: &[Info<'_>], output_info: &[Info<'_>]) -> bool { + // Categorize lifetimes into source/syntax buckets. + let mut n_hidden = 0; + let mut n_elided = 0; + let mut n_named = 0; + + for info in input_info.iter().chain(output_info) { + use LifetimeSource::*; + use hir::LifetimeSyntax::*; + + let syntax_source = (info.lifetime.syntax, info.lifetime.source); + + match syntax_source { + // Ignore any other kind of lifetime. + (_, Other) => continue, + + // E.g. `&T`. + (Implicit, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `&'_ T`. + (ExplicitAnonymous, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `ContainsLifetime<'_>`. + (ExplicitAnonymous, Path { .. }) => n_elided += 1, + + // E.g. `ContainsLifetime`. + (Implicit, Path { .. }) => n_hidden += 1, + + // E.g. `&'a T`. + (ExplicitBound, Reference | OutlivesBound | PreciseCapturing) | + // E.g. `ContainsLifetime<'a>`. + (ExplicitBound, Path { .. }) => n_named += 1, + }; + } + + let syntax_counts = (n_hidden, n_elided, n_named); + tracing::debug!(?syntax_counts); + + matches!(syntax_counts, (_, 0, 0) | (0, _, 0) | (0, 0, _)) +} + +fn emit_mismatch_diagnostic<'tcx>( + cx: &LateContext<'tcx>, + input_info: &[Info<'_>], + output_info: &[Info<'_>], +) { + // There can only ever be zero or one bound lifetime + // for a given lifetime resolution. + let mut bound_lifetime = None; + + // We offer the following kinds of suggestions (when appropriate + // such that the suggestion wouldn't violate the lint): + // + // 1. Every lifetime becomes named, when there is already a + // user-provided name. + // + // 2. A "mixed" signature, where references become implicit + // and paths become explicitly anonymous. + // + // 3. Every lifetime becomes implicit. + // + // 4. Every lifetime becomes explicitly anonymous. + // + // Number 2 is arguably the most common pattern and the one we + // should push strongest. Number 3 is likely the next most common, + // followed by number 1. Coming in at a distant last would be + // number 4. + // + // Beyond these, there are variants of acceptable signatures that + // we won't suggest because they are very low-value. For example, + // we will never suggest `fn(&T1, &'_ T2) -> &T3` even though that + // would pass the lint. + // + // The following collections are the lifetime instances that we + // suggest changing to a given alternate style. + + // 1. Convert all to named. + let mut suggest_change_to_explicit_bound = Vec::new(); + + // 2. Convert to mixed. We track each kind of change separately. + let mut suggest_change_to_mixed_implicit = Vec::new(); + let mut suggest_change_to_mixed_explicit_anonymous = Vec::new(); + + // 3. Convert all to implicit. + let mut suggest_change_to_implicit = Vec::new(); + + // 4. Convert all to explicit anonymous. + let mut suggest_change_to_explicit_anonymous = Vec::new(); + + // Some styles prevent using implicit syntax at all. + let mut allow_suggesting_implicit = true; + + // It only makes sense to suggest mixed if we have both sources. + let mut saw_a_reference = false; + let mut saw_a_path = false; + + for info in input_info.iter().chain(output_info) { + use LifetimeSource::*; + use hir::LifetimeSyntax::*; + + let syntax_source = (info.lifetime.syntax, info.lifetime.source); + + if let (_, Other) = syntax_source { + // Ignore any other kind of lifetime. + continue; + } + + if let (ExplicitBound, _) = syntax_source { + bound_lifetime = Some(info); + } + + match syntax_source { + // E.g. `&T`. + (Implicit, Reference) => { + suggest_change_to_explicit_anonymous.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `&'_ T`. + (ExplicitAnonymous, Reference) => { + suggest_change_to_implicit.push(info); + suggest_change_to_mixed_implicit.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `ContainsLifetime`. + (Implicit, Path { .. }) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `ContainsLifetime<'_>`. + (ExplicitAnonymous, Path { .. }) => { + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `&'a T`. + (ExplicitBound, Reference) => { + suggest_change_to_implicit.push(info); + suggest_change_to_mixed_implicit.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + // E.g. `ContainsLifetime<'a>`. + (ExplicitBound, Path { .. }) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + (Implicit, OutlivesBound | PreciseCapturing) => { + panic!("This syntax / source combination is not possible"); + } + + // E.g. `+ '_`, `+ use<'_>`. + (ExplicitAnonymous, OutlivesBound | PreciseCapturing) => { + suggest_change_to_explicit_bound.push(info); + } + + // E.g. `+ 'a`, `+ use<'a>`. + (ExplicitBound, OutlivesBound | PreciseCapturing) => { + suggest_change_to_mixed_explicit_anonymous.push(info); + suggest_change_to_explicit_anonymous.push(info); + } + + (_, Other) => { + panic!("This syntax / source combination has already been skipped"); + } + } + + if matches!(syntax_source, (_, Path { .. } | OutlivesBound | PreciseCapturing)) { + allow_suggesting_implicit = false; + } + + match syntax_source { + (_, Reference) => saw_a_reference = true, + (_, Path { .. }) => saw_a_path = true, + _ => {} + } + } + + let make_implicit_suggestions = + |infos: &[&Info<'_>]| infos.iter().map(|i| i.removing_span()).collect::<Vec<_>>(); + + let inputs = input_info.iter().map(|info| info.reporting_span()).collect(); + let outputs = output_info.iter().map(|info| info.reporting_span()).collect(); + + let explicit_bound_suggestion = bound_lifetime.map(|info| { + build_mismatch_suggestion(info.lifetime_name(), &suggest_change_to_explicit_bound) + }); + + let is_bound_static = bound_lifetime.is_some_and(|info| info.is_static()); + + tracing::debug!(?bound_lifetime, ?explicit_bound_suggestion, ?is_bound_static); + + let should_suggest_mixed = + // Do we have a mixed case? + (saw_a_reference && saw_a_path) && + // Is there anything to change? + (!suggest_change_to_mixed_implicit.is_empty() || + !suggest_change_to_mixed_explicit_anonymous.is_empty()) && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let mixed_suggestion = should_suggest_mixed.then(|| { + let implicit_suggestions = make_implicit_suggestions(&suggest_change_to_mixed_implicit); + + let explicit_anonymous_suggestions = suggest_change_to_mixed_explicit_anonymous + .iter() + .map(|info| info.suggestion("'_")) + .collect(); + + lints::MismatchedLifetimeSyntaxesSuggestion::Mixed { + implicit_suggestions, + explicit_anonymous_suggestions, + tool_only: false, + } + }); + + tracing::debug!( + ?suggest_change_to_mixed_implicit, + ?suggest_change_to_mixed_explicit_anonymous, + ?mixed_suggestion, + ); + + let should_suggest_implicit = + // Is there anything to change? + !suggest_change_to_implicit.is_empty() && + // We never want to hide the lifetime in a path (or similar). + allow_suggesting_implicit && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let implicit_suggestion = should_suggest_implicit.then(|| { + let suggestions = make_implicit_suggestions(&suggest_change_to_implicit); + + lints::MismatchedLifetimeSyntaxesSuggestion::Implicit { suggestions, tool_only: false } + }); + + tracing::debug!( + ?should_suggest_implicit, + ?suggest_change_to_implicit, + allow_suggesting_implicit, + ?implicit_suggestion, + ); + + let should_suggest_explicit_anonymous = + // Is there anything to change? + !suggest_change_to_explicit_anonymous.is_empty() && + // If we have `'static`, we don't want to remove it. + !is_bound_static; + + let explicit_anonymous_suggestion = should_suggest_explicit_anonymous + .then(|| build_mismatch_suggestion("'_", &suggest_change_to_explicit_anonymous)); + + tracing::debug!( + ?should_suggest_explicit_anonymous, + ?suggest_change_to_explicit_anonymous, + ?explicit_anonymous_suggestion, + ); + + let lifetime_name = bound_lifetime.map(|info| info.lifetime_name()).unwrap_or("'_").to_owned(); + + // We can produce a number of suggestions which may overwhelm + // the user. Instead, we order the suggestions based on Rust + // idioms. The "best" choice is shown to the user and the + // remaining choices are shown to tools only. + let mut suggestions = Vec::new(); + suggestions.extend(explicit_bound_suggestion); + suggestions.extend(mixed_suggestion); + suggestions.extend(implicit_suggestion); + suggestions.extend(explicit_anonymous_suggestion); + + cx.emit_span_lint( + MISMATCHED_LIFETIME_SYNTAXES, + Vec::clone(&inputs), + lints::MismatchedLifetimeSyntaxes { lifetime_name, inputs, outputs, suggestions }, + ); +} + +fn build_mismatch_suggestion( + lifetime_name: &str, + infos: &[&Info<'_>], +) -> lints::MismatchedLifetimeSyntaxesSuggestion { + let lifetime_name = lifetime_name.to_owned(); + + let suggestions = infos.iter().map(|info| info.suggestion(&lifetime_name)).collect(); + + lints::MismatchedLifetimeSyntaxesSuggestion::Explicit { + lifetime_name, + suggestions, + tool_only: false, + } +} + +#[derive(Debug)] +struct Info<'tcx> { + type_span: Span, + referenced_type_span: Option<Span>, + lifetime: &'tcx hir::Lifetime, +} + +impl<'tcx> Info<'tcx> { + fn lifetime_name(&self) -> &str { + self.lifetime.ident.as_str() + } + + fn is_static(&self) -> bool { + self.lifetime.is_static() + } + + /// When reporting a lifetime that is implicit, we expand the span + /// to include the type. Otherwise we end up pointing at nothing, + /// which is a bit confusing. + fn reporting_span(&self) -> Span { + if self.lifetime.is_implicit() { self.type_span } else { self.lifetime.ident.span } + } + + /// When removing an explicit lifetime from a reference, + /// we want to remove the whitespace after the lifetime. + /// + /// ```rust + /// fn x(a: &'_ u8) {} + /// ``` + /// + /// Should become: + /// + /// ```rust + /// fn x(a: &u8) {} + /// ``` + // FIXME: Ideally, we'd also remove the lifetime declaration. + fn removing_span(&self) -> Span { + let mut span = self.suggestion("'dummy").0; + + if let Some(referenced_type_span) = self.referenced_type_span { + span = span.until(referenced_type_span); + } + + span + } + + fn suggestion(&self, lifetime_name: &str) -> (Span, String) { + self.lifetime.suggestion(lifetime_name) + } +} + +type LifetimeInfoMap<'tcx> = FxIndexMap<&'tcx hir::LifetimeKind, Vec<Info<'tcx>>>; + +struct LifetimeInfoCollector<'a, 'tcx> { + type_span: Span, + referenced_type_span: Option<Span>, + map: &'a mut LifetimeInfoMap<'tcx>, +} + +impl<'a, 'tcx> LifetimeInfoCollector<'a, 'tcx> { + fn collect(ty: &'tcx hir::Ty<'tcx>, map: &'a mut LifetimeInfoMap<'tcx>) { + let mut this = Self { type_span: ty.span, referenced_type_span: None, map }; + + intravisit::walk_unambig_ty(&mut this, ty); + } +} + +impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> { + #[instrument(skip(self))] + fn visit_lifetime(&mut self, lifetime: &'tcx hir::Lifetime) { + let type_span = self.type_span; + let referenced_type_span = self.referenced_type_span; + + let info = Info { type_span, referenced_type_span, lifetime }; + + self.map.entry(&lifetime.kind).or_default().push(info); + } + + #[instrument(skip(self))] + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) -> Self::Result { + let old_type_span = self.type_span; + let old_referenced_type_span = self.referenced_type_span; + + self.type_span = ty.span; + if let hir::TyKind::Ref(_, ty) = ty.kind { + self.referenced_type_span = Some(ty.ty.span); + } + + intravisit::walk_ty(self, ty); + + self.type_span = old_type_span; + self.referenced_type_span = old_referenced_type_span; + } +} diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 00586309572..9d3c74a9a2b 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -6,19 +6,19 @@ use rustc_abi::ExternAbi; use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, DiagArgValue, DiagMessage, DiagStyledString, ElidedLifetimeInPathSubdiag, - EmissionGuarantee, LintDiagnostic, MultiSpan, SubdiagMessageOp, Subdiagnostic, SuggestionStyle, + EmissionGuarantee, LintDiagnostic, MultiSpan, Subdiagnostic, SuggestionStyle, }; +use rustc_hir as hir; use rustc_hir::def::Namespace; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::VisitorExt; -use rustc_hir::{self as hir, MissingLifetimeKind}; use rustc_macros::{LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::inhabitedness::InhabitedPredicate; use rustc_middle::ty::{Clause, PolyExistentialTraitRef, Ty, TyCtxt}; use rustc_session::Session; use rustc_session::lint::AmbiguityErrorDiag; use rustc_span::edition::Edition; -use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, kw, sym}; +use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol, sym}; use crate::builtin::{InitError, ShorthandAssocTyCollector, TypeAliasBounds}; use crate::errors::{OverruledAttributeSub, RequestedLevel}; @@ -55,6 +55,53 @@ pub(crate) enum ShadowedIntoIterDiagSub { }, } +// autorefs.rs +#[derive(LintDiagnostic)] +#[diag(lint_implicit_unsafe_autorefs)] +#[note] +pub(crate) struct ImplicitUnsafeAutorefsDiag<'a> { + #[label(lint_raw_ptr)] + pub raw_ptr_span: Span, + pub raw_ptr_ty: Ty<'a>, + #[subdiagnostic] + pub origin: ImplicitUnsafeAutorefsOrigin<'a>, + #[subdiagnostic] + pub method: Option<ImplicitUnsafeAutorefsMethodNote>, + #[subdiagnostic] + pub suggestion: ImplicitUnsafeAutorefsSuggestion, +} + +#[derive(Subdiagnostic)] +pub(crate) enum ImplicitUnsafeAutorefsOrigin<'a> { + #[note(lint_autoref)] + Autoref { + #[primary_span] + autoref_span: Span, + autoref_ty: Ty<'a>, + }, + #[note(lint_overloaded_deref)] + OverloadedDeref, +} + +#[derive(Subdiagnostic)] +#[note(lint_method_def)] +pub(crate) struct ImplicitUnsafeAutorefsMethodNote { + #[primary_span] + pub def_span: Span, + pub method_name: Symbol, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")] +pub(crate) struct ImplicitUnsafeAutorefsSuggestion { + pub mutbl: &'static str, + pub deref: &'static str, + #[suggestion_part(code = "({mutbl}{deref}")] + pub start_span: Span, + #[suggestion_part(code = ")")] + pub end_span: Span, +} + // builtin.rs #[derive(LintDiagnostic)] #[diag(lint_builtin_while_true)] @@ -449,11 +496,7 @@ pub(crate) struct BuiltinUnpermittedTypeInitSub { } impl Subdiagnostic for BuiltinUnpermittedTypeInitSub { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { let mut err = self.err; loop { if let Some(span) = err.span { @@ -504,16 +547,12 @@ pub(crate) struct BuiltinClashingExternSub<'a> { } impl Subdiagnostic for BuiltinClashingExternSub<'_> { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { let mut expected_str = DiagStyledString::new(); expected_str.push(self.expected.fn_sig(self.tcx).to_string(), false); let mut found_str = DiagStyledString::new(); found_str.push(self.found.fn_sig(self.tcx).to_string(), true); - diag.note_expected_found(&"", expected_str, &"", found_str); + diag.note_expected_found("", expected_str, "", found_str); } } @@ -591,24 +630,40 @@ pub(crate) struct ExpectationNote { // ptr_nulls.rs #[derive(LintDiagnostic)] -pub(crate) enum PtrNullChecksDiag<'a> { - #[diag(lint_ptr_null_checks_fn_ptr)] - #[help(lint_help)] +pub(crate) enum UselessPtrNullChecksDiag<'a> { + #[diag(lint_useless_ptr_null_checks_fn_ptr)] + #[help] FnPtr { orig_ty: Ty<'a>, #[label] label: Span, }, - #[diag(lint_ptr_null_checks_ref)] + #[diag(lint_useless_ptr_null_checks_ref)] Ref { orig_ty: Ty<'a>, #[label] label: Span, }, - #[diag(lint_ptr_null_checks_fn_ret)] + #[diag(lint_useless_ptr_null_checks_fn_ret)] FnRet { fn_name: Ident }, } +#[derive(LintDiagnostic)] +pub(crate) enum InvalidNullArgumentsDiag { + #[diag(lint_invalid_null_arguments)] + #[help(lint_doc)] + NullPtrInline { + #[label(lint_origin)] + null_span: Span, + }, + #[diag(lint_invalid_null_arguments)] + #[help(lint_doc)] + NullPtrThroughBinding { + #[note(lint_origin)] + null_span: Span, + }, +} + // for_loops_over_fallibles.rs #[derive(LintDiagnostic)] #[diag(lint_for_loops_over_fallibles)] @@ -808,11 +863,7 @@ pub(crate) struct HiddenUnicodeCodepointsDiagLabels { } impl Subdiagnostic for HiddenUnicodeCodepointsDiagLabels { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { for (c, span) in self.spans { diag.span_label(span, format!("{c:?}")); } @@ -826,11 +877,7 @@ pub(crate) enum HiddenUnicodeCodepointsDiagSub { // Used because of multiple multipart_suggestion and note impl Subdiagnostic for HiddenUnicodeCodepointsDiagSub { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { match self { HiddenUnicodeCodepointsDiagSub::Escape { spans } => { diag.multipart_suggestion_with_style( @@ -944,6 +991,11 @@ pub(crate) struct TyQualified { pub(crate) struct TypeIrInherentUsage; #[derive(LintDiagnostic)] +#[diag(lint_type_ir_trait_usage)] +#[note] +pub(crate) struct TypeIrTraitUsage; + +#[derive(LintDiagnostic)] #[diag(lint_non_glob_import_type_ir_inherent)] pub(crate) struct NonGlobImportTypeIrInherent { #[suggestion(code = "{snippet}", applicability = "maybe-incorrect")] @@ -994,11 +1046,7 @@ pub(crate) struct NonBindingLetSub { } impl Subdiagnostic for NonBindingLetSub { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { let can_suggest_binding = self.drop_fn_start_end.is_some() || !self.is_assign_desugar; if can_suggest_binding { @@ -1282,11 +1330,7 @@ pub(crate) enum NonSnakeCaseDiagSub { } impl Subdiagnostic for NonSnakeCaseDiagSub { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { match self { NonSnakeCaseDiagSub::Label { span } => { diag.span_label(span, fluent::lint_label); @@ -1608,11 +1652,7 @@ pub(crate) enum OverflowingBinHexSign { } impl Subdiagnostic for OverflowingBinHexSign { - fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( - self, - diag: &mut Diag<'_, G>, - _f: &F, - ) { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { match self { OverflowingBinHexSign::Positive => { diag.note(fluent::lint_positive_note); @@ -1742,13 +1782,20 @@ pub(crate) enum InvalidNanComparisonsSuggestion { #[derive(LintDiagnostic)] pub(crate) enum AmbiguousWidePointerComparisons<'a> { #[diag(lint_ambiguous_wide_pointer_comparisons)] - Spanful { + SpanfulEq { #[subdiagnostic] addr_suggestion: AmbiguousWidePointerComparisonsAddrSuggestion<'a>, #[subdiagnostic] addr_metadata_suggestion: Option<AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a>>, }, #[diag(lint_ambiguous_wide_pointer_comparisons)] + SpanfulCmp { + #[subdiagnostic] + cast_suggestion: AmbiguousWidePointerComparisonsCastSuggestion<'a>, + #[subdiagnostic] + expect_suggestion: AmbiguousWidePointerComparisonsExpectSuggestion<'a>, + }, + #[diag(lint_ambiguous_wide_pointer_comparisons)] #[help(lint_addr_metadata_suggestion)] #[help(lint_addr_suggestion)] Spanless, @@ -1776,48 +1823,67 @@ pub(crate) struct AmbiguousWidePointerComparisonsAddrMetadataSuggestion<'a> { } #[derive(Subdiagnostic)] -pub(crate) enum AmbiguousWidePointerComparisonsAddrSuggestion<'a> { - #[multipart_suggestion( - lint_addr_suggestion, - style = "verbose", - // FIXME(#53934): make machine-applicable again - applicability = "maybe-incorrect" - )] - AddrEq { - ne: &'a str, - deref_left: &'a str, - deref_right: &'a str, - l_modifiers: &'a str, - r_modifiers: &'a str, - #[suggestion_part(code = "{ne}std::ptr::addr_eq({deref_left}")] - left: Span, - #[suggestion_part(code = "{l_modifiers}, {deref_right}")] - middle: Span, - #[suggestion_part(code = "{r_modifiers})")] - right: Span, - }, - #[multipart_suggestion( - lint_addr_suggestion, - style = "verbose", - // FIXME(#53934): make machine-applicable again - applicability = "maybe-incorrect" +#[multipart_suggestion( + lint_addr_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" +)] +pub(crate) struct AmbiguousWidePointerComparisonsAddrSuggestion<'a> { + pub(crate) ne: &'a str, + pub(crate) deref_left: &'a str, + pub(crate) deref_right: &'a str, + pub(crate) l_modifiers: &'a str, + pub(crate) r_modifiers: &'a str, + #[suggestion_part(code = "{ne}std::ptr::addr_eq({deref_left}")] + pub(crate) left: Span, + #[suggestion_part(code = "{l_modifiers}, {deref_right}")] + pub(crate) middle: Span, + #[suggestion_part(code = "{r_modifiers})")] + pub(crate) right: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + lint_cast_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" +)] +pub(crate) struct AmbiguousWidePointerComparisonsCastSuggestion<'a> { + pub(crate) deref_left: &'a str, + pub(crate) deref_right: &'a str, + pub(crate) paren_left: &'a str, + pub(crate) paren_right: &'a str, + pub(crate) l_modifiers: &'a str, + pub(crate) r_modifiers: &'a str, + #[suggestion_part(code = "({deref_left}")] + pub(crate) left_before: Option<Span>, + #[suggestion_part(code = "{l_modifiers}{paren_left}.cast::<()>()")] + pub(crate) left_after: Span, + #[suggestion_part(code = "({deref_right}")] + pub(crate) right_before: Option<Span>, + #[suggestion_part(code = "{r_modifiers}{paren_right}.cast::<()>()")] + pub(crate) right_after: Span, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + lint_expect_suggestion, + style = "verbose", + // FIXME(#53934): make machine-applicable again + applicability = "maybe-incorrect" +)] +pub(crate) struct AmbiguousWidePointerComparisonsExpectSuggestion<'a> { + pub(crate) paren_left: &'a str, + pub(crate) paren_right: &'a str, + // FIXME(#127436): Adjust once resolved + #[suggestion_part( + code = r#"{{ #[expect(ambiguous_wide_pointer_comparisons, reason = "...")] {paren_left}"# )] - Cast { - deref_left: &'a str, - deref_right: &'a str, - paren_left: &'a str, - paren_right: &'a str, - l_modifiers: &'a str, - r_modifiers: &'a str, - #[suggestion_part(code = "({deref_left}")] - left_before: Option<Span>, - #[suggestion_part(code = "{l_modifiers}{paren_left}.cast::<()>()")] - left_after: Span, - #[suggestion_part(code = "({deref_right}")] - right_before: Option<Span>, - #[suggestion_part(code = "{r_modifiers}{paren_right}.cast::<()>()")] - right_after: Span, - }, + pub(crate) before: Span, + #[suggestion_part(code = "{paren_right} }}")] + pub(crate) after: Span, } #[derive(LintDiagnostic)] @@ -2233,6 +2299,16 @@ pub(crate) mod unexpected_cfg_name { pub(crate) enum CodeSuggestion { #[help(lint_unexpected_cfg_define_features)] DefineFeatures, + #[multipart_suggestion( + lint_unexpected_cfg_name_version_syntax, + applicability = "machine-applicable" + )] + VersionSyntax { + #[suggestion_part(code = "(")] + between_name_and_value: Span, + #[suggestion_part(code = ")")] + after_value: Span, + }, #[suggestion( lint_unexpected_cfg_name_similar_name_value, applicability = "maybe-incorrect", @@ -2676,58 +2752,6 @@ pub(crate) struct ElidedLifetimesInPaths { pub subdiag: ElidedLifetimeInPathSubdiag, } -pub(crate) struct ElidedNamedLifetime { - pub span: Span, - pub kind: MissingLifetimeKind, - pub name: Symbol, - pub declaration: Option<Span>, -} - -impl<G: EmissionGuarantee> LintDiagnostic<'_, G> for ElidedNamedLifetime { - fn decorate_lint(self, diag: &mut rustc_errors::Diag<'_, G>) { - let Self { span, kind, name, declaration } = self; - diag.primary_message(fluent::lint_elided_named_lifetime); - diag.arg("name", name); - diag.span_label(span, fluent::lint_label_elided); - if let Some(declaration) = declaration { - diag.span_label(declaration, fluent::lint_label_named); - } - // FIXME(GrigorenkoPV): this `if` and `return` should be removed, - // but currently this lint's suggestions can conflict with those of `clippy::needless_lifetimes`: - // https://github.com/rust-lang/rust/pull/129840#issuecomment-2323349119 - // HACK: `'static` suggestions will never sonflict, emit only those for now. - if name != kw::StaticLifetime { - return; - } - match kind { - MissingLifetimeKind::Underscore => diag.span_suggestion_verbose( - span, - fluent::lint_suggestion, - format!("{name}"), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Ampersand => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("{name} "), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Comma => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("{name}, "), - Applicability::MachineApplicable, - ), - MissingLifetimeKind::Brackets => diag.span_suggestion_verbose( - span.shrink_to_hi(), - fluent::lint_suggestion, - format!("<{name}>"), - Applicability::MachineApplicable, - ), - }; - } -} - #[derive(LintDiagnostic)] #[diag(lint_invalid_crate_type_value)] pub(crate) struct UnknownCrateTypes { @@ -3001,7 +3025,9 @@ pub(crate) struct ByteSliceInPackedStructWithDerive { #[derive(LintDiagnostic)] #[diag(lint_unused_extern_crate)] pub(crate) struct UnusedExternCrate { - #[suggestion(code = "", applicability = "machine-applicable")] + #[label] + pub span: Span, + #[suggestion(code = "", applicability = "machine-applicable", style = "verbose")] pub removal_span: Span, } @@ -3163,3 +3189,128 @@ pub(crate) struct ReservedMultihash { #[suggestion(code = " ", applicability = "machine-applicable")] pub suggestion: Span, } + +#[derive(Debug)] +pub(crate) struct MismatchedLifetimeSyntaxes { + pub lifetime_name: String, + pub inputs: Vec<Span>, + pub outputs: Vec<Span>, + + pub suggestions: Vec<MismatchedLifetimeSyntaxesSuggestion>, +} + +impl<'a, G: EmissionGuarantee> LintDiagnostic<'a, G> for MismatchedLifetimeSyntaxes { + fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, G>) { + diag.primary_message(fluent::lint_mismatched_lifetime_syntaxes); + + diag.arg("lifetime_name", self.lifetime_name); + + diag.arg("n_inputs", self.inputs.len()); + for input in self.inputs { + let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_inputs); + diag.span_label(input, a); + } + + diag.arg("n_outputs", self.outputs.len()); + for output in self.outputs { + let a = diag.eagerly_translate(fluent::lint_label_mismatched_lifetime_syntaxes_outputs); + diag.span_label(output, a); + } + + let mut suggestions = self.suggestions.into_iter(); + if let Some(s) = suggestions.next() { + diag.subdiagnostic(s); + + for mut s in suggestions { + s.make_tool_only(); + diag.subdiagnostic(s); + } + } + } +} + +#[derive(Debug)] +pub(crate) enum MismatchedLifetimeSyntaxesSuggestion { + Implicit { + suggestions: Vec<Span>, + tool_only: bool, + }, + + Mixed { + implicit_suggestions: Vec<Span>, + explicit_anonymous_suggestions: Vec<(Span, String)>, + tool_only: bool, + }, + + Explicit { + lifetime_name: String, + suggestions: Vec<(Span, String)>, + tool_only: bool, + }, +} + +impl MismatchedLifetimeSyntaxesSuggestion { + fn make_tool_only(&mut self) { + use MismatchedLifetimeSyntaxesSuggestion::*; + + let tool_only = match self { + Implicit { tool_only, .. } | Mixed { tool_only, .. } | Explicit { tool_only, .. } => { + tool_only + } + }; + + *tool_only = true; + } +} + +impl Subdiagnostic for MismatchedLifetimeSyntaxesSuggestion { + fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) { + use MismatchedLifetimeSyntaxesSuggestion::*; + + let style = |tool_only| { + if tool_only { SuggestionStyle::CompletelyHidden } else { SuggestionStyle::ShowAlways } + }; + + match self { + Implicit { suggestions, tool_only } => { + let suggestions = suggestions.into_iter().map(|s| (s, String::new())).collect(); + diag.multipart_suggestion_with_style( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_implicit, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + + Mixed { implicit_suggestions, explicit_anonymous_suggestions, tool_only } => { + let implicit_suggestions = + implicit_suggestions.into_iter().map(|s| (s, String::new())); + + let suggestions = + implicit_suggestions.chain(explicit_anonymous_suggestions).collect(); + + diag.multipart_suggestion_with_style( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_mixed, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + + Explicit { lifetime_name, suggestions, tool_only } => { + diag.arg("lifetime_name", lifetime_name); + + let msg = diag.eagerly_translate( + fluent::lint_mismatched_lifetime_syntaxes_suggestion_explicit, + ); + + diag.multipart_suggestion_with_style( + msg, + suggestions, + Applicability::MachineApplicable, + style(tool_only), + ); + } + } + } +} diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs index 35db8632625..dea7c8ac708 100644 --- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs +++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable { let def_id = item.owner_id.to_def_id(); // NOTE(nbdd0121): use `dyn_compatibility_violations` instead of `is_dyn_compatible` because // the latter will report `where_clause_object_safety` lint. - if let hir::ItemKind::Trait(_, _, _, _, _) = item.kind + if let hir::ItemKind::Trait(_, _, ident, ..) = item.kind && cx.tcx.is_dyn_compatible(def_id) { let direct_super_traits_iter = cx @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable { cx.emit_span_lint( MULTIPLE_SUPERTRAIT_UPCASTABLE, cx.tcx.def_span(def_id), - crate::lints::MultipleSupertraitUpcastable { ident: item.ident }, + crate::lints::MultipleSupertraitUpcastable { ident }, ); } } diff --git a/compiler/rustc_lint/src/non_ascii_idents.rs b/compiler/rustc_lint/src/non_ascii_idents.rs index 66e207a451e..9c11fb41aa6 100644 --- a/compiler/rustc_lint/src/non_ascii_idents.rs +++ b/compiler/rustc_lint/src/non_ascii_idents.rs @@ -159,12 +159,13 @@ impl EarlyLintPass for NonAsciiIdents { use rustc_span::Span; use unicode_security::GeneralSecurityProfile; - let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).0 != Level::Allow; + let check_non_ascii_idents = cx.builder.lint_level(NON_ASCII_IDENTS).level != Level::Allow; let check_uncommon_codepoints = - cx.builder.lint_level(UNCOMMON_CODEPOINTS).0 != Level::Allow; - let check_confusable_idents = cx.builder.lint_level(CONFUSABLE_IDENTS).0 != Level::Allow; + cx.builder.lint_level(UNCOMMON_CODEPOINTS).level != Level::Allow; + let check_confusable_idents = + cx.builder.lint_level(CONFUSABLE_IDENTS).level != Level::Allow; let check_mixed_script_confusables = - cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).0 != Level::Allow; + cx.builder.lint_level(MIXED_SCRIPT_CONFUSABLES).level != Level::Allow; if !check_non_ascii_idents && !check_uncommon_codepoints diff --git a/compiler/rustc_lint/src/non_fmt_panic.rs b/compiler/rustc_lint/src/non_fmt_panic.rs index ac9f8d92dac..16c06100808 100644 --- a/compiler/rustc_lint/src/non_fmt_panic.rs +++ b/compiler/rustc_lint/src/non_fmt_panic.rs @@ -7,7 +7,7 @@ use rustc_parse_format::{ParseMode, Parser, Piece}; use rustc_session::lint::FutureIncompatibilityReason; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::edition::Edition; -use rustc_span::{InnerSpan, Span, Symbol, hygiene, kw, sym}; +use rustc_span::{InnerSpan, Span, Symbol, hygiene, sym}; use rustc_trait_selection::infer::InferCtxtExt; use crate::lints::{NonFmtPanicBraces, NonFmtPanicUnused}; @@ -167,7 +167,7 @@ fn check_panic<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>, arg: &'tc .get_diagnostic_item(sym::Debug) .is_some_and(|t| infcx.type_implements_trait(t, [ty], param_env).may_apply()); - let suggest_panic_any = !is_str && panic == sym::std_panic_macro; + let suggest_panic_any = !is_str && panic == Some(sym::std_panic_macro); let fmt_applicability = if suggest_panic_any { // If we can use panic_any, use that as the MachineApplicable suggestion. @@ -297,10 +297,13 @@ fn find_delimiters(cx: &LateContext<'_>, span: Span) -> Option<(Span, Span, char )) } -fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, Symbol, Symbol) { +fn panic_call<'tcx>( + cx: &LateContext<'tcx>, + f: &'tcx hir::Expr<'tcx>, +) -> (Span, Option<Symbol>, Symbol) { let mut expn = f.span.ctxt().outer_expn_data(); - let mut panic_macro = kw::Empty; + let mut panic_macro = None; // Unwrap more levels of macro expansion, as panic_2015!() // was likely expanded from panic!() and possibly from @@ -320,7 +323,7 @@ fn panic_call<'tcx>(cx: &LateContext<'tcx>, f: &'tcx hir::Expr<'tcx>) -> (Span, break; } expn = parent; - panic_macro = name; + panic_macro = Some(name); } let macro_symbol = diff --git a/compiler/rustc_lint/src/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs index 0890890d12c..b877f909fc0 100644 --- a/compiler/rustc_lint/src/non_local_def.rs +++ b/compiler/rustc_lint/src/non_local_def.rs @@ -104,8 +104,10 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { // determining if we are in a doctest context can't currently be determined // by the code itself (there are no specific attributes), but fortunately rustdoc // sets a perma-unstable env var for libtest so we just reuse that for now - let is_at_toplevel_doctest = - || self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok(); + let is_at_toplevel_doctest = || { + self.body_depth == 2 + && cx.tcx.env_var_os("UNSTABLE_RUSTDOC_TEST_PATH".as_ref()).is_some() + }; match item.kind { ItemKind::Impl(impl_) => { @@ -181,10 +183,10 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { && 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 ItemKind::Const(ident, _, ty, _) = item.kind && let TyKind::Tup(&[]) = ty.kind { - Some(item.ident.span) + Some(ident.span) } else { None }; @@ -238,7 +240,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { }, ) } - ItemKind::Macro(_macro, MacroKind::Bang) + ItemKind::Macro(_, _macro, MacroKind::Bang) if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) => { cx.emit_span_lint( diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs index 49f9ad39780..31c18074466 100644 --- a/compiler/rustc_lint/src/nonstandard_style.rs +++ b/compiler/rustc_lint/src/nonstandard_style.rs @@ -1,5 +1,6 @@ use rustc_abi::ExternAbi; -use rustc_attr_parsing::{AttributeKind, AttributeParser, ReprAttr}; +use rustc_attr_data_structures::{AttributeKind, ReprAttr}; +use rustc_attr_parsing::AttributeParser; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::FnKind; use rustc_hir::{AttrArgs, AttrItem, Attribute, GenericParamKind, PatExprKind, PatKind}; @@ -172,20 +173,22 @@ impl EarlyLintPass for NonCamelCaseTypes { } match &it.kind { - ast::ItemKind::TyAlias(..) - | ast::ItemKind::Enum(..) - | ast::ItemKind::Struct(..) - | ast::ItemKind::Union(..) => self.check_case(cx, "type", &it.ident), - ast::ItemKind::Trait(..) => self.check_case(cx, "trait", &it.ident), - ast::ItemKind::TraitAlias(..) => self.check_case(cx, "trait alias", &it.ident), + ast::ItemKind::TyAlias(box ast::TyAlias { ident, .. }) + | ast::ItemKind::Enum(ident, ..) + | ast::ItemKind::Struct(ident, ..) + | ast::ItemKind::Union(ident, ..) => self.check_case(cx, "type", ident), + ast::ItemKind::Trait(box ast::Trait { ident, .. }) => { + self.check_case(cx, "trait", ident) + } + ast::ItemKind::TraitAlias(ident, _, _) => self.check_case(cx, "trait alias", ident), // N.B. This check is only for inherent associated types, so that we don't lint against // trait impls where we should have warned for the trait definition already. ast::ItemKind::Impl(box ast::Impl { of_trait: None, items, .. }) => { for it in items { // FIXME: this doesn't respect `#[allow(..)]` on the item itself. - if let ast::AssocItemKind::Type(..) = it.kind { - self.check_case(cx, "associated type", &it.ident); + if let ast::AssocItemKind::Type(alias) = &it.kind { + self.check_case(cx, "associated type", &alias.ident); } } } @@ -194,8 +197,8 @@ impl EarlyLintPass for NonCamelCaseTypes { } fn check_trait_item(&mut self, cx: &EarlyContext<'_>, it: &ast::AssocItem) { - if let ast::AssocItemKind::Type(..) = it.kind { - self.check_case(cx, "associated type", &it.ident); + if let ast::AssocItemKind::Type(alias) = &it.kind { + self.check_case(cx, "associated type", &alias.ident); } } @@ -274,18 +277,13 @@ impl NonSnakeCase { let ident = ident.trim_start_matches('\''); let ident = ident.trim_matches('_'); - let mut allow_underscore = true; - ident.chars().all(|c| { - allow_underscore = match c { - '_' if !allow_underscore => return false, - '_' => false, - // It would be more obvious to use `c.is_lowercase()`, - // but some characters do not have a lowercase form - c if !c.is_uppercase() => true, - _ => return false, - }; - true - }) + if ident.contains("__") { + return false; + } + + // This correctly handles letters in languages with and without + // cases, as well as numbers and underscores. + !ident.chars().any(char::is_uppercase) } let name = ident.name.as_str(); @@ -342,8 +340,8 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { let crate_ident = if let Some(name) = &cx.tcx.sess.opts.crate_name { Some(Ident::from_str(name)) } else { - ast::attr::find_by_name(cx.tcx.hir().attrs(hir::CRATE_HIR_ID), sym::crate_name) - .and_then(|attr| { + ast::attr::find_by_name(cx.tcx.hir_attrs(hir::CRATE_HIR_ID), sym::crate_name).and_then( + |attr| { if let Attribute::Unparsed(n) = attr && let AttrItem { args: AttrArgs::Eq { eq_span: _, expr: lit }, .. } = n.as_ref() @@ -371,7 +369,8 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { } else { None } - }) + }, + ) }; if let Some(ident) = &crate_ident { @@ -419,16 +418,28 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase { } fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - if let hir::ItemKind::Mod(_) = it.kind { - self.check_snake_case(cx, "module", &it.ident); + if let hir::ItemKind::Mod(ident, _) = it.kind { + self.check_snake_case(cx, "module", &ident); + } + } + + fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_, hir::AmbigArg>) { + if let hir::TyKind::BareFn(hir::BareFnTy { param_idents, .. }) = &ty.kind { + for param_ident in *param_idents { + if let Some(param_ident) = param_ident { + self.check_snake_case(cx, "variable", param_ident); + } + } } } fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) { - if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind { + if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(param_idents)) = item.kind { self.check_snake_case(cx, "trait method", &item.ident); - for param_name in pnames { - self.check_snake_case(cx, "variable", param_name); + for param_ident in param_idents { + if let Some(param_ident) = param_ident { + self.check_snake_case(cx, "variable", param_ident); + } } } } @@ -500,13 +511,15 @@ impl NonUpperCaseGlobals { impl<'tcx> LateLintPass<'tcx> for NonUpperCaseGlobals { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - let attrs = cx.tcx.hir().attrs(it.hir_id()); + let attrs = cx.tcx.hir_attrs(it.hir_id()); match it.kind { - hir::ItemKind::Static(..) if !ast::attr::contains_name(attrs, sym::no_mangle) => { - NonUpperCaseGlobals::check_upper_case(cx, "static variable", &it.ident); + hir::ItemKind::Static(_, ident, ..) + if !ast::attr::contains_name(attrs, sym::no_mangle) => + { + NonUpperCaseGlobals::check_upper_case(cx, "static variable", &ident); } - hir::ItemKind::Const(..) => { - NonUpperCaseGlobals::check_upper_case(cx, "constant", &it.ident); + hir::ItemKind::Const(ident, ..) => { + NonUpperCaseGlobals::check_upper_case(cx, "constant", &ident); } _ => {} } diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 7eaf83f5acf..f836094191e 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -1,9 +1,8 @@ use rustc_hir::{self as hir, AmbigArg}; use rustc_infer::infer::TyCtxtInferExt; use rustc_macros::{LintDiagnostic, Subdiagnostic}; -use rustc_middle::ty::fold::BottomUpFolder; use rustc_middle::ty::print::{PrintTraitPredicateExt as _, TraitPredPrintModifiersAndPath}; -use rustc_middle::ty::{self, Ty, TypeFoldable}; +use rustc_middle::ty::{self, BottomUpFolder, Ty, TypeFoldable}; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::{Span, kw}; use rustc_trait_selection::traits::{self, ObligationCtxt}; @@ -43,6 +42,7 @@ declare_lint! { /// /// type Tait = impl Sized; /// + /// #[define_opaque(Tait)] /// fn test() -> impl Trait<Assoc = Tait> { /// 42 /// } diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs index 1489f9de819..826bce2c315 100644 --- a/compiler/rustc_lint/src/ptr_nulls.rs +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -1,9 +1,11 @@ use rustc_ast::LitKind; use rustc_hir::{BinOpKind, Expr, ExprKind, TyKind}; +use rustc_middle::ty::RawPtr; use rustc_session::{declare_lint, declare_lint_pass}; -use rustc_span::sym; +use rustc_span::{Span, sym}; -use crate::lints::PtrNullChecksDiag; +use crate::lints::{InvalidNullArgumentsDiag, UselessPtrNullChecksDiag}; +use crate::utils::peel_casts; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { @@ -31,17 +33,40 @@ declare_lint! { "useless checking of non-null-typed pointer" } -declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS]); +declare_lint! { + /// The `invalid_null_arguments` lint checks for invalid usage of null pointers in arguments. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// # use std::{slice, ptr}; + /// // Undefined behavior + /// # let _slice: &[u8] = + /// unsafe { slice::from_raw_parts(ptr::null(), 0) }; + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Calling methods whos safety invariants requires non-null ptr with a null pointer + /// is [Undefined Behavior](https://doc.rust-lang.org/reference/behavior-considered-undefined.html)! + INVALID_NULL_ARGUMENTS, + Deny, + "invalid null pointer in arguments" +} + +declare_lint_pass!(PtrNullChecks => [USELESS_PTR_NULL_CHECKS, INVALID_NULL_ARGUMENTS]); /// This function checks if the expression is from a series of consecutive casts, /// ie. `(my_fn as *const _ as *mut _).cast_mut()` and whether the original expression is either /// a fn ptr, a reference, or a function call whose definition is /// annotated with `#![rustc_never_returns_null_ptr]`. /// If this situation is present, the function returns the appropriate diagnostic. -fn incorrect_check<'a, 'tcx: 'a>( +fn useless_check<'a, 'tcx: 'a>( cx: &'a LateContext<'tcx>, mut e: &'a Expr<'a>, -) -> Option<PtrNullChecksDiag<'tcx>> { +) -> Option<UselessPtrNullChecksDiag<'tcx>> { let mut had_at_least_one_cast = false; loop { e = e.peel_blocks(); @@ -50,14 +75,14 @@ fn incorrect_check<'a, 'tcx: 'a>( && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr) && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) { - return Some(PtrNullChecksDiag::FnRet { fn_name }); + return Some(UselessPtrNullChecksDiag::FnRet { fn_name }); } else if let ExprKind::Call(path, _args) = e.kind && let ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && cx.tcx.has_attr(def_id, sym::rustc_never_returns_null_ptr) && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) { - return Some(PtrNullChecksDiag::FnRet { fn_name }); + return Some(UselessPtrNullChecksDiag::FnRet { fn_name }); } e = if let ExprKind::Cast(expr, t) = e.kind && let TyKind::Ptr(_) = t.kind @@ -73,9 +98,9 @@ fn incorrect_check<'a, 'tcx: 'a>( } else if had_at_least_one_cast { let orig_ty = cx.typeck_results().expr_ty(e); return if orig_ty.is_fn() { - Some(PtrNullChecksDiag::FnPtr { orig_ty, label: e.span }) + Some(UselessPtrNullChecksDiag::FnPtr { orig_ty, label: e.span }) } else if orig_ty.is_ref() { - Some(PtrNullChecksDiag::Ref { orig_ty, label: e.span }) + Some(UselessPtrNullChecksDiag::Ref { orig_ty, label: e.span }) } else { None }; @@ -85,6 +110,25 @@ fn incorrect_check<'a, 'tcx: 'a>( } } +/// Checks if the given expression is a null pointer (modulo casting) +fn is_null_ptr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Span> { + let (expr, _) = peel_casts(cx, expr); + + if let ExprKind::Call(path, []) = expr.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_item) = cx.tcx.get_diagnostic_name(def_id) + { + (diag_item == sym::ptr_null || diag_item == sym::ptr_null_mut).then_some(expr.span) + } else if let ExprKind::Lit(spanned) = expr.kind + && let LitKind::Int(v, _) = spanned.node + { + (v == 0).then_some(expr.span) + } else { + None + } +} + impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { match expr.kind { @@ -97,12 +141,68 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_const_is_null | sym::ptr_is_null) ) - && let Some(diag) = incorrect_check(cx, arg) => + && let Some(diag) = useless_check(cx, arg) => { cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) } // Catching: + // <path>(arg...) where `arg` is null-ptr and `path` is a fn that expect non-null-ptr + ExprKind::Call(path, args) + if let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(diag_name) = cx.tcx.get_diagnostic_name(def_id) => + { + // `arg` positions where null would cause U.B and whenever ZST are allowed. + // + // We should probably have a `rustc` attribute, but checking them is costly, + // maybe if we checked for null ptr first, it would be acceptable? + let (arg_indices, are_zsts_allowed): (&[_], _) = match diag_name { + sym::ptr_read + | sym::ptr_read_unaligned + | sym::ptr_read_volatile + | sym::ptr_replace + | sym::ptr_write + | sym::ptr_write_bytes + | sym::ptr_write_unaligned + | sym::ptr_write_volatile => (&[0], true), + sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut => (&[0], false), + sym::ptr_copy + | sym::ptr_copy_nonoverlapping + | sym::ptr_swap + | sym::ptr_swap_nonoverlapping => (&[0, 1], true), + _ => return, + }; + + for &arg_idx in arg_indices { + if let Some(arg) = args.get(arg_idx) + && let Some(null_span) = is_null_ptr(cx, arg) + && let Some(ty) = cx.typeck_results().expr_ty_opt(arg) + && let RawPtr(ty, _mutbl) = ty.kind() + { + // If ZST are fine, don't lint on them + let typing_env = cx.typing_env(); + if are_zsts_allowed + && cx + .tcx + .layout_of(typing_env.as_query_input(*ty)) + .is_ok_and(|layout| layout.is_1zst()) + { + break; + } + + let diag = if arg.span.contains(null_span) { + InvalidNullArgumentsDiag::NullPtrInline { null_span } + } else { + InvalidNullArgumentsDiag::NullPtrThroughBinding { null_span } + }; + + cx.emit_span_lint(INVALID_NULL_ARGUMENTS, expr.span, diag) + } + } + } + + // Catching: // (fn_ptr as *<const/mut> <ty>).is_null() ExprKind::MethodCall(_, receiver, _, _) if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) @@ -110,18 +210,18 @@ impl<'tcx> LateLintPass<'tcx> for PtrNullChecks { cx.tcx.get_diagnostic_name(def_id), Some(sym::ptr_const_is_null | sym::ptr_is_null) ) - && let Some(diag) = incorrect_check(cx, receiver) => + && let Some(diag) = useless_check(cx, receiver) => { cx.emit_span_lint(USELESS_PTR_NULL_CHECKS, expr.span, diag) } ExprKind::Binary(op, left, right) if matches!(op.node, BinOpKind::Eq) => { let to_check: &Expr<'_>; - let diag: PtrNullChecksDiag<'_>; - if let Some(ddiag) = incorrect_check(cx, left) { + let diag: UselessPtrNullChecksDiag<'_>; + if let Some(ddiag) = useless_check(cx, left) { to_check = right; diag = ddiag; - } else if let Some(ddiag) = incorrect_check(cx, right) { + } else if let Some(ddiag) = useless_check(cx, right) { to_check = left; diag = ddiag; } else { diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs index 7c6656f91c9..1d4d380cb68 100644 --- a/compiler/rustc_lint/src/reference_casting.rs +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -6,6 +6,7 @@ use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::sym; use crate::lints::InvalidReferenceCastingDiag; +use crate::utils::peel_casts; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { @@ -235,46 +236,3 @@ fn is_cast_to_bigger_memory_layout<'tcx>( None } } - -fn peel_casts<'tcx>(cx: &LateContext<'tcx>, mut e: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, bool) { - let mut gone_trough_unsafe_cell_raw_get = false; - - loop { - e = e.peel_blocks(); - // <expr> as ... - e = if let ExprKind::Cast(expr, _) = e.kind { - expr - // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const() - } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind - && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && matches!( - cx.tcx.get_diagnostic_name(def_id), - Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) - ) - { - expr - // ptr::from_ref(<expr>), UnsafeCell::raw_get(<expr>) or mem::transmute<_, _>(<expr>) - } else if let ExprKind::Call(path, [arg]) = e.kind - && let ExprKind::Path(ref qpath) = path.kind - && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() - && matches!( - cx.tcx.get_diagnostic_name(def_id), - Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get | sym::transmute) - ) - { - if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) { - gone_trough_unsafe_cell_raw_get = true; - } - arg - } else { - let init = cx.expr_or_init(e); - if init.hir_id != e.hir_id { - init - } else { - break; - } - }; - } - - (e, gone_trough_unsafe_cell_raw_get) -} diff --git a/compiler/rustc_lint/src/shadowed_into_iter.rs b/compiler/rustc_lint/src/shadowed_into_iter.rs index 571cab934fd..00fa0499556 100644 --- a/compiler/rustc_lint/src/shadowed_into_iter.rs +++ b/compiler/rustc_lint/src/shadowed_into_iter.rs @@ -1,4 +1,4 @@ -use rustc_hir as hir; +use rustc_hir::{self as hir, LangItem}; use rustc_middle::ty::{self, Ty}; use rustc_session::lint::FutureIncompatibilityReason; use rustc_session::{declare_lint, impl_lint_pass}; @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter { let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { return; }; - if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() { + if !cx.tcx.is_lang_item(method_def_id, LangItem::IntoIterIntoIter) { return; } diff --git a/compiler/rustc_lint/src/static_mut_refs.rs b/compiler/rustc_lint/src/static_mut_refs.rs index 50021157dda..4dda3c7951b 100644 --- a/compiler/rustc_lint/src/static_mut_refs.rs +++ b/compiler/rustc_lint/src/static_mut_refs.rs @@ -51,7 +51,7 @@ declare_lint! { /// This lint is "warn" by default on editions up to 2021, in 2024 is "deny". pub STATIC_MUT_REFS, Warn, - "shared references or mutable references of mutable static is discouraged", + "creating a shared reference to mutable static", @future_incompatible = FutureIncompatibleInfo { reason: FutureIncompatibilityReason::EditionError(Edition::Edition2024), reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2024/static-mut-references.html>", diff --git a/compiler/rustc_lint/src/transmute.rs b/compiler/rustc_lint/src/transmute.rs new file mode 100644 index 00000000000..bc1d4587d07 --- /dev/null +++ b/compiler/rustc_lint/src/transmute.rs @@ -0,0 +1,278 @@ +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::{self as hir}; +use rustc_macros::LintDiagnostic; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint, impl_lint_pass}; +use rustc_span::sym; + +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `ptr_to_integer_transmute_in_consts` lint detects pointer to integer + /// transmute in const functions and associated constants. + /// + /// ### Example + /// + /// ```rust + /// const fn foo(ptr: *const u8) -> usize { + /// unsafe { + /// std::mem::transmute::<*const u8, usize>(ptr) + /// } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Transmuting pointers to integers in a `const` context is undefined behavior. + /// Any attempt to use the resulting integer will abort const-evaluation. + /// + /// But sometimes the compiler might not emit an error for pointer to integer transmutes + /// inside const functions and associated consts because they are evaluated only when referenced. + /// Therefore, this lint serves as an extra layer of defense to prevent any undefined behavior + /// from compiling without any warnings or errors. + /// + /// See [std::mem::transmute] in the reference for more details. + /// + /// [std::mem::transmute]: https://doc.rust-lang.org/std/mem/fn.transmute.html + pub PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, + Warn, + "detects pointer to integer transmutes in const functions and associated constants", +} + +declare_lint! { + /// The `unnecessary_transmutes` lint detects transmutations that have safer alternatives. + /// + /// ### Example + /// + /// ```rust + /// fn bytes_at_home(x: [u8; 4]) -> u32 { + /// unsafe { std::mem::transmute(x) } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Using an explicit method is preferable over calls to + /// [`transmute`](https://doc.rust-lang.org/std/mem/fn.transmute.html) as + /// they more clearly communicate the intent, are easier to review, and + /// are less likely to accidentally result in unsoundness. + pub UNNECESSARY_TRANSMUTES, + Warn, + "detects transmutes that can also be achieved by other operations" +} + +pub(crate) struct CheckTransmutes; + +impl_lint_pass!(CheckTransmutes => [PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, UNNECESSARY_TRANSMUTES]); + +impl<'tcx> LateLintPass<'tcx> for CheckTransmutes { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + let hir::ExprKind::Call(callee, [arg]) = expr.kind else { + return; + }; + let hir::ExprKind::Path(qpath) = callee.kind else { + return; + }; + let Res::Def(DefKind::Fn, def_id) = cx.qpath_res(&qpath, callee.hir_id) else { + return; + }; + if !cx.tcx.is_intrinsic(def_id, sym::transmute) { + return; + }; + let body_owner_def_id = cx.tcx.hir_enclosing_body_owner(expr.hir_id); + let const_context = cx.tcx.hir_body_const_context(body_owner_def_id); + let args = cx.typeck_results().node_args(callee.hir_id); + + let src = args.type_at(0); + let dst = args.type_at(1); + + check_ptr_transmute_in_const(cx, expr, body_owner_def_id, const_context, src, dst); + check_unnecessary_transmute(cx, expr, callee, arg, const_context, src, dst); + } +} + +/// Check for transmutes that exhibit undefined behavior. +/// For example, transmuting pointers to integers in a const context. +/// +/// Why do we consider const functions and associated constants only? +/// +/// Generally, undefined behavior in const items are handled by the evaluator. +/// But, const functions and associated constants are evaluated only when referenced. +/// This can result in undefined behavior in a library going unnoticed until +/// the function or constant is actually used. +/// +/// Therefore, we only consider const functions and associated constants here and leave +/// other const items to be handled by the evaluator. +fn check_ptr_transmute_in_const<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + body_owner_def_id: LocalDefId, + const_context: Option<hir::ConstContext>, + src: Ty<'tcx>, + dst: Ty<'tcx>, +) { + if matches!(const_context, Some(hir::ConstContext::ConstFn)) + || matches!(cx.tcx.def_kind(body_owner_def_id), DefKind::AssocConst) + { + if src.is_raw_ptr() && dst.is_integral() { + cx.tcx.emit_node_span_lint( + PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, + expr.hir_id, + expr.span, + UndefinedTransmuteLint, + ); + } + } +} + +/// Check for transmutes that overlap with stdlib methods. +/// For example, transmuting `[u8; 4]` to `u32`. +/// +/// We chose not to lint u8 -> bool transmutes, see #140431. +fn check_unnecessary_transmute<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + callee: &'tcx hir::Expr<'tcx>, + arg: &'tcx hir::Expr<'tcx>, + const_context: Option<hir::ConstContext>, + src: Ty<'tcx>, + dst: Ty<'tcx>, +) { + let callee_span = callee.span.find_ancestor_inside(expr.span).unwrap_or(callee.span); + let (sugg, help) = match (src.kind(), dst.kind()) { + // dont check the length; transmute does that for us. + // [u8; _] => primitive + (ty::Array(t, _), ty::Uint(_) | ty::Float(_) | ty::Int(_)) + if *t.kind() == ty::Uint(ty::UintTy::U8) => + { + ( + Some(vec![(callee_span, format!("{dst}::from_ne_bytes"))]), + Some( + "there's also `from_le_bytes` and `from_be_bytes` if you expect a particular byte order", + ), + ) + } + // primitive => [u8; _] + (ty::Uint(_) | ty::Float(_) | ty::Int(_), ty::Array(t, _)) + if *t.kind() == ty::Uint(ty::UintTy::U8) => + { + ( + Some(vec![(callee_span, format!("{src}::to_ne_bytes"))]), + Some( + "there's also `to_le_bytes` and `to_be_bytes` if you expect a particular byte order", + ), + ) + } + // char → u32 + (ty::Char, ty::Uint(ty::UintTy::U32)) => { + (Some(vec![(callee_span, "u32::from".to_string())]), None) + } + // char (→ u32) → i32 + (ty::Char, ty::Int(ty::IntTy::I32)) => ( + Some(vec![ + (callee_span, "u32::from".to_string()), + (expr.span.shrink_to_hi(), ".cast_signed()".to_string()), + ]), + None, + ), + // u32 → char + (ty::Uint(ty::UintTy::U32), ty::Char) => ( + Some(vec![(callee_span, "char::from_u32_unchecked".to_string())]), + Some("consider using `char::from_u32(…).unwrap()`"), + ), + // i32 → char + (ty::Int(ty::IntTy::I32), ty::Char) => ( + Some(vec![ + (callee_span, "char::from_u32_unchecked(i32::cast_unsigned".to_string()), + (expr.span.shrink_to_hi(), ")".to_string()), + ]), + Some("consider using `char::from_u32(i32::cast_unsigned(…)).unwrap()`"), + ), + // uNN → iNN + (ty::Uint(_), ty::Int(_)) => { + (Some(vec![(callee_span, format!("{src}::cast_signed"))]), None) + } + // iNN → uNN + (ty::Int(_), ty::Uint(_)) => { + (Some(vec![(callee_span, format!("{src}::cast_unsigned"))]), None) + } + // fNN → usize, isize + (ty::Float(_), ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize)) => ( + Some(vec![ + (callee_span, format!("{src}::to_bits")), + (expr.span.shrink_to_hi(), format!(" as {dst}")), + ]), + None, + ), + // fNN (→ uNN) → iNN + (ty::Float(_), ty::Int(..)) => ( + Some(vec![ + (callee_span, format!("{src}::to_bits")), + (expr.span.shrink_to_hi(), ".cast_signed()".to_string()), + ]), + None, + ), + // fNN → uNN + (ty::Float(_), ty::Uint(..)) => { + (Some(vec![(callee_span, format!("{src}::to_bits"))]), None) + } + // xsize → fNN + (ty::Uint(ty::UintTy::Usize) | ty::Int(ty::IntTy::Isize), ty::Float(_)) => ( + Some(vec![ + (callee_span, format!("{dst}::from_bits")), + (arg.span.shrink_to_hi(), " as _".to_string()), + ]), + None, + ), + // iNN (→ uNN) → fNN + (ty::Int(_), ty::Float(_)) => ( + Some(vec![ + (callee_span, format!("{dst}::from_bits({src}::cast_unsigned")), + (expr.span.shrink_to_hi(), ")".to_string()), + ]), + None, + ), + // uNN → fNN + (ty::Uint(_), ty::Float(_)) => { + (Some(vec![(callee_span, format!("{dst}::from_bits"))]), None) + } + // bool → x8 in const context since `From::from` is not const yet + // FIXME: Consider arg expr's precedence to avoid parentheses. + // FIXME(const_traits): Remove this when `From::from` is constified. + (ty::Bool, ty::Int(..) | ty::Uint(..)) if const_context.is_some() => ( + Some(vec![ + (callee_span, "".to_string()), + (expr.span.shrink_to_hi(), format!(" as {dst}")), + ]), + None, + ), + // bool → x8 using `x8::from` + (ty::Bool, ty::Int(..) | ty::Uint(..)) => { + (Some(vec![(callee_span, format!("{dst}::from"))]), None) + } + _ => return, + }; + + cx.tcx.node_span_lint(UNNECESSARY_TRANSMUTES, expr.hir_id, expr.span, |diag| { + diag.primary_message("unnecessary transmute"); + if let Some(sugg) = sugg { + diag.multipart_suggestion("replace this with", sugg, Applicability::MachineApplicable); + } + if let Some(help) = help { + diag.help(help); + } + }); +} + +#[derive(LintDiagnostic)] +#[diag(lint_undefined_transmute)] +#[note] +#[note(lint_note2)] +#[help] +pub(crate) struct UndefinedTransmuteLint; diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index fcadbfc3c4a..fec23354c91 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -14,7 +14,7 @@ use rustc_middle::ty::{ }; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, Symbol, source_map, sym}; +use rustc_span::{Span, Symbol, sym}; use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; @@ -22,7 +22,8 @@ mod improper_ctypes; use crate::lints::{ AmbiguousWidePointerComparisons, AmbiguousWidePointerComparisonsAddrMetadataSuggestion, - AmbiguousWidePointerComparisonsAddrSuggestion, AtomicOrderingFence, AtomicOrderingLoad, + AmbiguousWidePointerComparisonsAddrSuggestion, AmbiguousWidePointerComparisonsCastSuggestion, + AmbiguousWidePointerComparisonsExpectSuggestion, AtomicOrderingFence, AtomicOrderingLoad, AtomicOrderingStore, ImproperCTypes, InvalidAtomicOrderingDiag, InvalidNanComparisons, InvalidNanComparisonsSuggestion, UnpredictableFunctionPointerComparisons, UnpredictableFunctionPointerComparisonsSuggestion, UnusedComparisons, UsesPowerAlignment, @@ -223,7 +224,7 @@ impl TypeLimits { fn lint_nan<'tcx>( cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>, - binop: hir::BinOp, + binop: hir::BinOpKind, l: &'tcx hir::Expr<'tcx>, r: &'tcx hir::Expr<'tcx>, ) { @@ -262,19 +263,19 @@ fn lint_nan<'tcx>( InvalidNanComparisons::EqNe { suggestion } } - let lint = match binop.node { + let lint = match binop { hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, l) => { eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { nan_plus_binop: l_span.until(r_span), float: r_span.shrink_to_hi(), - neg: (binop.node == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()), + neg: (binop == hir::BinOpKind::Ne).then(|| r_span.shrink_to_lo()), }) } hir::BinOpKind::Eq | hir::BinOpKind::Ne if is_nan(cx, r) => { eq_ne(e, l, r, |l_span, r_span| InvalidNanComparisonsSuggestion::Spanful { nan_plus_binop: l_span.shrink_to_hi().to(r_span), float: l_span.shrink_to_hi(), - neg: (binop.node == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()), + neg: (binop == hir::BinOpKind::Ne).then(|| l_span.shrink_to_lo()), }) } hir::BinOpKind::Lt | hir::BinOpKind::Le | hir::BinOpKind::Gt | hir::BinOpKind::Ge @@ -360,6 +361,7 @@ fn lint_wide_pointer<'tcx>( let ne = if cmpop == ComparisonOp::BinOp(hir::BinOpKind::Ne) { "!" } else { "" }; let is_eq_ne = matches!(cmpop, ComparisonOp::BinOp(hir::BinOpKind::Eq | hir::BinOpKind::Ne)); let is_dyn_comparison = l_inner_ty_is_dyn && r_inner_ty_is_dyn; + let via_method_call = matches!(&e.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)); let left = e.span.shrink_to_lo().until(l_span.shrink_to_lo()); let middle = l_span.shrink_to_hi().until(r_span.shrink_to_lo()); @@ -374,21 +376,21 @@ fn lint_wide_pointer<'tcx>( cx.emit_span_lint( AMBIGUOUS_WIDE_POINTER_COMPARISONS, e.span, - AmbiguousWidePointerComparisons::Spanful { - addr_metadata_suggestion: (is_eq_ne && !is_dyn_comparison).then(|| { - AmbiguousWidePointerComparisonsAddrMetadataSuggestion { - ne, - deref_left, - deref_right, - l_modifiers, - r_modifiers, - left, - middle, - right, - } - }), - addr_suggestion: if is_eq_ne { - AmbiguousWidePointerComparisonsAddrSuggestion::AddrEq { + if is_eq_ne { + AmbiguousWidePointerComparisons::SpanfulEq { + addr_metadata_suggestion: (!is_dyn_comparison).then(|| { + AmbiguousWidePointerComparisonsAddrMetadataSuggestion { + ne, + deref_left, + deref_right, + l_modifiers, + r_modifiers, + left, + middle, + right, + } + }), + addr_suggestion: AmbiguousWidePointerComparisonsAddrSuggestion { ne, deref_left, deref_right, @@ -397,9 +399,11 @@ fn lint_wide_pointer<'tcx>( left, middle, right, - } - } else { - AmbiguousWidePointerComparisonsAddrSuggestion::Cast { + }, + } + } else { + AmbiguousWidePointerComparisons::SpanfulCmp { + cast_suggestion: AmbiguousWidePointerComparisonsCastSuggestion { deref_left, deref_right, l_modifiers, @@ -410,8 +414,14 @@ fn lint_wide_pointer<'tcx>( left_after: l_span.shrink_to_hi(), right_before: (r_ty_refs != 0).then_some(r_span.shrink_to_lo()), right_after: r_span.shrink_to_hi(), - } - }, + }, + expect_suggestion: AmbiguousWidePointerComparisonsExpectSuggestion { + paren_left: if via_method_call { "" } else { "(" }, + paren_right: if via_method_call { "" } else { ")" }, + before: e.span.shrink_to_lo(), + after: e.span.shrink_to_hi(), + }, + } }, ); } @@ -560,11 +570,11 @@ 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) { + if is_comparison(binop.node) { + if !check_limits(cx, binop.node, l, r) { cx.emit_span_lint(UNUSED_COMPARISONS, e.span, UnusedComparisons); } else { - lint_nan(cx, e, binop, l, r); + lint_nan(cx, e, binop.node, l, r); let cmpop = ComparisonOp::BinOp(binop.node); lint_wide_pointer(cx, e, cmpop, l, r); lint_fn_pointer(cx, e, cmpop, l, r); @@ -591,8 +601,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { _ => {} }; - fn is_valid<T: PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool { - match binop.node { + fn is_valid<T: PartialOrd>(binop: hir::BinOpKind, v: T, min: T, max: T) -> bool { + match binop { hir::BinOpKind::Lt => v > min && v <= max, hir::BinOpKind::Le => v >= min && v < max, hir::BinOpKind::Gt => v >= min && v < max, @@ -602,22 +612,19 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { } } - fn rev_binop(binop: hir::BinOp) -> hir::BinOp { - source_map::respan( - binop.span, - match binop.node { - hir::BinOpKind::Lt => hir::BinOpKind::Gt, - hir::BinOpKind::Le => hir::BinOpKind::Ge, - hir::BinOpKind::Gt => hir::BinOpKind::Lt, - hir::BinOpKind::Ge => hir::BinOpKind::Le, - _ => return binop, - }, - ) + fn rev_binop(binop: hir::BinOpKind) -> hir::BinOpKind { + match binop { + hir::BinOpKind::Lt => hir::BinOpKind::Gt, + hir::BinOpKind::Le => hir::BinOpKind::Ge, + hir::BinOpKind::Gt => hir::BinOpKind::Lt, + hir::BinOpKind::Ge => hir::BinOpKind::Le, + _ => binop, + } } fn check_limits( cx: &LateContext<'_>, - binop: hir::BinOp, + binop: hir::BinOpKind, l: &hir::Expr<'_>, r: &hir::Expr<'_>, ) -> bool { @@ -659,9 +666,9 @@ impl<'tcx> LateLintPass<'tcx> for TypeLimits { } } - fn is_comparison(binop: hir::BinOp) -> bool { + fn is_comparison(binop: hir::BinOpKind) -> bool { matches!( - binop.node, + binop, hir::BinOpKind::Eq | hir::BinOpKind::Lt | hir::BinOpKind::Le @@ -756,10 +763,10 @@ declare_lint! { /// *subsequent* fields of the associated structs to use an alignment value /// where the floating-point type is aligned on a 4-byte boundary. /// - /// The power alignment rule for structs needed for C compatibility is - /// unimplementable within `repr(C)` in the compiler without building in - /// handling of references to packed fields and infectious nested layouts, - /// so a warning is produced in these situations. + /// Effectively, subsequent floating-point fields act as-if they are `repr(packed(4))`. This + /// would be unsound to do in a `repr(C)` type without all the restrictions that come with + /// `repr(packed)`. Rust instead chooses a layout that maintains soundness of Rust code, at the + /// expense of incompatibility with C code. /// /// ### Example /// @@ -791,8 +798,10 @@ declare_lint! { /// - offset_of!(Floats, a) == 0 /// - offset_of!(Floats, b) == 8 /// - offset_of!(Floats, c) == 12 - /// However, rust currently aligns `c` at offset_of!(Floats, c) == 16. - /// Thus, a warning should be produced for the above struct in this case. + /// + /// However, Rust currently aligns `c` at `offset_of!(Floats, c) == 16`. + /// Using offset 12 would be unsound since `f64` generally must be 8-aligned on this target. + /// Thus, a warning is produced for the above struct. USES_POWER_ALIGNMENT, Warn, "Structs do not follow the power alignment rule under repr(C)" @@ -867,8 +876,8 @@ fn ty_is_known_nonnull<'tcx>( return true; } - // `UnsafeCell` has its niche hidden. - if def.is_unsafe_cell() { + // `UnsafeCell` and `UnsafePinned` have their niche hidden. + if def.is_unsafe_cell() || def.is_unsafe_pinned() { return false; } @@ -879,25 +888,36 @@ fn ty_is_known_nonnull<'tcx>( } ty::Pat(base, pat) => { ty_is_known_nonnull(tcx, typing_env, *base, mode) - || Option::unwrap_or_default( - try { - match **pat { - ty::PatternKind::Range { start, end } => { - let start = start.try_to_value()?.try_to_bits(tcx, typing_env)?; - let end = end.try_to_value()?.try_to_bits(tcx, typing_env)?; - - // This also works for negative numbers, as we just need - // to ensure we aren't wrapping over zero. - start > 0 && end >= start - } - } - }, - ) + || pat_ty_is_known_nonnull(tcx, typing_env, *pat) } _ => false, } } +fn pat_ty_is_known_nonnull<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + pat: ty::Pattern<'tcx>, +) -> bool { + Option::unwrap_or_default( + try { + match *pat { + ty::PatternKind::Range { start, end } => { + let start = start.try_to_value()?.try_to_bits(tcx, typing_env)?; + let end = end.try_to_value()?.try_to_bits(tcx, typing_env)?; + + // This also works for negative numbers, as we just need + // to ensure we aren't wrapping over zero. + start > 0 && end >= start + } + ty::PatternKind::Or(patterns) => { + patterns.iter().all(|pat| pat_ty_is_known_nonnull(tcx, typing_env, pat)) + } + } + }, + ) +} + /// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. /// If the type passed in was not scalar, returns None. fn get_nullable_type<'tcx>( @@ -1039,13 +1059,29 @@ pub(crate) fn repr_nullable_ptr<'tcx>( } None } - ty::Pat(base, pat) => match **pat { - ty::PatternKind::Range { .. } => get_nullable_type(tcx, typing_env, *base), - }, + ty::Pat(base, pat) => get_nullable_type_from_pat(tcx, typing_env, *base, *pat), _ => None, } } +fn get_nullable_type_from_pat<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + base: Ty<'tcx>, + pat: ty::Pattern<'tcx>, +) -> Option<Ty<'tcx>> { + match *pat { + ty::PatternKind::Range { .. } => get_nullable_type(tcx, typing_env, base), + ty::PatternKind::Or(patterns) => { + let first = get_nullable_type_from_pat(tcx, typing_env, base, patterns[0])?; + for &pat in &patterns[1..] { + assert_eq!(first, get_nullable_type_from_pat(tcx, typing_env, base, pat)?); + } + Some(first) + } + } +} + impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { /// Check if the type is array and emit an unsafe type lint. fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { @@ -1193,9 +1229,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - let is_non_exhaustive = - def.non_enum_variant().is_field_list_non_exhaustive(); - if is_non_exhaustive && !def.did().is_local() { + if def.non_enum_variant().field_list_has_applicable_non_exhaustive() { return FfiUnsafe { ty, reason: if def.is_struct() { @@ -1248,14 +1282,12 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { }; } - use improper_ctypes::{ - check_non_exhaustive_variant, non_local_and_non_exhaustive, - }; + use improper_ctypes::check_non_exhaustive_variant; - let non_local_def = non_local_and_non_exhaustive(def); + let non_exhaustive = def.variant_list_has_applicable_non_exhaustive(); // Check the contained variants. let ret = def.variants().iter().try_for_each(|variant| { - check_non_exhaustive_variant(non_local_def, variant) + check_non_exhaustive_variant(non_exhaustive, variant) .map_break(|reason| FfiUnsafe { ty, reason, help: None })?; match self.check_variant_for_ffi(acc, ty, def, variant, args) { @@ -1282,10 +1314,6 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { // but only the base type is relevant for being representable in FFI. ty::Pat(base, ..) => self.check_type_for_ffi(acc, base), - ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => { - FfiUnsafe { ty, reason: fluent::lint_improper_ctypes_128bit, help: None } - } - // Primitive types with a stable representation. ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, @@ -1378,7 +1406,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { ty::UnsafeBinder(_) => todo!("FIXME(unsafe_binder)"), ty::Param(..) - | ty::Alias(ty::Projection | ty::Inherent | ty::Weak, ..) + | ty::Alias(ty::Projection | ty::Inherent | ty::Free, ..) | ty::Infer(..) | ty::Bound(..) | ty::Error(_) @@ -1407,7 +1435,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { CItemKind::Definition => "fn", }; let span_note = if let ty::Adt(def, _) = ty.kind() - && let Some(sp) = self.cx.tcx.hir().span_if_local(def.did()) + && let Some(sp) = self.cx.tcx.hir_span_if_local(def.did()) { Some(sp) } else { @@ -1422,7 +1450,7 @@ 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 { + impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for ProhibitOpaqueTypes { type Result = ControlFlow<Ty<'tcx>>; fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { @@ -1564,7 +1592,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } } - impl<'tcx> ty::visit::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> { + impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for FnPtrFinder<'tcx> { type Result = (); fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { @@ -1589,7 +1617,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { fn check_foreign_item(&mut self, cx: &LateContext<'tcx>, it: &hir::ForeignItem<'tcx>) { let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; - let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); + let abi = cx.tcx.hir_get_foreign_abi(it.hir_id()); match it.kind { hir::ForeignItemKind::Fn(sig, _, _) => { @@ -1625,19 +1653,20 @@ impl ImproperCTypesDefinitions { cx: &LateContext<'tcx>, ty: Ty<'tcx>, ) -> bool { + assert!(cx.tcx.sess.target.os == "aix"); // Structs (under repr(C)) follow the power alignment rule if: // - the first field of the struct is a floating-point type that // is greater than 4-bytes, or // - the first field of the struct is an aggregate whose // recursively first field is a floating-point type greater than // 4 bytes. - if cx.tcx.sess.target.os != "aix" { - return false; - } if ty.is_floating_point() && ty.primitive_size(cx.tcx).bytes() > 4 { return true; } else if let Adt(adt_def, _) = ty.kind() && adt_def.is_struct() + && adt_def.repr().c() + && !adt_def.repr().packed() + && adt_def.repr().align.is_none() { let struct_variant = adt_def.variant(VariantIdx::ZERO); // Within a nested struct, all fields are examined to correctly @@ -1659,27 +1688,23 @@ impl ImproperCTypesDefinitions { item: &'tcx hir::Item<'tcx>, ) { let adt_def = cx.tcx.adt_def(item.owner_id.to_def_id()); + // repr(C) structs also with packed or aligned representation + // should be ignored. if adt_def.repr().c() && !adt_def.repr().packed() + && adt_def.repr().align.is_none() && cx.tcx.sess.target.os == "aix" && !adt_def.all_fields().next().is_none() { - let struct_variant_data = item.expect_struct().0; - for (index, ..) in struct_variant_data.fields().iter().enumerate() { + let struct_variant_data = item.expect_struct().2; + for field_def in struct_variant_data.fields().iter().skip(1) { // Struct fields (after the first field) are checked for the // power alignment rule, as fields after the first are likely // to be the fields that are misaligned. - if index != 0 { - let first_field_def = struct_variant_data.fields()[index]; - let def_id = first_field_def.def_id; - let ty = cx.tcx.type_of(def_id).instantiate_identity(); - if self.check_arg_for_power_alignment(cx, ty) { - cx.emit_span_lint( - USES_POWER_ALIGNMENT, - first_field_def.span, - UsesPowerAlignment, - ); - } + let def_id = field_def.def_id; + let ty = cx.tcx.type_of(def_id).instantiate_identity(); + if self.check_arg_for_power_alignment(cx, ty) { + cx.emit_span_lint(USES_POWER_ALIGNMENT, field_def.span, UsesPowerAlignment); } } } @@ -1696,9 +1721,9 @@ impl ImproperCTypesDefinitions { impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { match item.kind { - hir::ItemKind::Static(ty, ..) - | hir::ItemKind::Const(ty, ..) - | hir::ItemKind::TyAlias(ty, ..) => { + hir::ItemKind::Static(_, _, ty, _) + | hir::ItemKind::Const(_, _, ty, _) + | hir::ItemKind::TyAlias(_, _, ty) => { self.check_ty_maybe_containing_foreign_fnptr( cx, ty, @@ -1765,7 +1790,7 @@ declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { - if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { + if let hir::ItemKind::Enum(_, _, ref enum_definition) = it.kind { let t = cx.tcx.type_of(it.owner_id).instantiate_identity(); let ty = cx.tcx.erase_regions(t); let Ok(layout) = cx.layout_of(ty) else { return }; diff --git a/compiler/rustc_lint/src/types/improper_ctypes.rs b/compiler/rustc_lint/src/types/improper_ctypes.rs index 1030101c545..13afa540afc 100644 --- a/compiler/rustc_lint/src/types/improper_ctypes.rs +++ b/compiler/rustc_lint/src/types/improper_ctypes.rs @@ -15,13 +15,13 @@ use crate::fluent_generated as fluent; /// so we don't need the lint to account for it. /// e.g. going from enum Foo { A, B, C } to enum Foo { A, B, C, D(u32) }. pub(crate) fn check_non_exhaustive_variant( - non_local_def: bool, + non_exhaustive_variant_list: bool, variant: &ty::VariantDef, ) -> ControlFlow<DiagMessage, ()> { // non_exhaustive suggests it is possible that someone might break ABI // see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 // so warn on complex enums being used outside their crate - if non_local_def { + if non_exhaustive_variant_list { // which is why we only warn about really_tagged_union reprs from https://rust.tf/rfc2195 // with an enum like `#[repr(u8)] enum Enum { A(DataA), B(DataB), }` // but exempt enums with unit ctors like C's (e.g. from rust-bindgen) @@ -30,8 +30,7 @@ pub(crate) fn check_non_exhaustive_variant( } } - let non_exhaustive_variant_fields = variant.is_field_list_non_exhaustive(); - if non_exhaustive_variant_fields && !variant.def_id.is_local() { + if variant.field_list_has_applicable_non_exhaustive() { return ControlFlow::Break(fluent::lint_improper_ctypes_non_exhaustive_variant); } @@ -42,10 +41,3 @@ fn variant_has_complex_ctor(variant: &ty::VariantDef) -> bool { // CtorKind::Const means a "unit" ctor !matches!(variant.ctor_kind(), Some(CtorKind::Const)) } - -// non_exhaustive suggests it is possible that someone might break ABI -// see: https://github.com/rust-lang/rust/issues/44109#issuecomment-537583344 -// so warn on complex enums being used outside their crate -pub(crate) fn non_local_and_non_exhaustive(def: ty::AdtDef<'_>) -> bool { - def.is_variant_list_non_exhaustive() && !def.did().is_local() -} diff --git a/compiler/rustc_lint/src/types/literal.rs b/compiler/rustc_lint/src/types/literal.rs index 7cb00262b6f..d44f45177bd 100644 --- a/compiler/rustc_lint/src/types/literal.rs +++ b/compiler/rustc_lint/src/types/literal.rs @@ -5,7 +5,7 @@ use rustc_middle::ty::Ty; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::{bug, ty}; use rustc_span::Span; -use {rustc_ast as ast, rustc_attr_parsing as attr, rustc_hir as hir}; +use {rustc_ast as ast, rustc_attr_data_structures as attrs, rustc_hir as hir}; use crate::LateContext; use crate::context::LintContext; @@ -131,18 +131,18 @@ fn report_bin_hex_error( cx: &LateContext<'_>, hir_id: HirId, span: Span, - ty: attr::IntType, + ty: attrs::IntType, size: Size, repr_str: String, val: u128, negative: bool, ) { let (t, actually) = match ty { - attr::IntType::SignedInt(t) => { + attrs::IntType::SignedInt(t) => { let actually = if negative { -(size.sign_extend(val)) } else { size.sign_extend(val) }; (t.name_str(), actually.to_string()) } - attr::IntType::UnsignedInt(t) => { + attrs::IntType::UnsignedInt(t) => { let actually = size.truncate(val); (t.name_str(), actually.to_string()) } @@ -264,7 +264,7 @@ fn lint_int_literal<'tcx>( cx, hir_id, span, - attr::IntType::SignedInt(ty::ast_int_ty(t)), + attrs::IntType::SignedInt(ty::ast_int_ty(t)), Integer::from_int_ty(cx, t).size(), repr_str, v, @@ -336,7 +336,7 @@ fn lint_uint_literal<'tcx>( cx, hir_id, span, - attr::IntType::UnsignedInt(ty::ast_uint_ty(t)), + attrs::IntType::UnsignedInt(ty::ast_uint_ty(t)), Integer::from_uint_ty(cx, t).size(), repr_str, lit_val, diff --git a/compiler/rustc_lint/src/unqualified_local_imports.rs b/compiler/rustc_lint/src/unqualified_local_imports.rs index 50c5119285f..0076cae3cff 100644 --- a/compiler/rustc_lint/src/unqualified_local_imports.rs +++ b/compiler/rustc_lint/src/unqualified_local_imports.rs @@ -1,4 +1,3 @@ -use rustc_hir::def::{DefKind, Res}; use rustc_hir::{self as hir}; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::kw; @@ -47,17 +46,15 @@ declare_lint_pass!(UnqualifiedLocalImports => [UNQUALIFIED_LOCAL_IMPORTS]); impl<'tcx> LateLintPass<'tcx> for UnqualifiedLocalImports { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { let hir::ItemKind::Use(path, _kind) = item.kind else { return }; - // `path` has three resolutions for the type, module, value namespaces. - // Check if any of them qualifies: local crate, and not a macro. - // (Macros can't be imported any other way so we don't complain about them.) - let is_local_import = |res: &Res| { - matches!( - res, - hir::def::Res::Def(def_kind, def_id) - if def_id.is_local() && !matches!(def_kind, DefKind::Macro(_)), - ) - }; - if !path.res.iter().any(is_local_import) { + // Check the type and value namespace resolutions for a local crate. + let is_local_import = matches!( + path.res.type_ns, + Some(hir::def::Res::Def(_, def_id)) if def_id.is_local() + ) || matches!( + path.res.value_ns, + Some(hir::def::Res::Def(_, def_id)) if def_id.is_local() + ); + if !is_local_import { return; } // So this does refer to something local. Let's check whether it starts with `self`, diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 7b43aac90c7..1620f425794 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1,8 +1,7 @@ use std::iter; -use rustc_ast as ast; use rustc_ast::util::{classify, parser}; -use rustc_ast::{ExprKind, StmtKind}; +use rustc_ast::{self as ast, ExprKind, HasAttrs as _, StmtKind}; use rustc_errors::{MultiSpan, pluralize}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; @@ -780,26 +779,30 @@ trait UnusedDelimLint { right_pos: Option<BytePos>, is_kw: bool, ) { - let spans = match value.kind { - ast::ExprKind::Block(ref block, None) if let [stmt] = block.stmts.as_slice() => stmt - .span - .find_ancestor_inside(value.span) - .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi()))), + let span_with_attrs = match value.kind { + ast::ExprKind::Block(ref block, None) if let [stmt] = block.stmts.as_slice() => { + // For the statements with attributes, like `{ #[allow()] println!("Hello!") }`, + // the span should contains the attributes, or the suggestion will remove them. + if let Some(attr_lo) = stmt.attrs().iter().map(|attr| attr.span.lo()).min() { + stmt.span.with_lo(attr_lo) + } else { + stmt.span + } + } ast::ExprKind::Paren(ref expr) => { // For the expr with attributes, like `let _ = (#[inline] || println!("Hello!"));`, // the span should contains the attributes, or the suggestion will remove them. - let expr_span_with_attrs = - if let Some(attr_lo) = expr.attrs.iter().map(|attr| attr.span.lo()).min() { - expr.span.with_lo(attr_lo) - } else { - expr.span - }; - expr_span_with_attrs.find_ancestor_inside(value.span).map(|expr_span| { - (value.span.with_hi(expr_span.lo()), value.span.with_lo(expr_span.hi())) - }) + if let Some(attr_lo) = expr.attrs.iter().map(|attr| attr.span.lo()).min() { + expr.span.with_lo(attr_lo) + } else { + expr.span + } } _ => return, }; + let spans = span_with_attrs + .find_ancestor_inside(value.span) + .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi()))); let keep_space = ( left_pos.is_some_and(|s| s >= value.span.lo()), right_pos.is_some_and(|s| s <= value.span.hi()), @@ -942,6 +945,22 @@ trait UnusedDelimLint { match s.kind { StmtKind::Let(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => { if let Some((init, els)) = local.kind.init_else_opt() { + if els.is_some() + && let ExprKind::Paren(paren) = &init.kind + && !init.span.eq_ctxt(paren.span) + { + // This branch prevents cases where parentheses wrap an expression + // resulting from macro expansion, such as: + // ``` + // macro_rules! x { + // () => { None::<i32> }; + // } + // let Some(_) = (x!{}) else { return }; + // // -> let Some(_) = (None::<i32>) else { return }; + // // ~ ~ No Lint + // ``` + return; + } let ctx = match els { None => UnusedDelimsCtx::AssignedValue, Some(_) => UnusedDelimsCtx::AssignedValueLetElse, @@ -1201,7 +1220,8 @@ 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 | Expr(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) | Err(_) => {}, + | Missing | Wild | Never | Rest | Expr(..) | 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); diff --git a/compiler/rustc_lint/src/utils.rs b/compiler/rustc_lint/src/utils.rs new file mode 100644 index 00000000000..a7295d9c532 --- /dev/null +++ b/compiler/rustc_lint/src/utils.rs @@ -0,0 +1,55 @@ +use rustc_hir::{Expr, ExprKind}; +use rustc_span::sym; + +use crate::LateContext; + +/// Given an expression, peel all of casts (`<expr> as ...`, `<expr>.cast{,_mut,_const}()`, +/// `ptr::from_ref(<expr>)`, ...) and init expressions. +/// +/// Returns the innermost expression and a boolean representing if one of the casts was +/// `UnsafeCell::raw_get(<expr>)` +pub(crate) fn peel_casts<'tcx>( + cx: &LateContext<'tcx>, + mut e: &'tcx Expr<'tcx>, +) -> (&'tcx Expr<'tcx>, bool) { + let mut gone_trough_unsafe_cell_raw_get = false; + + loop { + e = e.peel_blocks(); + // <expr> as ... + e = if let ExprKind::Cast(expr, _) = e.kind { + expr + // <expr>.cast(), <expr>.cast_mut() or <expr>.cast_const() + } else if let ExprKind::MethodCall(_, expr, [], _) = e.kind + && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_cast | sym::const_ptr_cast | sym::ptr_cast_mut | sym::ptr_cast_const) + ) + { + expr + // ptr::from_ref(<expr>), UnsafeCell::raw_get(<expr>) or mem::transmute<_, _>(<expr>) + } else if let ExprKind::Call(path, [arg]) = e.kind + && let ExprKind::Path(ref qpath) = path.kind + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(def_id), + Some(sym::ptr_from_ref | sym::unsafe_cell_raw_get | sym::transmute) + ) + { + if cx.tcx.is_diagnostic_item(sym::unsafe_cell_raw_get, def_id) { + gone_trough_unsafe_cell_raw_get = true; + } + arg + } else { + let init = cx.expr_or_init(e); + if init.hir_id != e.hir_id { + init + } else { + break; + } + }; + } + + (e, gone_trough_unsafe_cell_raw_get) +} |
