diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs index 0d1b875cbed5b..9da4a0914a310 100644 --- a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -134,9 +134,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { // whether or not the right-hand side is a place expression if let LocalInfo::User(BindingForm::Var(VarBindingForm { opt_match_place: Some((opt_match_place, match_span)), - binding_mode: _, - opt_ty_info: _, - pat_span: _, + .. })) = *local_decl.local_info() { let stmt_source_info = self.body.source_info(location); diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs index bd068b29c1235..65565787014b4 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -305,8 +305,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { LocalInfo::User(BindingForm::Var(mir::VarBindingForm { binding_mode: BindingAnnotation(ByRef::No, Mutability::Not), opt_ty_info: Some(sp), - opt_match_place: _, - pat_span: _, + .. })) => { if suggest { err.span_note(sp, "the binding is already a mutable borrow"); @@ -729,6 +728,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { opt_ty_info: _, opt_match_place: _, pat_span, + introductions: _, })) => pat_span, _ => local_decl.source_info.span, }; diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 1f92cc4d76399..ed0e5620dd3b2 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -749,6 +749,7 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> { sess.time("MIR_effect_checking", || { for def_id in tcx.hir().body_owners() { tcx.ensure().has_ffi_unwind_calls(def_id); + tcx.ensure().check_liveness(def_id); // If we need to codegen, ensure that we emit all errors from // `mir_drops_elaborated_and_const_checked` now, to avoid discovering diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs index 7ecac0c0e7838..3026bc51a349e 100644 --- a/compiler/rustc_middle/src/mir/mod.rs +++ b/compiler/rustc_middle/src/mir/mod.rs @@ -999,6 +999,8 @@ pub struct VarBindingForm<'tcx> { pub opt_match_place: Option<(Option>, Span)>, /// The span of the pattern in which this variable was bound. pub pat_span: Span, + /// For each introduction place, record here the span and whether this was a shorthand pattern. + pub introductions: Vec<(Span, /* is_shorthand */ bool)>, } #[derive(Clone, Debug, TyEncodable, TyDecodable)] @@ -1008,7 +1010,7 @@ pub enum BindingForm<'tcx> { /// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit. ImplicitSelf(ImplicitSelfKind), /// Reference used in a guard expression to ensure immutability. - RefForGuard, + RefForGuard(Local), } TrivialTypeTraversalImpls! { BindingForm<'tcx> } @@ -1025,7 +1027,7 @@ mod binding_form_impl { match self { Var(binding) => binding.hash_stable(hcx, hasher), ImplicitSelf(kind) => kind.hash_stable(hcx, hasher), - RefForGuard => (), + RefForGuard(local) => local.hash_stable(hcx, hasher), } } } @@ -1208,9 +1210,7 @@ impl<'tcx> LocalDecl<'tcx> { LocalInfo::User( BindingForm::Var(VarBindingForm { binding_mode: BindingAnnotation(ByRef::No, _), - opt_ty_info: _, - opt_match_place: _, - pat_span: _, + .. }) | BindingForm::ImplicitSelf(ImplicitSelfKind::Imm), ) ) @@ -1225,9 +1225,7 @@ impl<'tcx> LocalDecl<'tcx> { LocalInfo::User( BindingForm::Var(VarBindingForm { binding_mode: BindingAnnotation(ByRef::No, _), - opt_ty_info: _, - opt_match_place: _, - pat_span: _, + .. }) | BindingForm::ImplicitSelf(_), ) ) @@ -1244,7 +1242,7 @@ impl<'tcx> LocalDecl<'tcx> { /// expression that is used to access said variable for the guard of the /// match arm. pub fn is_ref_for_guard(&self) -> bool { - matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard)) + matches!(self.local_info(), LocalInfo::User(BindingForm::RefForGuard(_))) } /// Returns `Some` if this is a reference to a static item that is used to diff --git a/compiler/rustc_middle/src/mir/statement.rs b/compiler/rustc_middle/src/mir/statement.rs index 069c8019cb2c8..831cbe5520e0e 100644 --- a/compiler/rustc_middle/src/mir/statement.rs +++ b/compiler/rustc_middle/src/mir/statement.rs @@ -50,7 +50,7 @@ impl<'tcx> StatementKind<'tcx> { impl ProjectionElem { /// Returns `true` if the target of this projection may refer to a different region of memory /// than the base. - fn is_indirect(&self) -> bool { + pub fn is_indirect(&self) -> bool { match self { Self::Deref => true, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 5ef7a20f460ed..c3c3c397794bc 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -943,8 +943,10 @@ rustc_queries! { desc { |tcx| "checking privacy in {}", describe_as_module(key.to_local_def_id(), tcx) } } - query check_liveness(key: LocalDefId) { - desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key) } + query check_liveness(key: LocalDefId) -> &'tcx rustc_index::bit_set::BitSet { + arena_cache + desc { |tcx| "checking liveness of variables in `{}`", tcx.def_path_str(key.to_def_id()) } + cache_on_disk_if(tcx) { tcx.is_typeck_child(key.to_def_id()) } } /// Return the live symbols in the crate for dead code check. diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index f10b204cd477b..436f39b36de57 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -738,6 +738,7 @@ pub enum PatKind<'tcx> { /// Is this the leftmost occurrence of the binding, i.e., is `var` the /// `HirId` of this pattern? is_primary: bool, + is_shorthand: bool, }, /// `Foo(...)` or `Foo{...}` or `Foo`, where `Foo` is a variant name from an ADT with diff --git a/compiler/rustc_middle/src/ty/closure.rs b/compiler/rustc_middle/src/ty/closure.rs index 7db64504f85ea..5bdfb022022e9 100644 --- a/compiler/rustc_middle/src/ty/closure.rs +++ b/compiler/rustc_middle/src/ty/closure.rs @@ -325,6 +325,8 @@ pub fn place_to_string_for_capture<'tcx>(tcx: TyCtxt<'tcx>, place: &HirPlace<'tc ) } }, + // Just change the type to the hidden type, so we can actually project. + HirProjectionKind::OpaqueCast => {} proj => bug!("{:?} unexpected because it isn't captured", proj), } } diff --git a/compiler/rustc_mir_build/src/build/block.rs b/compiler/rustc_mir_build/src/build/block.rs index 00e99f330f727..956cd06dfe822 100644 --- a/compiler/rustc_mir_build/src/build/block.rs +++ b/compiler/rustc_mir_build/src/build/block.rs @@ -204,6 +204,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, node, span, + false, OutsideGuard, true, ); @@ -290,7 +291,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pattern, UserTypeProjections::none(), &mut |this, _, _, node, span, _, _| { - this.storage_live_binding(block, node, span, OutsideGuard, true); + this.storage_live_binding( + block, + node, + span, + false, + OutsideGuard, + true, + ); this.schedule_drop_for_binding(node, span, OutsideGuard); }, ) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 367c391b45a49..0fb4c0bcda4e4 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -627,8 +627,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { subpattern: None, .. } => { - let place = - self.storage_live_binding(block, var, irrefutable_pat.span, OutsideGuard, true); + let place = self.storage_live_binding( + block, + var, + irrefutable_pat.span, + false, + OutsideGuard, + true, + ); unpack!(block = self.expr_into_dest(place, block, initializer_id)); // Inject a fake read, see comments on `FakeReadCause::ForLet`. @@ -661,8 +667,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }, ascription: thir::Ascription { ref annotation, variance: _ }, } => { - let place = - self.storage_live_binding(block, var, irrefutable_pat.span, OutsideGuard, true); + let place = self.storage_live_binding( + block, + var, + irrefutable_pat.span, + false, + OutsideGuard, + true, + ); unpack!(block = self.expr_into_dest(place, block, initializer_id)); // Inject a fake read, see comments on `FakeReadCause::ForLet`. @@ -855,6 +867,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block: BasicBlock, var: LocalVarId, span: Span, + is_shorthand: bool, for_guard: ForGuard, schedule_drop: bool, ) -> Place<'tcx> { @@ -868,6 +881,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { { self.schedule_drop(span, region_scope, local_id, DropKind::Storage); } + let local_info = self.local_decls[local_id].local_info.as_mut().assert_crate_local(); + if let LocalInfo::User(BindingForm::Var(var_info)) = &mut **local_info { + var_info.introductions.push((span, is_shorthand)); + } Place::from(local_id) } @@ -1149,6 +1166,7 @@ struct Binding<'tcx> { source: Place<'tcx>, var_id: LocalVarId, binding_mode: BindingAnnotation, + is_shorthand: bool, } /// Indicates that the type of `source` must be a subtype of the @@ -2332,6 +2350,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, RefWithinGuard, schedule_drops, ); @@ -2345,6 +2364,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, OutsideGuard, schedule_drops, ); @@ -2384,6 +2404,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block, binding.var_id, binding.span, + binding.is_shorthand, OutsideGuard, schedule_drops, ) @@ -2437,6 +2458,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_ty_info: None, opt_match_place, pat_span, + introductions: Vec::new(), }, )))), }; @@ -2457,7 +2479,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { user_ty: None, source_info, local_info: ClearCrossCrate::Set(Box::new(LocalInfo::User( - BindingForm::RefForGuard, + BindingForm::RefForGuard(for_arm_body), ))), }); self.var_debug_info.push(VarDebugInfo { diff --git a/compiler/rustc_mir_build/src/build/matches/util.rs b/compiler/rustc_mir_build/src/build/matches/util.rs index 440be873d4ee7..7ce1ce78d69a2 100644 --- a/compiler/rustc_mir_build/src/build/matches/util.rs +++ b/compiler/rustc_mir_build/src/build/matches/util.rs @@ -154,12 +154,13 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { TestCase::Irrefutable { ascription, binding: None } } - PatKind::Binding { mode, var, ref subpattern, .. } => { + PatKind::Binding { mode, var, ref subpattern, is_shorthand, .. } => { let binding = place.map(|source| super::Binding { span: pattern.span, source, var_id: var, binding_mode: mode, + is_shorthand, }); if let Some(subpattern) = subpattern.as_ref() { diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 6972bc00e0b2e..57e2f7a8889b3 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -60,12 +60,6 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx thir::BodyTy::Const(ty) => construct_const(tcx, def, thir, expr, ty), }; - // this must run before MIR dump, because - // "not all control paths return a value" is reported here. - // - // maybe move the check to a MIR pass? - tcx.ensure().check_liveness(def); - // Don't steal here, instead steal in unsafeck. This is so that // pattern inline constants can be evaluated as part of building the // THIR of the parent function without a cycle. @@ -947,6 +941,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { opt_ty_info: param.ty_span, opt_match_place: Some((None, span)), pat_span: span, + introductions: vec![(span, false)], })) }; self.var_indices.insert(var, LocalsForNode::One(local)); diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 133cf8e334929..fa36360e2cad5 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -311,6 +311,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { ty: var_ty, subpattern: self.lower_opt_pattern(sub), is_primary: id == pat.hir_id, + is_shorthand: false, } } @@ -328,9 +329,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let res = self.typeck_results.qpath_res(qpath, pat.hir_id); let subpatterns = fields .iter() - .map(|field| FieldPat { - field: self.typeck_results.field_index(field.hir_id), - pattern: self.lower_pattern(field.pat), + .map(|field| { + let mut pattern = self.lower_pattern(field.pat); + if let PatKind::Binding { ref mut is_shorthand, .. } = pattern.kind { + *is_shorthand = field.is_shorthand; + } + let field = self.typeck_results.field_index(field.hir_id); + FieldPat { field, pattern } }) .collect(); diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index ef15082a48138..2a0ed31ce117c 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -635,13 +635,14 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_pat(subpattern, depth_lvl + 3); print_indented!(self, "}", depth_lvl + 1); } - PatKind::Binding { name, mode, var, ty, subpattern, is_primary } => { + PatKind::Binding { name, mode, var, ty, subpattern, is_primary, is_shorthand } => { print_indented!(self, "Binding {", depth_lvl + 1); print_indented!(self, format!("name: {:?}", name), depth_lvl + 2); print_indented!(self, format!("mode: {:?}", mode), depth_lvl + 2); print_indented!(self, format!("var: {:?}", var), depth_lvl + 2); print_indented!(self, format!("ty: {:?}", ty), depth_lvl + 2); print_indented!(self, format!("is_primary: {:?}", is_primary), depth_lvl + 2); + print_indented!(self, format!("is_shorthand: {:?}", is_shorthand), depth_lvl + 2); if let Some(subpattern) = subpattern { print_indented!(self, "subpattern: Some( ", depth_lvl + 2); diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs index c5adb81b614ce..d664f75c3680e 100644 --- a/compiler/rustc_mir_dataflow/src/lib.rs +++ b/compiler/rustc_mir_dataflow/src/lib.rs @@ -17,10 +17,11 @@ pub use self::drop_flag_effects::{ drop_flag_effects_for_function_entry, drop_flag_effects_for_location, move_path_children_matching, on_all_children_bits, on_lookup_result_bits, }; +use self::framework::SwitchIntEdgeEffects; pub use self::framework::{ fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, Backward, Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice, MaybeReachable, Results, ResultsCursor, - ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects, + ResultsVisitable, ResultsVisitor, }; use self::move_paths::MoveData; diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index f9b79d72b0504..8677f0dc3cc64 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -17,13 +17,37 @@ mir_transform_ffi_unwind_call = call to {$foreign -> mir_transform_fn_item_ref = taking a reference to a function item does not give a function pointer .suggestion = cast `{$ident}` to obtain a function pointer +mir_transform_maybe_string_interpolation = you might have meant to use string interpolation in this string literal + mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspend point, but should not be .label = the value is held across this suspend point .note = {$reason} .help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point mir_transform_operation_will_panic = this operation will panic at runtime +mir_transform_string_interpolation_only_works = string interpolation only works in `format!` invocations + mir_transform_unaligned_packed_ref = reference to packed field is unaligned .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +mir_transform_unused_assign = value assigned to `{$name}` is never read + .help = maybe it is overwritten before being read? + +mir_transform_unused_assign_passed = value passed to `{$name}` is never read + .help = maybe it is overwritten before being read? + +mir_transform_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read + .help = did you mean to capture by reference instead? + +mir_transform_unused_var_assigned_only = variable `{$name}` is assigned to, but never used + .note = consider using `_{$name}` instead + +mir_transform_unused_var_underscore = if this is intentional, prefix it with an underscore + +mir_transform_unused_variable = unused variable: `{$name}` + +mir_transform_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable + +mir_transform_unused_variable_try_ignore = try ignoring the field diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 0634e321ea303..d458e13ecb4df 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -1,4 +1,7 @@ -use rustc_errors::{codes::*, Diag, DiagMessage, LintDiagnostic}; +use rustc_errors::{ + codes::*, Applicability, Diag, DiagMessage, EmissionGuarantee, LintDiagnostic, + SubdiagMessageOp, Subdiagnostic, +}; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::mir::AssertKind; use rustc_middle::ty::TyCtxt; @@ -91,6 +94,101 @@ pub(crate) struct FnItemRef { pub ident: String, } +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_capture_maybe_capture_ref)] +#[help] +pub(crate) struct UnusedCaptureMaybeCaptureRef { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_var_assigned_only)] +#[note] +pub(crate) struct UnusedVarAssignedOnly { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_assign)] +#[help] +pub(crate) struct UnusedAssign { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_assign_passed)] +#[help] +pub(crate) struct UnusedAssignPassed { + pub name: String, +} + +#[derive(LintDiagnostic)] +#[diag(mir_transform_unused_variable)] +pub(crate) struct UnusedVariable { + pub name: String, + #[subdiagnostic] + pub string_interp: Vec, + #[subdiagnostic] + pub sugg: UnusedVariableSugg, +} + +#[derive(Subdiagnostic)] +pub(crate) enum UnusedVariableSugg { + #[multipart_suggestion( + mir_transform_unused_variable_try_ignore, + applicability = "machine-applicable" + )] + TryIgnore { + #[suggestion_part(code = "{name}: _")] + shorthands: Vec, + #[suggestion_part(code = "_")] + non_shorthands: Vec, + name: String, + }, + + #[multipart_suggestion( + mir_transform_unused_var_underscore, + applicability = "machine-applicable" + )] + TryPrefix { + #[suggestion_part(code = "_{name}")] + spans: Vec, + name: String, + }, + + #[help(mir_transform_unused_variable_args_in_macro)] + NoSugg { + #[primary_span] + span: Span, + name: String, + }, +} + +pub(crate) struct UnusedVariableStringInterp { + pub lit: Span, +} + +impl Subdiagnostic for UnusedVariableStringInterp { + fn add_to_diag_with>( + self, + diag: &mut Diag<'_, G>, + _: F, + ) { + diag.span_label( + self.lit, + crate::fluent_generated::mir_transform_maybe_string_interpolation, + ); + diag.multipart_suggestion( + crate::fluent_generated::mir_transform_string_interpolation_only_works, + vec![ + (self.lit.shrink_to_lo(), String::from("format!(")), + (self.lit.shrink_to_hi(), String::from(")")), + ], + Applicability::MachineApplicable, + ); + } +} + pub(crate) struct MustNotSupend<'tcx, 'a> { pub tcx: TyCtxt<'tcx>, pub yield_sp: Span, diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index e477c068229ff..40514e3ed251c 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -84,6 +84,7 @@ mod jump_threading; mod known_panics_lint; mod large_enums; mod lint; +mod liveness; mod lower_intrinsics; mod lower_slice_len; mod match_branches; @@ -112,6 +113,7 @@ mod sroa; mod unreachable_enum_branching; mod unreachable_prop; +use liveness::check_liveness; use rustc_const_eval::transform::check_consts::{self, ConstCx}; use rustc_const_eval::transform::validate; use rustc_mir_dataflow::rustc_peek; @@ -132,6 +134,7 @@ pub fn provide(providers: &mut Providers) { mir_for_ctfe, mir_coroutine_witnesses: coroutine::mir_coroutine_witnesses, optimized_mir, + check_liveness, is_mir_available, is_ctfe_mir_available: |tcx, did| is_mir_available(tcx, did), mir_callgraph_reachable: inline::cycle::mir_callgraph_reachable, @@ -400,6 +403,8 @@ fn mir_drops_elaborated_and_const_checked(tcx: TyCtxt<'_>, def: LocalDefId) -> & } } + tcx.ensure_with_value().check_liveness(def); + let (body, _) = tcx.mir_promoted(def); let mut body = body.steal(); if let Some(error_reported) = mir_borrowck.tainted_by_errors { diff --git a/compiler/rustc_mir_transform/src/liveness.rs b/compiler/rustc_mir_transform/src/liveness.rs new file mode 100644 index 0000000000000..cd502b57e9952 --- /dev/null +++ b/compiler/rustc_mir_transform/src/liveness.rs @@ -0,0 +1,1101 @@ +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::LocalDefId; +use rustc_index::bit_set::BitSet; +use rustc_index::IndexVec; +use rustc_middle::mir::visit::{ + MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor, +}; +use rustc_middle::mir::*; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_mir_dataflow::{ + fmt::DebugWithContext, Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis, + ResultsCursor, +}; +use rustc_session::lint; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_target::abi::FieldIdx; + +use crate::errors; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum AccessKind { + Param, + Assign, + Capture, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum CaptureKind { + Closure(ty::ClosureKind), + Coroutine, + CoroutineClosure, + None, +} + +struct AssignmentResult { + /// Set of locals that are live at least once. This is used to report fully unused locals. + ever_live: BitSet, + /// Set of locals that have a non-trivial drop. This is used to skip reporting unused + /// assignment if it would be used by the `Drop` impl. + ever_dropped: BitSet, + /// Set of assignments for each local. Here, assignment is understood in the AST sense. Any + /// MIR that may look like an assignment (Assign, DropAndReplace, Yield, Call) are considered. + /// + /// For each local, we return a map: for each source position, whether the statement is live + /// and which kind of access it performs. When we encounter multiple statements at the same + /// location, we only increase the liveness, in order to avoid false positives. + assignments: IndexVec>, +} + +#[tracing::instrument(level = "debug", skip(tcx))] +pub fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> BitSet { + // Don't run unused pass for #[naked] + if tcx.has_attr(def_id.to_def_id(), sym::naked) { + return BitSet::new_empty(0); + } + + // Don't run unused pass for #[derive] + let parent = tcx.parent(tcx.typeck_root_def_id(def_id.to_def_id())); + if let DefKind::Impl { of_trait: true } = tcx.def_kind(parent) + && tcx.has_attr(parent, sym::automatically_derived) + { + return BitSet::new_empty(0); + } + + let mut body = &*tcx.mir_promoted(def_id).0.borrow(); + let mut body_mem; + + // Don't run if there are errors. + if body.tainted_by_errors.is_some() { + return BitSet::new_empty(0); + } + + let mut checked_places = PlaceSet::default(); + checked_places.insert_locals(&body.local_decls); + + // The body is the one of a closure or generator, so we also want to analyse captures. + let (capture_kind, num_captures) = if tcx.is_closure_like(def_id.to_def_id()) { + let mut self_ty = body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty; + let mut self_is_ref = false; + if let ty::Ref(_, ty, _) = self_ty.kind() { + self_ty = *ty; + self_is_ref = true; + } + + let (capture_kind, args) = match self_ty.kind() { + ty::Closure(_, args) => { + (CaptureKind::Closure(args.as_closure().kind()), ty::UpvarArgs::Closure(args)) + } + &ty::Coroutine(_, args) => (CaptureKind::Coroutine, ty::UpvarArgs::Coroutine(args)), + &ty::CoroutineClosure(_, args) => { + (CaptureKind::CoroutineClosure, ty::UpvarArgs::CoroutineClosure(args)) + } + _ => bug!("expected closure or generator, found {:?}", self_ty), + }; + + let captures = tcx.closure_captures(def_id); + checked_places.insert_captures(tcx, self_is_ref, captures, args.upvar_tys()); + + // `FnMut` closures can modify captured values and carry those + // modified values with them in subsequent calls. To model this behaviour, + // we consider the `FnMut` closure as jumping to `bb0` upon return. + if let CaptureKind::Closure(ty::ClosureKind::FnMut) = capture_kind + && checked_places.captures.iter().any(|(_, by_ref)| !by_ref) + { + // FIXME: stop cloning the body. + body_mem = body.clone(); + for bbdata in body_mem.basic_blocks_mut() { + if let TerminatorKind::Return = bbdata.terminator().kind { + bbdata.terminator_mut().kind = TerminatorKind::Goto { target: START_BLOCK }; + } + } + body = &body_mem; + } + + (capture_kind, args.upvar_tys().len()) + } else { + (CaptureKind::None, 0) + }; + + // Get the remaining variables' names from debuginfo. + checked_places.record_debuginfo(&body.var_debug_info); + + let self_assignment = find_self_assignments(&checked_places, body); + + let mut live = + MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment } + .into_engine(tcx, body) + .iterate_to_fixpoint() + .into_results_cursor(body); + + let AssignmentResult { mut ever_live, ever_dropped, mut assignments } = + find_dead_assignments(tcx, &checked_places, &mut live, body); + + // Match guards introduce a different local to freeze the guarded value as immutable. + // Having two locals, we need to make sure that we do not report an unused_variable + // when the guard local is used but not the arm local, or vice versa, like in this example. + // + // match 5 { + // x if x > 2 => {} + // ^ ^- This is `local` + // +------ This is `arm_local` + // _ => {} + // } + // + for (index, place) in checked_places.iter() { + let local = place.local; + if let &LocalInfo::User(BindingForm::RefForGuard(arm_local)) = + body.local_decls[local].local_info() + { + debug_assert!(place.projection.is_empty()); + + // Local to use in the arm. + let Some((arm_index, _proj)) = checked_places.get(arm_local.into()) else { continue }; + debug_assert_ne!(index, arm_index); + debug_assert_eq!(_proj, &[]); + + // Mark the arm local as used if the guard local is used. + if ever_live.contains(index) { + ever_live.insert(arm_index); + } + + // Some assignments are common to both locals in the source code. + // Sadly, we can only detect this using the `source_info`. + // Therefore, we loop over all the assignments we have for the guard local: + // - if they already appeared for the arm local, the assignment is live if one of the + // two versions is live; + // - if it does not appear for the arm local, it happened inside the guard, so we add + // it as-is. + let guard_assignments = std::mem::take(&mut assignments[index]); + let arm_assignments = &mut assignments[arm_index]; + for (source_info, (live, kind)) in guard_assignments { + match arm_assignments.entry(source_info) { + IndexEntry::Vacant(v) => { + v.insert((live, kind)); + } + IndexEntry::Occupied(mut o) => { + o.get_mut().0 |= live; + } + } + } + } + } + + // Report to caller the set of dead captures. + let mut dead_captures = BitSet::new_empty(num_captures); + + // First, report fully unused locals. + debug!("report fully unused places"); + for (index, place) in checked_places.iter() { + if ever_live.contains(index) { + continue; + } + + // This is a capture: let the enclosing function report the unused variable. + if is_capture(*place) { + debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL); + for p in place.projection { + if let PlaceElem::Field(f, _) = p { + dead_captures.insert(*f); + break; + } + } + continue; + } + + let Some((ref name, def_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + let local = place.local; + let decl = &body.local_decls[local]; + + if decl.from_compiler_desugaring() { + continue; + } + + // Only report actual user-defined binding from now on. + let LocalInfo::User(BindingForm::Var(binding)) = decl.local_info() else { continue }; + let Some(hir_id) = decl.source_info.scope.lint_root(&body.source_scopes) else { continue }; + + let introductions = &binding.introductions; + + // #117284, when `ident_span` and `def_span` have different contexts + // we can't provide a good suggestion, instead we pointed out the spans from macro + let from_macro = def_span.from_expansion() + && introductions.iter().any(|(ident_span, _)| ident_span.eq_ctxt(def_span)); + + let statements = &mut assignments[index]; + if statements.is_empty() { + let sugg = if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else { + errors::UnusedVariableSugg::TryPrefix { spans: vec![def_span], name: name.clone() } + }; + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); + continue; + } + + // Idiomatic rust assigns a value to a local upon definition. However, we do not want to + // warn twice, for the unused local and for the unused assignment. Therefore, we remove + // from the list of assignments the ones that happen at the definition site. + statements.retain(|source_info, _| { + source_info.span.find_ancestor_inside(binding.pat_span).is_none() + }); + + // Extra assignments that we recognize thanks to the initialization span. We need to + // take care of macro contexts here to be accurate. + if let Some((_, initializer_span)) = binding.opt_match_place { + statements.retain(|source_info, _| { + let within = source_info.span.find_ancestor_inside(initializer_span); + let outer_initializer_span = + initializer_span.find_ancestor_in_same_ctxt(source_info.span); + within.is_none() + && outer_initializer_span.map_or(true, |s| !s.contains(source_info.span)) + }); + } + + if !statements.is_empty() { + // We have a dead local with outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if ever_dropped.contains(index) { + continue; + } + + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + def_span, + errors::UnusedVarAssignedOnly { name: name.clone() }, + ); + continue; + } + + // We do not have outstanding assignments, suggest renaming the binding. + let spans = introductions.iter().map(|(span, _)| *span).collect::>(); + + let any_shorthand = introductions.iter().any(|(_, is_shorthand)| *is_shorthand); + + let sugg = if any_shorthand { + errors::UnusedVariableSugg::TryIgnore { + name: name.clone(), + shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + non_shorthands: introductions + .iter() + .filter_map( + |&(span, is_shorthand)| { + if !is_shorthand { Some(span) } else { None } + }, + ) + .collect(), + } + } else if from_macro { + errors::UnusedVariableSugg::NoSugg { span: def_span, name: name.clone() } + } else if !introductions.is_empty() { + errors::UnusedVariableSugg::TryPrefix { + name: name.clone(), + spans: introductions.iter().map(|&(span, _)| span).collect(), + } + } else { + errors::UnusedVariableSugg::TryPrefix { name: name.clone(), spans: vec![def_span] } + }; + + tcx.emit_node_span_lint( + lint::builtin::UNUSED_VARIABLES, + hir_id, + spans, + errors::UnusedVariable { + name: name.clone(), + string_interp: maybe_suggest_literal_matching_name(body, name), + sugg, + }, + ); + } + + // Second, report unused assignments that do not correspond to initialization. + // Initializations have been removed in the previous loop reporting unused variables. + debug!("report dead assignments"); + for (index, statements) in assignments.into_iter_enumerated() { + if statements.is_empty() { + continue; + } + + let Some((ref name, decl_span)) = checked_places.names[index] else { continue }; + if name.is_empty() || name.starts_with('_') || name == "self" { + continue; + } + + // We have outstanding assignments and with non-trivial drop. + // This is probably a drop-guard, so we do not issue a warning there. + if ever_dropped.contains(index) { + continue; + } + + // We probed MIR in reverse order for dataflow. + // We revert the vector to give a consistent order to the user. + for (source_info, (live, kind)) in statements.into_iter().rev() { + if live { + continue; + } + + // Report the dead assignment. + let Some(hir_id) = source_info.scope.lint_root(&body.source_scopes) else { continue }; + + match kind { + AccessKind::Assign => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssign { name: name.clone() }, + ), + AccessKind::Param => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + source_info.span, + errors::UnusedAssignPassed { name: name.clone() }, + ), + AccessKind::Capture => tcx.emit_node_span_lint( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + decl_span, + errors::UnusedCaptureMaybeCaptureRef { name: name.clone() }, + ), + } + } + } + + dead_captures +} + +/// Small helper to make semantics easier to read. +#[inline] +fn is_capture(place: PlaceRef<'_>) -> bool { + if !place.projection.is_empty() { + debug_assert_eq!(place.local, ty::CAPTURE_STRUCT_LOCAL); + true + } else { + false + } +} + +/// Give a diagnostic when any of the string constants look like a naked format string that would +/// interpolate our dead local. +fn maybe_suggest_literal_matching_name( + body: &Body<'_>, + name: &str, +) -> Vec { + struct LiteralFinder<'body, 'tcx> { + body: &'body Body<'tcx>, + name: String, + name_colon: String, + found: Vec, + } + + impl<'tcx> Visitor<'tcx> for LiteralFinder<'_, 'tcx> { + fn visit_constant(&mut self, constant: &ConstOperand<'tcx>, loc: Location) { + if let ty::Ref(_, ref_ty, _) = constant.ty().kind() + && ref_ty.kind() == &ty::Str + { + let rendered_constant = constant.const_.to_string(); + if rendered_constant.contains(&self.name) + || rendered_constant.contains(&self.name_colon) + { + let lit = self.body.source_info(loc).span; + self.found.push(errors::UnusedVariableStringInterp { lit }); + } + } + } + } + + let mut finder = LiteralFinder { + body, + name: format!("{{{name}}}"), + name_colon: format!("{{{name}:"), + found: vec![], + }; + finder.visit_body(body); + finder.found +} + +/// Compute self-assignments of the form `a += b`. +/// +/// MIR building generates 2 statements and 1 terminator for such assignments: +/// - _temp = CheckedBinaryOp(a, b) +/// - assert(!_temp.1) +/// - a = _temp.0 +/// +/// This function tries to detect this pattern in order to avoid marking statement as a definition +/// and use. This will let the analysis be dictated by the next use of `a`. +/// +/// Note that we will still need to account for the use of `b`. +fn find_self_assignments<'tcx>( + checked_places: &PlaceSet<'tcx>, + body: &Body<'tcx>, +) -> FxHashSet { + let mut self_assign = FxHashSet::default(); + + const FIELD_0: FieldIdx = FieldIdx::from_u32(0); + const FIELD_1: FieldIdx = FieldIdx::from_u32(1); + + for (bb, bb_data) in body.basic_blocks.iter_enumerated() { + for (statement_index, stmt) in bb_data.statements.iter().enumerate() { + let StatementKind::Assign(box (first_place, rvalue)) = &stmt.kind else { continue }; + match rvalue { + // Straight self-assignment. + Rvalue::BinaryOp(op, box (Operand::Copy(lhs), _)) => { + if lhs != first_place { + continue; + } + + // We ignore indirect self-assignment, because both occurences of `dest` are uses. + let is_indirect = checked_places + .get(first_place.as_ref()) + .map_or(false, |(_, projections)| is_indirect(projections)); + if is_indirect { + continue; + } + + self_assign.insert(Location { block: bb, statement_index }); + + // Checked division verifies overflow before performing the division, so we + // need to go and ignore this check in the predecessor block. + if let BinOp::Div | BinOp::Rem = op + && statement_index == 0 + && let &[pred] = body.basic_blocks.predecessors()[bb].as_slice() + && let TerminatorKind::Assert { msg, .. } = + &body.basic_blocks[pred].terminator().kind + && let AssertKind::Overflow(..) = **msg + && let len = body.basic_blocks[pred].statements.len() + && len >= 2 + { + // BitAnd of two checks. + self_assign.insert(Location { block: pred, statement_index: len - 1 }); + // `lhs == MIN`. + self_assign.insert(Location { block: pred, statement_index: len - 2 }); + } + } + // For checked binary ops, the MIR builder inserts an assertion in between. + Rvalue::CheckedBinaryOp(_, box (Operand::Copy(lhs), _)) => { + // Checked binary ops only appear at the end of the block, before the assertion. + if statement_index + 1 != bb_data.statements.len() { + continue; + } + + let TerminatorKind::Assert { + cond, + target, + msg: box AssertKind::Overflow(..), + .. + } = &bb_data.terminator().kind + else { + continue; + }; + let Some(assign) = body.basic_blocks[*target].statements.first() else { + continue; + }; + let StatementKind::Assign(box (dest, Rvalue::Use(Operand::Move(temp)))) = + assign.kind + else { + continue; + }; + + if dest != *lhs { + continue; + } + + let Operand::Move(cond) = cond else { continue }; + let [PlaceElem::Field(FIELD_0, _)] = &temp.projection.as_slice() else { + continue; + }; + let [PlaceElem::Field(FIELD_1, _)] = &cond.projection.as_slice() else { + continue; + }; + + // We ignore indirect self-assignment, because both occurences of `dest` are uses. + let is_indirect = checked_places + .get(dest.as_ref()) + .map_or(false, |(_, projections)| is_indirect(projections)); + if is_indirect { + continue; + } + + if first_place.local == temp.local + && first_place.local == cond.local + && first_place.projection.is_empty() + { + // Original block + self_assign.insert(Location { + block: bb, + statement_index: bb_data.statements.len() - 1, + }); + self_assign.insert(Location { + block: bb, + statement_index: bb_data.statements.len(), + }); + // Target block + self_assign.insert(Location { block: *target, statement_index: 0 }); + } + } + _ => {} + } + } + } + + self_assign +} + +#[derive(Default, Debug)] +struct PlaceSet<'tcx> { + places: IndexVec>, + names: IndexVec>, + + /// Places corresponding to locals, common case. + locals: IndexVec>, + + // Handling of captures. + /// If `_1` is a reference, we need to add a `Deref` to the matched place. + capture_field_pos: usize, + /// Captured fields. + captures: IndexVec, +} + +impl<'tcx> PlaceSet<'tcx> { + fn insert_locals(&mut self, decls: &IndexVec>) { + self.locals = IndexVec::from_elem(None, &decls); + for (local, decl) in decls.iter_enumerated() { + // Record all user-written locals for the analysis. + // We also keep the `RefForGuard` locals (more on that below). + if let LocalInfo::User(BindingForm::Var(_) | BindingForm::RefForGuard(_)) = + decl.local_info() + { + let index = self.places.push(local.into()); + self.locals[local] = Some(index); + let _index = self.names.push(None); + debug_assert_eq!(index, _index); + } + } + } + + fn insert_captures( + &mut self, + tcx: TyCtxt<'tcx>, + self_is_ref: bool, + captures: &[&'tcx ty::CapturedPlace<'tcx>], + upvars: &ty::List>, + ) { + // We should not track the environment local separately. + debug_assert_eq!(self.locals[ty::CAPTURE_STRUCT_LOCAL], None); + + let self_place = Place { + local: ty::CAPTURE_STRUCT_LOCAL, + projection: tcx.mk_place_elems(if self_is_ref { &[PlaceElem::Deref] } else { &[] }), + }; + if self_is_ref { + self.capture_field_pos = 1; + } + + for (f, (capture, ty)) in std::iter::zip(captures, upvars).enumerate() { + let f = FieldIdx::from_usize(f); + let elem = PlaceElem::Field(f, ty); + let by_ref = matches!(capture.info.capture_kind, ty::UpvarCapture::ByRef(..)); + let place = if by_ref { + self_place.project_deeper(&[elem, PlaceElem::Deref], tcx) + } else { + self_place.project_deeper(&[elem], tcx) + }; + let index = self.places.push(place.as_ref()); + let _f = self.captures.push((index, by_ref)); + debug_assert_eq!(_f, f); + + // Record a variable name from the capture, because it is much friendlier than the + // debuginfo name. + self.names.insert(index, (capture.to_string(tcx), capture.get_path_span(tcx))); + } + } + + fn record_debuginfo(&mut self, var_debug_info: &Vec>) { + for var_debug_info in var_debug_info { + if let VarDebugInfoContents::Place(place) = var_debug_info.value + && let Some(index) = self.locals[place.local] + { + self.names.get_or_insert_with(index, || { + (var_debug_info.name.to_string(), var_debug_info.source_info.span) + }); + } + } + } + + fn get(&self, place: PlaceRef<'tcx>) -> Option<(PlaceIndex, &'tcx [PlaceElem<'tcx>])> { + if let Some(index) = self.locals[place.local] { + return Some((index, place.projection)); + } + if place.local == ty::CAPTURE_STRUCT_LOCAL + && !self.captures.is_empty() + && self.capture_field_pos < place.projection.len() + && let PlaceElem::Field(f, _) = place.projection[self.capture_field_pos] + && let Some((index, by_ref)) = self.captures.get(f) + { + let mut start = self.capture_field_pos + 1; + if *by_ref { + // Account for an extra Deref. + start += 1; + } + // We may have an attempt to access `_1.f` as a shallow reborrow. Just ignore it. + if start <= place.projection.len() { + let projection = &place.projection[start..]; + return Some((*index, projection)); + } + } + None + } + + fn iter(&self) -> impl Iterator)> { + self.places.iter_enumerated() + } + + fn len(&self) -> usize { + self.places.len() + } +} + +/// Collect all assignments to checked locals. +/// +/// Assignments are collected, even if they are live. Dead assignments are reported, and live +/// assignments are used to make diagnostics correct for match guards. +fn find_dead_assignments<'tcx>( + tcx: TyCtxt<'tcx>, + checked_places: &PlaceSet<'tcx>, + cursor: &mut ResultsCursor<'_, 'tcx, MaybeLivePlaces<'_, 'tcx>>, + body: &Body<'tcx>, +) -> AssignmentResult { + let mut ever_live = BitSet::new_empty(checked_places.len()); + let mut ever_dropped = BitSet::new_empty(checked_places.len()); + let mut assignments = IndexVec::>::from_elem( + Default::default(), + &checked_places.places, + ); + + let mut check_place = + |place: Place<'tcx>, kind, source_info: SourceInfo, live: &BitSet| { + if let Some((index, extra_projections)) = checked_places.get(place.as_ref()) { + if !is_indirect(extra_projections) { + match assignments[index].entry(source_info) { + IndexEntry::Vacant(v) => { + v.insert((live.contains(index), kind)); + } + IndexEntry::Occupied(mut o) => { + // There were already a sighting. Mark this statement as live if it was, + // to avoid false positives. + o.get_mut().0 |= live.contains(index); + } + } + } + } + }; + + let param_env = tcx.param_env(body.source.def_id()); + let mut record_drop = |place: Place<'tcx>| { + if let Some((index, &[])) = checked_places.get(place.as_ref()) { + let ty = place.ty(&body.local_decls, tcx).ty; + let needs_drop = matches!( + ty.kind(), + ty::Closure(..) + | ty::Coroutine(..) + | ty::Tuple(..) + | ty::Adt(..) + | ty::Dynamic(..) + | ty::Array(..) + | ty::Slice(..) + | ty::Alias(ty::Opaque, ..) + ) && ty.needs_drop(tcx, param_env); + if needs_drop { + ever_dropped.insert(index); + } + } + }; + + for (bb, bb_data) in traversal::postorder(body) { + cursor.seek_to_block_end(bb); + let live = cursor.get(); + ever_live.union(live); + + let terminator = bb_data.terminator(); + match &terminator.kind { + TerminatorKind::Call { destination: place, .. } + | TerminatorKind::Yield { resume_arg: place, .. } => { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + record_drop(*place) + } + TerminatorKind::Drop { place, .. } => record_drop(*place), + TerminatorKind::InlineAsm { operands, .. } => { + for operand in operands { + if let InlineAsmOperand::Out { place: Some(place), .. } + | InlineAsmOperand::InOut { out_place: Some(place), .. } = operand + { + check_place(*place, AccessKind::Assign, terminator.source_info, live); + } + } + } + _ => {} + } + + for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() { + cursor.seek_before_primary_effect(Location { block: bb, statement_index }); + let live = cursor.get(); + ever_live.union(live); + match &statement.kind { + StatementKind::Assign(box (place, _)) + | StatementKind::Deinit(box place) + | StatementKind::SetDiscriminant { box place, .. } => { + check_place(*place, AccessKind::Assign, statement.source_info, live); + } + StatementKind::Retag(_, _) + | StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Coverage(_) + | StatementKind::Intrinsic(_) + | StatementKind::Nop + | StatementKind::FakeRead(_) + | StatementKind::PlaceMention(_) + | StatementKind::ConstEvalCounter + | StatementKind::AscribeUserType(_, _) => (), + } + } + } + + // Check liveness of function arguments on entry. + { + cursor.seek_to_block_start(START_BLOCK); + let live = cursor.get(); + ever_live.union(live); + + // Verify that arguments and captured values are useful. + for (index, place) in checked_places.iter() { + let kind = if is_capture(*place) { + // This is a by-ref capture, an assignment to it will modify surrounding + // environment, so we do not report it. + if place.projection.last() == Some(&PlaceElem::Deref) { + continue; + } + + AccessKind::Capture + } else if body.local_kind(place.local) == LocalKind::Arg { + AccessKind::Param + } else { + continue; + }; + let source_info = body.local_decls[place.local].source_info; + assignments[index].insert(source_info, (live.contains(index), kind)); + } + } + + AssignmentResult { ever_live, ever_dropped, assignments } +} + +rustc_index::newtype_index! { + pub struct PlaceIndex {} +} + +impl DebugWithContext> for PlaceIndex { + fn fmt_with( + &self, + ctxt: &MaybeLivePlaces<'_, '_>, + f: &mut std::fmt::Formatter<'_>, + ) -> std::fmt::Result { + std::fmt::Debug::fmt(&ctxt.checked_places.places[*self], f) + } +} + +pub struct MaybeLivePlaces<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + checked_places: &'a PlaceSet<'tcx>, + capture_kind: CaptureKind, + self_assignment: FxHashSet, +} + +impl<'tcx> MaybeLivePlaces<'_, 'tcx> { + fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, 'tcx, T> { + TransferFunction { + tcx: self.tcx, + checked_places: &self.checked_places, + capture_kind: self.capture_kind, + trans, + self_assignment: &self.self_assignment, + } + } +} + +impl<'tcx> AnalysisDomain<'tcx> for MaybeLivePlaces<'_, 'tcx> { + type Domain = BitSet; + type Direction = Backward; + + const NAME: &'static str = "liveness-lint"; + + fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain { + // bottom = not live + BitSet::new_empty(self.checked_places.len()) + } + + fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) { + // No variables are live until we observe a use + } +} + +impl<'tcx> GenKillAnalysis<'tcx> for MaybeLivePlaces<'_, 'tcx> { + type Idx = PlaceIndex; + + fn domain_size(&self, _: &Body<'tcx>) -> usize { + self.checked_places.len() + } + + fn statement_effect( + &mut self, + trans: &mut impl GenKill, + statement: &Statement<'tcx>, + location: Location, + ) { + self.transfer_function(trans).visit_statement(statement, location); + } + + fn terminator_effect<'mir>( + &mut self, + trans: &mut Self::Domain, + terminator: &'mir Terminator<'tcx>, + location: Location, + ) -> TerminatorEdges<'mir, 'tcx> { + self.transfer_function(trans).visit_terminator(terminator, location); + terminator.edges() + } + + fn call_return_effect( + &mut self, + _trans: &mut Self::Domain, + _block: BasicBlock, + _return_places: CallReturnPlaces<'_, 'tcx>, + ) { + // FIXME: what should happen here? + } +} + +struct TransferFunction<'a, 'tcx, T> { + tcx: TyCtxt<'tcx>, + checked_places: &'a PlaceSet<'tcx>, + trans: &'a mut T, + capture_kind: CaptureKind, + self_assignment: &'a FxHashSet, +} + +impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, 'tcx, T> +where + T: GenKill, +{ + fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) { + match statement.kind { + // `ForLet` fake read erroneously marks the just-assigned local as live. + // This defeats the purpose of the analysis for `let` bindings. + StatementKind::FakeRead(box (FakeReadCause::ForLet(..), _)) => return, + // Handle self-assignment by restricting the read/write they do. + StatementKind::Assign(box (ref dest, ref rvalue)) + if self.self_assignment.contains(&location) => + { + if let Rvalue::CheckedBinaryOp(_, box (_, ref rhs)) = rvalue { + // We are computing the binary operation: + // - the LHS will be assigned, so we don't read it; + // - the RHS still needs to be read. + self.visit_operand(rhs, location); + self.visit_place( + dest, + PlaceContext::MutatingUse(MutatingUseContext::Store), + location, + ); + } else if let Rvalue::BinaryOp(_, box (_, ref rhs)) = rvalue { + // We are computing the binary operation: + // - the LHS is being updated, so we don't read it; + // - the RHS still needs to be read. + self.visit_operand(rhs, location); + } else { + // This is the second part of a checked self-assignment, + // we are assigning the result. + // We do not consider the write to the destination as a `def`. + // `self_assignment` must be false if the assignment is indirect. + self.visit_rvalue(rvalue, location); + } + } + _ => self.super_statement(statement, location), + } + } + + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + // By-ref captures could be read by the surrounding environement, so we mark + // them as live upon yield and return. + match terminator.kind { + TerminatorKind::Return + | TerminatorKind::Yield { .. } + | TerminatorKind::Goto { target: START_BLOCK } // Inserted for the `FnMut` case. + if self.capture_kind != CaptureKind::None => + { + // All indirect captures have an effect on the environment, so we mark them as live. + for (index, place) in self.checked_places.iter() { + if place.local == ty::CAPTURE_STRUCT_LOCAL + && place.projection.last() == Some(&PlaceElem::Deref) + { + self.trans.gen(index); + } + } + } + // Do not consider a drop to be a use. We whitelist interesting drops elsewhere. + TerminatorKind::Drop { .. } => {} + // Ignore assertions since they must be triggered by actual code. + TerminatorKind::Assert { .. } => {} + _ => self.super_terminator(terminator, location), + } + } + + fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { + match rvalue { + // When a closure/generator does not use some of its captures, do not consider these + // captures as live in the surrounding function. This allows to report unused variables, + // even if they have been (uselessly) captured. + Rvalue::Aggregate( + box AggregateKind::Closure(def_id, _) | box AggregateKind::Coroutine(def_id, _), + ref operands, + ) => { + if let Some(def_id) = def_id.as_local() { + let dead_captures = self.tcx.check_liveness(def_id); + for (field, operand) in + operands.iter_enumerated().take(dead_captures.domain_size()) + { + if !dead_captures.contains(field) { + self.visit_operand(operand, location); + } + } + } + } + _ => self.super_rvalue(rvalue, location), + } + } + + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { + if let Some((index, extra_projections)) = self.checked_places.get(place.as_ref()) { + for i in (extra_projections.len()..=place.projection.len()).rev() { + let place_part = + PlaceRef { local: place.local, projection: &place.projection[..i] }; + let extra_projections = &place.projection[i..]; + + if let Some(&elem) = extra_projections.get(0) { + self.visit_projection_elem(place_part, elem, context, location); + } + } + + match DefUse::for_place(extra_projections, context) { + Some(DefUse::Def) => self.trans.kill(index), + Some(DefUse::Use) => self.trans.gen(index), + None => {} + } + } else { + self.super_place(place, context, location) + } + } + + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { + if let Some((index, _proj)) = self.checked_places.get(local.into()) { + debug_assert_eq!(_proj, &[]); + match DefUse::for_place(&[], context) { + Some(DefUse::Def) => self.trans.kill(index), + Some(DefUse::Use) => self.trans.gen(index), + _ => {} + } + } + } +} + +#[derive(Eq, PartialEq, Debug, Clone)] +enum DefUse { + Def, + Use, +} + +fn is_indirect(proj: &[PlaceElem<'_>]) -> bool { + proj.iter().any(|p| p.is_indirect()) +} + +impl DefUse { + fn for_place<'tcx>(projection: &[PlaceElem<'tcx>], context: PlaceContext) -> Option { + let is_indirect = is_indirect(projection); + match context { + PlaceContext::MutatingUse( + MutatingUseContext::Store + | MutatingUseContext::Deinit + | MutatingUseContext::SetDiscriminant, + ) => { + if is_indirect { + // Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a + // use. + Some(DefUse::Use) + } else if projection.is_empty() { + Some(DefUse::Def) + } else { + None + } + } + + // For the associated terminators, this is only a `Def` when the terminator returns + // "successfully." As such, we handle this case separately in `call_return_effect` + // above. However, if the place looks like `*_5`, this is still unconditionally a use of + // `_5`. + PlaceContext::MutatingUse( + MutatingUseContext::Call + | MutatingUseContext::Yield + | MutatingUseContext::AsmOutput, + ) => is_indirect.then_some(DefUse::Use), + + // All other contexts are uses... + PlaceContext::MutatingUse( + MutatingUseContext::AddressOf + | MutatingUseContext::Borrow + | MutatingUseContext::Drop + | MutatingUseContext::Retag, + ) + | PlaceContext::NonMutatingUse( + NonMutatingUseContext::AddressOf + | NonMutatingUseContext::Copy + | NonMutatingUseContext::Inspect + | NonMutatingUseContext::Move + | NonMutatingUseContext::FakeBorrow + | NonMutatingUseContext::SharedBorrow + | NonMutatingUseContext::PlaceMention, + ) => Some(DefUse::Use), + + PlaceContext::NonUse( + NonUseContext::StorageLive + | NonUseContext::StorageDead + | NonUseContext::AscribeUserTy(_) + | NonUseContext::VarDebugInfo, + ) => None, + + PlaceContext::MutatingUse(MutatingUseContext::Projection) + | PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => { + unreachable!("A projection could be a def or a use and must be handled separately") + } + } + } +} diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 7fc523ffe0dea..34c1915355000 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -445,7 +445,6 @@ passes_macro_export_on_decl_macro = passes_macro_use = `#[{$name}]` only has an effect on `extern crate` and modules -passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal passes_missing_const_err = attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const` .help = make the function or method const @@ -669,8 +668,6 @@ passes_skipping_const_checks = skipping const checks passes_stability_promotable = attribute cannot be applied to an expression -passes_string_interpolation_only_works = string interpolation only works in `format!` invocations - passes_target_feature_on_statement = {passes_should_be_applied_to_fn} .warn = {-passes_previously_accepted} @@ -729,15 +726,6 @@ passes_unused = unused attribute .suggestion = remove this attribute -passes_unused_assign = value assigned to `{$name}` is never read - .help = maybe it is overwritten before being read? - -passes_unused_assign_passed = value passed to `{$name}` is never read - .help = maybe it is overwritten before being read? - -passes_unused_capture_maybe_capture_ref = value captured by `{$name}` is never read - .help = did you mean to capture by reference instead? - passes_unused_default_method_body_const_note = `default_method_body_is_const` has been replaced with `#[const_trait]` on traits @@ -758,25 +746,6 @@ passes_unused_multiple = passes_unused_no_lints_note = attribute `{$name}` without any lints has no effect -passes_unused_var_assigned_only = variable `{$name}` is assigned to, but never used - .note = consider using `_{$name}` instead - -passes_unused_var_maybe_capture_ref = unused variable: `{$name}` - .help = did you mean to capture by reference instead? - -passes_unused_var_remove_field = unused variable: `{$name}` -passes_unused_var_remove_field_suggestion = try removing the field - -passes_unused_variable_args_in_macro = `{$name}` is captured in macro and introduced a unused variable - -passes_unused_variable_try_ignore = unused variable: `{$name}` - .suggestion = try ignoring the field - -passes_unused_variable_try_prefix = unused variable: `{$name}` - .label = unused variable - .suggestion = if this is intentional, prefix it with an underscore - - passes_used_compiler_linker = `used(compiler)` and `used(linker)` can't be used together diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 3f26ea4507d41..a3ca8ae629b10 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -7,7 +7,7 @@ use crate::fluent_generated as fluent; use rustc_ast::Label; use rustc_errors::{ codes::*, Applicability, Diag, DiagCtxt, DiagSymbolList, Diagnostic, EmissionGuarantee, Level, - MultiSpan, SubdiagMessageOp, Subdiagnostic, + MultiSpan, }; use rustc_hir::{self as hir, ExprKind, Target}; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; @@ -1640,45 +1640,6 @@ pub struct UnreachableDueToUninhabited<'desc, 'tcx> { pub ty: Ty<'tcx>, } -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_maybe_capture_ref)] -#[help] -pub struct UnusedVarMaybeCaptureRef { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_capture_maybe_capture_ref)] -#[help] -pub struct UnusedCaptureMaybeCaptureRef { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_remove_field)] -pub struct UnusedVarRemoveField { - pub name: String, - #[subdiagnostic] - pub sugg: UnusedVarRemoveFieldSugg, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion( - passes_unused_var_remove_field_suggestion, - applicability = "machine-applicable" -)] -pub struct UnusedVarRemoveFieldSugg { - #[suggestion_part(code = "")] - pub spans: Vec, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_var_assigned_only)] -#[note] -pub struct UnusedVarAssignedOnly { - pub name: String, -} - #[derive(LintDiagnostic)] #[diag(passes_unnecessary_stable_feature)] pub struct UnnecessaryStableFeature { @@ -1703,86 +1664,6 @@ pub struct UnnecessaryPartialStableFeature { #[note] pub struct IneffectiveUnstableImpl; -#[derive(LintDiagnostic)] -#[diag(passes_unused_assign)] -#[help] -pub struct UnusedAssign { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_assign_passed)] -#[help] -pub struct UnusedAssignPassed { - pub name: String, -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_variable_try_prefix)] -pub struct UnusedVariableTryPrefix { - #[label] - pub label: Option, - #[subdiagnostic] - pub string_interp: Vec, - #[subdiagnostic] - pub sugg: UnusedVariableSugg, - pub name: String, -} - -#[derive(Subdiagnostic)] -pub enum UnusedVariableSugg { - #[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")] - TryPrefixSugg { - #[suggestion_part(code = "_{name}")] - spans: Vec, - name: String, - }, - #[help(passes_unused_variable_args_in_macro)] - NoSugg { - #[primary_span] - span: Span, - name: String, - }, -} - -pub struct UnusedVariableStringInterp { - pub lit: Span, - pub lo: Span, - pub hi: Span, -} - -impl Subdiagnostic for UnusedVariableStringInterp { - fn add_to_diag_with>( - self, - diag: &mut Diag<'_, G>, - _f: F, - ) { - diag.span_label(self.lit, crate::fluent_generated::passes_maybe_string_interpolation); - diag.multipart_suggestion( - crate::fluent_generated::passes_string_interpolation_only_works, - vec![(self.lo, String::from("format!(")), (self.hi, String::from(")"))], - Applicability::MachineApplicable, - ); - } -} - -#[derive(LintDiagnostic)] -#[diag(passes_unused_variable_try_ignore)] -pub struct UnusedVarTryIgnore { - #[subdiagnostic] - pub sugg: UnusedVarTryIgnoreSugg, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion(passes_suggestion, applicability = "maybe-incorrect")] -pub struct UnusedVarTryIgnoreSugg { - #[suggestion_part(code = "{name}: _")] - pub shorthands: Vec, - #[suggestion_part(code = "_")] - pub non_shorthands: Vec, - pub name: String, -} - #[derive(LintDiagnostic)] #[diag(passes_attr_crate_level)] #[note] diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index e03052bcfede8..3d7399d38e403 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -34,7 +34,6 @@ pub mod hir_stats; mod lang_items; pub mod layout_test; mod lib_features; -mod liveness; pub mod loops; mod naked_functions; mod reachable; @@ -55,7 +54,6 @@ pub fn provide(providers: &mut Providers) { lib_features::provide(providers); loops::provide(providers); naked_functions::provide(providers); - liveness::provide(providers); reachable::provide(providers); stability::provide(providers); upvars::provide(providers); diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs deleted file mode 100644 index 125084f47505e..0000000000000 --- a/compiler/rustc_passes/src/liveness.rs +++ /dev/null @@ -1,1747 +0,0 @@ -//! A classic liveness analysis based on dataflow over the AST. Computes, -//! for each local variable in a function, whether that variable is live -//! at a given point. Program execution points are identified by their -//! IDs. -//! -//! # Basic idea -//! -//! The basic model is that each local variable is assigned an index. We -//! represent sets of local variables using a vector indexed by this -//! index. The value in the vector is either 0, indicating the variable -//! is dead, or the ID of an expression that uses the variable. -//! -//! We conceptually walk over the AST in reverse execution order. If we -//! find a use of a variable, we add it to the set of live variables. If -//! we find an assignment to a variable, we remove it from the set of live -//! variables. When we have to merge two flows, we take the union of -//! those two flows -- if the variable is live on both paths, we simply -//! pick one ID. In the event of loops, we continue doing this until a -//! fixed point is reached. -//! -//! ## Checking initialization -//! -//! At the function entry point, all variables must be dead. If this is -//! not the case, we can report an error using the ID found in the set of -//! live variables, which identifies a use of the variable which is not -//! dominated by an assignment. -//! -//! ## Checking moves -//! -//! After each explicit move, the variable must be dead. -//! -//! ## Computing last uses -//! -//! Any use of the variable where the variable is dead afterwards is a -//! last use. -//! -//! # Implementation details -//! -//! The actual implementation contains two (nested) walks over the AST. -//! The outer walk has the job of building up the ir_maps instance for the -//! enclosing function. On the way down the tree, it identifies those AST -//! nodes and variable IDs that will be needed for the liveness analysis -//! and assigns them contiguous IDs. The liveness ID for an AST node is -//! called a `live_node` (it's a newtype'd `u32`) and the ID for a variable -//! is called a `variable` (another newtype'd `u32`). -//! -//! On the way back up the tree, as we are about to exit from a function -//! declaration we allocate a `liveness` instance. Now that we know -//! precisely how many nodes and variables we need, we can allocate all -//! the various arrays that we will need to precisely the right size. We then -//! perform the actual propagation on the `liveness` instance. -//! -//! This propagation is encoded in the various `propagate_through_*()` -//! methods. It effectively does a reverse walk of the AST; whenever we -//! reach a loop node, we iterate until a fixed point is reached. -//! -//! ## The `RWU` struct -//! -//! At each live node `N`, we track three pieces of information for each -//! variable `V` (these are encapsulated in the `RWU` struct): -//! -//! - `reader`: the `LiveNode` ID of some node which will read the value -//! that `V` holds on entry to `N`. Formally: a node `M` such -//! that there exists a path `P` from `N` to `M` where `P` does not -//! write `V`. If the `reader` is `None`, then the current -//! value will never be read (the variable is dead, essentially). -//! -//! - `writer`: the `LiveNode` ID of some node which will write the -//! variable `V` and which is reachable from `N`. Formally: a node `M` -//! such that there exists a path `P` from `N` to `M` and `M` writes -//! `V`. If the `writer` is `None`, then there is no writer -//! of `V` that follows `N`. -//! -//! - `used`: a boolean value indicating whether `V` is *used*. We -//! distinguish a *read* from a *use* in that a *use* is some read that -//! is not just used to generate a new value. For example, `x += 1` is -//! a read but not a use. This is used to generate better warnings. -//! -//! ## Special nodes and variables -//! -//! We generate various special nodes for various, well, special purposes. -//! These are described in the `Liveness` struct. - -use crate::errors; - -use self::LiveNodeKind::*; -use self::VarKind::*; - -use rustc_data_structures::fx::FxIndexMap; -use rustc_hir as hir; -use rustc_hir::def::*; -use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet}; -use rustc_index::IndexVec; -use rustc_middle::query::Providers; -use rustc_middle::ty::{self, RootVariableMinCaptureList, Ty, TyCtxt}; -use rustc_session::lint; -use rustc_span::symbol::{kw, sym, Symbol}; -use rustc_span::{BytePos, Span}; - -use std::io; -use std::io::prelude::*; -use std::rc::Rc; - -mod rwu_table; - -rustc_index::newtype_index! { - #[debug_format = "v({})"] - pub struct Variable {} -} - -rustc_index::newtype_index! { - #[debug_format = "ln({})"] - pub struct LiveNode {} -} - -#[derive(Copy, Clone, PartialEq, Debug)] -enum LiveNodeKind { - UpvarNode(Span), - ExprNode(Span, HirId), - VarDefNode(Span, HirId), - ClosureNode, - ExitNode, - ErrNode, -} - -fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String { - let sm = tcx.sess.source_map(); - match lnk { - UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)), - ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)), - VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)), - ClosureNode => "Closure node".to_owned(), - ExitNode => "Exit node".to_owned(), - ErrNode => "Error node".to_owned(), - } -} - -fn check_liveness(tcx: TyCtxt<'_>, def_id: LocalDefId) { - // Don't run unused pass for #[derive()] - let parent = tcx.local_parent(def_id); - if let DefKind::Impl { .. } = tcx.def_kind(parent) - && tcx.has_attr(parent, sym::automatically_derived) - { - return; - } - - // Don't run unused pass for #[naked] - if tcx.has_attr(def_id.to_def_id(), sym::naked) { - return; - } - - let mut maps = IrMaps::new(tcx); - let body_id = tcx.hir().body_owned_by(def_id); - let hir_id = tcx.hir().body_owner(body_id); - let body = tcx.hir().body(body_id); - - if let Some(upvars) = tcx.upvars_mentioned(def_id) { - for &var_hir_id in upvars.keys() { - let var_name = tcx.hir().name(var_hir_id); - maps.add_variable(Upvar(var_hir_id, var_name)); - } - } - - // gather up the various local variables, significant expressions, - // and so forth: - maps.visit_body(body); - - // compute liveness - let mut lsets = Liveness::new(&mut maps, def_id); - let entry_ln = lsets.compute(body, hir_id); - lsets.log_liveness(entry_ln, body_id.hir_id); - - // check for various error conditions - lsets.visit_body(body); - lsets.warn_about_unused_upvars(entry_ln); - lsets.warn_about_unused_args(body, entry_ln); -} - -pub fn provide(providers: &mut Providers) { - *providers = Providers { check_liveness, ..*providers }; -} - -// ______________________________________________________________________ -// Creating ir_maps -// -// This is the first pass and the one that drives the main -// computation. It walks up and down the IR once. On the way down, -// we count for each function the number of variables as well as -// liveness nodes. A liveness node is basically an expression or -// capture clause that does something of interest: either it has -// interesting control flow or it uses/defines a local variable. -// -// On the way back up, at each function node we create liveness sets -// (we now know precisely how big to make our various vectors and so -// forth) and then do the data-flow propagation to compute the set -// of live variables at each program point. -// -// Finally, we run back over the IR one last time and, using the -// computed liveness, check various safety conditions. For example, -// there must be no live nodes at the definition site for a variable -// unless it has an initializer. Similarly, each non-mutable local -// variable must not be assigned if there is some successor -// assignment. And so forth. - -struct CaptureInfo { - ln: LiveNode, - var_hid: HirId, -} - -#[derive(Copy, Clone, Debug)] -struct LocalInfo { - id: HirId, - name: Symbol, - is_shorthand: bool, -} - -#[derive(Copy, Clone, Debug)] -enum VarKind { - Param(HirId, Symbol), - Local(LocalInfo), - Upvar(HirId, Symbol), -} - -struct CollectLitsVisitor<'tcx> { - lit_exprs: Vec<&'tcx hir::Expr<'tcx>>, -} - -impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if let hir::ExprKind::Lit(_) = expr.kind { - self.lit_exprs.push(expr); - } - intravisit::walk_expr(self, expr); - } -} - -struct IrMaps<'tcx> { - tcx: TyCtxt<'tcx>, - live_node_map: HirIdMap, - variable_map: HirIdMap, - capture_info_map: HirIdMap>>, - var_kinds: IndexVec, - lnks: IndexVec, -} - -impl<'tcx> IrMaps<'tcx> { - fn new(tcx: TyCtxt<'tcx>) -> IrMaps<'tcx> { - IrMaps { - tcx, - live_node_map: HirIdMap::default(), - variable_map: HirIdMap::default(), - capture_info_map: Default::default(), - var_kinds: IndexVec::new(), - lnks: IndexVec::new(), - } - } - - fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode { - let ln = self.lnks.push(lnk); - - debug!("{:?} is of kind {}", ln, live_node_kind_to_string(lnk, self.tcx)); - - ln - } - - fn add_live_node_for_node(&mut self, hir_id: HirId, lnk: LiveNodeKind) { - let ln = self.add_live_node(lnk); - self.live_node_map.insert(hir_id, ln); - - debug!("{:?} is node {:?}", ln, hir_id); - } - - fn add_variable(&mut self, vk: VarKind) -> Variable { - let v = self.var_kinds.push(vk); - - match vk { - Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) | Upvar(node_id, _) => { - self.variable_map.insert(node_id, v); - } - } - - debug!("{:?} is {:?}", v, vk); - - v - } - - fn variable(&self, hir_id: HirId, span: Span) -> Variable { - match self.variable_map.get(&hir_id) { - Some(&var) => var, - None => { - span_bug!(span, "no variable registered for id {:?}", hir_id); - } - } - } - - fn variable_name(&self, var: Variable) -> Symbol { - match self.var_kinds[var] { - Local(LocalInfo { name, .. }) | Param(_, name) | Upvar(_, name) => name, - } - } - - fn variable_is_shorthand(&self, var: Variable) -> bool { - match self.var_kinds[var] { - Local(LocalInfo { is_shorthand, .. }) => is_shorthand, - Param(..) | Upvar(..) => false, - } - } - - fn set_captures(&mut self, hir_id: HirId, cs: Vec) { - self.capture_info_map.insert(hir_id, Rc::new(cs)); - } - - fn collect_shorthand_field_ids(&self, pat: &hir::Pat<'tcx>) -> HirIdSet { - // For struct patterns, take note of which fields used shorthand - // (`x` rather than `x: x`). - let mut shorthand_field_ids = HirIdSet::default(); - - pat.walk_always(|pat| { - if let hir::PatKind::Struct(_, fields, _) = pat.kind { - let short = fields.iter().filter(|f| f.is_shorthand); - shorthand_field_ids.extend(short.map(|f| f.pat.hir_id)); - } - }); - - shorthand_field_ids - } - - fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) { - let shorthand_field_ids = self.collect_shorthand_field_ids(pat); - - pat.each_binding(|_, hir_id, _, ident| { - self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id)); - self.add_variable(Local(LocalInfo { - id: hir_id, - name: ident.name, - is_shorthand: shorthand_field_ids.contains(&hir_id), - })); - }); - } -} - -impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { - fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) { - self.add_from_pat(local.pat); - if local.els.is_some() { - self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id)); - } - intravisit::walk_local(self, local); - } - - fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { - self.add_from_pat(&arm.pat); - intravisit::walk_arm(self, arm); - } - - fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { - let shorthand_field_ids = self.collect_shorthand_field_ids(param.pat); - param.pat.each_binding(|_bm, hir_id, _x, ident| { - let var = match param.pat.kind { - rustc_hir::PatKind::Struct(..) => Local(LocalInfo { - id: hir_id, - name: ident.name, - is_shorthand: shorthand_field_ids.contains(&hir_id), - }), - _ => Param(hir_id, ident.name), - }; - self.add_variable(var); - }); - intravisit::walk_param(self, param); - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - // live nodes required for uses or definitions of variables: - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res); - if let Res::Local(_var_hir_id) = path.res { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - } - hir::ExprKind::Closure(closure) => { - // Interesting control flow (for loops can contain labeled - // breaks or continues) - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - - // Make a live_node for each mentioned variable, with the span - // being the location that the variable is used. This results - // in better error messages than just pointing at the closure - // construction site. - let mut call_caps = Vec::new(); - if let Some(upvars) = self.tcx.upvars_mentioned(closure.def_id) { - call_caps.extend(upvars.keys().map(|var_id| { - let upvar = upvars[var_id]; - let upvar_ln = self.add_live_node(UpvarNode(upvar.span)); - CaptureInfo { ln: upvar_ln, var_hid: *var_id } - })); - } - self.set_captures(expr.hir_id, call_caps); - } - - hir::ExprKind::Let(let_expr) => { - self.add_from_pat(let_expr.pat); - } - - // live nodes required for interesting control flow: - hir::ExprKind::If(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Loop(..) - | hir::ExprKind::Yield(..) => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - } - - // Inline assembly may contain labels. - hir::ExprKind::InlineAsm(asm) if asm.contains_label() => { - self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); - intravisit::walk_expr(self, expr); - } - - // otherwise, live nodes are not required: - hir::ExprKind::Index(..) - | hir::ExprKind::Field(..) - | hir::ExprKind::Array(..) - | hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - | hir::ExprKind::Tup(..) - | hir::ExprKind::Binary(..) - | hir::ExprKind::AddrOf(..) - | hir::ExprKind::Cast(..) - | hir::ExprKind::DropTemps(..) - | hir::ExprKind::Unary(..) - | hir::ExprKind::Break(..) - | hir::ExprKind::Continue(_) - | hir::ExprKind::Lit(_) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Ret(..) - | hir::ExprKind::Become(..) - | hir::ExprKind::Block(..) - | hir::ExprKind::Assign(..) - | hir::ExprKind::AssignOp(..) - | hir::ExprKind::Struct(..) - | hir::ExprKind::Repeat(..) - | hir::ExprKind::InlineAsm(..) - | hir::ExprKind::OffsetOf(..) - | hir::ExprKind::Type(..) - | hir::ExprKind::Err(_) - | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) - | hir::ExprKind::Path(hir::QPath::LangItem(..)) => {} - } - intravisit::walk_expr(self, expr); - } -} - -// ______________________________________________________________________ -// Computing liveness sets -// -// Actually we compute just a bit more than just liveness, but we use -// the same basic propagation framework in all cases. - -const ACC_READ: u32 = 1; -const ACC_WRITE: u32 = 2; -const ACC_USE: u32 = 4; - -struct Liveness<'a, 'tcx> { - ir: &'a mut IrMaps<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, - param_env: ty::ParamEnv<'tcx>, - closure_min_captures: Option<&'tcx RootVariableMinCaptureList<'tcx>>, - successors: IndexVec>, - rwu_table: rwu_table::RWUTable, - - /// A live node representing a point of execution before closure entry & - /// after closure exit. Used to calculate liveness of captured variables - /// through calls to the same closure. Used for Fn & FnMut closures only. - closure_ln: LiveNode, - /// A live node representing every 'exit' from the function, whether it be - /// by explicit return, panic, or other means. - exit_ln: LiveNode, - - // mappings from loop node ID to LiveNode - // ("break" label should map to loop node ID, - // it probably doesn't now) - break_ln: HirIdMap, - cont_ln: HirIdMap, -} - -impl<'a, 'tcx> Liveness<'a, 'tcx> { - fn new(ir: &'a mut IrMaps<'tcx>, body_owner: LocalDefId) -> Liveness<'a, 'tcx> { - let typeck_results = ir.tcx.typeck(body_owner); - let param_env = ir.tcx.param_env(body_owner); - let closure_min_captures = typeck_results.closure_min_captures.get(&body_owner); - let closure_ln = ir.add_live_node(ClosureNode); - let exit_ln = ir.add_live_node(ExitNode); - - let num_live_nodes = ir.lnks.len(); - let num_vars = ir.var_kinds.len(); - - Liveness { - ir, - typeck_results, - param_env, - closure_min_captures, - successors: IndexVec::from_elem_n(None, num_live_nodes), - rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars), - closure_ln, - exit_ln, - break_ln: Default::default(), - cont_ln: Default::default(), - } - } - - fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode { - match self.ir.live_node_map.get(&hir_id) { - Some(&ln) => ln, - None => { - // This must be a mismatch between the ir_map construction - // above and the propagation code below; the two sets of - // code have to agree about which AST nodes are worth - // creating liveness nodes for. - span_bug!(span, "no live node registered for node {:?}", hir_id); - } - } - } - - fn variable(&self, hir_id: HirId, span: Span) -> Variable { - self.ir.variable(hir_id, span) - } - - fn define_bindings_in_pat(&mut self, pat: &hir::Pat<'_>, mut succ: LiveNode) -> LiveNode { - // In an or-pattern, only consider the first non-never pattern; any later patterns - // must have the same bindings, and we also consider that pattern - // to be the "authoritative" set of ids. - pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| { - let ln = self.live_node(hir_id, pat_sp); - let var = self.variable(hir_id, ident.span); - self.init_from_succ(ln, succ); - self.define(ln, var); - succ = ln; - }); - succ - } - - fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_reader(ln, var) - } - - // Is this variable live on entry to any of its successor nodes? - fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool { - let successor = self.successors[ln].unwrap(); - self.live_on_entry(successor, var) - } - - fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_used(ln, var) - } - - fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_writer(ln, var) - } - - fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool { - match self.successors[ln] { - Some(successor) => self.assigned_on_entry(successor, var), - None => { - self.ir.tcx.dcx().delayed_bug("no successor"); - true - } - } - } - - fn write_vars(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()> - where - F: FnMut(Variable) -> bool, - { - for var_idx in 0..self.ir.var_kinds.len() { - let var = Variable::from(var_idx); - if test(var) { - write!(wr, " {var:?}")?; - } - } - Ok(()) - } - - #[allow(unused_must_use)] - fn ln_str(&self, ln: LiveNode) -> String { - let mut wr = Vec::new(); - { - let wr = &mut wr as &mut dyn Write; - write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]); - self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var)); - write!(wr, " writes"); - self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var)); - write!(wr, " uses"); - self.write_vars(wr, |var| self.rwu_table.get_used(ln, var)); - - write!(wr, " precedes {:?}]", self.successors[ln]); - } - String::from_utf8(wr).unwrap() - } - - fn log_liveness(&self, entry_ln: LiveNode, hir_id: hir::HirId) { - // hack to skip the loop unless debug! is enabled: - debug!( - "^^ liveness computation results for body {} (entry={:?})", - { - for ln_idx in 0..self.ir.lnks.len() { - debug!("{:?}", self.ln_str(LiveNode::from(ln_idx))); - } - hir_id - }, - entry_ln - ); - } - - fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) { - self.successors[ln] = Some(succ_ln); - - // It is not necessary to initialize the RWUs here because they are all - // empty when created, and the sets only grow during iterations. - } - - fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) { - // more efficient version of init_empty() / merge_from_succ() - self.successors[ln] = Some(succ_ln); - self.rwu_table.copy(ln, succ_ln); - debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln)); - } - - fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool { - if ln == succ_ln { - return false; - } - - let changed = self.rwu_table.union(ln, succ_ln); - debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed); - changed - } - - // Indicates that a local variable was *defined*; we know that no - // uses of the variable can precede the definition (resolve checks - // this) so we just clear out all the data. - fn define(&mut self, writer: LiveNode, var: Variable) { - let used = self.rwu_table.get_used(writer, var); - self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used }); - debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer)); - } - - // Either read, write, or both depending on the acc bitset - fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) { - debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln)); - - let mut rwu = self.rwu_table.get(ln, var); - - if (acc & ACC_WRITE) != 0 { - rwu.reader = false; - rwu.writer = true; - } - - // Important: if we both read/write, must do read second - // or else the write will override. - if (acc & ACC_READ) != 0 { - rwu.reader = true; - } - - if (acc & ACC_USE) != 0 { - rwu.used = true; - } - - self.rwu_table.set(ln, var, rwu); - } - - fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode { - debug!("compute: for body {:?}", body.id().hir_id); - - // # Liveness of captured variables - // - // When computing the liveness for captured variables we take into - // account how variable is captured (ByRef vs ByValue) and what is the - // closure kind (Coroutine / FnOnce vs Fn / FnMut). - // - // Variables captured by reference are assumed to be used on the exit - // from the closure. - // - // In FnOnce closures, variables captured by value are known to be dead - // on exit since it is impossible to call the closure again. - // - // In Fn / FnMut closures, variables captured by value are live on exit - // if they are live on the entry to the closure, since only the closure - // itself can access them on subsequent calls. - - if let Some(closure_min_captures) = self.closure_min_captures { - // Mark upvars captured by reference as used after closure exits. - for (&var_hir_id, min_capture_list) in closure_min_captures { - for captured_place in min_capture_list { - match captured_place.info.capture_kind { - ty::UpvarCapture::ByRef(_) => { - let var = self.variable( - var_hir_id, - captured_place.get_capture_kind_span(self.ir.tcx), - ); - self.acc(self.exit_ln, var, ACC_READ | ACC_USE); - } - ty::UpvarCapture::ByValue => {} - } - } - } - } - - let succ = self.propagate_through_expr(body.value, self.exit_ln); - - if self.closure_min_captures.is_none() { - // Either not a closure, or closure without any captured variables. - // No need to determine liveness of captured variables, since there - // are none. - return succ; - } - - let ty = self.typeck_results.node_type(hir_id); - match ty.kind() { - ty::Closure(_def_id, args) => match args.as_closure().kind() { - ty::ClosureKind::Fn => {} - ty::ClosureKind::FnMut => {} - ty::ClosureKind::FnOnce => return succ, - }, - ty::CoroutineClosure(_def_id, args) => match args.as_coroutine_closure().kind() { - ty::ClosureKind::Fn => {} - ty::ClosureKind::FnMut => {} - ty::ClosureKind::FnOnce => return succ, - }, - ty::Coroutine(..) => return succ, - _ => { - span_bug!( - body.value.span, - "{} has upvars so it should have a closure type: {:?}", - hir_id, - ty - ); - } - }; - - // Propagate through calls to the closure. - loop { - self.init_from_succ(self.closure_ln, succ); - for param in body.params { - param.pat.each_binding(|_bm, hir_id, _x, ident| { - let var = self.variable(hir_id, ident.span); - self.define(self.closure_ln, var); - }) - } - - if !self.merge_from_succ(self.exit_ln, self.closure_ln) { - break; - } - assert_eq!(succ, self.propagate_through_expr(body.value, self.exit_ln)); - } - - succ - } - - fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode { - if blk.targeted_by_break { - self.break_ln.insert(blk.hir_id, succ); - } - let succ = self.propagate_through_opt_expr(blk.expr, succ); - blk.stmts.iter().rev().fold(succ, |succ, stmt| self.propagate_through_stmt(stmt, succ)) - } - - fn propagate_through_stmt(&mut self, stmt: &hir::Stmt<'_>, succ: LiveNode) -> LiveNode { - match stmt.kind { - hir::StmtKind::Let(local) => { - // Note: we mark the variable as defined regardless of whether - // there is an initializer. Initially I had thought to only mark - // the live variable as defined if it was initialized, and then we - // could check for uninit variables just by scanning what is live - // at the start of the function. But that doesn't work so well for - // immutable variables defined in a loop: - // loop { let x; x = 5; } - // because the "assignment" loops back around and generates an error. - // - // So now we just check that variables defined w/o an - // initializer are not live at the point of their - // initialization, which is mildly more complex than checking - // once at the func header but otherwise equivalent. - - if let Some(els) = local.els { - // Eventually, `let pat: ty = init else { els };` is mostly equivalent to - // `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };` - // except that extended lifetime applies at the `init` location. - // - // (e) - // | - // v - // (expr) - // / \ - // | | - // v v - // bindings els - // | - // v - // ( succ ) - // - if let Some(init) = local.init { - let else_ln = self.propagate_through_block(els, succ); - let ln = self.live_node(local.hir_id, local.span); - self.init_from_succ(ln, succ); - self.merge_from_succ(ln, else_ln); - let succ = self.propagate_through_expr(init, ln); - self.define_bindings_in_pat(local.pat, succ) - } else { - span_bug!( - stmt.span, - "variable is uninitialized but an unexpected else branch is found" - ) - } - } else { - let succ = self.propagate_through_opt_expr(local.init, succ); - self.define_bindings_in_pat(local.pat, succ) - } - } - hir::StmtKind::Item(..) => succ, - hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => { - self.propagate_through_expr(expr, succ) - } - } - } - - fn propagate_through_exprs(&mut self, exprs: &[Expr<'_>], succ: LiveNode) -> LiveNode { - exprs.iter().rev().fold(succ, |succ, expr| self.propagate_through_expr(expr, succ)) - } - - fn propagate_through_opt_expr( - &mut self, - opt_expr: Option<&Expr<'_>>, - succ: LiveNode, - ) -> LiveNode { - opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ)) - } - - fn propagate_through_expr(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - debug!("propagate_through_expr: {:?}", expr); - - match expr.kind { - // Interesting cases with control flow or which gen/kill - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE) - } - - hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ), - - hir::ExprKind::Closure { .. } => { - debug!("{:?} is an ExprKind::Closure", expr); - - // the construction of a closure itself is not important, - // but we have to consider the closed over variables. - let caps = self - .ir - .capture_info_map - .get(&expr.hir_id) - .cloned() - .unwrap_or_else(|| span_bug!(expr.span, "no registered caps")); - - caps.iter().rev().fold(succ, |succ, cap| { - self.init_from_succ(cap.ln, succ); - let var = self.variable(cap.var_hid, expr.span); - self.acc(cap.ln, var, ACC_READ | ACC_USE); - cap.ln - }) - } - - hir::ExprKind::Let(let_expr) => { - let succ = self.propagate_through_expr(let_expr.init, succ); - self.define_bindings_in_pat(let_expr.pat, succ) - } - - // Note that labels have been resolved, so we don't need to look - // at the label ident - hir::ExprKind::Loop(ref blk, ..) => self.propagate_through_loop(expr, blk, succ), - - hir::ExprKind::Yield(e, ..) => { - let yield_ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(yield_ln, succ); - self.merge_from_succ(yield_ln, self.exit_ln); - self.propagate_through_expr(e, yield_ln) - } - - hir::ExprKind::If(ref cond, ref then, ref else_opt) => { - // - // (cond) - // | - // v - // (expr) - // / \ - // | | - // v v - // (then)(els) - // | | - // v v - // ( succ ) - // - let else_ln = self.propagate_through_opt_expr(else_opt.as_deref(), succ); - let then_ln = self.propagate_through_expr(then, succ); - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, else_ln); - self.merge_from_succ(ln, then_ln); - self.propagate_through_expr(cond, ln) - } - - hir::ExprKind::Match(ref e, arms, _) => { - // - // (e) - // | - // v - // (expr) - // / | \ - // | | | - // v v v - // (..arms..) - // | | | - // v v v - // ( succ ) - // - // - let ln = self.live_node(expr.hir_id, expr.span); - self.init_empty(ln, succ); - for arm in arms { - let body_succ = self.propagate_through_expr(arm.body, succ); - - let guard_succ = arm - .guard - .as_ref() - .map_or(body_succ, |g| self.propagate_through_expr(g, body_succ)); - let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ); - self.merge_from_succ(ln, arm_succ); - } - self.propagate_through_expr(e, ln) - } - - hir::ExprKind::Ret(ref o_e) => { - // Ignore succ and subst exit_ln. - self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln) - } - - hir::ExprKind::Become(e) => { - // Ignore succ and subst exit_ln. - self.propagate_through_expr(e, self.exit_ln) - } - - hir::ExprKind::Break(label, ref opt_expr) => { - // Find which label this break jumps to - let target = match label.target_id { - Ok(hir_id) => self.break_ln.get(&hir_id), - Err(err) => span_bug!(expr.span, "loop scope error: {}", err), - } - .cloned(); - - // Now that we know the label we're going to, - // look it up in the break loop nodes table - - match target { - Some(b) => self.propagate_through_opt_expr(opt_expr.as_deref(), b), - None => span_bug!(expr.span, "`break` to unknown label"), - } - } - - hir::ExprKind::Continue(label) => { - // Find which label this expr continues to - let sc = label - .target_id - .unwrap_or_else(|err| span_bug!(expr.span, "loop scope error: {}", err)); - - // Now that we know the label we're going to, - // look it up in the continue loop nodes table - self.cont_ln.get(&sc).cloned().unwrap_or_else(|| { - self.ir.tcx.dcx().span_delayed_bug(expr.span, "continue to unknown label"); - self.ir.add_live_node(ErrNode) - }) - } - - hir::ExprKind::Assign(ref l, ref r, _) => { - // see comment on places in - // propagate_through_place_components() - let succ = self.write_place(l, succ, ACC_WRITE); - let succ = self.propagate_through_place_components(l, succ); - self.propagate_through_expr(r, succ) - } - - hir::ExprKind::AssignOp(_, ref l, ref r) => { - // an overloaded assign op is like a method call - if self.typeck_results.is_method_call(expr) { - let succ = self.propagate_through_expr(l, succ); - self.propagate_through_expr(r, succ) - } else { - // see comment on places in - // propagate_through_place_components() - let succ = self.write_place(l, succ, ACC_WRITE | ACC_READ); - let succ = self.propagate_through_expr(r, succ); - self.propagate_through_place_components(l, succ) - } - } - - // Uninteresting cases: just propagate in rev exec order - hir::ExprKind::Array(exprs) => self.propagate_through_exprs(exprs, succ), - - hir::ExprKind::Struct(_, fields, ref with_expr) => { - let succ = self.propagate_through_opt_expr(with_expr.as_deref(), succ); - fields - .iter() - .rev() - .fold(succ, |succ, field| self.propagate_through_expr(field.expr, succ)) - } - - hir::ExprKind::Call(ref f, args) => { - let succ = self.check_is_ty_uninhabited(expr, succ); - let succ = self.propagate_through_exprs(args, succ); - self.propagate_through_expr(f, succ) - } - - hir::ExprKind::MethodCall(.., receiver, args, _) => { - let succ = self.check_is_ty_uninhabited(expr, succ); - let succ = self.propagate_through_exprs(args, succ); - self.propagate_through_expr(receiver, succ) - } - - hir::ExprKind::Tup(exprs) => self.propagate_through_exprs(exprs, succ), - - hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => { - let r_succ = self.propagate_through_expr(r, succ); - - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, succ); - self.merge_from_succ(ln, r_succ); - - self.propagate_through_expr(l, ln) - } - - hir::ExprKind::Index(ref l, ref r, _) | hir::ExprKind::Binary(_, ref l, ref r) => { - let r_succ = self.propagate_through_expr(r, succ); - self.propagate_through_expr(l, r_succ) - } - - hir::ExprKind::AddrOf(_, _, ref e) - | hir::ExprKind::Cast(ref e, _) - | hir::ExprKind::Type(ref e, _) - | hir::ExprKind::DropTemps(ref e) - | hir::ExprKind::Unary(_, ref e) - | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(e, succ), - - hir::ExprKind::InlineAsm(asm) => { - // - // (inputs) - // | - // v - // (outputs) - // / \ - // | | - // v v - // (labels)(fallthrough) - // | | - // v v - // ( succ / exit_ln ) - - // Handle non-returning asm - let mut succ = - if self.typeck_results.expr_ty(expr).is_never() { self.exit_ln } else { succ }; - - // Do a first pass for labels only - if asm.contains_label() { - let ln = self.live_node(expr.hir_id, expr.span); - self.init_from_succ(ln, succ); - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::Label { block } => { - let label_ln = self.propagate_through_block(block, succ); - self.merge_from_succ(ln, label_ln); - } - hir::InlineAsmOperand::In { .. } - | hir::InlineAsmOperand::Out { .. } - | hir::InlineAsmOperand::InOut { .. } - | hir::InlineAsmOperand::SplitInOut { .. } - | hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } => {} - } - } - succ = ln; - } - - // Do a second pass for writing outputs only - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::In { .. } - | hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } - | hir::InlineAsmOperand::Label { .. } => {} - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - succ = self.write_place(expr, succ, ACC_WRITE); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE); - } - hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { - if let Some(expr) = out_expr { - succ = self.write_place(expr, succ, ACC_WRITE); - } - } - } - } - - // Then do a third pass for inputs - for (op, _op_sp) in asm.operands.iter().rev() { - match op { - hir::InlineAsmOperand::In { expr, .. } => { - succ = self.propagate_through_expr(expr, succ) - } - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - succ = self.propagate_through_place_components(expr, succ); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - succ = self.propagate_through_place_components(expr, succ); - } - hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { - if let Some(expr) = out_expr { - succ = self.propagate_through_place_components(expr, succ); - } - succ = self.propagate_through_expr(in_expr, succ); - } - hir::InlineAsmOperand::Const { .. } - | hir::InlineAsmOperand::SymFn { .. } - | hir::InlineAsmOperand::SymStatic { .. } - | hir::InlineAsmOperand::Label { .. } => {} - } - } - succ - } - - hir::ExprKind::Lit(..) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Err(_) - | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) - | hir::ExprKind::Path(hir::QPath::LangItem(..)) - | hir::ExprKind::OffsetOf(..) => succ, - - // Note that labels have been resolved, so we don't need to look - // at the label ident - hir::ExprKind::Block(ref blk, _) => self.propagate_through_block(blk, succ), - } - } - - fn propagate_through_place_components(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - // # Places - // - // In general, the full flow graph structure for an - // assignment/move/etc can be handled in one of two ways, - // depending on whether what is being assigned is a "tracked - // value" or not. A tracked value is basically a local - // variable or argument. - // - // The two kinds of graphs are: - // - // Tracked place Untracked place - // ----------------------++----------------------- - // || - // | || | - // v || v - // (rvalue) || (rvalue) - // | || | - // v || v - // (write of place) || (place components) - // | || | - // v || v - // (succ) || (succ) - // || - // ----------------------++----------------------- - // - // I will cover the two cases in turn: - // - // # Tracked places - // - // A tracked place is a local variable/argument `x`. In - // these cases, the link_node where the write occurs is linked - // to node id of `x`. The `write_place()` routine generates - // the contents of this node. There are no subcomponents to - // consider. - // - // # Non-tracked places - // - // These are places like `x[5]` or `x.f`. In that case, we - // basically ignore the value which is written to but generate - // reads for the components---`x` in these two examples. The - // components reads are generated by - // `propagate_through_place_components()` (this fn). - // - // # Illegal places - // - // It is still possible to observe assignments to non-places; - // these errors are detected in the later pass borrowck. We - // just ignore such cases and treat them as reads. - - match expr.kind { - hir::ExprKind::Path(_) => succ, - hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(e, succ), - _ => self.propagate_through_expr(expr, succ), - } - } - - // see comment on propagate_through_place() - fn write_place(&mut self, expr: &Expr<'_>, succ: LiveNode, acc: u32) -> LiveNode { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - self.access_path(expr.hir_id, path, succ, acc) - } - - // We do not track other places, so just propagate through - // to their subcomponents. Also, it may happen that - // non-places occur here, because those are detected in the - // later pass borrowck. - _ => succ, - } - } - - fn access_var( - &mut self, - hir_id: HirId, - var_hid: HirId, - succ: LiveNode, - acc: u32, - span: Span, - ) -> LiveNode { - let ln = self.live_node(hir_id, span); - if acc != 0 { - self.init_from_succ(ln, succ); - let var = self.variable(var_hid, span); - self.acc(ln, var, acc); - } - ln - } - - fn access_path( - &mut self, - hir_id: HirId, - path: &hir::Path<'_>, - succ: LiveNode, - acc: u32, - ) -> LiveNode { - match path.res { - Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span), - _ => succ, - } - } - - fn propagate_through_loop( - &mut self, - expr: &Expr<'_>, - body: &hir::Block<'_>, - succ: LiveNode, - ) -> LiveNode { - /* - We model control flow like this: - - (expr) <-+ - | | - v | - (body) --+ - - Note that a `continue` expression targeting the `loop` will have a successor of `expr`. - Meanwhile, a `break` expression will have a successor of `succ`. - */ - - // first iteration: - let ln = self.live_node(expr.hir_id, expr.span); - self.init_empty(ln, succ); - debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body); - - self.break_ln.insert(expr.hir_id, succ); - - self.cont_ln.insert(expr.hir_id, ln); - - let body_ln = self.propagate_through_block(body, ln); - - // repeat until fixed point is reached: - while self.merge_from_succ(ln, body_ln) { - assert_eq!(body_ln, self.propagate_through_block(body, ln)); - } - - ln - } - - fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { - let ty = self.typeck_results.expr_ty(expr); - let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id(); - if ty.is_inhabited_from(self.ir.tcx, m, self.param_env) { - return succ; - } - match self.ir.lnks[succ] { - LiveNodeKind::ExprNode(succ_span, succ_id) => { - self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression"); - } - LiveNodeKind::VarDefNode(succ_span, succ_id) => { - self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition"); - } - _ => {} - }; - self.exit_ln - } - - fn warn_about_unreachable<'desc>( - &mut self, - orig_span: Span, - orig_ty: Ty<'tcx>, - expr_span: Span, - expr_id: HirId, - descr: &'desc str, - ) { - if !orig_ty.is_never() { - // Unreachable code warnings are already emitted during type checking. - // However, during type checking, full type information is being - // calculated but not yet available, so the check for diverging - // expressions due to uninhabited result types is pretty crude and - // only checks whether ty.is_never(). Here, we have full type - // information available and can issue warnings for less obviously - // uninhabited types (e.g. empty enums). The check above is used so - // that we do not emit the same warning twice if the uninhabited type - // is indeed `!`. - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNREACHABLE_CODE, - expr_id, - expr_span, - errors::UnreachableDueToUninhabited { - expr: expr_span, - orig: orig_span, - descr, - ty: orig_ty, - }, - ); - } - } -} - -// _______________________________________________________________________ -// Checking for error conditions - -impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> { - fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) { - self.check_unused_vars_in_pat(local.pat, None, None, |spans, hir_id, ln, var| { - if local.init.is_some() { - self.warn_about_dead_assign(spans, hir_id, ln, var); - } - }); - - intravisit::walk_local(self, local); - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - check_expr(self, ex); - intravisit::walk_expr(self, ex); - } - - fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { - self.check_unused_vars_in_pat(arm.pat, None, None, |_, _, _, _| {}); - intravisit::walk_arm(self, arm); - } -} - -fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { - match expr.kind { - hir::ExprKind::Assign(ref l, ..) => { - this.check_place(l); - } - - hir::ExprKind::AssignOp(_, ref l, _) => { - if !this.typeck_results.is_method_call(expr) { - this.check_place(l); - } - } - - hir::ExprKind::InlineAsm(asm) => { - for (op, _op_sp) in asm.operands { - match op { - hir::InlineAsmOperand::Out { expr, .. } => { - if let Some(expr) = expr { - this.check_place(expr); - } - } - hir::InlineAsmOperand::InOut { expr, .. } => { - this.check_place(expr); - } - hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { - if let Some(out_expr) = out_expr { - this.check_place(out_expr); - } - } - _ => {} - } - } - } - - hir::ExprKind::Let(let_expr) => { - this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {}); - } - - // no correctness conditions related to liveness - hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Loop(..) - | hir::ExprKind::Index(..) - | hir::ExprKind::Field(..) - | hir::ExprKind::Array(..) - | hir::ExprKind::Tup(..) - | hir::ExprKind::Binary(..) - | hir::ExprKind::Cast(..) - | hir::ExprKind::If(..) - | hir::ExprKind::DropTemps(..) - | hir::ExprKind::Unary(..) - | hir::ExprKind::Ret(..) - | hir::ExprKind::Become(..) - | hir::ExprKind::Break(..) - | hir::ExprKind::Continue(..) - | hir::ExprKind::Lit(_) - | hir::ExprKind::ConstBlock(..) - | hir::ExprKind::Block(..) - | hir::ExprKind::AddrOf(..) - | hir::ExprKind::OffsetOf(..) - | hir::ExprKind::Struct(..) - | hir::ExprKind::Repeat(..) - | hir::ExprKind::Closure { .. } - | hir::ExprKind::Path(_) - | hir::ExprKind::Yield(..) - | hir::ExprKind::Type(..) - | hir::ExprKind::Err(_) => {} - } -} - -impl<'tcx> Liveness<'_, 'tcx> { - fn check_place(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - hir::ExprKind::Path(hir::QPath::Resolved(_, path)) => { - if let Res::Local(var_hid) = path.res { - // Assignment to an immutable variable or argument: only legal - // if there is no later assignment. If this local is actually - // mutable, then check for a reassignment to flag the mutability - // as being used. - let ln = self.live_node(expr.hir_id, expr.span); - let var = self.variable(var_hid, expr.span); - self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var); - } - } - _ => { - // For other kinds of places, no checks are required, - // and any embedded expressions are actually rvalues - intravisit::walk_expr(self, expr); - } - } - } - - fn should_warn(&self, var: Variable) -> Option { - let name = self.ir.variable_name(var); - if name == kw::Empty { - return None; - } - let name = name.as_str(); - if name.as_bytes()[0] == b'_' { - return None; - } - Some(name.to_owned()) - } - - fn warn_about_unused_upvars(&self, entry_ln: LiveNode) { - let Some(closure_min_captures) = self.closure_min_captures else { - return; - }; - - // If closure_min_captures is Some(), upvars must be Some() too. - for (&var_hir_id, min_capture_list) in closure_min_captures { - for captured_place in min_capture_list { - match captured_place.info.capture_kind { - ty::UpvarCapture::ByValue => {} - ty::UpvarCapture::ByRef(..) => continue, - }; - let span = captured_place.get_capture_kind_span(self.ir.tcx); - let var = self.variable(var_hir_id, span); - if self.used_on_entry(entry_ln, var) { - if !self.live_on_entry(entry_ln, var) { - if let Some(name) = self.should_warn(var) { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - var_hir_id, - vec![span], - errors::UnusedCaptureMaybeCaptureRef { name }, - ); - } - } - } else { - if let Some(name) = self.should_warn(var) { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - var_hir_id, - vec![span], - errors::UnusedVarMaybeCaptureRef { name }, - ); - } - } - } - } - } - - fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) { - for p in body.params { - self.check_unused_vars_in_pat( - p.pat, - Some(entry_ln), - Some(body), - |spans, hir_id, ln, var| { - if !self.live_on_entry(ln, var) - && let Some(name) = self.should_warn(var) - { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - spans, - errors::UnusedAssignPassed { name }, - ); - } - }, - ); - } - } - - fn check_unused_vars_in_pat( - &self, - pat: &hir::Pat<'_>, - entry_ln: Option, - opt_body: Option<&hir::Body<'_>>, - on_used_on_entry: impl Fn(Vec, HirId, LiveNode, Variable), - ) { - // In an or-pattern, only consider the variable; any later patterns must have the same - // bindings, and we also consider the first pattern to be the "authoritative" set of ids. - // However, we should take the ids and spans of variables with the same name from the later - // patterns so the suggestions to prefix with underscores will apply to those too. - let mut vars: FxIndexMap)> = - <_>::default(); - - pat.each_binding(|_, hir_id, pat_sp, ident| { - let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp)); - let var = self.variable(hir_id, ident.span); - let id_and_sp = (hir_id, pat_sp, ident.span); - vars.entry(self.ir.variable_name(var)) - .and_modify(|(.., hir_ids_and_spans)| hir_ids_and_spans.push(id_and_sp)) - .or_insert_with(|| (ln, var, vec![id_and_sp])); - }); - - let can_remove = match pat.kind { - hir::PatKind::Struct(_, fields, true) => { - // if all fields are shorthand, remove the struct field, otherwise, mark with _ as prefix - fields.iter().all(|f| f.is_shorthand) - } - _ => false, - }; - - for (_, (ln, var, hir_ids_and_spans)) in vars { - if self.used_on_entry(ln, var) { - let id = hir_ids_and_spans[0].0; - let spans = - hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect(); - on_used_on_entry(spans, id, ln, var); - } else { - self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body); - } - } - } - - #[instrument(skip(self), level = "INFO")] - fn report_unused( - &self, - hir_ids_and_spans: Vec<(HirId, Span, Span)>, - ln: LiveNode, - var: Variable, - can_remove: bool, - pat: &hir::Pat<'_>, - opt_body: Option<&hir::Body<'_>>, - ) { - let first_hir_id = hir_ids_and_spans[0].0; - if let Some(name) = self.should_warn(var).filter(|name| name != "self") { - // annoying: for parameters in funcs like `fn(x: i32) - // {ret}`, there is only one node, so asking about - // assigned_on_exit() is not meaningful. - let is_assigned = - if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) }; - - if is_assigned { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .into_iter() - .map(|(_, _, ident_span)| ident_span) - .collect::>(), - errors::UnusedVarAssignedOnly { name }, - ) - } else if can_remove { - let spans = hir_ids_and_spans - .iter() - .map(|(_, pat_span, _)| { - let span = self - .ir - .tcx - .sess - .source_map() - .span_extend_to_next_char(*pat_span, ',', true); - span.with_hi(BytePos(span.hi().0 + 1)) - }) - .collect(); - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans.iter().map(|(_, pat_span, _)| *pat_span).collect::>(), - errors::UnusedVarRemoveField { - name, - sugg: errors::UnusedVarRemoveFieldSugg { spans }, - }, - ); - } else { - let (shorthands, non_shorthands): (Vec<_>, Vec<_>) = - hir_ids_and_spans.iter().copied().partition(|(hir_id, _, ident_span)| { - let var = self.variable(*hir_id, *ident_span); - self.ir.variable_is_shorthand(var) - }); - - // If we have both shorthand and non-shorthand, prefer the "try ignoring - // the field" message, and suggest `_` for the non-shorthands. If we only - // have non-shorthand, then prefix with an underscore instead. - if !shorthands.is_empty() { - let shorthands = - shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect(); - let non_shorthands = - non_shorthands.into_iter().map(|(_, pat_span, _)| pat_span).collect(); - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .iter() - .map(|(_, pat_span, _)| *pat_span) - .collect::>(), - errors::UnusedVarTryIgnore { - sugg: errors::UnusedVarTryIgnoreSugg { - shorthands, - non_shorthands, - name, - }, - }, - ); - } else { - // #117284, when `pat_span` and `ident_span` have different contexts - // we can't provide a good suggestion, instead we pointed out the spans from macro - let from_macro = non_shorthands - .iter() - .find(|(_, pat_span, ident_span)| { - !pat_span.eq_ctxt(*ident_span) && pat_span.from_expansion() - }) - .map(|(_, pat_span, _)| *pat_span); - let non_shorthands = non_shorthands - .into_iter() - .map(|(_, _, ident_span)| ident_span) - .collect::>(); - - let suggestions = self.string_interp_suggestions(&name, opt_body); - let sugg = if let Some(span) = from_macro { - errors::UnusedVariableSugg::NoSugg { span, name: name.clone() } - } else { - errors::UnusedVariableSugg::TryPrefixSugg { - spans: non_shorthands, - name: name.clone(), - } - }; - - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_VARIABLES, - first_hir_id, - hir_ids_and_spans - .iter() - .map(|(_, _, ident_span)| *ident_span) - .collect::>(), - errors::UnusedVariableTryPrefix { - label: if !suggestions.is_empty() { Some(pat.span) } else { None }, - name, - sugg, - string_interp: suggestions, - }, - ); - } - } - } - } - - fn string_interp_suggestions( - &self, - name: &str, - opt_body: Option<&hir::Body<'_>>, - ) -> Vec { - let mut suggs = Vec::new(); - let Some(opt_body) = opt_body else { - return suggs; - }; - let mut visitor = CollectLitsVisitor { lit_exprs: vec![] }; - intravisit::walk_body(&mut visitor, opt_body); - for lit_expr in visitor.lit_exprs { - let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue }; - let rustc_ast::LitKind::Str(syb, _) = litx.node else { - continue; - }; - let name_str: &str = syb.as_str(); - let name_pa = format!("{{{name}}}"); - if name_str.contains(&name_pa) { - suggs.push(errors::UnusedVariableStringInterp { - lit: lit_expr.span, - lo: lit_expr.span.shrink_to_lo(), - hi: lit_expr.span.shrink_to_hi(), - }); - } - } - suggs - } - - fn warn_about_dead_assign(&self, spans: Vec, hir_id: HirId, ln: LiveNode, var: Variable) { - if !self.live_on_exit(ln, var) - && let Some(name) = self.should_warn(var) - { - self.ir.tcx.emit_node_span_lint( - lint::builtin::UNUSED_ASSIGNMENTS, - hir_id, - spans, - errors::UnusedAssign { name }, - ); - } - } -} diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs deleted file mode 100644 index 053bf5c234acf..0000000000000 --- a/compiler/rustc_passes/src/liveness/rwu_table.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::liveness::{LiveNode, Variable}; -use std::iter; - -#[derive(Clone, Copy)] -pub(super) struct RWU { - pub(super) reader: bool, - pub(super) writer: bool, - pub(super) used: bool, -} - -/// Conceptually, this is like a `Vec>`. But the number of -/// RWU's can get very large, so it uses a more compact representation. -pub(super) struct RWUTable { - /// Total number of live nodes. - live_nodes: usize, - /// Total number of variables. - vars: usize, - - /// A compressed representation of `RWU`s. - /// - /// Each word represents 2 different `RWU`s packed together. Each packed RWU - /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a - /// padding bit. - /// - /// The data for each live node is contiguous and starts at a word boundary, - /// so there might be an unused space left. - words: Vec, - /// Number of words per each live node. - live_node_words: usize, -} - -impl RWUTable { - const RWU_READER: u8 = 0b0001; - const RWU_WRITER: u8 = 0b0010; - const RWU_USED: u8 = 0b0100; - const RWU_MASK: u8 = 0b1111; - - /// Size of packed RWU in bits. - const RWU_BITS: usize = 4; - /// Size of a word in bits. - const WORD_BITS: usize = std::mem::size_of::() * 8; - /// Number of packed RWUs that fit into a single word. - const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS; - - pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable { - let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT; - Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] } - } - - fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) { - assert!(ln.index() < self.live_nodes); - assert!(var.index() < self.vars); - - let var = var.index(); - let word = var / Self::WORD_RWU_COUNT; - let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT); - (ln.index() * self.live_node_words + word, shift as u32) - } - - fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) { - assert!(a.index() < self.live_nodes); - assert!(b.index() < self.live_nodes); - assert!(a != b); - - let a_start = a.index() * self.live_node_words; - let b_start = b.index() * self.live_node_words; - - unsafe { - let ptr = self.words.as_mut_ptr(); - ( - std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words), - std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words), - ) - } - } - - pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) { - if dst == src { - return; - } - - let (dst_row, src_row) = self.pick2_rows_mut(dst, src); - dst_row.copy_from_slice(src_row); - } - - /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was - /// changed. - pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool { - if dst == src { - return false; - } - - let mut changed = false; - let (dst_row, src_row) = self.pick2_rows_mut(dst, src); - for (dst_word, src_word) in iter::zip(dst_row, &*src_row) { - let old = *dst_word; - let new = *dst_word | src_word; - *dst_word = new; - changed |= old != new; - } - changed - } - - pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_READER != 0 - } - - pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_WRITER != 0 - } - - pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool { - let (word, shift) = self.word_and_shift(ln, var); - (self.words[word] >> shift) & Self::RWU_USED != 0 - } - - pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU { - let (word, shift) = self.word_and_shift(ln, var); - let rwu_packed = self.words[word] >> shift; - RWU { - reader: rwu_packed & Self::RWU_READER != 0, - writer: rwu_packed & Self::RWU_WRITER != 0, - used: rwu_packed & Self::RWU_USED != 0, - } - } - - pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) { - let mut packed = 0; - if rwu.reader { - packed |= Self::RWU_READER; - } - if rwu.writer { - packed |= Self::RWU_WRITER; - } - if rwu.used { - packed |= Self::RWU_USED; - } - - let (word, shift) = self.word_and_shift(ln, var); - let word = &mut self.words[word]; - *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift) - } -} diff --git a/src/tools/clippy/tests/ui/needless_match.fixed b/src/tools/clippy/tests/ui/needless_match.fixed index a936eb463f964..9aeaddf66d6a1 100644 --- a/src/tools/clippy/tests/ui/needless_match.fixed +++ b/src/tools/clippy/tests/ui/needless_match.fixed @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/src/tools/clippy/tests/ui/needless_match.rs b/src/tools/clippy/tests/ui/needless_match.rs index b1dd6ff075d88..0504f2c2f7731 100644 --- a/src/tools/clippy/tests/ui/needless_match.rs +++ b/src/tools/clippy/tests/ui/needless_match.rs @@ -1,7 +1,7 @@ #![warn(clippy::needless_match)] #![allow(clippy::manual_map)] #![allow(dead_code)] - +#![allow(unused)] #[derive(Clone, Copy)] enum Simple { A, diff --git a/src/tools/clippy/tests/ui/useless_conversion.fixed b/src/tools/clippy/tests/ui/useless_conversion.fixed index ce00fde2f9930..bcb808bd8ee37 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.fixed +++ b/src/tools/clippy/tests/ui/useless_conversion.fixed @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] fn test_generic(val: T) -> T { let _ = val; diff --git a/src/tools/clippy/tests/ui/useless_conversion.rs b/src/tools/clippy/tests/ui/useless_conversion.rs index 399796195868d..129dad7ad8c27 100644 --- a/src/tools/clippy/tests/ui/useless_conversion.rs +++ b/src/tools/clippy/tests/ui/useless_conversion.rs @@ -1,5 +1,5 @@ #![deny(clippy::useless_conversion)] -#![allow(clippy::needless_if, clippy::unnecessary_wraps)] +#![allow(clippy::needless_if, clippy::unnecessary_wraps, unused)] fn test_generic(val: T) -> T { let _ = T::from(val); diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index c8d785f83e8f8..dd97d1208b291 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -323,6 +323,8 @@ impl ChangeFixture { } } + let _ = file_id; + let root = match current_source_root_kind { SourceRootKind::Local => SourceRoot::new_local(mem::take(&mut file_set)), SourceRootKind::Library => SourceRoot::new_library(mem::take(&mut file_set)), diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 65ea685b37c60..246b92230bafb 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -17,7 +17,7 @@ use std::path::{Path, PathBuf}; const ENTRY_LIMIT: usize = 900; // FIXME: The following limits should be reduced eventually. -const ISSUES_ENTRY_LIMIT: usize = 1733; +const ISSUES_ENTRY_LIMIT: usize = 1734; const ROOT_ENTRY_LIMIT: usize = 861; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ diff --git a/tests/ui/borrowck/borrowck-assign-to-subfield.rs b/tests/ui/borrowck/borrowck-assign-to-subfield.rs index 807941d9c8547..3fc959428cae0 100644 --- a/tests/ui/borrowck/borrowck-assign-to-subfield.rs +++ b/tests/ui/borrowck/borrowck-assign-to-subfield.rs @@ -1,6 +1,7 @@ //@ run-pass //@ pretty-expanded FIXME #23616 +#[allow(unused)] pub fn main() { struct A { a: isize, diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs index c550af21f0753..641066b002273 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs @@ -13,13 +13,11 @@ struct Point { pub fn f() { let mut a = 1; - let mut c = Point{ x:1, y:0 }; + let mut c = Point { x: 1, y: 0 }; // Captured by value, but variable is dead on entry. (move || { - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x = 1; + c.x = 1; //~ WARN value captured by `c.x` is never read println!("{}", c.x); a = 1; //~ WARN value captured by `a` is never read println!("{}", a); @@ -27,10 +25,10 @@ pub fn f() { // Read and written to, but never actually used. (move || { - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x += 1; - a += 1; //~ WARN unused variable: `a` + c.x += 1; //~ WARN value captured by `c.x` is never read + //~| WARN value assigned to `c.x` is never read + a += 1; //~ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read })(); (move || { @@ -45,10 +43,7 @@ pub fn f() { let b = Box::new(42); (move || { println!("{}", c.x); - // Never read because this is FnOnce closure. - // This will not trigger a warning for unused variable as - // c.x will be treated as a Non-tracked place - c.x += 1; + c.x += 1; //~ WARN value assigned to `c.x` is never read println!("{}", a); a += 1; //~ WARN value assigned to `a` is never read drop(b); @@ -56,35 +51,32 @@ pub fn f() { } #[derive(Debug)] -struct MyStruct<'a> { - x: Option<& 'a str>, +struct MyStruct<'a> { + x: Option<&'a str>, y: i32, } pub fn nested() { - let mut a : Option<& str>; + let mut a: Option<&str>; a = None; - let mut b : Option<& str>; + let mut b: Option<&str>; b = None; - let mut d = MyStruct{ x: None, y: 1}; - let mut e = MyStruct{ x: None, y: 1}; + let mut d = MyStruct { x: None, y: 1 }; + let mut e = MyStruct { x: None, y: 1 }; (|| { (|| { - // This will not trigger a warning for unused variable as - // d.x will be treated as a Non-tracked place - d.x = Some("d1"); + d.x = Some("d1"); //~ WARN value assigned to `d.x` is never read d.x = Some("d2"); a = Some("d1"); //~ WARN value assigned to `a` is never read a = Some("d2"); })(); (move || { - // This will not trigger a warning for unused variable as - //e.x will be treated as a Non-tracked place - e.x = Some("e1"); - e.x = Some("e2"); - b = Some("e1"); //~ WARN value assigned to `b` is never read - //~| WARN unused variable: `b` - b = Some("e2"); //~ WARN value assigned to `b` is never read + e.x = Some("e1"); //~ WARN value captured by `e.x` is never read + //~| WARN value assigned to `e.x` is never read + e.x = Some("e2"); //~ WARN value assigned to `e.x` is never read + b = Some("e1"); //~ WARN value captured by `b` is never read + //~| WARN value assigned to `b` is never read + b = Some("e2"); //~ WARN value assigned to `b` is never read })(); })(); } diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr index cf414adc0b943..f697662d3ee93 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness.stderr @@ -1,10 +1,10 @@ -warning: value captured by `a` is never read - --> $DIR/liveness.rs:24:9 +warning: value assigned to `c.x` is never read + --> $DIR/liveness.rs:46:9 | -LL | a = 1; - | ^ +LL | c.x += 1; + | ^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? note: the lint level is defined here --> $DIR/liveness.rs:5:9 | @@ -12,54 +12,125 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `a` - --> $DIR/liveness.rs:33:9 +warning: value assigned to `a` is never read + --> $DIR/liveness.rs:48:9 + | +LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c.x` is never read + --> $DIR/liveness.rs:28:9 + | +LL | c.x += 1; + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `c.x` is never read + --> $DIR/liveness.rs:28:9 + | +LL | c.x += 1; + | ^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness.rs:30:9 | LL | a += 1; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: value assigned to `a` is never read - --> $DIR/liveness.rs:53:9 + --> $DIR/liveness.rs:30:9 | LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c.x` is never read + --> $DIR/liveness.rs:20:9 + | +LL | c.x = 1; + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value captured by `a` is never read + --> $DIR/liveness.rs:22:9 + | +LL | a = 1; | ^ | + = help: did you mean to capture by reference instead? + +warning: value captured by `e.x` is never read + --> $DIR/liveness.rs:74:13 + | +LL | e.x = Some("e1"); + | ^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `e.x` is never read + --> $DIR/liveness.rs:74:13 + | +LL | e.x = Some("e1"); + | ^^^^^^^^^^^^^^^^ + | = help: maybe it is overwritten before being read? -warning: value assigned to `a` is never read +warning: value assigned to `e.x` is never read + --> $DIR/liveness.rs:76:13 + | +LL | e.x = Some("e2"); + | ^^^^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `b` is never read --> $DIR/liveness.rs:77:13 | -LL | a = Some("d1"); +LL | b = Some("e1"); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `b` is never read - --> $DIR/liveness.rs:85:13 + --> $DIR/liveness.rs:77:13 | LL | b = Some("e1"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `b` is never read - --> $DIR/liveness.rs:87:13 + --> $DIR/liveness.rs:79:13 | LL | b = Some("e2"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `b` - --> $DIR/liveness.rs:85:13 +warning: value assigned to `d.x` is never read + --> $DIR/liveness.rs:68:13 | -LL | b = Some("e1"); - | ^ +LL | d.x = Some("d1"); + | ^^^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? + +warning: value assigned to `a` is never read + --> $DIR/liveness.rs:70:13 + | +LL | a = Some("d1"); + | ^^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? -warning: 7 warnings emitted +warning: 16 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs index bcd4d1090a411..69b0a3eb7a5fc 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.rs @@ -12,14 +12,16 @@ struct MyStruct { pub fn unintentional_copy_one() { let mut a = 1; - let mut last = MyStruct{ a: 1, b: 1}; + //~^ unused variable: `a` + let mut last = MyStruct { a: 1, b: 1 }; + //~^ unused variable: `last` let mut f = move |s| { - // This will not trigger a warning for unused variable - // as last.a will be treated as a Non-tracked place last.a = s; + //~^ WARN value captured by `last.a` is never read + //~| WARN value assigned to `last.a` is never read a = s; - //~^ WARN value assigned to `a` is never read - //~| WARN unused variable: `a` + //~^ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read }; f(2); f(3); @@ -28,12 +30,16 @@ pub fn unintentional_copy_one() { pub fn unintentional_copy_two() { let mut a = 1; - let mut sum = MyStruct{ a: 1, b: 0}; + //~^ WARN unused variable: `a` + let mut sum = MyStruct { a: 1, b: 0 }; + //~^ WARN unused variable: `sum` (1..10).for_each(move |x| { - // This will not trigger a warning for unused variable - // as sum.b will be treated as a Non-tracked place sum.b += x; - a += x; //~ WARN unused variable: `a` + //~^ WARN value captured by `sum.b` is never read + //~| WARN value assigned to `sum.b` is never read + a += x; + //~^ WARN value captured by `a` is never read + //~| WARN value assigned to `a` is never read }); } diff --git a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr index 0410de4c7982a..35b6d547eece0 100644 --- a/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr +++ b/tests/ui/closures/2229_closure_analysis/diagnostics/liveness_unintentional_copy.stderr @@ -1,10 +1,10 @@ -warning: value assigned to `a` is never read - --> $DIR/liveness_unintentional_copy.rs:20:9 +warning: value captured by `last.a` is never read + --> $DIR/liveness_unintentional_copy.rs:19:9 | -LL | a = s; - | ^ +LL | last.a = s; + | ^^^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? note: the lint level is defined here --> $DIR/liveness_unintentional_copy.rs:4:9 | @@ -12,22 +12,87 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `a` - --> $DIR/liveness_unintentional_copy.rs:20:9 +warning: value assigned to `last.a` is never read + --> $DIR/liveness_unintentional_copy.rs:19:9 + | +LL | last.a = s; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness_unintentional_copy.rs:22:9 | LL | a = s; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + +warning: value assigned to `a` is never read + --> $DIR/liveness_unintentional_copy.rs:22:9 + | +LL | a = s; + | ^^^^^ + | + = help: maybe it is overwritten before being read? warning: unused variable: `a` - --> $DIR/liveness_unintentional_copy.rs:36:9 + --> $DIR/liveness_unintentional_copy.rs:14:9 + | +LL | let mut a = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_a` + | + = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + +warning: unused variable: `last` + --> $DIR/liveness_unintentional_copy.rs:16:9 + | +LL | let mut last = MyStruct { a: 1, b: 1 }; + | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_last` + +warning: value captured by `sum.b` is never read + --> $DIR/liveness_unintentional_copy.rs:37:9 + | +LL | sum.b += x; + | ^^^^^ + | + = help: did you mean to capture by reference instead? + +warning: value assigned to `sum.b` is never read + --> $DIR/liveness_unintentional_copy.rs:37:9 + | +LL | sum.b += x; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `a` is never read + --> $DIR/liveness_unintentional_copy.rs:40:9 | LL | a += x; | ^ | = help: did you mean to capture by reference instead? -warning: 3 warnings emitted +warning: value assigned to `a` is never read + --> $DIR/liveness_unintentional_copy.rs:40:9 + | +LL | a += x; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `a` + --> $DIR/liveness_unintentional_copy.rs:32:9 + | +LL | let mut a = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_a` + +warning: unused variable: `sum` + --> $DIR/liveness_unintentional_copy.rs:34:9 + | +LL | let mut sum = MyStruct { a: 1, b: 0 }; + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_sum` + +warning: 12 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/issue-87987.rs b/tests/ui/closures/2229_closure_analysis/issue-87987.rs index f79a8f1b57100..b599af2e6ccee 100644 --- a/tests/ui/closures/2229_closure_analysis/issue-87987.rs +++ b/tests/ui/closures/2229_closure_analysis/issue-87987.rs @@ -9,6 +9,7 @@ struct Props { fn main() { // Test 1 let props_2 = Props { field_1: 1, field_2: 1 }; + //~^ WARN unused variable: `props_2` let _ = || { let _: Props = props_2; diff --git a/tests/ui/closures/2229_closure_analysis/issue-87987.stderr b/tests/ui/closures/2229_closure_analysis/issue-87987.stderr index 5696a010c3f80..e141eaa97768b 100644 --- a/tests/ui/closures/2229_closure_analysis/issue-87987.stderr +++ b/tests/ui/closures/2229_closure_analysis/issue-87987.stderr @@ -1,3 +1,11 @@ +warning: unused variable: `props_2` + --> $DIR/issue-87987.rs:11:9 + | +LL | let props_2 = Props { field_1: 1, field_2: 1 }; + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_props_2` + | + = note: `#[warn(unused_variables)]` on by default + warning: fields `field_1` and `field_2` are never read --> $DIR/issue-87987.rs:5:5 | @@ -10,5 +18,5 @@ LL | field_2: u32, | = note: `#[warn(dead_code)]` on by default -warning: 1 warning emitted +warning: 2 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr index cf8bd7a0a2765..40274c88318c3 100644 --- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr +++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure-pattern-closure-within-closure.stderr @@ -1,8 +1,8 @@ -warning: unused variable: `g2` - --> $DIR/destructure-pattern-closure-within-closure.rs:10:17 +warning: unused variable: `t2` + --> $DIR/destructure-pattern-closure-within-closure.rs:13:21 | -LL | let (_, g2) = g; - | ^^ help: if this is intentional, prefix it with an underscore: `_g2` +LL | let (_, t2) = t; + | ^^ help: if this is intentional, prefix it with an underscore: `_t2` | note: the lint level is defined here --> $DIR/destructure-pattern-closure-within-closure.rs:3:9 @@ -11,11 +11,11 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` -warning: unused variable: `t2` - --> $DIR/destructure-pattern-closure-within-closure.rs:13:21 +warning: unused variable: `g2` + --> $DIR/destructure-pattern-closure-within-closure.rs:10:17 | -LL | let (_, t2) = t; - | ^^ help: if this is intentional, prefix it with an underscore: `_t2` +LL | let (_, g2) = g; + | ^^ help: if this is intentional, prefix it with an underscore: `_g2` warning: 2 warnings emitted diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs index 6d6779ca6bd20..4a26ec07c9d9c 100644 --- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs +++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.rs @@ -43,6 +43,7 @@ fn test3() { fn test4() { let t = (String::from("Hello"), String::from("World")); + //~^ WARN unused variable: `t` let c = || { let (_, _) = t; @@ -80,7 +81,9 @@ fn test7() { fn test8() { let x = 0; + //~^ WARN unused variable: `x` let tup = (1, 2); + //~^ WARN unused variable: `tup` let p = Point { x: 10, y: 20 }; let c = || { diff --git a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr index 7706f68ba5b44..6523f2b34d537 100644 --- a/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr +++ b/tests/ui/closures/2229_closure_analysis/run_pass/destructure_patterns.stderr @@ -29,11 +29,29 @@ warning: unused variable: `t2` LL | let (_, t2) = t; | ^^ help: if this is intentional, prefix it with an underscore: `_t2` +warning: unused variable: `t` + --> $DIR/destructure_patterns.rs:45:9 + | +LL | let t = (String::from("Hello"), String::from("World")); + | ^ help: if this is intentional, prefix it with an underscore: `_t` + warning: unused variable: `x` - --> $DIR/destructure_patterns.rs:88:21 + --> $DIR/destructure_patterns.rs:91:21 | LL | let Point { x, y } = p; | ^ help: try ignoring the field: `x: _` -warning: 5 warnings emitted +warning: unused variable: `x` + --> $DIR/destructure_patterns.rs:83:9 + | +LL | let x = 0; + | ^ help: if this is intentional, prefix it with an underscore: `_x` + +warning: unused variable: `tup` + --> $DIR/destructure_patterns.rs:85:9 + | +LL | let tup = (1, 2); + | ^^^ help: if this is intentional, prefix it with an underscore: `_tup` + +warning: 8 warnings emitted diff --git a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs index 39dd6cb031f79..da25c0146c543 100644 --- a/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs +++ b/tests/ui/const-generics/defaults/trait_object_lt_defaults.rs @@ -1,6 +1,6 @@ //@ aux-build:trait_object_lt_defaults_lib.rs //@ run-pass -#![allow(dead_code)] +#![allow(dead_code, unused)] extern crate trait_object_lt_defaults_lib; // Tests that `A<'a, 3, dyn Test>` is short for `A<'a, 3, dyn Test + 'a>` diff --git a/tests/ui/issues/issue-11958.rs b/tests/ui/issues/issue-11958.rs index 9185c5158af64..6291f5e4033a4 100644 --- a/tests/ui/issues/issue-11958.rs +++ b/tests/ui/issues/issue-11958.rs @@ -5,7 +5,8 @@ pub fn main() { let mut x = 1; + //~^ WARN unused variable: `x` let _thunk = Box::new(move|| { x = 2; }); - //~^ WARN value assigned to `x` is never read - //~| WARN unused variable: `x` + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } diff --git a/tests/ui/issues/issue-11958.stderr b/tests/ui/issues/issue-11958.stderr index 5dca4c2f01d56..f593057362477 100644 --- a/tests/ui/issues/issue-11958.stderr +++ b/tests/ui/issues/issue-11958.stderr @@ -1,20 +1,27 @@ -warning: value assigned to `x` is never read - --> $DIR/issue-11958.rs:8:36 +warning: value captured by `x` is never read + --> $DIR/issue-11958.rs:9:36 | LL | let _thunk = Box::new(move|| { x = 2; }); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `x` - --> $DIR/issue-11958.rs:8:36 +warning: value assigned to `x` is never read + --> $DIR/issue-11958.rs:9:36 | LL | let _thunk = Box::new(move|| { x = 2; }); - | ^ + | ^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `x` + --> $DIR/issue-11958.rs:7:9 + | +LL | let mut x = 1; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` | - = help: did you mean to capture by reference instead? = note: `#[warn(unused_variables)]` on by default -warning: 2 warnings emitted +warning: 3 warnings emitted diff --git a/tests/ui/issues/issue-24353.rs b/tests/ui/issues/issue-24353.rs index 369fc238d1173..c754b9d631a4e 100644 --- a/tests/ui/issues/issue-24353.rs +++ b/tests/ui/issues/issue-24353.rs @@ -4,5 +4,6 @@ fn main() { return (); let x = (); + //~^ WARN unused variable: `x` x } diff --git a/tests/ui/issues/issue-24353.stderr b/tests/ui/issues/issue-24353.stderr new file mode 100644 index 0000000000000..bd7699a1bbb0c --- /dev/null +++ b/tests/ui/issues/issue-24353.stderr @@ -0,0 +1,10 @@ +warning: unused variable: `x` + --> $DIR/issue-24353.rs:6:9 + | +LL | let x = (); + | ^ help: if this is intentional, prefix it with an underscore: `_x` + | + = note: `#[warn(unused_variables)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/lint/dead-code/issue-85071-2.rs b/tests/ui/lint/dead-code/issue-85071-2.rs index 5db8735899410..86a8d70a4e4ba 100644 --- a/tests/ui/lint/dead-code/issue-85071-2.rs +++ b/tests/ui/lint/dead-code/issue-85071-2.rs @@ -18,5 +18,4 @@ fn main() { let x = s.f(); //~^ WARNING: unused variable: `x` let _y = x; - //~^ WARNING: unreachable definition } diff --git a/tests/ui/lint/dead-code/issue-85071-2.stderr b/tests/ui/lint/dead-code/issue-85071-2.stderr index 5e963183d094b..d30dfc178bfec 100644 --- a/tests/ui/lint/dead-code/issue-85071-2.stderr +++ b/tests/ui/lint/dead-code/issue-85071-2.stderr @@ -1,23 +1,3 @@ -warning: unreachable definition - --> $DIR/issue-85071-2.rs:20:9 - | -LL | let x = s.f(); - | ----- any code following this expression is unreachable -LL | -LL | let _y = x; - | ^^ unreachable definition - | -note: this expression has type `Foo`, which is uninhabited - --> $DIR/issue-85071-2.rs:18:13 - | -LL | let x = s.f(); - | ^^^^^ -note: the lint level is defined here - --> $DIR/issue-85071-2.rs:7:26 - | -LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ - warning: unused variable: `x` --> $DIR/issue-85071-2.rs:18:9 | @@ -30,5 +10,5 @@ note: the lint level is defined here LL | #![warn(unused_variables,unreachable_code)] | ^^^^^^^^^^^^^^^^ -warning: 2 warnings emitted +warning: 1 warning emitted diff --git a/tests/ui/lint/dead-code/issue-85071.rs b/tests/ui/lint/dead-code/issue-85071.rs index 84f2c9fc74eeb..033cc75f47c67 100644 --- a/tests/ui/lint/dead-code/issue-85071.rs +++ b/tests/ui/lint/dead-code/issue-85071.rs @@ -15,5 +15,4 @@ fn main() { let x = f(); //~^ WARNING: unused variable: `x` let _ = x; - //~^ WARNING: unreachable expression } diff --git a/tests/ui/lint/dead-code/issue-85071.stderr b/tests/ui/lint/dead-code/issue-85071.stderr index 721fb8148d96b..8236c725624d0 100644 --- a/tests/ui/lint/dead-code/issue-85071.stderr +++ b/tests/ui/lint/dead-code/issue-85071.stderr @@ -1,23 +1,3 @@ -warning: unreachable expression - --> $DIR/issue-85071.rs:17:13 - | -LL | let x = f(); - | --- any code following this expression is unreachable -LL | -LL | let _ = x; - | ^ unreachable expression - | -note: this expression has type `Foo`, which is uninhabited - --> $DIR/issue-85071.rs:15:13 - | -LL | let x = f(); - | ^^^ -note: the lint level is defined here - --> $DIR/issue-85071.rs:9:26 - | -LL | #![warn(unused_variables,unreachable_code)] - | ^^^^^^^^^^^^^^^^ - warning: unused variable: `x` --> $DIR/issue-85071.rs:15:9 | @@ -30,5 +10,5 @@ note: the lint level is defined here LL | #![warn(unused_variables,unreachable_code)] | ^^^^^^^^^^^^^^^^ -warning: 2 warnings emitted +warning: 1 warning emitted diff --git a/tests/ui/lint/future-incompat-json-test.stderr b/tests/ui/lint/future-incompat-json-test.stderr index bf29b9577db5e..2e7d0cf626f1d 100644 --- a/tests/ui/lint/future-incompat-json-test.stderr +++ b/tests/ui/lint/future-incompat-json-test.stderr @@ -1,4 +1,4 @@ -{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x` +{"$message_type":"future_incompat","future_incompat_report":[{"diagnostic":{"$message_type":"diagnostic","message":"unused variable: `x`","code":{"code":"unused_variables","explanation":null},"level":"warning","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"if this is intentional, prefix it with an underscore","code":null,"level":"help","spans":[{"file_name":"$DIR/future-incompat-json-test.rs","byte_start":340,"byte_end":341,"line_start":9,"line_end":9,"column_start":9,"column_end":10,"is_primary":true,"text":[{"text":" let x = 1;","highlight_start":9,"highlight_end":10}],"label":null,"suggested_replacement":"_x","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"warning: unused variable: `x` --> $DIR/future-incompat-json-test.rs:9:9 | LL | let x = 1; diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs index 8e74531e7762a..5b5843a8ddbb6 100644 --- a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs +++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs @@ -13,4 +13,5 @@ macro_rules! pat { fn main() { let pat!(value) = Value { value: () }; + //~^ WARN value assigned to `value` is never read } diff --git a/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr new file mode 100644 index 0000000000000..e30d433cdfa38 --- /dev/null +++ b/tests/ui/lint/issue-49588-non-shorthand-field-patterns-in-pattern-macro.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `value` is never read + --> $DIR/issue-49588-non-shorthand-field-patterns-in-pattern-macro.rs:15:14 + | +LL | let pat!(value) = Value { value: () }; + | ^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/lint/lint-uppercase-variables.stderr b/tests/ui/lint/lint-uppercase-variables.stderr index 9220828014fda..42ec9364bc6e6 100644 --- a/tests/ui/lint/lint-uppercase-variables.stderr +++ b/tests/ui/lint/lint-uppercase-variables.stderr @@ -12,6 +12,12 @@ error[E0170]: pattern binding `Foo` is named the same as one of the variants of LL | let Foo = foo::Foo::Foo; | ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo` +error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo` + --> $DIR/lint-uppercase-variables.rs:33:17 + | +LL | fn in_param(Foo: foo::Foo) {} + | ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo` + warning: unused variable: `Foo` --> $DIR/lint-uppercase-variables.rs:22:9 | @@ -31,12 +37,6 @@ warning: unused variable: `Foo` LL | let Foo = foo::Foo::Foo; | ^^^ help: if this is intentional, prefix it with an underscore: `_Foo` -error[E0170]: pattern binding `Foo` is named the same as one of the variants of the type `foo::Foo` - --> $DIR/lint-uppercase-variables.rs:33:17 - | -LL | fn in_param(Foo: foo::Foo) {} - | ^^^ help: to match on the variant, qualify the path: `foo::Foo::Foo` - warning: unused variable: `Foo` --> $DIR/lint-uppercase-variables.rs:33:17 | diff --git a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr index 817e16fdcaa06..f116d4b6b2fb9 100644 --- a/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr +++ b/tests/ui/lint/rfc-2383-lint-reason/expect_lint_from_macro.stderr @@ -2,11 +2,19 @@ warning: unused variable: `x` --> $DIR/expect_lint_from_macro.rs:9:13 | LL | let x = 0; - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ ... LL | trigger_unused_variables_macro!(); | --------------------------------- in this macro invocation | +help: `x` is captured in macro and introduced a unused variable + --> $DIR/expect_lint_from_macro.rs:9:13 + | +LL | let x = 0; + | ^ +... +LL | trigger_unused_variables_macro!(); + | --------------------------------- in this macro invocation note: the lint level is defined here --> $DIR/expect_lint_from_macro.rs:5:9 | @@ -18,11 +26,19 @@ warning: unused variable: `x` --> $DIR/expect_lint_from_macro.rs:9:13 | LL | let x = 0; - | ^ help: if this is intentional, prefix it with an underscore: `_x` + | ^ ... LL | trigger_unused_variables_macro!(); | --------------------------------- in this macro invocation | +help: `x` is captured in macro and introduced a unused variable + --> $DIR/expect_lint_from_macro.rs:9:13 + | +LL | let x = 0; + | ^ +... +LL | trigger_unused_variables_macro!(); + | --------------------------------- in this macro invocation = note: this warning originates in the macro `trigger_unused_variables_macro` (in Nightly builds, run with -Z macro-backtrace for more info) warning: 2 warnings emitted diff --git a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr index 169f03aed9417..20be60a23c63c 100644 --- a/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr +++ b/tests/ui/lint/rfc-2383-lint-reason/force_warn_expected_lints_fulfilled.stderr @@ -1,3 +1,13 @@ +warning: variable does not need to be mutable + --> $DIR/force_warn_expected_lints_fulfilled.rs:32:9 + | +LL | let mut what_does_the_fox_say = "*ding* *deng* *dung*"; + | ----^^^^^^^^^^^^^^^^^^^^^ + | | + | help: remove this `mut` + | + = note: requested on the command line with `--force-warn unused-mut` + warning: unused variable: `x` --> $DIR/force_warn_expected_lints_fulfilled.rs:20:9 | @@ -12,16 +22,6 @@ warning: unused variable: `fox_name` LL | let fox_name = "Sir Nibbles"; | ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_fox_name` -warning: variable does not need to be mutable - --> $DIR/force_warn_expected_lints_fulfilled.rs:32:9 - | -LL | let mut what_does_the_fox_say = "*ding* *deng* *dung*"; - | ----^^^^^^^^^^^^^^^^^^^^^ - | | - | help: remove this `mut` - | - = note: requested on the command line with `--force-warn unused-mut` - warning: unused variable: `this_should_fulfill_the_expectation` --> $DIR/force_warn_expected_lints_fulfilled.rs:43:9 | diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs index eea0f4c594dc8..3d622b6991300 100644 --- a/tests/ui/lint/unused/issue-117284-arg-in-macro.rs +++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.rs @@ -1,7 +1,7 @@ #![deny(unused_variables)] macro_rules! make_var { ($struct:ident, $var:ident) => { - let $var = $struct.$var; + let $var = $struct.$var; //~ ERROR unused variable: `var` }; } @@ -12,6 +12,6 @@ struct MyStruct { fn main() { let s = MyStruct { var: 42 }; - make_var!(s, var); //~ ERROR unused variable: `var` + make_var!(s, var); let a = 1; //~ ERROR unused variable: `a` } diff --git a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr index 84efaa4f3687b..b4a6871713c06 100644 --- a/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr +++ b/tests/ui/lint/unused/issue-117284-arg-in-macro.stderr @@ -1,8 +1,11 @@ error: unused variable: `var` - --> $DIR/issue-117284-arg-in-macro.rs:15:18 + --> $DIR/issue-117284-arg-in-macro.rs:4:13 | +LL | let $var = $struct.$var; + | ^^^^ +... LL | make_var!(s, var); - | ^^^ + | ----------------- in this macro invocation | help: `var` is captured in macro and introduced a unused variable --> $DIR/issue-117284-arg-in-macro.rs:4:13 diff --git a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr index fe2e3afc82ec8..c378b307b8b54 100644 --- a/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr +++ b/tests/ui/lint/unused/issue-47390-unused-variable-in-struct-pattern.stderr @@ -1,27 +1,45 @@ -warning: unused variable: `i_think_continually` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9 +warning: variable does not need to be mutable + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 | -LL | let i_think_continually = 2; - | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually` +LL | let mut mut_unused_var = 1; + | ----^^^^^^^^^^^^^^ + | | + | help: remove this `mut` | note: the lint level is defined here --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:5:9 | LL | #![warn(unused)] // UI tests pass `-A unused` (#43896) | ^^^^^^ + = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]` + +warning: variable does not need to be mutable + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 + | +LL | let (mut var, unused_var) = (1, 2); + | ----^^^ + | | + | help: remove this `mut` + +warning: unused variable: `i_think_continually` + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:26:9 + | +LL | let i_think_continually = 2; + | ^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_i_think_continually` + | = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: unused variable: `mut_unused_var` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:13 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 | LL | let mut mut_unused_var = 1; - | ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var` + | ^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_mut_unused_var` warning: unused variable: `var` - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:14 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 | LL | let (mut var, unused_var) = (1, 2); - | ^^^ help: if this is intentional, prefix it with an underscore: `_var` + | ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_var` warning: unused variable: `unused_var` --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:19 @@ -36,22 +54,13 @@ LL | if let SoulHistory { corridors_of_light, | ^^^^^^^^^^^^^^^^^^ help: try ignoring the field: `corridors_of_light: _` warning: variable `hours_are_suns` is assigned to, but never used - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:30 + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:46:26 | LL | mut hours_are_suns, - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ | = note: consider using `_hours_are_suns` instead -warning: value assigned to `hours_are_suns` is never read - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9 - | -LL | hours_are_suns = false; - | ^^^^^^^^^^^^^^ - | - = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` - warning: unused variable: `fire` --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:52:32 | @@ -94,23 +103,14 @@ warning: unused variable: `case` LL | Tuple(Large::Suit { case }, ()) => {} | ^^^^ help: try ignoring the field: `case: _` -warning: variable does not need to be mutable - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:33:9 - | -LL | let mut mut_unused_var = 1; - | ----^^^^^^^^^^^^^^ - | | - | help: remove this `mut` +warning: value assigned to `hours_are_suns` is never read + --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:48:9 | - = note: `#[warn(unused_mut)]` implied by `#[warn(unused)]` - -warning: variable does not need to be mutable - --> $DIR/issue-47390-unused-variable-in-struct-pattern.rs:37:10 +LL | hours_are_suns = false; + | ^^^^^^^^^^^^^^^^^^^^^^ | -LL | let (mut var, unused_var) = (1, 2); - | ----^^^ - | | - | help: remove this `mut` + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` warning: 16 warnings emitted diff --git a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr index f2e6168998c44..c501aa25f1352 100644 --- a/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr +++ b/tests/ui/lint/unused/issue-54180-unused-ref-field.stderr @@ -11,6 +11,12 @@ LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` +error: unused variable: `x` + --> $DIR/issue-54180-unused-ref-field.rs:29:45 + | +LL | let _: i32 = points.iter().map(|Point { x, y }| y).sum(); + | ^ help: try ignoring the field: `x: _` + error: unused variable: `f1` --> $DIR/issue-54180-unused-ref-field.rs:26:13 | @@ -23,11 +29,5 @@ error: unused variable: `x` LL | Point { y, ref mut x } => y, | ^^^^^^^^^ help: try ignoring the field: `x: _` -error: unused variable: `x` - --> $DIR/issue-54180-unused-ref-field.rs:29:45 - | -LL | let _: i32 = points.iter().map(|Point { x, y }| y).sum(); - | ^ help: try ignoring the field: `x: _` - error: aborting due to 4 previous errors diff --git a/tests/ui/lint/unused/lint-unused-variables.stderr b/tests/ui/lint/unused/lint-unused-variables.stderr index 09729eeba7923..dc60ef374c0a8 100644 --- a/tests/ui/lint/unused/lint-unused-variables.stderr +++ b/tests/ui/lint/unused/lint-unused-variables.stderr @@ -10,18 +10,18 @@ note: the lint level is defined here LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ -error: unused variable: `a` - --> $DIR/lint-unused-variables.rs:22:9 - | -LL | a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - error: unused variable: `b` --> $DIR/lint-unused-variables.rs:14:5 | LL | b: i32, | ^ help: if this is intentional, prefix it with an underscore: `_b` +error: unused variable: `a` + --> $DIR/lint-unused-variables.rs:22:9 + | +LL | a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: unused variable: `b` --> $DIR/lint-unused-variables.rs:29:9 | @@ -58,17 +58,17 @@ error: unused variable: `b` LL | b: i32, | ^ help: if this is intentional, prefix it with an underscore: `_b` -error: unused variable: `a` - --> $DIR/lint-unused-variables.rs:68:9 - | -LL | a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - error: unused variable: `b` --> $DIR/lint-unused-variables.rs:74:9 | LL | b: i32, | ^ help: if this is intentional, prefix it with an underscore: `_b` +error: unused variable: `a` + --> $DIR/lint-unused-variables.rs:68:9 + | +LL | a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: aborting due to 11 previous errors diff --git a/tests/ui/liveness/liveness-asm.stderr b/tests/ui/liveness/liveness-asm.stderr index 57d89e44dcb86..ca1c07046d0ef 100644 --- a/tests/ui/liveness/liveness-asm.stderr +++ b/tests/ui/liveness/liveness-asm.stderr @@ -1,8 +1,8 @@ warning: value assigned to `src` is never read - --> $DIR/liveness-asm.rs:14:32 + --> $DIR/liveness-asm.rs:14:5 | LL | asm!("/*{0}*/", inout(reg) src); - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -12,10 +12,10 @@ LL | #![warn(unused_assignments)] | ^^^^^^^^^^^^^^^^^^ warning: value assigned to `src` is never read - --> $DIR/liveness-asm.rs:24:39 + --> $DIR/liveness-asm.rs:24:5 | LL | asm!("/*{0}*/", inout(reg) src => src); - | ^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? diff --git a/tests/ui/liveness/liveness-consts.rs b/tests/ui/liveness/liveness-consts.rs index 40d30fb9113a9..7e56bf8c2cda4 100644 --- a/tests/ui/liveness/liveness-consts.rs +++ b/tests/ui/liveness/liveness-consts.rs @@ -4,16 +4,18 @@ pub static A: i32 = { let mut i = 0; - let mut a = 0; //~ WARN variable `a` is assigned to, but never used + let mut a = 0; + //~^ WARN variable `a` is assigned to, but never used while i < 10 { i += 1; a += 1; + //~^ WARN value assigned to `a` is never read } i }; pub const B: u32 = { - let mut b = 1; + let mut b = 1; //~ WARN value assigned to `b` is never read b += 1; //~ WARN value assigned to `b` is never read b = 42; b diff --git a/tests/ui/liveness/liveness-consts.stderr b/tests/ui/liveness/liveness-consts.stderr index 34ce39473379e..2d2750fbaddff 100644 --- a/tests/ui/liveness/liveness-consts.stderr +++ b/tests/ui/liveness/liveness-consts.stderr @@ -1,5 +1,5 @@ warning: unused variable: `e` - --> $DIR/liveness-consts.rs:24:13 + --> $DIR/liveness-consts.rs:26:13 | LL | let e = 1; | ^ help: if this is intentional, prefix it with an underscore: `_e` @@ -12,53 +12,69 @@ LL | #![warn(unused)] = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` warning: unused variable: `s` - --> $DIR/liveness-consts.rs:33:24 + --> $DIR/liveness-consts.rs:35:24 | LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8; { let z = 18; 100 }] { | ^ help: if this is intentional, prefix it with an underscore: `_s` warning: unused variable: `z` - --> $DIR/liveness-consts.rs:33:55 + --> $DIR/liveness-consts.rs:35:55 | LL | pub fn f(x: [u8; { let s = 17; 100 }]) -> [u8; { let z = 18; 100 }] { | ^ help: if this is intentional, prefix it with an underscore: `_z` warning: variable `a` is assigned to, but never used - --> $DIR/liveness-consts.rs:7:13 + --> $DIR/liveness-consts.rs:7:9 | LL | let mut a = 0; - | ^ + | ^^^^^ | = note: consider using `_a` instead +warning: value assigned to `a` is never read + --> $DIR/liveness-consts.rs:11:9 + | +LL | a += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` + warning: value assigned to `b` is never read - --> $DIR/liveness-consts.rs:17:5 + --> $DIR/liveness-consts.rs:18:17 + | +LL | let mut b = 1; + | ^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `b` is never read + --> $DIR/liveness-consts.rs:19:5 | LL | b += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` warning: unused variable: `z` - --> $DIR/liveness-consts.rs:60:13 + --> $DIR/liveness-consts.rs:62:13 | LL | let z = 42; | ^ help: if this is intentional, prefix it with an underscore: `_z` warning: value assigned to `t` is never read - --> $DIR/liveness-consts.rs:42:9 + --> $DIR/liveness-consts.rs:44:9 | LL | t = t + t; - | ^ + | ^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: unused variable: `w` - --> $DIR/liveness-consts.rs:49:13 + --> $DIR/liveness-consts.rs:51:13 | LL | let w = 10; | ^ help: if this is intentional, prefix it with an underscore: `_w` -warning: 8 warnings emitted +warning: 10 warnings emitted diff --git a/tests/ui/liveness/liveness-dead.stderr b/tests/ui/liveness/liveness-dead.stderr index de6d5bd993d62..4f7dda49cd03e 100644 --- a/tests/ui/liveness/liveness-dead.stderr +++ b/tests/ui/liveness/liveness-dead.stderr @@ -1,8 +1,8 @@ error: value assigned to `x` is never read - --> $DIR/liveness-dead.rs:9:13 + --> $DIR/liveness-dead.rs:9:24 | LL | let mut x: isize = 3; - | ^ + | ^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -15,15 +15,15 @@ error: value assigned to `x` is never read --> $DIR/liveness-dead.rs:17:5 | LL | x = 4; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? error: value passed to `x` is never read - --> $DIR/liveness-dead.rs:20:11 + --> $DIR/liveness-dead.rs:20:7 | LL | fn f4(mut x: i32) { - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? @@ -31,7 +31,7 @@ error: value assigned to `x` is never read --> $DIR/liveness-dead.rs:27:5 | LL | x = 4; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? diff --git a/tests/ui/liveness/liveness-unused.rs b/tests/ui/liveness/liveness-unused.rs index ba635e6638c8c..a781481055391 100644 --- a/tests/ui/liveness/liveness-unused.rs +++ b/tests/ui/liveness/liveness-unused.rs @@ -38,6 +38,7 @@ fn f3b() { //~^ ERROR variable `z` is assigned to, but never used loop { z += 4; + //~^ ERROR value assigned to `z` is never read } } @@ -45,6 +46,7 @@ fn f3b() { fn f3c() { let mut z = 3; loop { z += 4; } + //~^ ERROR value assigned to `z` is never read } #[allow(unused_variables)] @@ -54,6 +56,16 @@ fn f3d() { x += 4; } +fn f3e() { + let a = 13; + let mut z = 3; + //~^ ERROR variable `z` is assigned to, but never used + loop { + z += a; + //~^ ERROR value assigned to `z` is never read + } +} + fn f4() { match Some(3) { Some(i) => { @@ -67,7 +79,15 @@ enum tri { a(isize), b(isize), c(isize) } -fn f4b() -> isize { +fn f4b() { + match tri::a(3) { + tri::a(i) | tri::b(i) | tri::c(i) => { + //~^ ERROR unused variable: `i` + } + } +} + +fn f4c() -> isize { match tri::a(3) { tri::a(i) | tri::b(i) | tri::c(i) => { i @@ -75,6 +95,13 @@ fn f4b() -> isize { } } +fn f4d() { + match tri::a(3) { + tri::a(i) | tri::b(i) | tri::c(i) if i == 0 => {} + _ => {} + } +} + fn f5a() { for x in 1..10 { } //~^ ERROR unused variable: `x` @@ -137,5 +164,88 @@ fn f7() { drop(a); } +fn f8(a: u32) { + let _ = a; +} + +fn f9() { + let mut a = 10; + //~^ ERROR variable `a` is assigned to, but never used + let b = 13; + let c = 13; + let d = 13; + let e = 13; + let f = 13; + let g = 13; + let h = 13; + + a += b; + //~^ ERROR value assigned to `a` is never read + a -= c; + //~^ ERROR value assigned to `a` is never read + a *= d; + //~^ ERROR value assigned to `a` is never read + a /= e; + //~^ ERROR value assigned to `a` is never read + a |= f; + //~^ ERROR value assigned to `a` is never read + a &= g; + //~^ ERROR value assigned to `a` is never read + a %= h; + //~^ ERROR value assigned to `a` is never read +} + +fn f9b() { + let mut a = 10; + let b = 13; + let c = 13; + let d = 13; + let e = 13; + let f = 13; + let g = 13; + let h = 13; + + a += b; + a -= c; + a *= d; + a /= e; + a |= f; + a &= g; + a %= h; + + let _ = a; +} + +fn f9c() { + let mut a = 10.; + //~^ ERROR variable `a` is assigned to, but never used + let b = 13.; + let c = 13.; + let d = 13.; + let e = 13.; + let f = 13.; + + a += b; + //~^ ERROR value assigned to `a` is never read + a -= c; + //~^ ERROR value assigned to `a` is never read + a *= d; + //~^ ERROR value assigned to `a` is never read + a /= e; + //~^ ERROR value assigned to `a` is never read + a %= f; + //~^ ERROR value assigned to `a` is never read +} + +fn f10(mut a: T, b: T) { + //~^ ERROR variable `a` is assigned to, but never used + a = b; + //~^ ERROR value assigned to `a` is never read +} + +fn f10b(mut a: Box, b: Box) { + a = b; +} + fn main() { } diff --git a/tests/ui/liveness/liveness-unused.stderr b/tests/ui/liveness/liveness-unused.stderr index f6c478ddbc72c..24fa7d04b8538 100644 --- a/tests/ui/liveness/liveness-unused.stderr +++ b/tests/ui/liveness/liveness-unused.stderr @@ -1,5 +1,5 @@ warning: unreachable statement - --> $DIR/liveness-unused.rs:92:9 + --> $DIR/liveness-unused.rs:119:9 | LL | continue; | -------- any code following this expression is unreachable @@ -44,10 +44,10 @@ LL | let x = 3; | ^ help: if this is intentional, prefix it with an underscore: `_x` error: variable `x` is assigned to, but never used - --> $DIR/liveness-unused.rs:30:13 + --> $DIR/liveness-unused.rs:30:9 | LL | let mut x = 3; - | ^ + | ^^^^^ | = note: consider using `_x` instead @@ -55,7 +55,7 @@ error: value assigned to `x` is never read --> $DIR/liveness-unused.rs:32:5 | LL | x += 4; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? note: the lint level is defined here @@ -65,39 +65,82 @@ LL | #![deny(unused_assignments)] | ^^^^^^^^^^^^^^^^^^ error: variable `z` is assigned to, but never used - --> $DIR/liveness-unused.rs:37:13 + --> $DIR/liveness-unused.rs:37:9 | LL | let mut z = 3; - | ^ + | ^^^^^ | = note: consider using `_z` instead +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:40:9 + | +LL | z += 4; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:48:12 + | +LL | loop { z += 4; } + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `z` is assigned to, but never used + --> $DIR/liveness-unused.rs:61:9 + | +LL | let mut z = 3; + | ^^^^^ + | + = note: consider using `_z` instead + +error: value assigned to `z` is never read + --> $DIR/liveness-unused.rs:64:9 + | +LL | z += a; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + error: unused variable: `i` - --> $DIR/liveness-unused.rs:59:12 + --> $DIR/liveness-unused.rs:71:12 | LL | Some(i) => { | ^ help: if this is intentional, prefix it with an underscore: `_i` +error: unused variable: `i` + --> $DIR/liveness-unused.rs:84:14 + | +LL | tri::a(i) | tri::b(i) | tri::c(i) => { + | ^ ^ ^ + | +help: if this is intentional, prefix it with an underscore + | +LL | tri::a(_i) | tri::b(_i) | tri::c(_i) => { + | ~~ ~~ ~~ + error: unused variable: `x` - --> $DIR/liveness-unused.rs:79:9 + --> $DIR/liveness-unused.rs:106:9 | LL | for x in 1..10 { } | ^ help: if this is intentional, prefix it with an underscore: `_x` error: unused variable: `x` - --> $DIR/liveness-unused.rs:84:10 + --> $DIR/liveness-unused.rs:111:10 | LL | for (x, _) in [1, 2, 3].iter().enumerate() { } | ^ help: if this is intentional, prefix it with an underscore: `_x` error: unused variable: `x` - --> $DIR/liveness-unused.rs:89:13 + --> $DIR/liveness-unused.rs:116:13 | LL | for (_, x) in [1, 2, 3].iter().enumerate() { | ^ help: if this is intentional, prefix it with an underscore: `_x` error: variable `x` is assigned to, but never used - --> $DIR/liveness-unused.rs:112:9 + --> $DIR/liveness-unused.rs:139:9 | LL | let x; | ^ @@ -105,12 +148,140 @@ LL | let x; = note: consider using `_x` instead error: value assigned to `x` is never read - --> $DIR/liveness-unused.rs:116:9 + --> $DIR/liveness-unused.rs:143:9 | LL | x = 0; - | ^ + | ^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:172:9 + | +LL | let mut a = 10; + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:182:5 + | +LL | a += b; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:184:5 + | +LL | a -= c; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:186:5 + | +LL | a *= d; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:188:5 + | +LL | a /= e; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:190:5 + | +LL | a |= f; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:192:5 + | +LL | a &= g; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:194:5 + | +LL | a %= h; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:220:9 + | +LL | let mut a = 10.; + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:228:5 + | +LL | a += b; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:230:5 + | +LL | a -= c; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:232:5 + | +LL | a *= d; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:234:5 + | +LL | a /= e; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:236:5 + | +LL | a %= f; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +error: variable `a` is assigned to, but never used + --> $DIR/liveness-unused.rs:240:11 + | +LL | fn f10(mut a: T, b: T) { + | ^^^^^ + | + = note: consider using `_a` instead + +error: value assigned to `a` is never read + --> $DIR/liveness-unused.rs:242:5 + | +LL | a = b; + | ^ | = help: maybe it is overwritten before being read? -error: aborting due to 13 previous errors; 1 warning emitted +error: aborting due to 34 previous errors; 1 warning emitted diff --git a/tests/ui/liveness/liveness-upvars.rs b/tests/ui/liveness/liveness-upvars.rs index 7898b97888230..1f60d7d72eef1 100644 --- a/tests/ui/liveness/liveness-upvars.rs +++ b/tests/ui/liveness/liveness-upvars.rs @@ -7,8 +7,8 @@ pub fn unintentional_copy_one() { let mut last = None; let mut f = move |s| { - last = Some(s); //~ WARN value assigned to `last` is never read - //~| WARN unused variable: `last` + last = Some(s); //~ WARN value captured by `last` is never read + //~| WARN value assigned to `last` is never read }; f("a"); f("b"); @@ -19,7 +19,9 @@ pub fn unintentional_copy_one() { pub fn unintentional_copy_two() { let mut sum = 0; (1..10).for_each(move |x| { - sum += x; //~ WARN unused variable: `sum` + sum += x; + //~^ WARN value captured by `sum` is never read + //~| WARN value assigned to `sum` is never read }); dbg!(sum); } @@ -39,11 +41,14 @@ pub fn f() { // Read and written to, but never actually used. let _ = move || { - c += 1; //~ WARN unused variable: `c` + c += 1; + //~^ WARN value captured by `c` is never read + //~| WARN value assigned to `c` is never read }; let _ = async move { - c += 1; //~ WARN value assigned to `c` is never read - //~| WARN unused variable: `c` + c += 1; + //~^ WARN value captured by `c` is never read + //~| WARN value assigned to `c` is never read }; let _ = move || { @@ -74,8 +79,8 @@ pub fn nested() { d = Some("d2"); }; let _ = move || { - e = Some("e1"); //~ WARN value assigned to `e` is never read - //~| WARN unused variable: `e` + e = Some("e1"); //~ WARN value captured by `e` is never read + //~| WARN value assigned to `e` is never read e = Some("e2"); //~ WARN value assigned to `e` is never read }; }; @@ -84,7 +89,8 @@ pub fn nested() { pub fn g(mut v: T) { let _ = |r| { if r { - v = T::default(); //~ WARN value assigned to `v` is never read + v = T::default(); + //~^ WARN value assigned to `v` is never read } else { drop(v); } @@ -96,8 +102,8 @@ pub fn h() { let _ = move |b| { loop { if b { - z = T::default(); //~ WARN value assigned to `z` is never read - //~| WARN unused variable: `z` + z = T::default(); //~ WARN value captured by `z` is never read + //~| WARN value assigned to `z` is never read } else { return; } @@ -123,7 +129,7 @@ pub fn async_coroutine() { let _ = async move { state = 4; //~ WARN value assigned to `state` is never read - //~| WARN unused variable: `state` + //~| WARN value captured by `state` is never read yield_now().await; state = 5; //~ WARN value assigned to `state` is never read }; diff --git a/tests/ui/liveness/liveness-upvars.stderr b/tests/ui/liveness/liveness-upvars.stderr index 82f62371ec59d..cfed2830164ad 100644 --- a/tests/ui/liveness/liveness-upvars.stderr +++ b/tests/ui/liveness/liveness-upvars.stderr @@ -1,10 +1,10 @@ -warning: value assigned to `last` is never read +warning: value captured by `last` is never read --> $DIR/liveness-upvars.rs:10:9 | LL | last = Some(s); | ^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? note: the lint level is defined here --> $DIR/liveness-upvars.rs:4:9 | @@ -12,16 +12,15 @@ LL | #![warn(unused)] | ^^^^^^ = note: `#[warn(unused_assignments)]` implied by `#[warn(unused)]` -warning: unused variable: `last` +warning: value assigned to `last` is never read --> $DIR/liveness-upvars.rs:10:9 | LL | last = Some(s); - | ^^^^ + | ^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` implied by `#[warn(unused)]` + = help: maybe it is overwritten before being read? -warning: unused variable: `sum` +warning: value captured by `sum` is never read --> $DIR/liveness-upvars.rs:22:9 | LL | sum += x; @@ -29,24 +28,32 @@ LL | sum += x; | = help: did you mean to capture by reference instead? -warning: value captured by `c` is never read - --> $DIR/liveness-upvars.rs:32:9 +warning: value assigned to `sum` is never read + --> $DIR/liveness-upvars.rs:22:9 | -LL | c = 1; - | ^ +LL | sum += x; + | ^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: value captured by `c` is never read - --> $DIR/liveness-upvars.rs:36:9 +warning: value assigned to `c` is never read + --> $DIR/liveness-upvars.rs:69:9 | -LL | c = 1; - | ^ +LL | c += 1; + | ^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: unused variable: `c` - --> $DIR/liveness-upvars.rs:42:9 +warning: value assigned to `c` is never read + --> $DIR/liveness-upvars.rs:63:9 + | +LL | c += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:49:9 | LL | c += 1; | ^ @@ -54,15 +61,15 @@ LL | c += 1; = help: did you mean to capture by reference instead? warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:45:9 + --> $DIR/liveness-upvars.rs:49:9 | LL | c += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `c` - --> $DIR/liveness-upvars.rs:45:9 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:44:9 | LL | c += 1; | ^ @@ -70,116 +77,124 @@ LL | c += 1; = help: did you mean to capture by reference instead? warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:58:9 + --> $DIR/liveness-upvars.rs:44:9 | LL | c += 1; - | ^ + | ^^^^^^ | = help: maybe it is overwritten before being read? -warning: value assigned to `c` is never read - --> $DIR/liveness-upvars.rs:64:9 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:38:9 | -LL | c += 1; +LL | c = 1; | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? -warning: value assigned to `d` is never read - --> $DIR/liveness-upvars.rs:73:13 +warning: value captured by `c` is never read + --> $DIR/liveness-upvars.rs:34:9 | -LL | d = Some("d1"); +LL | c = 1; + | ^ + | + = help: did you mean to capture by reference instead? + +warning: value captured by `e` is never read + --> $DIR/liveness-upvars.rs:82:13 + | +LL | e = Some("e1"); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `e` is never read - --> $DIR/liveness-upvars.rs:77:13 + --> $DIR/liveness-upvars.rs:82:13 | LL | e = Some("e1"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `e` is never read - --> $DIR/liveness-upvars.rs:79:13 + --> $DIR/liveness-upvars.rs:84:13 | LL | e = Some("e2"); - | ^ + | ^^^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `e` - --> $DIR/liveness-upvars.rs:77:13 +warning: value assigned to `d` is never read + --> $DIR/liveness-upvars.rs:78:13 | -LL | e = Some("e1"); - | ^ +LL | d = Some("d1"); + | ^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? warning: value assigned to `v` is never read - --> $DIR/liveness-upvars.rs:87:13 + --> $DIR/liveness-upvars.rs:92:13 | LL | v = T::default(); | ^ | = help: maybe it is overwritten before being read? -warning: value assigned to `z` is never read - --> $DIR/liveness-upvars.rs:99:17 +warning: value captured by `z` is never read + --> $DIR/liveness-upvars.rs:105:17 | LL | z = T::default(); | ^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? -warning: unused variable: `z` - --> $DIR/liveness-upvars.rs:99:17 +warning: value assigned to `z` is never read + --> $DIR/liveness-upvars.rs:105:17 | LL | z = T::default(); - | ^ + | ^^^^^^^^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? -warning: value assigned to `state` is never read - --> $DIR/liveness-upvars.rs:125:9 +warning: value captured by `state` is never read + --> $DIR/liveness-upvars.rs:131:9 | LL | state = 4; | ^^^^^ | - = help: maybe it is overwritten before being read? + = help: did you mean to capture by reference instead? warning: value assigned to `state` is never read - --> $DIR/liveness-upvars.rs:128:9 + --> $DIR/liveness-upvars.rs:131:9 | -LL | state = 5; - | ^^^^^ +LL | state = 4; + | ^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: unused variable: `state` - --> $DIR/liveness-upvars.rs:125:9 +warning: value assigned to `state` is never read + --> $DIR/liveness-upvars.rs:134:9 | -LL | state = 4; - | ^^^^^ +LL | state = 5; + | ^^^^^^^^^ | - = help: did you mean to capture by reference instead? + = help: maybe it is overwritten before being read? warning: value assigned to `s` is never read - --> $DIR/liveness-upvars.rs:137:9 + --> $DIR/liveness-upvars.rs:143:9 | LL | s = 1; - | ^ + | ^^^^^ | = help: maybe it is overwritten before being read? warning: value assigned to `s` is never read - --> $DIR/liveness-upvars.rs:139:9 + --> $DIR/liveness-upvars.rs:145:9 | LL | s = yield (); - | ^ + | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? -warning: 22 warnings emitted +warning: 24 warnings emitted diff --git a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs index edbd9f35d4dfa..9deee6129ca76 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-default-to-static.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs index 986fc83679934..baf24696f41a4 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-ref-struct.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] use std::fmt::Display; diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs index 3c88f2b9f3765..5edbb24e53570 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-box.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs index 412695f708670..e2e4656cc9a0b 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-mut.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs index 591f843a284a2..29db02abc8f67 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr-struct.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] trait Test { fn foo(&self) { } diff --git a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs index bc47b8d46a11e..e0c8d0ee4c145 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-from-rptr.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] use std::fmt::Display; diff --git a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs index 53b9c4886450e..2f6e1bb06e29c 100644 --- a/tests/ui/object-lifetime/object-lifetime-default-inferred.rs +++ b/tests/ui/object-lifetime/object-lifetime-default-inferred.rs @@ -4,7 +4,7 @@ //@ pretty-expanded FIXME #23616 -#![allow(dead_code)] +#![allow(dead_code, unused)] #![feature(generic_arg_infer)] trait Test { diff --git a/tests/ui/parser/intersection-patterns-1.fixed b/tests/ui/parser/intersection-patterns-1.fixed index f63d57472cf10..7a2e74c2600df 100644 --- a/tests/ui/parser/intersection-patterns-1.fixed +++ b/tests/ui/parser/intersection-patterns-1.fixed @@ -9,6 +9,7 @@ //@ run-rustfix #![allow(unused_variables)] +#![allow(unused_assignments)] fn main() { let s: Option = None; diff --git a/tests/ui/parser/intersection-patterns-1.rs b/tests/ui/parser/intersection-patterns-1.rs index 3a457659aac2e..fb18f14840011 100644 --- a/tests/ui/parser/intersection-patterns-1.rs +++ b/tests/ui/parser/intersection-patterns-1.rs @@ -9,6 +9,7 @@ //@ run-rustfix #![allow(unused_variables)] +#![allow(unused_assignments)] fn main() { let s: Option = None; diff --git a/tests/ui/parser/intersection-patterns-1.stderr b/tests/ui/parser/intersection-patterns-1.stderr index dc968656c91ff..a760f58a1b97b 100644 --- a/tests/ui/parser/intersection-patterns-1.stderr +++ b/tests/ui/parser/intersection-patterns-1.stderr @@ -1,5 +1,5 @@ error: pattern on wrong side of `@` - --> $DIR/intersection-patterns-1.rs:17:9 + --> $DIR/intersection-patterns-1.rs:18:9 | LL | Some(x) @ y => {} | -------^^^- @@ -9,7 +9,7 @@ LL | Some(x) @ y => {} | help: switch the order: `y @ Some(x)` error: pattern on wrong side of `@` - --> $DIR/intersection-patterns-1.rs:27:9 + --> $DIR/intersection-patterns-1.rs:28:9 | LL | 1 ..= 5 @ e => {} | -------^^^- diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs index 3d26b5e87d93f..d766411e4f980 100644 --- a/tests/ui/pattern/bindings-after-at/bind-by-copy.rs +++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.rs @@ -1,5 +1,6 @@ //@ run-pass #![allow(unused)] +#![warn(unused_assignments)] // Test copy @@ -34,10 +35,12 @@ pub fn main() { let mut x@B {b, ..} = B {a: 10, b: C {c: 20}}; assert_eq!(x.a, 10); x.b.c = 30; + //~^ WARN value assigned to `x` is never read assert_eq!(b.c, 20); let mut y@D {d, ..} = D {a: 10, d: C {c: 20}}; assert_eq!(y.a, 10); y.d.c = 30; + //~^ WARN value assigned to `y` is never read assert_eq!(d.c, 20); match (E::E { a: 10, e: C { c: 20 } }) { @@ -50,7 +53,9 @@ pub fn main() { } match (E::E { a: 10, e: C { c: 20 } }) { mut x @ E::E{ a, e: C { mut c } } => { + //~^ WARN value assigned to `a` is never read x = E::NotE; + //~^ WARN value assigned to `x` is never read c += 30; assert_eq!(c, 50); } diff --git a/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr new file mode 100644 index 0000000000000..d775b69ef0a5e --- /dev/null +++ b/tests/ui/pattern/bindings-after-at/bind-by-copy.stderr @@ -0,0 +1,39 @@ +warning: value assigned to `x` is never read + --> $DIR/bind-by-copy.rs:37:5 + | +LL | x.b.c = 30; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? +note: the lint level is defined here + --> $DIR/bind-by-copy.rs:3:9 + | +LL | #![warn(unused_assignments)] + | ^^^^^^^^^^^^^^^^^^ + +warning: value assigned to `y` is never read + --> $DIR/bind-by-copy.rs:42:5 + | +LL | y.d.c = 30; + | ^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `x` is never read + --> $DIR/bind-by-copy.rs:57:13 + | +LL | x = E::NotE; + | ^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value assigned to `a` is never read + --> $DIR/bind-by-copy.rs:55:23 + | +LL | mut x @ E::E{ a, e: C { mut c } } => { + | ^ + | + = help: maybe it is overwritten before being read? + +warning: 4 warnings emitted + diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs index 3090f68c72b77..b3dc9e5842334 100644 --- a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs +++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.rs @@ -58,6 +58,7 @@ fn match_with_or() { fn nested_mixed() { match (&Some(5), &Some(6)) { (Some(a), &Some(mut b)) => { + //~^ WARN value assigned to `b` is never read // Here, the `a` will be `&i32`, because in the first half of the tuple // we hit a non-reference pattern and shift into `ref` mode. // diff --git a/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr new file mode 100644 index 0000000000000..aa0aed2d8d790 --- /dev/null +++ b/tests/ui/rfcs/rfc-2005-default-binding-mode/general.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `b` is never read + --> $DIR/general.rs:60:25 + | +LL | (Some(a), &Some(mut b)) => { + | ^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr new file mode 100644 index 0000000000000..b210619f8d780 --- /dev/null +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.default.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `small` is never read + --> $DIR/std-panic-locations.rs:46:31 + | +LL | assert_panicked(move || { small[1] += 1; }); + | ^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr new file mode 100644 index 0000000000000..b210619f8d780 --- /dev/null +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.mir-opt.stderr @@ -0,0 +1,11 @@ +warning: value assigned to `small` is never read + --> $DIR/std-panic-locations.rs:46:31 + | +LL | assert_panicked(move || { small[1] += 1; }); + | ^^^^^^^^^^^^^ + | + = help: maybe it is overwritten before being read? + = note: `#[warn(unused_assignments)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs index bd62a6447851f..62338819625d4 100644 --- a/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs +++ b/tests/ui/rfcs/rfc-2091-track-caller/std-panic-locations.rs @@ -44,6 +44,7 @@ fn main() { assert_panicked(move || { small[1]; }); assert_panicked(move || { small.index_mut(1); }); assert_panicked(move || { small[1] += 1; }); + //~^ WARN value assigned to `small` is never read let sorted: BTreeMap = Default::default(); assert_panicked(|| { sorted.index(&false); }); diff --git a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr index 16e1af46059a4..733f2db159a9d 100644 --- a/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr +++ b/tests/ui/rfcs/rfc-2565-param-attrs/param-attrs-cfg.stderr @@ -10,12 +10,6 @@ note: the lint level is defined here LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ -error: unused variable: `a` - --> $DIR/param-attrs-cfg.rs:41:27 - | -LL | #[cfg(something)] a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - error: unused variable: `b` --> $DIR/param-attrs-cfg.rs:30:23 | @@ -28,6 +22,12 @@ error: unused variable: `c` LL | #[cfg_attr(nothing, cfg(nothing))] c: i32, | ^ help: if this is intentional, prefix it with an underscore: `_c` +error: unused variable: `a` + --> $DIR/param-attrs-cfg.rs:41:27 + | +LL | #[cfg(something)] a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: unused variable: `b` --> $DIR/param-attrs-cfg.rs:48:27 | @@ -100,12 +100,6 @@ error: unused variable: `c` LL | #[cfg_attr(nothing, cfg(nothing))] c: i32, | ^ help: if this is intentional, prefix it with an underscore: `_c` -error: unused variable: `a` - --> $DIR/param-attrs-cfg.rs:107:27 - | -LL | #[cfg(something)] a: i32, - | ^ help: if this is intentional, prefix it with an underscore: `_a` - error: unused variable: `b` --> $DIR/param-attrs-cfg.rs:113:27 | @@ -118,5 +112,11 @@ error: unused variable: `c` LL | #[cfg_attr(nothing, cfg(nothing))] c: i32, | ^ help: if this is intentional, prefix it with an underscore: `_c` +error: unused variable: `a` + --> $DIR/param-attrs-cfg.rs:107:27 + | +LL | #[cfg(something)] a: i32, + | ^ help: if this is intentional, prefix it with an underscore: `_a` + error: aborting due to 19 previous errors diff --git a/tests/ui/suggestions/try-removing-the-field.rs b/tests/ui/suggestions/try-removing-the-field.rs index dc1bde082c4f1..29da1964354ec 100644 --- a/tests/ui/suggestions/try-removing-the-field.rs +++ b/tests/ui/suggestions/try-removing-the-field.rs @@ -10,7 +10,7 @@ struct Foo { fn use_foo(x: Foo) -> i32 { let Foo { foo, bar, .. } = x; //~ WARNING unused variable: `bar` - //~| help: try removing the field + //~| help: try ignoring the field return foo; } @@ -24,7 +24,7 @@ fn use_match(x: Foo) { match x { Foo { foo, .. } => { //~ WARNING unused variable - //~| help: try removing the field + //~| help: try ignoring the field } } } diff --git a/tests/ui/suggestions/try-removing-the-field.stderr b/tests/ui/suggestions/try-removing-the-field.stderr index 7a6013d4a6eab..06175c9d672d6 100644 --- a/tests/ui/suggestions/try-removing-the-field.stderr +++ b/tests/ui/suggestions/try-removing-the-field.stderr @@ -2,9 +2,7 @@ warning: unused variable: `bar` --> $DIR/try-removing-the-field.rs:12:20 | LL | let Foo { foo, bar, .. } = x; - | ^^^- - | | - | help: try removing the field + | ^^^ help: try ignoring the field: `bar: _` | = note: `#[warn(unused_variables)]` on by default @@ -18,9 +16,7 @@ warning: unused variable: `foo` --> $DIR/try-removing-the-field.rs:26:15 | LL | Foo { foo, .. } => { - | ^^^- - | | - | help: try removing the field + | ^^^ help: try ignoring the field: `foo: _` warning: 3 warnings emitted diff --git a/tests/ui/suggestions/unused-closure-argument.stderr b/tests/ui/suggestions/unused-closure-argument.stderr index 55195ce50a13e..9f70501389c43 100644 --- a/tests/ui/suggestions/unused-closure-argument.stderr +++ b/tests/ui/suggestions/unused-closure-argument.stderr @@ -1,8 +1,8 @@ error: unused variable: `x` - --> $DIR/unused-closure-argument.rs:12:23 + --> $DIR/unused-closure-argument.rs:17:15 | -LL | .map(|Point { x, y }| y) - | ^ help: try ignoring the field: `x: _` +LL | .map(|x| 4) + | ^ help: if this is intentional, prefix it with an underscore: `_x` | note: the lint level is defined here --> $DIR/unused-closure-argument.rs:1:9 @@ -11,10 +11,10 @@ LL | #![deny(unused_variables)] | ^^^^^^^^^^^^^^^^ error: unused variable: `x` - --> $DIR/unused-closure-argument.rs:17:15 + --> $DIR/unused-closure-argument.rs:12:23 | -LL | .map(|x| 4) - | ^ help: if this is intentional, prefix it with an underscore: `_x` +LL | .map(|Point { x, y }| y) + | ^ help: try ignoring the field: `x: _` error: aborting due to 2 previous errors diff --git a/tests/ui/thir-print/thir-tree-match.stdout b/tests/ui/thir-print/thir-tree-match.stdout index b248d2a82684f..ddfacaeaa2c27 100644 --- a/tests/ui/thir-print/thir-tree-match.stdout +++ b/tests/ui/thir-print/thir-tree-match.stdout @@ -16,6 +16,7 @@ params: [ var: LocalVarId(HirId(DefId(0:16 ~ thir_tree_match[fcf8]::has_match).2)) ty: Foo is_primary: true + is_shorthand: false subpattern: None } } diff --git a/tests/ui/type/issue-100584.stderr b/tests/ui/type/issue-100584.stderr index e1db14d1f001b..0a0d96f2c9dbc 100644 --- a/tests/ui/type/issue-100584.stderr +++ b/tests/ui/type/issue-100584.stderr @@ -2,7 +2,7 @@ error: unused variable: `xyza` --> $DIR/issue-100584.rs:2:8 | LL | fn foo(xyza: &str) { - | ^^^^ unused variable + | ^^^^ LL | LL | let _ = "{xyza}"; | -------- you might have meant to use string interpolation in this string literal @@ -26,7 +26,7 @@ error: unused variable: `xyza` --> $DIR/issue-100584.rs:7:9 | LL | fn foo3(xyza: &str) { - | ^^^^ unused variable + | ^^^^ LL | LL | let _ = "aaa{xyza}bbb"; | -------------- you might have meant to use string interpolation in this string literal diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs index 42287ac50701b..ee425abfd1e27 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs +++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.rs @@ -21,8 +21,9 @@ fn main() { call(move || { // this mutates a moved copy, and hence doesn't affect original - counter += 1; //~ WARN value assigned to `counter` is never read - //~| WARN unused variable: `counter` + counter += 1; + //~^ WARN value captured by `counter` is never read + //~| WARN value assigned to `counter` is never read }); assert_eq!(counter, 88); } diff --git a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr index 6450cc30ac05f..3f155948866ea 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-counter-not-moved.stderr @@ -1,27 +1,27 @@ -warning: unused variable: `item` - --> $DIR/unboxed-closures-counter-not-moved.rs:15:13 +warning: value captured by `counter` is never read + --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 | -LL | for item in y { - | ^^^^ help: if this is intentional, prefix it with an underscore: `_item` +LL | counter += 1; + | ^^^^^^^ | - = note: `#[warn(unused_variables)]` on by default + = help: did you mean to capture by reference instead? + = note: `#[warn(unused_assignments)]` on by default warning: value assigned to `counter` is never read --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 | LL | counter += 1; - | ^^^^^^^ + | ^^^^^^^^^^^^ | = help: maybe it is overwritten before being read? - = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `counter` - --> $DIR/unboxed-closures-counter-not-moved.rs:24:9 +warning: unused variable: `item` + --> $DIR/unboxed-closures-counter-not-moved.rs:15:13 | -LL | counter += 1; - | ^^^^^^^ +LL | for item in y { + | ^^^^ help: if this is intentional, prefix it with an underscore: `_item` | - = help: did you mean to capture by reference instead? + = note: `#[warn(unused_variables)]` on by default warning: 3 warnings emitted diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs index d883053d2763e..0e3744bbe00c2 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs +++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.rs @@ -14,11 +14,17 @@ fn set(x: &mut usize) { *x = 42; } fn main() { { let mut x = 0_usize; - move || x += 1; //~ WARN unused variable: `x` + //~^ WARN unused variable: `x` + move || x += 1; + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } { let mut x = 0_usize; - move || x += 1; //~ WARN unused variable: `x` + //~^ WARN unused variable: `x` + move || x += 1; + //~^ WARN value captured by `x` is never read + //~| WARN value assigned to `x` is never read } { let mut x = 0_usize; diff --git a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr index 5c06f4e621c17..ec177ad011f55 100644 --- a/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr +++ b/tests/ui/unboxed-closures/unboxed-closures-move-mutable.stderr @@ -1,19 +1,49 @@ -warning: unused variable: `x` - --> $DIR/unboxed-closures-move-mutable.rs:17:17 +warning: value captured by `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:25:17 | LL | move || x += 1; | ^ | = help: did you mean to capture by reference instead? - = note: `#[warn(unused_variables)]` on by default + = note: `#[warn(unused_assignments)]` on by default -warning: unused variable: `x` - --> $DIR/unboxed-closures-move-mutable.rs:21:17 +warning: value assigned to `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:25:17 + | +LL | move || x += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: value captured by `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:18:17 | LL | move || x += 1; | ^ | = help: did you mean to capture by reference instead? -warning: 2 warnings emitted +warning: value assigned to `x` is never read + --> $DIR/unboxed-closures-move-mutable.rs:18:17 + | +LL | move || x += 1; + | ^^^^^^ + | + = help: maybe it is overwritten before being read? + +warning: unused variable: `x` + --> $DIR/unboxed-closures-move-mutable.rs:16:13 + | +LL | let mut x = 0_usize; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` + | + = note: `#[warn(unused_variables)]` on by default + +warning: unused variable: `x` + --> $DIR/unboxed-closures-move-mutable.rs:23:13 + | +LL | let mut x = 0_usize; + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_x` + +warning: 6 warnings emitted