diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index e5b8e5f0eb9a8..ac2fa8515bc1d 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -273,6 +273,12 @@ declare_lint! { "detects name collision with an existing but unstable method" } +declare_lint! { + pub UNUSED_LABELS, + Allow, + "detects labels that are never used" +} + /// Does nothing as a lint pass, but registers some `Lint`s /// which are used by other parts of the compiler. #[derive(Copy, Clone)] @@ -318,6 +324,7 @@ impl LintPass for HardwiredLints { UNUSED_MUT, SINGLE_USE_LIFETIME, UNUSED_LIFETIME, + UNUSED_LABELS, TYVAR_BEHIND_RAW_POINTER, ELIDED_LIFETIME_IN_PATH, BARE_TRAIT_OBJECT, diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index cf1348bfbfb64..cfd5cf5a0f98c 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -177,6 +177,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) { UNUSED_DOC_COMMENT, UNUSED_EXTERN_CRATES, UNUSED_FEATURES, + UNUSED_LABELS, UNUSED_PARENS); add_lint_group!(sess, diff --git a/src/librustc_resolve/check_unused.rs b/src/librustc_resolve/check_unused.rs index 163f6a64010b5..590ce168d5d0f 100644 --- a/src/librustc_resolve/check_unused.rs +++ b/src/librustc_resolve/check_unused.rs @@ -142,6 +142,10 @@ pub fn check_crate(resolver: &mut Resolver, krate: &ast::Crate) { } } + for (id, span) in resolver.unused_labels.iter() { + resolver.session.buffer_lint(lint::builtin::UNUSED_LABELS, *id, *span, "unused label"); + } + let mut visitor = UnusedImportCheckVisitor { resolver, unused_imports: NodeMap(), diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index 94333c3a1d72e..bc5d50491ad6b 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -1468,6 +1468,10 @@ pub struct Resolver<'a> { pub maybe_unused_trait_imports: NodeSet, pub maybe_unused_extern_crates: Vec<(NodeId, Span)>, + /// A list of labels as of yet unused. Labels will be removed from this map when + /// they are used (in a `break` or `continue` statement) + pub unused_labels: FxHashMap, + /// privacy errors are delayed until the end in order to deduplicate them privacy_errors: Vec>, /// ambiguity errors are delayed for deduplication @@ -1747,6 +1751,8 @@ impl<'a> Resolver<'a> { maybe_unused_trait_imports: NodeSet(), maybe_unused_extern_crates: Vec::new(), + unused_labels: FxHashMap(), + privacy_errors: Vec::new(), ambiguity_errors: Vec::new(), use_injections: Vec::new(), @@ -3682,6 +3688,7 @@ impl<'a> Resolver<'a> { where F: FnOnce(&mut Resolver) { if let Some(label) = label { + self.unused_labels.insert(id, label.ident.span); let def = Def::Label(id); self.with_label_rib(|this| { this.label_ribs.last_mut().unwrap().bindings.insert(label.ident, def); @@ -3730,9 +3737,10 @@ impl<'a> Resolver<'a> { ResolutionError::UndeclaredLabel(&label.ident.name.as_str(), close_match)); } - Some(def @ Def::Label(_)) => { + Some(Def::Label(id)) => { // Since this def is a label, it is never read. - self.record_def(expr.id, PathResolution::new(def)); + self.record_def(expr.id, PathResolution::new(Def::Label(id))); + self.unused_labels.remove(&id); } Some(_) => { span_bug!(expr.span, "label wasn't mapped to a label def!"); diff --git a/src/test/ui/label_break_value_continue.rs b/src/test/ui/label_break_value_continue.rs index 52e24b759d1d2..4a505dff3d467 100644 --- a/src/test/ui/label_break_value_continue.rs +++ b/src/test/ui/label_break_value_continue.rs @@ -9,6 +9,7 @@ // except according to those terms. #![feature(label_break_value)] +#![allow(unused_labels)] // Simple continue pointing to an unlabeled break should yield in an error fn continue_simple() { diff --git a/src/test/ui/label_break_value_continue.stderr b/src/test/ui/label_break_value_continue.stderr index 24c2d1a22d01c..12a21a8a59441 100644 --- a/src/test/ui/label_break_value_continue.stderr +++ b/src/test/ui/label_break_value_continue.stderr @@ -1,17 +1,17 @@ error[E0695]: unlabeled `continue` inside of a labeled block - --> $DIR/label_break_value_continue.rs:16:9 + --> $DIR/label_break_value_continue.rs:17:9 | LL | continue; //~ ERROR unlabeled `continue` inside of a labeled block | ^^^^^^^^ `continue` statements that would diverge to or through a labeled block need to bear a label error[E0696]: `continue` pointing to a labeled block - --> $DIR/label_break_value_continue.rs:23:9 + --> $DIR/label_break_value_continue.rs:24:9 | LL | continue 'b; //~ ERROR `continue` pointing to a labeled block | ^^^^^^^^^^^ labeled blocks cannot be `continue`'d | note: labeled block the continue points to - --> $DIR/label_break_value_continue.rs:22:5 + --> $DIR/label_break_value_continue.rs:23:5 | LL | / 'b: { LL | | continue 'b; //~ ERROR `continue` pointing to a labeled block @@ -19,7 +19,7 @@ LL | | } | |_____^ error[E0695]: unlabeled `continue` inside of a labeled block - --> $DIR/label_break_value_continue.rs:31:13 + --> $DIR/label_break_value_continue.rs:32:13 | LL | continue; //~ ERROR unlabeled `continue` inside of a labeled block | ^^^^^^^^ `continue` statements that would diverge to or through a labeled block need to bear a label diff --git a/src/test/ui/label_break_value_unlabeled_break.rs b/src/test/ui/label_break_value_unlabeled_break.rs index 38918da291c86..454ebd4c6cf6d 100644 --- a/src/test/ui/label_break_value_unlabeled_break.rs +++ b/src/test/ui/label_break_value_unlabeled_break.rs @@ -9,6 +9,7 @@ // except according to those terms. #![feature(label_break_value)] +#![allow(unused_labels)] // Simple unlabeled break should yield in an error fn unlabeled_break_simple() { diff --git a/src/test/ui/label_break_value_unlabeled_break.stderr b/src/test/ui/label_break_value_unlabeled_break.stderr index 8a25975a7bda2..62c4a12231baf 100644 --- a/src/test/ui/label_break_value_unlabeled_break.stderr +++ b/src/test/ui/label_break_value_unlabeled_break.stderr @@ -1,11 +1,11 @@ error[E0695]: unlabeled `break` inside of a labeled block - --> $DIR/label_break_value_unlabeled_break.rs:16:9 + --> $DIR/label_break_value_unlabeled_break.rs:17:9 | LL | break; //~ ERROR unlabeled `break` inside of a labeled block | ^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label error[E0695]: unlabeled `break` inside of a labeled block - --> $DIR/label_break_value_unlabeled_break.rs:24:13 + --> $DIR/label_break_value_unlabeled_break.rs:25:13 | LL | break; //~ ERROR unlabeled `break` inside of a labeled block | ^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label diff --git a/src/test/ui/lint/unused_labels.rs b/src/test/ui/lint/unused_labels.rs new file mode 100644 index 0000000000000..23add604da6ab --- /dev/null +++ b/src/test/ui/lint/unused_labels.rs @@ -0,0 +1,96 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// The output should warn when a loop label is not used. However, it +// should also deal with the edge cases where a label is shadowed, +// within nested loops + +// compile-pass + +#![feature(label_break_value)] +#![warn(unused_labels)] + +fn main() { + 'unused_while_label: while 0 == 0 { + //~^ WARN unused label + } + + let opt = Some(0); + 'unused_while_let_label: while let Some(_) = opt { + //~^ WARN unused label + } + + 'unused_for_label: for _ in 0..10 { + //~^ WARN unused label + } + + 'used_loop_label: loop { + break 'used_loop_label; + } + + 'used_loop_label_outer_1: for _ in 0..10 { + 'used_loop_label_inner_1: for _ in 0..10 { + break 'used_loop_label_inner_1; + } + break 'used_loop_label_outer_1; + } + + 'used_loop_label_outer_2: for _ in 0..10 { + 'unused_loop_label_inner_2: for _ in 0..10 { + //~^ WARN unused label + break 'used_loop_label_outer_2; + } + } + + 'unused_loop_label_outer_3: for _ in 0..10 { + //~^ WARN unused label + 'used_loop_label_inner_3: for _ in 0..10 { + break 'used_loop_label_inner_3; + } + } + + // You should be able to break the same label many times + 'many_used: loop { + if true { + break 'many_used; + } else { + break 'many_used; + } + } + + // Test breaking many times with the same inner label doesn't break the + // warning on the outer label + 'many_used_shadowed: for _ in 0..10 { + //~^ WARN unused label + 'many_used_shadowed: for _ in 0..10 { + //~^ WARN label name `'many_used_shadowed` shadows a label name that is already in scope + if 1 % 2 == 0 { + break 'many_used_shadowed; + } else { + break 'many_used_shadowed; + } + } + } + + 'unused_loop_label: loop { + //~^ WARN unused label + break; + } + + // Make sure unused block labels give warnings... + 'unused_block_label: { + //~^ WARN unused label + } + + // ...and that used ones don't: + 'used_block_label: { + break 'used_block_label; + } +} diff --git a/src/test/ui/lint/unused_labels.stderr b/src/test/ui/lint/unused_labels.stderr new file mode 100644 index 0000000000000..825f5e281f0b9 --- /dev/null +++ b/src/test/ui/lint/unused_labels.stderr @@ -0,0 +1,63 @@ +warning: unused label + --> $DIR/unused_labels.rs:21:5 + | +LL | 'unused_while_label: while 0 == 0 { + | ^^^^^^^^^^^^^^^^^^^ + | +note: lint level defined here + --> $DIR/unused_labels.rs:18:9 + | +LL | #![warn(unused_labels)] + | ^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:26:5 + | +LL | 'unused_while_let_label: while let Some(_) = opt { + | ^^^^^^^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:30:5 + | +LL | 'unused_for_label: for _ in 0..10 { + | ^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:46:9 + | +LL | 'unused_loop_label_inner_2: for _ in 0..10 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:52:5 + | +LL | 'unused_loop_label_outer_3: for _ in 0..10 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:70:5 + | +LL | 'many_used_shadowed: for _ in 0..10 { + | ^^^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:82:5 + | +LL | 'unused_loop_label: loop { + | ^^^^^^^^^^^^^^^^^^ + +warning: unused label + --> $DIR/unused_labels.rs:88:5 + | +LL | 'unused_block_label: { + | ^^^^^^^^^^^^^^^^^^^ + +warning: label name `'many_used_shadowed` shadows a label name that is already in scope + --> $DIR/unused_labels.rs:72:9 + | +LL | 'many_used_shadowed: for _ in 0..10 { + | ------------------- first declared here +LL | //~^ WARN unused label +LL | 'many_used_shadowed: for _ in 0..10 { + | ^^^^^^^^^^^^^^^^^^^ lifetime 'many_used_shadowed already in scope + diff --git a/src/test/ui/loops-reject-duplicate-labels-2.rs b/src/test/ui/loops-reject-duplicate-labels-2.rs index 598c7370b8906..b273e7a0c7c9e 100644 --- a/src/test/ui/loops-reject-duplicate-labels-2.rs +++ b/src/test/ui/loops-reject-duplicate-labels-2.rs @@ -18,6 +18,7 @@ // discussed here: // https://internals.rust-lang.org/t/psa-rejecting-duplicate-loop-labels/1833 +#[allow(unused_labels)] pub fn foo() { { 'fl: for _ in 0..10 { break; } } { 'fl: loop { break; } } //~ WARN label name `'fl` shadows a label name that is already in scope diff --git a/src/test/ui/loops-reject-duplicate-labels-2.stderr b/src/test/ui/loops-reject-duplicate-labels-2.stderr index 830270a99d112..41b4a850f1bfb 100644 --- a/src/test/ui/loops-reject-duplicate-labels-2.stderr +++ b/src/test/ui/loops-reject-duplicate-labels-2.stderr @@ -1,5 +1,5 @@ warning: label name `'fl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:23:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:24:7 | LL | { 'fl: for _ in 0..10 { break; } } | --- first declared here @@ -7,7 +7,7 @@ LL | { 'fl: loop { break; } } //~ WARN label name `'fl` shadows | ^^^ lifetime 'fl already in scope warning: label name `'lf` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:25:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:26:7 | LL | { 'lf: loop { break; } } | --- first declared here @@ -15,7 +15,7 @@ LL | { 'lf: for _ in 0..10 { break; } } //~ WARN label name `'lf` shadows | ^^^ lifetime 'lf already in scope warning: label name `'wl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:27:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:28:7 | LL | { 'wl: while 2 > 1 { break; } } | --- first declared here @@ -23,7 +23,7 @@ LL | { 'wl: loop { break; } } //~ WARN label name `'wl` shadows | ^^^ lifetime 'wl already in scope warning: label name `'lw` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:29:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:30:7 | LL | { 'lw: loop { break; } } | --- first declared here @@ -31,7 +31,7 @@ LL | { 'lw: while 2 > 1 { break; } } //~ WARN label name `'lw` shadows | ^^^ lifetime 'lw already in scope warning: label name `'fw` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:31:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:32:7 | LL | { 'fw: for _ in 0..10 { break; } } | --- first declared here @@ -39,7 +39,7 @@ LL | { 'fw: while 2 > 1 { break; } } //~ WARN label name `'fw` shadows | ^^^ lifetime 'fw already in scope warning: label name `'wf` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:33:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:34:7 | LL | { 'wf: while 2 > 1 { break; } } | --- first declared here @@ -47,7 +47,7 @@ LL | { 'wf: for _ in 0..10 { break; } } //~ WARN label name `'wf` shadows | ^^^ lifetime 'wf already in scope warning: label name `'tl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:35:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:36:7 | LL | { 'tl: while let Some(_) = None:: { break; } } | --- first declared here @@ -55,7 +55,7 @@ LL | { 'tl: loop { break; } } //~ WARN label name `'tl` shadows | ^^^ lifetime 'tl already in scope warning: label name `'lt` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels-2.rs:37:7 + --> $DIR/loops-reject-duplicate-labels-2.rs:38:7 | LL | { 'lt: loop { break; } } | --- first declared here @@ -63,7 +63,7 @@ LL | { 'lt: while let Some(_) = None:: { break; } } | ^^^ lifetime 'lt already in scope error: compilation successful - --> $DIR/loops-reject-duplicate-labels-2.rs:42:1 + --> $DIR/loops-reject-duplicate-labels-2.rs:43:1 | LL | / pub fn main() { //~ ERROR compilation successful LL | | foo(); diff --git a/src/test/ui/loops-reject-duplicate-labels.rs b/src/test/ui/loops-reject-duplicate-labels.rs index d768b002ab10c..ad24f69871c85 100644 --- a/src/test/ui/loops-reject-duplicate-labels.rs +++ b/src/test/ui/loops-reject-duplicate-labels.rs @@ -15,6 +15,7 @@ // Issue #21633: reject duplicate loop labels in function bodies. // This is testing the exact cases that are in the issue description. +#[allow(unused_labels)] fn foo() { 'fl: for _ in 0..10 { break; } 'fl: loop { break; } //~ WARN label name `'fl` shadows a label name that is already in scope diff --git a/src/test/ui/loops-reject-duplicate-labels.stderr b/src/test/ui/loops-reject-duplicate-labels.stderr index a71f98b812a8c..d0cb81544f828 100644 --- a/src/test/ui/loops-reject-duplicate-labels.stderr +++ b/src/test/ui/loops-reject-duplicate-labels.stderr @@ -1,5 +1,5 @@ warning: label name `'fl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:20:5 + --> $DIR/loops-reject-duplicate-labels.rs:21:5 | LL | 'fl: for _ in 0..10 { break; } | --- first declared here @@ -7,7 +7,7 @@ LL | 'fl: loop { break; } //~ WARN label name `'fl` shadows a labe | ^^^ lifetime 'fl already in scope warning: label name `'lf` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:23:5 + --> $DIR/loops-reject-duplicate-labels.rs:24:5 | LL | 'lf: loop { break; } | --- first declared here @@ -15,7 +15,7 @@ LL | 'lf: for _ in 0..10 { break; } //~ WARN label name `'lf` shadows a labe | ^^^ lifetime 'lf already in scope warning: label name `'wl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:25:5 + --> $DIR/loops-reject-duplicate-labels.rs:26:5 | LL | 'wl: while 2 > 1 { break; } | --- first declared here @@ -23,7 +23,7 @@ LL | 'wl: loop { break; } //~ WARN label name `'wl` shadows a labe | ^^^ lifetime 'wl already in scope warning: label name `'lw` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:27:5 + --> $DIR/loops-reject-duplicate-labels.rs:28:5 | LL | 'lw: loop { break; } | --- first declared here @@ -31,7 +31,7 @@ LL | 'lw: while 2 > 1 { break; } //~ WARN label name `'lw` shadows a labe | ^^^ lifetime 'lw already in scope warning: label name `'fw` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:29:5 + --> $DIR/loops-reject-duplicate-labels.rs:30:5 | LL | 'fw: for _ in 0..10 { break; } | --- first declared here @@ -39,7 +39,7 @@ LL | 'fw: while 2 > 1 { break; } //~ WARN label name `'fw` shadows a labe | ^^^ lifetime 'fw already in scope warning: label name `'wf` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:31:5 + --> $DIR/loops-reject-duplicate-labels.rs:32:5 | LL | 'wf: while 2 > 1 { break; } | --- first declared here @@ -47,7 +47,7 @@ LL | 'wf: for _ in 0..10 { break; } //~ WARN label name `'wf` shadows a labe | ^^^ lifetime 'wf already in scope warning: label name `'tl` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:33:5 + --> $DIR/loops-reject-duplicate-labels.rs:34:5 | LL | 'tl: while let Some(_) = None:: { break; } | --- first declared here @@ -55,7 +55,7 @@ LL | 'tl: loop { break; } //~ WARN label name `'tl` shadows a labe | ^^^ lifetime 'tl already in scope warning: label name `'lt` shadows a label name that is already in scope - --> $DIR/loops-reject-duplicate-labels.rs:35:5 + --> $DIR/loops-reject-duplicate-labels.rs:36:5 | LL | 'lt: loop { break; } | --- first declared here @@ -63,7 +63,7 @@ LL | 'lt: while let Some(_) = None:: { break; } | ^^^ lifetime 'lt already in scope error: compilation successful - --> $DIR/loops-reject-duplicate-labels.rs:49:1 + --> $DIR/loops-reject-duplicate-labels.rs:50:1 | LL | / pub fn main() { //~ ERROR compilation successful LL | | let s = S; diff --git a/src/test/ui/suggestions/suggest-labels.rs b/src/test/ui/suggestions/suggest-labels.rs index 8c97301f40b91..9fb519c57edf3 100644 --- a/src/test/ui/suggestions/suggest-labels.rs +++ b/src/test/ui/suggestions/suggest-labels.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#[allow(unreachable_code)] +#[allow(unreachable_code, unused_labels)] fn main() { 'foo: loop { break 'fo; //~ ERROR use of undeclared label