diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index abf3c9a363fca..4ed99df1e8169 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 f49805d9b4e15..6ed8370e8c1c7 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 441dc3c7e888c..5a5e9db81a243 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 9396d769dc702..8909cf33af911 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 c897a5e8701c4..16c634e9afd50 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 8f7abb55e2376..94546ca09d00d 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 60c2960b160ec..a8a1899317f87 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 894c7328b5f89..5958b389c9f4c 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 af83f14bbda5d..1714cec5221de 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 2362831d8b8f6..7a6e5952cb956 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 52801c35ba3d3..630fb2c41cded 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 9a73f1a53e52f..9b1212694f5ba 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` 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 0d38bd3935194..d5aeab47e62b0 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` 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 65cc4e2ef1e41..0417e952eb71d 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 0000000000000..d1f1819d9f35f --- /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 62f1a7f98ea16..ef1d866c3ff3b 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 0000000000000..43ff23600766e --- /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>; + +fn bar_function(function: Foo) -> RcFoo { + //~^ 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 0000000000000..4504039097b5d --- /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(function: Foo) -> RcFoo { + | ------------ ^^^^^^^^ 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 428cc637f5567..3470a7e09ad8b 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 1534b6d07deb0..6da99cf6435cf 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 e30747782339c..fdfc5907c1674 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 bdc66e83083f6..74c35083e60b5 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 62968ea54e0aa..70b8d8da19b32 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 2494af1208f14..6b7b0c819cca6 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 d8b3903b98e7c..f10ecf5f201e3 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(