about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/rvalue.rs9
-rw-r--r--compiler/rustc_error_codes/src/error_codes/E0591.md4
-rw-r--r--compiler/rustc_infer/src/infer/mod.rs2
-rw-r--r--compiler/rustc_target/src/spec/mod.rs14
-rw-r--r--library/std/src/io/error/tests.rs2
-rw-r--r--library/std/src/sys/unix/kernel_copy.rs2
-rwxr-xr-xsrc/ci/scripts/should-skip-this.sh2
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css9
-rw-r--r--src/test/ui/async-await/issue-64130-1-sync.rs2
-rw-r--r--src/test/ui/async-await/issue-64130-2-send.rs2
-rw-r--r--src/test/ui/async-await/issue-64130-3-other.rs2
-rw-r--r--src/test/ui/const-generics/occurs-check/unused-substs-2.rs2
-rw-r--r--src/test/ui/const-generics/occurs-check/unused-substs-3.rs2
-rw-r--r--src/test/ui/deprecation/deprecation-lint.rs2
-rw-r--r--src/test/ui/dyn-star/box.rs17
-rw-r--r--src/test/ui/explain.stdout4
-rw-r--r--src/test/ui/issues/issue-102964.rs10
-rw-r--r--src/test/ui/issues/issue-102964.stderr19
-rw-r--r--src/test/ui/issues/issue-29746.rs2
-rw-r--r--src/test/ui/issues/issue-75907.rs2
-rw-r--r--src/test/ui/issues/issue-75907_b.rs2
-rw-r--r--src/test/ui/lint/lint-stability-deprecated.rs2
-rw-r--r--src/test/ui/proc-macro/meta-macro-hygiene.rs4
-rw-r--r--src/test/ui/proc-macro/meta-macro-hygiene.stdout4
-rw-r--r--src/tools/tidy/src/features.rs263
25 files changed, 226 insertions, 159 deletions
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index abf3c9a363f..4ed99df1e81 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -14,6 +14,7 @@ use rustc_middle::ty::cast::{CastTy, IntTy};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf};
 use rustc_middle::ty::{self, adjustment::PointerCast, Instance, Ty, TyCtxt};
 use rustc_span::source_map::{Span, DUMMY_SP};
+use rustc_target::abi::Size;
 
 impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
     #[instrument(level = "trace", skip(self, bx))]
@@ -285,6 +286,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                 bug!("Only valid to do a DynStar cast into a DynStar type")
                             };
                         let vtable = get_vtable(bx.cx(), source.ty(self.mir, bx.tcx()), trait_ref);
