diff options
Diffstat (limited to 'compiler/rustc_lint/src')
| -rw-r--r-- | compiler/rustc_lint/src/builtin.rs | 12 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/drop_forget_useless.rs | 5 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/errors.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/expect.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/impl_trait_overcaptures.rs | 14 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/internal.rs | 152 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/levels.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lints.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/reference_casting.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/tail_expr_drop_order.rs | 306 |
11 files changed, 416 insertions, 95 deletions
diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 5f6e7fb314d..d8482567bbe 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1330,7 +1330,7 @@ impl UnreachablePub { BuiltinUnreachablePub { what, suggestion: (vis_span, applicability), - help: exportable.then_some(()), + help: exportable, }, ); } @@ -1925,8 +1925,8 @@ impl ExplicitOutlivesRequirements { fn lifetimes_outliving_lifetime<'tcx>( tcx: TyCtxt<'tcx>, inferred_outlives: impl Iterator<Item = &'tcx (ty::Clause<'tcx>, Span)>, - item: DefId, - lifetime: DefId, + item: LocalDefId, + lifetime: LocalDefId, ) -> Vec<ty::Region<'tcx>> { let item_generics = tcx.generics_of(item); @@ -1934,7 +1934,7 @@ impl ExplicitOutlivesRequirements { .filter_map(|(clause, _)| match clause.kind().skip_binder() { ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(a, b)) => match *a { ty::ReEarlyParam(ebr) - if item_generics.region_param(ebr, tcx).def_id == lifetime => + if item_generics.region_param(ebr, tcx).def_id == lifetime.to_def_id() => { Some(b) } @@ -1982,7 +1982,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 })), + .any(|r| matches!(**r, ty::ReEarlyParam(ebr) if { item_generics.region_param(ebr, tcx).def_id == def_id.to_def_id() })), _ => false, }; @@ -2097,7 +2097,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitOutlivesRequirements { inferred_outlives .iter() .filter(|(_, span)| !predicate.span.contains(*span)), - item.owner_id.to_def_id(), + item.owner_id.def_id, region_def_id, ), &predicate.bounds, diff --git a/compiler/rustc_lint/src/drop_forget_useless.rs b/compiler/rustc_lint/src/drop_forget_useless.rs index 2060858cc8a..a9de258e005 100644 --- a/compiler/rustc_lint/src/drop_forget_useless.rs +++ b/compiler/rustc_lint/src/drop_forget_useless.rs @@ -151,10 +151,11 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetUseless { && let Node::Stmt(stmt) = node && let StmtKind::Semi(e) = stmt.kind && e.hir_id == expr.hir_id + && let Some(arg_span) = arg.span.find_ancestor_inside(expr.span) { UseLetUnderscoreIgnoreSuggestion::Suggestion { - start_span: expr.span.shrink_to_lo().until(arg.span), - end_span: arg.span.shrink_to_hi().until(expr.span.shrink_to_hi()), + start_span: expr.span.shrink_to_lo().until(arg_span), + end_span: arg_span.shrink_to_hi().until(expr.span.shrink_to_hi()), } } else { UseLetUnderscoreIgnoreSuggestion::Note diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index 23e6b73ee37..85ee18aba8f 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -77,7 +77,7 @@ pub struct UnknownToolInScopedLint { pub tool_name: Symbol, pub lint_name: String, #[help] - pub is_nightly_build: Option<()>, + pub is_nightly_build: bool, } #[derive(Diagnostic)] diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 35af694213d..42b33f9882d 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -24,7 +24,7 @@ fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option<Symbol>) { && tool_filter.map_or(true, |filter| expectation.lint_tool == Some(filter)) { let rationale = expectation.reason.map(|rationale| ExpectationNote { rationale }); - let note = expectation.is_unfulfilled_lint_expectations.then_some(()); + let note = expectation.is_unfulfilled_lint_expectations; tcx.emit_node_span_lint( UNFULFILLED_LINT_EXPECTATIONS, *hir_id, diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs index e914169f4c3..2e0da69e920 100644 --- a/compiler/rustc_lint/src/impl_trait_overcaptures.rs +++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs @@ -27,8 +27,6 @@ declare_lint! { /// ### Example /// /// ```rust,compile_fail - /// # #![feature(precise_capturing)] - /// # #![allow(incomplete_features)] /// # #![deny(impl_trait_overcaptures)] /// # use std::fmt::Display; /// let mut x = vec![]; @@ -56,7 +54,6 @@ declare_lint! { pub IMPL_TRAIT_OVERCAPTURES, Allow, "`impl Trait` will capture more lifetimes than possibly intended in edition 2024", - @feature_gate = precise_capturing; //@future_incompatible = FutureIncompatibleInfo { // reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), // reference: "<FIXME>", @@ -75,8 +72,7 @@ declare_lint! { /// ### Example /// /// ```rust,compile_fail - /// # #![feature(precise_capturing, lifetime_capture_rules_2024)] - /// # #![allow(incomplete_features)] + /// # #![feature(lifetime_capture_rules_2024)] /// # #![deny(impl_trait_redundant_captures)] /// fn test<'a>(x: &'a i32) -> impl Sized + use<'a> { x } /// ``` @@ -90,7 +86,6 @@ declare_lint! { pub IMPL_TRAIT_REDUNDANT_CAPTURES, Warn, "redundant precise-capturing `use<...>` syntax on an `impl Trait`", - @feature_gate = precise_capturing; } declare_lint_pass!( @@ -305,16 +300,17 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for VisitOpaqueTypes<'tcx> { Some( ResolvedArg::EarlyBound(def_id) | ResolvedArg::LateBound(_, _, def_id), ) => { - if self.tcx.def_kind(self.tcx.parent(def_id)) == DefKind::OpaqueTy { + if self.tcx.def_kind(self.tcx.local_parent(def_id)) == DefKind::OpaqueTy + { let def_id = self .tcx - .map_opaque_lifetime_to_parent_lifetime(def_id.expect_local()) + .map_opaque_lifetime_to_parent_lifetime(def_id) .opt_param_def_id(self.tcx, self.parent_def_id.to_def_id()) .expect("variable should have been duplicated from parent"); explicitly_captured.insert(def_id); } else { - explicitly_captured.insert(def_id); + explicitly_captured.insert(def_id.to_def_id()); } } _ => { diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 044c9413f0b..65571815019 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -8,7 +8,7 @@ use rustc_hir::{ BinOp, BinOpKind, Expr, ExprKind, GenericArg, HirId, Impl, Item, ItemKind, Node, Pat, PatKind, Path, PathSegment, QPath, Ty, TyKind, }; -use rustc_middle::ty::{self, Ty as MiddleTy}; +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::symbol::{kw, sym, Symbol}; @@ -415,14 +415,17 @@ declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE 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| { + 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, call_tys) = match expr.kind { + let (span, def_id, fn_gen_args, arg_tys_and_spans) = match expr.kind { ExprKind::Call(callee, args) => { match cx.typeck_results().node_type(callee.hir_id).kind() { &ty::FnDef(def_id, fn_gen_args) => { - let call_tys: Vec<_> = - args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect(); - (callee.span, def_id, fn_gen_args, call_tys) + (callee.span, def_id, fn_gen_args, collect_args_tys_and_spans(args, false)) } _ => return, // occurs for fns passed as args } @@ -432,38 +435,40 @@ impl LateLintPass<'_> for Diagnostics { else { return; }; - let mut call_tys: Vec<_> = - args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect(); - call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self` - (span, def_id, fn_gen_args, call_tys) + let mut args = collect_args_tys_and_spans(args, true); + args.insert(0, (cx.tcx.types.self_param, _recv.span)); // dummy inserted for `self` + (span, def_id, fn_gen_args, args) } _ => return, }; - // Is the callee marked with `#[rustc_lint_diagnostics]`? - let has_attr = ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, fn_gen_args) - .ok() - .flatten() - .is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics)); - - // Closure: is the type `{D,Subd}iagMessage`? - let is_diag_message = |ty: MiddleTy<'_>| { - if let Some(adt_def) = ty.ty_adt_def() - && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) - && matches!(name, sym::DiagMessage | sym::SubdiagMessage) - { - true - } else { - false - } - }; + Self::diagnostic_outside_of_impl(cx, span, expr.hir_id, def_id, fn_gen_args); + Self::untranslatable_diagnostic(cx, def_id, &arg_tys_and_spans); + } +} - // Does the callee have one or more `impl Into<{D,Subd}iagMessage>` parameters? - let mut impl_into_diagnostic_message_params = vec![]; +impl Diagnostics { + // Is the type `{D,Subd}iagMessage`? + fn is_diag_message<'cx>(cx: &LateContext<'cx>, ty: MiddleTy<'cx>) -> bool { + if let Some(adt_def) = ty.ty_adt_def() + && let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did()) + && matches!(name, sym::DiagMessage | sym::SubdiagMessage) + { + true + } else { + false + } + } + + fn untranslatable_diagnostic<'cx>( + cx: &LateContext<'cx>, + def_id: DefId, + arg_tys_and_spans: &[(MiddleTy<'cx>, Span)], + ) { let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates; for (i, ¶m_ty) in fn_sig.inputs().iter().enumerate() { - if let ty::Param(p) = param_ty.kind() { + if let ty::Param(sig_param) = param_ty.kind() { // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`. for pred in predicates.iter() { if let Some(trait_pred) = pred.as_trait_clause() @@ -471,27 +476,53 @@ impl LateLintPass<'_> for Diagnostics { && trait_ref.self_ty() == param_ty // correct predicate for the param? && cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id) && let ty1 = trait_ref.args.type_at(1) - && is_diag_message(ty1) + && Self::is_diag_message(cx, ty1) { - impl_into_diagnostic_message_params.push((i, p.name)); + // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg + // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an + // `UNTRANSLATABLE_DIAGNOSTIC` lint. + let (arg_ty, arg_span) = arg_tys_and_spans[i]; + + // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`? + let is_translatable = Self::is_diag_message(cx, arg_ty) + || matches!(arg_ty.kind(), ty::Param(arg_param) if arg_param.name == sig_param.name); + if !is_translatable { + cx.emit_span_lint( + UNTRANSLATABLE_DIAGNOSTIC, + arg_span, + UntranslatableDiag, + ); + } } } } } + } - // Is the callee interesting? - if !has_attr && impl_into_diagnostic_message_params.is_empty() { + fn diagnostic_outside_of_impl<'cx>( + cx: &LateContext<'cx>, + span: Span, + current_id: HirId, + def_id: DefId, + fn_gen_args: GenericArgsRef<'cx>, + ) { + // Is the callee marked with `#[rustc_lint_diagnostics]`? + let Some(inst) = + ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, fn_gen_args).ok().flatten() + else { return; - } + }; + let has_attr = cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics); + if !has_attr { + return; + }; - // Is the parent method marked with `#[rustc_lint_diagnostics]`? - let mut parent_has_attr = false; - for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) { + for (hir_id, _parent) in cx.tcx.hir().parent_iter(current_id) { if let Some(owner_did) = hir_id.as_owner() && cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics) { - parent_has_attr = true; - break; + // The parent method is marked with `#[rustc_lint_diagnostics]` + return; } } @@ -500,37 +531,22 @@ impl LateLintPass<'_> for Diagnostics { // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`. // // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint. - if has_attr && !parent_has_attr { - let mut is_inside_appropriate_impl = false; - for (_hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) { - debug!(?parent); - if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent - && let Impl { of_trait: Some(of_trait), .. } = impl_ - && let Some(def_id) = of_trait.trait_def_id() - && let Some(name) = cx.tcx.get_diagnostic_name(def_id) - && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic) - { - is_inside_appropriate_impl = true; - break; - } - } - debug!(?is_inside_appropriate_impl); - if !is_inside_appropriate_impl { - cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl); + 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_ + && let Some(def_id) = of_trait.trait_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) + && matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic) + { + is_inside_appropriate_impl = true; + break; } } - - // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg - // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an - // `UNTRANSLATABLE_DIAGNOSTIC` lint. - for (param_i, param_i_p_name) in impl_into_diagnostic_message_params { - // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`? - let arg_ty = call_tys[param_i]; - let is_translatable = is_diag_message(arg_ty) - || matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name); - if !is_translatable { - cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag); - } + debug!(?is_inside_appropriate_impl); + if !is_inside_appropriate_impl { + cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl); } } } diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 44117e5d7a5..91d4f95df80 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -936,7 +936,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { span: tool_ident.map(|ident| ident.span), tool_name: tool_name.unwrap(), lint_name: pprust::path_to_string(&meta_item.path), - is_nightly_build: sess.is_nightly_build().then_some(()), + is_nightly_build: sess.is_nightly_build(), }); continue; } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 4f3933d461b..a6e7d288f08 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -78,6 +78,7 @@ mod ptr_nulls; mod redundant_semicolon; mod reference_casting; mod shadowed_into_iter; +mod tail_expr_drop_order; mod traits; mod types; mod unit_bindings; @@ -115,6 +116,7 @@ use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use shadowed_into_iter::ShadowedIntoIter; pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER}; +use tail_expr_drop_order::TailExprDropOrder; use traits::*; use types::*; use unit_bindings::*; @@ -238,6 +240,7 @@ late_lint_methods!( AsyncFnInTrait: AsyncFnInTrait, NonLocalDefinitions: NonLocalDefinitions::default(), ImplTraitOvercaptures: ImplTraitOvercaptures, + TailExprDropOrder: TailExprDropOrder, ] ] ); @@ -569,7 +572,8 @@ fn register_builtins(store: &mut LintStore) { "byte_slice_in_packed_struct_with_derive", "converted into hard error, see issue #107457 \ <https://github.com/rust-lang/rust/issues/107457> for more information", - ) + ); + store.register_removed("writes_through_immutable_pointer", "converted into hard error"); } fn register_internals(store: &mut LintStore) { diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 03962d796f4..c12c5427997 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -257,7 +257,7 @@ pub struct BuiltinUnreachablePub<'a> { #[suggestion(code = "pub(crate)")] pub suggestion: (Span, Applicability), #[help] - pub help: Option<()>, + pub help: bool, } #[derive(LintDiagnostic)] @@ -572,7 +572,7 @@ pub struct Expectation { #[subdiagnostic] pub rationale: Option<ExpectationNote>, #[note] - pub note: Option<()>, + pub note: bool, } #[derive(Subdiagnostic)] @@ -756,7 +756,7 @@ pub enum InvalidReferenceCastingDiag<'tcx> { #[label] orig_cast: Option<Span>, #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)] - ty_has_interior_mutability: Option<()>, + ty_has_interior_mutability: bool, }, #[diag(lint_invalid_reference_casting_assign_to_ref)] #[note(lint_invalid_reference_casting_note_book)] @@ -764,7 +764,7 @@ pub enum InvalidReferenceCastingDiag<'tcx> { #[label] orig_cast: Option<Span>, #[note(lint_invalid_reference_casting_note_ty_has_interior_mutability)] - ty_has_interior_mutability: Option<()>, + ty_has_interior_mutability: bool, }, #[diag(lint_invalid_reference_casting_bigger_layout)] #[note(lint_layout)] diff --git a/compiler/rustc_lint/src/reference_casting.rs b/compiler/rustc_lint/src/reference_casting.rs index 5e8c39c0023..45d97403d60 100644 --- a/compiler/rustc_lint/src/reference_casting.rs +++ b/compiler/rustc_lint/src/reference_casting.rs @@ -54,8 +54,6 @@ impl<'tcx> LateLintPass<'tcx> for InvalidReferenceCasting { && let Some(ty_has_interior_mutability) = is_cast_from_ref_to_mut_ptr(cx, init, &mut peel_casts) { - let ty_has_interior_mutability = ty_has_interior_mutability.then_some(()); - cx.emit_span_lint( INVALID_REFERENCE_CASTING, expr.span, diff --git a/compiler/rustc_lint/src/tail_expr_drop_order.rs b/compiler/rustc_lint/src/tail_expr_drop_order.rs new file mode 100644 index 00000000000..f9ecc8c9806 --- /dev/null +++ b/compiler/rustc_lint/src/tail_expr_drop_order.rs @@ -0,0 +1,306 @@ +use std::mem::swap; + +use rustc_ast::UnOp; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self as hir, Block, Expr, ExprKind, LetStmt, Pat, PatKind, QPath, StmtKind}; +use rustc_macros::LintDiagnostic; +use rustc_middle::ty; +use rustc_session::lint::FutureIncompatibilityReason; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::edition::Edition; +use rustc_span::Span; + +use crate::{LateContext, LateLintPass}; + +declare_lint! { + /// The `tail_expr_drop_order` lint looks for those values generated at the tail expression location, that of type + /// with a significant `Drop` implementation, such as locks. + /// In case there are also local variables of type with significant `Drop` implementation as well, + /// this lint warns you of a potential transposition in the drop order. + /// Your discretion on the new drop order introduced by Edition 2024 is required. + /// + /// ### Example + /// ```rust,edition2024 + /// #![feature(shorter_tail_lifetimes)] + /// #![warn(tail_expr_drop_order)] + /// struct Droppy(i32); + /// impl Droppy { + /// fn get(&self) -> i32 { + /// self.0 + /// } + /// } + /// impl Drop for Droppy { + /// fn drop(&mut self) { + /// // This is a custom destructor and it induces side-effects that is observable + /// // especially when the drop order at a tail expression changes. + /// println!("loud drop {}", self.0); + /// } + /// } + /// fn edition_2024() -> i32 { + /// let another_droppy = Droppy(0); + /// Droppy(1).get() + /// } + /// fn main() { + /// edition_2024(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// In tail expression of blocks or function bodies, + /// values of type with significant `Drop` implementation has an ill-specified drop order + /// before Edition 2024 so that they are dropped only after dropping local variables. + /// Edition 2024 introduces a new rule with drop orders for them, + /// so that they are dropped first before dropping local variables. + /// + /// A significant `Drop::drop` destructor here refers to an explicit, arbitrary + /// implementation of the `Drop` trait on the type, with exceptions including `Vec`, + /// `Box`, `Rc`, `BTreeMap` and `HashMap` that are marked by the compiler otherwise + /// so long that the generic types have no significant destructor recursively. + /// In other words, a type has a significant drop destructor when it has a `Drop` implementation + /// or its destructor invokes a significant destructor on a type. + /// Since we cannot completely reason about the change by just inspecting the existence of + /// a significant destructor, this lint remains only a suggestion and is set to `allow` by default. + /// + /// This lint only points out the issue with `Droppy`, which will be dropped before `another_droppy` + /// does in Edition 2024. + /// No fix will be proposed by this lint. + /// However, the most probable fix is to hoist `Droppy` into its own local variable binding. + /// ```rust + /// struct Droppy(i32); + /// impl Droppy { + /// fn get(&self) -> i32 { + /// self.0 + /// } + /// } + /// fn edition_2024() -> i32 { + /// let value = Droppy(0); + /// let another_droppy = Droppy(1); + /// value.get() + /// } + /// ``` + pub TAIL_EXPR_DROP_ORDER, + Allow, + "Detect and warn on significant change in drop order in tail expression location", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024), + reference: "issue #123739 <https://github.com/rust-lang/rust/issues/123739>", + }; +} + +declare_lint_pass!(TailExprDropOrder => [TAIL_EXPR_DROP_ORDER]); + +impl TailExprDropOrder { + fn check_fn_or_closure<'tcx>( + cx: &LateContext<'tcx>, + fn_kind: hir::intravisit::FnKind<'tcx>, + body: &'tcx hir::Body<'tcx>, + def_id: rustc_span::def_id::LocalDefId, + ) { + let mut locals = vec![]; + if matches!(fn_kind, hir::intravisit::FnKind::Closure) { + for &capture in cx.tcx.closure_captures(def_id) { + if matches!(capture.info.capture_kind, ty::UpvarCapture::ByValue) + && capture.place.ty().has_significant_drop(cx.tcx, cx.param_env) + { + locals.push(capture.var_ident.span); + } + } + } + for param in body.params { + if cx + .typeck_results() + .node_type(param.hir_id) + .has_significant_drop(cx.tcx, cx.param_env) + { + locals.push(param.span); + } + } + if let hir::ExprKind::Block(block, _) = body.value.kind { + LintVisitor { cx, locals }.check_block_inner(block); + } else { + LintTailExpr { cx, locals: &locals, is_root_tail_expr: true }.visit_expr(body.value); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for TailExprDropOrder { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: hir::intravisit::FnKind<'tcx>, + _: &'tcx hir::FnDecl<'tcx>, + body: &'tcx hir::Body<'tcx>, + _: Span, + def_id: rustc_span::def_id::LocalDefId, + ) { + if cx.tcx.sess.at_least_rust_2024() && cx.tcx.features().shorter_tail_lifetimes { + Self::check_fn_or_closure(cx, fn_kind, body, def_id); + } + } +} + +struct LintVisitor<'tcx, 'a> { + cx: &'a LateContext<'tcx>, + // We only record locals that have significant drops + locals: Vec<Span>, +} + +struct LocalCollector<'tcx, 'a> { + cx: &'a LateContext<'tcx>, + locals: &'a mut Vec<Span>, +} + +impl<'tcx, 'a> Visitor<'tcx> for LocalCollector<'tcx, 'a> { + type Result = (); + fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) { + if let PatKind::Binding(_binding_mode, id, ident, pat) = pat.kind { + let ty = self.cx.typeck_results().node_type(id); + if ty.has_significant_drop(self.cx.tcx, self.cx.param_env) { + self.locals.push(ident.span); + } + if let Some(pat) = pat { + self.visit_pat(pat); + } + } else { + intravisit::walk_pat(self, pat); + } + } +} + +impl<'tcx, 'a> Visitor<'tcx> for LintVisitor<'tcx, 'a> { + fn visit_block(&mut self, block: &'tcx Block<'tcx>) { + let mut locals = <_>::default(); + swap(&mut locals, &mut self.locals); + self.check_block_inner(block); + swap(&mut locals, &mut self.locals); + } + fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) { + LocalCollector { cx: self.cx, locals: &mut self.locals }.visit_local(local); + } +} + +impl<'tcx, 'a> LintVisitor<'tcx, 'a> { + fn check_block_inner(&mut self, block: &Block<'tcx>) { + if !block.span.at_least_rust_2024() { + // We only lint for Edition 2024 onwards + return; + } + let Some(tail_expr) = block.expr else { return }; + for stmt in block.stmts { + match stmt.kind { + StmtKind::Let(let_stmt) => self.visit_local(let_stmt), + StmtKind::Item(_) => {} + StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e), + } + } + if self.locals.is_empty() { + return; + } + LintTailExpr { cx: self.cx, locals: &self.locals, is_root_tail_expr: true } + .visit_expr(tail_expr); + } +} + +struct LintTailExpr<'tcx, 'a> { + cx: &'a LateContext<'tcx>, + is_root_tail_expr: bool, + locals: &'a [Span], +} + +impl<'tcx, 'a> LintTailExpr<'tcx, 'a> { + fn expr_eventually_point_into_local(mut expr: &Expr<'tcx>) -> bool { + loop { + match expr.kind { + ExprKind::Index(access, _, _) | ExprKind::Field(access, _) => expr = access, + ExprKind::AddrOf(_, _, referee) | ExprKind::Unary(UnOp::Deref, referee) => { + expr = referee + } + ExprKind::Path(_) + if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind + && let [local, ..] = path.segments + && let Res::Local(_) = local.res => + { + return true; + } + _ => return false, + } + } + } + + fn expr_generates_nonlocal_droppy_value(&self, expr: &Expr<'tcx>) -> bool { + if Self::expr_eventually_point_into_local(expr) { + return false; + } + self.cx.typeck_results().expr_ty(expr).has_significant_drop(self.cx.tcx, self.cx.param_env) + } +} + +impl<'tcx, 'a> Visitor<'tcx> for LintTailExpr<'tcx, 'a> { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + if self.is_root_tail_expr { + self.is_root_tail_expr = false; + } else if self.expr_generates_nonlocal_droppy_value(expr) { + self.cx.tcx.emit_node_span_lint( + TAIL_EXPR_DROP_ORDER, + expr.hir_id, + expr.span, + TailExprDropOrderLint { spans: self.locals.to_vec() }, + ); + return; + } + match expr.kind { + ExprKind::Match(scrutinee, _, _) => self.visit_expr(scrutinee), + + ExprKind::ConstBlock(_) + | ExprKind::Array(_) + | ExprKind::Break(_, _) + | ExprKind::Continue(_) + | ExprKind::Ret(_) + | ExprKind::Become(_) + | ExprKind::Yield(_, _) + | ExprKind::InlineAsm(_) + | ExprKind::If(_, _, _) + | ExprKind::Loop(_, _, _, _) + | ExprKind::Closure(_) + | ExprKind::DropTemps(_) + | ExprKind::OffsetOf(_, _) + | ExprKind::Assign(_, _, _) + | ExprKind::AssignOp(_, _, _) + | ExprKind::Lit(_) + | ExprKind::Err(_) => {} + + ExprKind::MethodCall(_, _, _, _) + | ExprKind::Call(_, _) + | ExprKind::Type(_, _) + | ExprKind::Tup(_) + | ExprKind::Binary(_, _, _) + | ExprKind::Unary(_, _) + | ExprKind::Path(_) + | ExprKind::Let(_) + | ExprKind::Cast(_, _) + | ExprKind::Field(_, _) + | ExprKind::Index(_, _, _) + | ExprKind::AddrOf(_, _, _) + | ExprKind::Struct(_, _, _) + | ExprKind::Repeat(_, _) => intravisit::walk_expr(self, expr), + + ExprKind::Block(_, _) => { + // We do not lint further because the drop order stays the same inside the block + } + } + } + fn visit_block(&mut self, block: &'tcx Block<'tcx>) { + LintVisitor { cx: self.cx, locals: <_>::default() }.check_block_inner(block); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint_tail_expr_drop_order)] +struct TailExprDropOrderLint { + #[label] + pub spans: Vec<Span>, +} |