+                        let vtable = bx.pointercast(vtable, bx.cx().type_ptr_to(bx.cx().type_isize()));
+                        // FIXME(dyn-star): this is probably not the best way to check if this is
+                        // a pointer, and really we should ensure that the value is a suitable
+                        // pointer earlier in the compilation process.
+                        let data = match operand.layout.pointee_info_at(bx.cx(), Size::ZERO) {
+                            Some(_) => bx.ptrtoint(data, bx.cx().type_isize()),
+                            None => data,
+                        };
                         OperandValue::Pair(data, vtable)
                     }
                     mir::CastKind::Pointer(
diff --git a/compiler/rustc_error_codes/src/error_codes/E0591.md b/compiler/rustc_error_codes/src/error_codes/E0591.md
index f49805d9b4e..6ed8370e8c1 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0591.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0591.md
@@ -53,8 +53,8 @@ unsafe {
 ```
 
 Here, transmute is being used to convert the types of the fn arguments.
-This pattern is incorrect because, because the type of `foo` is a function
-**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
+This pattern is incorrect because the type of `foo` is a function **item**
+(`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
 is a function pointer, which is not zero-sized.
 This pattern should be rewritten. There are a few possible ways to do this:
 
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 441dc3c7e88..5a5e9db81a2 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -1283,7 +1283,7 @@ impl<'tcx> InferCtxt<'tcx> {
         assert!(old_value.is_none());
     }
 
-    /// Process the region constraints and return any any errors that
+    /// Process the region constraints and return any errors that
     /// result. After this, no more unification operations should be
     /// done -- or the compiler will panic -- but it is legal to use
     /// `resolve_vars_if_possible` as well as `fully_resolve`.
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 9396d769dc7..8909cf33af9 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1739,11 +1739,15 @@ impl TargetOptions {
                     self.lld_flavor_json,
                     self.linker_is_gnu_json,
                 );
-                match linker_flavor {
-                    LinkerFlavor::Gnu(_, Lld::Yes)
-                    | LinkerFlavor::Darwin(_, Lld::Yes)
-                    | LinkerFlavor::Msvc(Lld::Yes) => {}
-                    _ => add_link_args_iter(args, linker_flavor, args_json.iter().cloned()),
+                // Normalize to no lld to avoid asserts.
+                let linker_flavor = match linker_flavor {
+                    LinkerFlavor::Gnu(cc, _) => LinkerFlavor::Gnu(cc, Lld::No),
+                    LinkerFlavor::Darwin(cc, _) => LinkerFlavor::Darwin(cc, Lld::No),
+                    LinkerFlavor::Msvc(_) => LinkerFlavor::Msvc(Lld::No),
+                    _ => linker_flavor,
+                };
+                if !args.contains_key(&linker_flavor) {
+                    add_link_args_iter(args, linker_flavor, args_json.iter().cloned());
                 }
             }
         }
diff --git a/library/std/src/io/error/tests.rs b/library/std/src/io/error/tests.rs
index c897a5e8701..16c634e9afd 100644
--- a/library/std/src/io/error/tests.rs
+++ b/library/std/src/io/error/tests.rs
@@ -86,7 +86,7 @@ fn test_errorkind_packing() {
     assert_eq!(Error::from(ErrorKind::NotFound).kind(), ErrorKind::NotFound);
     assert_eq!(Error::from(ErrorKind::PermissionDenied).kind(), ErrorKind::PermissionDenied);
     assert_eq!(Error::from(ErrorKind::Uncategorized).kind(), ErrorKind::Uncategorized);
-    // Check that the innards look like like what we want.
+    // Check that the innards look like what we want.
     assert_matches!(
         Error::from(ErrorKind::OutOfMemory).repr.data(),
         ErrorData::Simple(ErrorKind::OutOfMemory),
diff --git a/library/std/src/sys/unix/kernel_copy.rs b/library/std/src/sys/unix/kernel_copy.rs
index 8f7abb55e23..94546ca09d0 100644
--- a/library/std/src/sys/unix/kernel_copy.rs
+++ b/library/std/src/sys/unix/kernel_copy.rs
@@ -20,7 +20,7 @@
 //! Since those syscalls have requirements that cannot be fully checked in advance and
 //! gathering additional information about file descriptors would require additional syscalls
 //! anyway it simply attempts to use them one after another (guided by inaccurate hints) to
-//! figure out which one works and and falls back to the generic read-write copy loop if none of them
+//! figure out which one works and falls back to the generic read-write copy loop if none of them
 //! does.
 //! Once a working syscall is found for a pair of file descriptors it will be called in a loop
 //! until the copy operation is completed.
diff --git a/src/ci/scripts/should-skip-this.sh b/src/ci/scripts/should-skip-this.sh
index 60c2960b160..a8a1899317f 100755
--- a/src/ci/scripts/should-skip-this.sh
+++ b/src/ci/scripts/should-skip-this.sh
@@ -19,7 +19,7 @@ if [[ -n "${CI_ONLY_WHEN_SUBMODULES_CHANGED-}" ]]; then
         # those files are present in the diff a submodule was updated.
         echo "Submodules were updated"
     elif ! (git diff --quiet "$BASE_COMMIT" -- \
-             src/tools/clippy src/tools/rustfmt src/tools/miri
+             src/tools/clippy src/tools/rustfmt src/tools/miri \
              library/std/src/sys); then
         # There is not an easy blanket search for subtrees. For now, manually list
         # the subtrees.
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 894c7328b5f..5958b389c9f 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -692,16 +692,13 @@ pre, .rustdoc.source .example-wrap {
 
 .item-info {
 	display: block;
+	margin-left: 24px;
 }
 
-.content .item-info code {
+.item-info code {
 	font-size: 0.875rem;
 }
 
-.content .item-info {
-	margin-left: 24px;
-}
-
 #main-content > .item-info {
 	margin-top: 0;
 	margin-left: 0;
@@ -1945,7 +1942,7 @@ in storage.js plus the media query with (min-width: 701px)
 	}
 
 	/* Align summary-nested and unnested item-info gizmos. */
-	.content .impl-items > .item-info {
+	.impl-items > .item-info {
 		margin-left: 34px;
 	}
 }
diff --git a/src/test/ui/async-await/issue-64130-1-sync.rs b/src/test/ui/async-await/issue-64130-1-sync.rs
index af83f14bbda..1714cec5221 100644
--- a/src/test/ui/async-await/issue-64130-1-sync.rs
+++ b/src/test/ui/async-await/issue-64130-1-sync.rs
@@ -1,7 +1,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the specialized async-await-specific error when futures don't implement an
+// This tests the specialized async-await-specific error when futures don't implement an
 // auto trait (which is specifically Sync) due to some type that was captured.
 
 struct Foo;
diff --git a/src/test/ui/async-await/issue-64130-2-send.rs b/src/test/ui/async-await/issue-64130-2-send.rs
index 2362831d8b8..7a6e5952cb9 100644
--- a/src/test/ui/async-await/issue-64130-2-send.rs
+++ b/src/test/ui/async-await/issue-64130-2-send.rs
@@ -1,7 +1,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the specialized async-await-specific error when futures don't implement an
+// This tests the specialized async-await-specific error when futures don't implement an
 // auto trait (which is specifically Send) due to some type that was captured.
 
 struct Foo;
diff --git a/src/test/ui/async-await/issue-64130-3-other.rs b/src/test/ui/async-await/issue-64130-3-other.rs
index 52801c35ba3..630fb2c41cd 100644
--- a/src/test/ui/async-await/issue-64130-3-other.rs
+++ b/src/test/ui/async-await/issue-64130-3-other.rs
@@ -2,7 +2,7 @@
 #![feature(negative_impls)]
 // edition:2018
 
-// This tests the the unspecialized async-await-specific error when futures don't implement an
+// This tests the unspecialized async-await-specific error when futures don't implement an
 // auto trait (which is not Send or Sync) due to some type that was captured.
 
 auto trait Qux {}
diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-2.rs b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
index 9a73f1a53e5..9b1212694f5 100644
--- a/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
+++ b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs
@@ -1,7 +1,7 @@
 #![feature(generic_const_exprs)]
 #![allow(incomplete_features)]
 
-// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
+// The goal is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
 //
 // If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>` we introduced an
 // artificial inference cycle.
diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-3.rs b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
index 0d38bd39351..d5aeab47e62 100644
--- a/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
+++ b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs
@@ -1,7 +1,7 @@
 #![feature(generic_const_exprs)]
 #![allow(incomplete_features)]
 
-// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
+// The goal is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst.
 //
 // If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty<ct>` we introduced an
 // artificial inference cycle.
diff --git a/src/test/ui/deprecation/deprecation-lint.rs b/src/test/ui/deprecation/deprecation-lint.rs
index 65cc4e2ef1e..0417e952eb7 100644
--- a/src/test/ui/deprecation/deprecation-lint.rs
+++ b/src/test/ui/deprecation/deprecation-lint.rs
@@ -51,7 +51,7 @@ mod cross_crate {
 
         let _ = nested::DeprecatedTupleStruct (1); //~ ERROR use of deprecated tuple struct `deprecation_lint::nested::DeprecatedTupleStruct`: text
 
-        // At the moment, the lint checker only checks stability in
+        // At the moment, the lint checker only checks stability
         // in the arguments of macros.
         // Eventually, we will want to lint the contents of the
         // macro in the module *defining* it. Also, stability levels
diff --git a/src/test/ui/dyn-star/box.rs b/src/test/ui/dyn-star/box.rs
new file mode 100644
index 00000000000..d1f1819d9f3
--- /dev/null
+++ b/src/test/ui/dyn-star/box.rs
@@ -0,0 +1,17 @@
+// run-pass
+// compile-flags: -C opt-level=0
+
+#![feature(dyn_star)]
+#![allow(incomplete_features)]
+
+use std::fmt::Display;
+
+fn make_dyn_star() -> dyn* Display {
+    Box::new(42) as dyn* Display
+}
+
+fn main() {
+    let x = make_dyn_star();
+
+    println!("{x}");
+}
diff --git a/src/test/ui/explain.stdout b/src/test/ui/explain.stdout
index 62f1a7f98ea..ef1d866c3ff 100644
--- a/src/test/ui/explain.stdout
+++ b/src/test/ui/explain.stdout
@@ -47,8 +47,8 @@ unsafe {
 ```
 
 Here, transmute is being used to convert the types of the fn arguments.
-This pattern is incorrect because, because the type of `foo` is a function
-**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
+This pattern is incorrect because the type of `foo` is a function **item**
+(`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
 is a function pointer, which is not zero-sized.
 This pattern should be rewritten. There are a few possible ways to do this:
 
diff --git a/src/test/ui/issues/issue-102964.rs b/src/test/ui/issues/issue-102964.rs
new file mode 100644
index 00000000000..43ff2360076
--- /dev/null
+++ b/src/test/ui/issues/issue-102964.rs
@@ -0,0 +1,10 @@
+use std::rc::Rc;
+type Foo<'a, T> = &'a dyn Fn(&T);
+type RcFoo<'a, T> = Rc<Foo<'a, T>>;
+
+fn bar_function<T>(function: Foo<T>) -> RcFoo<T> {
+    //~^ ERROR mismatched types
+    let rc = Rc::new(function);
+}
+
+fn main() {}
diff --git a/src/test/ui/issues/issue-102964.stderr b/src/test/ui/issues/issue-102964.stderr
new file mode 100644
index 00000000000..4504039097b
--- /dev/null
+++ b/src/test/ui/issues/issue-102964.stderr
@@ -0,0 +1,19 @@
+error[E0308]: mismatched types
+  --> $DIR/issue-102964.rs:5:41
+   |
+LL | fn bar_function<T>(function: Foo<T>) -> RcFoo<T> {
+   |    ------------                         ^^^^^^^^ expected struct `Rc`, found `()`
+   |    |
+   |    implicitly returns `()` as its body has no tail or `return` expression
+   |
+   = note: expected struct `Rc<&dyn for<'a> Fn(&'a T)>`
+           found unit type `()`
+help: consider returning the local binding `rc`
+   |
+LL ~     let rc = Rc::new(function);
+LL +     rc
+   |
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/issues/issue-29746.rs b/src/test/ui/issues/issue-29746.rs
index 428cc637f55..3470a7e09ad 100644
--- a/src/test/ui/issues/issue-29746.rs
+++ b/src/test/ui/issues/issue-29746.rs
@@ -7,7 +7,7 @@ macro_rules! zip {
         zip!([$($rest),*], $a.zip($b), (x,y), [x,y])
     };
 
-    // Intermediate steps to build the zipped expression, the match pattern, and
+    // Intermediate steps to build the zipped expression, the match pattern
     //  and the output tuple of the closure, using macro hygiene to repeatedly
     //  introduce new variables named 'x'.
     ([$a:expr, $($rest:expr),*], $zip:expr, $pat:pat, [$($flat:expr),*]) => {
diff --git a/src/test/ui/issues/issue-75907.rs b/src/test/ui/issues/issue-75907.rs
index 1534b6d07de..6da99cf6435 100644
--- a/src/test/ui/issues/issue-75907.rs
+++ b/src/test/ui/issues/issue-75907.rs
@@ -1,4 +1,4 @@
-// Test for for diagnostic improvement issue #75907
+// Test for diagnostic improvement issue #75907
 
 mod foo {
     pub(crate) struct Foo(u8);
diff --git a/src/test/ui/issues/issue-75907_b.rs b/src/test/ui/issues/issue-75907_b.rs
index e3074778233..fdfc5907c16 100644
--- a/src/test/ui/issues/issue-75907_b.rs
+++ b/src/test/ui/issues/issue-75907_b.rs
@@ -1,4 +1,4 @@
-// Test for for diagnostic improvement issue #75907, extern crate
+// Test for diagnostic improvement issue #75907, extern crate
 // aux-build:issue-75907.rs
 
 extern crate issue_75907 as a;
diff --git a/src/test/ui/lint/lint-stability-deprecated.rs b/src/test/ui/lint/lint-stability-deprecated.rs
index bdc66e83083..74c35083e60 100644
--- a/src/test/ui/lint/lint-stability-deprecated.rs
+++ b/src/test/ui/lint/lint-stability-deprecated.rs
@@ -130,7 +130,7 @@ mod cross_crate {
         let _ = UnstableTupleStruct (1);
         let _ = StableTupleStruct (1);
 
-        // At the moment, the lint checker only checks stability in
+        // At the moment, the lint checker only checks stability
         // in the arguments of macros.
         // Eventually, we will want to lint the contents of the
         // macro in the module *defining* it. Also, stability levels
diff --git a/src/test/ui/proc-macro/meta-macro-hygiene.rs b/src/test/ui/proc-macro/meta-macro-hygiene.rs
index 62968ea54e0..70b8d8da19b 100644
--- a/src/test/ui/proc-macro/meta-macro-hygiene.rs
+++ b/src/test/ui/proc-macro/meta-macro-hygiene.rs
@@ -19,8 +19,8 @@ macro_rules! produce_it {
         // `print_def_site!` will respan the `$crate` identifier
         // with `Span::def_site()`. This should cause it to resolve
         // relative to `meta_macro`, *not* `make_macro` (despite
-        // the fact that that `print_def_site` is produced by
-        // a `macro_rules!` macro in `make_macro`).
+        // the fact that `print_def_site` is produced by a
+        // `macro_rules!` macro in `make_macro`).
         meta_macro::print_def_site!($crate::dummy!());
     }
 }
diff --git a/src/test/ui/proc-macro/meta-macro-hygiene.stdout b/src/test/ui/proc-macro/meta-macro-hygiene.stdout
index 2494af1208f..6b7b0c819cc 100644
--- a/src/test/ui/proc-macro/meta-macro-hygiene.stdout
+++ b/src/test/ui/proc-macro/meta-macro-hygiene.stdout
@@ -35,8 +35,8 @@ macro_rules! produce_it
         // `print_def_site!` will respan the `$crate` identifier
         // with `Span::def_site()`. This should cause it to resolve
         // relative to `meta_macro`, *not* `make_macro` (despite
-        // the fact that that `print_def_site` is produced by
-        // a `macro_rules!` macro in `make_macro`).
+        // the fact that `print_def_site` is produced by a
+        // `macro_rules!` macro in `make_macro`).
     }
 }
 
diff --git a/src/tools/tidy/src/features.rs b/src/tools/tidy/src/features.rs
index d8b3903b98e..f10ecf5f201 100644
--- a/src/tools/tidy/src/features.rs
+++ b/src/tools/tidy/src/features.rs
@@ -10,7 +10,7 @@
 //! * Language features in a group are sorted by feature name.
 
 use crate::walk::{filter_dirs, walk, walk_many};
-use std::collections::HashMap;
+use std::collections::hash_map::{Entry, HashMap};
 use std::fmt;
 use std::fs;
 use std::num::NonZeroU32;
@@ -280,13 +280,14 @@ fn test_filen_gate(filen_underscore: &str, features: &mut Features) -> bool {
 }
 
 pub fn collect_lang_features(base_compiler_path: &Path, bad: &mut bool) -> Features {
-    let mut all = collect_lang_features_in(base_compiler_path, "active.rs", bad);
-    all.extend(collect_lang_features_in(base_compiler_path, "accepted.rs", bad));
-    all.extend(collect_lang_features_in(base_compiler_path, "removed.rs", bad));
-    all
+    let mut features = Features::new();
+    collect_lang_features_in(&mut features, base_compiler_path, "active.rs", bad);
+    collect_lang_features_in(&mut features, base_compiler_path, "accepted.rs", bad);
+    collect_lang_features_in(&mut features, base_compiler_path, "removed.rs", bad);
+    features
 }
 
-fn collect_lang_features_in(base: &Path, file: &str, bad: &mut bool) -> Features {
+fn collect_lang_features_in(features: &mut Features, base: &Path, file: &str, bad: &mut bool) {
     let path = base.join("rustc_feature").join("src").join(file);
     let contents = t!(fs::read_to_string(&path));
 
@@ -298,135 +299,145 @@ fn collect_lang_features_in(base: &Path, file: &str, bad: &mut bool) -> Features
     let mut in_feature_group = false;
     let mut prev_names = vec![];
 
-    contents
-        .lines()
-        .zip(1..)
-        .filter_map(|(line, line_number)| {
-            let line = line.trim();
-
-            // Within -start and -end, the tracking issue can be omitted.
-            match line {
-                "// no-tracking-issue-start" => {
-                    next_feature_omits_tracking_issue = true;
-                    return None;
-                }
-                "// no-tracking-issue-end" => {
-                    next_feature_omits_tracking_issue = false;
-                    return None;
-                }
-                _ => {}
+    let lines = contents.lines().zip(1..);
+    for (line, line_number) in lines {
+        let line = line.trim();
+
+        // Within -start and -end, the tracking issue can be omitted.
+        match line {
+            "// no-tracking-issue-start" => {
+                next_feature_omits_tracking_issue = true;
+                continue;
             }
+            "// no-tracking-issue-end" => {
+                next_feature_omits_tracking_issue = false;
+                continue;
+            }
+            _ => {}
+        }
 
-            if line.starts_with(FEATURE_GROUP_START_PREFIX) {
-                if in_feature_group {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: \
+        if line.starts_with(FEATURE_GROUP_START_PREFIX) {
+            if in_feature_group {
+                tidy_error!(
+                    bad,
+                    "{}:{}: \
                         new feature group is started without ending the previous one",
-                        path.display(),
-                        line_number,
-                    );
-                }
-
-                in_feature_group = true;
-                prev_names = vec![];
-                return None;
-            } else if line.starts_with(FEATURE_GROUP_END_PREFIX) {
-                in_feature_group = false;
-                prev_names = vec![];
-                return None;
+                    path.display(),
+                    line_number,
+                );
             }
 
-            let mut parts = line.split(',');
-            let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
-                Some("active") => Status::Unstable,
-                Some("incomplete") => Status::Unstable,
-                Some("removed") => Status::Removed,
-                Some("accepted") => Status::Stable,
-                _ => return None,
-            };
-            let name = parts.next().unwrap().trim();
-
-            let since_str = parts.next().unwrap().trim().trim_matches('"');
-            let since = match since_str.parse() {
-                Ok(since) => Some(since),
-                Err(err) => {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: failed to parse since: {} ({:?})",
-                        path.display(),
-                        line_number,
-                        since_str,
-                        err,
-                    );
-                    None
-                }
-            };
-            if in_feature_group {
-                if prev_names.last() > Some(&name) {
-                    // This assumes the user adds the feature name at the end of the list, as we're
-                    // not looking ahead.
-                    let correct_index = match prev_names.binary_search(&name) {
-                        Ok(_) => {
-                            // This only occurs when the feature name has already been declared.
-                            tidy_error!(
-                                bad,
-                                "{}:{}: duplicate feature {}",
-                                path.display(),
-                                line_number,
-                                name,
-                            );
-                            // skip any additional checks for this line
-                            return None;
-                        }
-                        Err(index) => index,
-                    };
+            in_feature_group = true;
+            prev_names = vec![];
+            continue;
+        } else if line.starts_with(FEATURE_GROUP_END_PREFIX) {
+            in_feature_group = false;
+            prev_names = vec![];
+            continue;
+        }
 
-                    let correct_placement = if correct_index == 0 {
-                        "at the beginning of the feature group".to_owned()
-                    } else if correct_index == prev_names.len() {
-                        // I don't believe this is reachable given the above assumption, but it
-                        // doesn't hurt to be safe.
-                        "at the end of the feature group".to_owned()
-                    } else {
-                        format!(
-                            "between {} and {}",
-                            prev_names[correct_index - 1],
-                            prev_names[correct_index],
-                        )
-                    };
+        let mut parts = line.split(',');
+        let level = match parts.next().map(|l| l.trim().trim_start_matches('(')) {
+            Some("active") => Status::Unstable,
+            Some("incomplete") => Status::Unstable,
+            Some("removed") => Status::Removed,
+            Some("accepted") => Status::Stable,
+            _ => continue,
+        };
+        let name = parts.next().unwrap().trim();
+
+        let since_str = parts.next().unwrap().trim().trim_matches('"');
+        let since = match since_str.parse() {
+            Ok(since) => Some(since),
+            Err(err) => {
+                tidy_error!(
+                    bad,
+                    "{}:{}: failed to parse since: {} ({:?})",
+                    path.display(),
+                    line_number,
+                    since_str,
+                    err,
+                );
+                None
+            }
+        };
+        if in_feature_group {
+            if prev_names.last() > Some(&name) {
+                // This assumes the user adds the feature name at the end of the list, as we're
+                // not looking ahead.
+                let correct_index = match prev_names.binary_search(&name) {
+                    Ok(_) => {
+                        // This only occurs when the feature name has already been declared.
+                        tidy_error!(
+                            bad,
+                            "{}:{}: duplicate feature {}",
+                            path.display(),
+                            line_number,
+                            name,
+                        );
+                        // skip any additional checks for this line
+                        continue;
+                    }
+                    Err(index) => index,
+                };
 
-                    tidy_error!(
-                        bad,
-                        "{}:{}: feature {} is not sorted by feature name (should be {})",
-                        path.display(),
-                        line_number,
-                        name,
-                        correct_placement,
-                    );
-                }
-                prev_names.push(name);
+                let correct_placement = if correct_index == 0 {
+                    "at the beginning of the feature group".to_owned()
+                } else if correct_index == prev_names.len() {
+                    // I don't believe this is reachable given the above assumption, but it
+                    // doesn't hurt to be safe.
+                    "at the end of the feature group".to_owned()
+                } else {
+                    format!(
+                        "between {} and {}",
+                        prev_names[correct_index - 1],
+                        prev_names[correct_index],
+                    )
+                };
+
+                tidy_error!(
+                    bad,
+                    "{}:{}: feature {} is not sorted by feature name (should be {})",
+                    path.display(),
+                    line_number,
+                    name,
+                    correct_placement,
+                );
             }
+            prev_names.push(name);
+        }
 
-            let issue_str = parts.next().unwrap().trim();
-            let tracking_issue = if issue_str.starts_with("None") {
-                if level == Status::Unstable && !next_feature_omits_tracking_issue {
-                    tidy_error!(
-                        bad,
-                        "{}:{}: no tracking issue for feature {}",
-                        path.display(),
-                        line_number,
-                        name,
-                    );
-                }
-                None
-            } else {
-                let s = issue_str.split('(').nth(1).unwrap().split(')').next().unwrap();
-                Some(s.parse().unwrap())
-            };
-            Some((name.to_owned(), Feature { level, since, has_gate_test: false, tracking_issue }))
-        })
-        .collect()
+        let issue_str = parts.next().unwrap().trim();
+        let tracking_issue = if issue_str.starts_with("None") {
+            if level == Status::Unstable && !next_feature_omits_tracking_issue {
+                tidy_error!(
+                    bad,
+                    "{}:{}: no tracking issue for feature {}",
+                    path.display(),
+                    line_number,
+                    name,
+                );
+            }
+            None
+        } else {
+            let s = issue_str.split('(').nth(1).unwrap().split(')').next().unwrap();
+            Some(s.parse().unwrap())
+        };
+        match features.entry(name.to_owned()) {
+            Entry::Occupied(e) => {
+                tidy_error!(
+                    bad,
+                    "{}:{} feature {name} already specified with status '{}'",
+                    path.display(),
+                    line_number,
+                    e.get().level,
+                );
+            }
+            Entry::Vacant(e) => {
+                e.insert(Feature { level, since, has_gate_test: false, tracking_issue });
+            }
+        }
+    }
 }
 
 fn get_and_check_lib_features(