From 6000edd1372a526f67ef0302de7d8290a4e35f62 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Thu, 21 May 2026 13:19:55 -0700 Subject: [PATCH 01/23] Add `T: PartialEq` bounds to derived `StructuralPartialEq` impls. Fixes . The changes in `const_to_pat.rs` are entirely to avoid regressing diagnostic quality, and should not make any difference to what code is accepted. The change in `compute_applicable_impls_for_diagnostics` and its callers is entirely to be able to reuse that algorithm for this purpose. --- .../src/deriving/cmp/partial_eq.rs | 5 +- .../src/thir/pattern/const_to_pat.rs | 115 ++++++++++++++---- .../src/error_reporting/infer/mod.rs | 3 +- .../src/error_reporting/traits/ambiguity.rs | 44 ++++--- ...erive-and-manual-partialeq-issue-147714.rs | 31 +++++ ...e-and-manual-partialeq-issue-147714.stderr | 20 +++ .../ui/consts/const_in_pattern/issue-65466.rs | 2 +- .../const_in_pattern/issue-65466.stderr | 2 +- tests/ui/derives/deriving-all-codegen.stdout | 14 ++- 9 files changed, 184 insertions(+), 52 deletions(-) create mode 100644 tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.rs create mode 100644 tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.stderr diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs index c8ec5deb92c43..372b4e820f72d 100644 --- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs +++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs @@ -22,7 +22,10 @@ pub(crate) fn expand_deriving_partial_eq( path: path_std!(marker::StructuralPartialEq), skip_path_as_bound: true, // crucial! needs_copy_as_bound_if_packed: false, - additional_bounds: Vec::new(), + // The `StructuralPartialEq` impl must have the *same* bounds as the `PartialEq` impl, + // or it will apply in situations where it should not, such as in the bug + // . + additional_bounds: vec![ty::Ty::Path(path_std!(cmp::PartialEq))], // We really don't support unions, but that's already checked by the impl generated below; // a second check here would lead to redundant error messages. supports_unions: true, diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 0c6e1b83c9535..a8de38e5ceb71 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -15,6 +15,9 @@ use rustc_middle::thir::{FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitableExt, TypeVisitor}; use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, Span}; +use rustc_trait_selection::error_reporting::traits::ambiguity::{ + CandidateSource, compute_applicable_impls_for_diagnostics, +}; use rustc_trait_selection::traits::ObligationCause; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use tracing::{debug, instrument, trace}; @@ -209,15 +212,35 @@ impl<'tcx> ConstToPat<'tcx> { let kind = match ty.kind() { // Extremely important check for all ADTs! - // Make sure they are eligible to be used in patterns, and if not, emit an error. + // Make sure they are eligible to be used in patterns (structural), and if not, emit an + // error. ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { // This ADT cannot be used as a constant in patterns. debug!(?adt_def, ?value.ty, "ADT type in pattern is not `type_marked_structural`"); let PartialEqImplStatus { - is_derived, structural_partial_eq, non_blanket_impl, .. + is_derived, + possibly_inapplicable_structural_partial_eq, + non_blanket_impl, + possibly_inapplicable_derived_partial_eq, + has_impl, + .. } = type_has_partial_eq_impl(self.tcx, self.typing_env, ty); + + // If we have a derived PartialEq impl but it does not apply, + // then error about that instead, because `TypeNotStructural` gives advice that is + // relevant only when the problem is that `ty` does not derive `PartialEq`. + // + // Note that this is a duplicate of a check in `unevaluated_to_pat()`, + // which we would run later if we weren’t emitting an error now. + if possibly_inapplicable_derived_partial_eq && !has_impl { + let mut err = + self.tcx.dcx().create_err(TypeNotPartialEq { span: self.span, ty }); + extend_type_not_partial_eq(self.tcx, self.typing_env, ty, &mut err); + return self.mk_err(err, ty); + } + let (manual_partialeq_impl_span, manual_partialeq_impl_note) = - match (structural_partial_eq, non_blanket_impl) { + match (possibly_inapplicable_structural_partial_eq, non_blanket_impl) { (true, _) => (None, false), (_, Some(def_id)) if def_id.is_local() && !is_derived => { (Some(tcx.def_span(def_id)), false) @@ -465,8 +488,9 @@ fn extend_type_not_partial_eq<'tcx>( let PartialEqImplStatus { has_impl, is_derived, - structural_partial_eq, + possibly_inapplicable_structural_partial_eq: structural_partial_eq, non_blanket_impl, + possibly_inapplicable_derived_partial_eq: _, } = type_has_partial_eq_impl(self.tcx, self.typing_env, ty); match (has_impl, is_derived, structural_partial_eq, non_blanket_impl) { (_, _, true, _) => {} @@ -535,10 +559,20 @@ fn extend_type_not_partial_eq<'tcx>( #[derive(Debug)] struct PartialEqImplStatus { + /// There is a `PartialEq` impl that applies to the type. has_impl: bool, + + /// The `PartialEq` impl is `#[automatically_derived]`. is_derived: bool, - structural_partial_eq: bool, + /// The `DefId` of the same impl that `is_derived` refers to. non_blanket_impl: Option, + + /// If true, there is a `StructuralPartialEq` implementation, + /// but its bounds might not be satisfied. + possibly_inapplicable_structural_partial_eq: bool, + /// If true, there is a derived `PartialEq` implementation for the type, + /// but its bounds might not be satisfied. + possibly_inapplicable_derived_partial_eq: bool, } #[instrument(level = "trace", skip(tcx), ret)] @@ -557,23 +591,6 @@ fn type_has_partial_eq_impl<'tcx>( let structural_partial_eq_trait_id = tcx.require_lang_item(hir::LangItem::StructuralPeq, DUMMY_SP); - let partial_eq_obligation = Obligation::new( - tcx, - ObligationCause::dummy(), - param_env, - ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty]), - ); - - let mut automatically_derived = false; - let mut structural_peq = false; - let mut impl_def_id = None; - for def_id in tcx.non_blanket_impls_for_ty(partial_eq_trait_id, ty) { - automatically_derived = find_attr!(tcx, def_id, AutomaticallyDerived); - impl_def_id = Some(def_id); - } - for _ in tcx.non_blanket_impls_for_ty(structural_partial_eq_trait_id, ty) { - structural_peq = true; - } // This *could* accept a type that isn't actually `PartialEq`, because region bounds get // ignored. However that should be pretty much impossible since consts that do not depend on // generics can only mention the `'static` lifetime, and how would one have a type that's @@ -584,10 +601,60 @@ fn type_has_partial_eq_impl<'tcx>( // that patterns can only do things that the code could also do without patterns, but it is // needed for backwards compatibility. The actual pattern matching compares primitive values, // `PartialEq::eq` never gets invoked, so there's no risk of us running non-const code. + let has_impl = { + let obligation = Obligation::new( + tcx, + ObligationCause::dummy(), + param_env, + ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty]), + ); + infcx.predicate_must_hold_modulo_regions(&obligation) + }; + + // Determine whether there are is a derived `PartialEq` implementation, whether or not its + // bounds are met. + let possibly_inapplicable_derived_partial_eq = { + let obligation = Obligation::new( + tcx, + ObligationCause::dummy(), + param_env, + ty::Binder::dummy(ty::TraitRef::new(tcx, partial_eq_trait_id, [ty, ty])), + ); + compute_applicable_impls_for_diagnostics(&infcx, &obligation, true).iter().any( + |candidate_source| { + matches!( + candidate_source, + &CandidateSource::DefId(def_id) + if find_attr!(tcx, def_id, AutomaticallyDerived) + ) + }, + ) + }; + + let possibly_inapplicable_structural_partial_eq = { + let obligation = Obligation::new( + tcx, + ObligationCause::dummy(), + param_env, + ty::Binder::dummy(ty::TraitRef::new(tcx, structural_partial_eq_trait_id, [ty])), + ); + compute_applicable_impls_for_diagnostics(&infcx, &obligation, true) + .iter() + .any(|candidate_source| matches!(candidate_source, CandidateSource::DefId(_))) + }; + + let mut automatically_derived = false; + let mut impl_def_id = None; + for def_id in tcx.non_blanket_impls_for_ty(partial_eq_trait_id, ty) { + automatically_derived = find_attr!(tcx, def_id, AutomaticallyDerived); + impl_def_id = Some(def_id); + } + PartialEqImplStatus { - has_impl: infcx.predicate_must_hold_modulo_regions(&partial_eq_obligation), + has_impl, is_derived: automatically_derived, - structural_partial_eq: structural_peq, + possibly_inapplicable_structural_partial_eq, non_blanket_impl: impl_def_id, + possibly_inapplicable_derived_partial_eq, } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index c9e2312895820..c6e796c7e3de3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -268,7 +268,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let obligation = Obligation::new(tcx, ObligationCause::dummy(), param_env, ty::Binder::dummy(trait_ref)); - let applicable_impls = compute_applicable_impls_for_diagnostics(self.infcx, &obligation); + let applicable_impls = + compute_applicable_impls_for_diagnostics(self.infcx, &obligation, false); for candidate in applicable_impls { let impl_def_id = match candidate { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs index 108262d507ef7..904612015c404 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs @@ -32,6 +32,7 @@ pub enum CandidateSource { pub fn compute_applicable_impls_for_diagnostics<'tcx>( infcx: &InferCtxt<'tcx>, obligation: &PolyTraitObligation<'tcx>, + ignore_predicates_of_impls: bool, ) -> Vec { let tcx = infcx.tcx; let param_env = obligation.param_env; @@ -71,26 +72,28 @@ pub fn compute_applicable_impls_for_diagnostics<'tcx>( _ => return false, } - let obligations = tcx - .predicates_of(impl_def_id) - .instantiate(tcx, impl_args) - .into_iter() - .map(|(predicate, _)| { - Obligation::new( - tcx, - ObligationCause::dummy(), - param_env, - predicate.skip_norm_wip(), - ) - }) - // Kinda hacky, but let's just throw away obligations that overflow. - // This may reduce the accuracy of this check (if the obligation guides - // inference or it actually resulted in error after others are processed) - // ... but this is diagnostics code. - .filter(|obligation| { - infcx.next_trait_solver() || infcx.evaluate_obligation(obligation).is_ok() - }); - ocx.register_obligations(obligations); + if !ignore_predicates_of_impls { + let obligations = tcx + .predicates_of(impl_def_id) + .instantiate(tcx, impl_args) + .into_iter() + .map(|(predicate, _)| { + Obligation::new( + tcx, + ObligationCause::dummy(), + param_env, + predicate.skip_norm_wip(), + ) + }) + // Kinda hacky, but let's just throw away obligations that overflow. + // This may reduce the accuracy of this check (if the obligation guides + // inference or it actually resulted in error after others are processed) + // ... but this is diagnostics code. + .filter(|obligation| { + infcx.next_trait_solver() || infcx.evaluate_obligation(obligation).is_ok() + }); + ocx.register_obligations(obligations); + } ocx.try_evaluate_obligations().is_empty() }) @@ -306,6 +309,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let mut ambiguities = compute_applicable_impls_for_diagnostics( self.infcx, &obligation.with(self.tcx, trait_pred), + false, ); let has_non_region_infer = trait_pred .skip_binder() diff --git a/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.rs b/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.rs new file mode 100644 index 0000000000000..493023f6bdb0e --- /dev/null +++ b/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.rs @@ -0,0 +1,31 @@ +// `derive(PartialEq)`, which also implements `StructuralPartialEq`, must not allow the latter impl +// to be used with other non-derived implementations of `PartialEq`. +// +// Test case by theemathas from . + +#[allow(dead_code)] +#[derive(PartialEq)] +enum Thing { + A(T), + B, +} + +struct Incomparable; + +// This impl does not obey StructuralPartialEq's rules. +impl PartialEq for Thing { + fn eq(&self, _: &Self) -> bool { + panic!() + } +} + +// This constant does not obey StructuralPartialEq's rules, so it should not +// implement StructuralPartialEq. +const X: Thing = Thing::B; + +fn main() { + if let X = X { + //~^ ERROR constant of non-structural type `Thing` in a pattern + println!("equal"); + } +} diff --git a/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.stderr b/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.stderr new file mode 100644 index 0000000000000..52497e23df04d --- /dev/null +++ b/tests/ui/consts/const_in_pattern/derive-and-manual-partialeq-issue-147714.stderr @@ -0,0 +1,20 @@ +error: constant of non-structural type `Thing` in a pattern + --> $DIR/derive-and-manual-partialeq-issue-147714.rs:27:12 + | +LL | enum Thing { + | ------------- `Thing` must be annotated with `#[derive(PartialEq)]` to be usable in patterns +... +LL | const X: Thing = Thing::B; + | ---------------------------- constant defined here +... +LL | if let X = X { + | ^ constant of non-structural type + | +help: if `Thing` manually implemented `PartialEq`, you could check for equality instead of pattern matching + | +LL - if let X = X { +LL + if X == X { + | + +error: aborting due to 1 previous error + diff --git a/tests/ui/consts/const_in_pattern/issue-65466.rs b/tests/ui/consts/const_in_pattern/issue-65466.rs index 82838657b02f5..dba8e7a92163a 100644 --- a/tests/ui/consts/const_in_pattern/issue-65466.rs +++ b/tests/ui/consts/const_in_pattern/issue-65466.rs @@ -11,7 +11,7 @@ const C: &[O] = &[O::None]; fn main() { let x = O::None; match &[x][..] { - C => (), //~ ERROR constant of non-structural type `&[O]` in a pattern + C => (), //~ ERROR constant of non-structural type `O` in a pattern _ => (), } } diff --git a/tests/ui/consts/const_in_pattern/issue-65466.stderr b/tests/ui/consts/const_in_pattern/issue-65466.stderr index 511a38bbc00d8..29c49223f044a 100644 --- a/tests/ui/consts/const_in_pattern/issue-65466.stderr +++ b/tests/ui/consts/const_in_pattern/issue-65466.stderr @@ -1,4 +1,4 @@ -error: constant of non-structural type `&[O]` in a pattern +error: constant of non-structural type `O` in a pattern --> $DIR/issue-65466.rs:14:9 | LL | struct B; diff --git a/tests/ui/derives/deriving-all-codegen.stdout b/tests/ui/derives/deriving-all-codegen.stdout index 94e8b886436df..4f56ce5765685 100644 --- a/tests/ui/derives/deriving-all-codegen.stdout +++ b/tests/ui/derives/deriving-all-codegen.stdout @@ -836,7 +836,10 @@ impl ::core::hash::Hash } } #[automatically_derived] -impl ::core::marker::StructuralPartialEq for Generic { } +impl + ::core::marker::StructuralPartialEq for Generic where + T::A: ::core::cmp::PartialEq { +} #[automatically_derived] impl ::core::cmp::PartialEq for Generic where @@ -953,8 +956,9 @@ impl ::core::marker::StructuralPartialEq for PackedGeneric - { +impl + ::core::marker::StructuralPartialEq for PackedGeneric where + T::A: ::core::cmp::PartialEq { } #[automatically_derived] impl ::core::hash::Hash for } } #[automatically_derived] -impl ::core::marker::StructuralPartialEq for EnumGeneric { } +impl + ::core::marker::StructuralPartialEq for EnumGeneric { +} #[automatically_derived] impl ::core::cmp::PartialEq for EnumGeneric { From 2380ffd7e3aa00ce0bda55a4f983236e41fbb8ec Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 26 May 2026 10:23:32 -0700 Subject: [PATCH 02/23] Implement `std::sealed::Sealed` for numeric types --- library/std/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 7a548dfb33da7..007455bd1370c 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -783,6 +783,15 @@ mod sealed { pub trait Sealed {} } +macro_rules! impl_sealed { + ($($t:ty)*) => {$( + /// Allows implementations within `std`. + #[unstable(feature = "sealed", issue = "none")] + impl crate::sealed::Sealed for $t {} + )*} +} +impl_sealed! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 f32 f64 } + #[cfg(test)] #[allow(dead_code)] // Not used in all configurations. pub(crate) mod test_helpers; From 02777c4acfb27e25d012b4c0a2c42a9d1b5ab41a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 21 May 2026 07:21:20 +0200 Subject: [PATCH 03/23] Add `io::Read::read_le` and `io::Read::read_be` These functions make it easy to read a fixed-size type as little-endian or big-endian. They're trivial wrappers around the combination of `io::Read::read_array` and `T::from_le_bytes`/`T::from_be_bytes`. The implementation uses a sealed trait `FromEndianBytes`. That trait is currently in `std` and accepts a `&mut io::Read`. Once we can use associated consts in the types of method parameters, we can change this trait to have `from_le_bytes` and `from_be_bytes` methods, move it to `core`, and make it public. --- library/std/src/io/mod.rs | 97 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 43a6a18ac2c3f..09c41a5a24c9e 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -1265,6 +1265,72 @@ pub trait Read { assert_eq!(borrowed_buf.len(), N); Ok(unsafe { MaybeUninit::array_assume_init(buf) }) } + + /// Read and return a type (e.g. an integer) in little-endian order. + /// + /// You can specify the type with turbofish (`reader.read_le::()`), or let type inference + /// determine the type based on how the return value gets used. + /// + /// Like `read_exact`, if this function encounters an "end of file" before reading the desired + /// number of bytes, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// + /// ``` + /// #![feature(read_le)] + /// use std::io::Cursor; + /// use std::io::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buf = Cursor::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2]); + /// let x: u64 = buf.read_le()?; + /// let y: u32 = buf.read_le()?; + /// let z = buf.read_le::()?; + /// assert_eq!(x, 0x807060504030201); + /// assert_eq!(y, 0x6070809); + /// assert_eq!(z, 0x405); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "read_le", issue = "156983")] + #[inline] + fn read_le(&mut self) -> Result + where + Self: Sized, + { + T::read_le_from(self) + } + + /// Read and return a type (e.g. an integer) in big-endian order. + /// + /// You can specify the type with turbofish (`reader.read_be::()`), or let type inference + /// determine the type based on how the return value gets used. + /// + /// Like `read_exact`, if this function encounters an "end of file" before reading the desired + /// number of bytes, it returns an error of the kind [`ErrorKind::UnexpectedEof`]. + /// + /// ``` + /// #![feature(read_le)] + /// use std::io::Cursor; + /// use std::io::prelude::*; + /// + /// fn main() -> std::io::Result<()> { + /// let mut buf = Cursor::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2]); + /// let x: u64 = buf.read_be()?; + /// let y: u32 = buf.read_be()?; + /// let z = buf.read_be::()?; + /// assert_eq!(x, 0x102030405060708); + /// assert_eq!(y, 0x9080706); + /// assert_eq!(z, 0x504); + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "read_le", issue = "156983")] + #[inline] + fn read_be(&mut self) -> Result + where + Self: Sized, + { + T::read_be_from(self) + } } /// Reads all bytes from a [reader][Read] into a new [`String`]. @@ -3157,3 +3223,34 @@ impl Iterator for Lines { } } } + +/// Trait for types that can be converted from a fixed-size byte array with a specified endianness +#[unstable(feature = "read_le_be_internals", reason = "internals", issue = "none")] +// Once we can use associated consts in the types of method parameters, rewrite this to have +// `from_le_bytes` and `from_be_bytes` methods, move it to `core`, and make it public. +pub trait FromEndianBytes: crate::sealed::Sealed + Sized { + #[doc(hidden)] + fn read_le_from(r: &mut impl Read) -> Result; + + #[doc(hidden)] + fn read_be_from(r: &mut impl Read) -> Result; +} + +macro_rules! impl_from_endian_bytes { + ($($t:ty),*$(,)?) => {$( + #[unstable(feature = "read_le_be_internals", reason = "internals", issue = "none")] + impl FromEndianBytes for $t { + #[inline] + fn read_le_from(r: &mut impl Read) -> Result { + Ok(<$t>::from_le_bytes(r.read_array()?)) + } + + #[inline] + fn read_be_from(r: &mut impl Read) -> Result { + Ok(<$t>::from_be_bytes(r.read_array()?)) + } + } + )*}; +} + +impl_from_endian_bytes!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64); From 18da44f8f262e4decc9b0fe62b423e2456386513 Mon Sep 17 00:00:00 2001 From: vad Date: Mon, 1 Jun 2026 15:40:45 +0200 Subject: [PATCH 04/23] tests: codegen-llvm: Expect the new mangling scheme in bpf-abi-indirect-return The new Rust symbol mangling[0] uses prefix `_R` instead of `@_ZN`. [0] https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html --- tests/codegen-llvm/bpf-abi-indirect-return.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/codegen-llvm/bpf-abi-indirect-return.rs b/tests/codegen-llvm/bpf-abi-indirect-return.rs index b5787cc83a2b4..5e9cccb715cd1 100644 --- a/tests/codegen-llvm/bpf-abi-indirect-return.rs +++ b/tests/codegen-llvm/bpf-abi-indirect-return.rs @@ -16,7 +16,7 @@ fn outer(a: u64) -> u64 { inner_big(v).a[0] as u64 } -// CHECK-LABEL: define {{.*}} @_ZN{{.*}}inner_res{{.*}}E( +// CHECK-LABEL: define {{.*}} @_R{{.*}}inner_res( // CHECK-SAME: ptr{{[^,]*}}, // CHECK-SAME: i64{{[^)]*}} #[inline(never)] @@ -29,7 +29,7 @@ struct Big { b: u64, } -// CHECK-LABEL: define {{.*}} @_ZN{{.*}}inner_big{{.*}}E( +// CHECK-LABEL: define {{.*}} @_R{{.*}}inner_big( // CHECK-SAME: ptr{{[^,]*}}, // CHECK-SAME: i64{{[^)]*}} #[inline(never)] From cece4f3a1e117732f2e5e18474c71734cabebbf1 Mon Sep 17 00:00:00 2001 From: vad Date: Wed, 3 Jun 2026 16:21:20 +0200 Subject: [PATCH 05/23] tests: codegen-llvm: Use minicore in bpf-abi-indirect-return Instead of keeping it as `only-bpf`, use minicore. This way we make sure it will never get outdated again. --- tests/codegen-llvm/bpf-abi-indirect-return.rs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/tests/codegen-llvm/bpf-abi-indirect-return.rs b/tests/codegen-llvm/bpf-abi-indirect-return.rs index 5e9cccb715cd1..a0406742e2210 100644 --- a/tests/codegen-llvm/bpf-abi-indirect-return.rs +++ b/tests/codegen-llvm/bpf-abi-indirect-return.rs @@ -1,27 +1,17 @@ // Checks that results larger than one register are returned indirectly -//@ only-bpf +//@ add-minicore //@ needs-llvm-components: bpf //@ compile-flags: --target bpfel-unknown-none -#![no_std] -#![no_main] +#![crate_type = "lib"] +#![feature(no_core)] +#![no_core] + +extern crate minicore; #[no_mangle] fn outer(a: u64) -> u64 { - let v = match inner_res(a) { - Ok(v) => v, - Err(()) => 0, - }; - - inner_big(v).a[0] as u64 -} - -// CHECK-LABEL: define {{.*}} @_R{{.*}}inner_res( -// CHECK-SAME: ptr{{[^,]*}}, -// CHECK-SAME: i64{{[^)]*}} -#[inline(never)] -fn inner_res(a: u64) -> Result { - if a == 0 { Err(()) } else { Ok(a + 1) } + inner_big(a).b } struct Big { @@ -36,8 +26,3 @@ struct Big { fn inner_big(a: u64) -> Big { Big { a: [a as u16; 32], b: 42 } } - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} From 123d0d1f361205d81aca1d4c0bcc244bd192e816 Mon Sep 17 00:00:00 2001 From: Lars Schumann Date: Tue, 21 Apr 2026 20:04:28 +0000 Subject: [PATCH 06/23] constify array try from vec --- library/alloc/src/lib.rs | 1 + library/alloc/src/raw_vec/mod.rs | 6 +- library/alloc/src/vec/mod.rs | 17 +++-- .../consts/bad-array-size-in-type-err.stderr | 4 ++ .../const-eval/issue-65394.stock.stderr | 4 ++ tests/ui/consts/const-eval/livedrop.stderr | 4 ++ .../control-flow/drop-fail.precise.stderr | 10 +-- tests/ui/consts/control-flow/drop-fail.rs | 25 +++++--- .../control-flow/drop-fail.stock.stderr | 22 +++---- tests/ui/consts/miri_unleashed/assoc_const.rs | 15 +++-- .../consts/miri_unleashed/assoc_const.stderr | 24 +++---- tests/ui/consts/miri_unleashed/drop.rs | 18 ++++-- tests/ui/consts/miri_unleashed/drop.stderr | 10 +-- tests/ui/consts/promote-not.stderr | 4 ++ tests/ui/consts/promoted_const_call3.stderr | 8 +++ tests/ui/consts/promoted_const_call5.stderr | 4 ++ .../consts/qualif-indirect-mutation-fail.rs | 24 ++++--- .../qualif-indirect-mutation-fail.stderr | 62 +++++++++---------- tests/ui/drop/repeat-drop-2.stderr | 4 ++ ...op-elaboration-after-borrowck-error.stderr | 8 +++ ...-indestructible-indestructible.next.stderr | 12 ++-- ...h-indestructible-indestructible.old.stderr | 12 ++-- ...sure-with-indestructible-indestructible.rs | 14 ++++- 23 files changed, 196 insertions(+), 116 deletions(-) diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 7b41023ff31bf..e9a31b37e5af9 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -104,6 +104,7 @@ #![feature(const_convert)] #![feature(const_default)] #![feature(const_destruct)] +#![feature(const_drop_in_place)] #![feature(const_eval_select)] #![feature(const_heap)] #![feature(const_index)] diff --git a/library/alloc/src/raw_vec/mod.rs b/library/alloc/src/raw_vec/mod.rs index 0309d63cce06d..11cf4c4d8d0b6 100644 --- a/library/alloc/src/raw_vec/mod.rs +++ b/library/alloc/src/raw_vec/mod.rs @@ -417,7 +417,8 @@ impl RawVec { } } -unsafe impl<#[may_dangle] T, A: Allocator> Drop for RawVec { +#[rustc_const_unstable(feature = "const_heap", issue = "79597")] +const unsafe impl<#[may_dangle] T, A: [const] Allocator + [const] Destruct> Drop for RawVec { /// Frees the memory owned by the `RawVec` *without* trying to drop its contents. fn drop(&mut self) { // SAFETY: We are in a Drop impl, self.inner will not be used again. @@ -861,7 +862,10 @@ impl RawVecInner { } Ok(()) } +} +#[rustc_const_unstable(feature = "const_heap", issue = "79597")] +const impl RawVecInner { /// # Safety /// /// This function deallocates the owned allocation, but does not update `ptr` or `cap` to diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index a08e99a277d70..f16f2161b967c 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -79,9 +79,7 @@ use core::cmp::Ordering; use core::hash::{Hash, Hasher}; #[cfg(not(no_global_oom_handling))] use core::iter; -#[cfg(not(no_global_oom_handling))] -use core::marker::Destruct; -use core::marker::{Freeze, PhantomData}; +use core::marker::{Destruct, Freeze, PhantomData}; use core::mem::{self, Assume, ManuallyDrop, MaybeUninit, SizedTypeProperties, TransmuteFrom}; use core::ops::{self, Index, IndexMut, Range, RangeBounds}; use core::ptr::{self, NonNull}; @@ -2190,7 +2188,8 @@ impl Vec { /// [`spare_capacity_mut()`]: Vec::spare_capacity_mut #[inline] #[stable(feature = "rust1", since = "1.0.0")] - pub unsafe fn set_len(&mut self, new_len: usize) { + #[rustc_const_unstable(feature = "const_heap", issue = "79597")] + pub const unsafe fn set_len(&mut self, new_len: usize) { ub_checks::assert_unsafe_precondition!( check_library_ub, "Vec::set_len requires that new_len <= capacity()", @@ -4255,7 +4254,10 @@ impl Ord for Vec { } #[stable(feature = "rust1", since = "1.0.0")] -unsafe impl<#[may_dangle] T, A: Allocator> Drop for Vec { +#[rustc_const_unstable(feature = "const_heap", issue = "79597")] +const unsafe impl<#[may_dangle] T: [const] Destruct, A: [const] Allocator + [const] Destruct> Drop + for Vec +{ fn drop(&mut self) { unsafe { // use drop for [T] @@ -4474,7 +4476,10 @@ impl From<&str> for Vec { } #[stable(feature = "array_try_from_vec", since = "1.48.0")] -impl TryFrom> for [T; N] { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +const impl + TryFrom> for [T; N] +{ type Error = Vec; /// Gets the entire contents of the `Vec` as an array, diff --git a/tests/ui/consts/bad-array-size-in-type-err.stderr b/tests/ui/consts/bad-array-size-in-type-err.stderr index 84e16f8d931ea..13b0b6c4f4a65 100644 --- a/tests/ui/consts/bad-array-size-in-type-err.stderr +++ b/tests/ui/consts/bad-array-size-in-type-err.stderr @@ -57,6 +57,10 @@ LL | pub const fn i(_: Wrap) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 7 previous errors diff --git a/tests/ui/consts/const-eval/issue-65394.stock.stderr b/tests/ui/consts/const-eval/issue-65394.stock.stderr index f33593862a763..514d454df3dc0 100644 --- a/tests/ui/consts/const-eval/issue-65394.stock.stderr +++ b/tests/ui/consts/const-eval/issue-65394.stock.stderr @@ -6,6 +6,10 @@ LL | let mut x = Vec::::new(); ... LL | }; | - value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 1 previous error diff --git a/tests/ui/consts/const-eval/livedrop.stderr b/tests/ui/consts/const-eval/livedrop.stderr index 1add814060370..b090e35f07e3f 100644 --- a/tests/ui/consts/const-eval/livedrop.stderr +++ b/tests/ui/consts/const-eval/livedrop.stderr @@ -6,6 +6,10 @@ LL | let mut always_returned = None; ... LL | always_returned = never_returned; | --------------- value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error: aborting due to 1 previous error diff --git a/tests/ui/consts/control-flow/drop-fail.precise.stderr b/tests/ui/consts/control-flow/drop-fail.precise.stderr index 32afc51c3ee34..6f07d2329615b 100644 --- a/tests/ui/consts/control-flow/drop-fail.precise.stderr +++ b/tests/ui/consts/control-flow/drop-fail.precise.stderr @@ -1,14 +1,14 @@ -error[E0493]: destructor of `Option>` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:9:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:16:9 | -LL | let x = Some(Vec::new()); +LL | let x = Some(NotConstDestruct); | ^ the destructor for this type cannot be evaluated in constants ... LL | }; | - value is dropped here -error[E0493]: destructor of `Option>` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:40:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:47:9 | LL | let mut tmp = None; | ^^^^^^^ the destructor for this type cannot be evaluated in constants diff --git a/tests/ui/consts/control-flow/drop-fail.rs b/tests/ui/consts/control-flow/drop-fail.rs index 2b73e37b23d03..3593e5af26b00 100644 --- a/tests/ui/consts/control-flow/drop-fail.rs +++ b/tests/ui/consts/control-flow/drop-fail.rs @@ -3,10 +3,17 @@ #![feature(const_destruct)] #![cfg_attr(precise, feature(const_precise_live_drops))] + +struct NotConstDestruct; + +impl Drop for NotConstDestruct { + fn drop(&mut self) {} +} + // `x` is *not* always moved into the final value and may be dropped inside the initializer. -const _: Option> = { - let y: Option> = None; - let x = Some(Vec::new()); +const _: Option = { + let y: Option = None; + let x = Some(NotConstDestruct); //[stock,precise]~^ ERROR destructor of if true { @@ -18,16 +25,16 @@ const _: Option> = { // We only clear `NeedsDrop` if a local is moved from in entirely. This is a shortcoming of the // existing analysis. -const _: Vec = { - let vec_tuple = (Vec::new(),); +const _: NotConstDestruct = { + let vec_tuple = (NotConstDestruct,); //[stock]~^ ERROR destructor of vec_tuple.0 }; // This applies to single-field enum variants as well. -const _: Vec = { - let x: Result<_, Vec> = Ok(Vec::new()); +const _: NotConstDestruct = { + let x: Result<_, NotConstDestruct> = Ok(NotConstDestruct); //[stock]~^ ERROR destructor of match x { @@ -35,8 +42,8 @@ const _: Vec = { } }; -const _: Option> = { - let mut some = Some(Vec::new()); +const _: Option = { + let mut some = Some(NotConstDestruct); let mut tmp = None; //[stock,precise]~^ ERROR destructor of diff --git a/tests/ui/consts/control-flow/drop-fail.stock.stderr b/tests/ui/consts/control-flow/drop-fail.stock.stderr index 8fe60fd736765..3caef5810939c 100644 --- a/tests/ui/consts/control-flow/drop-fail.stock.stderr +++ b/tests/ui/consts/control-flow/drop-fail.stock.stderr @@ -1,32 +1,32 @@ -error[E0493]: destructor of `Option>` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:9:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:16:9 | -LL | let x = Some(Vec::new()); +LL | let x = Some(NotConstDestruct); | ^ the destructor for this type cannot be evaluated in constants ... LL | }; | - value is dropped here -error[E0493]: destructor of `(Vec,)` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:22:9 +error[E0493]: destructor of `(NotConstDestruct,)` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:29:9 | -LL | let vec_tuple = (Vec::new(),); +LL | let vec_tuple = (NotConstDestruct,); | ^^^^^^^^^ the destructor for this type cannot be evaluated in constants ... LL | }; | - value is dropped here -error[E0493]: destructor of `Result, Vec>` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:30:9 +error[E0493]: destructor of `Result` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:37:9 | -LL | let x: Result<_, Vec> = Ok(Vec::new()); +LL | let x: Result<_, NotConstDestruct> = Ok(NotConstDestruct); | ^ the destructor for this type cannot be evaluated in constants ... LL | }; | - value is dropped here -error[E0493]: destructor of `Option>` cannot be evaluated at compile-time - --> $DIR/drop-fail.rs:40:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/drop-fail.rs:47:9 | LL | let mut tmp = None; | ^^^^^^^ the destructor for this type cannot be evaluated in constants diff --git a/tests/ui/consts/miri_unleashed/assoc_const.rs b/tests/ui/consts/miri_unleashed/assoc_const.rs index 812207ee80900..753a53077e086 100644 --- a/tests/ui/consts/miri_unleashed/assoc_const.rs +++ b/tests/ui/consts/miri_unleashed/assoc_const.rs @@ -4,6 +4,13 @@ // a test demonstrating why we do need to run static const qualification on associated constants // instead of just checking the final constant + +struct NotConstDestruct; + +impl Drop for NotConstDestruct { + fn drop(&mut self) {} +} + trait Foo { const X: T; } @@ -15,18 +22,18 @@ trait Bar> { impl Foo for () { const X: u32 = 42; } -impl Foo> for String { - const X: Vec = Vec::new(); +impl Foo for NotConstDestruct { + const X: NotConstDestruct = NotConstDestruct; } impl Bar for () {} -impl Bar, String> for String {} +impl Bar for NotConstDestruct {} fn main() { // this is fine, but would have been forbidden by the static checks on `F` let x = <() as Bar>::F; // this test only causes errors due to the line below, so post-monomorphization - let y = , String>>::F; + let y = >::F; } //~? WARN skipping const checks diff --git a/tests/ui/consts/miri_unleashed/assoc_const.stderr b/tests/ui/consts/miri_unleashed/assoc_const.stderr index 0b276c8d70705..29f371373aef9 100644 --- a/tests/ui/consts/miri_unleashed/assoc_const.stderr +++ b/tests/ui/consts/miri_unleashed/assoc_const.stderr @@ -1,32 +1,32 @@ -error[E0080]: calling non-const function ` as Drop>::drop` - --> $DIR/assoc_const.rs:12:31 +error[E0080]: calling non-const function `::drop` + --> $DIR/assoc_const.rs:19:31 | LL | const F: u32 = (U::X, 42).1; - | ^ evaluation of `, std::string::String>>::F` failed inside this call + | ^ evaluation of `>::F` failed inside this call | -note: inside `std::ptr::drop_glue::<(Vec, u32)> - shim(Some((Vec, u32)))` +note: inside `std::ptr::drop_glue::<(NotConstDestruct, u32)> - shim(Some((NotConstDestruct, u32)))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue::> - shim(Some(Vec))` +note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL note: erroneous constant encountered - --> $DIR/assoc_const.rs:29:13 + --> $DIR/assoc_const.rs:36:13 | -LL | let y = , String>>::F; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let y = >::F; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/assoc_const.rs:29:13 + --> $DIR/assoc_const.rs:36:13 | -LL | let y = , String>>::F; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let y = >::F; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: skipping const checks | help: skipping check that does not even have a feature gate - --> $DIR/assoc_const.rs:12:20 + --> $DIR/assoc_const.rs:19:20 | LL | const F: u32 = (U::X, 42).1; | ^^^^^^^^^^ diff --git a/tests/ui/consts/miri_unleashed/drop.rs b/tests/ui/consts/miri_unleashed/drop.rs index 0edd3a03c5996..dc7d3f5aa243e 100644 --- a/tests/ui/consts/miri_unleashed/drop.rs +++ b/tests/ui/consts/miri_unleashed/drop.rs @@ -2,20 +2,26 @@ use std::mem::ManuallyDrop; + +struct NotConstDestruct; + +impl Drop for NotConstDestruct { + fn drop(&mut self) {} +} + fn main() {} static TEST_OK: () = { - let v: Vec = Vec::new(); + let v: NotConstDestruct = NotConstDestruct; let _v = ManuallyDrop::new(v); }; // Make sure we catch executing bad drop functions. // The actual error is tested by the error-pattern above. static TEST_BAD: () = { - let _v: Vec = Vec::new(); -}; -//~^ NOTE failed inside this call -//~| ERROR calling non-const function ` as Drop>::drop` -//~| NOTE inside `std::ptr::drop_glue::> - shim(Some(Vec))` + let _v: NotConstDestruct = NotConstDestruct; +}; //~ NOTE failed inside this call + //~| ERROR calling non-const function `::drop` + //~| NOTE inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` //~? WARN skipping const checks diff --git a/tests/ui/consts/miri_unleashed/drop.stderr b/tests/ui/consts/miri_unleashed/drop.stderr index 2dfe75c468595..6e2afda763171 100644 --- a/tests/ui/consts/miri_unleashed/drop.stderr +++ b/tests/ui/consts/miri_unleashed/drop.stderr @@ -1,18 +1,18 @@ -error[E0080]: calling non-const function ` as Drop>::drop` - --> $DIR/drop.rs:16:1 +error[E0080]: calling non-const function `::drop` + --> $DIR/drop.rs:23:1 | LL | }; | ^ evaluation of `TEST_BAD` failed inside this call | -note: inside `std::ptr::drop_glue::> - shim(Some(Vec))` +note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL warning: skipping const checks | help: skipping check that does not even have a feature gate - --> $DIR/drop.rs:15:9 + --> $DIR/drop.rs:22:9 | -LL | let _v: Vec = Vec::new(); +LL | let _v: NotConstDestruct = NotConstDestruct; | ^^ error: aborting due to 1 previous error; 1 warning emitted diff --git a/tests/ui/consts/promote-not.stderr b/tests/ui/consts/promote-not.stderr index ec552d9dd7d48..ebe2a963a74b1 100644 --- a/tests/ui/consts/promote-not.stderr +++ b/tests/ui/consts/promote-not.stderr @@ -46,6 +46,10 @@ LL | let x = &String::new(); ... LL | }; | - value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0716]: temporary value dropped while borrowed --> $DIR/promote-not.rs:60:33 diff --git a/tests/ui/consts/promoted_const_call3.stderr b/tests/ui/consts/promoted_const_call3.stderr index 34c833d5bb77c..22d017abe6111 100644 --- a/tests/ui/consts/promoted_const_call3.stderr +++ b/tests/ui/consts/promoted_const_call3.stderr @@ -6,6 +6,10 @@ LL | let _: &'static _ = &String::new(); LL | LL | }; | - value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `String` cannot be evaluated at compile-time --> $DIR/promoted_const_call3.rs:8:30 @@ -14,6 +18,10 @@ LL | let _: &'static _ = &id(&String::new()); | ^^^^^^^^^^^^^ - value is dropped here | | | the destructor for this type cannot be evaluated in constants + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0716]: temporary value dropped while borrowed --> $DIR/promoted_const_call3.rs:13:26 diff --git a/tests/ui/consts/promoted_const_call5.stderr b/tests/ui/consts/promoted_const_call5.stderr index 1b5fa4352837e..b65422e44a510 100644 --- a/tests/ui/consts/promoted_const_call5.stderr +++ b/tests/ui/consts/promoted_const_call5.stderr @@ -5,6 +5,10 @@ LL | let _: &'static _ = &id(&new_string()); | ^^^^^^^^^^^^ - value is dropped here | | | the destructor for this type cannot be evaluated in constants + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0716]: temporary value dropped while borrowed --> $DIR/promoted_const_call5.rs:31:26 diff --git a/tests/ui/consts/qualif-indirect-mutation-fail.rs b/tests/ui/consts/qualif-indirect-mutation-fail.rs index b70fca7b86430..00c9d4900f5f6 100644 --- a/tests/ui/consts/qualif-indirect-mutation-fail.rs +++ b/tests/ui/consts/qualif-indirect-mutation-fail.rs @@ -1,32 +1,38 @@ //@ compile-flags: --crate-type=lib #![feature(const_precise_live_drops)] +struct NotConstDestruct; + +impl Drop for NotConstDestruct { + fn drop(&mut self) {} +} + // Mutable borrow of a field with drop impl. pub const fn f() { - let mut a: (u32, Option) = (0, None); //~ ERROR destructor of + let mut a: (u32, Option) = (0, None); //~ ERROR destructor of let _ = &mut a.1; } // Mutable borrow of a type with drop impl. pub const A1: () = { let mut x = None; //~ ERROR destructor of - let mut y = Some(String::new()); + let mut y = Some(NotConstDestruct); let a = &mut x; let b = &mut y; std::mem::swap(a, b); std::mem::forget(y); -}; //~ ERROR calling non-const function ` as Drop>::drop` +}; //~ ERROR calling non-const function `::drop` // Mutable borrow of a type with drop impl. pub const A2: () = { let mut x = None; - let mut y = Some(String::new()); + let mut y = Some(NotConstDestruct); let a = &mut x; let b = &mut y; std::mem::swap(a, b); std::mem::forget(y); let _z = x; //~ ERROR destructor of -}; //~ ERROR calling non-const function ` as Drop>::drop` +}; //~ ERROR calling non-const function `::drop` // Shared borrow of a type that might be !Freeze and Drop. pub const fn g1() { @@ -43,19 +49,19 @@ pub const fn g2() { // Mutable raw reference to a Drop type. pub const fn address_of_mut() { - let mut x: Option = None; //~ ERROR destructor of + let mut x: Option = None; //~ ERROR destructor of &raw mut x; - let mut y: Option = None; //~ ERROR destructor of + let mut y: Option = None; //~ ERROR destructor of std::ptr::addr_of_mut!(y); } // Const raw reference to a Drop type. Conservatively assumed to allow mutation // until resolution of https://github.com/rust-lang/rust/issues/56604. pub const fn address_of_const() { - let x: Option = None; //~ ERROR destructor of + let x: Option = None; //~ ERROR destructor of &raw const x; - let y: Option = None; //~ ERROR destructor of + let y: Option = None; //~ ERROR destructor of std::ptr::addr_of!(y); } diff --git a/tests/ui/consts/qualif-indirect-mutation-fail.stderr b/tests/ui/consts/qualif-indirect-mutation-fail.stderr index 102bbe03b2efd..9bf679f8a5348 100644 --- a/tests/ui/consts/qualif-indirect-mutation-fail.stderr +++ b/tests/ui/consts/qualif-indirect-mutation-fail.stderr @@ -1,5 +1,5 @@ -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:12:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:18:9 | LL | let mut x = None; | ^^^^^ the destructor for this type cannot be evaluated in constants @@ -7,51 +7,47 @@ LL | let mut x = None; LL | }; | - value is dropped here -error[E0080]: calling non-const function ` as Drop>::drop` - --> $DIR/qualif-indirect-mutation-fail.rs:18:1 +error[E0080]: calling non-const function `::drop` + --> $DIR/qualif-indirect-mutation-fail.rs:24:1 | LL | }; | ^ evaluation of `A1` failed inside this call | -note: inside `std::ptr::drop_glue::> - shim(Some(Option))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue:: - shim(Some(String))` +note: inside `std::ptr::drop_glue::> - shim(Some(Option))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue::> - shim(Some(Vec))` +note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:28:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:34:9 | LL | let _z = x; | ^^ the destructor for this type cannot be evaluated in constants LL | }; | - value is dropped here -error[E0080]: calling non-const function ` as Drop>::drop` - --> $DIR/qualif-indirect-mutation-fail.rs:29:1 +error[E0080]: calling non-const function `::drop` + --> $DIR/qualif-indirect-mutation-fail.rs:35:1 | LL | }; | ^ evaluation of `A2` failed inside this call | -note: inside `std::ptr::drop_glue::> - shim(Some(Option))` +note: inside `std::ptr::drop_glue::> - shim(Some(Option))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue:: - shim(Some(String))` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::drop_glue::> - shim(Some(Vec))` +note: inside `std::ptr::drop_glue:: - shim(Some(NotConstDestruct))` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -error[E0493]: destructor of `(u32, Option)` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:6:9 +error[E0493]: destructor of `(u32, Option)` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:12:9 | -LL | let mut a: (u32, Option) = (0, None); +LL | let mut a: (u32, Option) = (0, None); | ^^^^^ the destructor for this type cannot be evaluated in constant functions LL | let _ = &mut a.1; LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:33:9 + --> $DIR/qualif-indirect-mutation-fail.rs:39:9 | LL | let x: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions @@ -60,44 +56,44 @@ LL | } | - value is dropped here error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:41:9 + --> $DIR/qualif-indirect-mutation-fail.rs:47:9 | LL | let _y = x; | ^^ the destructor for this type cannot be evaluated in constant functions LL | } | - value is dropped here -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:49:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:55:9 | -LL | let mut y: Option = None; +LL | let mut y: Option = None; | ^^^^^ the destructor for this type cannot be evaluated in constant functions LL | std::ptr::addr_of_mut!(y); LL | } | - value is dropped here -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:46:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:52:9 | -LL | let mut x: Option = None; +LL | let mut x: Option = None; | ^^^^^ the destructor for this type cannot be evaluated in constant functions ... LL | } | - value is dropped here -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:59:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:65:9 | -LL | let y: Option = None; +LL | let y: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions LL | std::ptr::addr_of!(y); LL | } | - value is dropped here -error[E0493]: destructor of `Option` cannot be evaluated at compile-time - --> $DIR/qualif-indirect-mutation-fail.rs:56:9 +error[E0493]: destructor of `Option` cannot be evaluated at compile-time + --> $DIR/qualif-indirect-mutation-fail.rs:62:9 | -LL | let x: Option = None; +LL | let x: Option = None; | ^ the destructor for this type cannot be evaluated in constant functions ... LL | } diff --git a/tests/ui/drop/repeat-drop-2.stderr b/tests/ui/drop/repeat-drop-2.stderr index cea7baf697664..ff2bc7fe6bf1d 100644 --- a/tests/ui/drop/repeat-drop-2.stderr +++ b/tests/ui/drop/repeat-drop-2.stderr @@ -6,6 +6,10 @@ LL | const _: [String; 0] = [String::new(); 0]; | || | |the destructor for this type cannot be evaluated in constants | value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0382]: use of moved value: `foo` --> $DIR/repeat-drop-2.rs:4:17 diff --git a/tests/ui/mir/drop-elaboration-after-borrowck-error.stderr b/tests/ui/mir/drop-elaboration-after-borrowck-error.stderr index 22d05fa4ddab0..a3b8b97b3ac0c 100644 --- a/tests/ui/mir/drop-elaboration-after-borrowck-error.stderr +++ b/tests/ui/mir/drop-elaboration-after-borrowck-error.stderr @@ -6,6 +6,10 @@ LL | a[0] = String::new(); | | | the destructor for this type cannot be evaluated in statics | value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `[String; 1]` cannot be evaluated at compile-time --> $DIR/drop-elaboration-after-borrowck-error.rs:5:9 @@ -15,6 +19,10 @@ LL | let a: [String; 1]; ... LL | }; | - value is dropped here + | + = note: see issue #133214 for more information + = help: add `#![feature(const_destruct)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `T` cannot be evaluated at compile-time --> $DIR/drop-elaboration-after-borrowck-error.rs:17:9 diff --git a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.next.stderr b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.next.stderr index 8b5c4f59fbdee..62d9091c50b10 100644 --- a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.next.stderr +++ b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.next.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `Vec: const Destruct` is not satisfied - --> $DIR/const-closure-with-indestructible-indestructible.rs:10:16 +error[E0277]: the trait bound `NotConstDestruct: const Destruct` is not satisfied + --> $DIR/const-closure-with-indestructible-indestructible.rs:18:16 | LL | i_need(const || { | _________------_^ | | | | | required by a bound introduced by this call LL | | -LL | | let y = v; +LL | | let y = n; LL | | }) | |_________^ | note: required by a bound in `i_need` - --> $DIR/const-closure-with-indestructible-indestructible.rs:5:20 + --> $DIR/const-closure-with-indestructible-indestructible.rs:13:20 | -LL | const fn i_need(x: F) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `i_need` +LL | const fn i_need(x: F) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `i_need` error: aborting due to 1 previous error diff --git a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.old.stderr b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.old.stderr index 8b5c4f59fbdee..62d9091c50b10 100644 --- a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.old.stderr +++ b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.old.stderr @@ -1,20 +1,20 @@ -error[E0277]: the trait bound `Vec: const Destruct` is not satisfied - --> $DIR/const-closure-with-indestructible-indestructible.rs:10:16 +error[E0277]: the trait bound `NotConstDestruct: const Destruct` is not satisfied + --> $DIR/const-closure-with-indestructible-indestructible.rs:18:16 | LL | i_need(const || { | _________------_^ | | | | | required by a bound introduced by this call LL | | -LL | | let y = v; +LL | | let y = n; LL | | }) | |_________^ | note: required by a bound in `i_need` - --> $DIR/const-closure-with-indestructible-indestructible.rs:5:20 + --> $DIR/const-closure-with-indestructible-indestructible.rs:13:20 | -LL | const fn i_need(x: F) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `i_need` +LL | const fn i_need(x: F) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `i_need` error: aborting due to 1 previous error diff --git a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.rs b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.rs index d26260dd2d078..c6dcd119347d4 100644 --- a/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.rs +++ b/tests/ui/traits/const-traits/const-closure-with-indestructible-indestructible.rs @@ -2,14 +2,22 @@ //@ ignore-compare-mode-next-solver (explicit revisions) //@[next] compile-flags: -Znext-solver #![feature(const_trait_impl, const_closures, const_destruct)] -const fn i_need(x: F) {} + + +struct NotConstDestruct; + +impl Drop for NotConstDestruct { + fn drop(&mut self) {} +} + +const fn i_need(x: F) {} fn main() { const { - let v = Vec::::new(); + let n = NotConstDestruct; i_need(const || { //~^ ERROR the trait bound - let y = v; + let y = n; }) }; } From db241b6b460d50553fc3881fedad4f0d29c5eeba Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 13 Jun 2026 19:05:21 +0100 Subject: [PATCH 07/23] std: sys: solid: clamp connect_timeout tv_sec instead of truncating connect_timeout cast timeout.as_secs() (u64) straight to tv_sec, which is c_long (i32 on 32-bit Solid), so timeouts over ~68 years truncated to a wrong or negative value. Clamp to c_long::MAX, matching set_timeout in the same file. --- library/std/src/sys/net/connection/socket/solid.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/solid.rs b/library/std/src/sys/net/connection/socket/solid.rs index 137ea7e28cf36..9fe08ec951e75 100644 --- a/library/std/src/sys/net/connection/socket/solid.rs +++ b/library/std/src/sys/net/connection/socket/solid.rs @@ -142,8 +142,13 @@ impl Socket { return Err(io::Error::ZERO_TIMEOUT); } - let mut timeout = - netc::timeval { tv_sec: timeout.as_secs() as _, tv_usec: timeout.subsec_micros() as _ }; + let secs = if timeout.as_secs() > netc::c_long::MAX as u64 { + netc::c_long::MAX + } else { + timeout.as_secs() as netc::c_long + }; + + let mut timeout = netc::timeval { tv_sec: secs, tv_usec: timeout.subsec_micros() as _ }; if timeout.tv_sec == 0 && timeout.tv_usec == 0 { timeout.tv_usec = 1; } From 25acba896325cd73a09630e98e836e8096c8a182 Mon Sep 17 00:00:00 2001 From: Shivendra Sharma Date: Wed, 6 May 2026 14:15:43 +0530 Subject: [PATCH 08/23] diagnostics: point to coroutine body on higher-ranked auto trait errors --- .../nice_region_error/placeholder_error.rs | 43 ++++++++++++++++++- .../coroutine-auto-trait-span-issue-155880.rs | 30 +++++++++++++ ...outine-auto-trait-span-issue-155880.stderr | 17 ++++++++ ...er-ranked-auto-trait-11.assumptions.stderr | 5 ++- ...ranked-auto-trait-11.no_assumptions.stderr | 5 ++- .../issue-110963-early.no_assumptions.stderr | 10 ++++- tests/ui/coroutine/auto-trait-regions.stderr | 6 +++ 7 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 tests/ui/async-await/coroutine-auto-trait-span-issue-155880.rs create mode 100644 tests/ui/async-await/coroutine-auto-trait-span-issue-155880.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs index fdbf4cf228de9..9307ea265c180 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/placeholder_error.rs @@ -332,7 +332,7 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { leading_ellipsis, ); - self.tcx().dcx().create_err(TraitPlaceholderMismatch { + let mut err = self.tcx().dcx().create_err(TraitPlaceholderMismatch { span, satisfy_span, where_span, @@ -340,7 +340,46 @@ impl<'tcx> NiceRegionError<'_, 'tcx> { def_id, trait_def_id: self.tcx().def_path_str(trait_def_id), actual_impl_expl_notes, - }) + }); + + let mut current_code = cause.code(); + let mut coroutine_def_id = None; + + loop { + match current_code { + ObligationCauseCode::MatchImpl(inner_cause, _) => { + current_code = inner_cause.code(); + } + ObligationCauseCode::BuiltinDerived(derived) => { + let self_ty = derived.parent_trait_pred.skip_binder().self_ty(); + + if let ty::Coroutine(def_id, _) | ty::CoroutineWitness(def_id, _) = + self_ty.kind() + { + coroutine_def_id = Some(*def_id); + break; + } + + current_code = &derived.parent_code; + } + _ => break, + } + } + + if let Some(def_id) = coroutine_def_id { + if self.tcx().trait_is_auto(trait_def_id) { + let c_span = self.tcx().def_span(def_id); + let descr = self.tcx().def_descr(def_id); + let trait_name = self.tcx().def_path_str(trait_def_id); + + err.span_label( + c_span, + format!("this {descr} captures a value whose type is not `{trait_name}`"), + ); + } + } + + err } /// Add notes with details about the expected and actual trait refs, with attention to cases diff --git a/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.rs b/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.rs new file mode 100644 index 0000000000000..8bc8ef550ad5a --- /dev/null +++ b/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.rs @@ -0,0 +1,30 @@ +//@ edition: 2021 +// Regression test for + +trait Trait { + type Assoc<'a> + where + Self: 'a; +} + +impl Trait for T { + type Assoc<'a> = () + where + Self: 'a; +} + +async fn inner<'a, T: Trait + 'a>(_: T, x: T::Assoc<'a>) -> T::Assoc<'a> { + std::future::ready(x).await +} + +async fn outer<'a>() { + let x = 1u32; + inner(&x, ()).await; +} + +fn is_send(_: T) {} + +fn main() { + is_send(outer()) + //~^ ERROR implementation of `Send` is not general enough +} diff --git a/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.stderr b/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.stderr new file mode 100644 index 0000000000000..3048cc1d9c1c3 --- /dev/null +++ b/tests/ui/async-await/coroutine-auto-trait-span-issue-155880.stderr @@ -0,0 +1,17 @@ +error: implementation of `Send` is not general enough + --> $DIR/coroutine-auto-trait-span-issue-155880.rs:28:5 + | +LL | async fn inner<'a, T: Trait + 'a>(_: T, x: T::Assoc<'a>) -> T::Assoc<'a> { + | __________________________________________________________________________- +LL | | std::future::ready(x).await +LL | | } + | |_- this async fn captures a value whose type is not `Send` +... +LL | is_send(outer()) + | ^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough + | + = note: `Send` would have to be implemented for the type `&'0 u32`, for any lifetime `'0`... + = note: ...but `Send` is actually implemented for the type `&'1 u32`, for some specific lifetime `'1` + +error: aborting due to 1 previous error + diff --git a/tests/ui/async-await/higher-ranked-auto-trait-11.assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-11.assumptions.stderr index d39843f628c46..331a8c0540307 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-11.assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-11.assumptions.stderr @@ -11,7 +11,10 @@ error: implementation of `Send` is not general enough --> $DIR/higher-ranked-auto-trait-11.rs:27:9 | LL | Box::pin(async move { >::foo().await }) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough + | ^^^^^^^^^----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | | + | | this async block captures a value whose type is not `Send` + | implementation of `Send` is not general enough | = note: `Send` would have to be implemented for the type `>::Future`, for any lifetime `'0`... = note: ...but `Send` is actually implemented for the type `>::Future`, for some specific lifetime `'1` diff --git a/tests/ui/async-await/higher-ranked-auto-trait-11.no_assumptions.stderr b/tests/ui/async-await/higher-ranked-auto-trait-11.no_assumptions.stderr index d39843f628c46..331a8c0540307 100644 --- a/tests/ui/async-await/higher-ranked-auto-trait-11.no_assumptions.stderr +++ b/tests/ui/async-await/higher-ranked-auto-trait-11.no_assumptions.stderr @@ -11,7 +11,10 @@ error: implementation of `Send` is not general enough --> $DIR/higher-ranked-auto-trait-11.rs:27:9 | LL | Box::pin(async move { >::foo().await }) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Send` is not general enough + | ^^^^^^^^^----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | | + | | this async block captures a value whose type is not `Send` + | implementation of `Send` is not general enough | = note: `Send` would have to be implemented for the type `>::Future`, for any lifetime `'0`... = note: ...but `Send` is actually implemented for the type `>::Future`, for some specific lifetime `'1` diff --git a/tests/ui/async-await/return-type-notation/issue-110963-early.no_assumptions.stderr b/tests/ui/async-await/return-type-notation/issue-110963-early.no_assumptions.stderr index bb43636492409..6b434ecca0109 100644 --- a/tests/ui/async-await/return-type-notation/issue-110963-early.no_assumptions.stderr +++ b/tests/ui/async-await/return-type-notation/issue-110963-early.no_assumptions.stderr @@ -1,7 +1,10 @@ error: implementation of `Send` is not general enough --> $DIR/issue-110963-early.rs:17:5 | -LL | / spawn(async move { +LL | spawn(async move { + | ^ ---------- this async block captures a value whose type is not `Send` + | _____| + | | LL | | let mut hc = hc; LL | | if !hc.check().await { LL | | log_health_check_failure().await; @@ -15,7 +18,10 @@ LL | | }); error: implementation of `Send` is not general enough --> $DIR/issue-110963-early.rs:17:5 | -LL | / spawn(async move { +LL | spawn(async move { + | ^ ---------- this async block captures a value whose type is not `Send` + | _____| + | | LL | | let mut hc = hc; LL | | if !hc.check().await { LL | | log_health_check_failure().await; diff --git a/tests/ui/coroutine/auto-trait-regions.stderr b/tests/ui/coroutine/auto-trait-regions.stderr index beb689d868d46..04acddafc414c 100644 --- a/tests/ui/coroutine/auto-trait-regions.stderr +++ b/tests/ui/coroutine/auto-trait-regions.stderr @@ -33,6 +33,9 @@ LL | let generator = #[coroutine] static move || { error: implementation of `Foo` is not general enough --> $DIR/auto-trait-regions.rs:31:5 | +LL | let generator = #[coroutine] move || { + | ------- this coroutine captures a value whose type is not `Foo` +... LL | assert_foo(generator); | ^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough | @@ -42,6 +45,9 @@ LL | assert_foo(generator); error: implementation of `Foo` is not general enough --> $DIR/auto-trait-regions.rs:51:5 | +LL | let generator = #[coroutine] move || { + | ------- this coroutine captures a value whose type is not `Foo` +... LL | assert_foo(generator); | ^^^^^^^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough | From 796bc5886004800d5571de9a9bc9deb5b0e0640a Mon Sep 17 00:00:00 2001 From: cezarbbb Date: Tue, 9 Jun 2026 11:06:08 +0800 Subject: [PATCH 09/23] add -Zstaticlib-rename-internal-symbols --- .../rustc_codegen_ssa/src/back/archive.rs | 66 ++- compiler/rustc_codegen_ssa/src/back/link.rs | 31 +- .../rustc_codegen_ssa/src/back/symbol_edit.rs | 473 +++++++++++++++--- compiler/rustc_codegen_ssa/src/base.rs | 1 + compiler/rustc_codegen_ssa/src/errors.rs | 8 + compiler/rustc_codegen_ssa/src/lib.rs | 1 + compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_session/src/config.rs | 8 + compiler/rustc_session/src/options.rs | 4 +- .../staticlib-rename-internal-symbols.md | 19 + .../rmake.rs | 163 ++++++ .../dual_main.c | 14 + .../staticlib-rename-internal-symbols/liba.rs | 17 + .../staticlib-rename-internal-symbols/libb.rs | 15 + .../rmake.rs | 165 ++++++ ...ename-internal-symbols-wrong-crate-type.rs | 8 + ...e-internal-symbols-wrong-crate-type.stderr | 2 + 17 files changed, 904 insertions(+), 92 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/staticlib-rename-internal-symbols.md create mode 100644 tests/run-make/staticlib-rename-internal-symbols-macho/rmake.rs create mode 100644 tests/run-make/staticlib-rename-internal-symbols/dual_main.c create mode 100644 tests/run-make/staticlib-rename-internal-symbols/liba.rs create mode 100644 tests/run-make/staticlib-rename-internal-symbols/libb.rs create mode 100644 tests/run-make/staticlib-rename-internal-symbols/rmake.rs create mode 100644 tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.rs create mode 100644 tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index b5d5893941ef2..bdf346d8e69d2 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::env; use std::error::Error; use std::ffi::OsString; @@ -22,7 +23,7 @@ use tracing::trace; use super::metadata::{create_compressed_metadata_file, search_for_section}; use super::rmeta_link; -use super::symbol_edit::apply_hide; +use super::symbol_edit::{apply_edits, collect_internal_names}; use crate::common; // Public for ArchiveBuilderBuilder::extract_bundled_libs pub use crate::errors::ExtractBundledLibsError; @@ -314,12 +315,18 @@ pub enum AddArchiveKind<'a> { Other, } +pub struct ArchiveSymbols { + pub exported: FxHashSet, + pub rename_suffix: Option, + pub hide: bool, +} + pub trait ArchiveBuilder { fn add_file(&mut self, path: &Path, kind: ArchiveEntryKind); fn add_archive(&mut self, archive: &Path, kind: AddArchiveKind<'_>) -> io::Result<()>; - fn build(self: Box, output: &Path, exported_symbols: Option>) -> bool; + fn build(self: Box, output: &Path, symbols: Option) -> bool; } fn target_archive_format_to_object_kind(format: &str) -> Option { @@ -534,9 +541,9 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> { /// Combine the provided files, rlibs, and native libraries into a single /// `Archive`. - fn build(self: Box, output: &Path, exported_symbols: Option>) -> bool { + fn build(self: Box, output: &Path, symbols: Option) -> bool { let sess = self.sess; - match self.build_inner(output, exported_symbols) { + match self.build_inner(output, symbols) { Ok(any_members) => any_members, Err(error) => { sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error }) @@ -546,11 +553,7 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> { } impl<'a> ArArchiveBuilder<'a> { - fn build_inner( - self, - output: &Path, - exported_symbols: Option>, - ) -> io::Result { + fn build_inner(self, output: &Path, symbols: Option) -> io::Result { let archive_kind = match &*self.sess.target.archive_format { "gnu" => ArchiveKind::Gnu, "bsd" => ArchiveKind::Bsd, @@ -562,6 +565,39 @@ impl<'a> ArArchiveBuilder<'a> { } }; + // Collect all internally-defined symbol names across every Rust object file. + // This set is needed because rename must also apply to *undefined* references + // (cross-object calls within the staticlib), but we cannot use `!exported.contains(name)` + // alone — that would also match external C symbols like `malloc` which must not be renamed. + let rename = if let Some(sym) = &symbols + && let Some(rename_suffix) = sym.rename_suffix.as_deref() + { + let mut names = FxHashSet::default(); + for (_, entry) in &self.entries { + if entry.kind != ArchiveEntryKind::RustObj { + continue; + } + match &entry.source { + ArchiveEntrySource::Archive { archive_index, file_range } => { + let src_archive = &self.src_archives[*archive_index]; + let start = file_range.0 as usize; + let end = start + file_range.1 as usize; + if let Some(data) = src_archive.1.get(start..end) { + collect_internal_names(data, &sym.exported, &mut names); + } + } + ArchiveEntrySource::File(file) => { + if let Ok(data) = fs::read(file) { + collect_internal_names(&data, &sym.exported, &mut names); + } + } + } + } + Some((names, rename_suffix)) + } else { + None + }; + let mut entries = Vec::new(); for (entry_name, entry) in self.entries { @@ -588,9 +624,9 @@ impl<'a> ArArchiveBuilder<'a> { }; if entry.kind == ArchiveEntryKind::RustObj - && let Some(exported) = &exported_symbols + && let Some(sym) = &symbols { - Box::new(apply_hide(data, exported)) + Box::new(apply_edits(data, &sym.exported, sym.hide, rename.as_ref())) } else { Box::new(data) } @@ -602,9 +638,13 @@ impl<'a> ArArchiveBuilder<'a> { ) .map_err(|err| io_error_context("failed to map object file", err))?; if entry.kind == ArchiveEntryKind::RustObj - && let Some(exported) = &exported_symbols + && let Some(sym) = &symbols { - Box::new(apply_hide(&mmap, exported)) + let edited = apply_edits(&mmap, &sym.exported, sym.hide, rename.as_ref()); + match edited { + Cow::Borrowed(_) => Box::new(mmap) as Box>, + Cow::Owned(v) => Box::new(v), + } } else { Box::new(mmap) as Box> } diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index c68220aea78f7..2c3ee1bae09f8 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -53,7 +53,9 @@ use rustc_target::spec::{ }; use tracing::{debug, info, warn}; -use super::archive::{AddArchiveKind, ArchiveBuilder, ArchiveBuilderBuilder, ArchiveEntryKind}; +use super::archive::{ + AddArchiveKind, ArchiveBuilder, ArchiveBuilderBuilder, ArchiveEntryKind, ArchiveSymbols, +}; use super::command::Command; use super::linker::{self, Linker}; use super::metadata::{MetadataPosition, create_wrapper_file}; @@ -566,11 +568,21 @@ fn link_staticlib( sess.dcx().emit_fatal(e); } - let exported_symbols = if sess.opts.unstable_opts.staticlib_hide_internal_symbols { + let hide = sess.opts.unstable_opts.staticlib_hide_internal_symbols; + let rename = sess.opts.unstable_opts.staticlib_rename_internal_symbols; + + let exported_symbols = if hide || rename { if !matches!(sess.target.binary_format, BinaryFormat::Elf | BinaryFormat::MachO) { - sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported { - binary_format: sess.target.archive_format.to_string(), - }); + if hide { + sess.dcx().emit_warn(errors::StaticlibHideInternalSymbolsUnsupported { + binary_format: sess.target.archive_format.to_string(), + }); + } + if rename { + sess.dcx().emit_warn(errors::StaticlibRenameInternalSymbolsUnsupported { + binary_format: sess.target.archive_format.to_string(), + }); + } None } else { crate_info @@ -581,7 +593,14 @@ fn link_staticlib( } else { None }; - ab.build(out_filename, exported_symbols); + + let symbols = exported_symbols.map(|exported| ArchiveSymbols { + exported, + rename_suffix: rename.then(|| crate_info.symbol_rename_suffix.clone()), + hide, + }); + + ab.build(out_filename, symbols); let crates = crate_info.used_crates.iter(); diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_edit.rs b/compiler/rustc_codegen_ssa/src/back/symbol_edit.rs index 19ecb3d92d209..ef8008216187a 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_edit.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_edit.rs @@ -1,42 +1,170 @@ -// We use the `object` crate for the read-only pass over ELF/Mach-O object files -// because its `Sym`/`Nlist` traits provide clean access to symbol properties without -// manual byte parsing. However, `object` does not expose mutable views into the data, -// so we cannot use it to modify symbol fields in place. Instead, the read-only pass -// collects byte-level patches (offset + new value), and the write pass -// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O -// parsing — similar to how linker relocations work. +//! Binary-level symbol editing for staticlib post-processing. +//! +//! - **Hide**: sets STV_HIDDEN (ELF) or N_PEXT (Mach-O) on non-exported symbols. +//! - **Rename**: appends a vendor-specific suffix to non-exported symbol names by +//! rebuilding the string table. +use std::borrow::Cow; use std::mem; -use object::read::elf::Sym as _; +use object::read::elf::{SectionHeader as _, Sym as _}; use object::read::macho::Nlist; use object::{Endianness, elf, macho}; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -/// A byte-level patch collected in the read-only pass and applied in the write pass. struct Patch { offset: usize, value: u8, } -/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes. -fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec { - let mut buf = data.to_vec(); - for p in patches { - buf[p.offset] = p.value; +struct RenameEntry { + name_field_offset: usize, + name: String, +} + +pub(super) fn apply_edits<'a>( + data: &'a [u8], + exported: &FxHashSet, + hide: bool, + rename: Option<&(FxHashSet, &str)>, +) -> Cow<'a, [u8]> { + let result = match object::File::parse(data).ok() { + Some(object::File::Elf64(_)) => elf_edit_impl::>( + data, + exported, + hide, + rename, + mem::offset_of!(elf::Sym64, st_other), + ), + Some(object::File::Elf32(_)) => elf_edit_impl::>( + data, + exported, + hide, + rename, + mem::offset_of!(elf::Sym32, st_other), + ), + Some(object::File::MachO64(_)) => macho_edit_impl::>( + data, + exported, + hide, + rename, + mem::offset_of!(macho::Nlist64, n_type), + ), + Some(object::File::MachO32(_)) => macho_edit_impl::>( + data, + exported, + hide, + rename, + mem::offset_of!(macho::Nlist32, n_type), + ), + _ => None, + }; + match result { + Some(v) => Cow::Owned(v), + None => Cow::Borrowed(data), + } +} + +pub(super) fn collect_internal_names( + data: &[u8], + exported: &FxHashSet, + out: &mut FxHashSet, +) { + let Ok(file) = object::File::parse(data) else { return }; + match file { + object::File::Elf64(_) => { + elf_collect_impl::>(data, exported, out) + } + object::File::Elf32(_) => { + elf_collect_impl::>(data, exported, out) + } + object::File::MachO64(_) => { + macho_collect_impl::>(data, exported, out) + } + object::File::MachO32(_) => { + macho_collect_impl::>(data, exported, out) + } + _ => {} + } +} + +fn elf_collect_impl>( + data: &[u8], + exported: &FxHashSet, + out: &mut FxHashSet, +) where + u64: From, +{ + let Ok(header) = Elf::parse(data) else { return }; + let Ok(endian) = header.endian() else { return }; + let Ok(sections) = header.sections(endian, data) else { return }; + let Ok(symtab) = sections.symbols(endian, data, elf::SHT_SYMTAB) else { return }; + let strings = symtab.strings(); + + for sym in symtab.iter() { + let binding = sym.st_bind(); + if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK { + continue; + } + if sym.is_undefined(endian) { + continue; + } + let Ok(name_bytes) = sym.name(endian, strings) else { continue }; + let Ok(name) = str::from_utf8(name_bytes) else { continue }; + if !exported.contains(name) { + out.insert(name.to_string()); + } + } +} + +fn macho_collect_impl>( + data: &[u8], + exported: &FxHashSet, + out: &mut FxHashSet, +) { + let Ok(header) = Mach::parse(data, 0) else { return }; + let Ok(endian) = header.endian() else { return }; + let Ok(mut commands) = header.load_commands(endian, data, 0) else { return }; + + let symtab_cmd = loop { + let Ok(Some(cmd)) = commands.next() else { return }; + if let Ok(Some(st)) = cmd.symtab() { + break st; + } + }; + let Ok(symtab) = symtab_cmd.symbols::(endian, data) else { return }; + let strings = symtab.strings(); + + for nlist in symtab.iter() { + if nlist.is_stab() { + continue; + } + if nlist.is_undefined() { + continue; + } + if nlist.n_type() & macho::N_EXT == 0 { + continue; + } + let Ok(name_bytes) = nlist.name(endian, strings) else { continue }; + let Ok(name) = str::from_utf8(name_bytes) else { continue }; + let name = name.strip_prefix('_').unwrap_or(name); + if !exported.contains(name) { + out.insert(name.to_string()); + } } - buf } // --------------------------------------------------------------------------- -// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list +// ELF: single-pass collection + apply // --------------------------------------------------------------------------- -fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader>( - data: &'data [u8], - st_other_offset: usize, +fn elf_edit_impl>( + data: &[u8], exported: &FxHashSet, -) -> Option> + hide: bool, + rename: Option<&(FxHashSet, &str)>, + st_other_offset: usize, +) -> Option> where u64: From, { @@ -44,113 +172,314 @@ where let endian = header.endian().ok()?; let sections = header.sections(endian, data).ok()?; let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?; - let data_ptr = data.as_ptr() as usize; let strings = symtab.strings(); + let mut patches = Vec::new(); + let mut renames = Vec::new(); for sym in symtab.iter() { let binding = sym.st_bind(); if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK { continue; } - if sym.is_undefined(endian) { - continue; - } let Ok(name_bytes) = sym.name(endian, strings) else { continue }; let Ok(name) = str::from_utf8(name_bytes) else { continue }; - if !exported.contains(name) { - let sym_addr = sym as *const Elf::Sym as usize; + + let sym_addr = sym as *const Elf::Sym as usize; + + if hide && !sym.is_undefined(endian) && !exported.contains(name) { let offset = sym_addr - data_ptr + st_other_offset; let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN; patches.push(Patch { offset, value: new_vis }); } + if let Some((rename_set, _)) = rename { + if rename_set.contains(name) { + renames.push(RenameEntry { + name_field_offset: sym_addr - data_ptr, + name: name.to_string(), + }); + } + } + } + + if patches.is_empty() && renames.is_empty() { + return None; } - Some(patches) + let mut result = data.to_vec(); + for p in &patches { + result[p.offset] = p.value; + } + + if !renames.is_empty() { + let suffix = rename.unwrap().1; + if let Some(renamed) = + elf_rebuild_strtab::(&result, &renames, suffix, §ions, header, endian) + { + result = renamed; + } + } + + Some(result) +} + +fn elf_rebuild_strtab>( + data: &[u8], + renames: &[RenameEntry], + suffix: &str, + sections: &object::read::elf::SectionTable<'_, Elf>, + header: &Elf, + endian: Endianness, +) -> Option> +where + u64: From, +{ + let mut strtab_si: Option = None; + for section in sections.iter() { + if section.sh_type(endian) == elf::SHT_SYMTAB { + strtab_si = Some(section.sh_link(endian) as usize); + break; + } + } + let strtab_si = strtab_si?; + + let e_shoff = u64::from(header.e_shoff(endian)) as usize; + let e_shentsize = mem::size_of::(); + let e_shnum = sections.len(); + + let strtab_section = sections.section(object::SectionIndex(strtab_si)).ok()?; + let old_strtab_offset = u64::from(strtab_section.sh_offset(endian)) as usize; + let old_strtab_size = u64::from(strtab_section.sh_size(endian)) as usize; + let old_strtab = data.get(old_strtab_offset..old_strtab_offset + old_strtab_size)?; + + let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix); + + let is_64 = mem::size_of::() == 8; + let new_strtab_file_off = data.len(); + let new_strtab_size = new_strtab.len(); + let new_e_shoff_raw = new_strtab_file_off + new_strtab_size; + let new_e_shoff = (new_e_shoff_raw + 7) & !7; + let padding = new_e_shoff - new_e_shoff_raw; + let section_headers_size = e_shentsize * e_shnum; + + let result_size = new_e_shoff + section_headers_size; + let mut result = Vec::with_capacity(result_size); + result.extend_from_slice(data); + result.extend_from_slice(&new_strtab); + result.resize(result.len() + padding, 0); + let sh_data = data.get(e_shoff..e_shoff + section_headers_size)?; + result.extend_from_slice(sh_data); + + if is_64 { + write_u64_at( + &mut result, + mem::offset_of!(elf::FileHeader64, e_shoff), + new_e_shoff as u64, + endian, + ); + } else { + write_u32_at( + &mut result, + mem::offset_of!(elf::FileHeader32, e_shoff), + new_e_shoff as u32, + endian, + ); + } + + let new_strtab_shdr_offset = new_e_shoff + strtab_si * e_shentsize; + + if is_64 { + let sh_offset_field = mem::offset_of!(elf::SectionHeader64, sh_offset); + let sh_size_field = mem::offset_of!(elf::SectionHeader64, sh_size); + write_u64_at( + &mut result, + new_strtab_shdr_offset + sh_offset_field, + new_strtab_file_off as u64, + endian, + ); + write_u64_at( + &mut result, + new_strtab_shdr_offset + sh_size_field, + new_strtab_size as u64, + endian, + ); + } else { + let sh_offset_field = mem::offset_of!(elf::SectionHeader32, sh_offset); + let sh_size_field = mem::offset_of!(elf::SectionHeader32, sh_size); + write_u32_at( + &mut result, + new_strtab_shdr_offset + sh_offset_field, + new_strtab_file_off as u32, + endian, + ); + write_u32_at( + &mut result, + new_strtab_shdr_offset + sh_size_field, + new_strtab_size as u32, + endian, + ); + } + + for entry in renames { + let new_st_name = rename_map[&entry.name]; + write_u32_at(&mut result, entry.name_field_offset, new_st_name, endian); + } + + Some(result) } // --------------------------------------------------------------------------- -// Mach-O hide – same architecture: read-only pass via `object`, write via patches +// Mach-O: single-pass collection + apply // --------------------------------------------------------------------------- -fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader>( - data: &'data [u8], - n_type_offset: usize, +fn macho_edit_impl>( + data: &[u8], exported: &FxHashSet, -) -> Option> { + hide: bool, + rename: Option<&(FxHashSet, &str)>, + n_type_offset: usize, +) -> Option> { let header = Mach::parse(data, 0).ok()?; let endian = header.endian().ok()?; let mut commands = header.load_commands(endian, data, 0).ok()?; - let symtab_cmd = loop { + let (symtab_cmd, symtab_cmd_offset) = loop { let cmd = commands.next().ok()??; if let Some(st) = cmd.symtab().ok().flatten() { - break st; + break (st, cmd.raw_data().as_ptr() as usize - data.as_ptr() as usize); } }; + let symtab: object::read::macho::SymbolTable<'_, Mach, &_> = symtab_cmd.symbols(endian, data).ok()?; - let data_ptr = data.as_ptr() as usize; let strings = symtab.strings(); + let mut patches = Vec::new(); + let mut renames = Vec::new(); for nlist in symtab.iter() { if nlist.is_stab() { continue; } - if nlist.is_undefined() { - continue; - } if nlist.n_type() & macho::N_EXT == 0 { continue; } let Ok(name_bytes) = nlist.name(endian, strings) else { continue }; - let Ok(name) = str::from_utf8(name_bytes) else { continue }; - let name = name.strip_prefix('_').unwrap_or(name); - if !exported.contains(name) { - let nlist_addr = nlist as *const Mach::Nlist as usize; + let Ok(raw_name) = str::from_utf8(name_bytes) else { continue }; + let name = raw_name.strip_prefix('_').unwrap_or(raw_name); + + let nlist_addr = nlist as *const Mach::Nlist as usize; + + if hide && !nlist.is_undefined() && !exported.contains(name) { let offset = nlist_addr - data_ptr + n_type_offset; patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT }); } + if let Some((rename_set, _)) = rename { + if rename_set.contains(name) { + renames.push(RenameEntry { + name_field_offset: nlist_addr - data_ptr, + name: raw_name.to_string(), + }); + } + } + } + + if patches.is_empty() && renames.is_empty() { + return None; } - Some(patches) + let mut result = data.to_vec(); + for p in &patches { + result[p.offset] = p.value; + } + + if !renames.is_empty() { + let suffix = rename.unwrap().1; + if let Some(renamed) = + macho_rebuild_strtab(&result, &renames, suffix, &symtab_cmd, symtab_cmd_offset, endian) + { + result = renamed; + } + } + + Some(result) +} + +fn macho_rebuild_strtab( + data: &[u8], + renames: &[RenameEntry], + suffix: &str, + symtab_cmd: &macho::SymtabCommand, + symtab_cmd_offset: usize, + endian: Endianness, +) -> Option> { + let old_stroff = symtab_cmd.stroff.get(endian) as usize; + let old_strsize = symtab_cmd.strsize.get(endian) as usize; + let old_strtab = data.get(old_stroff..old_stroff + old_strsize)?; + + let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix); + + let new_strtab_file_off = data.len(); + let new_strtab_size = new_strtab.len(); + + let mut result = Vec::with_capacity(data.len() + new_strtab_size); + result.extend_from_slice(data); + result.extend_from_slice(&new_strtab); + + let stroff_off = mem::offset_of!(macho::SymtabCommand, stroff); + let strsize_off = mem::offset_of!(macho::SymtabCommand, strsize); + write_u32_at(&mut result, symtab_cmd_offset + stroff_off, new_strtab_file_off as u32, endian); + write_u32_at(&mut result, symtab_cmd_offset + strsize_off, new_strtab_size as u32, endian); + + for entry in renames { + let new_strx = rename_map[&entry.name]; + write_u32_at(&mut result, entry.name_field_offset, new_strx, endian); + } + + Some(result) } // --------------------------------------------------------------------------- -// Unified dispatch: top-level detection via `object::File::parse` +// Shared helpers // --------------------------------------------------------------------------- -fn hide_patches(data: &[u8], exported: &FxHashSet) -> Option> { - let file = object::File::parse(data).ok()?; - match file { - object::File::Elf64(_) => elf_hide_patches_impl::>( - data, - mem::offset_of!(elf::Sym64, st_other), - exported, - ), - object::File::Elf32(_) => elf_hide_patches_impl::>( - data, - mem::offset_of!(elf::Sym32, st_other), - exported, - ), - object::File::MachO64(_) => macho_hide_patches_impl::>( - data, - mem::offset_of!(macho::Nlist64, n_type), - exported, - ), - object::File::MachO32(_) => macho_hide_patches_impl::>( - data, - mem::offset_of!(macho::Nlist32, n_type), - exported, - ), - _ => None, +fn build_renamed_strtab( + old_strtab: &[u8], + renames: &[RenameEntry], + suffix: &str, +) -> (Vec, FxHashMap) { + let mut new_strtab = old_strtab.to_vec(); + let mut rename_map: FxHashMap = FxHashMap::default(); + + let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect(); + sorted_names.sort(); + sorted_names.dedup(); + + for name in &sorted_names { + let new_offset = new_strtab.len() as u32; + new_strtab.extend_from_slice(name.as_bytes()); + new_strtab.extend_from_slice(suffix.as_bytes()); + new_strtab.push(0); + rename_map.insert(name.to_string(), new_offset); } + + (new_strtab, rename_map) } -pub(super) fn apply_hide(data: &[u8], exported: &FxHashSet) -> Vec { - let patches = hide_patches(data, exported).unwrap_or_default(); - apply_patches(data, &patches) +fn write_u32_at(buf: &mut [u8], offset: usize, value: u32, endian: Endianness) { + let bytes = match endian { + Endianness::Little => value.to_le_bytes(), + Endianness::Big => value.to_be_bytes(), + }; + buf[offset..offset + 4].copy_from_slice(&bytes); +} + +fn write_u64_at(buf: &mut [u8], offset: usize, value: u64, endian: Endianness) { + let bytes = match endian { + Endianness::Little => value.to_le_bytes(), + Endianness::Big => value.to_be_bytes(), + }; + buf[offset..offset + 8].copy_from_slice(&bytes); } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index c14d4aec48c35..2a5e421282f4e 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -962,6 +962,7 @@ impl CrateInfo { natvis_debugger_visualizers: Default::default(), lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx), metadata_symbol: exported_symbols::metadata_symbol_name(tcx), + symbol_rename_suffix: format!(".rs{:x}", tcx.stable_crate_id(LOCAL_CRATE)), each_linked_rlib_file_for_lto: Default::default(), exported_symbols_for_lto: Default::default(), }; diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 9326e477465f0..37352eaa7d55f 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -701,6 +701,14 @@ pub(crate) struct StaticlibHideInternalSymbolsUnsupported { pub binary_format: String, } +#[derive(Diagnostic)] +#[diag( + "-Zstaticlib-rename-internal-symbols only supports ELF and Mach-O targets, but the target uses `{$binary_format}`" +)] +pub(crate) struct StaticlibRenameInternalSymbolsUnsupported { + pub binary_format: String, +} + #[derive(Diagnostic)] #[diag("entry symbol `main` declared multiple times")] #[help( diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 616397dd77545..10ae8a9ee0b38 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -263,6 +263,7 @@ pub struct CrateInfo { pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, pub metadata_symbol: String, + pub symbol_rename_suffix: String, pub each_linked_rlib_file_for_lto: Vec, pub exported_symbols_for_lto: Vec, } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 622077caddc40..7bbb651889fd8 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -867,6 +867,7 @@ fn test_unstable_options_tracking_hash() { tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1)); tracked!(stack_protector, StackProtector::All); tracked!(staticlib_hide_internal_symbols, true); + tracked!(staticlib_rename_internal_symbols, true); tracked!(teach, true); tracked!(thinlto, Some(true)); tracked!(tiny_const_eval_limit, true); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 1b3217ed0a030..a712e55bc4ce1 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2473,6 +2473,14 @@ pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::M ); } + if unstable_opts.staticlib_rename_internal_symbols + && !crate_types.contains(&CrateType::StaticLib) + { + early_dcx.early_warn( + "-Zstaticlib-rename-internal-symbols has no effect without `--crate-type staticlib`", + ); + } + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); if !unstable_opts.unstable_options && json_timings { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 6ae0d9721bc00..ba8c433e3dfd7 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2689,7 +2689,9 @@ written to standard error output)"), staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED], "allow staticlibs to have rust dylib dependencies"), staticlib_hide_internal_symbols: bool = (false, parse_bool, [TRACKED], - "hide non-exported symbols in ELF static libraries by setting STV_HIDDEN"), + "hide non-exported Rust symbols when building staticlibs by setting STV_HIDDEN"), + staticlib_rename_internal_symbols: bool = (false, parse_bool, [TRACKED], + "rename non-exported Rust symbols when building staticlibs to avoid conflicts"), staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED], "prefer dynamic linking to static linking for staticlibs (default: no)"), strict_init_checks: bool = (false, parse_bool, [TRACKED], diff --git a/src/doc/unstable-book/src/compiler-flags/staticlib-rename-internal-symbols.md b/src/doc/unstable-book/src/compiler-flags/staticlib-rename-internal-symbols.md new file mode 100644 index 0000000000000..f25a45ae7efc9 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/staticlib-rename-internal-symbols.md @@ -0,0 +1,19 @@ +# `staticlib-rename-internal-symbols` + +When building a `staticlib`, this option renames all non-exported Rust-internal +symbols by appending a `_rs{hash}` suffix. This prevents symbol collisions when +multiple Rust static libraries are linked into the same final binary. + +This option only renames symbols; it does not change their visibility. +Use `-Zstaticlib-hide-internal-symbols` in addition if you also want to hide +internal symbols. + +Only symbols explicitly exported via `#[no_mangle]` or `#[export_name]` are left +unchanged. All other `GLOBAL`/`WEAK` symbols (including `pub(crate)` and `pub` +items without `#[no_mangle]`) are renamed. + +This option can only be used with `--crate-type staticlib`. Using it with +other crate types will result in a compilation warning. + +Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.). +On unsupported targets (Windows), a warning is emitted and the flag has no effect. diff --git a/tests/run-make/staticlib-rename-internal-symbols-macho/rmake.rs b/tests/run-make/staticlib-rename-internal-symbols-macho/rmake.rs new file mode 100644 index 0000000000000..911dadc16325d --- /dev/null +++ b/tests/run-make/staticlib-rename-internal-symbols-macho/rmake.rs @@ -0,0 +1,163 @@ +//@ only-apple +//@ ignore-cross-compile + +use std::collections::HashSet; + +use run_make_support::object::Endianness; +use run_make_support::object::macho::{MachHeader64, N_EXT, N_PEXT, N_SECT, N_STAB, N_TYPE}; +use run_make_support::object::read::archive::ArchiveFile; +use run_make_support::object::read::macho::{MachHeader as _, Nlist as _}; +use run_make_support::path_helpers::source_root; +use run_make_support::{cc, extra_c_flags, object, rfs, run, rustc, static_lib_name}; + +type MachOFileHeader64 = MachHeader64; +type SymbolTable<'data> = + run_make_support::object::read::macho::SymbolTable<'data, MachOFileHeader64>; + +const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"]; + +fn main() { + let hide_sibling = source_root().join("tests/run-make/staticlib-hide-internal-symbols"); + let rename_sibling = source_root().join("tests/run-make/staticlib-rename-internal-symbols"); + rfs::copy(hide_sibling.join("lib.rs"), "lib.rs"); + rfs::copy(hide_sibling.join("main.c"), "main.c"); + rfs::copy(rename_sibling.join("liba.rs"), "liba.rs"); + rfs::copy(rename_sibling.join("libb.rs"), "libb.rs"); + rfs::copy(rename_sibling.join("dual_main.c"), "dual_main.c"); + + test_basic_functionality(); + test_suffix_present(); + test_dual_staticlib_linking(); +} + +fn test_basic_functionality() { + let lib_name = static_lib_name("lib"); + + rustc() + .input("lib.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run(); + run("main"); + + rfs::remove_file(&lib_name); +} + +fn test_suffix_present() { + let lib_name = static_lib_name("lib"); + + rustc() + .input("lib.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + let data = rfs::read(&lib_name); + check_rename_symbols(&data); + + rfs::remove_file(&lib_name); +} + +fn test_dual_staticlib_linking() { + let liba_name = static_lib_name("liba"); + let libb_name = static_lib_name("libb"); + + rustc() + .input("liba.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + rustc() + .input("libb.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + cc().input("dual_main.c") + .input(&liba_name) + .input(&libb_name) + .out_exe("dual_main") + .args(extra_c_flags()) + .run(); + run("dual_main"); +} + +fn check_rename_symbols(archive_data: &[u8]) { + let archive = ArchiveFile::parse(archive_data).unwrap(); + let mut found_exported = HashSet::new(); + let mut found_suffix = false; + + for member in archive.members() { + let member = member.unwrap(); + if !member.name().ends_with(b".rcgu.o") { + continue; + } + let data = member.data(archive_data).unwrap(); + + let Ok(header) = MachOFileHeader64::parse(data, 0) else { continue }; + let Ok(endian) = header.endian() else { continue }; + + let Some(symtab) = find_symtab(header, endian, data) else { continue }; + let strtab = symtab.strings(); + + for nlist in symtab.iter() { + let n_type = nlist.n_type(); + if n_type & N_STAB != 0 { + continue; + } + if n_type & N_EXT == 0 { + continue; + } + if n_type & N_TYPE != N_SECT { + continue; + } + + let Ok(name_bytes) = nlist.name(endian, strtab) else { continue }; + let Ok(name) = std::str::from_utf8(name_bytes) else { continue }; + let name = name.strip_prefix('_').unwrap_or(name); + + if EXPORTED.contains(&name) { + assert!( + !name.contains(".rs"), + "exported symbol `{name}` should not contain .rs suffix" + ); + found_exported.insert(name.to_string()); + } else { + assert!( + name.contains(".rs"), + "internal symbol `{name}` should contain .rs suffix after rename" + ); + found_suffix = true; + } + } + } + + assert!(found_suffix, "expected to find at least one renamed symbol with .rs suffix"); + for expected in EXPORTED { + assert!( + found_exported.contains(*expected), + "expected to find exported symbol `{expected}` in archive" + ); + } +} + +fn find_symtab<'data>( + header: &MachOFileHeader64, + endian: Endianness, + data: &'data [u8], +) -> Option> { + let mut commands = header.load_commands(endian, data, 0).ok()?; + while let Ok(Some(command)) = commands.next() { + if let Ok(Some(symtab_cmd)) = command.symtab() { + return symtab_cmd.symbols::(endian, data).ok(); + } + } + None +} diff --git a/tests/run-make/staticlib-rename-internal-symbols/dual_main.c b/tests/run-make/staticlib-rename-internal-symbols/dual_main.c new file mode 100644 index 0000000000000..21f4d5cae9b55 --- /dev/null +++ b/tests/run-make/staticlib-rename-internal-symbols/dual_main.c @@ -0,0 +1,14 @@ +extern int liba_process(int v); +extern int liba_answer(); +extern int libb_multiply(int a, int b); +extern int libb_greet(); + +int main() { + if (liba_answer() != 42) return 1; + if (liba_process(10) != 31) return 1; + + if (libb_multiply(6, 7) != 42) return 1; + if (libb_greet() != 99) return 1; + + return 0; +} diff --git a/tests/run-make/staticlib-rename-internal-symbols/liba.rs b/tests/run-make/staticlib-rename-internal-symbols/liba.rs new file mode 100644 index 0000000000000..ca23944df44ea --- /dev/null +++ b/tests/run-make/staticlib-rename-internal-symbols/liba.rs @@ -0,0 +1,17 @@ +#![crate_type = "staticlib"] + +mod internal { + pub fn compute(v: i32) -> i32 { + v * 3 + 1 + } +} + +#[no_mangle] +pub extern "C" fn liba_process(v: i32) -> i32 { + internal::compute(v) +} + +#[no_mangle] +pub extern "C" fn liba_answer() -> i32 { + 42 +} diff --git a/tests/run-make/staticlib-rename-internal-symbols/libb.rs b/tests/run-make/staticlib-rename-internal-symbols/libb.rs new file mode 100644 index 0000000000000..1eca8f3d254ca --- /dev/null +++ b/tests/run-make/staticlib-rename-internal-symbols/libb.rs @@ -0,0 +1,15 @@ +#![crate_type = "staticlib"] + +fn internal_multiply(a: i32, b: i32) -> i32 { + a * b +} + +#[no_mangle] +pub extern "C" fn libb_multiply(a: i32, b: i32) -> i32 { + internal_multiply(a, b) +} + +#[no_mangle] +pub extern "C" fn libb_greet() -> i32 { + 99 +} diff --git a/tests/run-make/staticlib-rename-internal-symbols/rmake.rs b/tests/run-make/staticlib-rename-internal-symbols/rmake.rs new file mode 100644 index 0000000000000..57123dba38f45 --- /dev/null +++ b/tests/run-make/staticlib-rename-internal-symbols/rmake.rs @@ -0,0 +1,165 @@ +//@ only-elf +//@ ignore-cross-compile + +use std::collections::HashSet; + +use run_make_support::object::read::archive::ArchiveFile; +use run_make_support::object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _}; +use run_make_support::object::{Endianness, elf}; +use run_make_support::path_helpers::source_root; +use run_make_support::{cc, extra_c_flags, rfs, run, rustc, static_lib_name}; + +const EXPORTED: &[&str] = &["my_add", "my_hash_lookup", "call_internal", "my_safe_div"]; + +fn main() { + let sibling = source_root().join("tests/run-make/staticlib-hide-internal-symbols"); + rfs::copy(sibling.join("lib.rs"), "lib.rs"); + rfs::copy(sibling.join("main.c"), "main.c"); + + test_basic_functionality(); + test_rs_suffix_present(); + test_dual_staticlib_linking(); +} + +fn test_basic_functionality() { + let lib_name = static_lib_name("lib"); + + rustc() + .input("lib.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + cc().input("main.c").input(&lib_name).out_exe("main").args(extra_c_flags()).run(); + run("main"); + + rfs::remove_file(&lib_name); +} + +fn test_rs_suffix_present() { + let lib_name = static_lib_name("lib"); + + rustc() + .input("lib.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + let data = rfs::read(&lib_name); + check_rename_symbols(&data); + + rfs::remove_file(&lib_name); +} + +fn test_dual_staticlib_linking() { + let liba_name = static_lib_name("liba"); + let libb_name = static_lib_name("libb"); + + rustc() + .input("liba.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + rustc() + .input("libb.rs") + .crate_type("staticlib") + .arg("-Zstaticlib-rename-internal-symbols") + .opt() + .run(); + + cc().input("dual_main.c") + .input(&liba_name) + .input(&libb_name) + .out_exe("dual_main") + .args(extra_c_flags()) + .run(); + run("dual_main"); +} + +fn check_rename_symbols(archive_data: &[u8]) { + let archive = ArchiveFile::parse(archive_data).unwrap(); + let mut found_exported = HashSet::new(); + let mut found_rs_suffix = false; + + for member in archive.members() { + let member = member.unwrap(); + if !member.name().ends_with(b".rcgu.o") { + continue; + } + let data = member.data(archive_data).unwrap(); + + if let Ok(header) = elf::FileHeader64::::parse(data) { + check_elf_symbols(header, data, &mut found_exported, &mut found_rs_suffix); + } else if let Ok(header) = elf::FileHeader32::::parse(data) { + check_elf_symbols(header, data, &mut found_exported, &mut found_rs_suffix); + } + } + + assert!(found_rs_suffix, "expected to find at least one renamed symbol with .rs suffix"); + for expected in EXPORTED { + assert!( + found_exported.contains(*expected), + "expected to find exported symbol `{expected}` in archive" + ); + } +} + +fn check_elf_symbols>( + header: &Elf, + data: &[u8], + found_exported: &mut HashSet, + found_rs_suffix: &mut bool, +) { + let Ok(endian) = header.endian() else { return }; + let Ok(sections) = header.sections(endian, data) else { return }; + + for (si, section) in sections.enumerate() { + if section.sh_type(endian) != elf::SHT_SYMTAB { + continue; + } + let Ok(symbols) = run_make_support::object::read::elf::SymbolTable::parse( + endian, data, §ions, si, section, + ) else { + continue; + }; + let strtab = symbols.strings(); + + for symbol in symbols.symbols() { + let bind = symbol.st_bind(); + let shndx = symbol.st_shndx(endian); + + if shndx == elf::SHN_UNDEF { + continue; + } + if bind != elf::STB_GLOBAL && bind != elf::STB_WEAK { + continue; + } + + let Ok(name_bytes) = symbol.name(endian, strtab) else { continue }; + let Ok(name) = str::from_utf8(name_bytes) else { continue }; + + if EXPORTED.contains(&name) { + assert!( + !name.contains(".rs"), + "exported symbol `{name}` should not contain .rs suffix" + ); + assert_eq!( + symbol.st_visibility(), + elf::STV_DEFAULT, + "exported symbol `{name}` should be STV_DEFAULT" + ); + found_exported.insert(name.to_string()); + } else { + assert!( + name.contains(".rs"), + "internal symbol `{name}` should contain .rs suffix after rename" + ); + *found_rs_suffix = true; + } + } + } +} diff --git a/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.rs b/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.rs new file mode 100644 index 0000000000000..684bdbda6969b --- /dev/null +++ b/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.rs @@ -0,0 +1,8 @@ +//@ check-pass +//@ compile-flags: -Zstaticlib-rename-internal-symbols --crate-type bin + +#![feature(no_core)] +#![no_core] +#![no_main] + +//~? WARN has no effect without `--crate-type staticlib` diff --git a/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.stderr b/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.stderr new file mode 100644 index 0000000000000..d4095ede81a34 --- /dev/null +++ b/tests/ui/linking/staticlib-rename-internal-symbols-wrong-crate-type.stderr @@ -0,0 +1,2 @@ +warning: -Zstaticlib-rename-internal-symbols has no effect without `--crate-type staticlib` + From 7b35e2eeca73061f9e97bf689f11ad6ad1edf659 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 16 Jun 2026 08:12:23 +0200 Subject: [PATCH 10/23] renovate: Disable lockfile maintenance Requested in https://github.com/rust-lang/team/pull/2519#issuecomment-4712206395 --- .github/renovate.json5 | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 87f5f103ddfca..b78604cd977d4 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,12 +3,5 @@ // Let Renovatebot keep an opened issue that tracks our dependencies "dependencyDashboard": true, // Disable "normal" package updates - "enabledManagers": [], - // Update lockfiles once per week - "lockFileMaintenance": { - "enabled": true, - "schedule": [ - "before 5am on Tuesday" - ] - } + "enabledManagers": [] } From 890eec1eaa6eeea0e9db56c0bfc9f394b9b34160 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 16 Jun 2026 08:20:45 +0200 Subject: [PATCH 11/23] renovate: Gate all updates behind Dependency Dashboard approval Removes the `enabledManagers` override so all managers run, but requires manual approval from the Dependency Dashboard before any PRs are opened. The dashboard then acts as a preview without cluttering the PR list. --- .github/renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b78604cd977d4..61f13e50d5892 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -2,6 +2,6 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", // Let Renovatebot keep an opened issue that tracks our dependencies "dependencyDashboard": true, - // Disable "normal" package updates - "enabledManagers": [] + // Require manual approval from the Dependency Dashboard before opening PRs + "dependencyDashboardApproval": true } From 18451b4288e6bc6b92e3867a6bf1802421c48983 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 16 Jun 2026 08:30:42 +0200 Subject: [PATCH 12/23] renovate: Ignore subtree paths Subtrees are updated in their upstream repositories and synced into the tree, so Renovate should not manage their dependencies here. Without this, Renovate would surface pinned actions from every subtree workflow file (see https://github.com/rust-lang/rust/pull/134127). Mirrors the subtree list in `external-repos.md`. --- .github/renovate.json5 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 61f13e50d5892..2b3fb9873d53f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,5 +3,19 @@ // Let Renovatebot keep an opened issue that tracks our dependencies "dependencyDashboard": true, // Require manual approval from the Dependency Dashboard before opening PRs - "dependencyDashboardApproval": true + "dependencyDashboardApproval": true, + // Don't manage dependencies inside subtrees. They are updated upstream and + // synced in. See `src/doc/rustc-dev-guide/src/external-repos.md` for the list. + "ignorePaths": [ + "compiler/rustc_codegen_cranelift/**", + "compiler/rustc_codegen_gcc/**", + "library/compiler-builtins/**", + "library/portable-simd/**", + "library/stdarch/**", + "src/doc/rustc-dev-guide/**", + "src/tools/clippy/**", + "src/tools/miri/**", + "src/tools/rust-analyzer/**", + "src/tools/rustfmt/**" + ] } From dafc23a627842c69deab96ae5b8b692e6e529bd2 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 16 Jun 2026 08:35:03 +0200 Subject: [PATCH 13/23] renovate: Enable `helpers:pinGitHubActionDigests` preset Pins GitHub Actions to their commit SHA digests and keeps them updated. This lets Renovate handle the pinning that was proposed in https://github.com/rust-lang/rust/pull/155089 instead of maintaining it manually. --- .github/renovate.json5 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 2b3fb9873d53f..26f930b53867e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,5 +1,9 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + // Pin GitHub Actions to their commit SHA digests + "helpers:pinGitHubActionDigests" + ], // Let Renovatebot keep an opened issue that tracks our dependencies "dependencyDashboard": true, // Require manual approval from the Dependency Dashboard before opening PRs From 52ed05cd97171ca04725484691393b9883ab7de7 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 13:03:09 +0200 Subject: [PATCH 14/23] Generalize where bound handling --- .../src/collect/item_bounds.rs | 4 ++ .../src/collect/predicates_of.rs | 16 ++++-- .../src/hir_ty_lowering/bounds.rs | 49 ++++++++++++------- .../src/hir_ty_lowering/dyn_trait.rs | 1 + .../src/hir_ty_lowering/mod.rs | 5 +- 5 files changed, 52 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index 4409f2c068eb8..f005a831de2a0 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -58,6 +58,7 @@ fn associated_type_bounds<'tcx>( &mut bounds, item_ty, hir_bounds, + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, span, ); @@ -65,6 +66,7 @@ fn associated_type_bounds<'tcx>( &mut bounds, item_ty, hir_bounds, + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, span, ); @@ -384,6 +386,7 @@ fn opaque_type_bounds<'tcx>( &mut bounds, item_ty, hir_bounds, + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, span, ); @@ -391,6 +394,7 @@ fn opaque_type_bounds<'tcx>( &mut bounds, item_ty, hir_bounds, + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, span, ); diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs index db22b57ad22a6..6e6b6602f3a74 100644 --- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs @@ -200,6 +200,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen &mut bounds, tcx.types.self_param, self_bounds, + &[], ImpliedBoundsContext::TraitDef(def_id), span, ); @@ -207,6 +208,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen &mut bounds, tcx.types.self_param, self_bounds, + &[], ImpliedBoundsContext::TraitDef(def_id), span, ); @@ -239,14 +241,16 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen &mut bounds, param_ty, &[], - ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates), + hir_generics.predicates, + ImpliedBoundsContext::TyParam(param.def_id), param.span, ); icx.lowerer().add_default_traits( &mut bounds, param_ty, &[], - ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates), + hir_generics.predicates, + ImpliedBoundsContext::TyParam(param.def_id), param.span, ); trace!(?bounds); @@ -692,6 +696,7 @@ pub(super) fn implied_predicates_with_filter<'tcx>( &mut bounds, self_param_ty, superbounds, + &[], ImpliedBoundsContext::TraitDef(trait_def_id), item.span, ); @@ -699,6 +704,7 @@ pub(super) fn implied_predicates_with_filter<'tcx>( &mut bounds, self_param_ty, superbounds, + &[], ImpliedBoundsContext::TraitDef(trait_def_id), item.span, ); @@ -994,14 +1000,16 @@ impl<'tcx> ItemCtxt<'tcx> { &mut bounds, param_ty, &[], - ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates), + hir_generics.predicates, + ImpliedBoundsContext::TyParam(param.def_id), param.span, ); self.lowerer().add_default_traits( &mut bounds, param_ty, &[], - ImpliedBoundsContext::TyParam(param.def_id, hir_generics.predicates), + hir_generics.predicates, + ImpliedBoundsContext::TyParam(param.def_id), param.span, ); } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index 9d6c647329aa0..c586eba36f9ec 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -59,7 +59,8 @@ impl CollectedSizednessBounds { fn search_bounds_for<'tcx>( hir_bounds: &'tcx [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, mut f: impl FnMut(&'tcx PolyTraitRef<'tcx>), ) { let mut search_bounds = |hir_bounds: &'tcx [hir::GenericBound<'tcx>]| { @@ -73,8 +74,8 @@ fn search_bounds_for<'tcx>( }; search_bounds(hir_bounds); - if let ImpliedBoundsContext::TyParam(self_ty, where_clause) = context { - for clause in where_clause { + if let ImpliedBoundsContext::TyParam(self_ty) = context { + for clause in where_bounds { if let hir::WherePredicateKind::BoundPredicate(pred) = clause.kind && pred.is_param_bound(self_ty.to_def_id()) { @@ -86,11 +87,12 @@ fn search_bounds_for<'tcx>( fn collect_bounds<'a, 'tcx>( hir_bounds: &'a [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, target_did: DefId, ) -> CollectedBound { let mut collect_into = CollectedBound::default(); - search_bounds_for(hir_bounds, context, |ptr| { + search_bounds_for(hir_bounds, where_bounds, context, |ptr| { if !matches!(ptr.trait_ref.path.res, Res::Def(DefKind::Trait, did) if did == target_did) { return; } @@ -107,17 +109,18 @@ fn collect_bounds<'a, 'tcx>( fn collect_sizedness_bounds<'tcx>( tcx: TyCtxt<'tcx>, hir_bounds: &'tcx [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, span: Span, ) -> CollectedSizednessBounds { let sized_did = tcx.require_lang_item(hir::LangItem::Sized, span); - let sized = collect_bounds(hir_bounds, context, sized_did); + let sized = collect_bounds(hir_bounds, where_bounds, context, sized_did); let meta_sized_did = tcx.require_lang_item(hir::LangItem::MetaSized, span); - let meta_sized = collect_bounds(hir_bounds, context, meta_sized_did); + let meta_sized = collect_bounds(hir_bounds, where_bounds, context, meta_sized_did); let pointee_sized_did = tcx.require_lang_item(hir::LangItem::PointeeSized, span); - let pointee_sized = collect_bounds(hir_bounds, context, pointee_sized_did); + let pointee_sized = collect_bounds(hir_bounds, where_bounds, context, pointee_sized_did); CollectedSizednessBounds { sized, meta_sized, pointee_sized } } @@ -150,7 +153,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { bounds: &mut Vec<(ty::Clause<'tcx>, Span)>, self_ty: Ty<'tcx>, hir_bounds: &'tcx [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, span: Span, ) { let tcx = self.tcx(); @@ -181,7 +185,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } } - let collected = collect_sizedness_bounds(tcx, hir_bounds, context, span); + let collected = collect_sizedness_bounds(tcx, hir_bounds, where_bounds, context, span); if (collected.sized.maybe || collected.sized.negative) && !collected.sized.positive && !collected.meta_sized.any() @@ -213,11 +217,20 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { bounds: &mut Vec<(ty::Clause<'tcx>, Span)>, self_ty: Ty<'tcx>, hir_bounds: &[hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, span: Span, ) { self.tcx().default_traits().iter().for_each(|default_trait| { - self.add_default_trait(*default_trait, bounds, self_ty, hir_bounds, context, span); + self.add_default_trait( + *default_trait, + bounds, + self_ty, + hir_bounds, + where_bounds, + context, + span, + ); }); } @@ -230,7 +243,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { bounds: &mut Vec<(ty::Clause<'tcx>, Span)>, self_ty: Ty<'tcx>, hir_bounds: &[hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, span: Span, ) { let tcx = self.tcx(); @@ -244,7 +258,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } if let Some(trait_did) = tcx.lang_items().get(trait_) - && self.should_add_default_traits(trait_did, hir_bounds, context) + && self.should_add_default_traits(trait_did, hir_bounds, where_bounds, context) { add_trait_bound(tcx, bounds, self_ty, trait_did, span); } @@ -255,9 +269,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { &self, trait_def_id: DefId, hir_bounds: &'a [hir::GenericBound<'tcx>], - context: ImpliedBoundsContext<'tcx>, + where_bounds: &'tcx [hir::WherePredicate<'tcx>], + context: ImpliedBoundsContext, ) -> bool { - let collected = collect_bounds(hir_bounds, context, trait_def_id); + let collected = collect_bounds(hir_bounds, where_bounds, context, trait_def_id); !find_attr!(self.tcx(), crate, RustcNoImplicitBounds) && !collected.any() } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index 761af258822dd..c6edd30cd63de 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -83,6 +83,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .iter() .map(|&trait_ref| hir::GenericBound::Trait(trait_ref)) .collect::>(), + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, span, ); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index 7909fdbf2365e..fbf184e17f5ce 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -59,12 +59,12 @@ use crate::{NoVariantNamed, check_c_variadic_abi}; /// The context in which an implied bound is being added to a item being lowered (i.e. a sizedness /// trait or a default trait) #[derive(Clone, Copy)] -pub(crate) enum ImpliedBoundsContext<'tcx> { +pub(crate) enum ImpliedBoundsContext { /// An implied bound is added to a trait definition (i.e. a new supertrait), used when adding /// a default `MetaSized` supertrait TraitDef(LocalDefId), /// An implied bound is added to a type parameter - TyParam(LocalDefId, &'tcx [hir::WherePredicate<'tcx>]), + TyParam(LocalDefId), /// An implied bound being added in any other context AssociatedTypeOrImplTrait, } @@ -3093,6 +3093,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { &mut bounds, self_ty, hir_bounds, + &[], ImpliedBoundsContext::AssociatedTypeOrImplTrait, hir_ty.span, ); From 9abb8acdc014dbfa107df0a871d0d48878b34977 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 10:28:51 +0200 Subject: [PATCH 15/23] Exhaustively match on `DesugaringKind` --- compiler/rustc_span/src/hygiene.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index 1c742052783cd..a2537939ff8ad 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -928,7 +928,17 @@ impl SyntaxContext { | DesugaringKind::Async | DesugaringKind::Await, ) => false, - ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external" + ExpnKind::AstPass(_) + | ExpnKind::Desugaring( + DesugaringKind::BoundModifier + | DesugaringKind::QuestionMark + | DesugaringKind::TryBlock + | DesugaringKind::Contract + | DesugaringKind::RangeExpr + | DesugaringKind::PatTyRange + | DesugaringKind::FormatLiteral { .. } + | DesugaringKind::YeetExpr, + ) => true, // well, it's "external" ExpnKind::Macro(MacroKind::Bang, _) => { // Dummy span for the `def_site` means it's an external macro. expn_data.def_site.is_dummy() || sm.is_imported(expn_data.def_site) From d3c8318ff26f9ca944cf64f474e5020aa4c6b759 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 10:16:10 +0200 Subject: [PATCH 16/23] Add expansion info to default bounds --- .../rustc_hir_analysis/src/check/check.rs | 6 ++-- .../src/hir_ty_lowering/bounds.rs | 34 +++++++++++++------ .../rustc_hir_typeck/src/method/suggest.rs | 8 ++--- compiler/rustc_span/src/hygiene.rs | 10 ++++++ .../src/error_reporting/traits/suggestions.rs | 22 +++++------- tests/ui/box/into-boxed-slice-fail.stderr | 4 +-- tests/ui/closures/unsized_value_move.stderr | 2 +- tests/ui/coherence/deep-bad-copy-reason.rs | 1 + .../ui/coherence/deep-bad-copy-reason.stderr | 6 ++-- .../unused-type-param-suggestion.rs | 3 +- .../unused-type-param-suggestion.stderr | 1 - tests/ui/dst/dst-rvalue.stderr | 4 +-- tests/ui/extern/extern-types-unsized.stderr | 8 ++--- .../sized-hierarchy/overflow.current.stderr | 6 ++-- .../enum-unit-variant-trait-bound.stderr | 2 +- 15 files changed, 68 insertions(+), 49 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 917360ab8119e..ba938eb91a4aa 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -24,7 +24,7 @@ use rustc_middle::ty::{ TypeVisitable, TypeVisitableExt, Unnormalized, fold_regions, }; use rustc_session::lint::builtin::UNINHABITED_STATIC; -use rustc_span::sym; +use rustc_span::{DesugaringKind, sym}; use rustc_target::spec::{AbiMap, AbiMapping}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::traits; @@ -2117,7 +2117,9 @@ fn check_type_alias_type_params_are_used<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalD // * check for emptiness to detect lone user-written `?Sized` bounds // * compare the param span to the pred span to detect lone user-written `Sized` bounds let has_explicit_bounds = bounded_params.is_empty() - || (*bounded_params).get(¶m.index).is_some_and(|&&pred_sp| pred_sp != span); + || (*bounded_params).get(¶m.index).is_some_and(|&&pred_sp| { + !pred_sp.is_desugaring(DesugaringKind::DefaultBound { def: param.def_id }) + }); let const_param_help = !has_explicit_bounds; let mut diag = tcx.dcx().create_err(diagnostics::UnusedGenericParameter { diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index c586eba36f9ec..e0e2926dfeb83 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -12,7 +12,7 @@ use rustc_middle::ty::{ self as ty, IsSuggestable, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, Upcast, }; -use rustc_span::{ErrorGuaranteed, Ident, Span, kw}; +use rustc_span::{DesugaringKind, ErrorGuaranteed, Ident, Span, kw}; use rustc_trait_selection::traits; use tracing::{debug, instrument}; @@ -25,17 +25,17 @@ use crate::hir_ty_lowering::{ #[derive(Debug, Default)] struct CollectedBound { /// `Trait` - positive: bool, + positive: Option, /// `?Trait` - maybe: bool, + maybe: Option, /// `!Trait` - negative: bool, + negative: Option, } impl CollectedBound { /// Returns `true` if any of `Trait`, `?Trait` or `!Trait` were encountered. fn any(&self) -> bool { - self.positive || self.maybe || self.negative + self.positive.is_some() || self.maybe.is_some() || self.negative.is_some() } } @@ -98,9 +98,9 @@ fn collect_bounds<'a, 'tcx>( } match ptr.modifiers.polarity { - hir::BoundPolarity::Maybe(_) => collect_into.maybe = true, - hir::BoundPolarity::Negative(_) => collect_into.negative = true, - hir::BoundPolarity::Positive => collect_into.positive = true, + hir::BoundPolarity::Maybe(_) => collect_into.maybe = Some(ptr.span), + hir::BoundPolarity::Negative(_) => collect_into.negative = Some(ptr.span), + hir::BoundPolarity::Positive => collect_into.positive = Some(ptr.span), } }); collect_into @@ -184,10 +184,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ImpliedBoundsContext::TyParam(..) | ImpliedBoundsContext::AssociatedTypeOrImplTrait => { } } - let collected = collect_sizedness_bounds(tcx, hir_bounds, where_bounds, context, span); - if (collected.sized.maybe || collected.sized.negative) - && !collected.sized.positive + if let Some(span) = collected.sized.maybe.or(collected.sized.negative) + && collected.sized.positive.is_none() && !collected.meta_sized.any() && !collected.pointee_sized.any() { @@ -195,6 +194,19 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { // other explicit ones) - this can happen for trait aliases as well as bounds. add_trait_bound(tcx, bounds, self_ty, meta_sized_did, span); } else if !collected.any() { + let span = match context { + ImpliedBoundsContext::TyParam(def) => { + self.tcx().with_stable_hashing_context(|hcx| { + span.mark_with_reason( + None, + DesugaringKind::DefaultBound { def: def.into() }, + span.edition(), + hcx, + ) + }) + } + _ => span, + }; match context { ImpliedBoundsContext::TraitDef(..) => { // If there are no explicit sizedness bounds on a trait then add a default diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 8c9f764c0bded..7110a1edbe28b 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -34,8 +34,8 @@ use rustc_middle::ty::print::{ use rustc_middle::ty::{self, GenericArgKind, IsSuggestable, Ty, TyCtxt, TypeVisitableExt}; use rustc_span::def_id::DefIdSet; use rustc_span::{ - DUMMY_SP, ErrorGuaranteed, ExpnKind, FileName, Ident, MacroKind, Span, Symbol, edit_distance, - kw, sym, + DUMMY_SP, DesugaringKind, ErrorGuaranteed, ExpnKind, FileName, Ident, MacroKind, Span, Symbol, + edit_distance, kw, sym, }; use rustc_trait_selection::error_reporting::traits::DefIdOrName; use rustc_trait_selection::infer::InferCtxtExt; @@ -1948,8 +1948,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => false, } }); - for param in generics.params { - if param.span == cause_span && sized_pred { + if sized_pred && let Some(DesugaringKind::DefaultBound { def }) = cause_span.desugaring_kind() { + if let Some(param) = generics.params.iter().find(|p| p.def_id.to_def_id() == def) { let (sp, sugg) = match param.colon_span { Some(sp) => (sp.shrink_to_hi(), " ?Sized +"), None => (param.span.shrink_to_hi(), ": ?Sized"), diff --git a/compiler/rustc_span/src/hygiene.rs b/compiler/rustc_span/src/hygiene.rs index a2537939ff8ad..9688978f35fec 100644 --- a/compiler/rustc_span/src/hygiene.rs +++ b/compiler/rustc_span/src/hygiene.rs @@ -926,6 +926,7 @@ impl SyntaxContext { | DesugaringKind::WhileLoop | DesugaringKind::OpaqueTy | DesugaringKind::Async + | DesugaringKind::DefaultBound { .. } | DesugaringKind::Await, ) => false, ExpnKind::AstPass(_) @@ -1235,6 +1236,13 @@ pub enum DesugaringKind { source: bool, }, RangeExpr, + /// Implicit `Sized` or `MetaSized` bounds. The actual source location points to just the + /// param or item for which the implicit bound was generated. + DefaultBound { + /// The definition this implied bound was added to. + /// So far only supports params, but may be used for super trait bounds and assoc ty bounds in the future + def: DefId, + }, } impl DesugaringKind { @@ -1257,6 +1265,7 @@ impl DesugaringKind { "expression that expanded into a format string literal" } DesugaringKind::RangeExpr => "range expression", + DesugaringKind::DefaultBound { .. } => "implied bound", } } @@ -1277,6 +1286,7 @@ impl DesugaringKind { DesugaringKind::PatTyRange => value == "PatTyRange", DesugaringKind::FormatLiteral { .. } => value == "FormatLiteral", DesugaringKind::RangeExpr => value == "RangeExpr", + DesugaringKind::DefaultBound { .. } => value == "ImpliedBound", } } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 0849215c13404..b0f2bafa9eafc 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3598,12 +3598,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { tcx.visible_parent_map(()).get(&def_id).is_some() }; if tcx.is_lang_item(def_id, LangItem::Sized) { - // Check if this is an implicit bound, even in foreign crates. - if tcx - .generics_of(item_def_id) - .own_params - .iter() - .any(|param| tcx.def_span(param.def_id) == span) + if let Some(DesugaringKind::DefaultBound { .. }) = + span.desugaring_kind() { a = "an implicit `Sized`"; this = @@ -4217,12 +4213,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // implicit, mention it as such. if let Some(pred) = predicate.as_trait_clause() && self.tcx.is_lang_item(pred.def_id(), LangItem::Sized) - && self - .tcx - .generics_of(data.impl_or_alias_def_id) - .own_params - .iter() - .any(|param| self.tcx.def_span(param.def_id) == data.span) + && let Some(DesugaringKind::DefaultBound { .. }) = + data.span.desugaring_kind() { spans.push_span_label( data.span, @@ -5912,7 +5904,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let sized_trait = self.tcx.lang_items().sized_trait(); debug!(?generics.params); debug!(?generics.predicates); - let Some(param) = generics.params.iter().find(|param| param.span == span) else { + let Some(DesugaringKind::DefaultBound { def }) = span.desugaring_kind() else { + return; + }; + let Some(param) = generics.params.iter().find(|param| param.def_id.to_def_id() == def) + else { return; }; // Check that none of the explicit trait bounds is `Sized`. Assume that an explicit diff --git a/tests/ui/box/into-boxed-slice-fail.stderr b/tests/ui/box/into-boxed-slice-fail.stderr index f102f666dc276..2d57c4b75e751 100644 --- a/tests/ui/box/into-boxed-slice-fail.stderr +++ b/tests/ui/box/into-boxed-slice-fail.stderr @@ -7,7 +7,7 @@ LL | let _ = Box::into_boxed_slice(boxed_slice); | required by a bound introduced by this call | = help: the trait `Sized` is not implemented for `[u8]` -note: required by a bound in `Box::::into_boxed_slice` +note: required by an implicit `Sized` bound in `Box::::into_boxed_slice` --> $SRC_DIR/alloc/src/boxed.rs:LL:COL error[E0277]: the size for values of type `[u8]` cannot be known at compilation time @@ -28,7 +28,7 @@ LL | let _ = Box::into_boxed_slice(boxed_trait); | required by a bound introduced by this call | = help: the trait `Sized` is not implemented for `dyn Debug` -note: required by a bound in `Box::::into_boxed_slice` +note: required by an implicit `Sized` bound in `Box::::into_boxed_slice` --> $SRC_DIR/alloc/src/boxed.rs:LL:COL error[E0277]: the size for values of type `dyn Debug` cannot be known at compilation time diff --git a/tests/ui/closures/unsized_value_move.stderr b/tests/ui/closures/unsized_value_move.stderr index a9a26a42d1675..4c7273c3a0060 100644 --- a/tests/ui/closures/unsized_value_move.stderr +++ b/tests/ui/closures/unsized_value_move.stderr @@ -7,7 +7,7 @@ LL | (|| Box::new(*(&[0][..])))(); | required by a bound introduced by this call | = help: the trait `Sized` is not implemented for `[{integer}]` -note: required by a bound in `Box::::new` +note: required by an implicit `Sized` bound in `Box::::new` --> $SRC_DIR/alloc/src/boxed.rs:LL:COL help: references are always `Sized`, even if they point to unsized data; consider not dereferencing the expression | diff --git a/tests/ui/coherence/deep-bad-copy-reason.rs b/tests/ui/coherence/deep-bad-copy-reason.rs index f1c2698bad5cc..dbc33c009e542 100644 --- a/tests/ui/coherence/deep-bad-copy-reason.rs +++ b/tests/ui/coherence/deep-bad-copy-reason.rs @@ -14,6 +14,7 @@ pub struct ListS { pub struct Interned<'a, T>(&'a T); //~^ NOTE: required by an implicit `Sized` //~| NOTE: required by the implicit `Sized` +//~| NOTE: in this expansion of desugaring of implied bound impl<'a, T> Clone for Interned<'a, T> { fn clone(&self) -> Self { diff --git a/tests/ui/coherence/deep-bad-copy-reason.stderr b/tests/ui/coherence/deep-bad-copy-reason.stderr index 534f26c39c2d4..131796a6c05b0 100644 --- a/tests/ui/coherence/deep-bad-copy-reason.stderr +++ b/tests/ui/coherence/deep-bad-copy-reason.stderr @@ -1,5 +1,5 @@ error[E0204]: the trait `Copy` cannot be implemented for this type - --> $DIR/deep-bad-copy-reason.rs:38:24 + --> $DIR/deep-bad-copy-reason.rs:39:24 | LL | pub struct List<'tcx, T>(Interned<'tcx, ListS>); | ------------------------ this field does not implement `Copy` @@ -8,13 +8,13 @@ LL | impl<'tcx, T> Copy for List<'tcx, T> {} | ^^^^^^^^^^^^^ | note: the `Copy` impl for `Interned<'tcx, ListS>` requires that `OpaqueListContents: Sized` - --> $DIR/deep-bad-copy-reason.rs:26:26 + --> $DIR/deep-bad-copy-reason.rs:27:26 | LL | pub struct List<'tcx, T>(Interned<'tcx, ListS>); | ^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the size for values of type `OpaqueListContents` cannot be known at compilation time - --> $DIR/deep-bad-copy-reason.rs:26:26 + --> $DIR/deep-bad-copy-reason.rs:27:26 | LL | pub struct List<'tcx, T>(Interned<'tcx, ListS>); | ^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time diff --git a/tests/ui/const-generics/unused-type-param-suggestion.rs b/tests/ui/const-generics/unused-type-param-suggestion.rs index b8ae4f6b56b70..917dc972eb737 100644 --- a/tests/ui/const-generics/unused-type-param-suggestion.rs +++ b/tests/ui/const-generics/unused-type-param-suggestion.rs @@ -1,4 +1,4 @@ -#![crate_type="lib"] +#![crate_type = "lib"] struct S; //~^ ERROR type parameter `N` is never used @@ -25,4 +25,3 @@ type C = (); type D = (); //~^ ERROR type parameter `N` is never used //~| HELP consider removing `N` -//~| HELP if you intended `N` to be a const parameter diff --git a/tests/ui/const-generics/unused-type-param-suggestion.stderr b/tests/ui/const-generics/unused-type-param-suggestion.stderr index a7aa477ab31a5..67b704d8bc725 100644 --- a/tests/ui/const-generics/unused-type-param-suggestion.stderr +++ b/tests/ui/const-generics/unused-type-param-suggestion.stderr @@ -47,7 +47,6 @@ LL | type D = (); | ^ unused type parameter | = help: consider removing `N` or referring to it in the body of the type alias - = help: if you intended `N` to be a const parameter, use `const N: /* Type */` instead error: aborting due to 6 previous errors diff --git a/tests/ui/dst/dst-rvalue.stderr b/tests/ui/dst/dst-rvalue.stderr index d8c529617f75f..b452311f70a56 100644 --- a/tests/ui/dst/dst-rvalue.stderr +++ b/tests/ui/dst/dst-rvalue.stderr @@ -7,7 +7,7 @@ LL | let _x: Box = Box::new(*"hello world"); | required by a bound introduced by this call | = help: the trait `Sized` is not implemented for `str` -note: required by a bound in `Box::::new` +note: required by an implicit `Sized` bound in `Box::::new` --> $SRC_DIR/alloc/src/boxed.rs:LL:COL help: references are always `Sized`, even if they point to unsized data; consider not dereferencing the expression | @@ -24,7 +24,7 @@ LL | let _x: Box<[isize]> = Box::new(*array); | required by a bound introduced by this call | = help: the trait `Sized` is not implemented for `[isize]` -note: required by a bound in `Box::::new` +note: required by an implicit `Sized` bound in `Box::::new` --> $SRC_DIR/alloc/src/boxed.rs:LL:COL help: references are always `Sized`, even if they point to unsized data; consider not dereferencing the expression | diff --git a/tests/ui/extern/extern-types-unsized.stderr b/tests/ui/extern/extern-types-unsized.stderr index 9953e5686632a..6c1d7a7b61b3c 100644 --- a/tests/ui/extern/extern-types-unsized.stderr +++ b/tests/ui/extern/extern-types-unsized.stderr @@ -67,10 +67,10 @@ LL | assert_sized::>(); | = help: the nightly-only, unstable trait `MetaSized` is not implemented for `A` note: required by a bound in `Bar` - --> $DIR/extern-types-unsized.rs:14:12 + --> $DIR/extern-types-unsized.rs:14:15 | LL | struct Bar { - | ^ required by this bound in `Bar` + | ^^^^^^ required by this bound in `Bar` error[E0277]: the size for values of type `A` cannot be known at compilation time --> $DIR/extern-types-unsized.rs:32:20 @@ -102,10 +102,10 @@ LL | assert_sized::>>(); | = help: the nightly-only, unstable trait `MetaSized` is not implemented for `A` note: required by a bound in `Bar` - --> $DIR/extern-types-unsized.rs:14:12 + --> $DIR/extern-types-unsized.rs:14:15 | LL | struct Bar { - | ^ required by this bound in `Bar` + | ^^^^^^ required by this bound in `Bar` error: aborting due to 6 previous errors diff --git a/tests/ui/sized-hierarchy/overflow.current.stderr b/tests/ui/sized-hierarchy/overflow.current.stderr index da58a6d2f7bf6..9853be2a7f8e0 100644 --- a/tests/ui/sized-hierarchy/overflow.current.stderr +++ b/tests/ui/sized-hierarchy/overflow.current.stderr @@ -8,9 +8,9 @@ note: required for `Box` to implement `ParseTokens` --> $DIR/overflow.rs:13:31 | LL | impl ParseTokens for Box { - | - ^^^^^^^^^^^ ^^^^^^ - | | - | unsatisfied trait bound introduced here + | ------ ^^^^^^^^^^^ ^^^^^^ + | | + | unsatisfied trait bound introduced here = note: 1 redundant requirement hidden = note: required for `Box>` to implement `ParseTokens` diff --git a/tests/ui/trait-bounds/enum-unit-variant-trait-bound.stderr b/tests/ui/trait-bounds/enum-unit-variant-trait-bound.stderr index 9a3bcaa0c4aa4..7402129cddeac 100644 --- a/tests/ui/trait-bounds/enum-unit-variant-trait-bound.stderr +++ b/tests/ui/trait-bounds/enum-unit-variant-trait-bound.stderr @@ -5,7 +5,7 @@ LL | let _ = Option::<[u8]>::None; | ^^^^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `[u8]` -note: required by a bound in `None` +note: required by an implicit `Sized` bound in `None` --> $SRC_DIR/core/src/option.rs:LL:COL error: aborting due to 1 previous error From 0981e9c2cb1abecd02b64aed991d09cdb3fd513e Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Tue, 16 Jun 2026 10:10:53 +0100 Subject: [PATCH 17/23] Refactor `AliasTy`, `AliasTerm` and `UnevaluatedConst` to use `Alias` --- compiler/rustc_type_ir/src/const_kind.rs | 26 +-------- compiler/rustc_type_ir/src/lib.rs | 2 + compiler/rustc_type_ir/src/predicate.rs | 74 ++++++------------------ compiler/rustc_type_ir/src/ty/alias.rs | 56 ++++++++++++++++++ compiler/rustc_type_ir/src/ty/mod.rs | 3 + compiler/rustc_type_ir/src/ty_kind.rs | 40 +------------ 6 files changed, 85 insertions(+), 116 deletions(-) create mode 100644 compiler/rustc_type_ir/src/ty/alias.rs create mode 100644 compiler/rustc_type_ir/src/ty/mod.rs diff --git a/compiler/rustc_type_ir/src/const_kind.rs b/compiler/rustc_type_ir/src/const_kind.rs index b3a33b775ad10..2955aff10eb6a 100644 --- a/compiler/rustc_type_ir/src/const_kind.rs +++ b/compiler/rustc_type_ir/src/const_kind.rs @@ -9,7 +9,7 @@ use rustc_type_ir_macros::{ GenericTypeVisitable, Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic, }; -use crate::{self as ty, BoundVarIndexKind, Interner}; +use crate::{self as ty, BoundVarIndexKind, Interner, UnevaluatedConst}; /// Represents a constant in Rust. #[derive_where(Clone, Copy, Hash, PartialEq; I: Interner)] @@ -67,26 +67,6 @@ impl fmt::Debug for ConstKind { } } -/// An unevaluated (potentially generic) constant used in the type-system. -#[derive_where(Clone, Copy, Debug, Hash, PartialEq; I: Interner)] -#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] -#[cfg_attr( - feature = "nightly", - derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) -)] -pub struct UnevaluatedConst { - #[type_foldable(identity)] - #[type_visitable(ignore)] - pub kind: UnevaluatedConstKind, - pub args: I::GenericArgs, - - /// This field exists to prevent the creation of `UnevaluatedConst` without using [`UnevaluatedConst::new`]. - #[derive_where(skip(Debug))] - pub(crate) _use_unevaluated_const_new_instead: (), -} - -impl Eq for UnevaluatedConst {} - impl UnevaluatedConst { #[inline] pub fn new( @@ -103,7 +83,7 @@ impl UnevaluatedConst { }; interner.debug_assert_args_compatible(def_id, args); } - UnevaluatedConst { kind, args, _use_unevaluated_const_new_instead: () } + UnevaluatedConst { kind, args, _use_alias_new_instead: () } } pub fn type_of(self, interner: I) -> ty::Unnormalized { @@ -121,7 +101,7 @@ impl UnevaluatedConst { /// and handled in very similar ways. The documentation for AliasTyKind/etc. may be helpful when /// learning about UnevaluatedConstKind. #[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] -#[derive(GenericTypeVisitable, Lift_Generic)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index d9906795dfba6..82fa72825d4d4 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -52,6 +52,7 @@ mod pattern; mod predicate; mod predicate_kind; mod region_kind; +mod ty; mod ty_info; mod ty_kind; mod unnormalized; @@ -80,6 +81,7 @@ pub use predicate_kind::*; pub use region_kind::*; pub use rustc_ast_ir::{FloatTy, IntTy, Movability, Mutability, Pinnedness, UintTy}; use rustc_type_ir_macros::GenericTypeVisitable; +pub use ty::{Alias, AliasTerm, AliasTy, UnevaluatedConst}; pub use ty_info::*; pub use ty_kind::*; pub use unnormalized::Unnormalized; diff --git a/compiler/rustc_type_ir/src/predicate.rs b/compiler/rustc_type_ir/src/predicate.rs index a7f7c2fa44358..1a07010442ab4 100644 --- a/compiler/rustc_type_ir/src/predicate.rs +++ b/compiler/rustc_type_ir/src/predicate.rs @@ -9,9 +9,10 @@ use rustc_type_ir_macros::{ }; use crate::inherent::*; +use crate::ty::AliasTerm; use crate::upcast::{Upcast, UpcastFrom}; use crate::visit::TypeVisitableExt as _; -use crate::{self as ty, AliasTyKind, Interner, UnevaluatedConstKind}; +use crate::{self as ty, Alias, AliasTyKind, Interner, UnevaluatedConstKind}; /// `A: 'region` #[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, A)] @@ -537,7 +538,7 @@ impl ty::Binder> { } #[derive_where(Clone, Copy, PartialEq, Eq, Hash, Debug; I: Interner)] -#[derive(Lift_Generic, TypeVisitable_Generic, GenericTypeVisitable)] +#[derive(TypeVisitable_Generic, TypeFoldable_Generic, Lift_Generic, GenericTypeVisitable)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) @@ -640,41 +641,6 @@ impl From> for AliasTermKind { } } -/// Represents the unprojected term of a projection goal. -/// -/// * For a projection, this would be `>::N<...>`. -/// * For an inherent projection, this would be `Ty::N<...>`. -/// * For an opaque type, there is no explicit syntax. -#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] -#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] -#[cfg_attr( - feature = "nightly", - derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) -)] -pub struct AliasTerm { - /// The parameters of the associated or opaque item. - /// - /// For a projection, these are the generic parameters for the trait and the - /// GAT parameters, if there are any. - /// - /// For an inherent projection, they consist of the self type and the GAT parameters, - /// if there are any. - /// - /// For RPIT the generic parameters are for the generics of the function, - /// while for TAIT it is used for the generic parameters of the alias. - pub args: I::GenericArgs, - - #[type_foldable(identity)] - #[type_visitable(ignore)] - pub kind: AliasTermKind, - - /// This field exists to prevent the creation of `AliasTerm` without using [`AliasTerm::new_from_args`]. - #[derive_where(skip(Debug))] - _use_alias_term_new_instead: (), -} - -impl Eq for AliasTerm {} - impl AliasTerm { pub fn new_from_args( interner: I, @@ -694,7 +660,7 @@ impl AliasTerm { }; interner.debug_assert_args_compatible(def_id, args); } - AliasTerm { kind, args, _use_alias_term_new_instead: () } + AliasTerm { kind, args, _use_alias_new_instead: () } } pub fn new( @@ -724,7 +690,7 @@ impl AliasTerm { panic!("Cannot turn `{}` into `AliasTy`", kind.descr()) } }; - ty::AliasTy { kind, args: self.args, _use_alias_ty_new_instead: () } + ty::AliasTy { kind, args: self.args, _use_alias_new_instead: () } } pub fn expect_ct(self) -> ty::UnevaluatedConst { @@ -742,7 +708,7 @@ impl AliasTerm { panic!("Cannot turn `{}` into `UnevaluatedConst`", kind.descr()) } }; - ty::UnevaluatedConst { kind, args: self.args, _use_unevaluated_const_new_instead: () } + ty::UnevaluatedConst { kind, args: self.args, _use_alias_new_instead: () } } pub fn to_term(self, interner: I) -> I::Term { @@ -897,21 +863,13 @@ impl AliasTerm { impl From> for AliasTerm { fn from(ty: ty::AliasTy) -> Self { - AliasTerm { - args: ty.args, - kind: AliasTermKind::from(ty.kind), - _use_alias_term_new_instead: (), - } + AliasTerm { args: ty.args, kind: AliasTermKind::from(ty.kind), _use_alias_new_instead: () } } } impl From> for AliasTerm { fn from(ty: ty::UnevaluatedConst) -> Self { - AliasTerm { - args: ty.args, - kind: AliasTermKind::from(ty.kind), - _use_alias_term_new_instead: (), - } + AliasTerm { args: ty.args, kind: AliasTermKind::from(ty.kind), _use_alias_new_instead: () } } } @@ -990,19 +948,17 @@ impl fmt::Debug for ProjectionPredicate { /// Used by the new solver to normalize an alias. This always expects the `term` to /// be an unconstrained inference variable which is used as the output. -#[derive_where(Clone, Copy, Hash, PartialEq; I: Interner)] +#[derive_where(Clone, Copy, Hash, Eq, PartialEq; I: Interner, K)] #[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) )] -pub struct NormalizesTo { - pub alias: AliasTerm, +pub struct NormalizesTo> { + pub alias: Alias, pub term: I::Term, } -impl Eq for NormalizesTo {} - impl NormalizesTo { pub fn self_ty(self) -> I::Ty { self.alias.self_ty() @@ -1017,7 +973,13 @@ impl NormalizesTo { } } -impl fmt::Debug for NormalizesTo { +impl fmt::Debug for NormalizesTo +where + // `TypeVisitable_Generic` derived on `NormalizesTo` creates a field-level + // `Alias: TypeVisitable` bound. Since `TypeVisitable: fmt::Debug`, + // that proves `Alias: fmt::Debug`, but not `K: fmt::Debug`. + Alias: fmt::Debug, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "NormalizesTo({:?}, {:?})", self.alias, self.term) } diff --git a/compiler/rustc_type_ir/src/ty/alias.rs b/compiler/rustc_type_ir/src/ty/alias.rs new file mode 100644 index 0000000000000..d61c2f2ca7ff7 --- /dev/null +++ b/compiler/rustc_type_ir/src/ty/alias.rs @@ -0,0 +1,56 @@ +use derive_where::derive_where; +#[cfg(feature = "nightly")] +use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash_NoContext}; +use rustc_type_ir_macros::{ + GenericTypeVisitable, Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic, +}; + +use crate::predicate::AliasTermKind; +use crate::ty_kind::AliasTyKind; +use crate::{Interner, UnevaluatedConstKind}; + +/// Represents an alias of a type, constant, or other term-like item. +/// +/// * For a projection, this would be `>::N<...>`. +/// * For an inherent projection, this would be `Ty::N<...>`. +/// * For an opaque type, there is no explicit syntax. +#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner, K)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] +#[cfg_attr( + feature = "nightly", + derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) +)] +pub struct Alias { + pub kind: K, + + /// The parameters of the associated, opaque, or constant alias. + /// + /// For a projection, these are the generic parameters for the trait and the + /// GAT parameters, if there are any. + /// + /// For an inherent projection, they consist of the self type and the GAT parameters, + /// if there are any. + /// + /// For RPIT the generic parameters are for the generics of the function, + /// while for TAIT it is used for the generic parameters of the alias. + pub args: I::GenericArgs, + + /// This field exists to prevent the creation of `Alias` without using the relevant constructor. + #[derive_where(skip(Debug))] + #[type_visitable(ignore)] + #[type_foldable(identity)] + #[lift(identity)] + pub(crate) _use_alias_new_instead: (), +} + +impl Eq for Alias {} + +impl Alias { + pub fn kind(self, _interner: I) -> K { + self.kind + } +} + +pub type AliasTerm = Alias>; +pub type AliasTy = Alias>; +pub type UnevaluatedConst = Alias>; diff --git a/compiler/rustc_type_ir/src/ty/mod.rs b/compiler/rustc_type_ir/src/ty/mod.rs new file mode 100644 index 0000000000000..fec25193513c0 --- /dev/null +++ b/compiler/rustc_type_ir/src/ty/mod.rs @@ -0,0 +1,3 @@ +mod alias; + +pub use alias::{Alias, AliasTerm, AliasTy, UnevaluatedConst}; diff --git a/compiler/rustc_type_ir/src/ty_kind.rs b/compiler/rustc_type_ir/src/ty_kind.rs index 784a91424156f..c3d52f4a191ac 100644 --- a/compiler/rustc_type_ir/src/ty_kind.rs +++ b/compiler/rustc_type_ir/src/ty_kind.rs @@ -17,6 +17,7 @@ use rustc_type_ir_macros::{ use self::TyKind::*; pub use self::closure::*; use crate::inherent::*; +use crate::ty::AliasTy; #[cfg(feature = "nightly")] use crate::visit::TypeVisitable; use crate::{self as ty, BoundVarIndexKind, FloatTy, IntTy, Interner, UintTy}; @@ -24,7 +25,7 @@ use crate::{self as ty, BoundVarIndexKind, FloatTy, IntTy, Interner, UintTy}; mod closure; #[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] -#[derive(GenericTypeVisitable, Lift_Generic)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] #[cfg_attr( feature = "nightly", derive(Encodable_NoContext, Decodable_NoContext, StableHash_NoContext) @@ -427,45 +428,10 @@ impl fmt::Debug for TyKind { } } -/// Represents the projection of an associated, opaque, or lazy-type-alias type. -/// -/// * For a projection, this would be `>::N<...>`. -/// * For an inherent projection, this would be `Ty::N<...>`. -/// * For an opaque type, there is no explicit syntax. -#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner)] -#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic, Lift_Generic)] -#[cfg_attr( - feature = "nightly", - derive(Decodable_NoContext, Encodable_NoContext, StableHash_NoContext) -)] -pub struct AliasTy { - /// The parameters of the associated or opaque type. - /// - /// For a projection, these are the generic parameters for the trait and the - /// GAT parameters, if there are any. - /// - /// For an inherent projection, they consist of the self type and the GAT parameters, - /// if there are any. - /// - /// For RPIT the generic parameters are for the generics of the function, - /// while for TAIT it is used for the generic parameters of the alias. - pub args: I::GenericArgs, - - #[type_foldable(identity)] - #[type_visitable(ignore)] - pub kind: AliasTyKind, - - /// This field exists to prevent the creation of `AliasTy` without using [`AliasTy::new_from_args`]. - #[derive_where(skip(Debug))] - pub(crate) _use_alias_ty_new_instead: (), -} - -impl Eq for AliasTy {} - impl AliasTy { pub fn new_from_args(interner: I, kind: AliasTyKind, args: I::GenericArgs) -> AliasTy { interner.debug_assert_args_compatible(kind.def_id(), args); - AliasTy { kind, args, _use_alias_ty_new_instead: () } + AliasTy { kind, args, _use_alias_new_instead: () } } pub fn new( From 30e608d1fe58e9b7f466e1ee6178eb27334b3d73 Mon Sep 17 00:00:00 2001 From: James Barford-Evans Date: Tue, 16 Jun 2026 10:11:17 +0100 Subject: [PATCH 18/23] Update tests to reflect Alias changes --- tests/mir-opt/issue_99325.main.built.after.32bit.mir | 2 +- tests/mir-opt/issue_99325.main.built.after.64bit.mir | 2 +- tests/ui/attributes/dump-preds.stderr | 2 +- tests/ui/coherence/occurs-check/associated-type.next.stderr | 4 ++-- tests/ui/coherence/occurs-check/associated-type.old.stderr | 4 ++-- tests/ui/higher-ranked/structually-relate-aliases.stderr | 2 +- tests/ui/offset-of/inside-array-length.stderr | 2 +- tests/ui/traits/next-solver/issue-118950-root-region.stderr | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/mir-opt/issue_99325.main.built.after.32bit.mir b/tests/mir-opt/issue_99325.main.built.after.32bit.mir index 29fdccf85aa00..14a14ffe377f2 100644 --- a/tests/mir-opt/issue_99325.main.built.after.32bit.mir +++ b/tests/mir-opt/issue_99325.main.built.after.32bit.mir @@ -2,7 +2,7 @@ | User Type Annotations | 0: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [&*b"AAAA"], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:13:16: 13:46, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} -| 1: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [UnevaluatedConst { kind: Anon { def_id: DefId(0:8 ~ issue_99325[d56d]::main::{constant#1}) }, args: [], .. }], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:14:16: 14:68, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} +| 1: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [Alias { kind: Anon { def_id: DefId(0:8 ~ issue_99325[d56d]::main::{constant#1}) }, args: [], .. }], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:14:16: 14:68, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} | fn main() -> () { let mut _0: (); diff --git a/tests/mir-opt/issue_99325.main.built.after.64bit.mir b/tests/mir-opt/issue_99325.main.built.after.64bit.mir index 29fdccf85aa00..14a14ffe377f2 100644 --- a/tests/mir-opt/issue_99325.main.built.after.64bit.mir +++ b/tests/mir-opt/issue_99325.main.built.after.64bit.mir @@ -2,7 +2,7 @@ | User Type Annotations | 0: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [&*b"AAAA"], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:13:16: 13:46, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} -| 1: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [UnevaluatedConst { kind: Anon { def_id: DefId(0:8 ~ issue_99325[d56d]::main::{constant#1}) }, args: [], .. }], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:14:16: 14:68, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} +| 1: user_ty: Canonical { value: TypeOf(DefId(0:3 ~ issue_99325[d56d]::function_with_bytes), UserArgs { args: [Alias { kind: Anon { def_id: DefId(0:8 ~ issue_99325[d56d]::main::{constant#1}) }, args: [], .. }], user_self_ty: None }), max_universe: U0, var_kinds: [] }, span: $DIR/issue_99325.rs:14:16: 14:68, inferred_ty: fn() -> &'static [u8] {function_with_bytes::<&*b"AAAA">} | fn main() -> () { let mut _0: (); diff --git a/tests/ui/attributes/dump-preds.stderr b/tests/ui/attributes/dump-preds.stderr index 12d9308431734..b4be76450c6a9 100644 --- a/tests/ui/attributes/dump-preds.stderr +++ b/tests/ui/attributes/dump-preds.stderr @@ -33,7 +33,7 @@ error: rustc_dump_item_bounds LL | type Assoc: std::ops::Deref | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: Binder { value: ProjectionPredicate(AliasTerm { args: [Alias(AliasTy { args: [Self/#0, T/#1, P/#2], kind: Projection { def_id: DefId(..) }, .. })], kind: ProjectionTy { def_id: DefId(..) }, .. }, Term::Ty(())), bound_vars: [] } + = note: Binder { value: ProjectionPredicate(Alias { kind: ProjectionTy { def_id: DefId(..) }, args: [Alias(Alias { kind: Projection { def_id: DefId(..) }, args: [Self/#0, T/#1, P/#2], .. })], .. }, Term::Ty(())), bound_vars: [] } = note: Binder { value: TraitPredicate(<>::Assoc

as std::ops::Deref>, polarity:Positive), bound_vars: [] } = note: Binder { value: TraitPredicate(<>::Assoc

as std::marker::Sized>, polarity:Positive), bound_vars: [] } diff --git a/tests/ui/coherence/occurs-check/associated-type.next.stderr b/tests/ui/coherence/occurs-check/associated-type.next.stderr index 38fb75483f3b3..d68808010bead 100644 --- a/tests/ui/coherence/occurs-check/associated-type.next.stderr +++ b/tests/ui/coherence/occurs-check/associated-type.next.stderr @@ -1,5 +1,5 @@ - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, .. } - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], .. } error[E0119]: conflicting implementations of trait `Overlap fn(&'a (), ())>` for type `for<'a> fn(&'a (), ())` --> $DIR/associated-type.rs:32:1 | diff --git a/tests/ui/coherence/occurs-check/associated-type.old.stderr b/tests/ui/coherence/occurs-check/associated-type.old.stderr index 1dac3ecea2687..0472f93cae32f 100644 --- a/tests/ui/coherence/occurs-check/associated-type.old.stderr +++ b/tests/ui/coherence/occurs-check/associated-type.old.stderr @@ -1,5 +1,5 @@ - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, .. } - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: ProjectionTy { def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) }, args: [*const ?1t, '^0.Named(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1))], .. } error[E0119]: conflicting implementations of trait `Overlap fn(&'a (), ())>` for type `for<'a> fn(&'a (), ())` --> $DIR/associated-type.rs:32:1 | diff --git a/tests/ui/higher-ranked/structually-relate-aliases.stderr b/tests/ui/higher-ranked/structually-relate-aliases.stderr index 4649aac47cdbf..8996bf11b3721 100644 --- a/tests/ui/higher-ranked/structually-relate-aliases.stderr +++ b/tests/ui/higher-ranked/structually-relate-aliases.stderr @@ -1,4 +1,4 @@ - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: [?1t, '^0.Named(DefId(0:15 ~ structually_relate_aliases[de75]::{impl#1}::'a))], kind: ProjectionTy { def_id: DefId(0:5 ~ structually_relate_aliases[de75]::ToUnit::Unit) }, .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: ProjectionTy { def_id: DefId(0:5 ~ structually_relate_aliases[de75]::ToUnit::Unit) }, args: [?1t, '^0.Named(DefId(0:15 ~ structually_relate_aliases[de75]::{impl#1}::'a))], .. } error[E0277]: the trait bound `for<'a> T: ToUnit<'a>` is not satisfied --> $DIR/structually-relate-aliases.rs:13:36 | diff --git a/tests/ui/offset-of/inside-array-length.stderr b/tests/ui/offset-of/inside-array-length.stderr index fd47e6c8b6805..4b2a50acf82cb 100644 --- a/tests/ui/offset-of/inside-array-length.stderr +++ b/tests/ui/offset-of/inside-array-length.stderr @@ -37,7 +37,7 @@ LL | fn foo<'a, T: 'a>(_: [(); std::mem::offset_of!((T,), 0)]) {} = note: ...which requires caching mir of `foo::{constant#0}` for CTFE... = note: ...which requires elaborating drops for `foo::{constant#0}`... = note: ...which requires borrow-checking `foo::{constant#0}`... - = note: ...which requires normalizing `Binder { value: ConstEvaluatable(UnevaluatedConst { kind: Anon { def_id: DefId(0:7 ~ inside_array_length[07d6]::foo::{constant#0}) }, args: ['^c_1, T/#1], .. }), bound_vars: [] }`... + = note: ...which requires normalizing `Binder { value: ConstEvaluatable(Alias { kind: Anon { def_id: DefId(0:7 ~ inside_array_length[07d6]::foo::{constant#0}) }, args: ['^c_1, T/#1], .. }), bound_vars: [] }`... = note: ...which again requires evaluating type-level constant, completing the cycle = note: cycle used when normalizing `inside_array_length::::foo::{constant#0}` = note: for more information, see and diff --git a/tests/ui/traits/next-solver/issue-118950-root-region.stderr b/tests/ui/traits/next-solver/issue-118950-root-region.stderr index 32adbb491bee7..eac8b6e11bf1b 100644 --- a/tests/ui/traits/next-solver/issue-118950-root-region.stderr +++ b/tests/ui/traits/next-solver/issue-118950-root-region.stderr @@ -16,7 +16,7 @@ help: this trait has no implementations, consider adding one LL | trait ToUnit<'a> { | ^^^^^^^^^^^^^^^^ - WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: AliasTerm { args: ['^0.Named(DefId(0:15 ~ issue_118950_root_region[d54f]::{impl#1}::'a)), ?1t], kind: FreeTy { def_id: DefId(0:8 ~ issue_118950_root_region[d54f]::Assoc) }, .. } + WARN rustc_infer::infer::relate::generalize may incompletely handle alias type: Alias { kind: FreeTy { def_id: DefId(0:8 ~ issue_118950_root_region[d54f]::Assoc) }, args: ['^0.Named(DefId(0:15 ~ issue_118950_root_region[d54f]::{impl#1}::'a)), ?1t], .. } error: aborting due to 2 previous errors Some errors have detailed explanations: E0277, E0425. From 16c1bf45bd2caf7b587a8bfe7c65e696b3b6aebd Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 2 Jun 2026 12:50:22 +0200 Subject: [PATCH 19/23] Add expansion info to assoc type default bounds --- .../src/collect/item_bounds.rs | 8 ++++---- .../src/hir_ty_lowering/bounds.rs | 16 ++++++++++++---- .../src/hir_ty_lowering/dyn_trait.rs | 2 +- .../src/hir_ty_lowering/mod.rs | 9 ++++++--- .../src/error_reporting/traits/suggestions.rs | 10 ++++++---- .../associated-types/issue-63593.current.stderr | 4 ++-- .../ui/associated-types/issue-63593.next.stderr | 4 ++-- tests/ui/drop/dropck-normalize-errors.nll.stderr | 4 ++-- .../drop/dropck-normalize-errors.polonius.stderr | 4 ++-- .../assoc_type_bounds_implicit_sized.stderr | 4 ++-- ...ed-gat-bound-during-assoc-ty-selection.stderr | 4 ++-- .../issue-74816.current.stderr | 4 ++-- .../issue-74816.next.stderr | 4 ++-- .../in-trait/refine-resolution-errors.stderr | 4 ++-- .../suggest-maybe-sized-bound.stderr | 4 ++-- tests/ui/traits/cycle-cache-err-60010.stderr | 4 ++-- ...ned-projection-normalization-2.current.stderr | 4 ++-- ...rained-projection-normalization-2.next.stderr | 4 ++-- ...ained-projection-normalization.current.stderr | 4 ++-- ...strained-projection-normalization.next.stderr | 4 ++-- 20 files changed, 59 insertions(+), 46 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs index f005a831de2a0..7603f11b7ebef 100644 --- a/compiler/rustc_hir_analysis/src/collect/item_bounds.rs +++ b/compiler/rustc_hir_analysis/src/collect/item_bounds.rs @@ -59,7 +59,7 @@ fn associated_type_bounds<'tcx>( item_ty, hir_bounds, &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::AssociatedType(assoc_item_def_id), span, ); icx.lowerer().add_default_traits( @@ -67,7 +67,7 @@ fn associated_type_bounds<'tcx>( item_ty, hir_bounds, &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::AssociatedType(assoc_item_def_id), span, ); @@ -387,7 +387,7 @@ fn opaque_type_bounds<'tcx>( item_ty, hir_bounds, &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::ImplTrait, span, ); icx.lowerer().add_default_traits( @@ -395,7 +395,7 @@ fn opaque_type_bounds<'tcx>( item_ty, hir_bounds, &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::ImplTrait, span, ); } diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs index e0e2926dfeb83..74d5447b03e80 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs @@ -181,8 +181,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { return; } } - ImpliedBoundsContext::TyParam(..) | ImpliedBoundsContext::AssociatedTypeOrImplTrait => { - } + ImpliedBoundsContext::TyParam(..) + | ImpliedBoundsContext::AssociatedType(..) + | ImpliedBoundsContext::TraitObject + | ImpliedBoundsContext::TraitAscription + | ImpliedBoundsContext::ImplTrait => {} } let collected = collect_sizedness_bounds(tcx, hir_bounds, where_bounds, context, span); if let Some(span) = collected.sized.maybe.or(collected.sized.negative) @@ -195,7 +198,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { add_trait_bound(tcx, bounds, self_ty, meta_sized_did, span); } else if !collected.any() { let span = match context { - ImpliedBoundsContext::TyParam(def) => { + ImpliedBoundsContext::TraitDef(def) + | ImpliedBoundsContext::TyParam(def) + | ImpliedBoundsContext::AssociatedType(def) => { self.tcx().with_stable_hashing_context(|hcx| { span.mark_with_reason( None, @@ -214,7 +219,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { add_trait_bound(tcx, bounds, self_ty, meta_sized_did, span); } ImpliedBoundsContext::TyParam(..) - | ImpliedBoundsContext::AssociatedTypeOrImplTrait => { + | ImpliedBoundsContext::AssociatedType(..) + | ImpliedBoundsContext::TraitObject + | ImpliedBoundsContext::TraitAscription + | ImpliedBoundsContext::ImplTrait => { // If there are no explicit sizedness bounds on a parameter then add a default // `Sized` bound. let sized_did = tcx.require_lang_item(hir::LangItem::Sized, span); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs index c6edd30cd63de..e75e5059bc1dc 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/dyn_trait.rs @@ -84,7 +84,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .map(|&trait_ref| hir::GenericBound::Trait(trait_ref)) .collect::>(), &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::TraitObject, span, ); diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index fbf184e17f5ce..e029a7b311fb0 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -65,8 +65,11 @@ pub(crate) enum ImpliedBoundsContext { TraitDef(LocalDefId), /// An implied bound is added to a type parameter TyParam(LocalDefId), - /// An implied bound being added in any other context - AssociatedTypeOrImplTrait, + /// Associated type bounds + AssociatedType(LocalDefId), + ImplTrait, + TraitObject, + TraitAscription, } /// A path segment that is semantically allowed to have generic arguments. @@ -3094,7 +3097,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { self_ty, hir_bounds, &[], - ImpliedBoundsContext::AssociatedTypeOrImplTrait, + ImpliedBoundsContext::TraitAscription, hir_ty.span, ); self.register_trait_ascription_bounds(bounds, hir_ty.hir_id, hir_ty.span); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index b0f2bafa9eafc..c3f4a09b2d431 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -3571,7 +3571,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { } } let mut a = "a"; - let mut this = "this bound"; + let mut this = "this bound".to_owned(); let mut note = None; let mut help = None; if let ty::PredicateKind::Clause(clause) = predicate.kind().skip_binder() { @@ -3598,12 +3598,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { tcx.visible_parent_map(()).get(&def_id).is_some() }; if tcx.is_lang_item(def_id, LangItem::Sized) { - if let Some(DesugaringKind::DefaultBound { .. }) = + if let Some(DesugaringKind::DefaultBound { def }) = span.desugaring_kind() { a = "an implicit `Sized`"; - this = - "the implicit `Sized` requirement on this type parameter"; + this = format!( + "the implicit `Sized` requirement on this {}", + tcx.def_kind(def).descr(def) + ); } if let Some(hir::Node::TraitItem(hir::TraitItem { generics, diff --git a/tests/ui/associated-types/issue-63593.current.stderr b/tests/ui/associated-types/issue-63593.current.stderr index 76fdefeb4e521..4fd7489dca019 100644 --- a/tests/ui/associated-types/issue-63593.current.stderr +++ b/tests/ui/associated-types/issue-63593.current.stderr @@ -4,11 +4,11 @@ error[E0277]: the size for values of type `Self` cannot be known at compilation LL | type This = Self; | ^^^^ doesn't have a size known at compile-time | -note: required by a bound in `MyTrait::This` +note: required by an implicit `Sized` bound in `MyTrait::This` --> $DIR/issue-63593.rs:13:5 | LL | type This = Self; - | ^^^^^^^^^^^^^^^^^ required by this bound in `MyTrait::This` + | ^^^^^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `MyTrait::This` help: consider further restricting `Self` | LL | trait MyTrait: Sized { diff --git a/tests/ui/associated-types/issue-63593.next.stderr b/tests/ui/associated-types/issue-63593.next.stderr index 76fdefeb4e521..4fd7489dca019 100644 --- a/tests/ui/associated-types/issue-63593.next.stderr +++ b/tests/ui/associated-types/issue-63593.next.stderr @@ -4,11 +4,11 @@ error[E0277]: the size for values of type `Self` cannot be known at compilation LL | type This = Self; | ^^^^ doesn't have a size known at compile-time | -note: required by a bound in `MyTrait::This` +note: required by an implicit `Sized` bound in `MyTrait::This` --> $DIR/issue-63593.rs:13:5 | LL | type This = Self; - | ^^^^^^^^^^^^^^^^^ required by this bound in `MyTrait::This` + | ^^^^^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `MyTrait::This` help: consider further restricting `Self` | LL | trait MyTrait: Sized { diff --git a/tests/ui/drop/dropck-normalize-errors.nll.stderr b/tests/ui/drop/dropck-normalize-errors.nll.stderr index 39ec4033eab2f..1766f136ac4d1 100644 --- a/tests/ui/drop/dropck-normalize-errors.nll.stderr +++ b/tests/ui/drop/dropck-normalize-errors.nll.stderr @@ -47,11 +47,11 @@ note: required because it appears within the type `BDecoder` | LL | pub struct BDecoder { | ^^^^^^^^ -note: required by a bound in `Decode::Decoder` +note: required by an implicit `Sized` bound in `Decode::Decoder` --> $DIR/dropck-normalize-errors.rs:8:5 | LL | type Decoder; - | ^^^^^^^^^^^^^ required by this bound in `Decode::Decoder` + | ^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Decode::Decoder` help: consider relaxing the implicit `Sized` restriction | LL | type Decoder: ?Sized; diff --git a/tests/ui/drop/dropck-normalize-errors.polonius.stderr b/tests/ui/drop/dropck-normalize-errors.polonius.stderr index 3d72801b34363..3a08a2268d6bc 100644 --- a/tests/ui/drop/dropck-normalize-errors.polonius.stderr +++ b/tests/ui/drop/dropck-normalize-errors.polonius.stderr @@ -47,11 +47,11 @@ note: required because it appears within the type `BDecoder` | LL | pub struct BDecoder { | ^^^^^^^^ -note: required by a bound in `Decode::Decoder` +note: required by an implicit `Sized` bound in `Decode::Decoder` --> $DIR/dropck-normalize-errors.rs:8:5 | LL | type Decoder; - | ^^^^^^^^^^^^^ required by this bound in `Decode::Decoder` + | ^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Decode::Decoder` help: consider relaxing the implicit `Sized` restriction | LL | type Decoder: ?Sized; diff --git a/tests/ui/dyn-compatibility/assoc_type_bounds_implicit_sized.stderr b/tests/ui/dyn-compatibility/assoc_type_bounds_implicit_sized.stderr index 9bb770ce43199..8092333d6d200 100644 --- a/tests/ui/dyn-compatibility/assoc_type_bounds_implicit_sized.stderr +++ b/tests/ui/dyn-compatibility/assoc_type_bounds_implicit_sized.stderr @@ -5,11 +5,11 @@ LL | type Item = dyn Trait; | ^^^^^^^^^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `(dyn Trait + 'static)` -note: required by a bound in `TraitWithAType::Item` +note: required by an implicit `Sized` bound in `TraitWithAType::Item` --> $DIR/assoc_type_bounds_implicit_sized.rs:4:5 | LL | type Item; - | ^^^^^^^^^^ required by this bound in `TraitWithAType::Item` + | ^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `TraitWithAType::Item` help: consider relaxing the implicit `Sized` restriction | LL | type Item: ?Sized; diff --git a/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr b/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr index 674e28829073f..7bef897e032d1 100644 --- a/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr +++ b/tests/ui/generic-associated-types/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.stderr @@ -5,11 +5,11 @@ LL | type Pointer = dyn Deref; | ^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `(dyn Deref + 'static)` -note: required by a bound in `PointerFamily::Pointer` +note: required by an implicit `Sized` bound in `PointerFamily::Pointer` --> $DIR/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.rs:4:5 | LL | type Pointer: Deref; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PointerFamily::Pointer` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `PointerFamily::Pointer` help: consider relaxing the implicit `Sized` restriction | LL | type Pointer: Deref + ?Sized; diff --git a/tests/ui/generic-associated-types/issue-74816.current.stderr b/tests/ui/generic-associated-types/issue-74816.current.stderr index 335486c6538c1..79aeaa79b4f19 100644 --- a/tests/ui/generic-associated-types/issue-74816.current.stderr +++ b/tests/ui/generic-associated-types/issue-74816.current.stderr @@ -20,11 +20,11 @@ error[E0277]: the size for values of type `Self` cannot be known at compilation LL | type Associated: Trait1 = Self; | ^^^^ doesn't have a size known at compile-time | -note: required by a bound in `Trait2::Associated` +note: required by an implicit `Sized` bound in `Trait2::Associated` --> $DIR/issue-74816.rs:12:5 | LL | type Associated: Trait1 = Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Trait2::Associated` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Trait2::Associated` help: consider further restricting `Self` | LL | trait Trait2: Sized { diff --git a/tests/ui/generic-associated-types/issue-74816.next.stderr b/tests/ui/generic-associated-types/issue-74816.next.stderr index 335486c6538c1..79aeaa79b4f19 100644 --- a/tests/ui/generic-associated-types/issue-74816.next.stderr +++ b/tests/ui/generic-associated-types/issue-74816.next.stderr @@ -20,11 +20,11 @@ error[E0277]: the size for values of type `Self` cannot be known at compilation LL | type Associated: Trait1 = Self; | ^^^^ doesn't have a size known at compile-time | -note: required by a bound in `Trait2::Associated` +note: required by an implicit `Sized` bound in `Trait2::Associated` --> $DIR/issue-74816.rs:12:5 | LL | type Associated: Trait1 = Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Trait2::Associated` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Trait2::Associated` help: consider further restricting `Self` | LL | trait Trait2: Sized { diff --git a/tests/ui/impl-trait/in-trait/refine-resolution-errors.stderr b/tests/ui/impl-trait/in-trait/refine-resolution-errors.stderr index af71e52b87d71..8b730d9535d0a 100644 --- a/tests/ui/impl-trait/in-trait/refine-resolution-errors.stderr +++ b/tests/ui/impl-trait/in-trait/refine-resolution-errors.stderr @@ -13,11 +13,11 @@ LL | LL | type Assoc = T; | ^ doesn't have a size known at compile-time | -note: required by a bound in `Mirror::Assoc` +note: required by an implicit `Sized` bound in `Mirror::Assoc` --> $DIR/refine-resolution-errors.rs:7:5 | LL | type Assoc; - | ^^^^^^^^^^^ required by this bound in `Mirror::Assoc` + | ^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Mirror::Assoc` help: consider removing the `?Sized` bound to make the type parameter `Sized` | LL - impl Mirror for () { diff --git a/tests/ui/trait-bounds/suggest-maybe-sized-bound.stderr b/tests/ui/trait-bounds/suggest-maybe-sized-bound.stderr index b459ad47e067a..63d3b01f27a2e 100644 --- a/tests/ui/trait-bounds/suggest-maybe-sized-bound.stderr +++ b/tests/ui/trait-bounds/suggest-maybe-sized-bound.stderr @@ -22,11 +22,11 @@ LL | type P = [u8]; | ^^^^ doesn't have a size known at compile-time | = help: the trait `Sized` is not implemented for `[u8]` -note: required by a bound in `main::Trait::P` +note: required by an implicit `Sized` bound in `main::Trait::P` --> $DIR/suggest-maybe-sized-bound.rs:13:9 | LL | type P; - | ^^^^^^^^^^ required by this bound in `Trait::P` + | ^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Trait::P` help: consider relaxing the implicit `Sized` restriction | LL | type P: ?Sized; diff --git a/tests/ui/traits/cycle-cache-err-60010.stderr b/tests/ui/traits/cycle-cache-err-60010.stderr index 9665d5badf599..605ef34e5d2b8 100644 --- a/tests/ui/traits/cycle-cache-err-60010.stderr +++ b/tests/ui/traits/cycle-cache-err-60010.stderr @@ -80,11 +80,11 @@ note: required because it appears within the type `SalsaStorage` | LL | struct SalsaStorage { | ^^^^^^^^^^^^ -note: required by a bound in `Database::Storage` +note: required by an implicit `Sized` bound in `Database::Storage` --> $DIR/cycle-cache-err-60010.rs:7:5 | LL | type Storage; - | ^^^^^^^^^^^^^ required by this bound in `Database::Storage` + | ^^^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Database::Storage` help: consider relaxing the implicit `Sized` restriction | LL | type Storage: ?Sized; diff --git a/tests/ui/traits/normalize/unconstrained-projection-normalization-2.current.stderr b/tests/ui/traits/normalize/unconstrained-projection-normalization-2.current.stderr index e8742e7f09902..3ec07397ec148 100644 --- a/tests/ui/traits/normalize/unconstrained-projection-normalization-2.current.stderr +++ b/tests/ui/traits/normalize/unconstrained-projection-normalization-2.current.stderr @@ -22,11 +22,11 @@ LL | LL | type Assoc = T; | ^ doesn't have a size known at compile-time | -note: required by a bound in `Every::Assoc` +note: required by an implicit `Sized` bound in `Every::Assoc` --> $DIR/unconstrained-projection-normalization-2.rs:12:5 | LL | type Assoc; - | ^^^^^^^^^^^ required by this bound in `Every::Assoc` + | ^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Every::Assoc` help: consider removing the `?Sized` bound to make the type parameter `Sized` | LL - impl Every for Thing { diff --git a/tests/ui/traits/normalize/unconstrained-projection-normalization-2.next.stderr b/tests/ui/traits/normalize/unconstrained-projection-normalization-2.next.stderr index e8742e7f09902..3ec07397ec148 100644 --- a/tests/ui/traits/normalize/unconstrained-projection-normalization-2.next.stderr +++ b/tests/ui/traits/normalize/unconstrained-projection-normalization-2.next.stderr @@ -22,11 +22,11 @@ LL | LL | type Assoc = T; | ^ doesn't have a size known at compile-time | -note: required by a bound in `Every::Assoc` +note: required by an implicit `Sized` bound in `Every::Assoc` --> $DIR/unconstrained-projection-normalization-2.rs:12:5 | LL | type Assoc; - | ^^^^^^^^^^^ required by this bound in `Every::Assoc` + | ^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Every::Assoc` help: consider removing the `?Sized` bound to make the type parameter `Sized` | LL - impl Every for Thing { diff --git a/tests/ui/traits/normalize/unconstrained-projection-normalization.current.stderr b/tests/ui/traits/normalize/unconstrained-projection-normalization.current.stderr index 6e908df0aba2d..73bf568d8aec0 100644 --- a/tests/ui/traits/normalize/unconstrained-projection-normalization.current.stderr +++ b/tests/ui/traits/normalize/unconstrained-projection-normalization.current.stderr @@ -22,11 +22,11 @@ LL | LL | type Assoc = T; | ^ doesn't have a size known at compile-time | -note: required by a bound in `Every::Assoc` +note: required by an implicit `Sized` bound in `Every::Assoc` --> $DIR/unconstrained-projection-normalization.rs:11:5 | LL | type Assoc; - | ^^^^^^^^^^^ required by this bound in `Every::Assoc` + | ^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Every::Assoc` help: consider removing the `?Sized` bound to make the type parameter `Sized` | LL - impl Every for Thing { diff --git a/tests/ui/traits/normalize/unconstrained-projection-normalization.next.stderr b/tests/ui/traits/normalize/unconstrained-projection-normalization.next.stderr index 6e908df0aba2d..73bf568d8aec0 100644 --- a/tests/ui/traits/normalize/unconstrained-projection-normalization.next.stderr +++ b/tests/ui/traits/normalize/unconstrained-projection-normalization.next.stderr @@ -22,11 +22,11 @@ LL | LL | type Assoc = T; | ^ doesn't have a size known at compile-time | -note: required by a bound in `Every::Assoc` +note: required by an implicit `Sized` bound in `Every::Assoc` --> $DIR/unconstrained-projection-normalization.rs:11:5 | LL | type Assoc; - | ^^^^^^^^^^^ required by this bound in `Every::Assoc` + | ^^^^^^^^^^^ required by the implicit `Sized` requirement on this associated type in `Every::Assoc` help: consider removing the `?Sized` bound to make the type parameter `Sized` | LL - impl Every for Thing { From 52b20ab57b0ac6aa9e61cc5dcda0e6686d4fbc03 Mon Sep 17 00:00:00 2001 From: Jamie Hill-Daniel Date: Tue, 16 Jun 2026 12:07:52 +0100 Subject: [PATCH 20/23] doc: Document `-Zlint-rust-version` --- .../src/compiler-flags/lint-rust-version.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/doc/unstable-book/src/compiler-flags/lint-rust-version.md diff --git a/src/doc/unstable-book/src/compiler-flags/lint-rust-version.md b/src/doc/unstable-book/src/compiler-flags/lint-rust-version.md new file mode 100644 index 0000000000000..ecc0e7fba6350 --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/lint-rust-version.md @@ -0,0 +1,9 @@ +# `lint-rust-version` + +The tracking issue for this feature is: [#157574](https://github.com/rust-lang/rust/issues/157574). + +------------------------ + +This feature allows you to specify a minimum Rust version for the crate, which will affect lint +emission. If following a lint suggestion would raise the MSRV above the provided value, it should +not be emitted. From 209ea4726e23ff0e190562fdd29e09cf2bdd31e6 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 16 Jun 2026 14:06:52 +0000 Subject: [PATCH 21/23] fix(rustc_codegen_ssa): Use cg_operand for Freeze check * fix(rustc_codegen_ssa): Use cg_operand for Freeze check * chore: remove unnecessary comment * test: add test for ssa rval monomorphization issue * fix: move ssa-rval-monomorphization test * tweak comment Co-authored-by: Ralf Jung --- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 2 +- .../ssa-rval-monomorphization-issue-157922.rs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/ui/generics/ssa-rval-monomorphization-issue-157922.rs diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index d792e3d126f5d..deb8ca12b059d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -43,7 +43,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // If this is storing a &Freeze reference with a retag, record that it's not // possible to perform writes through the stored pointer. let flags = if let ty::Ref(_, pointee_ty, Mutability::Not) = - operand.ty(self.mir, self.cx.tcx()).kind() + cg_operand.layout.ty.kind() && with_retag.yes() && pointee_ty.is_freeze(self.cx.tcx(), self.cx.typing_env()) { diff --git a/tests/ui/generics/ssa-rval-monomorphization-issue-157922.rs b/tests/ui/generics/ssa-rval-monomorphization-issue-157922.rs new file mode 100644 index 0000000000000..f7ebc58e5400a --- /dev/null +++ b/tests/ui/generics/ssa-rval-monomorphization-issue-157922.rs @@ -0,0 +1,25 @@ +//@ build-pass +// (codegen test) +// +// Ensure that the "freeze" check on stores works correctly in generic functions. +// Regression test for [#157922](https://github.com/rust-lang/rust/issues/157922). + +pub trait Field { + type Value; +} + +pub struct S; + +impl Field for S

{ + type Value = (); +} + +pub struct Foo(F::Value); + +fn f(a: &Foo>) { + let _f = if 1 > 0 { a } else { a }; +} + +pub fn main() { + f(&Foo::>(())); +} From cd2d10a5da7a7fe292c2bd9f263e953384463845 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 16 Jun 2026 16:16:55 +0200 Subject: [PATCH 22/23] Rename `rustc_resolve/src/errors.rs` into `rustc_resolve/src/diagnostics.rs` --- .../rustc_resolve/src/build_reduced_graph.rs | 35 +- compiler/rustc_resolve/src/check_unused.rs | 21 +- compiler/rustc_resolve/src/diagnostics.rs | 5370 +++++----------- compiler/rustc_resolve/src/errors.rs | 5413 ++++++++++++----- compiler/rustc_resolve/src/ident.rs | 10 +- compiler/rustc_resolve/src/imports.rs | 24 +- compiler/rustc_resolve/src/late.rs | 70 +- .../rustc_resolve/src/late/diagnostics.rs | 28 +- compiler/rustc_resolve/src/lib.rs | 6 +- compiler/rustc_resolve/src/macros.rs | 39 +- 10 files changed, 5541 insertions(+), 5475 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 3e527b6aafa1f..fdd194b526937 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -31,14 +31,14 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::DefCollector; -use crate::diagnostics::StructCtor; +use crate::errors::StructCtor; use crate::imports::{ImportData, ImportKind, OnUnknownData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; use crate::{ BindingKey, Decl, DeclData, DeclKind, DelayedVisResolutionError, ExternModule, ExternPreludeEntry, Finalize, IdentKey, LocalModule, Module, ModuleKind, ModuleOrUniformRoot, - ParentScope, PathResult, Res, Resolver, Segment, Used, VisResolutionError, errors, + ParentScope, PathResult, Res, Resolver, Segment, Used, VisResolutionError, diagnostics, }; impl<'ra, 'tcx> Resolver<'ra, 'tcx> { @@ -706,9 +706,9 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { // Deny importing path-kw without renaming if rename.is_none() && ident.is_path_segment_keyword() { let ident = use_tree.ident(); - self.r.dcx().emit_err(errors::UnnamedImport { + self.r.dcx().emit_err(diagnostics::UnnamedImport { span: ident.span, - sugg: errors::UnnamedImportSugg { span: ident.span, ident }, + sugg: diagnostics::UnnamedImportSugg { span: ident.span, ident }, }); return; } @@ -996,7 +996,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let expansion = parent_scope.expansion; let (used, module, decl) = if orig_name.is_none() && orig_ident.name == kw::SelfLower { - self.r.dcx().emit_err(errors::ExternCrateSelfRequiresRenaming { span: sp }); + self.r.dcx().emit_err(diagnostics::ExternCrateSelfRequiresRenaming { span: sp }); return; } else if orig_name == Some(kw::SelfLower) { Some(self.r.graph_root.to_module()) @@ -1054,7 +1054,9 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { && entry.item_decl.is_none() { self.r.dcx().emit_err( - errors::MacroExpandedExternCrateCannotShadowExternArguments { span: item.span }, + diagnostics::MacroExpandedExternCrateCannotShadowExternArguments { + span: item.span, + }, ); } @@ -1125,7 +1127,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { allow_shadowing: bool, ) { if self.r.macro_use_prelude.insert(name, decl).is_some() && !allow_shadowing { - self.r.dcx().emit_err(errors::MacroUseNameAlreadyInUse { span, name }); + self.r.dcx().emit_err(diagnostics::MacroUseNameAlreadyInUse { span, name }); } } @@ -1137,14 +1139,14 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { AttributeParser::parse_limited(self.r.tcx.sess, &item.attrs, &[sym::macro_use]) { if self.parent_scope.module.expect_local().parent.is_some() { - self.r - .dcx() - .emit_err(errors::ExternCrateLoadingMacroNotAtCrateRoot { span: item.span }); + self.r.dcx().emit_err(diagnostics::ExternCrateLoadingMacroNotAtCrateRoot { + span: item.span, + }); } if let ItemKind::ExternCrate(Some(orig_name), _) = item.kind && orig_name == kw::SelfLower { - self.r.dcx().emit_err(errors::MacroUseExternCrateSelf { span }); + self.r.dcx().emit_err(diagnostics::MacroUseExternCrateSelf { span }); } match arguments { @@ -1208,7 +1210,7 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { let import_decl = self.r.new_import_decl(binding, import); self.add_macro_use_decl(ident.name, import_decl, ident.span, allow_shadowing); } else { - self.r.dcx().emit_err(errors::ImportedMacroNotFound { span: ident.span }); + self.r.dcx().emit_err(diagnostics::ImportedMacroNotFound { span: ident.span }); } } } @@ -1220,15 +1222,16 @@ impl<'a, 'ra, 'tcx> DefCollector<'a, 'ra, 'tcx> { for attr in attrs { if attr.has_name(sym::macro_escape) { let inner_attribute = matches!(attr.style, ast::AttrStyle::Inner); - self.r - .dcx() - .emit_warn(errors::MacroExternDeprecated { span: attr.span, inner_attribute }); + self.r.dcx().emit_warn(diagnostics::MacroExternDeprecated { + span: attr.span, + inner_attribute, + }); } else if !attr.has_name(sym::macro_use) { continue; } if !attr.is_word() { - self.r.dcx().emit_err(errors::ArgumentsMacroUseNotAllowed { span: attr.span }); + self.r.dcx().emit_err(diagnostics::ArgumentsMacroUseNotAllowed { span: attr.span }); } return true; } diff --git a/compiler/rustc_resolve/src/check_unused.rs b/compiler/rustc_resolve/src/check_unused.rs index 3fd9adc10c882..32e4bb9262fa8 100644 --- a/compiler/rustc_resolve/src/check_unused.rs +++ b/compiler/rustc_resolve/src/check_unused.rs @@ -38,7 +38,7 @@ use rustc_session::lint::builtin::{ use rustc_span::{DUMMY_SP, Ident, Span, kw}; use crate::imports::{Import, ImportKind}; -use crate::{DeclKind, IdentKey, LateDecl, Resolver, errors, module_to_string}; +use crate::{DeclKind, IdentKey, LateDecl, Resolver, diagnostics, module_to_string}; struct UnusedImport { use_tree: ast::UseTree, @@ -170,7 +170,7 @@ impl<'a, 'ra, 'tcx> UnusedImportCheckVisitor<'a, 'ra, 'tcx> { UNUSED_EXTERN_CRATES, extern_crate.id, span, - crate::errors::UnusedExternCrate { + crate::diagnostics::UnusedExternCrate { span: extern_crate.span, removal_span: extern_crate.span_with_attributes, }, @@ -234,7 +234,7 @@ impl<'a, 'ra, 'tcx> UnusedImportCheckVisitor<'a, 'ra, 'tcx> { UNUSED_EXTERN_CRATES, extern_crate.id, extern_crate.span, - crate::errors::ExternCrateNotIdiomatic { + crate::diagnostics::ExternCrateNotIdiomatic { span: vis_span.between(ident_span), code: if vis_span.is_empty() { "use " } else { " use " }, }, @@ -426,7 +426,7 @@ impl Resolver<'_, '_> { MACRO_USE_EXTERN_CRATE, import.root_id, import.span, - crate::errors::MacroUseDeprecated, + crate::diagnostics::MacroUseDeprecated, ); } } @@ -450,7 +450,7 @@ impl Resolver<'_, '_> { UNUSED_IMPORTS, import.root_id, import.span, - crate::errors::UnusedMacroUse, + crate::diagnostics::UnusedMacroUse, ); } _ => {} @@ -524,9 +524,12 @@ impl Resolver<'_, '_> { move |dcx, level, sess| { let sugg = can_suggest_removal.then(|| { if remove_whole_use { - errors::UnusedImportsSugg::RemoveWholeUse { span: remove_spans[0] } + diagnostics::UnusedImportsSugg::RemoveWholeUse { span: remove_spans[0] } } else { - errors::UnusedImportsSugg::RemoveImports { remove_spans, num_to_remove } + diagnostics::UnusedImportsSugg::RemoveImports { + remove_spans, + num_to_remove, + } } }); let test_module_span = test_module_span.map(|span| { @@ -536,7 +539,7 @@ impl Resolver<'_, '_> { .guess_head_span(span) }); - errors::UnusedImports { + diagnostics::UnusedImports { sugg, test_module_span, num_snippets: span_snippets.len(), @@ -593,7 +596,7 @@ impl Resolver<'_, '_> { UNUSED_QUALIFICATIONS, unn_qua.node_id, unn_qua.path_span, - errors::UnusedQualifications { removal_span: unn_qua.removal_span }, + diagnostics::UnusedQualifications { removal_span: unn_qua.removal_span }, ); } diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index aef8d7ed7c54e..2baf423e296d6 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1,3892 +1,1778 @@ -// ignore-tidy-filelength -use std::mem; -use std::ops::ControlFlow; - -use itertools::Itertools as _; -use rustc_ast::visit::{self, Visitor}; -use rustc_ast::{ - self as ast, CRATE_NODE_ID, Crate, DUMMY_NODE_ID, ItemKind, ModKind, NodeId, Path, - join_path_idents, -}; -use rustc_ast_pretty::pprust; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; +use rustc_errors::formatting::DiagMessageAddArg; use rustc_errors::{ - Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, - struct_span_code_err, -}; -use rustc_feature::BUILTIN_ATTRIBUTES; -use rustc_hir::attrs::{CfgEntry, StrippedCfgItem}; -use rustc_hir::def::Namespace::{self, *}; -use rustc_hir::def::{CtorKind, CtorOf, DefKind, MacroKinds, NonMacroAttrKind, PerNS}; -use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; -use rustc_hir::{PrimTy, Stability, StabilityLevel, find_attr}; -use rustc_middle::bug; -use rustc_middle::ty::{TyCtxt, Visibility}; -use rustc_session::Session; -use rustc_session::lint::builtin::{ - ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, - AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, + Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, ElidedLifetimeInPathSubdiag, + EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, msg, }; -use rustc_session::utils::was_invoked_from_cargo; -use rustc_span::edit_distance::find_best_match_for_name; -use rustc_span::edition::Edition; -use rustc_span::hygiene::MacroKind; -use rustc_span::source_map::SourceMap; -use rustc_span::{ - BytePos, Ident, RemapPathScopeComponents, Span, Spanned, Symbol, SyntaxContext, kw, sym, -}; -use thin_vec::{ThinVec, thin_vec}; -use tracing::{debug, instrument}; +use rustc_macros::{Diagnostic, Subdiagnostic}; +use rustc_span::{Ident, Span, Spanned, Symbol}; + +use crate::Res; +use crate::late::PatternSource; + +#[derive(Diagnostic)] +#[diag("can't use {$is_self -> + [true] `Self` + *[false] generic parameters + } from outer item", code = E0401)] +#[note( + "nested items are independent from their parent item for everything except for privacy and name resolution" +)] +pub(crate) struct GenericParamsFromOuterItem { + #[primary_span] + #[label( + "use of {$is_self -> + [true] `Self` + *[false] generic parameter + } from outer item" + )] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) label: Option, + #[subdiagnostic] + pub(crate) refer_to_type_directly: Option, + #[subdiagnostic] + pub(crate) use_let: Option, + #[subdiagnostic] + pub(crate) sugg: Option, + #[subdiagnostic] + pub(crate) static_or_const: Option, + pub(crate) is_self: bool, + #[subdiagnostic] + pub(crate) item: Option, +} -use crate::errors::{ - self, AddedMacroUse, ChangeImportBinding, ChangeImportBindingSuggestion, ConsiderAddingADerive, - ExplicitUnsafeTraits, MacroDefinedLater, MacroRulesNot, MacroSuggMovePosition, - MaybeMissingMacroRulesName, -}; -use crate::hygiene::Macros20NormalizedSyntaxContext; -use crate::imports::{Import, ImportKind}; -use crate::late::{DiagMetadata, PatternSource, Rib}; -use crate::{ - AmbiguityError, AmbiguityKind, AmbiguityWarning, BindingError, BindingKey, Decl, DeclKind, - DelayedVisResolutionError, Finalize, ForwardGenericParamBanReason, HasGenericParams, IdentKey, - LateDecl, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot, ParentScope, PathResult, - PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet, Segment, UseError, Used, - VisResolutionError, errors as errs, path_names_to_string, -}; +#[derive(Subdiagnostic)] +#[label( + "{$is_self -> + [true] `Self` + *[false] generic parameter + } used in this inner {$descr}" +)] +pub(crate) struct GenericParamsFromOuterItemInnerItem { + #[primary_span] + pub(crate) span: Span, + pub(crate) descr: String, + pub(crate) is_self: bool, +} + +#[derive(Subdiagnostic)] +pub(crate) enum GenericParamsFromOuterItemStaticOrConst { + #[note("a `static` is a separate item from the item that contains it")] + Static, + #[note("a `const` is a separate item from the item that contains it")] + Const, +} -/// A vector of spans and replacements, a message and applicability. -pub(crate) type Suggestion = (Vec<(Span, String)>, String, Applicability); +#[derive(Subdiagnostic)] +pub(crate) enum GenericParamsFromOuterItemLabel { + #[label("can't use `Self` here")] + SelfTyParam(#[primary_span] Span), + #[label("`Self` type implicitly declared here, by this `impl`")] + SelfTyAlias(#[primary_span] Span), + #[label("type parameter from outer item")] + TyParam(#[primary_span] Span), + #[label("const parameter from outer item")] + ConstParam(#[primary_span] Span), +} -/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a -/// similarly named label and whether or not it is reachable. -pub(crate) type LabelSuggestion = (Ident, bool); +#[derive(Subdiagnostic)] +#[suggestion( + "try introducing a local generic parameter here", + code = "{snippet}", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct GenericParamsFromOuterItemSugg { + #[primary_span] + pub(crate) span: Span, + pub(crate) snippet: String, +} -#[derive(Clone)] -pub(crate) struct StructCtor { - pub res: Res, - pub vis: Visibility, - pub field_visibilities: Vec>, +#[derive(Subdiagnostic)] +#[suggestion( + "try using a local `let` binding instead", + code = "let", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct GenericParamsFromOuterItemUseLet { + #[primary_span] + pub(crate) span: Span, } -impl StructCtor { - pub(crate) fn has_private_fields<'ra>(&self, m: Module<'ra>, r: &Resolver<'ra, '_>) -> bool { - self.field_visibilities.iter().any(|&vis| !r.is_accessible_from(vis, m)) - } +#[derive(Subdiagnostic)] +#[suggestion( + "refer to the type directly here instead", + code = "{snippet}", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct UseTypeDirectly { + #[primary_span] + pub(crate) span: Span, + pub(crate) snippet: String, } -#[derive(Debug)] -pub(crate) enum SuggestionTarget { - /// The target has a similar name as the name used by the programmer (probably a typo) - SimilarlyNamed, - /// The target is the only valid item that can be used in the corresponding context - SingleItem, +#[derive(Diagnostic)] +#[diag("the name `{$name}` is already used for a generic parameter in this item's generic parameters", code = E0403)] +pub(crate) struct NameAlreadyUsedInParameterList { + #[primary_span] + #[label("already used")] + pub(crate) span: Span, + #[label("first use of `{$name}`")] + pub(crate) first_use_span: Span, + pub(crate) name: Ident, +} + +#[derive(Diagnostic)] +#[diag("method `{$method}` is not a member of trait `{$trait_}`", code = E0407)] +pub(crate) struct MethodNotMemberOfTrait { + #[primary_span] + #[label("not a member of trait `{$trait_}`")] + pub(crate) span: Span, + pub(crate) method: Ident, + pub(crate) trait_: String, + #[subdiagnostic] + pub(crate) sub: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "there is an associated function with a similar name", + code = "{candidate}", + applicability = "maybe-incorrect" +)] +pub(crate) struct AssociatedFnWithSimilarNameExists { + #[primary_span] + pub(crate) span: Span, + pub(crate) candidate: Symbol, +} + +#[derive(Diagnostic)] +#[diag("type `{$type_}` is not a member of trait `{$trait_}`", code = E0437)] +pub(crate) struct TypeNotMemberOfTrait { + #[primary_span] + #[label("not a member of trait `{$trait_}`")] + pub(crate) span: Span, + pub(crate) type_: Ident, + pub(crate) trait_: String, + #[subdiagnostic] + pub(crate) sub: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "there is an associated type with a similar name", + code = "{candidate}", + applicability = "maybe-incorrect" +)] +pub(crate) struct AssociatedTypeWithSimilarNameExists { + #[primary_span] + pub(crate) span: Span, + pub(crate) candidate: Symbol, +} + +#[derive(Diagnostic)] +#[diag("const `{$const_}` is not a member of trait `{$trait_}`", code = E0438)] +pub(crate) struct ConstNotMemberOfTrait { + #[primary_span] + #[label("not a member of trait `{$trait_}`")] + pub(crate) span: Span, + pub(crate) const_: Ident, + pub(crate) trait_: String, + #[subdiagnostic] + pub(crate) sub: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "there is an associated constant with a similar name", + code = "{candidate}", + applicability = "maybe-incorrect" +)] +pub(crate) struct AssociatedConstWithSimilarNameExists { + #[primary_span] + pub(crate) span: Span, + pub(crate) candidate: Symbol, +} + +#[derive(Diagnostic)] +#[diag("variable `{$variable_name}` is bound inconsistently across alternatives separated by `|`", code = E0409)] +pub(crate) struct VariableBoundWithDifferentMode { + #[primary_span] + #[label("bound in different ways")] + pub(crate) span: Span, + #[label("first binding")] + pub(crate) first_binding_span: Span, + pub(crate) variable_name: Ident, +} + +#[derive(Diagnostic)] +#[diag("identifier `{$identifier}` is bound more than once in this parameter list", code = E0415)] +pub(crate) struct IdentifierBoundMoreThanOnceInParameterList { + #[primary_span] + #[label("used as parameter more than once")] + pub(crate) span: Span, + pub(crate) identifier: Ident, +} + +#[derive(Diagnostic)] +#[diag("identifier `{$identifier}` is bound more than once in the same pattern", code = E0416)] +pub(crate) struct IdentifierBoundMoreThanOnceInSamePattern { + #[primary_span] + #[label("used in a pattern more than once")] + pub(crate) span: Span, + pub(crate) identifier: Ident, +} + +#[derive(Diagnostic)] +#[diag("use of undeclared label `{$name}`", code = E0426)] +pub(crate) struct UndeclaredLabel { + #[primary_span] + #[label("undeclared label `{$name}`")] + pub(crate) span: Span, + pub(crate) name: Symbol, + #[subdiagnostic] + pub(crate) sub_reachable: Option, + #[subdiagnostic] + pub(crate) sub_reachable_suggestion: Option, + #[subdiagnostic] + pub(crate) sub_unreachable: Option, +} + +#[derive(Subdiagnostic)] +#[label("a label with a similar name is reachable")] +pub(crate) struct LabelWithSimilarNameReachable(#[primary_span] pub(crate) Span); + +#[derive(Subdiagnostic)] +#[suggestion( + "try using similarly named label", + code = "{ident_name}", + applicability = "maybe-incorrect" +)] +pub(crate) struct TryUsingSimilarlyNamedLabel { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident_name: Symbol, +} + +#[derive(Subdiagnostic)] +#[label("a label with a similar name exists but is unreachable")] +pub(crate) struct UnreachableLabelWithSimilarNameExists { + #[primary_span] + pub(crate) ident_span: Span, +} + +#[derive(Diagnostic)] +#[diag("can't capture dynamic environment in a fn item", code = E0434)] +#[help("use the `|| {\"{\"} ... {\"}\"}` closure form instead")] +pub(crate) struct CannotCaptureDynamicEnvironmentInFnItem { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag("attempt to use a non-constant value in a constant", code = E0435)] +pub(crate) struct AttemptToUseNonConstantValueInConstant<'a> { + #[primary_span] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) with: Option>, + #[subdiagnostic] + pub(crate) with_label: Option, + #[subdiagnostic] + pub(crate) without: Option>, +} + +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "consider using `{$suggestion}` instead of `{$current}`", + style = "verbose", + applicability = "has-placeholders" +)] +pub(crate) struct AttemptToUseNonConstantValueInConstantWithSuggestion<'a> { + // #[primary_span] + #[suggestion_part(code = "{suggestion} ")] + pub(crate) span: Span, + pub(crate) suggestion: &'a str, + #[suggestion_part(code = ": /* Type */")] + pub(crate) type_span: Option, + pub(crate) current: &'a str, +} + +#[derive(Subdiagnostic)] +#[label("non-constant value")] +pub(crate) struct AttemptToUseNonConstantValueInConstantLabelWithSuggestion { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Subdiagnostic)] +#[label("this would need to be a `{$suggestion}`")] +pub(crate) struct AttemptToUseNonConstantValueInConstantWithoutSuggestion<'a> { + #[primary_span] + pub(crate) ident_span: Span, + pub(crate) suggestion: &'a str, +} + +#[derive(Diagnostic)] +#[diag("{$shadowing_binding}s cannot shadow {$shadowed_binding}s", code = E0530)] +pub(crate) struct BindingShadowsSomethingUnacceptable<'a> { + #[primary_span] + #[label("cannot be named the same as {$article} {$shadowed_binding}")] + pub(crate) span: Span, + pub(crate) shadowing_binding: PatternSource, + pub(crate) shadowed_binding: Res, + pub(crate) article: &'a str, + #[subdiagnostic] + pub(crate) sub_suggestion: Option, + #[label("the {$shadowed_binding} `{$name}` is {$participle} here")] + pub(crate) shadowed_binding_span: Span, + pub(crate) participle: &'a str, + pub(crate) name: Symbol, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "try specify the pattern arguments", + code = "{name}(..)", + applicability = "unspecified" +)] +pub(crate) struct BindingShadowsSomethingUnacceptableSuggestion { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag("generic parameter defaults cannot reference parameters before they are declared", code = E0128)] +pub(crate) struct ForwardDeclaredGenericParam { + #[primary_span] + #[label("cannot reference `{$param}` before it is declared")] + pub(crate) span: Span, + pub(crate) param: Symbol, +} + +#[derive(Diagnostic)] +#[diag("const parameter types cannot reference parameters before they are declared")] +pub(crate) struct ForwardDeclaredGenericInConstParamTy { + #[primary_span] + #[label("const parameter type cannot reference `{$param}` before it is declared")] + pub(crate) span: Span, + pub(crate) param: Symbol, +} + +#[derive(Diagnostic)] +#[diag("the type of const parameters must not depend on other generic parameters", code = E0770)] +pub(crate) struct ParamInTyOfConstParam { + #[primary_span] + #[label("the type must not depend on the parameter `{$name}`")] + pub(crate) span: Span, + pub(crate) name: Symbol, +} + +#[derive(Diagnostic)] +#[diag("generic parameters cannot use `Self` in their defaults", code = E0735)] +pub(crate) struct SelfInGenericParamDefault { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag("cannot use `Self` in const parameter type")] +pub(crate) struct SelfInConstGenericTy { + #[primary_span] + pub(crate) span: Span, +} + +#[derive(Diagnostic)] +#[diag( + "{$is_gca -> + [true] generic parameters in const blocks are not allowed; use a named `const` item instead + *[false] generic parameters may not be used in const operations +}" +)] +pub(crate) struct ParamInNonTrivialAnonConst { + #[primary_span] + #[label("cannot perform const operation using `{$name}`")] + pub(crate) span: Span, + pub(crate) name: Symbol, + #[subdiagnostic] + pub(crate) param_kind: ParamKindInNonTrivialAnonConst, + #[help("add `#![feature(generic_const_exprs)]` to allow generic const expressions")] + pub(crate) help: bool, + pub(crate) is_gca: bool, + #[help( + "consider factoring the expression into a `type const` item and use it as the const argument instead" + )] + pub(crate) help_gca: bool, } #[derive(Debug)] -pub(crate) struct TypoSuggestion { - pub candidate: Symbol, - /// The source location where the name is defined; None if the name is not defined - /// in source e.g. primitives - pub span: Option, - pub res: Res, - pub target: SuggestionTarget, -} - -impl TypoSuggestion { - pub(crate) fn new(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { - Self { candidate, span: Some(span), res, target: SuggestionTarget::SimilarlyNamed } - } - pub(crate) fn typo_from_name(candidate: Symbol, res: Res) -> TypoSuggestion { - Self { candidate, span: None, res, target: SuggestionTarget::SimilarlyNamed } - } - pub(crate) fn single_item(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { - Self { candidate, span: Some(span), res, target: SuggestionTarget::SingleItem } - } +#[derive(Subdiagnostic)] +pub(crate) enum ParamKindInNonTrivialAnonConst { + #[note("type parameters may not be used in const expressions")] + Type, + #[help("const parameters may only be used as standalone arguments here, i.e. `{$name}`")] + Const { name: Symbol }, + #[note("lifetime parameters may not be used in const expressions")] + Lifetime, } -/// A free importable items suggested in case of resolution failure. -#[derive(Debug, Clone)] -pub(crate) struct ImportSuggestion { - pub did: Option, - pub descr: &'static str, - pub path: Path, - pub accessible: bool, - // false if the path traverses a foreign `#[doc(hidden)]` item. - pub doc_visible: bool, - pub via_import: bool, - /// An extra note that should be issued if this item is suggested - pub note: Option, - pub is_stable: bool, -} - -/// Adjust the impl span so that just the `impl` keyword is taken by removing -/// everything after `<` (`"impl Iterator for A {}" -> "impl"`) and -/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`). -/// -/// *Attention*: the method used is very fragile since it essentially duplicates the work of the -/// parser. If you need to use this function or something similar, please consider updating the -/// `source_map` functions and this function to something more robust. -fn reduce_impl_span_to_impl_keyword(sm: &SourceMap, impl_span: Span) -> Span { - let impl_span = sm.span_until_char(impl_span, '<'); - sm.span_until_whitespace(impl_span) -} - -impl<'ra, 'tcx> Resolver<'ra, 'tcx> { - pub(crate) fn dcx(&self) -> DiagCtxtHandle<'tcx> { - self.tcx.dcx() - } +#[derive(Diagnostic)] +#[diag("use of unreachable label `{$name}`", code = E0767)] +#[note("labels are unreachable through functions, closures, async blocks and modules")] +pub(crate) struct UnreachableLabel { + #[primary_span] + #[label("unreachable label `{$name}`")] + pub(crate) span: Span, + pub(crate) name: Symbol, + #[label("unreachable label defined here")] + pub(crate) definition_span: Span, + #[subdiagnostic] + pub(crate) sub_suggestion: Option, + #[subdiagnostic] + pub(crate) sub_suggestion_label: Option, + #[subdiagnostic] + pub(crate) sub_unreachable_label: Option, +} - pub(crate) fn report_errors(&mut self, krate: &Crate) { - self.report_delayed_vis_resolution_errors(); - self.report_with_use_injections(krate); - - for &(span_use, span_def) in &self.macro_expanded_macro_export_errors { - self.lint_buffer.buffer_lint( - MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, - CRATE_NODE_ID, - span_use, - errors::MacroExpandedMacroExportsAccessedByAbsolutePaths { definition: span_def }, - ); - } +#[derive(Subdiagnostic)] +#[suggestion( + "try using similarly named label", + code = "{ident_name}", + applicability = "maybe-incorrect" +)] +pub(crate) struct UnreachableLabelSubSuggestion { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident_name: Symbol, +} - for ambiguity_error in &self.ambiguity_errors { - let mut diag = self.ambiguity_diagnostic(ambiguity_error); - - if let Some(ambiguity_warning) = ambiguity_error.warning { - let node_id = match ambiguity_error.b1.0.kind { - DeclKind::Import { import, .. } => import.root_id, - DeclKind::Def(_) => CRATE_NODE_ID, - }; - - let lint = match ambiguity_warning { - _ if ambiguity_error.ambig_vis.is_some() => AMBIGUOUS_IMPORT_VISIBILITIES, - AmbiguityWarning::GlobImport => AMBIGUOUS_GLOB_IMPORTS, - AmbiguityWarning::PanicImport => AMBIGUOUS_PANIC_IMPORTS, - }; - - self.lint_buffer.buffer_lint(lint, node_id, diag.ident.span, diag); - } else { - diag.is_error = true; - self.dcx().emit_err(diag); - } - } +#[derive(Subdiagnostic)] +#[label("a label with a similar name is reachable")] +pub(crate) struct UnreachableLabelSubLabel { + #[primary_span] + pub(crate) ident_span: Span, +} - let mut reported_spans = FxHashSet::default(); - for error in mem::take(&mut self.privacy_errors) { - if reported_spans.insert(error.dedup_span) { - self.report_privacy_error(&error); - } - } - } +#[derive(Subdiagnostic)] +#[label("a label with a similar name exists but is also unreachable")] +pub(crate) struct UnreachableLabelSubLabelUnreachable { + #[primary_span] + pub(crate) ident_span: Span, +} - fn report_delayed_vis_resolution_errors(&mut self) { - for DelayedVisResolutionError { vis, parent_scope, error } in - mem::take(&mut self.delayed_vis_resolution_errors) - { - match self.try_resolve_visibility(&parent_scope, &vis, true) { - Ok(_) => self.report_vis_error(error), - Err(error) => self.report_vis_error(error), - }; - } - } +#[derive(Diagnostic)] +#[diag("invalid `sym` operand")] +#[help("`sym` operands must refer to either a function or a static")] +pub(crate) struct InvalidAsmSym { + #[primary_span] + #[label("is a local variable")] + pub(crate) span: Span, +} - fn report_with_use_injections(&mut self, krate: &Crate) { - for UseError { mut err, candidates, node_id, instead, suggestion, path, is_call } in - mem::take(&mut self.use_injections) - { - let (span, found_use) = if node_id != DUMMY_NODE_ID { - UsePlacementFinder::check(krate, node_id) - } else { - (None, FoundUse::No) - }; - - if !candidates.is_empty() { - show_candidates( - self.tcx, - &mut err, - span, - &candidates, - if instead { Instead::Yes } else { Instead::No }, - found_use, - DiagMode::Normal, - path, - "", - ); - err.emit(); - } else if let Some((span, msg, sugg, appl)) = suggestion { - err.span_suggestion_verbose(span, msg, sugg, appl); - err.emit(); - } else if let [segment] = path.as_slice() - && is_call - { - err.stash(segment.ident.span, rustc_errors::StashKey::CallIntoMethod); - } else { - err.emit(); - } - } - } +#[derive(Diagnostic)] +#[diag("attempt to use a non-constant value in a constant")] +pub(crate) struct LowercaseSelf { + #[primary_span] + #[suggestion( + "try using `Self`", + code = "Self", + applicability = "maybe-incorrect", + style = "short" + )] + pub(crate) span: Span, +} - pub(crate) fn report_conflict( - &mut self, - ident: IdentKey, - ns: Namespace, - old_binding: Decl<'ra>, - new_binding: Decl<'ra>, - ) { - // Error on the second of two conflicting names - if old_binding.span.lo() > new_binding.span.lo() { - return self.report_conflict(ident, ns, new_binding, old_binding); - } +#[derive(Debug)] +#[derive(Diagnostic)] +#[diag("never patterns cannot contain variable bindings")] +pub(crate) struct BindingInNeverPattern { + #[primary_span] + #[suggestion( + "use a wildcard `_` instead", + code = "_", + applicability = "machine-applicable", + style = "short" + )] + pub(crate) span: Span, +} - let container = match old_binding.parent_module.unwrap().expect_local().kind { - // Avoid using TyCtxt::def_kind_descr in the resolver, because it - // indirectly *calls* the resolver, and would cause a query cycle. - ModuleKind::Def(kind, def_id, _, _) => kind.descr(def_id), - ModuleKind::Block => "block", - }; +#[derive(Diagnostic)] +#[diag("duplicate definitions with name `{$name}`:", code = E0201)] +pub(crate) struct TraitImplDuplicate { + #[primary_span] + #[label("duplicate definition")] + pub(crate) span: Span, + #[label("previous definition here")] + pub(crate) old_span: Span, + #[label("item in trait")] + pub(crate) trait_item_span: Span, + pub(crate) name: Ident, +} - let (name, span) = - (ident.name, self.tcx.sess.source_map().guess_head_span(new_binding.span)); +#[derive(Diagnostic)] +#[diag("relative paths are not supported in visibilities in 2018 edition or later")] +pub(crate) struct Relative2018 { + #[primary_span] + pub(crate) span: Span, + #[suggestion("try", code = "crate::{path_str}", applicability = "maybe-incorrect")] + pub(crate) path_span: Span, + pub(crate) path_str: String, +} - if self.name_already_seen.get(&name) == Some(&span) { - return; - } +#[derive(Diagnostic)] +#[diag("visibilities can only be restricted to ancestor modules", code = E0742)] +pub(crate) struct AncestorOnly(#[primary_span] pub(crate) Span); + +#[derive(Diagnostic)] +#[diag("expected module, found {$res} `{$path_str}`", code = E0577)] +pub(crate) struct ExpectedModuleFound { + #[primary_span] + #[label("not a module")] + pub(crate) span: Span, + pub(crate) res: Res, + pub(crate) path_str: String, +} - let old_kind = match (ns, old_binding.res()) { - (ValueNS, _) => "value", - (MacroNS, _) => "macro", - (TypeNS, _) if old_binding.is_extern_crate() => "extern crate", - (TypeNS, Res::Def(DefKind::Mod, _)) => "module", - (TypeNS, Res::Def(DefKind::Trait, _)) => "trait", - (TypeNS, _) => "type", - }; - - let code = match (old_binding.is_extern_crate(), new_binding.is_extern_crate()) { - (true, true) => E0259, - (true, _) | (_, true) => match new_binding.is_import() && old_binding.is_import() { - true => E0254, - false => E0260, - }, - _ => match (old_binding.is_import_user_facing(), new_binding.is_import_user_facing()) { - (false, false) => E0428, - (true, true) => E0252, - _ => E0255, - }, - }; - - let label = match new_binding.is_import_user_facing() { - true => errors::NameDefinedMultipleTimeLabel::Reimported { span, name }, - false => errors::NameDefinedMultipleTimeLabel::Redefined { span, name }, - }; - - let old_binding_label = - (!old_binding.span.is_dummy() && old_binding.span != span).then(|| { - let span = self.tcx.sess.source_map().guess_head_span(old_binding.span); - match old_binding.is_import_user_facing() { - true => errors::NameDefinedMultipleTimeOldBindingLabel::Import { - span, - old_kind, - name, - }, - false => errors::NameDefinedMultipleTimeOldBindingLabel::Definition { - span, - old_kind, - name, - }, - } - }); - - let mut err = self - .dcx() - .create_err(errors::NameDefinedMultipleTime { - span, - name, - descr: ns.descr(), - container, - label, - old_binding_label, - }) - .with_code(code); - - // See https://github.com/rust-lang/rust/issues/32354 - use DeclKind::Import; - let can_suggest = |binding: Decl<'_>, import: self::Import<'_>| { - !binding.span.is_dummy() - && !matches!(import.kind, ImportKind::MacroUse { .. } | ImportKind::MacroExport) - }; - let import = match (&new_binding.kind, &old_binding.kind) { - // If there are two imports where one or both have attributes then prefer removing the - // import without attributes. - (Import { import: new, .. }, Import { import: old, .. }) - if { - (new.has_attributes || old.has_attributes) - && can_suggest(old_binding, *old) - && can_suggest(new_binding, *new) - } => - { - if old.has_attributes { - Some((*new, new_binding.span, true)) - } else { - Some((*old, old_binding.span, true)) - } - } - // Otherwise prioritize the new binding. - (Import { import, .. }, other) if can_suggest(new_binding, *import) => { - Some((*import, new_binding.span, other.is_import())) - } - (other, Import { import, .. }) if can_suggest(old_binding, *import) => { - Some((*import, old_binding.span, other.is_import())) - } - _ => None, - }; - - // Check if the target of the use for both bindings is the same. - let duplicate = new_binding.res().opt_def_id() == old_binding.res().opt_def_id(); - let has_dummy_span = new_binding.span.is_dummy() || old_binding.span.is_dummy(); - let from_item = - self.extern_prelude.get(&ident).is_none_or(|entry| entry.introduced_by_item()); - // Only suggest removing an import if both bindings are to the same def, if both spans - // aren't dummy spans. Further, if both bindings are imports, then the ident must have - // been introduced by an item. - let should_remove_import = duplicate - && !has_dummy_span - && ((new_binding.is_extern_crate() || old_binding.is_extern_crate()) || from_item); - - match import { - Some((import, span, true)) if should_remove_import && import.is_nested() => { - self.add_suggestion_for_duplicate_nested_use(&mut err, import, span); - } - Some((import, _, true)) if should_remove_import && !import.is_glob() => { - // Simple case - remove the entire import. Due to the above match arm, this can - // only be a single use so just remove it entirely. - err.subdiagnostic(errors::ToolOnlyRemoveUnnecessaryImport { - span: import.use_span_with_attributes, - }); - } - Some((import, span, _)) => { - self.add_suggestion_for_rename_of_use(&mut err, name, import, span); - } - _ => {} - } +#[derive(Diagnostic)] +#[diag("cannot determine resolution for the visibility", code = E0578)] +pub(crate) struct Indeterminate(#[primary_span] pub(crate) Span); + +#[derive(Diagnostic)] +#[diag("trait implementation can only be restricted to ancestor modules")] +pub(crate) struct RestrictionAncestorOnly(#[primary_span] pub(crate) Span); + +#[derive(Diagnostic)] +#[diag("cannot use a tool module through an import")] +pub(crate) struct ToolModuleImported { + #[primary_span] + pub(crate) span: Span, + #[note("the tool module imported here")] + pub(crate) import: Span, +} - err.emit(); - self.name_already_seen.insert(name, span); - } +#[derive(Diagnostic)] +#[diag("visibility must resolve to a module")] +pub(crate) struct ModuleOnly(#[primary_span] pub(crate) Span); + +#[derive(Diagnostic)] +#[diag("expected {$expected}, found {$found} `{$macro_path}`")] +pub(crate) struct MacroExpectedFound<'a> { + #[primary_span] + #[label("not {$article} {$expected}")] + pub(crate) span: Span, + pub(crate) found: &'a str, + pub(crate) article: &'static str, + pub(crate) expected: &'a str, + pub(crate) macro_path: &'a str, + #[subdiagnostic] + pub(crate) remove_surrounding_derive: Option, + #[subdiagnostic] + pub(crate) add_as_non_derive: Option>, +} - /// This function adds a suggestion to change the binding name of a new import that conflicts - /// with an existing import. - /// - /// ```text,ignore (diagnostic) - /// help: you can use `as` to change the binding name of the import - /// | - /// LL | use foo::bar as other_bar; - /// | ^^^^^^^^^^^^^^^^^^^^^ - /// ``` - fn add_suggestion_for_rename_of_use( - &self, - err: &mut Diag<'_>, - name: Symbol, - import: Import<'_>, - binding_span: Span, - ) { - let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() { - format!("Other{name}") - } else { - format!("other_{name}") - }; - - let mut suggestion = None; - let mut span = binding_span; - match import.kind { - ImportKind::Single { source, .. } => { - if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0) - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(binding_span) - && pos as usize <= snippet.len() - { - span = binding_span.with_lo(binding_span.lo() + BytePos(pos)).with_hi( - binding_span.hi() - BytePos(if snippet.ends_with(';') { 1 } else { 0 }), - ); - suggestion = Some(format!(" as {suggested_name}")); - } - } - ImportKind::ExternCrate { source, target, .. } => { - suggestion = Some(format!( - "extern crate {} as {};", - source.unwrap_or(target.name), - suggested_name, - )) - } - _ => unreachable!(), - } +#[derive(Subdiagnostic)] +#[help("remove from the surrounding `derive()`")] +pub(crate) struct RemoveSurroundingDerive { + #[primary_span] + pub(crate) span: Span, +} - if let Some(suggestion) = suggestion { - err.subdiagnostic(ChangeImportBindingSuggestion { span, suggestion }); - } else { - err.subdiagnostic(ChangeImportBinding { span }); - } - } +#[derive(Subdiagnostic)] +#[help( + " + add as non-Derive macro + `#[{$macro_path}]`" +)] +pub(crate) struct AddAsNonDerive<'a> { + pub(crate) macro_path: &'a str, +} - /// This function adds a suggestion to remove an unnecessary binding from an import that is - /// nested. In the following example, this function will be invoked to remove the `a` binding - /// in the second use statement: - /// - /// ```ignore (diagnostic) - /// use issue_52891::a; - /// use issue_52891::{d, a, e}; - /// ``` - /// - /// The following suggestion will be added: - /// - /// ```ignore (diagnostic) - /// use issue_52891::{d, a, e}; - /// ^-- help: remove unnecessary import - /// ``` - /// - /// If the nested use contains only one import then the suggestion will remove the entire - /// line. - /// - /// It is expected that the provided import is nested - this isn't checked by the - /// function. If this invariant is not upheld, this function's behaviour will be unexpected - /// as characters expected by span manipulations won't be present. - fn add_suggestion_for_duplicate_nested_use( - &self, - err: &mut Diag<'_>, - import: Import<'_>, - binding_span: Span, - ) { - assert!(import.is_nested()); - - // Two examples will be used to illustrate the span manipulations we're doing: - // - // - Given `use issue_52891::{d, a, e};` where `a` is a duplicate then `binding_span` is - // `a` and `import.use_span` is `issue_52891::{d, a, e};`. - // - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is - // `a` and `import.use_span` is `issue_52891::{d, e, a};`. - - let (found_closing_brace, span) = - find_span_of_binding_until_next_binding(self.tcx.sess, binding_span, import.use_span); - - // If there was a closing brace then identify the span to remove any trailing commas from - // previous imports. - if found_closing_brace { - if let Some(span) = extend_span_to_previous_binding(self.tcx.sess, span) { - err.subdiagnostic(errors::ToolOnlyRemoveUnnecessaryImport { span }); - } else { - // Remove the entire line if we cannot extend the span back, this indicates an - // `issue_52891::{self}` case. - err.subdiagnostic(errors::RemoveUnnecessaryImport { - span: import.use_span_with_attributes, - }); - } +#[derive(Diagnostic)] +#[diag("can't use a procedural macro from the same crate that defines it")] +pub(crate) struct ProcMacroSameCrate { + #[primary_span] + pub(crate) span: Span, + #[help("you can define integration tests in a directory named `tests`")] + pub(crate) is_test: bool, +} - return; - } +#[derive(Diagnostic)] +#[diag("cannot find {$ns_descr} `{$ident}` in this scope")] +pub(crate) struct ProcMacroDeriveResolutionFallback { + #[label("names from parent modules are not accessible without an explicit import")] + pub span: Span, + pub ns_descr: &'static str, + pub ident: Symbol, +} - err.subdiagnostic(errors::RemoveUnnecessaryImport { span }); - } +#[derive(Diagnostic)] +#[diag( + "macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths" +)] +pub(crate) struct MacroExpandedMacroExportsAccessedByAbsolutePaths { + #[note("the macro is defined here")] + pub definition: Span, +} - pub(crate) fn lint_if_path_starts_with_module( - &mut self, - finalize: Finalize, - path: &[Segment], - second_binding: Option>, - ) { - let Finalize { node_id, root_span, .. } = finalize; - - let first_name = match path.get(0) { - // In the 2018 edition this lint is a hard error, so nothing to do - Some(seg) if seg.ident.span.is_rust_2015() && self.tcx.sess.is_rust_2015() => { - seg.ident.name - } - _ => return, - }; +#[derive(Diagnostic)] +#[diag("`#[macro_use]` is not supported on `extern crate self`")] +pub(crate) struct MacroUseExternCrateSelf { + #[primary_span] + pub(crate) span: Span, +} - // We're only interested in `use` paths which should start with - // `{{root}}` currently. - if first_name != kw::PathRoot { - return; - } +#[derive(Diagnostic)] +#[diag("not sure whether the path is accessible or not")] +#[note("the type may have associated items, but we are currently not checking them")] +pub(crate) struct CfgAccessibleUnsure { + #[primary_span] + pub(crate) span: Span, +} - match path.get(1) { - // If this import looks like `crate::...` it's already good - Some(Segment { ident, .. }) if ident.name == kw::Crate => return, - // Otherwise go below to see if it's an extern crate - Some(_) => {} - // If the path has length one (and it's `PathRoot` most likely) - // then we don't know whether we're gonna be importing a crate or an - // item in our crate. Defer this lint to elsewhere - None => return, - } +#[derive(Debug)] +#[derive(Diagnostic)] +#[diag("generic parameters may not be used in enum discriminant values")] +pub(crate) struct ParamInEnumDiscriminant { + #[primary_span] + #[label("cannot perform const operation using `{$name}`")] + pub(crate) span: Span, + pub(crate) name: Symbol, + #[subdiagnostic] + pub(crate) param_kind: ParamKindInEnumDiscriminant, +} - // If the first element of our path was actually resolved to an - // `ExternCrate` (also used for `crate::...`) then no need to issue a - // warning, this looks all good! - if let Some(binding) = second_binding - && let DeclKind::Import { import, .. } = binding.kind - // Careful: we still want to rewrite paths from renamed extern crates. - && let ImportKind::ExternCrate { source: None, .. } = import.kind - { - return; - } +#[derive(Debug)] +#[derive(Subdiagnostic)] +pub(crate) enum ParamKindInEnumDiscriminant { + #[note("type parameters may not be used in enum discriminant values")] + Type, + #[note("const parameters may not be used in enum discriminant values")] + Const, + #[note("lifetime parameters may not be used in enum discriminant values")] + Lifetime, +} - self.lint_buffer.dyn_buffer_lint_any( - ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, - node_id, - root_span, - move |dcx, level, sess| { - let (replacement, applicability) = match sess - .downcast_ref::() - .expect("expected a `Session`") - .source_map() - .span_to_snippet(root_span) - { - Ok(ref s) => { - // FIXME(Manishearth) ideally the emitting code - // can tell us whether or not this is global - let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; - - (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) - } - Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), - }; - errors::AbsPathWithModule { - sugg: errors::AbsPathWithModuleSugg { - span: root_span, - applicability, - replacement, - }, - } - .into_diag(dcx, level) - }, - ); - } +#[derive(Subdiagnostic)] +#[label("you can use `as` to change the binding name of the import")] +pub(crate) struct ChangeImportBinding { + #[primary_span] + pub(crate) span: Span, +} - pub(crate) fn add_module_candidates( - &self, - module: Module<'ra>, - names: &mut Vec, - filter_fn: &impl Fn(Res) -> bool, - ctxt: Option, - ) { - module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { - let res = binding.res(); - if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { - names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); - } - }); - } +#[derive(Subdiagnostic)] +#[suggestion( + "you can use `as` to change the binding name of the import", + code = "{suggestion}", + applicability = "maybe-incorrect" +)] +pub(crate) struct ChangeImportBindingSuggestion { + #[primary_span] + pub(crate) span: Span, + pub(crate) suggestion: String, +} - /// Combines an error with provided span and emits it. - /// - /// This takes the error provided, combines it with the span and any additional spans inside the - /// error and emits it. - pub(crate) fn report_error( - &mut self, - span: Span, - resolution_error: ResolutionError<'ra>, - ) -> ErrorGuaranteed { - self.into_struct_error(span, resolution_error).emit() - } +#[derive(Diagnostic)] +#[diag("imports cannot refer to {$what}")] +pub(crate) struct ImportsCannotReferTo<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) what: &'a str, +} + +#[derive(Diagnostic)] +#[diag("cannot find {$expected} `{$ident}` in this scope")] +pub(crate) struct CannotFindIdentInThisScope<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) expected: &'a str, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +#[note("unsafe traits like `{$ident}` should be implemented explicitly")] +pub(crate) struct ExplicitUnsafeTraits { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +#[note("a macro with the same name exists, but it appears later")] +pub(crate) struct MacroDefinedLater { + #[primary_span] + pub(crate) span: Span, +} - pub(crate) fn into_struct_error( - &mut self, +#[derive(Subdiagnostic)] +#[label("consider moving the definition of `{$ident}` before this call")] +pub(crate) struct MacroSuggMovePosition { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} + +#[derive(Subdiagnostic)] +pub(crate) enum MacroRulesNot { + #[label("`{$ident}` exists, but has no rules for function-like invocation")] + Func { + #[primary_span] span: Span, - resolution_error: ResolutionError<'ra>, - ) -> Diag<'_> { - match resolution_error { - ResolutionError::GenericParamsFromOuterItem { - outer_res, - has_generic_params, - def_kind, - inner_item, - current_self_ty, - } => { - use errs::GenericParamsFromOuterItemLabel as Label; - let static_or_const = match def_kind { - DefKind::Static { .. } => { - Some(errs::GenericParamsFromOuterItemStaticOrConst::Static) - } - DefKind::Const { .. } => { - Some(errs::GenericParamsFromOuterItemStaticOrConst::Const) - } - _ => None, - }; - let is_self = - matches!(outer_res, Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }); - let mut err = errs::GenericParamsFromOuterItem { - span, - label: None, - refer_to_type_directly: None, - use_let: None, - sugg: None, - static_or_const, - is_self, - item: inner_item.as_ref().map(|(label_span, _, kind)| { - errs::GenericParamsFromOuterItemInnerItem { - span: *label_span, - descr: kind.descr().to_string(), - is_self, - } - }), - }; - - let sm = self.tcx.sess.source_map(); - // Note: do not early return for missing def_id here, - // we still want to provide suggestions for `Res::SelfTyParam` and `Res::SelfTyAlias`. - let def_id = match outer_res { - Res::SelfTyParam { .. } => { - err.label = Some(Label::SelfTyParam(span)); - None - } - Res::SelfTyAlias { alias_to: def_id, .. } => { - err.label = Some(Label::SelfTyAlias(reduce_impl_span_to_impl_keyword( - sm, - self.def_span(def_id), - ))); - err.refer_to_type_directly = - current_self_ty.map(|snippet| errs::UseTypeDirectly { span, snippet }); - None - } - Res::Def(DefKind::TyParam, def_id) => { - err.label = Some(Label::TyParam(self.def_span(def_id))); - Some(def_id) - } - Res::Def(DefKind::ConstParam, def_id) => { - err.label = Some(Label::ConstParam(self.def_span(def_id))); - Some(def_id) - } - _ => { - bug!( - "GenericParamsFromOuterItem should only be used with \ - Res::SelfTyParam, Res::SelfTyAlias, DefKind::TyParam or \ - DefKind::ConstParam" - ); - } - }; - - if let Some((_, item_span, ItemKind::Const(_))) = inner_item.as_ref() { - err.use_let = Some(errs::GenericParamsFromOuterItemUseLet { - span: sm.span_until_whitespace(*item_span), - }); - } - - if let Some(def_id) = def_id - && let HasGenericParams::Yes(span) = has_generic_params - && !matches!(inner_item, Some((_, _, ItemKind::Delegation(..)))) - { - let name = self.tcx.item_name(def_id); - let (span, snippet) = if span.is_empty() { - let snippet = format!("<{name}>"); - (span, snippet) - } else { - let span = sm.span_through_char(span, '<').shrink_to_hi(); - let snippet = format!("{name}, "); - (span, snippet) - }; - err.sugg = Some(errs::GenericParamsFromOuterItemSugg { span, snippet }); - } - - self.dcx().create_err(err) - } - ResolutionError::NameAlreadyUsedInParameterList(name, first_use_span) => self - .dcx() - .create_err(errs::NameAlreadyUsedInParameterList { span, first_use_span, name }), - ResolutionError::MethodNotMemberOfTrait(method, trait_, candidate) => { - self.dcx().create_err(errs::MethodNotMemberOfTrait { - span, - method, - trait_, - sub: candidate.map(|c| errs::AssociatedFnWithSimilarNameExists { - span: method.span, - candidate: c, - }), - }) - } - ResolutionError::TypeNotMemberOfTrait(type_, trait_, candidate) => { - self.dcx().create_err(errs::TypeNotMemberOfTrait { - span, - type_, - trait_, - sub: candidate.map(|c| errs::AssociatedTypeWithSimilarNameExists { - span: type_.span, - candidate: c, - }), - }) - } - ResolutionError::ConstNotMemberOfTrait(const_, trait_, candidate) => { - self.dcx().create_err(errs::ConstNotMemberOfTrait { - span, - const_, - trait_, - sub: candidate.map(|c| errs::AssociatedConstWithSimilarNameExists { - span: const_.span, - candidate: c, - }), - }) - } - ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => { - let BindingError { name, target, origin, could_be_path } = binding_error; - - let mut target_sp = target.iter().map(|pat| pat.span).collect::>(); - target_sp.sort(); - target_sp.dedup(); - let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::>(); - origin_sp.sort(); - origin_sp.dedup(); - - let msp = MultiSpan::from_spans(target_sp.clone()); - let mut err = self - .dcx() - .create_err(errors::VariableIsNotBoundInAllPatterns { multispan: msp, name }); - for sp in target_sp { - err.subdiagnostic(errors::PatternDoesntBindName { span: sp, name }); - } - for sp in &origin_sp { - err.subdiagnostic(errors::VariableNotInAllPatterns { span: *sp }); - } - let mut suggested_typo = false; - if !target.iter().all(|pat| matches!(pat.kind, ast::PatKind::Ident(..))) - && !origin.iter().all(|(_, pat)| matches!(pat.kind, ast::PatKind::Ident(..))) - { - // The check above is so that when we encounter `match foo { (a | b) => {} }`, - // we don't suggest `(a | a) => {}`, which would never be what the user wants. - let mut target_visitor = BindingVisitor::default(); - for pat in &target { - target_visitor.visit_pat(pat); - } - target_visitor.identifiers.sort(); - target_visitor.identifiers.dedup(); - let mut origin_visitor = BindingVisitor::default(); - for (_, pat) in &origin { - origin_visitor.visit_pat(pat); - } - origin_visitor.identifiers.sort(); - origin_visitor.identifiers.dedup(); - // Find if the binding could have been a typo - if let Some(typo) = - find_best_match_for_name(&target_visitor.identifiers, name.name, None) - && !origin_visitor.identifiers.contains(&typo) - { - err.subdiagnostic(errors::PatternBindingTypo { spans: origin_sp, typo }); - suggested_typo = true; - } - } - if could_be_path { - let import_suggestions = self.lookup_import_candidates( - name, - Namespace::ValueNS, - &parent_scope, - &|res: Res| { - matches!( - res, - Res::Def( - DefKind::Ctor(CtorOf::Variant, CtorKind::Const) - | DefKind::Ctor(CtorOf::Struct, CtorKind::Const) - | DefKind::Const { .. } - | DefKind::AssocConst { .. }, - _, - ) - ) - }, - ); - - if import_suggestions.is_empty() && !suggested_typo { - let kind_matches: [fn(DefKind) -> bool; 4] = [ - |kind| matches!(kind, DefKind::Ctor(CtorOf::Variant, CtorKind::Const)), - |kind| matches!(kind, DefKind::Ctor(CtorOf::Struct, CtorKind::Const)), - |kind| matches!(kind, DefKind::Const { .. }), - |kind| matches!(kind, DefKind::AssocConst { .. }), - ]; - let mut local_names = vec![]; - self.add_module_candidates( - parent_scope.module, - &mut local_names, - &|res| matches!(res, Res::Def(_, _)), - None, - ); - let local_names: FxHashSet<_> = local_names - .into_iter() - .filter_map(|s| match s.res { - Res::Def(_, def_id) => Some(def_id), - _ => None, - }) - .collect(); - - let mut local_suggestions = vec![]; - let mut suggestions = vec![]; - for matches_kind in kind_matches { - if let Some(suggestion) = self.early_lookup_typo_candidate( - ScopeSet::All(Namespace::ValueNS), - &parent_scope, - name, - &|res: Res| match res { - Res::Def(k, _) => matches_kind(k), - _ => false, - }, - ) && let Res::Def(kind, mut def_id) = suggestion.res - { - if let DefKind::Ctor(_, _) = kind { - def_id = self.tcx.parent(def_id); - } - let kind = kind.descr(def_id); - if local_names.contains(&def_id) { - // The item is available in the current scope. Very likely to - // be a typo. Don't use the full path. - local_suggestions.push(( - suggestion.candidate, - suggestion.candidate.to_string(), - kind, - )); - } else { - suggestions.push(( - suggestion.candidate, - self.def_path_str(def_id), - kind, - )); - } - } - } - let suggestions = if !local_suggestions.is_empty() { - // There is at least one item available in the current scope that is a - // likely typo. We only show those. - local_suggestions - } else { - suggestions - }; - for (name, sugg, kind) in suggestions { - err.span_suggestion_verbose( - span, - format!( - "you might have meant to use the similarly named {kind} `{name}`", - ), - sugg, - Applicability::MaybeIncorrect, - ); - suggested_typo = true; - } - } - if import_suggestions.is_empty() && !suggested_typo { - let help_msg = format!( - "if you meant to match on a unit struct, unit variant or a `const` \ - item, consider making the path in the pattern qualified: \ - `path::to::ModOrType::{name}`", - ); - err.span_help(span, help_msg); - } - show_candidates( - self.tcx, - &mut err, - Some(span), - &import_suggestions, - Instead::No, - FoundUse::Yes, - DiagMode::Pattern, - vec![], - "", - ); - } - err - } - ResolutionError::VariableBoundWithDifferentMode(variable_name, first_binding_span) => { - self.dcx().create_err(errs::VariableBoundWithDifferentMode { - span, - first_binding_span, - variable_name, - }) - } - ResolutionError::IdentifierBoundMoreThanOnceInParameterList(identifier) => self - .dcx() - .create_err(errs::IdentifierBoundMoreThanOnceInParameterList { span, identifier }), - ResolutionError::IdentifierBoundMoreThanOnceInSamePattern(identifier) => self - .dcx() - .create_err(errs::IdentifierBoundMoreThanOnceInSamePattern { span, identifier }), - ResolutionError::UndeclaredLabel { name, suggestion } => { - let ((sub_reachable, sub_reachable_suggestion), sub_unreachable) = match suggestion - { - // A reachable label with a similar name exists. - Some((ident, true)) => ( - ( - Some(errs::LabelWithSimilarNameReachable(ident.span)), - Some(errs::TryUsingSimilarlyNamedLabel { - span, - ident_name: ident.name, - }), - ), - None, - ), - // An unreachable label with a similar name exists. - Some((ident, false)) => ( - (None, None), - Some(errs::UnreachableLabelWithSimilarNameExists { - ident_span: ident.span, - }), - ), - // No similarly-named labels exist. - None => ((None, None), None), - }; - self.dcx().create_err(errs::UndeclaredLabel { - span, - name, - sub_reachable, - sub_reachable_suggestion, - sub_unreachable, - }) - } - ResolutionError::FailedToResolve { segment, label, suggestion, module, message } => { - let mut err = struct_span_code_err!(self.dcx(), span, E0433, "{message}"); - err.span_label(span, label); - - if let Some((suggestions, msg, applicability)) = suggestion { - if suggestions.is_empty() { - err.help(msg); - return err; - } - err.multipart_suggestion(msg, suggestions, applicability); - } - - let module = match module { - Some(ModuleOrUniformRoot::Module(m)) if let Some(id) = m.opt_def_id() => id, - _ => CRATE_DEF_ID.to_def_id(), - }; - self.find_cfg_stripped(&mut err, &segment, module); - - err - } - ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => { - self.dcx().create_err(errs::CannotCaptureDynamicEnvironmentInFnItem { span }) - } - ResolutionError::AttemptToUseNonConstantValueInConstant { - ident, - suggestion, - current, - type_span, - } => { - // let foo =... - // ^^^ given this Span - // ------- get this Span to have an applicable suggestion - - // edit: - // only do this if the const and usage of the non-constant value are on the same line - // the further the two are apart, the higher the chance of the suggestion being wrong - - let sp = self - .tcx - .sess - .source_map() - .span_extend_to_prev_str(ident.span, current, true, false); - - let (with, with_label, without) = match sp { - Some(sp) if !self.tcx.sess.source_map().is_multiline(sp) => { - let sp = sp - .with_lo(BytePos(sp.lo().0 - (current.len() as u32))) - .until(ident.span); - - // Only suggest replacing the binding keyword if this is a simple - // binding. - // - // Note: this approach still incorrectly suggests for irrefutable - // patterns like `if let x = 1 { const { x } }`, since the text - // between `let` and the identifier is just whitespace. - // See tests/ui/consts/non-const-value-in-const-irrefutable-pat-binding.rs - let is_simple_binding = - self.tcx.sess.source_map().span_to_snippet(sp).is_ok_and(|snippet| { - let after_keyword = snippet[current.len()..].trim(); - after_keyword.is_empty() || after_keyword == "mut" - }); - - if is_simple_binding { - ( - Some(errs::AttemptToUseNonConstantValueInConstantWithSuggestion { - span: sp, - suggestion, - current, - type_span, - }), - Some(errs::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), - None, - ) - } else { - ( - None, - Some(errs::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), - None, - ) - } - } - _ => ( - None, - None, - Some(errs::AttemptToUseNonConstantValueInConstantWithoutSuggestion { - ident_span: ident.span, - suggestion, - }), - ), - }; - - self.dcx().create_err(errs::AttemptToUseNonConstantValueInConstant { - span, - with, - with_label, - without, - }) - } - ResolutionError::BindingShadowsSomethingUnacceptable { - shadowing_binding, - name, - participle, - article, - shadowed_binding, - shadowed_binding_span, - } => self.dcx().create_err(errs::BindingShadowsSomethingUnacceptable { - span, - shadowing_binding, - shadowed_binding, - article, - sub_suggestion: match (shadowing_binding, shadowed_binding) { - ( - PatternSource::Match, - Res::Def(DefKind::Ctor(CtorOf::Variant | CtorOf::Struct, CtorKind::Fn), _), - ) => Some(errs::BindingShadowsSomethingUnacceptableSuggestion { span, name }), - _ => None, - }, - shadowed_binding_span, - participle, - name, - }), - ResolutionError::ForwardDeclaredGenericParam(param, reason) => match reason { - ForwardGenericParamBanReason::Default => { - self.dcx().create_err(errs::ForwardDeclaredGenericParam { param, span }) - } - ForwardGenericParamBanReason::ConstParamTy => self - .dcx() - .create_err(errs::ForwardDeclaredGenericInConstParamTy { param, span }), - }, - ResolutionError::ParamInTyOfConstParam { name } => { - self.dcx().create_err(errs::ParamInTyOfConstParam { span, name }) - } - ResolutionError::ParamInNonTrivialAnonConst { is_gca, name, param_kind: is_type } => { - self.dcx().create_err(errs::ParamInNonTrivialAnonConst { - span, - name, - param_kind: is_type, - help: self.tcx.sess.is_nightly_build(), - is_gca, - help_gca: is_gca, - }) - } - ResolutionError::ParamInEnumDiscriminant { name, param_kind: is_type } => self - .dcx() - .create_err(errs::ParamInEnumDiscriminant { span, name, param_kind: is_type }), - ResolutionError::ForwardDeclaredSelf(reason) => match reason { - ForwardGenericParamBanReason::Default => { - self.dcx().create_err(errs::SelfInGenericParamDefault { span }) - } - ForwardGenericParamBanReason::ConstParamTy => { - self.dcx().create_err(errs::SelfInConstGenericTy { span }) - } - }, - ResolutionError::UnreachableLabel { name, definition_span, suggestion } => { - let ((sub_suggestion_label, sub_suggestion), sub_unreachable_label) = - match suggestion { - // A reachable label with a similar name exists. - Some((ident, true)) => ( - ( - Some(errs::UnreachableLabelSubLabel { ident_span: ident.span }), - Some(errs::UnreachableLabelSubSuggestion { - span, - // intentionally taking 'ident.name' instead of 'ident' itself, as this - // could be used in suggestion context - ident_name: ident.name, - }), - ), - None, - ), - // An unreachable label with a similar name exists. - Some((ident, false)) => ( - (None, None), - Some(errs::UnreachableLabelSubLabelUnreachable { - ident_span: ident.span, - }), - ), - // No similarly-named labels exist. - None => ((None, None), None), - }; - self.dcx().create_err(errs::UnreachableLabel { - span, - name, - definition_span, - sub_suggestion, - sub_suggestion_label, - sub_unreachable_label, - }) - } - ResolutionError::TraitImplMismatch { - name, - kind, - code, - trait_item_span, - trait_path, - } => self - .dcx() - .create_err(errors::TraitImplMismatch { - span, - name, - kind, - trait_path, - trait_item_span, - }) - .with_code(code), - ResolutionError::TraitImplDuplicate { name, trait_item_span, old_span } => self - .dcx() - .create_err(errs::TraitImplDuplicate { span, name, trait_item_span, old_span }), - ResolutionError::InvalidAsmSym => self.dcx().create_err(errs::InvalidAsmSym { span }), - ResolutionError::LowercaseSelf => self.dcx().create_err(errs::LowercaseSelf { span }), - ResolutionError::BindingInNeverPattern => { - self.dcx().create_err(errs::BindingInNeverPattern { span }) - } - } - } + ident: Ident, + }, + #[label("`{$ident}` exists, but has no `attr` rules")] + Attr { + #[primary_span] + span: Span, + ident: Ident, + }, + #[label("`{$ident}` exists, but has no `derive` rules")] + Derive { + #[primary_span] + span: Span, + ident: Ident, + }, +} - pub(crate) fn report_vis_error( - &mut self, - vis_resolution_error: VisResolutionError, - ) -> ErrorGuaranteed { - match vis_resolution_error { - VisResolutionError::Relative2018(span, path) => { - self.dcx().create_err(errs::Relative2018 { - span, - path_span: path.span, - // intentionally converting to String, as the text would also be used as - // in suggestion context - path_str: pprust::path_to_string(&path), - }) - } - VisResolutionError::AncestorOnly(span) => { - self.dcx().create_err(errs::AncestorOnly(span)) - } - VisResolutionError::FailedToResolve(span, segment, label, suggestion, message) => self - .into_struct_error( - span, - ResolutionError::FailedToResolve { - segment, - label, - suggestion, - module: None, - message, - }, - ), - VisResolutionError::ExpectedFound(span, path_str, res) => { - self.dcx().create_err(errs::ExpectedModuleFound { span, res, path_str }) - } - VisResolutionError::Indeterminate(span) => { - self.dcx().create_err(errs::Indeterminate(span)) - } - VisResolutionError::ModuleOnly(span) => self.dcx().create_err(errs::ModuleOnly(span)), - } - .emit() - } +#[derive(Subdiagnostic)] +#[note("maybe you have forgotten to define a name for this `macro_rules!`")] +pub(crate) struct MaybeMissingMacroRulesName { + #[primary_span] + pub(crate) spans: MultiSpan, +} - pub(crate) fn def_path_str(&self, mut def_id: DefId) -> String { - // We can't use `def_path_str` in resolve. - let mut path = vec![def_id]; - while let Some(parent) = self.tcx.opt_parent(def_id) { - def_id = parent; - path.push(def_id); - if def_id.is_top_level_module() { - break; - } - } - // We will only suggest importing directly if it is accessible through that path. - path.into_iter() - .rev() - .map(|def_id| { - self.tcx - .opt_item_name(def_id) - .map(|name| { - match ( - def_id.is_top_level_module(), - def_id.is_local(), - self.tcx.sess.edition(), - ) { - (true, true, Edition::Edition2015) => String::new(), - (true, true, _) => kw::Crate.to_string(), - (true, false, _) | (false, _, _) => name.to_string(), - } - }) - .unwrap_or_else(|| "_".to_string()) - }) - .collect::>() - .join("::") - } +#[derive(Subdiagnostic)] +#[help("have you added the `#[macro_use]` on the module/import?")] +pub(crate) struct AddedMacroUse; - pub(crate) fn add_scope_set_candidates( - &mut self, - suggestions: &mut Vec, - scope_set: ScopeSet<'ra>, - ps: &ParentScope<'ra>, - sp: Span, - filter_fn: &impl Fn(Res) -> bool, - ) { - let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); - self.cm().visit_scopes(scope_set, ps, ctxt, sp, None, |this, scope, use_prelude, _| { - match scope { - Scope::DeriveHelpers(expn_id) => { - let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); - if filter_fn(res) { - suggestions.extend( - this.helper_attrs.get(&expn_id).into_iter().flatten().map( - |&(ident, orig_ident_span, _)| { - TypoSuggestion::new(ident.name, orig_ident_span, res) - }, - ), - ); - } - } - Scope::DeriveHelpersCompat => { - // Never recommend deprecated helper attributes. - } - Scope::MacroRules(macro_rules_scope) => { - if let MacroRulesScope::Def(macro_rules_def) = macro_rules_scope.get() { - let res = macro_rules_def.decl.res(); - if filter_fn(res) { - suggestions.push(TypoSuggestion::new( - macro_rules_def.ident.name, - macro_rules_def.orig_ident_span, - res, - )) - } - } - } - Scope::ModuleNonGlobs(module, _) => { - this.add_module_candidates(module, suggestions, filter_fn, None); - } - Scope::ModuleGlobs(..) => { - // Already handled in `ModuleNonGlobs`. - } - Scope::MacroUsePrelude => { - suggestions.extend(this.macro_use_prelude.iter().filter_map( - |(name, binding)| { - let res = binding.res(); - filter_fn(res).then_some(TypoSuggestion::typo_from_name(*name, res)) - }, - )); - } - Scope::BuiltinAttrs => { - let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(sym::dummy)); - if filter_fn(res) { - suggestions.extend( - BUILTIN_ATTRIBUTES - .iter() - // These trace attributes are compiler-generated and have - // deliberately invalid names. - .filter(|attr| { - !matches!(**attr, sym::cfg_trace | sym::cfg_attr_trace) - }) - .map(|attr| TypoSuggestion::typo_from_name(*attr, res)), - ); - } - } - Scope::ExternPreludeItems => { - // Add idents from both item and flag scopes. - suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, entry)| { - let res = Res::Def(DefKind::Mod, CRATE_DEF_ID.to_def_id()); - filter_fn(res).then_some(TypoSuggestion::new(ident.name, entry.span(), res)) - })); - } - Scope::ExternPreludeFlags => {} - Scope::ToolPrelude => { - let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); - suggestions.extend( - this.registered_tools - .iter() - .map(|ident| TypoSuggestion::new(ident.name, ident.span, res)), - ); - } - Scope::StdLibPrelude => { - if let Some(prelude) = this.prelude { - let mut tmp_suggestions = Vec::new(); - this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); - suggestions.extend( - tmp_suggestions - .into_iter() - .filter(|s| use_prelude.into() || this.is_builtin_macro(s.res)), - ); - } - } - Scope::BuiltinTypes => { - suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| { - let res = Res::PrimTy(*prim_ty); - filter_fn(res) - .then_some(TypoSuggestion::typo_from_name(prim_ty.name(), res)) - })) - } - } +#[derive(Subdiagnostic)] +#[suggestion("consider adding a derive", code = "{suggestion}", applicability = "maybe-incorrect")] +pub(crate) struct ConsiderAddingADerive { + #[primary_span] + pub(crate) span: Span, + pub(crate) suggestion: String, +} - ControlFlow::<()>::Continue(()) - }); - } +#[derive(Diagnostic)] +#[diag("cannot determine resolution for the import")] +pub(crate) struct CannotDetermineImportResolution { + #[primary_span] + pub(crate) span: Span, +} - /// Lookup typo candidate in scope for a macro or import. - fn early_lookup_typo_candidate( - &mut self, - scope_set: ScopeSet<'ra>, - parent_scope: &ParentScope<'ra>, - ident: Ident, - filter_fn: &impl Fn(Res) -> bool, - ) -> Option { - let mut suggestions = Vec::new(); - self.add_scope_set_candidates( - &mut suggestions, - scope_set, - parent_scope, - ident.span, - filter_fn, - ); - - // Make sure error reporting is deterministic. - suggestions.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); - - match find_best_match_for_name( - &suggestions.iter().map(|suggestion| suggestion.candidate).collect::>(), - ident.name, - None, - ) { - Some(found) if found != ident.name => { - suggestions.into_iter().find(|suggestion| suggestion.candidate == found) - } - _ => None, - } - } +#[derive(Diagnostic)] +#[diag("cannot determine resolution for the {$kind} `{$path}`")] +#[note("import resolution is stuck, try simplifying macro imports")] +pub(crate) struct CannotDetermineMacroResolution { + #[primary_span] + pub(crate) span: Span, + pub(crate) kind: &'static str, + pub(crate) path: String, +} - fn lookup_import_candidates_from_module( - &self, - lookup_ident: Ident, - namespace: Namespace, - parent_scope: &ParentScope<'ra>, - start_module: Module<'ra>, - crate_path: ThinVec, - filter_fn: FilterFn, - ) -> Vec - where - FilterFn: Fn(Res) -> bool, - { - let mut candidates = Vec::new(); - let mut seen_modules = FxHashSet::default(); - let start_did = start_module.def_id(); - let mut worklist = vec![( - start_module, - ThinVec::::new(), - true, - start_did.is_local() || !self.tcx.is_doc_hidden(start_did), - true, - )]; - let mut worklist_via_import = vec![]; - - while let Some((in_module, path_segments, accessible, doc_visible, is_stable)) = - match worklist.pop() { - None => worklist_via_import.pop(), - Some(x) => Some(x), - } - { - let in_module_is_extern = !in_module.def_id().is_local(); - in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { - // Avoid non-importable candidates. - if name_binding.is_assoc_item() - && !this.tcx.features().import_trait_associated_functions() - { - return; - } - - if ident.name == kw::Underscore { - return; - } - - let child_accessible = - accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); - - // do not venture inside inaccessible items of other crates - if in_module_is_extern && !child_accessible { - return; - } - - let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); - - // There is an assumption elsewhere that paths of variants are in the enum's - // declaration and not imported. With this assumption, the variant component is - // chopped and the rest of the path is assumed to be the enum's own path. For - // errors where a variant is used as the type instead of the enum, this causes - // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. - if via_import && name_binding.is_possibly_imported_variant() { - return; - } - - // #90113: Do not count an inaccessible reexported item as a candidate. - if let DeclKind::Import { source_decl, .. } = name_binding.kind - && this.is_accessible_from(source_decl.vis(), parent_scope.module) - && !this.is_accessible_from(name_binding.vis(), parent_scope.module) - { - return; - } - - let res = name_binding.res(); - let did = match res { - Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), - _ => res.opt_def_id(), - }; - let child_doc_visible = doc_visible - && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); - - // collect results based on the filter function - // avoid suggesting anything from the same module in which we are resolving - // avoid suggesting anything with a hygienic name - if ident.name == lookup_ident.name - && ns == namespace - && in_module != parent_scope.module - && ident.ctxt.is_root() - && filter_fn(res) - { - // create the path - let mut segms = if lookup_ident.span.at_least_rust_2018() { - // crate-local absolute paths start with `crate::` in edition 2018 - // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) - crate_path.clone() - } else { - ThinVec::new() - }; - segms.append(&mut path_segments.clone()); - - segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - let path = Path { span: name_binding.span, segments: segms, tokens: None }; - - if child_accessible - // Remove invisible match if exists - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.accessible) - { - candidates.remove(idx); - } - - let is_stable = if is_stable - && let Some(did) = did - && this.is_stable(did, path.span) - { - true - } else { - false - }; - - // Rreplace unstable suggestions if we meet a new stable one, - // and do nothing if any other situation. For example, if we - // meet `std::ops::Range` after `std::range::legacy::Range`, - // we will remove the latter and then insert the former. - if is_stable - && let Some(idx) = candidates - .iter() - .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) - { - candidates.remove(idx); - } - - if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { - // See if we're recommending TryFrom, TryInto, or FromIterator and add - // a note about editions - let note = if let Some(did) = did { - let requires_note = !did.is_local() - && find_attr!( - this.tcx, - did, - RustcDiagnosticItem( - sym::TryInto | sym::TryFrom | sym::FromIterator - ) - ); - requires_note.then(|| { - format!( - "'{}' is included in the prelude starting in Edition 2021", - path_names_to_string(&path) - ) - }) - } else { - None - }; - - candidates.push(ImportSuggestion { - did, - descr: res.descr(), - path, - accessible: child_accessible, - doc_visible: child_doc_visible, - note, - via_import, - is_stable, - }); - } - } - - // collect submodules to explore - if let Some(def_id) = name_binding.res().module_like_def_id() { - // form the path - let mut path_segments = path_segments.clone(); - path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); - - let alias_import = if let DeclKind::Import { import, .. } = name_binding.kind - && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind - && import.parent_scope.expansion == parent_scope.expansion - { - true - } else { - false - }; - - let is_extern_crate_that_also_appears_in_prelude = - name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); - - if !is_extern_crate_that_also_appears_in_prelude || alias_import { - // add the module to the lookup - if seen_modules.insert(def_id) { - if via_import { &mut worklist_via_import } else { &mut worklist }.push( - ( - this.expect_module(def_id), - path_segments, - child_accessible, - child_doc_visible, - is_stable && this.is_stable(def_id, name_binding.span), - ), - ); - } - } - } - }) - } +#[derive(Diagnostic)] +#[diag("`{$ident}` is private, and cannot be re-exported", code = E0364)] +pub(crate) struct CannotBeReexportedPrivate { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - candidates - } +#[derive(Diagnostic)] +#[diag("`{$ident}` is only public within the crate, and cannot be re-exported outside", code = E0364)] +pub(crate) struct CannotBeReexportedCratePublic { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - fn is_stable(&self, did: DefId, span: Span) -> bool { - if did.is_local() { - return true; - } +#[derive(Diagnostic)] +#[diag("`{$ident}` is private, and cannot be re-exported", code = E0365)] +#[note("consider declaring type or module `{$ident}` with `pub`")] +pub(crate) struct CannotBeReexportedPrivateNS { + #[primary_span] + #[label("re-export of private `{$ident}`")] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - match self.tcx.lookup_stability(did) { - Some(Stability { - level: StabilityLevel::Unstable { implied_by, .. }, feature, .. - }) => { - if span.allows_unstable(feature) { - true - } else if self.tcx.features().enabled(feature) { - true - } else if let Some(implied_by) = implied_by - && self.tcx.features().enabled(implied_by) - { - true - } else { - false - } - } - Some(_) => true, - None => false, - } - } +#[derive(Diagnostic)] +#[diag("`{$ident}` is only public within the crate, and cannot be re-exported outside", code = E0365)] +#[note("consider declaring type or module `{$ident}` with `pub`")] +pub(crate) struct CannotBeReexportedCratePublicNS { + #[primary_span] + #[label("re-export of crate public `{$ident}`")] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - /// When name resolution fails, this method can be used to look up candidate - /// entities with the expected name. It allows filtering them using the - /// supplied predicate (which should be used to only accept the types of - /// definitions expected, e.g., traits). The lookup spans across all crates. - /// - /// N.B., the method does not look into imports, but this is not a problem, - /// since we report the definitions (thus, the de-aliased imports). - pub(crate) fn lookup_import_candidates( - &mut self, - lookup_ident: Ident, - namespace: Namespace, - parent_scope: &ParentScope<'ra>, - filter_fn: FilterFn, - ) -> Vec - where - FilterFn: Fn(Res) -> bool, - { - let crate_path = thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(kw::Crate))]; - let mut suggestions = self.lookup_import_candidates_from_module( - lookup_ident, - namespace, - parent_scope, - self.graph_root.to_module(), - crate_path, - &filter_fn, - ); - - if lookup_ident.span.at_least_rust_2018() { - for (ident, entry) in &self.extern_prelude { - if entry.span().from_expansion() { - // Idents are adjusted to the root context before being - // resolved in the extern prelude, so reporting this to the - // user is no help. This skips the injected - // `extern crate std` in the 2018 edition, which would - // otherwise cause duplicate suggestions. - continue; - } - let Some(crate_id) = - self.cstore_mut().maybe_process_path_extern(self.tcx, ident.name) - else { - continue; - }; - - let crate_def_id = crate_id.as_def_id(); - let crate_root = self.expect_module(crate_def_id); - - // Check if there's already an item in scope with the same name as the crate. - // If so, we have to disambiguate the potential import suggestions by making - // the paths *global* (i.e., by prefixing them with `::`). - let needs_disambiguation = - self.resolutions(parent_scope.module).borrow().iter().any( - |(key, name_resolution)| { - if key.ns == TypeNS - && key.ident == *ident - && let Some(decl) = name_resolution.borrow().best_decl() - { - match decl.res() { - // No disambiguation needed if the identically named item we - // found in scope actually refers to the crate in question. - Res::Def(_, def_id) => def_id != crate_def_id, - Res::PrimTy(_) => true, - _ => false, - } - } else { - false - } - }, - ); - let mut crate_path = ThinVec::new(); - if needs_disambiguation { - crate_path.push(ast::PathSegment::path_root(rustc_span::DUMMY_SP)); - } - crate_path.push(ast::PathSegment::from_ident(ident.orig(entry.span()))); - - suggestions.extend(self.lookup_import_candidates_from_module( - lookup_ident, - namespace, - parent_scope, - crate_root, - crate_path, - &filter_fn, - )); - } - } +#[derive(Diagnostic)] +#[diag("extern crate `{$ident}` is private and cannot be re-exported", code = E0365)] +pub(crate) struct PrivateExternCrateReexport { + pub ident: Ident, + #[suggestion( + "consider making the `extern crate` item publicly accessible", + code = "pub ", + style = "verbose", + applicability = "maybe-incorrect" + )] + pub sugg: Span, +} - suggestions.retain(|suggestion| suggestion.is_stable || self.tcx.sess.is_nightly_build()); - suggestions - } +#[derive(Subdiagnostic)] +#[help("consider adding a `#[macro_export]` to the macro in the imported module")] +pub(crate) struct ConsiderAddingMacroExport { + #[primary_span] + pub(crate) span: Span, +} - pub(crate) fn unresolved_macro_suggestions( - &mut self, - err: &mut Diag<'_>, - macro_kind: MacroKind, - parent_scope: &ParentScope<'ra>, - ident: Ident, - krate: &Crate, - sugg_span: Option, - ) { - // Bring all unused `derive` macros into `macro_map` so we ensure they can be used for - // suggestions. - self.register_macros_for_all_crates(); - - let is_expected = - &|res: Res| res.macro_kinds().is_some_and(|k| k.contains(macro_kind.into())); - let suggestion = self.early_lookup_typo_candidate( - ScopeSet::Macro(macro_kind), - parent_scope, - ident, - is_expected, - ); - if !self.add_typo_suggestion(err, suggestion, ident.span) { - self.detect_derive_attribute(err, ident, parent_scope, sugg_span); - } +#[derive(Subdiagnostic)] +#[suggestion( + "in case you want to use the macro within this crate only, reduce the visibility to `pub(crate)`", + code = "pub(crate)", + applicability = "maybe-incorrect" +)] +pub(crate) struct ConsiderMarkingAsPubCrate { + #[primary_span] + pub(crate) vis_span: Span, +} - let import_suggestions = - self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected); - let (span, found_use) = match parent_scope.module.nearest_parent_mod_node_id() { - DUMMY_NODE_ID => (None, FoundUse::No), - node_id => UsePlacementFinder::check(krate, node_id), - }; - show_candidates( - self.tcx, - err, - span, - &import_suggestions, - Instead::No, - found_use, - DiagMode::Normal, - vec![], - "", - ); - - if macro_kind == MacroKind::Bang && ident.name == sym::macro_rules { - let label_span = ident.span.shrink_to_hi(); - let mut spans = MultiSpan::from_span(label_span); - spans.push_span_label(label_span, "put a macro name here"); - err.subdiagnostic(MaybeMissingMacroRulesName { spans }); - return; - } +#[derive(Subdiagnostic)] +#[note("consider marking `{$ident}` as `pub` in the imported module")] +pub(crate) struct ConsiderMarkingAsPub { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - if macro_kind == MacroKind::Derive && (ident.name == sym::Send || ident.name == sym::Sync) { - err.subdiagnostic(ExplicitUnsafeTraits { span: ident.span, ident }); - return; - } +#[derive(Diagnostic)] +#[diag("cannot glob-import all possible crates")] +pub(crate) struct CannotGlobImportAllCrates { + #[primary_span] + pub(crate) span: Span, +} - let unused_macro = self.unused_macros.iter().find_map(|(def_id, (_, unused_ident))| { - if unused_ident.name == ident.name { Some((def_id, unused_ident)) } else { None } - }); - - if let Some((def_id, unused_ident)) = unused_macro { - let scope = self.local_macro_def_scopes[&def_id]; - let parent_nearest = parent_scope.module.nearest_parent_mod(); - let unused_macro_kinds = self.local_macro_map[def_id].macro_kinds(); - if !unused_macro_kinds.contains(macro_kind.into()) { - match macro_kind { - MacroKind::Bang => { - err.subdiagnostic(MacroRulesNot::Func { span: unused_ident.span, ident }); - } - MacroKind::Attr => { - err.subdiagnostic(MacroRulesNot::Attr { span: unused_ident.span, ident }); - } - MacroKind::Derive => { - err.subdiagnostic(MacroRulesNot::Derive { span: unused_ident.span, ident }); - } - } - return; - } - if Some(parent_nearest) == scope.opt_def_id() { - err.subdiagnostic(MacroDefinedLater { span: unused_ident.span }); - err.subdiagnostic(MacroSuggMovePosition { span: ident.span, ident }); - return; - } - } +#[derive(Subdiagnostic)] +#[suggestion( + "you might have meant to write a const parameter here", + code = "const ", + style = "verbose" +)] +pub(crate) struct UnexpectedResChangeTyToConstParamSugg { + #[primary_span] + pub span: Span, + #[applicability] + pub applicability: Applicability, +} - if ident.name == kw::Default - && let ModuleKind::Def(DefKind::Enum, def_id, _, _) = parent_scope.module.kind - { - let span = self.def_span(def_id); - let source_map = self.tcx.sess.source_map(); - let head_span = source_map.guess_head_span(span); - err.subdiagnostic(ConsiderAddingADerive { - span: head_span.shrink_to_lo(), - suggestion: "#[derive(Default)]\n".to_string(), - }); - } - for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] { - let Ok(binding) = self.cm().resolve_ident_in_scope_set( - ident, - ScopeSet::All(ns), - parent_scope, - None, - None, - None, - ) else { - continue; - }; - - let desc = match binding.res() { - Res::Def(DefKind::Macro(MacroKinds::BANG), _) => { - "a function-like macro".to_string() - } - Res::Def(DefKind::Macro(MacroKinds::ATTR), _) | Res::NonMacroAttr(..) => { - format!("an attribute: `#[{ident}]`") - } - Res::Def(DefKind::Macro(MacroKinds::DERIVE), _) => { - format!("a derive macro: `#[derive({ident})]`") - } - Res::Def(DefKind::Macro(kinds), _) => { - format!("{} {}", kinds.article(), kinds.descr()) - } - Res::ToolMod | Res::OpenMod(..) => { - // Don't confuse the user with tool modules or open modules. - continue; - } - Res::Def(DefKind::Trait, _) if macro_kind == MacroKind::Derive => { - "only a trait, without a derive macro".to_string() - } - res => format!( - "{} {}, not {} {}", - res.article(), - res.descr(), - macro_kind.article(), - macro_kind.descr_expected(), - ), - }; - if let crate::DeclKind::Import { import, .. } = binding.kind - && !import.span.is_dummy() - { - let note = errors::IdentImporterHereButItIsDesc { - span: import.span, - imported_ident: ident, - imported_ident_desc: &desc, - }; - err.subdiagnostic(note); - // Silence the 'unused import' warning we might get, - // since this diagnostic already covers that import. - self.record_use(ident, binding, Used::Other); - return; - } - let note = errors::IdentInScopeButItIsDesc { - imported_ident: ident, - imported_ident_desc: &desc, - }; - err.subdiagnostic(note); - return; - } +#[derive(Subdiagnostic)] +#[suggestion( + "you might have meant to introduce a const parameter `{$item_name}` on the {$item_location}", + code = "{snippet}", + applicability = "machine-applicable", + style = "verbose" +)] +pub(crate) struct UnexpectedMissingConstParameter { + #[primary_span] + pub span: Span, + pub snippet: String, + pub item_name: String, + pub item_location: String, +} - if self.macro_names.contains(&IdentKey::new(ident)) { - err.subdiagnostic(AddedMacroUse); - return; - } - } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "you might have meant to write a const parameter here", + applicability = "has-placeholders", + style = "verbose" +)] +pub(crate) struct UnexpectedResChangeTyParamToConstParamSugg { + #[suggestion_part(code = "const ")] + pub before: Span, + #[suggestion_part(code = ": /* Type */")] + pub after: Span, +} - /// Given an attribute macro that failed to be resolved, look for `derive` macros that could - /// provide it, either as-is or with small typos. - fn detect_derive_attribute( - &self, - err: &mut Diag<'_>, - ident: Ident, - parent_scope: &ParentScope<'ra>, - sugg_span: Option, - ) { - // Find all of the `derive`s in scope and collect their corresponding declared - // attributes. - // FIXME: this only works if the crate that owns the macro that has the helper_attr - // has already been imported. - let mut derives = vec![]; - let mut all_attrs: UnordMap> = UnordMap::default(); - // We're collecting these in a hashmap, and handle ordering the output further down. - #[allow(rustc::potential_query_instability)] - for (def_id, ext) in self - .local_macro_map - .iter() - .map(|(local_id, ext)| (local_id.to_def_id(), ext)) - .chain(self.extern_macro_map.borrow().iter().map(|(id, d)| (*id, d))) - { - for helper_attr in &ext.helper_attrs { - let item_name = self.tcx.item_name(def_id); - all_attrs.entry(*helper_attr).or_default().push(item_name); - if helper_attr == &ident.name { - derives.push(item_name); - } - } - } - let kind = MacroKind::Derive.descr(); - if !derives.is_empty() { - // We found an exact match for the missing attribute in a `derive` macro. Suggest it. - let mut derives: Vec = derives.into_iter().map(|d| d.to_string()).collect(); - derives.sort(); - derives.dedup(); - let msg = match &derives[..] { - [derive] => format!(" `{derive}`"), - [start @ .., last] => format!( - "s {} and `{last}`", - start.iter().map(|d| format!("`{d}`")).collect::>().join(", ") - ), - [] => unreachable!("we checked for this to be non-empty 10 lines above!?"), - }; - let msg = format!( - "`{}` is an attribute that can be used by the {kind}{msg}, you might be \ - missing a `derive` attribute", - ident.name, - ); - let sugg_span = - if let ModuleKind::Def(DefKind::Enum, id, _, _) = parent_scope.module.kind { - let span = self.def_span(id); - if span.from_expansion() { - None - } else { - // For enum variants sugg_span is empty but we can get the enum's Span. - Some(span.shrink_to_lo()) - } - } else { - // For items this `Span` will be populated, everything else it'll be None. - sugg_span - }; - match sugg_span { - Some(span) => { - err.span_suggestion_verbose( - span, - msg, - format!("#[derive({})]\n", derives.join(", ")), - Applicability::MaybeIncorrect, - ); - } - None => { - err.note(msg); - } - } - } else { - // We didn't find an exact match. Look for close matches. If any, suggest fixing typo. - let all_attr_names = all_attrs.keys().map(|s| *s).into_sorted_stable_ord(); - if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None) - && let Some(macros) = all_attrs.get(&best_match) - { - let mut macros: Vec = macros.into_iter().map(|d| d.to_string()).collect(); - macros.sort(); - macros.dedup(); - let msg = match ¯os[..] { - [] => return, - [name] => format!(" `{name}` accepts"), - [start @ .., end] => format!( - "s {} and `{end}` accept", - start.iter().map(|m| format!("`{m}`")).collect::>().join(", "), - ), - }; - let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute"); - err.span_suggestion_verbose( - ident.span, - msg, - best_match, - Applicability::MaybeIncorrect, - ); - } - } - } +#[derive(Subdiagnostic)] +#[suggestion( + "if you meant to collect the rest of the slice in `{$ident}`, use the at operator", + code = "{snippet}", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct UnexpectedResUseAtOpInSlicePatWithRangeSugg { + #[primary_span] + pub span: Span, + pub ident: Ident, + pub snippet: String, +} - pub(crate) fn add_typo_suggestion( - &self, - err: &mut Diag<'_>, - suggestion: Option, - span: Span, - ) -> bool { - let suggestion = match suggestion { - None => return false, - // We shouldn't suggest underscore. - Some(suggestion) if suggestion.candidate == kw::Underscore => return false, - Some(suggestion) => suggestion, - }; - - let mut did_label_def_span = false; - - if let Some(def_span) = suggestion.res.opt_def_id().map(|def_id| self.def_span(def_id)) { - if span.overlaps(def_span) { - // Don't suggest typo suggestion for itself like in the following: - // error[E0423]: expected function, tuple struct or tuple variant, found struct `X` - // --> $DIR/unicode-string-literal-syntax-error-64792.rs:4:14 - // | - // LL | struct X {} - // | ----------- `X` defined here - // LL | - // LL | const Y: X = X("ö"); - // | -------------^^^^^^- similarly named constant `Y` defined here - // | - // help: use struct literal syntax instead - // | - // LL | const Y: X = X {}; - // | ^^^^ - // help: a constant with a similar name exists - // | - // LL | const Y: X = Y("ö"); - // | ^ - return false; - } - let span = self.tcx.sess.source_map().guess_head_span(def_span); - let candidate_descr = suggestion.res.descr(); - let candidate = suggestion.candidate; - let label = match suggestion.target { - SuggestionTarget::SimilarlyNamed => { - errors::DefinedHere::SimilarlyNamed { span, candidate_descr, candidate } - } - SuggestionTarget::SingleItem => { - errors::DefinedHere::SingleItem { span, candidate_descr, candidate } - } - }; - did_label_def_span = true; - err.subdiagnostic(label); - } +#[derive(Diagnostic)] +#[diag("an `extern crate` loading macros must be at the crate root", code = E0468)] +pub(crate) struct ExternCrateLoadingMacroNotAtCrateRoot { + #[primary_span] + pub(crate) span: Span, +} - let (span, msg, sugg) = if let SuggestionTarget::SimilarlyNamed = suggestion.target - && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) - && let Some(span) = suggestion.span - && let Some(candidate) = suggestion.candidate.as_str().strip_prefix('_') - && snippet == candidate - { - let candidate = suggestion.candidate; - // When the suggested binding change would be from `x` to `_x`, suggest changing the - // original binding definition instead. (#60164) - let msg = format!( - "the leading underscore in `{candidate}` marks it as unused, consider renaming it to `{snippet}`" - ); - if !did_label_def_span { - err.span_label(span, format!("`{candidate}` defined here")); - } - (span, msg, snippet) - } else { - let msg = match suggestion.target { - SuggestionTarget::SimilarlyNamed => format!( - "{} {} with a similar name exists", - suggestion.res.article(), - suggestion.res.descr() - ), - SuggestionTarget::SingleItem => { - format!("maybe you meant this {}", suggestion.res.descr()) - } - }; - (span, msg, suggestion.candidate.to_ident_string()) - }; - err.span_suggestion_verbose(span, msg, sugg, Applicability::MaybeIncorrect); - true - } +#[derive(Diagnostic)] +#[diag("`extern crate self;` requires renaming")] +pub(crate) struct ExternCrateSelfRequiresRenaming { + #[primary_span] + #[suggestion( + "rename the `self` crate to be able to import it", + code = "extern crate self as name;", + applicability = "has-placeholders" + )] + pub(crate) span: Span, +} - fn decl_description(&self, b: Decl<'_>, ident: Ident, scope: Scope<'_>) -> String { - let res = b.res(); - if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) { - let (built_in, from) = match scope { - Scope::StdLibPrelude | Scope::MacroUsePrelude => ("", " from prelude"), - Scope::ExternPreludeFlags - if self.tcx.sess.opts.externs.get(ident.as_str()).is_some() - || matches!(res, Res::OpenMod(..)) => - { - ("", " passed with `--extern`") - } - _ => { - if matches!(res, Res::NonMacroAttr(..) | Res::PrimTy(..) | Res::ToolMod) { - // These already contain the "built-in" prefix or look bad with it. - ("", "") - } else { - (" built-in", "") - } - } - }; - - let a = if built_in.is_empty() { res.article() } else { "a" }; - format!("{a}{built_in} {thing}{from}", thing = res.descr()) - } else { - let introduced = if b.is_import_user_facing() { "imported" } else { "defined" }; - format!("the {thing} {introduced} here", thing = res.descr()) - } - } +#[derive(Diagnostic)] +#[diag("`{$name}` is already in scope")] +#[note("macro-expanded `#[macro_use]`s may not shadow existing macros (see RFC 1560)")] +pub(crate) struct MacroUseNameAlreadyInUse { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Symbol, +} - fn ambiguity_diagnostic(&self, ambiguity_error: &AmbiguityError<'ra>) -> errors::Ambiguity { - let AmbiguityError { kind, ambig_vis, ident, b1, b2, scope1, scope2, .. } = - *ambiguity_error; - let extern_prelude_ambiguity = || { - // Note: b1 may come from a module scope, as an extern crate item in module. - matches!(scope2, Scope::ExternPreludeFlags) - && self - .extern_prelude - .get(&IdentKey::new(ident)) - .is_some_and(|entry| entry.item_decl.map(|(b, ..)| b) == Some(b1)) - }; - let (b1, b2, scope1, scope2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() { - // We have to print the span-less alternative first, otherwise formatting looks bad. - (b2, b1, scope2, scope1, true) - } else { - (b1, b2, scope1, scope2, false) - }; - - let could_refer_to = |b: Decl<'_>, scope: Scope<'ra>, also: &str| { - let what = self.decl_description(b, ident, scope); - let note_msg = format!("`{ident}` could{also} refer to {what}"); - - let thing = b.res().descr(); - let mut help_msgs = Vec::new(); - if b.is_glob_import() - && (kind == AmbiguityKind::GlobVsGlob - || kind == AmbiguityKind::GlobVsExpanded - || kind == AmbiguityKind::GlobVsOuter && swapped != also.is_empty()) - { - help_msgs.push(format!( - "consider adding an explicit import of `{ident}` to disambiguate" - )) - } - if b.is_extern_crate() && ident.span.at_least_rust_2018() && !extern_prelude_ambiguity() - { - help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously")) - } +#[derive(Diagnostic)] +#[diag("imported macro not found", code = E0469)] +pub(crate) struct ImportedMacroNotFound { + #[primary_span] + pub(crate) span: Span, +} - if kind != AmbiguityKind::GlobVsGlob { - if let Scope::ModuleNonGlobs(module, _) | Scope::ModuleGlobs(module, _) = scope { - if module == self.graph_root.to_module() { - help_msgs.push(format!( - "use `crate::{ident}` to refer to this {thing} unambiguously" - )); - } else if module.is_normal() { - help_msgs.push(format!( - "use `self::{ident}` to refer to this {thing} unambiguously" - )); - } - } - } +#[derive(Diagnostic)] +#[diag("`#[macro_escape]` is a deprecated synonym for `#[macro_use]`")] +pub(crate) struct MacroExternDeprecated { + #[primary_span] + pub(crate) span: Span, + #[help("try an outer attribute: `#[macro_use]`")] + pub inner_attribute: bool, +} - ( - Spanned { node: note_msg, span: b.span }, - help_msgs - .iter() - .enumerate() - .map(|(i, help_msg)| { - let or = if i == 0 { "" } else { "or " }; - format!("{or}{help_msg}") - }) - .collect::>(), - ) - }; - let (b1_note, b1_help_msgs) = could_refer_to(b1, scope1, ""); - let (b2_note, b2_help_msgs) = could_refer_to(b2, scope2, " also"); - let help = if kind == AmbiguityKind::GlobVsGlob - && b1 - .parent_module - .and_then(|m| m.opt_def_id()) - .map(|d| !d.is_local()) - .unwrap_or_default() - { - Some(&[ - "consider updating this dependency to resolve this error", - "if updating the dependency does not resolve the problem report the problem to the author of the relevant crate", - ] as &[_]) - } else { - None - }; - - let ambig_vis = ambig_vis.map(|(vis1, vis2)| { - format!( - "{} or {}", - vis1.to_string(CRATE_DEF_ID, self.tcx), - vis2.to_string(CRATE_DEF_ID, self.tcx) - ) - }); - - errors::Ambiguity { - ident, - help, - ambig_vis, - kind: kind.descr(), - b1_note, - b1_help_msgs, - b2_note, - b2_help_msgs, - is_error: false, - } - } +#[derive(Diagnostic)] +#[diag("arguments to `macro_use` are not allowed here")] +pub(crate) struct ArgumentsMacroUseNotAllowed { + #[primary_span] + pub(crate) span: Span, +} - /// If the binding refers to a tuple struct constructor with fields, - /// returns the span of its fields. - fn ctor_fields_span(&self, decl: Decl<'_>) -> Option { - let DeclKind::Def(Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), ctor_def_id)) = - decl.kind - else { - return None; - }; - - let def_id = self.tcx.parent(ctor_def_id); - self.field_idents(def_id)?.iter().map(|&f| f.span).reduce(Span::to) // None for `struct Foo()` - } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "try renaming it with a name", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct UnnamedImportSugg { + #[suggestion_part(code = "{ident} as name")] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - /// Returns the path segments (as symbols) of a module, including `kw::Crate` at the start. - /// For example, for `crate::foo::bar`, returns `[Crate, foo, bar]`. - /// Returns `None` for block modules that don't have a `DefId`. - fn module_path_names(&self, module: Module<'ra>) -> Option> { - let mut path = Vec::new(); - let mut def_id = module.opt_def_id()?; - while let Some(parent) = self.tcx.opt_parent(def_id) { - if let Some(name) = self.tcx.opt_item_name(def_id) { - path.push(name); - } - if parent.is_top_level_module() { - break; - } - def_id = parent; - } - path.reverse(); - path.insert(0, kw::Crate); - Some(path) - } +#[derive(Diagnostic)] +#[diag("imports need to be explicitly named")] +pub(crate) struct UnnamedImport { + #[primary_span] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) sugg: UnnamedImportSugg, +} - /// Shortens a candidate import path to use `super::` (up to 1 level) or `self::` (same module) - /// relative to the current scope, if possible. Only applies to crate-local items and - /// only when the resulting path is actually shorter than the original. - fn shorten_candidate_path( - &self, - suggestion: &mut ImportSuggestion, - current_module: Module<'ra>, - ) { - const MAX_SUPER_PATH_ITEMS_IN_SUGGESTION: usize = 1; - - // Only shorten local items. - if suggestion.did.is_none_or(|did| !did.is_local()) { - return; - } +#[derive(Diagnostic)] +#[diag("macro-expanded `extern crate` items cannot shadow names passed with `--extern`")] +pub(crate) struct MacroExpandedExternCrateCannotShadowExternArguments { + #[primary_span] + pub(crate) span: Span, +} - // Build current module path: [Crate, foo, bar, ...]. - let Some(current_mod_path) = self.module_path_names(current_module) else { - return; - }; - - // Normalise candidate path: filter out `PathRoot` (`::`), and if the path - // doesn't start with `Crate`, prepend it (edition 2015 paths are relative - // to the crate root without an explicit `crate::` prefix). - let candidate_names = { - let filtered_segments: Vec<_> = suggestion - .path - .segments - .iter() - .filter(|segment| segment.ident.name != kw::PathRoot) - .collect(); - - let mut candidate_names: Vec = - filtered_segments.iter().map(|segment| segment.ident.name).collect(); - if candidate_names.first() != Some(&kw::Crate) { - candidate_names.insert(0, kw::Crate); - } - if candidate_names.len() < 2 { - return; - } - candidate_names - }; - - // The candidate's module path is everything except the last segment (the item name). - let candidate_mod_names = &candidate_names[..candidate_names.len() - 1]; - - // Find the longest common prefix between the current module and candidate module paths. - let common_prefix_length = current_mod_path - .iter() - .zip(candidate_mod_names.iter()) - .take_while(|(current, candidate)| current == candidate) - .count(); - - // Non-crate-local item; keep the full absolute path. - if common_prefix_length == 0 { - return; - } +#[derive(Diagnostic)] +#[diag("`&` without an explicit lifetime name cannot be used here", code = E0637)] +pub(crate) struct ElidedAnonymousLifetimeReportError { + #[primary_span] + #[label("explicit lifetime name needed here")] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) suggestion: Option, +} - let super_count = current_mod_path.len() - common_prefix_length; +#[derive(Diagnostic)] +#[diag( + "associated type `Iterator::Item` is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type" +)] +pub(crate) struct LendingIteratorReportError { + #[primary_span] + pub(crate) lifetime: Span, + #[note( + "you can't create an `Iterator` that borrows each `Item` from itself, but you can instead create a new type that borrows your existing type and implement `Iterator` for that new type" + )] + pub(crate) ty: Span, +} - // At the crate root, `use` paths resolve from the crate root anyway, so we can - // drop the `crate::` prefix entirely instead of replacing it with `self::`. - let at_crate_root = current_mod_path.len() == 1; +#[derive(Diagnostic)] +#[diag("missing lifetime in associated type")] +pub(crate) struct AnonymousLifetimeNonGatReportError { + #[primary_span] + #[label("this lifetime must come from the implemented type")] + pub(crate) lifetime: Span, + #[note( + "in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type" + )] + pub(crate) decl: MultiSpan, +} - let mut new_segments = if super_count == 0 && at_crate_root { - ThinVec::new() - } else { - let prefix_keyword = match super_count { - 0 => kw::SelfLower, - 1..=MAX_SUPER_PATH_ITEMS_IN_SUGGESTION => kw::Super, - _ => return, // Too many `super` levels; keep the full absolute path. - }; - thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(prefix_keyword),)] - }; - for &name in &candidate_names[common_prefix_length..] { - new_segments.push(ast::PathSegment::from_ident(Ident::with_dummy_span(name))); - } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "consider introducing a higher-ranked lifetime here", + applicability = "machine-applicable" +)] +pub(crate) struct ElidedAnonymousLifetimeReportErrorSuggestion { + #[suggestion_part(code = "for<'a> ")] + pub(crate) lo: Span, + #[suggestion_part(code = "'a ")] + pub(crate) hi: Span, +} - // Only apply if the result is strictly shorter than the original path. - if new_segments.len() >= suggestion.path.segments.len() { - return; - } +#[derive(Diagnostic)] +#[diag("`'_` cannot be used here", code = E0637)] +pub(crate) struct ExplicitAnonymousLifetimeReportError { + #[primary_span] + #[label("`'_` is a reserved lifetime name")] + pub(crate) span: Span, +} - suggestion.path = Path { span: suggestion.path.span, segments: new_segments, tokens: None }; - } +#[derive(Diagnostic)] +#[diag("implicit elided lifetime not allowed here", code = E0726)] +pub(crate) struct ImplicitElidedLifetimeNotAllowedHere { + #[primary_span] + pub(crate) span: Span, + #[subdiagnostic] + pub(crate) subdiag: ElidedLifetimeInPathSubdiag, +} - fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { - let PrivacyError { - ident, - decl, - outermost_res, - parent_scope, - single_nested, - dedup_span, - ref source, - } = *privacy_error; - - let res = decl.res(); - let ctor_fields_span = self.ctor_fields_span(decl); - let plain_descr = res.descr().to_string(); - let nonimport_descr = - if ctor_fields_span.is_some() { plain_descr + " constructor" } else { plain_descr }; - let import_descr = nonimport_descr.clone() + " import"; - let get_descr = |b: Decl<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; - - // Print the primary message. - let ident_descr = get_descr(decl); - let mut err = - self.dcx().create_err(errors::IsPrivate { span: ident.span, ident_descr, ident }); - - self.mention_default_field_values(source, ident, &mut err); - - let shown_candidates = if let Some((this_res, outer_ident)) = outermost_res { - let mut import_suggestions = self.lookup_import_candidates( - outer_ident, - this_res.ns().unwrap_or(Namespace::TypeNS), - &parent_scope, - &|res: Res| res == this_res, - ); - // Shorten candidate paths using `super::` or `self::` when possible. - for suggestion in &mut import_suggestions { - self.shorten_candidate_path(suggestion, parent_scope.module); - } - let point_to_def = !show_candidates( - self.tcx, - &mut err, - Some(dedup_span.until(outer_ident.span.shrink_to_hi())), - &import_suggestions, - Instead::Yes, - FoundUse::Yes, - DiagMode::Import { append: single_nested, unresolved_import: false }, - vec![], - "", - ); - // If we suggest importing a public re-export, don't point at the definition. - if point_to_def && ident.span != outer_ident.span { - let label = errors::OuterIdentIsNotPubliclyReexported { - span: outer_ident.span, - outer_ident_descr: this_res.descr(), - outer_ident, - }; - err.subdiagnostic(label); - } - !point_to_def - } else { - false - }; - - let mut non_exhaustive = None; - // If an ADT is foreign and marked as `non_exhaustive`, then that's - // probably why we have the privacy error. - // Otherwise, point out if the struct has any private fields. - if let Some(def_id) = res.opt_def_id() - && !def_id.is_local() - && let Some(attr_span) = find_attr!(self.tcx, def_id, NonExhaustive(span) => *span) - { - non_exhaustive = Some(attr_span); - } else if let Some(span) = ctor_fields_span { - let label = errors::ConstructorPrivateIfAnyFieldPrivate { span }; - err.subdiagnostic(label); - if let Res::Def(_, d) = res - && let Some(fields) = self.field_visibility_spans.get(&d) - { - let spans = fields.iter().map(|span| *span).collect(); - let sugg = - errors::ConsiderMakingTheFieldPublic { spans, number_of_fields: fields.len() }; - err.subdiagnostic(sugg); - } - } +#[derive(Diagnostic)] +#[diag("`'_` cannot be used here", code = E0637)] +#[help("use another lifetime specifier")] +pub(crate) struct UnderscoreLifetimeIsReserved { + #[primary_span] + #[label("`'_` is a reserved lifetime name")] + pub(crate) span: Span, +} - let mut sugg_paths: Vec<(Vec, bool)> = vec![]; - if let Some(mut def_id) = res.opt_def_id() { - // We can't use `def_path_str` in resolve. - let mut path = vec![def_id]; - while let Some(parent) = self.tcx.opt_parent(def_id) { - def_id = parent; - if !def_id.is_top_level_module() { - path.push(def_id); - } else { - break; - } - } - // We will only suggest importing directly if it is accessible through that path. - let path_names: Option> = path - .iter() - .rev() - .map(|def_id| { - self.tcx.opt_item_name(*def_id).map(|name| { - Ident::with_dummy_span(if def_id.is_top_level_module() { - kw::Crate - } else { - name - }) - }) - }) - .collect(); - if let Some(&def_id) = path.get(0) - && let Some(path) = path_names - { - if let Some(def_id) = def_id.as_local() { - if self.effective_visibilities.is_directly_public(def_id) { - sugg_paths.push((path, false)); - } - } else if self.is_accessible_from(self.tcx.visibility(def_id), parent_scope.module) - { - sugg_paths.push((path, false)); - } - } - } +#[derive(Diagnostic)] +#[diag("invalid lifetime parameter name: `{$lifetime}`", code = E0262)] +pub(crate) struct StaticLifetimeIsReserved { + #[primary_span] + #[label("'static is a reserved lifetime name")] + pub(crate) span: Span, + pub(crate) lifetime: Ident, +} - // Print the whole import chain to make it easier to see what happens. - let first_binding = decl; - let mut next_binding = Some(decl); - let mut next_ident = ident; - while let Some(binding) = next_binding { - let name = next_ident; - next_binding = match binding.kind { - _ if res == Res::Err => None, - DeclKind::Import { source_decl, import, .. } => match import.kind { - _ if source_decl.span.is_dummy() => None, - ImportKind::Single { source, .. } => { - next_ident = source; - Some(source_decl) - } - ImportKind::Glob { .. } - | ImportKind::MacroUse { .. } - | ImportKind::MacroExport => Some(source_decl), - ImportKind::ExternCrate { .. } => None, - }, - _ => None, - }; - - match binding.kind { - DeclKind::Import { source_decl, import, .. } => { - // Don't include `{{root}}` in suggestions - it's an internal symbol - // that should never be shown to users. - let path = import - .module_path - .iter() - .filter(|seg| seg.ident.name != kw::PathRoot) - .map(|seg| seg.ident.clone()) - .chain(std::iter::once(ident)) - .collect::>(); - let through_reexport = !matches!(source_decl.kind, DeclKind::Def(_)); - sugg_paths.push((path, through_reexport)); - } - DeclKind::Def(_) => {} - } - let first = binding == first_binding; - let def_span = self.tcx.sess.source_map().guess_head_span(binding.span); - let mut note_span = MultiSpan::from_span(def_span); - if !first && binding.vis().is_public() { - let desc = match binding.kind { - DeclKind::Import { .. } => "re-export", - _ => "directly", - }; - note_span.push_span_label(def_span, format!("you could import this {desc}")); - } - // Final step in the import chain, point out if the ADT is `non_exhaustive` - // which is probably why this privacy violation occurred. - if next_binding.is_none() - && let Some(span) = non_exhaustive - { - note_span.push_span_label( - span, - "cannot be constructed because it is `#[non_exhaustive]`", - ); - } - let note = errors::NoteAndRefersToTheItemDefinedHere { - span: note_span, - binding_descr: get_descr(binding), - binding_name: name, - first, - dots: next_binding.is_some(), - }; - err.subdiagnostic(note); - } - // The suggestion replaces `dedup_span` with a path reaching the failing ident. - // That's valid only when - // 1) the failing ident is the imported leaf, otherwise `as` renames and trailing segments - // get dropped, and - // 2) the use isn't nested, otherwise `dedup_span` is one ident in `{...}`. - // - // See issue #156060. - let can_replace_use = !shown_candidates - && !single_nested - && !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span); - if can_replace_use { - // We prioritize shorter paths, non-core imports and direct imports over the - // alternatives. - sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0].name == sym::core, *reexport)); - for (sugg, reexport) in sugg_paths { - if sugg.len() <= 1 { - // A single path segment suggestion is wrong. This happens on circular - // imports. `tests/ui/imports/issue-55884-2.rs` - continue; - } - let path = join_path_idents(sugg); - let sugg = if reexport { - errors::ImportIdent::ThroughReExport { span: dedup_span, ident, path } - } else { - errors::ImportIdent::Directly { span: dedup_span, ident, path } - }; - err.subdiagnostic(sugg); - break; - } - } +#[derive(Diagnostic)] +#[diag("variable `{$name}` is not bound in all patterns", code = E0408)] +pub(crate) struct VariableIsNotBoundInAllPatterns { + #[primary_span] + pub(crate) multispan: MultiSpan, + pub(crate) name: Ident, +} - err.emit(); - } +#[derive(Subdiagnostic, Debug, Clone)] +#[label("pattern doesn't bind `{$name}`")] +pub(crate) struct PatternDoesntBindName { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Ident, +} - /// When a private field is being set that has a default field value, we suggest using `..` and - /// setting the value of that field implicitly with its default. - /// - /// If we encounter code like - /// ```text - /// struct Priv; - /// pub struct S { - /// pub field: Priv = Priv, - /// } - /// ``` - /// which is used from a place where `Priv` isn't accessible - /// ```text - /// let _ = S { field: m::Priv1 {} }; - /// // ^^^^^ private struct - /// ``` - /// we will suggest instead using the `default_field_values` syntax instead: - /// ```text - /// let _ = S { .. }; - /// ``` - fn mention_default_field_values( - &self, - source: &Option, - ident: Ident, - err: &mut Diag<'_>, - ) { - let Some(expr) = source else { return }; - let ast::ExprKind::Struct(struct_expr) = &expr.kind else { return }; - // We don't have to handle type-relative paths because they're forbidden in ADT - // expressions, but that would change with `#[feature(more_qualified_paths)]`. - let Some(segment) = struct_expr.path.segments.last() else { return }; - let Some(partial_res) = self.partial_res_map.get(&segment.id) else { return }; - let Some(Res::Def(_, def_id)) = partial_res.full_res() else { - return; - }; - let Some(default_fields) = self.field_defaults(def_id) else { return }; - if struct_expr.fields.is_empty() { - return; - } - let last_span = struct_expr.fields.iter().last().unwrap().span; - let mut iter = struct_expr.fields.iter().peekable(); - let mut prev: Option = None; - while let Some(field) = iter.next() { - if field.expr.span.overlaps(ident.span) { - err.span_label(field.ident.span, "while setting this field"); - if default_fields.contains(&field.ident.name) { - let sugg = if last_span == field.span { - vec![(field.span, "..".to_string())] - } else { - vec![ - ( - // Account for trailing commas and ensure we remove them. - match (prev, iter.peek()) { - (_, Some(next)) => field.span.with_hi(next.span.lo()), - (Some(prev), _) => field.span.with_lo(prev.hi()), - (None, None) => field.span, - }, - String::new(), - ), - (last_span.shrink_to_hi(), ", ..".to_string()), - ] - }; - err.multipart_suggestion( - format!( - "the type `{ident}` of field `{}` is private, but you can construct \ - the default value defined for it in `{}` using `..` in the struct \ - initializer expression", - field.ident, - self.tcx.item_name(def_id), - ), - sugg, - Applicability::MachineApplicable, - ); - break; - } - } - prev = Some(field.span); - } - } +#[derive(Subdiagnostic, Debug, Clone)] +#[label("variable not in all patterns")] +pub(crate) struct VariableNotInAllPatterns { + #[primary_span] + pub(crate) span: Span, +} - pub(crate) fn find_similarly_named_module_or_crate( - &self, - ident: Symbol, - current_module: Module<'ra>, - ) -> Option { - let mut candidates = self - .extern_prelude - .keys() - .map(|ident| ident.name) - .chain( - self.local_module_map - .iter() - .filter(|(_, module)| { - let module = module.to_module(); - current_module.is_ancestor_of(module) && current_module != module - }) - .flat_map(|(_, module)| module.name()), - ) - .chain( - self.extern_module_map - .borrow() - .iter() - .filter(|(_, module)| { - let module = module.to_module(); - current_module.is_ancestor_of(module) && current_module != module - }) - .flat_map(|(_, module)| module.name()), - ) - .filter(|c| !c.to_string().is_empty()) - .collect::>(); - candidates.sort(); - candidates.dedup(); - find_best_match_for_name(&candidates, ident, None).filter(|sugg| *sugg != ident) - } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "you might have meant to use the similarly named previously used binding `{$typo}`", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct PatternBindingTypo { + #[suggestion_part(code = "{typo}")] + pub(crate) spans: Vec, + pub(crate) typo: Symbol, +} - pub(crate) fn report_path_resolution_error( - &mut self, - path: &[Segment], - opt_ns: Option, // `None` indicates a module path in import - parent_scope: &ParentScope<'ra>, - ribs: Option<&PerNS>>>, - ignore_decl: Option>, - ignore_import: Option>, - module: Option>, - failed_segment_idx: usize, - ident: Ident, - diag_metadata: Option<&DiagMetadata<'_>>, - ) -> (String, String, Option) { - let is_last = failed_segment_idx == path.len() - 1; - let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; - let module_def_id = match module { - Some(ModuleOrUniformRoot::Module(module)) => module.opt_def_id(), - _ => None, - }; - let scope = match &path[..failed_segment_idx] { - [.., prev] => { - if prev.ident.name == kw::PathRoot { - format!("the crate root") - } else { - format!("`{}`", prev.ident) - } - } - _ => format!("this scope"), - }; - let message = format!("cannot find `{ident}` in {scope}"); - - if module_def_id == Some(CRATE_DEF_ID.to_def_id()) { - let is_mod = |res| matches!(res, Res::Def(DefKind::Mod, _)); - let mut candidates = self.lookup_import_candidates(ident, TypeNS, parent_scope, is_mod); - candidates - .sort_by_cached_key(|c| (c.path.segments.len(), pprust::path_to_string(&c.path))); - if let Some(candidate) = candidates.get(0) { - let path = { - // remove the possible common prefix of the path - let len = candidate.path.segments.len(); - let start_index = (0..=failed_segment_idx.min(len - 1)) - .find(|&i| path[i].ident.name != candidate.path.segments[i].ident.name) - .unwrap_or_default(); - let segments = - (start_index..len).map(|s| candidate.path.segments[s].clone()).collect(); - Path { segments, span: Span::default(), tokens: None } - }; - ( - message, - String::from("unresolved import"), - Some(( - vec![(ident.span, pprust::path_to_string(&path))], - String::from("a similar path exists"), - Applicability::MaybeIncorrect, - )), - ) - } else if ident.name == sym::core { - ( - message, - format!("you might be missing crate `{ident}`"), - Some(( - vec![(ident.span, "std".to_string())], - "try using `std` instead of `core`".to_string(), - Applicability::MaybeIncorrect, - )), - ) - } else if ident.name == kw::Underscore { - ( - "invalid crate or module name `_`".to_string(), - "`_` is not a valid crate or module name".to_string(), - None, - ) - } else if self.tcx.sess.is_rust_2015() { - ( - format!("cannot find module or crate `{ident}` in {scope}"), - format!("use of unresolved module or unlinked crate `{ident}`"), - Some(( - vec![( - self.current_crate_outer_attr_insert_span, - format!("extern crate {ident};\n"), - )], - if was_invoked_from_cargo() { - format!( - "if you wanted to use a crate named `{ident}`, use `cargo add \ - {ident}` to add it to your `Cargo.toml` and import it in your \ - code", - ) - } else { - format!( - "you might be missing a crate named `{ident}`, add it to your \ - project and import it in your code", - ) - }, - Applicability::MaybeIncorrect, - )), - ) - } else { - (message, format!("could not find `{ident}` in the crate root"), None) - } - } else if failed_segment_idx > 0 { - let parent = path[failed_segment_idx - 1].ident.name; - let parent = match parent { - // ::foo is mounted at the crate root for 2015, and is the extern - // prelude for 2018+ - kw::PathRoot if self.tcx.sess.edition() > Edition::Edition2015 => { - "the list of imported crates".to_owned() - } - kw::PathRoot | kw::Crate => "the crate root".to_owned(), - _ => format!("`{parent}`"), - }; - - let mut msg = format!("could not find `{ident}` in {parent}"); - if ns == TypeNS || ns == ValueNS { - let ns_to_try = if ns == TypeNS { ValueNS } else { TypeNS }; - let binding = if let Some(module) = module { - self.cm() - .resolve_ident_in_module( - module, - ident, - ns_to_try, - parent_scope, - None, - ignore_decl, - ignore_import, - ) - .ok() - } else if let Some(ribs) = ribs - && let Some(TypeNS | ValueNS) = opt_ns - { - assert!(ignore_import.is_none()); - match self.resolve_ident_in_lexical_scope( - ident, - ns_to_try, - parent_scope, - None, - &ribs[ns_to_try], - ignore_decl, - diag_metadata, - ) { - // we found a locally-imported or available item/module - Some(LateDecl::Decl(binding)) => Some(binding), - _ => None, - } - } else { - self.cm() - .resolve_ident_in_scope_set( - ident, - ScopeSet::All(ns_to_try), - parent_scope, - None, - ignore_decl, - ignore_import, - ) - .ok() - }; - if let Some(binding) = binding { - msg = format!( - "expected {}, found {} `{ident}` in {parent}", - ns.descr(), - binding.res().descr(), - ); - }; - } - (message, msg, None) - } else if ident.name == kw::SelfUpper { - // As mentioned above, `opt_ns` being `None` indicates a module path in import. - // We can use this to improve a confusing error for, e.g. `use Self::Variant` in an - // impl - if opt_ns.is_none() { - (message, "`Self` cannot be used in imports".to_string(), None) - } else { - ( - message, - "`Self` is only available in impls, traits, and type definitions".to_string(), - None, - ) - } - } else if ident.name.as_str().chars().next().is_some_and(|c| c.is_ascii_uppercase()) { - // Check whether the name refers to an item in the value namespace. - let binding = if let Some(ribs) = ribs { - assert!(ignore_import.is_none()); - self.resolve_ident_in_lexical_scope( - ident, - ValueNS, - parent_scope, - None, - &ribs[ValueNS], - ignore_decl, - diag_metadata, - ) - } else { - None - }; - let match_span = match binding { - // Name matches a local variable. For example: - // ``` - // fn f() { - // let Foo: &str = ""; - // println!("{}", Foo::Bar); // Name refers to local - // // variable `Foo`. - // } - // ``` - Some(LateDecl::RibDef(Res::Local(id))) => { - Some((*self.pat_span_map.get(&id).unwrap(), "a", "local binding")) - } - // Name matches item from a local name binding - // created by `use` declaration. For example: - // ``` - // pub const Foo: &str = ""; - // - // mod submod { - // use super::Foo; - // println!("{}", Foo::Bar); // Name refers to local - // // binding `Foo`. - // } - // ``` - Some(LateDecl::Decl(name_binding)) => Some(( - name_binding.span, - name_binding.res().article(), - name_binding.res().descr(), - )), - _ => None, - }; - - let message = format!("cannot find type `{ident}` in {scope}"); - let label = if let Some((span, article, descr)) = match_span { - format!( - "`{ident}` is declared as {article} {descr} at `{}`, not a type", - self.tcx - .sess - .source_map() - .span_to_short_string(span, RemapPathScopeComponents::DIAGNOSTICS) - ) - } else { - format!("use of undeclared type `{ident}`") - }; - (message, label, None) - } else { - let mut suggestion = None; - if ident.name == sym::alloc { - suggestion = Some(( - vec![], - String::from("add `extern crate alloc` to use the `alloc` crate"), - Applicability::MaybeIncorrect, - )) - } +#[derive(Diagnostic)] +#[diag("the name `{$name}` is defined multiple times")] +#[note("`{$name}` must be defined only once in the {$descr} namespace of this {$container}")] +pub(crate) struct NameDefinedMultipleTime { + #[primary_span] + pub(crate) span: Span, + pub(crate) name: Symbol, + pub(crate) descr: &'static str, + pub(crate) container: &'static str, + #[subdiagnostic] + pub(crate) label: NameDefinedMultipleTimeLabel, + #[subdiagnostic] + pub(crate) old_binding_label: Option, +} - suggestion = suggestion.or_else(|| { - self.find_similarly_named_module_or_crate(ident.name, parent_scope.module).map( - |sugg| { - ( - vec![(ident.span, sugg.to_string())], - String::from("there is a crate or module with a similar name"), - Applicability::MaybeIncorrect, - ) - }, - ) - }); - if let Ok(binding) = self.cm().resolve_ident_in_scope_set( - ident, - ScopeSet::All(ValueNS), - parent_scope, - None, - ignore_decl, - ignore_import, - ) { - let descr = binding.res().descr(); - let message = format!("cannot find module or crate `{ident}` in {scope}"); - (message, format!("{descr} `{ident}` is not a crate or module"), suggestion) - } else { - let suggestion = if suggestion.is_some() { - suggestion - } else if let Some(m) = self.undeclared_module_exists(ident) { - self.undeclared_module_suggest_declare(ident, m) - } else if was_invoked_from_cargo() { - Some(( - vec![], - format!( - "if you wanted to use a crate named `{ident}`, use `cargo add {ident}` \ - to add it to your `Cargo.toml`", - ), - Applicability::MaybeIncorrect, - )) - } else { - Some(( - vec![], - format!("you might be missing a crate named `{ident}`",), - Applicability::MaybeIncorrect, - )) - }; - let message = format!("cannot find module or crate `{ident}` in {scope}"); - ( - message, - format!("use of unresolved module or unlinked crate `{ident}`"), - suggestion, - ) - } - } - } +#[derive(Subdiagnostic)] +pub(crate) enum NameDefinedMultipleTimeLabel { + #[label("`{$name}` reimported here")] + Reimported { + #[primary_span] + span: Span, + name: Symbol, + }, + #[label("`{$name}` redefined here")] + Redefined { + #[primary_span] + span: Span, + name: Symbol, + }, +} - fn undeclared_module_suggest_declare( - &self, - ident: Ident, - path: std::path::PathBuf, - ) -> Option<(Vec<(Span, String)>, String, Applicability)> { - Some(( - vec![(self.current_crate_outer_attr_insert_span, format!("mod {ident};\n"))], - format!( - "to make use of source file {}, use `mod {ident}` \ - in this file to declare the module", - path.display() - ), - Applicability::MaybeIncorrect, - )) - } +#[derive(Subdiagnostic)] +pub(crate) enum NameDefinedMultipleTimeOldBindingLabel { + #[label("previous import of the {$old_kind} `{$name}` here")] + Import { + #[primary_span] + span: Span, + old_kind: &'static str, + name: Symbol, + }, + #[label("previous definition of the {$old_kind} `{$name}` here")] + Definition { + #[primary_span] + span: Span, + old_kind: &'static str, + name: Symbol, + }, +} - fn undeclared_module_exists(&self, ident: Ident) -> Option { - let map = self.tcx.sess.source_map(); - - let src = map.span_to_filename(ident.span).into_local_path()?; - let i = ident.as_str(); - // FIXME: add case where non parent using undeclared module (hard?) - let dir = src.parent()?; - let src = src.file_stem()?.to_str()?; - for file in [ - // …/x.rs - dir.join(i).with_extension("rs"), - // …/x/mod.rs - dir.join(i).join("mod.rs"), - ] { - if file.exists() { - return Some(file); - } - } - if !matches!(src, "main" | "lib" | "mod") { - for file in [ - // …/x/y.rs - dir.join(src).join(i).with_extension("rs"), - // …/x/y/mod.rs - dir.join(src).join(i).join("mod.rs"), - ] { - if file.exists() { - return Some(file); - } - } - } - None - } +#[derive(Diagnostic)] +#[diag("{$ident_descr} `{$ident}` is private", code = E0603)] +pub(crate) struct IsPrivate<'a> { + #[primary_span] + #[label("private {$ident_descr}")] + pub(crate) span: Span, + pub(crate) ident_descr: &'a str, + pub(crate) ident: Ident, +} - /// Adds suggestions for a path that cannot be resolved. - #[instrument(level = "debug", skip(self, parent_scope))] - pub(crate) fn make_path_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - match path[..] { - // `{{root}}::ident::...` on both editions. - // On 2015 `{{root}}` is usually added implicitly. - [first, second, ..] - if first.ident.name == kw::PathRoot && !second.ident.is_path_segment_keyword() => {} - // `ident::...` on 2018. - [first, ..] - if first.ident.span.at_least_rust_2018() - && !first.ident.is_path_segment_keyword() => - { - // Insert a placeholder that's later replaced by `self`/`super`/etc. - path.insert(0, Segment::from_ident(Ident::dummy())); - } - _ => return None, - } +#[derive(Diagnostic)] +#[diag("generic arguments in macro path")] +pub(crate) struct GenericArgumentsInMacroPath { + #[primary_span] + pub(crate) span: Span, +} - self.make_missing_self_suggestion(path.clone(), parent_scope) - .or_else(|| self.make_missing_crate_suggestion(path.clone(), parent_scope)) - .or_else(|| self.make_missing_super_suggestion(path.clone(), parent_scope)) - .or_else(|| self.make_external_crate_suggestion(path, parent_scope)) - } +#[derive(Diagnostic)] +#[diag("attributes starting with `rustc` are reserved for use by the `rustc` compiler")] +pub(crate) struct AttributesStartingWithRustcAreReserved { + #[primary_span] + pub(crate) span: Span, +} - /// Suggest a missing `self::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `self::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_self_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `self` and check if that is valid. - path[0].ident.name = kw::SelfLower; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { Some((path, None)) } else { None } - } +#[derive(Diagnostic)] +#[diag( + "attributes containing a segment starting with `rustc` are reserved for use by the `rustc` compiler" +)] +pub(crate) struct AttributesContainingRustcAreReserved { + #[primary_span] + pub(crate) span: Span, +} - /// Suggests a missing `crate::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `crate::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_crate_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `crate` and check if that is valid. - path[0].ident.name = kw::Crate; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { - Some(( - path, - Some( - "`use` statements changed in Rust 2018; read more at \ - " - .to_string(), - ), - )) - } else { - None - } - } +#[derive(Diagnostic)] +#[diag("cannot use {$article} {$descr} through an import")] +pub(crate) struct CannotUseThroughAnImport { + #[primary_span] + pub(crate) span: Span, + pub(crate) article: &'static str, + pub(crate) descr: &'static str, + #[note("the {$descr} imported here")] + pub(crate) binding_span: Option, +} - /// Suggests a missing `super::` if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foo::Bar; - /// | ^^^ did you mean `super::foo`? - /// ``` - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_missing_super_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - // Replace first ident with `crate` and check if that is valid. - path[0].ident.name = kw::Super; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?result); - if let PathResult::Module(..) = result { Some((path, None)) } else { None } - } +#[derive(Diagnostic)] +#[diag("name `{$ident}` is reserved in attribute namespace")] +pub(crate) struct NameReservedInAttributeNamespace { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Symbol, +} - /// Suggests a missing external crate name if that resolves to an correct module. - /// - /// ```text - /// | - /// LL | use foobar::Baz; - /// | ^^^^^^ did you mean `baz::foobar`? - /// ``` - /// - /// Used when importing a submodule of an external crate but missing that crate's - /// name as the first part of path. - #[instrument(level = "debug", skip(self, parent_scope))] - fn make_external_crate_suggestion( - &mut self, - mut path: Vec, - parent_scope: &ParentScope<'ra>, - ) -> Option<(Vec, Option)> { - if path[1].ident.span.is_rust_2015() { - return None; - } +#[derive(Diagnostic)] +#[diag("cannot find a built-in macro with name `{$ident}`")] +pub(crate) struct CannotFindBuiltinMacroWithName { + #[primary_span] + pub(crate) span: Span, + pub(crate) ident: Ident, +} - // Sort extern crate names in *reverse* order to get - // 1) some consistent ordering for emitted diagnostics, and - // 2) `std` suggestions before `core` suggestions. - let mut extern_crate_names = - self.extern_prelude.keys().map(|ident| ident.name).collect::>(); - extern_crate_names.sort_by(|a, b| b.as_str().cmp(a.as_str())); - - for name in extern_crate_names.into_iter() { - // Replace first ident with a crate name and check if that is valid. - path[0].ident.name = name; - let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); - debug!(?path, ?name, ?result); - if let PathResult::Module(..) = result { - return Some((path, None)); - } - } +#[derive(Diagnostic)] +#[diag("tool `{$tool}` was already registered")] +pub(crate) struct ToolWasAlreadyRegistered { + #[primary_span] + pub(crate) span: Span, + pub(crate) tool: Ident, + #[label("already registered here")] + pub(crate) old_ident_span: Span, +} - None - } +#[derive(Subdiagnostic)] +pub(crate) enum DefinedHere { + #[label("similarly named {$candidate_descr} `{$candidate}` defined here")] + SimilarlyNamed { + #[primary_span] + span: Span, + candidate_descr: &'static str, + candidate: Symbol, + }, + #[label("{$candidate_descr} `{$candidate}` defined here")] + SingleItem { + #[primary_span] + span: Span, + candidate_descr: &'static str, + candidate: Symbol, + }, +} - /// Suggests importing a macro from the root of the crate rather than a module within - /// the crate. - /// - /// ```text - /// help: a macro with this name exists at the root of the crate - /// | - /// LL | use issue_59764::makro; - /// | ^^^^^^^^^^^^^^^^^^ - /// | - /// = note: this could be because a macro annotated with `#[macro_export]` will be exported - /// at the root of the crate instead of the module where it is defined - /// ``` - pub(crate) fn check_for_module_export_macro( - &mut self, - import: Import<'ra>, - module: ModuleOrUniformRoot<'ra>, - ident: Ident, - ) -> Option<(Option, Option)> { - let ModuleOrUniformRoot::Module(mut crate_module) = module else { - return None; - }; +#[derive(Subdiagnostic)] +#[label("{$outer_ident_descr} `{$outer_ident}` is not publicly re-exported")] +pub(crate) struct OuterIdentIsNotPubliclyReexported { + #[primary_span] + pub(crate) span: Span, + pub(crate) outer_ident_descr: &'static str, + pub(crate) outer_ident: Ident, +} - while let Some(parent) = crate_module.parent { - crate_module = parent; - } +#[derive(Subdiagnostic)] +#[label("a constructor is private if any of the fields is private")] +pub(crate) struct ConstructorPrivateIfAnyFieldPrivate { + #[primary_span] + pub(crate) span: Span, +} - if module == ModuleOrUniformRoot::Module(crate_module) { - // Don't make a suggestion if the import was already from the root of the crate. - return None; - } +#[derive(Subdiagnostic)] +#[multipart_suggestion( + "{ $number_of_fields -> + [one] consider making the field publicly accessible + *[other] consider making the fields publicly accessible + }", + applicability = "maybe-incorrect", + style = "verbose" +)] +pub(crate) struct ConsiderMakingTheFieldPublic { + #[suggestion_part(code = "pub ")] + pub(crate) spans: Vec, + pub(crate) number_of_fields: usize, +} - let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); - let binding = self.resolution(crate_module, binding_key)?.best_decl()?; - let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { - return None; - }; - if !kinds.contains(MacroKinds::BANG) { - return None; - } - let module_name = crate_module.name().unwrap_or(kw::Crate); - let import_snippet = match import.kind { - ImportKind::Single { source, target, .. } if source != target => { - format!("{source} as {target}") - } - _ => format!("{ident}"), - }; - - let mut corrections: Vec<(Span, String)> = Vec::new(); - if !import.is_nested() { - // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove - // intermediate segments. - corrections.push((import.span, format!("{module_name}::{import_snippet}"))); - } else { - // Find the binding span (and any trailing commas and spaces). - // i.e. `use a::b::{c, d, e};` - // ^^^ - let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding( - self.tcx.sess, - import.span, - import.use_span, - ); - debug!(found_closing_brace, ?binding_span); - - let mut removal_span = binding_span; - - // If the binding span ended with a closing brace, as in the below example: - // i.e. `use a::b::{c, d};` - // ^ - // Then expand the span of characters to remove to include the previous - // binding's trailing comma. - // i.e. `use a::b::{c, d};` - // ^^^ - if found_closing_brace - && let Some(previous_span) = - extend_span_to_previous_binding(self.tcx.sess, binding_span) - { - debug!(?previous_span); - removal_span = removal_span.with_lo(previous_span.lo()); - } - debug!(?removal_span); - - // Remove the `removal_span`. - corrections.push((removal_span, "".to_string())); - - // Find the span after the crate name and if it has nested imports immediately - // after the crate name already. - // i.e. `use a::b::{c, d};` - // ^^^^^^^^^ - // or `use a::{b, c, d}};` - // ^^^^^^^^^^^ - let (has_nested, after_crate_name) = - find_span_immediately_after_crate_name(self.tcx.sess, import.use_span); - debug!(has_nested, ?after_crate_name); - - let source_map = self.tcx.sess.source_map(); - - // Make sure this is actually crate-relative. - let is_definitely_crate = import - .module_path - .first() - .is_some_and(|f| f.ident.name != kw::SelfLower && f.ident.name != kw::Super); - - // Add the import to the start, with a `{` if required. - let start_point = source_map.start_point(after_crate_name); - if is_definitely_crate - && let Ok(start_snippet) = source_map.span_to_snippet(start_point) - { - corrections.push(( - start_point, - if has_nested { - // In this case, `start_snippet` must equal '{'. - format!("{start_snippet}{import_snippet}, ") - } else { - // In this case, add a `{`, then the moved import, then whatever - // was there before. - format!("{{{import_snippet}, {start_snippet}") - }, - )); - - // Add a `};` to the end if nested, matching the `{` added at the start. - if !has_nested { - corrections.push((source_map.end_point(after_crate_name), "};".to_string())); - } - } else { - // If the root import is module-relative, add the import separately - corrections.push(( - import.use_span.shrink_to_lo(), - format!("use {module_name}::{import_snippet};\n"), - )); - } - } +#[derive(Subdiagnostic)] +pub(crate) enum ImportIdent { + #[suggestion( + "import `{$ident}` through the re-export", + code = "{path}", + applicability = "machine-applicable", + style = "verbose" + )] + ThroughReExport { + #[primary_span] + span: Span, + ident: Ident, + path: String, + }, + #[suggestion( + "import `{$ident}` directly", + code = "{path}", + applicability = "machine-applicable", + style = "verbose" + )] + Directly { + #[primary_span] + span: Span, + ident: Ident, + path: String, + }, +} - let suggestion = Some(( - corrections, - String::from("a macro with this name exists at the root of the crate"), - Applicability::MaybeIncorrect, - )); - Some(( - suggestion, - Some( - "this could be because a macro annotated with `#[macro_export]` will be exported \ - at the root of the crate instead of the module where it is defined" - .to_string(), - ), - )) - } +#[derive(Subdiagnostic)] +#[note( + "{$first -> + [true] {$dots -> + [true] the {$binding_descr} `{$binding_name}` is defined here... + *[false] the {$binding_descr} `{$binding_name}` is defined here + } + *[false] {$dots -> + [true] ...and refers to the {$binding_descr} `{$binding_name}` which is defined here... + *[false] ...and refers to the {$binding_descr} `{$binding_name}` which is defined here + } + }" +)] +pub(crate) struct NoteAndRefersToTheItemDefinedHere<'a> { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) binding_descr: &'a str, + pub(crate) binding_name: Ident, + pub(crate) first: bool, + pub(crate) dots: bool, +} - /// Finds a cfg-ed out item inside `module` with the matching name. - pub(crate) fn find_cfg_stripped(&self, err: &mut Diag<'_>, segment: &Symbol, module: DefId) { - let local_items; - let symbols = if module.is_local() { - local_items = self - .stripped_cfg_items - .iter() - .filter_map(|item| { - let parent_scope = self.local_modules.iter().find_map(|m| match m.kind { - ModuleKind::Def(_, def_id, node_id, _) if node_id == item.parent_scope => { - Some(def_id) - } - _ => None, - })?; - Some(StrippedCfgItem { parent_scope, ident: item.ident, cfg: item.cfg.clone() }) - }) - .collect::>(); - local_items.as_slice() - } else { - self.tcx.stripped_cfg_items(module.krate) - }; +#[derive(Subdiagnostic)] +#[suggestion("remove unnecessary import", code = "", applicability = "maybe-incorrect")] +pub(crate) struct RemoveUnnecessaryImport { + #[primary_span] + pub(crate) span: Span, +} - for &StrippedCfgItem { parent_scope, ident, ref cfg } in symbols { - if ident.name != *segment { - continue; - } +#[derive(Subdiagnostic)] +#[suggestion( + "remove unnecessary import", + code = "", + applicability = "maybe-incorrect", + style = "tool-only" +)] +pub(crate) struct ToolOnlyRemoveUnnecessaryImport { + #[primary_span] + pub(crate) span: Span, +} - let parent_module = self.get_nearest_non_block_module(parent_scope).def_id(); - - fn comes_from_same_module_for_glob( - r: &Resolver<'_, '_>, - parent_module: DefId, - module: DefId, - visited: &mut FxHashMap, - ) -> bool { - if let Some(&cached) = visited.get(&parent_module) { - // this branch is prevent from being called recursively infinity, - // because there has some cycles in globs imports, - // see more spec case at `tests/ui/cfg/diagnostics-reexport-2.rs#reexport32` - return cached; - } - visited.insert(parent_module, false); - let mut res = false; - let m = r.expect_module(parent_module); - if m.is_local() { - for importer in m.glob_importers.borrow().iter() { - if let Some(next_parent_module) = importer.parent_scope.module.opt_def_id() - { - if next_parent_module == module - || comes_from_same_module_for_glob( - r, - next_parent_module, - module, - visited, - ) - { - res = true; - break; - } - } - } - } - visited.insert(parent_module, res); - res - } +#[derive(Subdiagnostic)] +#[note("`{$imported_ident}` is imported here, but it is {$imported_ident_desc}")] +pub(crate) struct IdentImporterHereButItIsDesc<'a> { + #[primary_span] + pub(crate) span: Span, + pub(crate) imported_ident: Ident, + pub(crate) imported_ident_desc: &'a str, +} - let comes_from_same_module = parent_module == module - || comes_from_same_module_for_glob( - self, - parent_module, - module, - &mut Default::default(), - ); - if !comes_from_same_module { - continue; - } +#[derive(Subdiagnostic)] +#[note("`{$imported_ident}` is in scope, but it is {$imported_ident_desc}")] +pub(crate) struct IdentInScopeButItIsDesc<'a> { + pub(crate) imported_ident: Ident, + pub(crate) imported_ident_desc: &'a str, +} - let item_was = if let CfgEntry::NameValue { value: Some(feature), .. } = cfg.0 { - errors::ItemWas::BehindFeature { feature, span: cfg.1 } - } else { - errors::ItemWas::CfgOut { span: cfg.1 } - }; - let note = errors::FoundItemConfigureOut { span: ident.span, item_was }; - err.subdiagnostic(note); - } - } +pub(crate) struct FoundItemConfigureOut { + pub(crate) span: Span, + pub(crate) item_was: ItemWas, +} + +pub(crate) enum ItemWas { + BehindFeature { feature: Symbol, span: Span }, + CfgOut { span: Span }, +} - pub(crate) fn struct_ctor(&self, def_id: DefId) -> Option { - match def_id.as_local() { - Some(def_id) => self.struct_ctors.get(&def_id).cloned(), - None => { - self.cstore().ctor_untracked(self.tcx, def_id).map(|(ctor_kind, ctor_def_id)| { - let res = Res::Def(DefKind::Ctor(CtorOf::Struct, ctor_kind), ctor_def_id); - let vis = self.tcx.visibility(ctor_def_id); - let field_visibilities = self - .tcx - .associated_item_def_ids(def_id) - .iter() - .map(|&field_id| self.tcx.visibility(field_id)) - .collect(); - StructCtor { res, vis, field_visibilities } - }) +impl Subdiagnostic for FoundItemConfigureOut { + fn add_to_diag(self, diag: &mut Diag<'_, G>) { + let mut multispan: MultiSpan = self.span.into(); + match self.item_was { + ItemWas::BehindFeature { feature, span } => { + let value = feature.into_diag_arg(&mut None); + let msg = msg!("the item is gated behind the `{$feature}` feature") + .arg("feature", value) + .format(); + multispan.push_span_label(span, msg); + } + ItemWas::CfgOut { span } => { + multispan.push_span_label(span, msg!("the item is gated here")); } } + diag.span_note(multispan, msg!("found an item that was configured out")); } } -/// Given a `binding_span` of a binding within a use statement: -/// -/// ```ignore (illustrative) -/// use foo::{a, b, c}; -/// // ^ -/// ``` -/// -/// then return the span until the next binding or the end of the statement: -/// -/// ```ignore (illustrative) -/// use foo::{a, b, c}; -/// // ^^^ -/// ``` -fn find_span_of_binding_until_next_binding( - sess: &Session, - binding_span: Span, - use_span: Span, -) -> (bool, Span) { - let source_map = sess.source_map(); - - // Find the span of everything after the binding. - // i.e. `a, e};` or `a};` - let binding_until_end = binding_span.with_hi(use_span.hi()); - - // Find everything after the binding but not including the binding. - // i.e. `, e};` or `};` - let after_binding_until_end = binding_until_end.with_lo(binding_span.hi()); - - // Keep characters in the span until we encounter something that isn't a comma or - // whitespace. - // i.e. `, ` or ``. - // - // Also note whether a closing brace character was encountered. If there - // was, then later go backwards to remove any trailing commas that are left. - let mut found_closing_brace = false; - let after_binding_until_next_binding = - source_map.span_take_while(after_binding_until_end, |&ch| { - if ch == '}' { - found_closing_brace = true; - } - ch == ' ' || ch == ',' - }); - - // Combine the two spans. - // i.e. `a, ` or `a`. - // - // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };` - let span = binding_span.with_hi(after_binding_until_next_binding.hi()); - - (found_closing_brace, span) -} - -/// Given a `binding_span`, return the span through to the comma or opening brace of the previous -/// binding. -/// -/// ```ignore (illustrative) -/// use foo::a::{a, b, c}; -/// // ^^--- binding span -/// // | -/// // returned span -/// -/// use foo::{a, b, c}; -/// // --- binding span -/// ``` -fn extend_span_to_previous_binding(sess: &Session, binding_span: Span) -> Option { - let source_map = sess.source_map(); - - // `prev_source` will contain all of the source that came before the span. - // Then split based on a command and take the first (i.e. closest to our span) - // snippet. In the example, this is a space. - let prev_source = source_map.span_to_prev_source(binding_span).ok()?; - - let prev_comma = prev_source.rsplit(',').collect::>(); - let prev_starting_brace = prev_source.rsplit('{').collect::>(); - if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 { - return None; - } +#[derive(Diagnostic)] +#[diag("item `{$name}` is an associated {$kind}, which doesn't match its trait `{$trait_path}`")] +pub(crate) struct TraitImplMismatch { + #[primary_span] + #[label("does not match trait")] + pub(crate) span: Span, + pub(crate) name: Ident, + pub(crate) kind: &'static str, + pub(crate) trait_path: String, + #[label("item in trait")] + pub(crate) trait_item_span: Span, +} - let prev_comma = prev_comma.first().unwrap(); - let prev_starting_brace = prev_starting_brace.first().unwrap(); +#[derive(Diagnostic)] +#[diag("derive helper attribute is used before it is introduced")] +pub(crate) struct LegacyDeriveHelpers { + #[label("the attribute is introduced here")] + pub span: Span, +} - // If the amount of source code before the comma is greater than - // the amount of source code before the starting brace then we've only - // got one item in the nested item (eg. `issue_52891::{self}`). - if prev_comma.len() > prev_starting_brace.len() { - return None; - } +#[derive(Diagnostic)] +#[diag("unused extern crate")] +pub(crate) struct UnusedExternCrate { + #[label("unused")] + pub span: Span, + #[suggestion( + "remove the unused `extern crate`", + code = "", + applicability = "machine-applicable", + style = "verbose" + )] + pub removal_span: Span, +} - Some(binding_span.with_lo(BytePos( - // Take away the number of bytes for the characters we've found and an - // extra for the comma. - binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1, - ))) -} - -/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if -/// it is a nested use tree. -/// -/// ```ignore (illustrative) -/// use foo::a::{b, c}; -/// // ^^^^^^^^^^ -- false -/// -/// use foo::{a, b, c}; -/// // ^^^^^^^^^^ -- true -/// -/// use foo::{a, b::{c, d}}; -/// // ^^^^^^^^^^^^^^^ -- true -/// ``` -#[instrument(level = "debug", skip(sess))] -fn find_span_immediately_after_crate_name(sess: &Session, use_span: Span) -> (bool, Span) { - let source_map = sess.source_map(); - - // Using `use issue_59764::foo::{baz, makro};` as an example throughout.. - let mut num_colons = 0; - // Find second colon.. `use issue_59764:` - let until_second_colon = source_map.span_take_while(use_span, |c| { - if *c == ':' { - num_colons += 1; - } - !matches!(c, ':' if num_colons == 2) - }); - // Find everything after the second colon.. `foo::{baz, makro};` - let from_second_colon = use_span.with_lo(until_second_colon.hi() + BytePos(1)); - - let mut found_a_non_whitespace_character = false; - // Find the first non-whitespace character in `from_second_colon`.. `f` - let after_second_colon = source_map.span_take_while(from_second_colon, |c| { - if found_a_non_whitespace_character { - return false; - } - if !c.is_whitespace() { - found_a_non_whitespace_character = true; - } - true - }); +#[derive(Diagnostic)] +#[diag("{$kind} `{$name}` from private dependency '{$krate}' is re-exported")] +pub(crate) struct ReexportPrivateDependency { + pub name: Symbol, + pub kind: &'static str, + pub krate: Symbol, +} + +#[derive(Diagnostic)] +#[diag("unused label")] +pub(crate) struct UnusedLabel; - // Find the first `{` in from_second_colon.. `foo::{` - let next_left_bracket = source_map.span_through_char(from_second_colon, '{'); +#[derive(Diagnostic)] +#[diag("unused `#[macro_use]` import")] +pub(crate) struct UnusedMacroUse; - (next_left_bracket == after_second_colon, from_second_colon) +#[derive(Diagnostic)] +#[diag("applying the `#[macro_use]` attribute to an `extern crate` item is deprecated")] +#[help("remove it and import macros at use sites with a `use` item instead")] +pub(crate) struct MacroUseDeprecated; + +#[derive(Diagnostic)] +#[diag("macro `{$ident}` is private")] +pub(crate) struct MacroIsPrivate { + pub ident: Ident, } -/// A suggestion has already been emitted, change the wording slightly to clarify that both are -/// independent options. -enum Instead { - Yes, - No, +#[derive(Diagnostic)] +#[diag("unused macro definition: `{$name}`")] +pub(crate) struct UnusedMacroDefinition { + pub name: Symbol, } -/// Whether an existing place with an `use` item was found. -enum FoundUse { - Yes, - No, +#[derive(Diagnostic)] +#[diag("rule #{$n} of macro `{$name}` is never used")] +pub(crate) struct MacroRuleNeverUsed { + pub n: usize, + pub name: Symbol, } -/// Whether a binding is part of a pattern or a use statement. Used for diagnostics. -pub(crate) enum DiagMode { - Normal, - /// The binding is part of a pattern - Pattern, - /// The binding is part of a use statement - Import { - /// `true` means diagnostics is for unresolved import - unresolved_import: bool, - /// `true` mean add the tips afterward for case `use a::{b,c}`, - /// rather than replacing within. - append: bool, +#[derive(Diagnostic)] +#[diag("`extern crate` is not idiomatic in the new edition")] +pub(crate) struct ExternCrateNotIdiomatic { + #[suggestion( + "convert it to a `use`", + style = "verbose", + code = "{code}", + applicability = "machine-applicable" + )] + pub span: Span, + pub code: &'static str, +} + +#[derive(Diagnostic)] +#[diag("cannot find macro `{$path}` in the current scope when looking from {$location}")] +#[help("import `macro_rules` with `use` to make it callable above its definition")] +pub(crate) struct OutOfScopeMacroCalls { + #[label("not found from {$location}")] + pub span: Span, + pub path: String, + pub location: String, +} + +#[derive(Diagnostic)] +#[diag( + "glob import doesn't reexport anything with visibility `{$import_vis}` because no imported item is public enough" +)] +pub(crate) struct RedundantImportVisibility { + #[note("the most public imported item is `{$max_vis}`")] + pub span: Span, + #[help("reduce the glob import's visibility or increase visibility of imported items")] + pub help: (), + pub import_vis: String, + pub max_vis: String, +} + +#[derive(Diagnostic)] +#[diag("unknown diagnostic attribute")] +pub(crate) struct UnknownDiagnosticAttribute { + #[subdiagnostic] + pub help: Option, +} + +#[derive(Subdiagnostic)] +pub(crate) enum UnknownDiagnosticAttributeHelp { + #[suggestion( + "an attribute with a similar name exists", + style = "verbose", + code = "{typo_name}", + applicability = "machine-applicable" + )] + Typo { + #[primary_span] + span: Span, + typo_name: Symbol, }, + #[help("add `#![feature({$feature})]` to the crate attributes to enable")] + UseFeature { feature: Symbol }, } -pub(crate) fn import_candidates( - tcx: TyCtxt<'_>, - err: &mut Diag<'_>, - // This is `None` if all placement locations are inside expansions - use_placement_span: Option, - candidates: &[ImportSuggestion], - mode: DiagMode, - append: &str, -) { - show_candidates( - tcx, - err, - use_placement_span, - candidates, - Instead::Yes, - FoundUse::Yes, - mode, - vec![], - append, - ); -} - -type PathString<'a> = (String, &'a str, Option, &'a Option, bool); - -/// When an entity with a given name is not available in scope, we search for -/// entities with that name in all crates. This method allows outputting the -/// results of this search in a programmer-friendly way. If any entities are -/// found and suggested, returns `true`, otherwise returns `false`. -fn show_candidates( - tcx: TyCtxt<'_>, - err: &mut Diag<'_>, - // This is `None` if all placement locations are inside expansions - use_placement_span: Option, - candidates: &[ImportSuggestion], - instead: Instead, - found_use: FoundUse, - mode: DiagMode, - path: Vec, - append: &str, -) -> bool { - if candidates.is_empty() { - return false; - } +// FIXME: Make this properly translatable. +pub(crate) struct Ambiguity { + pub ident: Ident, + pub ambig_vis: Option, + pub kind: &'static str, + pub help: Option<&'static [&'static str]>, + pub b1_note: Spanned, + pub b1_help_msgs: Vec, + pub b2_note: Spanned, + pub b2_help_msgs: Vec, + /// If false, then it's a lint, if true, then it's an error with the `E0659` error code. + pub is_error: bool, +} - let mut showed = false; - let mut accessible_path_strings: Vec> = Vec::new(); - let mut inaccessible_path_strings: Vec> = Vec::new(); - - candidates.iter().for_each(|c| { - if c.accessible { - // Don't suggest `#[doc(hidden)]` items from other crates - if c.doc_visible { - accessible_path_strings.push(( - pprust::path_to_string(&c.path), - c.descr, - c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), - &c.note, - c.via_import, - )) - } - } else { - inaccessible_path_strings.push(( - pprust::path_to_string(&c.path), - c.descr, - c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), - &c.note, - c.via_import, - )) +impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for Ambiguity { + fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { + let Self { + ident, + ambig_vis, + kind, + help, + b1_note, + b1_help_msgs, + b2_note, + b2_help_msgs, + is_error, + } = self; + + let mut diag = Diag::new(dcx, level, "").with_span(ident.span); + if is_error { + diag.code(E0659); } - }); - - // we want consistent results across executions, but candidates are produced - // by iterating through a hash map, so make sure they are ordered: - for path_strings in [&mut accessible_path_strings, &mut inaccessible_path_strings] { - path_strings.sort_by(|a, b| a.0.cmp(&b.0)); - path_strings.dedup_by(|a, b| a.0 == b.0); - let core_path_strings = - path_strings.extract_if(.., |p| p.0.starts_with("core::")).collect::>(); - let std_path_strings = - path_strings.extract_if(.., |p| p.0.starts_with("std::")).collect::>(); - let foreign_crate_path_strings = - path_strings.extract_if(.., |p| !p.0.starts_with("crate::")).collect::>(); - - // We list the `crate` local paths first. - // Then we list the `std`/`core` paths. - if std_path_strings.len() == core_path_strings.len() { - // Do not list `core::` paths if we are already listing the `std::` ones. - path_strings.extend(std_path_strings); + if let Some(ambig_vis) = ambig_vis { + diag.primary_message(format!("ambiguous import visibility: {ambig_vis}")); } else { - path_strings.extend(std_path_strings); - path_strings.extend(core_path_strings); + diag.primary_message(format!("`{}` is ambiguous", ident)); + diag.span_label(ident.span, "ambiguous name"); } - // List all paths from foreign crates last. - path_strings.extend(foreign_crate_path_strings); + diag.note(format!("ambiguous because of {}", kind)); + diag.span_note(b1_note.span, b1_note.node); + if let Some(help) = help { + for help in help { + diag.help(*help); + } + } + for help_msg in b1_help_msgs { + diag.help(help_msg); + } + diag.span_note(b2_note.span, b2_note.node); + for help_msg in b2_help_msgs { + diag.help(help_msg); + } + diag } +} - if !accessible_path_strings.is_empty() { - let (determiner, kind, s, name, through) = - if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] { - ( - "this", - *descr, - "", - format!(" `{name}`"), - if *via_import { " through its public re-export" } else { "" }, - ) - } else { - // Get the unique item kinds and if there's only one, we use the right kind name - // instead of the more generic "items". - let kinds = accessible_path_strings - .iter() - .map(|(_, descr, _, _, _)| *descr) - .collect::>(); - let kind = if let Some(kind) = kinds.get_only() { kind } else { "item" }; - let s = if kind.ends_with('s') { "es" } else { "s" }; - - ("one of these", kind, s, String::new(), "") - }; - - let instead = if let Instead::Yes = instead { " instead" } else { "" }; - let mut msg = if let DiagMode::Pattern = mode { - format!( - "if you meant to match on {kind}{s}{instead}{name}, use the full path in the \ - pattern", - ) - } else { - format!("consider importing {determiner} {kind}{s}{through}{instead}") - }; +#[derive(Diagnostic)] +#[diag("lifetime parameter `{$ident}` never used")] +pub(crate) struct UnusedLifetime { + #[suggestion("elide the unused lifetime", code = "", applicability = "machine-applicable")] + pub deletion_span: Option, - for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { - err.note(note.clone()); - } + pub ident: Ident, +} - let append_candidates = |msg: &mut String, accessible_path_strings: Vec>| { - msg.push(':'); +#[derive(Diagnostic)] +#[diag("ambiguous glob re-exports")] +pub(crate) struct AmbiguousGlobReexports { + #[label("the name `{$name}` in the {$namespace} namespace is first re-exported here")] + pub first_reexport: Span, + #[label("but the name `{$name}` in the {$namespace} namespace is also re-exported here")] + pub duplicate_reexport: Span, - for candidate in accessible_path_strings { - msg.push('\n'); - msg.push_str(&candidate.0); - } - }; - - if let Some(span) = use_placement_span { - let (add_use, trailing) = match mode { - DiagMode::Pattern => { - err.span_suggestions( - span, - msg, - accessible_path_strings.into_iter().map(|a| a.0), - Applicability::MaybeIncorrect, - ); - return true; - } - DiagMode::Import { .. } => ("", ""), - DiagMode::Normal => ("use ", ";\n"), - }; - for candidate in &mut accessible_path_strings { - // produce an additional newline to separate the new use statement - // from the directly following item. - let additional_newline = if let FoundUse::No = found_use - && let DiagMode::Normal = mode - { - "\n" - } else { - "" - }; - candidate.0 = - format!("{add_use}{}{append}{trailing}{additional_newline}", candidate.0); - } + pub name: String, + pub namespace: String, +} - match mode { - DiagMode::Import { append: true, .. } => { - append_candidates(&mut msg, accessible_path_strings); - err.span_help(span, msg); - } - _ => { - err.span_suggestions_with_style( - span, - msg, - accessible_path_strings.into_iter().map(|a| a.0), - Applicability::MaybeIncorrect, - SuggestionStyle::ShowAlways, - ); - } - } +#[derive(Diagnostic)] +#[diag("private item shadows public glob re-export")] +pub(crate) struct HiddenGlobReexports { + #[note( + "the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here" + )] + pub glob_reexport: Span, + #[note("but the private item here shadows it")] + pub private_item: Span, + + pub name: String, + pub namespace: String, +} - if let [first, .., last] = &path[..] { - let sp = first.ident.span.until(last.ident.span); - // Our suggestion is empty, so make sure the span is not empty (or we'd ICE). - // Can happen for derive-generated spans. - if sp.can_be_used_for_suggestions() && !sp.is_empty() { - err.span_suggestion_verbose( - sp, - format!("if you import `{}`, refer to it directly", last.ident), - "", - Applicability::Unspecified, - ); - } - } - } else { - append_candidates(&mut msg, accessible_path_strings); - err.help(msg); - } - showed = true; - } - if !inaccessible_path_strings.is_empty() - && (!matches!(mode, DiagMode::Import { unresolved_import: false, .. })) - { - let prefix = - if let DiagMode::Pattern = mode { "you might have meant to match on " } else { "" }; - if let [(name, descr, source_span, note, _)] = &inaccessible_path_strings[..] { - let msg = format!( - "{prefix}{descr} `{name}`{} exists but is inaccessible", - if let DiagMode::Pattern = mode { ", which" } else { "" } - ); - - if let Some(source_span) = source_span { - let span = tcx.sess.source_map().guess_head_span(*source_span); - let mut multi_span = MultiSpan::from_span(span); - multi_span.push_span_label(span, "not accessible"); - err.span_note(multi_span, msg); - } else { - err.note(msg); - } - if let Some(note) = (*note).as_deref() { - err.note(note.to_string()); - } - } else { - let descr = inaccessible_path_strings - .iter() - .map(|&(_, descr, _, _, _)| descr) - .all_equal_value() - .unwrap_or("item"); - let plural_descr = - if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") }; - - let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible"); - let mut has_colon = false; - - let mut spans = Vec::new(); - for (name, _, source_span, _, _) in &inaccessible_path_strings { - if let Some(source_span) = source_span { - let span = tcx.sess.source_map().guess_head_span(*source_span); - spans.push((name, span)); - } else { - if !has_colon { - msg.push(':'); - has_colon = true; - } - msg.push('\n'); - msg.push_str(name); - } - } +#[derive(Diagnostic)] +#[diag("the item `{$ident}` is imported redundantly")] +pub(crate) struct RedundantImport { + #[subdiagnostic] + pub subs: Vec, + pub ident: Ident, +} - let mut multi_span = MultiSpan::from_spans(spans.iter().map(|(_, sp)| *sp).collect()); - for (name, span) in spans { - multi_span.push_span_label(span, format!("`{name}`: not accessible")); - } +#[derive(Subdiagnostic)] +pub(crate) enum RedundantImportSub { + #[label("the item `{$ident}` is already imported here")] + ImportedHere { + #[primary_span] + span: Span, + ident: Ident, + }, + #[label("the item `{$ident}` is already defined here")] + DefinedHere { + #[primary_span] + span: Span, + ident: Ident, + }, + #[label("the item `{$ident}` is already imported by the extern prelude")] + ImportedPrelude { + #[primary_span] + span: Span, + ident: Ident, + }, + #[label("the item `{$ident}` is already defined by the extern prelude")] + DefinedPrelude { + #[primary_span] + span: Span, + ident: Ident, + }, +} - for note in inaccessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { - err.note(note.clone()); - } +#[derive(Diagnostic)] +#[diag("unnecessary qualification")] +pub(crate) struct UnusedQualifications { + #[suggestion( + "remove the unnecessary path segments", + style = "verbose", + code = "", + applicability = "machine-applicable" + )] + pub removal_span: Span, +} - err.span_note(multi_span, msg); - } - showed = true; - } - showed +#[derive(Diagnostic)] +#[diag( + "{$elided -> + [true] `&` without an explicit lifetime name cannot be used here + *[false] `'_` cannot be used here + }" +)] +pub(crate) struct AssociatedConstElidedLifetime { + #[suggestion( + "use the `'static` lifetime", + style = "verbose", + code = "{code}", + applicability = "machine-applicable" + )] + pub span: Span, + + pub code: &'static str, + pub elided: bool, + #[note("cannot automatically infer `'static` because of other lifetimes in scope")] + pub lifetimes_in_scope: MultiSpan, } -#[derive(Debug)] -struct UsePlacementFinder { - target_module: NodeId, - first_legal_span: Option, - first_use_span: Option, -} - -impl UsePlacementFinder { - fn check(krate: &Crate, target_module: NodeId) -> (Option, FoundUse) { - let mut finder = - UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None }; - finder.visit_crate(krate); - if let Some(use_span) = finder.first_use_span { - (Some(use_span), FoundUse::Yes) - } else { - (finder.first_legal_span, FoundUse::No) - } - } +#[derive(Diagnostic)] +#[diag("lifetime parameter `{$ident}` only used once")] +pub(crate) struct SingleUseLifetime { + #[label("this lifetime...")] + pub param_span: Span, + #[label("...is used only here")] + pub use_span: Span, + #[subdiagnostic] + pub suggestion: Option, + + pub ident: Ident, } -impl<'tcx> Visitor<'tcx> for UsePlacementFinder { - fn visit_crate(&mut self, c: &Crate) { - if self.target_module == CRATE_NODE_ID { - let inject = c.spans.inject_use_span; - if is_span_suitable_for_use_injection(inject) { - self.first_legal_span = Some(inject); - } - self.first_use_span = search_for_any_use_in_items(&c.items); - } else { - visit::walk_crate(self, c); - } - } +#[derive(Subdiagnostic)] +#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] +pub(crate) struct SingleUseLifetimeSugg { + #[suggestion_part(code = "")] + pub deletion_span: Option, + #[suggestion_part(code = "{replace_lt}")] + pub use_span: Span, - fn visit_item(&mut self, item: &'tcx ast::Item) { - if self.target_module == item.id { - if let ItemKind::Mod(_, _, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind { - let inject = mod_spans.inject_use_span; - if is_span_suitable_for_use_injection(inject) { - self.first_legal_span = Some(inject); - } - self.first_use_span = search_for_any_use_in_items(items); - } - } else { - visit::walk_item(self, item); - } - } + pub replace_lt: String, } -#[derive(Default)] -struct BindingVisitor { - identifiers: Vec, - spans: FxHashMap>, +#[derive(Diagnostic)] +#[diag( + "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" +)] +pub(crate) struct AbsPathWithModule { + #[subdiagnostic] + pub sugg: AbsPathWithModuleSugg, } -impl<'tcx> Visitor<'tcx> for BindingVisitor { - fn visit_pat(&mut self, pat: &ast::Pat) { - if let ast::PatKind::Ident(_, ident, _) = pat.kind { - self.identifiers.push(ident.name); - self.spans.entry(ident.name).or_default().push(ident.span); - } - visit::walk_pat(self, pat); - } +#[derive(Subdiagnostic)] +#[suggestion("use `crate`", code = "{replacement}")] +pub(crate) struct AbsPathWithModuleSugg { + #[primary_span] + pub span: Span, + #[applicability] + pub applicability: Applicability, + pub replacement: String, } -fn search_for_any_use_in_items(items: &[Box]) -> Option { - for item in items { - if let ItemKind::Use(..) = item.kind - && is_span_suitable_for_use_injection(item.span) - { - let mut lo = item.span.lo(); - for attr in &item.attrs { - if attr.span.eq_ctxt(item.span) { - lo = std::cmp::min(lo, attr.span.lo()); - } - } - return Some(Span::new(lo, lo, item.span.ctxt(), item.span.parent())); - } - } - None +#[derive(Diagnostic)] +#[diag("hidden lifetime parameters in types are deprecated")] +pub(crate) struct ElidedLifetimesInPaths { + #[subdiagnostic] + pub subdiag: rustc_errors::ElidedLifetimeInPathSubdiag, +} + +#[derive(Diagnostic)] +#[diag( + "{$num_snippets -> + [one] unused import: {$span_snippets} + *[other] unused imports: {$span_snippets} + }" +)] +pub(crate) struct UnusedImports { + #[subdiagnostic] + pub sugg: Option, + #[help("if this is a test module, consider adding a `#[cfg(test)]` to the containing module")] + pub test_module_span: Option, + + pub span_snippets: DiagArgValue, + pub num_snippets: usize, } -fn is_span_suitable_for_use_injection(s: Span) -> bool { - // don't suggest placing a use before the prelude - // import or other generated ones - !s.from_expansion() +#[derive(Subdiagnostic)] +pub(crate) enum UnusedImportsSugg { + #[suggestion( + "remove the whole `use` item", + applicability = "machine-applicable", + code = "", + style = "tool-only" + )] + RemoveWholeUse { + #[primary_span] + span: Span, + }, + #[multipart_suggestion( + "{$num_to_remove -> + [one] remove the unused import + *[other] remove the unused imports + }", + applicability = "machine-applicable", + style = "tool-only" + )] + RemoveImports { + #[suggestion_part(code = "")] + remove_spans: Vec, + num_to_remove: usize, + }, } diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 2baf423e296d6..3f62f3b2df3bd 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1,1778 +1,3935 @@ +// ignore-tidy-filelength +use std::mem; +use std::ops::ControlFlow; + +use itertools::Itertools as _; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::{ + self as ast, CRATE_NODE_ID, Crate, DUMMY_NODE_ID, ItemKind, ModKind, NodeId, Path, + join_path_idents, +}; +use rustc_ast_pretty::pprust; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_errors::codes::*; -use rustc_errors::formatting::DiagMessageAddArg; use rustc_errors::{ - Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, ElidedLifetimeInPathSubdiag, - EmissionGuarantee, IntoDiagArg, Level, MultiSpan, Subdiagnostic, msg, + Applicability, Diag, DiagCtxtHandle, Diagnostic, ErrorGuaranteed, MultiSpan, SuggestionStyle, + struct_span_code_err, }; -use rustc_macros::{Diagnostic, Subdiagnostic}; -use rustc_span::{Ident, Span, Spanned, Symbol}; - -use crate::Res; -use crate::late::PatternSource; - -#[derive(Diagnostic)] -#[diag("can't use {$is_self -> - [true] `Self` - *[false] generic parameters - } from outer item", code = E0401)] -#[note( - "nested items are independent from their parent item for everything except for privacy and name resolution" -)] -pub(crate) struct GenericParamsFromOuterItem { - #[primary_span] - #[label( - "use of {$is_self -> - [true] `Self` - *[false] generic parameter - } from outer item" - )] - pub(crate) span: Span, - #[subdiagnostic] - pub(crate) label: Option, - #[subdiagnostic] - pub(crate) refer_to_type_directly: Option, - #[subdiagnostic] - pub(crate) use_let: Option, - #[subdiagnostic] - pub(crate) sugg: Option, - #[subdiagnostic] - pub(crate) static_or_const: Option, - pub(crate) is_self: bool, - #[subdiagnostic] - pub(crate) item: Option, -} - -#[derive(Subdiagnostic)] -#[label( - "{$is_self -> - [true] `Self` - *[false] generic parameter - } used in this inner {$descr}" -)] -pub(crate) struct GenericParamsFromOuterItemInnerItem { - #[primary_span] - pub(crate) span: Span, - pub(crate) descr: String, - pub(crate) is_self: bool, -} - -#[derive(Subdiagnostic)] -pub(crate) enum GenericParamsFromOuterItemStaticOrConst { - #[note("a `static` is a separate item from the item that contains it")] - Static, - #[note("a `const` is a separate item from the item that contains it")] - Const, -} - -#[derive(Subdiagnostic)] -pub(crate) enum GenericParamsFromOuterItemLabel { - #[label("can't use `Self` here")] - SelfTyParam(#[primary_span] Span), - #[label("`Self` type implicitly declared here, by this `impl`")] - SelfTyAlias(#[primary_span] Span), - #[label("type parameter from outer item")] - TyParam(#[primary_span] Span), - #[label("const parameter from outer item")] - ConstParam(#[primary_span] Span), -} - -#[derive(Subdiagnostic)] -#[suggestion( - "try introducing a local generic parameter here", - code = "{snippet}", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct GenericParamsFromOuterItemSugg { - #[primary_span] - pub(crate) span: Span, - pub(crate) snippet: String, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "try using a local `let` binding instead", - code = "let", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct GenericParamsFromOuterItemUseLet { - #[primary_span] - pub(crate) span: Span, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "refer to the type directly here instead", - code = "{snippet}", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct UseTypeDirectly { - #[primary_span] - pub(crate) span: Span, - pub(crate) snippet: String, -} - -#[derive(Diagnostic)] -#[diag("the name `{$name}` is already used for a generic parameter in this item's generic parameters", code = E0403)] -pub(crate) struct NameAlreadyUsedInParameterList { - #[primary_span] - #[label("already used")] - pub(crate) span: Span, - #[label("first use of `{$name}`")] - pub(crate) first_use_span: Span, - pub(crate) name: Ident, -} - -#[derive(Diagnostic)] -#[diag("method `{$method}` is not a member of trait `{$trait_}`", code = E0407)] -pub(crate) struct MethodNotMemberOfTrait { - #[primary_span] - #[label("not a member of trait `{$trait_}`")] - pub(crate) span: Span, - pub(crate) method: Ident, - pub(crate) trait_: String, - #[subdiagnostic] - pub(crate) sub: Option, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "there is an associated function with a similar name", - code = "{candidate}", - applicability = "maybe-incorrect" -)] -pub(crate) struct AssociatedFnWithSimilarNameExists { - #[primary_span] - pub(crate) span: Span, - pub(crate) candidate: Symbol, -} - -#[derive(Diagnostic)] -#[diag("type `{$type_}` is not a member of trait `{$trait_}`", code = E0437)] -pub(crate) struct TypeNotMemberOfTrait { - #[primary_span] - #[label("not a member of trait `{$trait_}`")] - pub(crate) span: Span, - pub(crate) type_: Ident, - pub(crate) trait_: String, - #[subdiagnostic] - pub(crate) sub: Option, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "there is an associated type with a similar name", - code = "{candidate}", - applicability = "maybe-incorrect" -)] -pub(crate) struct AssociatedTypeWithSimilarNameExists { - #[primary_span] - pub(crate) span: Span, - pub(crate) candidate: Symbol, -} - -#[derive(Diagnostic)] -#[diag("const `{$const_}` is not a member of trait `{$trait_}`", code = E0438)] -pub(crate) struct ConstNotMemberOfTrait { - #[primary_span] - #[label("not a member of trait `{$trait_}`")] - pub(crate) span: Span, - pub(crate) const_: Ident, - pub(crate) trait_: String, - #[subdiagnostic] - pub(crate) sub: Option, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "there is an associated constant with a similar name", - code = "{candidate}", - applicability = "maybe-incorrect" -)] -pub(crate) struct AssociatedConstWithSimilarNameExists { - #[primary_span] - pub(crate) span: Span, - pub(crate) candidate: Symbol, -} - -#[derive(Diagnostic)] -#[diag("variable `{$variable_name}` is bound inconsistently across alternatives separated by `|`", code = E0409)] -pub(crate) struct VariableBoundWithDifferentMode { - #[primary_span] - #[label("bound in different ways")] - pub(crate) span: Span, - #[label("first binding")] - pub(crate) first_binding_span: Span, - pub(crate) variable_name: Ident, -} - -#[derive(Diagnostic)] -#[diag("identifier `{$identifier}` is bound more than once in this parameter list", code = E0415)] -pub(crate) struct IdentifierBoundMoreThanOnceInParameterList { - #[primary_span] - #[label("used as parameter more than once")] - pub(crate) span: Span, - pub(crate) identifier: Ident, -} - -#[derive(Diagnostic)] -#[diag("identifier `{$identifier}` is bound more than once in the same pattern", code = E0416)] -pub(crate) struct IdentifierBoundMoreThanOnceInSamePattern { - #[primary_span] - #[label("used in a pattern more than once")] - pub(crate) span: Span, - pub(crate) identifier: Ident, -} - -#[derive(Diagnostic)] -#[diag("use of undeclared label `{$name}`", code = E0426)] -pub(crate) struct UndeclaredLabel { - #[primary_span] - #[label("undeclared label `{$name}`")] - pub(crate) span: Span, - pub(crate) name: Symbol, - #[subdiagnostic] - pub(crate) sub_reachable: Option, - #[subdiagnostic] - pub(crate) sub_reachable_suggestion: Option, - #[subdiagnostic] - pub(crate) sub_unreachable: Option, -} - -#[derive(Subdiagnostic)] -#[label("a label with a similar name is reachable")] -pub(crate) struct LabelWithSimilarNameReachable(#[primary_span] pub(crate) Span); - -#[derive(Subdiagnostic)] -#[suggestion( - "try using similarly named label", - code = "{ident_name}", - applicability = "maybe-incorrect" -)] -pub(crate) struct TryUsingSimilarlyNamedLabel { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident_name: Symbol, -} - -#[derive(Subdiagnostic)] -#[label("a label with a similar name exists but is unreachable")] -pub(crate) struct UnreachableLabelWithSimilarNameExists { - #[primary_span] - pub(crate) ident_span: Span, -} - -#[derive(Diagnostic)] -#[diag("can't capture dynamic environment in a fn item", code = E0434)] -#[help("use the `|| {\"{\"} ... {\"}\"}` closure form instead")] -pub(crate) struct CannotCaptureDynamicEnvironmentInFnItem { - #[primary_span] - pub(crate) span: Span, -} - -#[derive(Diagnostic)] -#[diag("attempt to use a non-constant value in a constant", code = E0435)] -pub(crate) struct AttemptToUseNonConstantValueInConstant<'a> { - #[primary_span] - pub(crate) span: Span, - #[subdiagnostic] - pub(crate) with: Option>, - #[subdiagnostic] - pub(crate) with_label: Option, - #[subdiagnostic] - pub(crate) without: Option>, -} - -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "consider using `{$suggestion}` instead of `{$current}`", - style = "verbose", - applicability = "has-placeholders" -)] -pub(crate) struct AttemptToUseNonConstantValueInConstantWithSuggestion<'a> { - // #[primary_span] - #[suggestion_part(code = "{suggestion} ")] - pub(crate) span: Span, - pub(crate) suggestion: &'a str, - #[suggestion_part(code = ": /* Type */")] - pub(crate) type_span: Option, - pub(crate) current: &'a str, -} - -#[derive(Subdiagnostic)] -#[label("non-constant value")] -pub(crate) struct AttemptToUseNonConstantValueInConstantLabelWithSuggestion { - #[primary_span] - pub(crate) span: Span, -} - -#[derive(Subdiagnostic)] -#[label("this would need to be a `{$suggestion}`")] -pub(crate) struct AttemptToUseNonConstantValueInConstantWithoutSuggestion<'a> { - #[primary_span] - pub(crate) ident_span: Span, - pub(crate) suggestion: &'a str, -} - -#[derive(Diagnostic)] -#[diag("{$shadowing_binding}s cannot shadow {$shadowed_binding}s", code = E0530)] -pub(crate) struct BindingShadowsSomethingUnacceptable<'a> { - #[primary_span] - #[label("cannot be named the same as {$article} {$shadowed_binding}")] - pub(crate) span: Span, - pub(crate) shadowing_binding: PatternSource, - pub(crate) shadowed_binding: Res, - pub(crate) article: &'a str, - #[subdiagnostic] - pub(crate) sub_suggestion: Option, - #[label("the {$shadowed_binding} `{$name}` is {$participle} here")] - pub(crate) shadowed_binding_span: Span, - pub(crate) participle: &'a str, - pub(crate) name: Symbol, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "try specify the pattern arguments", - code = "{name}(..)", - applicability = "unspecified" -)] -pub(crate) struct BindingShadowsSomethingUnacceptableSuggestion { - #[primary_span] - pub(crate) span: Span, - pub(crate) name: Symbol, -} - -#[derive(Diagnostic)] -#[diag("generic parameter defaults cannot reference parameters before they are declared", code = E0128)] -pub(crate) struct ForwardDeclaredGenericParam { - #[primary_span] - #[label("cannot reference `{$param}` before it is declared")] - pub(crate) span: Span, - pub(crate) param: Symbol, -} +use rustc_feature::BUILTIN_ATTRIBUTES; +use rustc_hir::attrs::{CfgEntry, StrippedCfgItem}; +use rustc_hir::def::Namespace::{self, *}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, MacroKinds, NonMacroAttrKind, PerNS}; +use rustc_hir::def_id::{CRATE_DEF_ID, DefId}; +use rustc_hir::{PrimTy, Stability, StabilityLevel, find_attr}; +use rustc_middle::bug; +use rustc_middle::ty::{TyCtxt, Visibility}; +use rustc_session::Session; +use rustc_session::lint::builtin::{ + ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, AMBIGUOUS_GLOB_IMPORTS, AMBIGUOUS_IMPORT_VISIBILITIES, + AMBIGUOUS_PANIC_IMPORTS, MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, +}; +use rustc_session::utils::was_invoked_from_cargo; +use rustc_span::edit_distance::find_best_match_for_name; +use rustc_span::edition::Edition; +use rustc_span::hygiene::MacroKind; +use rustc_span::source_map::SourceMap; +use rustc_span::{ + BytePos, Ident, RemapPathScopeComponents, Span, Spanned, Symbol, SyntaxContext, kw, sym, +}; +use thin_vec::{ThinVec, thin_vec}; +use tracing::{debug, instrument}; -#[derive(Diagnostic)] -#[diag("const parameter types cannot reference parameters before they are declared")] -pub(crate) struct ForwardDeclaredGenericInConstParamTy { - #[primary_span] - #[label("const parameter type cannot reference `{$param}` before it is declared")] - pub(crate) span: Span, - pub(crate) param: Symbol, -} +use crate::diagnostics::{ + self, AddedMacroUse, ChangeImportBinding, ChangeImportBindingSuggestion, ConsiderAddingADerive, + ExplicitUnsafeTraits, MacroDefinedLater, MacroRulesNot, MacroSuggMovePosition, + MaybeMissingMacroRulesName, +}; +use crate::hygiene::Macros20NormalizedSyntaxContext; +use crate::imports::{Import, ImportKind}; +use crate::late::{DiagMetadata, PatternSource, Rib}; +use crate::{ + AmbiguityError, AmbiguityKind, AmbiguityWarning, BindingError, BindingKey, Decl, DeclKind, + DelayedVisResolutionError, Finalize, ForwardGenericParamBanReason, HasGenericParams, IdentKey, + LateDecl, MacroRulesScope, Module, ModuleKind, ModuleOrUniformRoot, ParentScope, PathResult, + PrivacyError, Res, ResolutionError, Resolver, Scope, ScopeSet, Segment, UseError, Used, + VisResolutionError, path_names_to_string, +}; -#[derive(Diagnostic)] -#[diag("the type of const parameters must not depend on other generic parameters", code = E0770)] -pub(crate) struct ParamInTyOfConstParam { - #[primary_span] - #[label("the type must not depend on the parameter `{$name}`")] - pub(crate) span: Span, - pub(crate) name: Symbol, -} +/// A vector of spans and replacements, a message and applicability. +pub(crate) type Suggestion = (Vec<(Span, String)>, String, Applicability); -#[derive(Diagnostic)] -#[diag("generic parameters cannot use `Self` in their defaults", code = E0735)] -pub(crate) struct SelfInGenericParamDefault { - #[primary_span] - pub(crate) span: Span, -} +/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a +/// similarly named label and whether or not it is reachable. +pub(crate) type LabelSuggestion = (Ident, bool); -#[derive(Diagnostic)] -#[diag("cannot use `Self` in const parameter type")] -pub(crate) struct SelfInConstGenericTy { - #[primary_span] - pub(crate) span: Span, +#[derive(Clone)] +pub(crate) struct StructCtor { + pub res: Res, + pub vis: Visibility, + pub field_visibilities: Vec>, } -#[derive(Diagnostic)] -#[diag( - "{$is_gca -> - [true] generic parameters in const blocks are not allowed; use a named `const` item instead - *[false] generic parameters may not be used in const operations -}" -)] -pub(crate) struct ParamInNonTrivialAnonConst { - #[primary_span] - #[label("cannot perform const operation using `{$name}`")] - pub(crate) span: Span, - pub(crate) name: Symbol, - #[subdiagnostic] - pub(crate) param_kind: ParamKindInNonTrivialAnonConst, - #[help("add `#![feature(generic_const_exprs)]` to allow generic const expressions")] - pub(crate) help: bool, - pub(crate) is_gca: bool, - #[help( - "consider factoring the expression into a `type const` item and use it as the const argument instead" - )] - pub(crate) help_gca: bool, +impl StructCtor { + pub(crate) fn has_private_fields<'ra>(&self, m: Module<'ra>, r: &Resolver<'ra, '_>) -> bool { + self.field_visibilities.iter().any(|&vis| !r.is_accessible_from(vis, m)) + } } #[derive(Debug)] -#[derive(Subdiagnostic)] -pub(crate) enum ParamKindInNonTrivialAnonConst { - #[note("type parameters may not be used in const expressions")] - Type, - #[help("const parameters may only be used as standalone arguments here, i.e. `{$name}`")] - Const { name: Symbol }, - #[note("lifetime parameters may not be used in const expressions")] - Lifetime, -} - -#[derive(Diagnostic)] -#[diag("use of unreachable label `{$name}`", code = E0767)] -#[note("labels are unreachable through functions, closures, async blocks and modules")] -pub(crate) struct UnreachableLabel { - #[primary_span] - #[label("unreachable label `{$name}`")] - pub(crate) span: Span, - pub(crate) name: Symbol, - #[label("unreachable label defined here")] - pub(crate) definition_span: Span, - #[subdiagnostic] - pub(crate) sub_suggestion: Option, - #[subdiagnostic] - pub(crate) sub_suggestion_label: Option, - #[subdiagnostic] - pub(crate) sub_unreachable_label: Option, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "try using similarly named label", - code = "{ident_name}", - applicability = "maybe-incorrect" -)] -pub(crate) struct UnreachableLabelSubSuggestion { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident_name: Symbol, -} - -#[derive(Subdiagnostic)] -#[label("a label with a similar name is reachable")] -pub(crate) struct UnreachableLabelSubLabel { - #[primary_span] - pub(crate) ident_span: Span, -} - -#[derive(Subdiagnostic)] -#[label("a label with a similar name exists but is also unreachable")] -pub(crate) struct UnreachableLabelSubLabelUnreachable { - #[primary_span] - pub(crate) ident_span: Span, -} - -#[derive(Diagnostic)] -#[diag("invalid `sym` operand")] -#[help("`sym` operands must refer to either a function or a static")] -pub(crate) struct InvalidAsmSym { - #[primary_span] - #[label("is a local variable")] - pub(crate) span: Span, -} - -#[derive(Diagnostic)] -#[diag("attempt to use a non-constant value in a constant")] -pub(crate) struct LowercaseSelf { - #[primary_span] - #[suggestion( - "try using `Self`", - code = "Self", - applicability = "maybe-incorrect", - style = "short" - )] - pub(crate) span: Span, +pub(crate) enum SuggestionTarget { + /// The target has a similar name as the name used by the programmer (probably a typo) + SimilarlyNamed, + /// The target is the only valid item that can be used in the corresponding context + SingleItem, } #[derive(Debug)] -#[derive(Diagnostic)] -#[diag("never patterns cannot contain variable bindings")] -pub(crate) struct BindingInNeverPattern { - #[primary_span] - #[suggestion( - "use a wildcard `_` instead", - code = "_", - applicability = "machine-applicable", - style = "short" - )] - pub(crate) span: Span, +pub(crate) struct TypoSuggestion { + pub candidate: Symbol, + /// The source location where the name is defined; None if the name is not defined + /// in source e.g. primitives + pub span: Option, + pub res: Res, + pub target: SuggestionTarget, +} + +impl TypoSuggestion { + pub(crate) fn new(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { + Self { candidate, span: Some(span), res, target: SuggestionTarget::SimilarlyNamed } + } + pub(crate) fn typo_from_name(candidate: Symbol, res: Res) -> TypoSuggestion { + Self { candidate, span: None, res, target: SuggestionTarget::SimilarlyNamed } + } + pub(crate) fn single_item(candidate: Symbol, span: Span, res: Res) -> TypoSuggestion { + Self { candidate, span: Some(span), res, target: SuggestionTarget::SingleItem } + } } -#[derive(Diagnostic)] -#[diag("duplicate definitions with name `{$name}`:", code = E0201)] -pub(crate) struct TraitImplDuplicate { - #[primary_span] - #[label("duplicate definition")] - pub(crate) span: Span, - #[label("previous definition here")] - pub(crate) old_span: Span, - #[label("item in trait")] - pub(crate) trait_item_span: Span, - pub(crate) name: Ident, -} +/// A free importable items suggested in case of resolution failure. +#[derive(Debug, Clone)] +pub(crate) struct ImportSuggestion { + pub did: Option, + pub descr: &'static str, + pub path: Path, + pub accessible: bool, + // false if the path traverses a foreign `#[doc(hidden)]` item. + pub doc_visible: bool, + pub via_import: bool, + /// An extra note that should be issued if this item is suggested + pub note: Option, + pub is_stable: bool, +} + +/// Adjust the impl span so that just the `impl` keyword is taken by removing +/// everything after `<` (`"impl Iterator for A {}" -> "impl"`) and +/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`). +/// +/// *Attention*: the method used is very fragile since it essentially duplicates the work of the +/// parser. If you need to use this function or something similar, please consider updating the +/// `source_map` functions and this function to something more robust. +fn reduce_impl_span_to_impl_keyword(sm: &SourceMap, impl_span: Span) -> Span { + let impl_span = sm.span_until_char(impl_span, '<'); + sm.span_until_whitespace(impl_span) +} + +impl<'ra, 'tcx> Resolver<'ra, 'tcx> { + pub(crate) fn dcx(&self) -> DiagCtxtHandle<'tcx> { + self.tcx.dcx() + } -#[derive(Diagnostic)] -#[diag("relative paths are not supported in visibilities in 2018 edition or later")] -pub(crate) struct Relative2018 { - #[primary_span] - pub(crate) span: Span, - #[suggestion("try", code = "crate::{path_str}", applicability = "maybe-incorrect")] - pub(crate) path_span: Span, - pub(crate) path_str: String, -} + pub(crate) fn report_errors(&mut self, krate: &Crate) { + self.report_delayed_vis_resolution_errors(); + self.report_with_use_injections(krate); + + for &(span_use, span_def) in &self.macro_expanded_macro_export_errors { + self.lint_buffer.buffer_lint( + MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS, + CRATE_NODE_ID, + span_use, + diagnostics::MacroExpandedMacroExportsAccessedByAbsolutePaths { + definition: span_def, + }, + ); + } -#[derive(Diagnostic)] -#[diag("visibilities can only be restricted to ancestor modules", code = E0742)] -pub(crate) struct AncestorOnly(#[primary_span] pub(crate) Span); - -#[derive(Diagnostic)] -#[diag("expected module, found {$res} `{$path_str}`", code = E0577)] -pub(crate) struct ExpectedModuleFound { - #[primary_span] - #[label("not a module")] - pub(crate) span: Span, - pub(crate) res: Res, - pub(crate) path_str: String, -} + for ambiguity_error in &self.ambiguity_errors { + let mut diag = self.ambiguity_diagnostic(ambiguity_error); + + if let Some(ambiguity_warning) = ambiguity_error.warning { + let node_id = match ambiguity_error.b1.0.kind { + DeclKind::Import { import, .. } => import.root_id, + DeclKind::Def(_) => CRATE_NODE_ID, + }; + + let lint = match ambiguity_warning { + _ if ambiguity_error.ambig_vis.is_some() => AMBIGUOUS_IMPORT_VISIBILITIES, + AmbiguityWarning::GlobImport => AMBIGUOUS_GLOB_IMPORTS, + AmbiguityWarning::PanicImport => AMBIGUOUS_PANIC_IMPORTS, + }; + + self.lint_buffer.buffer_lint(lint, node_id, diag.ident.span, diag); + } else { + diag.is_error = true; + self.dcx().emit_err(diag); + } + } -#[derive(Diagnostic)] -#[diag("cannot determine resolution for the visibility", code = E0578)] -pub(crate) struct Indeterminate(#[primary_span] pub(crate) Span); - -#[derive(Diagnostic)] -#[diag("trait implementation can only be restricted to ancestor modules")] -pub(crate) struct RestrictionAncestorOnly(#[primary_span] pub(crate) Span); - -#[derive(Diagnostic)] -#[diag("cannot use a tool module through an import")] -pub(crate) struct ToolModuleImported { - #[primary_span] - pub(crate) span: Span, - #[note("the tool module imported here")] - pub(crate) import: Span, -} + let mut reported_spans = FxHashSet::default(); + for error in mem::take(&mut self.privacy_errors) { + if reported_spans.insert(error.dedup_span) { + self.report_privacy_error(&error); + } + } + } -#[derive(Diagnostic)] -#[diag("visibility must resolve to a module")] -pub(crate) struct ModuleOnly(#[primary_span] pub(crate) Span); - -#[derive(Diagnostic)] -#[diag("expected {$expected}, found {$found} `{$macro_path}`")] -pub(crate) struct MacroExpectedFound<'a> { - #[primary_span] - #[label("not {$article} {$expected}")] - pub(crate) span: Span, - pub(crate) found: &'a str, - pub(crate) article: &'static str, - pub(crate) expected: &'a str, - pub(crate) macro_path: &'a str, - #[subdiagnostic] - pub(crate) remove_surrounding_derive: Option, - #[subdiagnostic] - pub(crate) add_as_non_derive: Option>, -} + fn report_delayed_vis_resolution_errors(&mut self) { + for DelayedVisResolutionError { vis, parent_scope, error } in + mem::take(&mut self.delayed_vis_resolution_errors) + { + match self.try_resolve_visibility(&parent_scope, &vis, true) { + Ok(_) => self.report_vis_error(error), + Err(error) => self.report_vis_error(error), + }; + } + } -#[derive(Subdiagnostic)] -#[help("remove from the surrounding `derive()`")] -pub(crate) struct RemoveSurroundingDerive { - #[primary_span] - pub(crate) span: Span, -} + fn report_with_use_injections(&mut self, krate: &Crate) { + for UseError { mut err, candidates, node_id, instead, suggestion, path, is_call } in + mem::take(&mut self.use_injections) + { + let (span, found_use) = if node_id != DUMMY_NODE_ID { + UsePlacementFinder::check(krate, node_id) + } else { + (None, FoundUse::No) + }; + + if !candidates.is_empty() { + show_candidates( + self.tcx, + &mut err, + span, + &candidates, + if instead { Instead::Yes } else { Instead::No }, + found_use, + DiagMode::Normal, + path, + "", + ); + err.emit(); + } else if let Some((span, msg, sugg, appl)) = suggestion { + err.span_suggestion_verbose(span, msg, sugg, appl); + err.emit(); + } else if let [segment] = path.as_slice() + && is_call + { + err.stash(segment.ident.span, rustc_errors::StashKey::CallIntoMethod); + } else { + err.emit(); + } + } + } -#[derive(Subdiagnostic)] -#[help( - " - add as non-Derive macro - `#[{$macro_path}]`" -)] -pub(crate) struct AddAsNonDerive<'a> { - pub(crate) macro_path: &'a str, -} + pub(crate) fn report_conflict( + &mut self, + ident: IdentKey, + ns: Namespace, + old_binding: Decl<'ra>, + new_binding: Decl<'ra>, + ) { + // Error on the second of two conflicting names + if old_binding.span.lo() > new_binding.span.lo() { + return self.report_conflict(ident, ns, new_binding, old_binding); + } -#[derive(Diagnostic)] -#[diag("can't use a procedural macro from the same crate that defines it")] -pub(crate) struct ProcMacroSameCrate { - #[primary_span] - pub(crate) span: Span, - #[help("you can define integration tests in a directory named `tests`")] - pub(crate) is_test: bool, -} + let container = match old_binding.parent_module.unwrap().expect_local().kind { + // Avoid using TyCtxt::def_kind_descr in the resolver, because it + // indirectly *calls* the resolver, and would cause a query cycle. + ModuleKind::Def(kind, def_id, _, _) => kind.descr(def_id), + ModuleKind::Block => "block", + }; -#[derive(Diagnostic)] -#[diag("cannot find {$ns_descr} `{$ident}` in this scope")] -pub(crate) struct ProcMacroDeriveResolutionFallback { - #[label("names from parent modules are not accessible without an explicit import")] - pub span: Span, - pub ns_descr: &'static str, - pub ident: Symbol, -} + let (name, span) = + (ident.name, self.tcx.sess.source_map().guess_head_span(new_binding.span)); -#[derive(Diagnostic)] -#[diag( - "macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths" -)] -pub(crate) struct MacroExpandedMacroExportsAccessedByAbsolutePaths { - #[note("the macro is defined here")] - pub definition: Span, -} + if self.name_already_seen.get(&name) == Some(&span) { + return; + } -#[derive(Diagnostic)] -#[diag("`#[macro_use]` is not supported on `extern crate self`")] -pub(crate) struct MacroUseExternCrateSelf { - #[primary_span] - pub(crate) span: Span, -} + let old_kind = match (ns, old_binding.res()) { + (ValueNS, _) => "value", + (MacroNS, _) => "macro", + (TypeNS, _) if old_binding.is_extern_crate() => "extern crate", + (TypeNS, Res::Def(DefKind::Mod, _)) => "module", + (TypeNS, Res::Def(DefKind::Trait, _)) => "trait", + (TypeNS, _) => "type", + }; + + let code = match (old_binding.is_extern_crate(), new_binding.is_extern_crate()) { + (true, true) => E0259, + (true, _) | (_, true) => match new_binding.is_import() && old_binding.is_import() { + true => E0254, + false => E0260, + }, + _ => match (old_binding.is_import_user_facing(), new_binding.is_import_user_facing()) { + (false, false) => E0428, + (true, true) => E0252, + _ => E0255, + }, + }; + + let label = match new_binding.is_import_user_facing() { + true => diagnostics::NameDefinedMultipleTimeLabel::Reimported { span, name }, + false => diagnostics::NameDefinedMultipleTimeLabel::Redefined { span, name }, + }; + + let old_binding_label = + (!old_binding.span.is_dummy() && old_binding.span != span).then(|| { + let span = self.tcx.sess.source_map().guess_head_span(old_binding.span); + match old_binding.is_import_user_facing() { + true => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Import { + span, + old_kind, + name, + }, + false => diagnostics::NameDefinedMultipleTimeOldBindingLabel::Definition { + span, + old_kind, + name, + }, + } + }); + + let mut err = self + .dcx() + .create_err(diagnostics::NameDefinedMultipleTime { + span, + name, + descr: ns.descr(), + container, + label, + old_binding_label, + }) + .with_code(code); + + // See https://github.com/rust-lang/rust/issues/32354 + use DeclKind::Import; + let can_suggest = |binding: Decl<'_>, import: self::Import<'_>| { + !binding.span.is_dummy() + && !matches!(import.kind, ImportKind::MacroUse { .. } | ImportKind::MacroExport) + }; + let import = match (&new_binding.kind, &old_binding.kind) { + // If there are two imports where one or both have attributes then prefer removing the + // import without attributes. + (Import { import: new, .. }, Import { import: old, .. }) + if { + (new.has_attributes || old.has_attributes) + && can_suggest(old_binding, *old) + && can_suggest(new_binding, *new) + } => + { + if old.has_attributes { + Some((*new, new_binding.span, true)) + } else { + Some((*old, old_binding.span, true)) + } + } + // Otherwise prioritize the new binding. + (Import { import, .. }, other) if can_suggest(new_binding, *import) => { + Some((*import, new_binding.span, other.is_import())) + } + (other, Import { import, .. }) if can_suggest(old_binding, *import) => { + Some((*import, old_binding.span, other.is_import())) + } + _ => None, + }; + + // Check if the target of the use for both bindings is the same. + let duplicate = new_binding.res().opt_def_id() == old_binding.res().opt_def_id(); + let has_dummy_span = new_binding.span.is_dummy() || old_binding.span.is_dummy(); + let from_item = + self.extern_prelude.get(&ident).is_none_or(|entry| entry.introduced_by_item()); + // Only suggest removing an import if both bindings are to the same def, if both spans + // aren't dummy spans. Further, if both bindings are imports, then the ident must have + // been introduced by an item. + let should_remove_import = duplicate + && !has_dummy_span + && ((new_binding.is_extern_crate() || old_binding.is_extern_crate()) || from_item); + + match import { + Some((import, span, true)) if should_remove_import && import.is_nested() => { + self.add_suggestion_for_duplicate_nested_use(&mut err, import, span); + } + Some((import, _, true)) if should_remove_import && !import.is_glob() => { + // Simple case - remove the entire import. Due to the above match arm, this can + // only be a single use so just remove it entirely. + err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { + span: import.use_span_with_attributes, + }); + } + Some((import, span, _)) => { + self.add_suggestion_for_rename_of_use(&mut err, name, import, span); + } + _ => {} + } -#[derive(Diagnostic)] -#[diag("not sure whether the path is accessible or not")] -#[note("the type may have associated items, but we are currently not checking them")] -pub(crate) struct CfgAccessibleUnsure { - #[primary_span] - pub(crate) span: Span, -} + err.emit(); + self.name_already_seen.insert(name, span); + } -#[derive(Debug)] -#[derive(Diagnostic)] -#[diag("generic parameters may not be used in enum discriminant values")] -pub(crate) struct ParamInEnumDiscriminant { - #[primary_span] - #[label("cannot perform const operation using `{$name}`")] - pub(crate) span: Span, - pub(crate) name: Symbol, - #[subdiagnostic] - pub(crate) param_kind: ParamKindInEnumDiscriminant, -} + /// This function adds a suggestion to change the binding name of a new import that conflicts + /// with an existing import. + /// + /// ```text,ignore (diagnostic) + /// help: you can use `as` to change the binding name of the import + /// | + /// LL | use foo::bar as other_bar; + /// | ^^^^^^^^^^^^^^^^^^^^^ + /// ``` + fn add_suggestion_for_rename_of_use( + &self, + err: &mut Diag<'_>, + name: Symbol, + import: Import<'_>, + binding_span: Span, + ) { + let suggested_name = if name.as_str().chars().next().unwrap().is_uppercase() { + format!("Other{name}") + } else { + format!("other_{name}") + }; + + let mut suggestion = None; + let mut span = binding_span; + match import.kind { + ImportKind::Single { source, .. } => { + if let Some(pos) = source.span.hi().0.checked_sub(binding_span.lo().0) + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(binding_span) + && pos as usize <= snippet.len() + { + span = binding_span.with_lo(binding_span.lo() + BytePos(pos)).with_hi( + binding_span.hi() - BytePos(if snippet.ends_with(';') { 1 } else { 0 }), + ); + suggestion = Some(format!(" as {suggested_name}")); + } + } + ImportKind::ExternCrate { source, target, .. } => { + suggestion = Some(format!( + "extern crate {} as {};", + source.unwrap_or(target.name), + suggested_name, + )) + } + _ => unreachable!(), + } -#[derive(Debug)] -#[derive(Subdiagnostic)] -pub(crate) enum ParamKindInEnumDiscriminant { - #[note("type parameters may not be used in enum discriminant values")] - Type, - #[note("const parameters may not be used in enum discriminant values")] - Const, - #[note("lifetime parameters may not be used in enum discriminant values")] - Lifetime, -} + if let Some(suggestion) = suggestion { + err.subdiagnostic(ChangeImportBindingSuggestion { span, suggestion }); + } else { + err.subdiagnostic(ChangeImportBinding { span }); + } + } -#[derive(Subdiagnostic)] -#[label("you can use `as` to change the binding name of the import")] -pub(crate) struct ChangeImportBinding { - #[primary_span] - pub(crate) span: Span, -} + /// This function adds a suggestion to remove an unnecessary binding from an import that is + /// nested. In the following example, this function will be invoked to remove the `a` binding + /// in the second use statement: + /// + /// ```ignore (diagnostic) + /// use issue_52891::a; + /// use issue_52891::{d, a, e}; + /// ``` + /// + /// The following suggestion will be added: + /// + /// ```ignore (diagnostic) + /// use issue_52891::{d, a, e}; + /// ^-- help: remove unnecessary import + /// ``` + /// + /// If the nested use contains only one import then the suggestion will remove the entire + /// line. + /// + /// It is expected that the provided import is nested - this isn't checked by the + /// function. If this invariant is not upheld, this function's behaviour will be unexpected + /// as characters expected by span manipulations won't be present. + fn add_suggestion_for_duplicate_nested_use( + &self, + err: &mut Diag<'_>, + import: Import<'_>, + binding_span: Span, + ) { + assert!(import.is_nested()); + + // Two examples will be used to illustrate the span manipulations we're doing: + // + // - Given `use issue_52891::{d, a, e};` where `a` is a duplicate then `binding_span` is + // `a` and `import.use_span` is `issue_52891::{d, a, e};`. + // - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is + // `a` and `import.use_span` is `issue_52891::{d, e, a};`. + + let (found_closing_brace, span) = + find_span_of_binding_until_next_binding(self.tcx.sess, binding_span, import.use_span); + + // If there was a closing brace then identify the span to remove any trailing commas from + // previous imports. + if found_closing_brace { + if let Some(span) = extend_span_to_previous_binding(self.tcx.sess, span) { + err.subdiagnostic(diagnostics::ToolOnlyRemoveUnnecessaryImport { span }); + } else { + // Remove the entire line if we cannot extend the span back, this indicates an + // `issue_52891::{self}` case. + err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { + span: import.use_span_with_attributes, + }); + } -#[derive(Subdiagnostic)] -#[suggestion( - "you can use `as` to change the binding name of the import", - code = "{suggestion}", - applicability = "maybe-incorrect" -)] -pub(crate) struct ChangeImportBindingSuggestion { - #[primary_span] - pub(crate) span: Span, - pub(crate) suggestion: String, -} + return; + } -#[derive(Diagnostic)] -#[diag("imports cannot refer to {$what}")] -pub(crate) struct ImportsCannotReferTo<'a> { - #[primary_span] - pub(crate) span: Span, - pub(crate) what: &'a str, -} + err.subdiagnostic(diagnostics::RemoveUnnecessaryImport { span }); + } -#[derive(Diagnostic)] -#[diag("cannot find {$expected} `{$ident}` in this scope")] -pub(crate) struct CannotFindIdentInThisScope<'a> { - #[primary_span] - pub(crate) span: Span, - pub(crate) expected: &'a str, - pub(crate) ident: Ident, -} + pub(crate) fn lint_if_path_starts_with_module( + &mut self, + finalize: Finalize, + path: &[Segment], + second_binding: Option>, + ) { + let Finalize { node_id, root_span, .. } = finalize; + + let first_name = match path.get(0) { + // In the 2018 edition this lint is a hard error, so nothing to do + Some(seg) if seg.ident.span.is_rust_2015() && self.tcx.sess.is_rust_2015() => { + seg.ident.name + } + _ => return, + }; -#[derive(Subdiagnostic)] -#[note("unsafe traits like `{$ident}` should be implemented explicitly")] -pub(crate) struct ExplicitUnsafeTraits { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + // We're only interested in `use` paths which should start with + // `{{root}}` currently. + if first_name != kw::PathRoot { + return; + } -#[derive(Subdiagnostic)] -#[note("a macro with the same name exists, but it appears later")] -pub(crate) struct MacroDefinedLater { - #[primary_span] - pub(crate) span: Span, -} + match path.get(1) { + // If this import looks like `crate::...` it's already good + Some(Segment { ident, .. }) if ident.name == kw::Crate => return, + // Otherwise go below to see if it's an extern crate + Some(_) => {} + // If the path has length one (and it's `PathRoot` most likely) + // then we don't know whether we're gonna be importing a crate or an + // item in our crate. Defer this lint to elsewhere + None => return, + } -#[derive(Subdiagnostic)] -#[label("consider moving the definition of `{$ident}` before this call")] -pub(crate) struct MacroSuggMovePosition { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + // If the first element of our path was actually resolved to an + // `ExternCrate` (also used for `crate::...`) then no need to issue a + // warning, this looks all good! + if let Some(binding) = second_binding + && let DeclKind::Import { import, .. } = binding.kind + // Careful: we still want to rewrite paths from renamed extern crates. + && let ImportKind::ExternCrate { source: None, .. } = import.kind + { + return; + } -#[derive(Subdiagnostic)] -pub(crate) enum MacroRulesNot { - #[label("`{$ident}` exists, but has no rules for function-like invocation")] - Func { - #[primary_span] - span: Span, - ident: Ident, - }, - #[label("`{$ident}` exists, but has no `attr` rules")] - Attr { - #[primary_span] - span: Span, - ident: Ident, - }, - #[label("`{$ident}` exists, but has no `derive` rules")] - Derive { - #[primary_span] - span: Span, - ident: Ident, - }, -} + self.lint_buffer.dyn_buffer_lint_any( + ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE, + node_id, + root_span, + move |dcx, level, sess| { + let (replacement, applicability) = match sess + .downcast_ref::() + .expect("expected a `Session`") + .source_map() + .span_to_snippet(root_span) + { + Ok(ref s) => { + // FIXME(Manishearth) ideally the emitting code + // can tell us whether or not this is global + let opt_colon = if s.trim_start().starts_with("::") { "" } else { "::" }; + + (format!("crate{opt_colon}{s}"), Applicability::MachineApplicable) + } + Err(_) => ("crate::".to_string(), Applicability::HasPlaceholders), + }; + diagnostics::AbsPathWithModule { + sugg: diagnostics::AbsPathWithModuleSugg { + span: root_span, + applicability, + replacement, + }, + } + .into_diag(dcx, level) + }, + ); + } -#[derive(Subdiagnostic)] -#[note("maybe you have forgotten to define a name for this `macro_rules!`")] -pub(crate) struct MaybeMissingMacroRulesName { - #[primary_span] - pub(crate) spans: MultiSpan, -} + pub(crate) fn add_module_candidates( + &self, + module: Module<'ra>, + names: &mut Vec, + filter_fn: &impl Fn(Res) -> bool, + ctxt: Option, + ) { + module.for_each_child(self, |_this, ident, orig_ident_span, _ns, binding| { + let res = binding.res(); + if filter_fn(res) && ctxt.is_none_or(|ctxt| ctxt == *ident.ctxt) { + names.push(TypoSuggestion::new(ident.name, orig_ident_span, res)); + } + }); + } -#[derive(Subdiagnostic)] -#[help("have you added the `#[macro_use]` on the module/import?")] -pub(crate) struct AddedMacroUse; + /// Combines an error with provided span and emits it. + /// + /// This takes the error provided, combines it with the span and any additional spans inside the + /// error and emits it. + pub(crate) fn report_error( + &mut self, + span: Span, + resolution_error: ResolutionError<'ra>, + ) -> ErrorGuaranteed { + self.into_struct_error(span, resolution_error).emit() + } -#[derive(Subdiagnostic)] -#[suggestion("consider adding a derive", code = "{suggestion}", applicability = "maybe-incorrect")] -pub(crate) struct ConsiderAddingADerive { - #[primary_span] - pub(crate) span: Span, - pub(crate) suggestion: String, -} + pub(crate) fn into_struct_error( + &mut self, + span: Span, + resolution_error: ResolutionError<'ra>, + ) -> Diag<'_> { + match resolution_error { + ResolutionError::GenericParamsFromOuterItem { + outer_res, + has_generic_params, + def_kind, + inner_item, + current_self_ty, + } => { + use diagnostics::GenericParamsFromOuterItemLabel as Label; + let static_or_const = match def_kind { + DefKind::Static { .. } => { + Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Static) + } + DefKind::Const { .. } => { + Some(diagnostics::GenericParamsFromOuterItemStaticOrConst::Const) + } + _ => None, + }; + let is_self = + matches!(outer_res, Res::SelfTyParam { .. } | Res::SelfTyAlias { .. }); + let mut err = diagnostics::GenericParamsFromOuterItem { + span, + label: None, + refer_to_type_directly: None, + use_let: None, + sugg: None, + static_or_const, + is_self, + item: inner_item.as_ref().map(|(label_span, _, kind)| { + diagnostics::GenericParamsFromOuterItemInnerItem { + span: *label_span, + descr: kind.descr().to_string(), + is_self, + } + }), + }; + + let sm = self.tcx.sess.source_map(); + // Note: do not early return for missing def_id here, + // we still want to provide suggestions for `Res::SelfTyParam` and `Res::SelfTyAlias`. + let def_id = match outer_res { + Res::SelfTyParam { .. } => { + err.label = Some(Label::SelfTyParam(span)); + None + } + Res::SelfTyAlias { alias_to: def_id, .. } => { + err.label = Some(Label::SelfTyAlias(reduce_impl_span_to_impl_keyword( + sm, + self.def_span(def_id), + ))); + err.refer_to_type_directly = current_self_ty + .map(|snippet| diagnostics::UseTypeDirectly { span, snippet }); + None + } + Res::Def(DefKind::TyParam, def_id) => { + err.label = Some(Label::TyParam(self.def_span(def_id))); + Some(def_id) + } + Res::Def(DefKind::ConstParam, def_id) => { + err.label = Some(Label::ConstParam(self.def_span(def_id))); + Some(def_id) + } + _ => { + bug!( + "GenericParamsFromOuterItem should only be used with \ + Res::SelfTyParam, Res::SelfTyAlias, DefKind::TyParam or \ + DefKind::ConstParam" + ); + } + }; + + if let Some((_, item_span, ItemKind::Const(_))) = inner_item.as_ref() { + err.use_let = Some(diagnostics::GenericParamsFromOuterItemUseLet { + span: sm.span_until_whitespace(*item_span), + }); + } + + if let Some(def_id) = def_id + && let HasGenericParams::Yes(span) = has_generic_params + && !matches!(inner_item, Some((_, _, ItemKind::Delegation(..)))) + { + let name = self.tcx.item_name(def_id); + let (span, snippet) = if span.is_empty() { + let snippet = format!("<{name}>"); + (span, snippet) + } else { + let span = sm.span_through_char(span, '<').shrink_to_hi(); + let snippet = format!("{name}, "); + (span, snippet) + }; + err.sugg = Some(diagnostics::GenericParamsFromOuterItemSugg { span, snippet }); + } + + self.dcx().create_err(err) + } + ResolutionError::NameAlreadyUsedInParameterList(name, first_use_span) => { + self.dcx().create_err(diagnostics::NameAlreadyUsedInParameterList { + span, + first_use_span, + name, + }) + } + ResolutionError::MethodNotMemberOfTrait(method, trait_, candidate) => { + self.dcx().create_err(diagnostics::MethodNotMemberOfTrait { + span, + method, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedFnWithSimilarNameExists { + span: method.span, + candidate: c, + }), + }) + } + ResolutionError::TypeNotMemberOfTrait(type_, trait_, candidate) => { + self.dcx().create_err(diagnostics::TypeNotMemberOfTrait { + span, + type_, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedTypeWithSimilarNameExists { + span: type_.span, + candidate: c, + }), + }) + } + ResolutionError::ConstNotMemberOfTrait(const_, trait_, candidate) => { + self.dcx().create_err(diagnostics::ConstNotMemberOfTrait { + span, + const_, + trait_, + sub: candidate.map(|c| diagnostics::AssociatedConstWithSimilarNameExists { + span: const_.span, + candidate: c, + }), + }) + } + ResolutionError::VariableNotBoundInPattern(binding_error, parent_scope) => { + let BindingError { name, target, origin, could_be_path } = binding_error; + + let mut target_sp = target.iter().map(|pat| pat.span).collect::>(); + target_sp.sort(); + target_sp.dedup(); + let mut origin_sp = origin.iter().map(|(span, _)| *span).collect::>(); + origin_sp.sort(); + origin_sp.dedup(); + + let msp = MultiSpan::from_spans(target_sp.clone()); + let mut err = self.dcx().create_err(diagnostics::VariableIsNotBoundInAllPatterns { + multispan: msp, + name, + }); + for sp in target_sp { + err.subdiagnostic(diagnostics::PatternDoesntBindName { span: sp, name }); + } + for sp in &origin_sp { + err.subdiagnostic(diagnostics::VariableNotInAllPatterns { span: *sp }); + } + let mut suggested_typo = false; + if !target.iter().all(|pat| matches!(pat.kind, ast::PatKind::Ident(..))) + && !origin.iter().all(|(_, pat)| matches!(pat.kind, ast::PatKind::Ident(..))) + { + // The check above is so that when we encounter `match foo { (a | b) => {} }`, + // we don't suggest `(a | a) => {}`, which would never be what the user wants. + let mut target_visitor = BindingVisitor::default(); + for pat in &target { + target_visitor.visit_pat(pat); + } + target_visitor.identifiers.sort(); + target_visitor.identifiers.dedup(); + let mut origin_visitor = BindingVisitor::default(); + for (_, pat) in &origin { + origin_visitor.visit_pat(pat); + } + origin_visitor.identifiers.sort(); + origin_visitor.identifiers.dedup(); + // Find if the binding could have been a typo + if let Some(typo) = + find_best_match_for_name(&target_visitor.identifiers, name.name, None) + && !origin_visitor.identifiers.contains(&typo) + { + err.subdiagnostic(diagnostics::PatternBindingTypo { + spans: origin_sp, + typo, + }); + suggested_typo = true; + } + } + if could_be_path { + let import_suggestions = self.lookup_import_candidates( + name, + Namespace::ValueNS, + &parent_scope, + &|res: Res| { + matches!( + res, + Res::Def( + DefKind::Ctor(CtorOf::Variant, CtorKind::Const) + | DefKind::Ctor(CtorOf::Struct, CtorKind::Const) + | DefKind::Const { .. } + | DefKind::AssocConst { .. }, + _, + ) + ) + }, + ); + + if import_suggestions.is_empty() && !suggested_typo { + let kind_matches: [fn(DefKind) -> bool; 4] = [ + |kind| matches!(kind, DefKind::Ctor(CtorOf::Variant, CtorKind::Const)), + |kind| matches!(kind, DefKind::Ctor(CtorOf::Struct, CtorKind::Const)), + |kind| matches!(kind, DefKind::Const { .. }), + |kind| matches!(kind, DefKind::AssocConst { .. }), + ]; + let mut local_names = vec![]; + self.add_module_candidates( + parent_scope.module, + &mut local_names, + &|res| matches!(res, Res::Def(_, _)), + None, + ); + let local_names: FxHashSet<_> = local_names + .into_iter() + .filter_map(|s| match s.res { + Res::Def(_, def_id) => Some(def_id), + _ => None, + }) + .collect(); + + let mut local_suggestions = vec![]; + let mut suggestions = vec![]; + for matches_kind in kind_matches { + if let Some(suggestion) = self.early_lookup_typo_candidate( + ScopeSet::All(Namespace::ValueNS), + &parent_scope, + name, + &|res: Res| match res { + Res::Def(k, _) => matches_kind(k), + _ => false, + }, + ) && let Res::Def(kind, mut def_id) = suggestion.res + { + if let DefKind::Ctor(_, _) = kind { + def_id = self.tcx.parent(def_id); + } + let kind = kind.descr(def_id); + if local_names.contains(&def_id) { + // The item is available in the current scope. Very likely to + // be a typo. Don't use the full path. + local_suggestions.push(( + suggestion.candidate, + suggestion.candidate.to_string(), + kind, + )); + } else { + suggestions.push(( + suggestion.candidate, + self.def_path_str(def_id), + kind, + )); + } + } + } + let suggestions = if !local_suggestions.is_empty() { + // There is at least one item available in the current scope that is a + // likely typo. We only show those. + local_suggestions + } else { + suggestions + }; + for (name, sugg, kind) in suggestions { + err.span_suggestion_verbose( + span, + format!( + "you might have meant to use the similarly named {kind} `{name}`", + ), + sugg, + Applicability::MaybeIncorrect, + ); + suggested_typo = true; + } + } + if import_suggestions.is_empty() && !suggested_typo { + let help_msg = format!( + "if you meant to match on a unit struct, unit variant or a `const` \ + item, consider making the path in the pattern qualified: \ + `path::to::ModOrType::{name}`", + ); + err.span_help(span, help_msg); + } + show_candidates( + self.tcx, + &mut err, + Some(span), + &import_suggestions, + Instead::No, + FoundUse::Yes, + DiagMode::Pattern, + vec![], + "", + ); + } + err + } + ResolutionError::VariableBoundWithDifferentMode(variable_name, first_binding_span) => { + self.dcx().create_err(diagnostics::VariableBoundWithDifferentMode { + span, + first_binding_span, + variable_name, + }) + } + ResolutionError::IdentifierBoundMoreThanOnceInParameterList(identifier) => { + self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInParameterList { + span, + identifier, + }) + } + ResolutionError::IdentifierBoundMoreThanOnceInSamePattern(identifier) => { + self.dcx().create_err(diagnostics::IdentifierBoundMoreThanOnceInSamePattern { + span, + identifier, + }) + } + ResolutionError::UndeclaredLabel { name, suggestion } => { + let ((sub_reachable, sub_reachable_suggestion), sub_unreachable) = match suggestion + { + // A reachable label with a similar name exists. + Some((ident, true)) => ( + ( + Some(diagnostics::LabelWithSimilarNameReachable(ident.span)), + Some(diagnostics::TryUsingSimilarlyNamedLabel { + span, + ident_name: ident.name, + }), + ), + None, + ), + // An unreachable label with a similar name exists. + Some((ident, false)) => ( + (None, None), + Some(diagnostics::UnreachableLabelWithSimilarNameExists { + ident_span: ident.span, + }), + ), + // No similarly-named labels exist. + None => ((None, None), None), + }; + self.dcx().create_err(diagnostics::UndeclaredLabel { + span, + name, + sub_reachable, + sub_reachable_suggestion, + sub_unreachable, + }) + } + ResolutionError::FailedToResolve { segment, label, suggestion, module, message } => { + let mut err = struct_span_code_err!(self.dcx(), span, E0433, "{message}"); + err.span_label(span, label); + + if let Some((suggestions, msg, applicability)) = suggestion { + if suggestions.is_empty() { + err.help(msg); + return err; + } + err.multipart_suggestion(msg, suggestions, applicability); + } + + let module = match module { + Some(ModuleOrUniformRoot::Module(m)) if let Some(id) = m.opt_def_id() => id, + _ => CRATE_DEF_ID.to_def_id(), + }; + self.find_cfg_stripped(&mut err, &segment, module); + + err + } + ResolutionError::CannotCaptureDynamicEnvironmentInFnItem => { + self.dcx().create_err(diagnostics::CannotCaptureDynamicEnvironmentInFnItem { span }) + } + ResolutionError::AttemptToUseNonConstantValueInConstant { + ident, + suggestion, + current, + type_span, + } => { + // let foo =... + // ^^^ given this Span + // ------- get this Span to have an applicable suggestion + + // edit: + // only do this if the const and usage of the non-constant value are on the same line + // the further the two are apart, the higher the chance of the suggestion being wrong + + let sp = self + .tcx + .sess + .source_map() + .span_extend_to_prev_str(ident.span, current, true, false); + + let (with, with_label, without) = match sp { + Some(sp) if !self.tcx.sess.source_map().is_multiline(sp) => { + let sp = sp + .with_lo(BytePos(sp.lo().0 - (current.len() as u32))) + .until(ident.span); + + // Only suggest replacing the binding keyword if this is a simple + // binding. + // + // Note: this approach still incorrectly suggests for irrefutable + // patterns like `if let x = 1 { const { x } }`, since the text + // between `let` and the identifier is just whitespace. + // See tests/ui/consts/non-const-value-in-const-irrefutable-pat-binding.rs + let is_simple_binding = + self.tcx.sess.source_map().span_to_snippet(sp).is_ok_and(|snippet| { + let after_keyword = snippet[current.len()..].trim(); + after_keyword.is_empty() || after_keyword == "mut" + }); + + if is_simple_binding { + ( + Some(diagnostics::AttemptToUseNonConstantValueInConstantWithSuggestion { + span: sp, + suggestion, + current, + type_span, + }), + Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), + None, + ) + } else { + ( + None, + Some(diagnostics::AttemptToUseNonConstantValueInConstantLabelWithSuggestion { span }), + None, + ) + } + } + _ => ( + None, + None, + Some( + diagnostics::AttemptToUseNonConstantValueInConstantWithoutSuggestion { + ident_span: ident.span, + suggestion, + }, + ), + ), + }; + + self.dcx().create_err(diagnostics::AttemptToUseNonConstantValueInConstant { + span, + with, + with_label, + without, + }) + } + ResolutionError::BindingShadowsSomethingUnacceptable { + shadowing_binding, + name, + participle, + article, + shadowed_binding, + shadowed_binding_span, + } => self.dcx().create_err(diagnostics::BindingShadowsSomethingUnacceptable { + span, + shadowing_binding, + shadowed_binding, + article, + sub_suggestion: match (shadowing_binding, shadowed_binding) { + ( + PatternSource::Match, + Res::Def(DefKind::Ctor(CtorOf::Variant | CtorOf::Struct, CtorKind::Fn), _), + ) => Some(diagnostics::BindingShadowsSomethingUnacceptableSuggestion { + span, + name, + }), + _ => None, + }, + shadowed_binding_span, + participle, + name, + }), + ResolutionError::ForwardDeclaredGenericParam(param, reason) => match reason { + ForwardGenericParamBanReason::Default => { + self.dcx().create_err(diagnostics::ForwardDeclaredGenericParam { param, span }) + } + ForwardGenericParamBanReason::ConstParamTy => self + .dcx() + .create_err(diagnostics::ForwardDeclaredGenericInConstParamTy { param, span }), + }, + ResolutionError::ParamInTyOfConstParam { name } => { + self.dcx().create_err(diagnostics::ParamInTyOfConstParam { span, name }) + } + ResolutionError::ParamInNonTrivialAnonConst { is_gca, name, param_kind: is_type } => { + self.dcx().create_err(diagnostics::ParamInNonTrivialAnonConst { + span, + name, + param_kind: is_type, + help: self.tcx.sess.is_nightly_build(), + is_gca, + help_gca: is_gca, + }) + } + ResolutionError::ParamInEnumDiscriminant { name, param_kind: is_type } => { + self.dcx().create_err(diagnostics::ParamInEnumDiscriminant { + span, + name, + param_kind: is_type, + }) + } + ResolutionError::ForwardDeclaredSelf(reason) => match reason { + ForwardGenericParamBanReason::Default => { + self.dcx().create_err(diagnostics::SelfInGenericParamDefault { span }) + } + ForwardGenericParamBanReason::ConstParamTy => { + self.dcx().create_err(diagnostics::SelfInConstGenericTy { span }) + } + }, + ResolutionError::UnreachableLabel { name, definition_span, suggestion } => { + let ((sub_suggestion_label, sub_suggestion), sub_unreachable_label) = + match suggestion { + // A reachable label with a similar name exists. + Some((ident, true)) => ( + ( + Some(diagnostics::UnreachableLabelSubLabel { + ident_span: ident.span, + }), + Some(diagnostics::UnreachableLabelSubSuggestion { + span, + // intentionally taking 'ident.name' instead of 'ident' itself, as this + // could be used in suggestion context + ident_name: ident.name, + }), + ), + None, + ), + // An unreachable label with a similar name exists. + Some((ident, false)) => ( + (None, None), + Some(diagnostics::UnreachableLabelSubLabelUnreachable { + ident_span: ident.span, + }), + ), + // No similarly-named labels exist. + None => ((None, None), None), + }; + self.dcx().create_err(diagnostics::UnreachableLabel { + span, + name, + definition_span, + sub_suggestion, + sub_suggestion_label, + sub_unreachable_label, + }) + } + ResolutionError::TraitImplMismatch { + name, + kind, + code, + trait_item_span, + trait_path, + } => self + .dcx() + .create_err(diagnostics::TraitImplMismatch { + span, + name, + kind, + trait_path, + trait_item_span, + }) + .with_code(code), + ResolutionError::TraitImplDuplicate { name, trait_item_span, old_span } => { + self.dcx().create_err(diagnostics::TraitImplDuplicate { + span, + name, + trait_item_span, + old_span, + }) + } + ResolutionError::InvalidAsmSym => { + self.dcx().create_err(diagnostics::InvalidAsmSym { span }) + } + ResolutionError::LowercaseSelf => { + self.dcx().create_err(diagnostics::LowercaseSelf { span }) + } + ResolutionError::BindingInNeverPattern => { + self.dcx().create_err(diagnostics::BindingInNeverPattern { span }) + } + } + } -#[derive(Diagnostic)] -#[diag("cannot determine resolution for the import")] -pub(crate) struct CannotDetermineImportResolution { - #[primary_span] - pub(crate) span: Span, -} + pub(crate) fn report_vis_error( + &mut self, + vis_resolution_error: VisResolutionError, + ) -> ErrorGuaranteed { + match vis_resolution_error { + VisResolutionError::Relative2018(span, path) => { + self.dcx().create_err(diagnostics::Relative2018 { + span, + path_span: path.span, + // intentionally converting to String, as the text would also be used as + // in suggestion context + path_str: pprust::path_to_string(&path), + }) + } + VisResolutionError::AncestorOnly(span) => { + self.dcx().create_err(diagnostics::AncestorOnly(span)) + } + VisResolutionError::FailedToResolve(span, segment, label, suggestion, message) => self + .into_struct_error( + span, + ResolutionError::FailedToResolve { + segment, + label, + suggestion, + module: None, + message, + }, + ), + VisResolutionError::ExpectedFound(span, path_str, res) => { + self.dcx().create_err(diagnostics::ExpectedModuleFound { span, res, path_str }) + } + VisResolutionError::Indeterminate(span) => { + self.dcx().create_err(diagnostics::Indeterminate(span)) + } + VisResolutionError::ModuleOnly(span) => { + self.dcx().create_err(diagnostics::ModuleOnly(span)) + } + } + .emit() + } -#[derive(Diagnostic)] -#[diag("cannot determine resolution for the {$kind} `{$path}`")] -#[note("import resolution is stuck, try simplifying macro imports")] -pub(crate) struct CannotDetermineMacroResolution { - #[primary_span] - pub(crate) span: Span, - pub(crate) kind: &'static str, - pub(crate) path: String, -} + pub(crate) fn def_path_str(&self, mut def_id: DefId) -> String { + // We can't use `def_path_str` in resolve. + let mut path = vec![def_id]; + while let Some(parent) = self.tcx.opt_parent(def_id) { + def_id = parent; + path.push(def_id); + if def_id.is_top_level_module() { + break; + } + } + // We will only suggest importing directly if it is accessible through that path. + path.into_iter() + .rev() + .map(|def_id| { + self.tcx + .opt_item_name(def_id) + .map(|name| { + match ( + def_id.is_top_level_module(), + def_id.is_local(), + self.tcx.sess.edition(), + ) { + (true, true, Edition::Edition2015) => String::new(), + (true, true, _) => kw::Crate.to_string(), + (true, false, _) | (false, _, _) => name.to_string(), + } + }) + .unwrap_or_else(|| "_".to_string()) + }) + .collect::>() + .join("::") + } -#[derive(Diagnostic)] -#[diag("`{$ident}` is private, and cannot be re-exported", code = E0364)] -pub(crate) struct CannotBeReexportedPrivate { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + pub(crate) fn add_scope_set_candidates( + &mut self, + suggestions: &mut Vec, + scope_set: ScopeSet<'ra>, + ps: &ParentScope<'ra>, + sp: Span, + filter_fn: &impl Fn(Res) -> bool, + ) { + let ctxt = Macros20NormalizedSyntaxContext::new(sp.ctxt()); + self.cm().visit_scopes(scope_set, ps, ctxt, sp, None, |this, scope, use_prelude, _| { + match scope { + Scope::DeriveHelpers(expn_id) => { + let res = Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper); + if filter_fn(res) { + suggestions.extend( + this.helper_attrs.get(&expn_id).into_iter().flatten().map( + |&(ident, orig_ident_span, _)| { + TypoSuggestion::new(ident.name, orig_ident_span, res) + }, + ), + ); + } + } + Scope::DeriveHelpersCompat => { + // Never recommend deprecated helper attributes. + } + Scope::MacroRules(macro_rules_scope) => { + if let MacroRulesScope::Def(macro_rules_def) = macro_rules_scope.get() { + let res = macro_rules_def.decl.res(); + if filter_fn(res) { + suggestions.push(TypoSuggestion::new( + macro_rules_def.ident.name, + macro_rules_def.orig_ident_span, + res, + )) + } + } + } + Scope::ModuleNonGlobs(module, _) => { + this.add_module_candidates(module, suggestions, filter_fn, None); + } + Scope::ModuleGlobs(..) => { + // Already handled in `ModuleNonGlobs`. + } + Scope::MacroUsePrelude => { + suggestions.extend(this.macro_use_prelude.iter().filter_map( + |(name, binding)| { + let res = binding.res(); + filter_fn(res).then_some(TypoSuggestion::typo_from_name(*name, res)) + }, + )); + } + Scope::BuiltinAttrs => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Builtin(sym::dummy)); + if filter_fn(res) { + suggestions.extend( + BUILTIN_ATTRIBUTES + .iter() + // These trace attributes are compiler-generated and have + // deliberately invalid names. + .filter(|attr| { + !matches!(**attr, sym::cfg_trace | sym::cfg_attr_trace) + }) + .map(|attr| TypoSuggestion::typo_from_name(*attr, res)), + ); + } + } + Scope::ExternPreludeItems => { + // Add idents from both item and flag scopes. + suggestions.extend(this.extern_prelude.iter().filter_map(|(ident, entry)| { + let res = Res::Def(DefKind::Mod, CRATE_DEF_ID.to_def_id()); + filter_fn(res).then_some(TypoSuggestion::new(ident.name, entry.span(), res)) + })); + } + Scope::ExternPreludeFlags => {} + Scope::ToolPrelude => { + let res = Res::NonMacroAttr(NonMacroAttrKind::Tool); + suggestions.extend( + this.registered_tools + .iter() + .map(|ident| TypoSuggestion::new(ident.name, ident.span, res)), + ); + } + Scope::StdLibPrelude => { + if let Some(prelude) = this.prelude { + let mut tmp_suggestions = Vec::new(); + this.add_module_candidates(prelude, &mut tmp_suggestions, filter_fn, None); + suggestions.extend( + tmp_suggestions + .into_iter() + .filter(|s| use_prelude.into() || this.is_builtin_macro(s.res)), + ); + } + } + Scope::BuiltinTypes => { + suggestions.extend(PrimTy::ALL.iter().filter_map(|prim_ty| { + let res = Res::PrimTy(*prim_ty); + filter_fn(res) + .then_some(TypoSuggestion::typo_from_name(prim_ty.name(), res)) + })) + } + } -#[derive(Diagnostic)] -#[diag("`{$ident}` is only public within the crate, and cannot be re-exported outside", code = E0364)] -pub(crate) struct CannotBeReexportedCratePublic { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + ControlFlow::<()>::Continue(()) + }); + } -#[derive(Diagnostic)] -#[diag("`{$ident}` is private, and cannot be re-exported", code = E0365)] -#[note("consider declaring type or module `{$ident}` with `pub`")] -pub(crate) struct CannotBeReexportedPrivateNS { - #[primary_span] - #[label("re-export of private `{$ident}`")] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + /// Lookup typo candidate in scope for a macro or import. + fn early_lookup_typo_candidate( + &mut self, + scope_set: ScopeSet<'ra>, + parent_scope: &ParentScope<'ra>, + ident: Ident, + filter_fn: &impl Fn(Res) -> bool, + ) -> Option { + let mut suggestions = Vec::new(); + self.add_scope_set_candidates( + &mut suggestions, + scope_set, + parent_scope, + ident.span, + filter_fn, + ); + + // Make sure error reporting is deterministic. + suggestions.sort_by(|a, b| a.candidate.as_str().cmp(b.candidate.as_str())); + + match find_best_match_for_name( + &suggestions.iter().map(|suggestion| suggestion.candidate).collect::>(), + ident.name, + None, + ) { + Some(found) if found != ident.name => { + suggestions.into_iter().find(|suggestion| suggestion.candidate == found) + } + _ => None, + } + } -#[derive(Diagnostic)] -#[diag("`{$ident}` is only public within the crate, and cannot be re-exported outside", code = E0365)] -#[note("consider declaring type or module `{$ident}` with `pub`")] -pub(crate) struct CannotBeReexportedCratePublicNS { - #[primary_span] - #[label("re-export of crate public `{$ident}`")] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + fn lookup_import_candidates_from_module( + &self, + lookup_ident: Ident, + namespace: Namespace, + parent_scope: &ParentScope<'ra>, + start_module: Module<'ra>, + crate_path: ThinVec, + filter_fn: FilterFn, + ) -> Vec + where + FilterFn: Fn(Res) -> bool, + { + let mut candidates = Vec::new(); + let mut seen_modules = FxHashSet::default(); + let start_did = start_module.def_id(); + let mut worklist = vec![( + start_module, + ThinVec::::new(), + true, + start_did.is_local() || !self.tcx.is_doc_hidden(start_did), + true, + )]; + let mut worklist_via_import = vec![]; + + while let Some((in_module, path_segments, accessible, doc_visible, is_stable)) = + match worklist.pop() { + None => worklist_via_import.pop(), + Some(x) => Some(x), + } + { + let in_module_is_extern = !in_module.def_id().is_local(); + in_module.for_each_child(self, |this, ident, orig_ident_span, ns, name_binding| { + // Avoid non-importable candidates. + if name_binding.is_assoc_item() + && !this.tcx.features().import_trait_associated_functions() + { + return; + } + + if ident.name == kw::Underscore { + return; + } + + let child_accessible = + accessible && this.is_accessible_from(name_binding.vis(), parent_scope.module); + + // do not venture inside inaccessible items of other crates + if in_module_is_extern && !child_accessible { + return; + } + + let via_import = name_binding.is_import() && !name_binding.is_extern_crate(); + + // There is an assumption elsewhere that paths of variants are in the enum's + // declaration and not imported. With this assumption, the variant component is + // chopped and the rest of the path is assumed to be the enum's own path. For + // errors where a variant is used as the type instead of the enum, this causes + // funny looking invalid suggestions, i.e `foo` instead of `foo::MyEnum`. + if via_import && name_binding.is_possibly_imported_variant() { + return; + } + + // #90113: Do not count an inaccessible reexported item as a candidate. + if let DeclKind::Import { source_decl, .. } = name_binding.kind + && this.is_accessible_from(source_decl.vis(), parent_scope.module) + && !this.is_accessible_from(name_binding.vis(), parent_scope.module) + { + return; + } + + let res = name_binding.res(); + let did = match res { + Res::Def(DefKind::Ctor(..), did) => this.tcx.opt_parent(did), + _ => res.opt_def_id(), + }; + let child_doc_visible = doc_visible + && did.is_none_or(|did| did.is_local() || !this.tcx.is_doc_hidden(did)); + + // collect results based on the filter function + // avoid suggesting anything from the same module in which we are resolving + // avoid suggesting anything with a hygienic name + if ident.name == lookup_ident.name + && ns == namespace + && in_module != parent_scope.module + && ident.ctxt.is_root() + && filter_fn(res) + { + // create the path + let mut segms = if lookup_ident.span.at_least_rust_2018() { + // crate-local absolute paths start with `crate::` in edition 2018 + // FIXME: may also be stabilized for Rust 2015 (Issues #45477, #44660) + crate_path.clone() + } else { + ThinVec::new() + }; + segms.append(&mut path_segments.clone()); + + segms.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + let path = Path { span: name_binding.span, segments: segms, tokens: None }; + + if child_accessible + // Remove invisible match if exists + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.accessible) + { + candidates.remove(idx); + } + + let is_stable = if is_stable + && let Some(did) = did + && this.is_stable(did, path.span) + { + true + } else { + false + }; + + // Rreplace unstable suggestions if we meet a new stable one, + // and do nothing if any other situation. For example, if we + // meet `std::ops::Range` after `std::range::legacy::Range`, + // we will remove the latter and then insert the former. + if is_stable + && let Some(idx) = candidates + .iter() + .position(|v: &ImportSuggestion| v.did == did && !v.is_stable) + { + candidates.remove(idx); + } + + if candidates.iter().all(|v: &ImportSuggestion| v.did != did) { + // See if we're recommending TryFrom, TryInto, or FromIterator and add + // a note about editions + let note = if let Some(did) = did { + let requires_note = !did.is_local() + && find_attr!( + this.tcx, + did, + RustcDiagnosticItem( + sym::TryInto | sym::TryFrom | sym::FromIterator + ) + ); + requires_note.then(|| { + format!( + "'{}' is included in the prelude starting in Edition 2021", + path_names_to_string(&path) + ) + }) + } else { + None + }; + + candidates.push(ImportSuggestion { + did, + descr: res.descr(), + path, + accessible: child_accessible, + doc_visible: child_doc_visible, + note, + via_import, + is_stable, + }); + } + } + + // collect submodules to explore + if let Some(def_id) = name_binding.res().module_like_def_id() { + // form the path + let mut path_segments = path_segments.clone(); + path_segments.push(ast::PathSegment::from_ident(ident.orig(orig_ident_span))); + + let alias_import = if let DeclKind::Import { import, .. } = name_binding.kind + && let ImportKind::ExternCrate { source: Some(_), .. } = import.kind + && import.parent_scope.expansion == parent_scope.expansion + { + true + } else { + false + }; + + let is_extern_crate_that_also_appears_in_prelude = + name_binding.is_extern_crate() && lookup_ident.span.at_least_rust_2018(); + + if !is_extern_crate_that_also_appears_in_prelude || alias_import { + // add the module to the lookup + if seen_modules.insert(def_id) { + if via_import { &mut worklist_via_import } else { &mut worklist }.push( + ( + this.expect_module(def_id), + path_segments, + child_accessible, + child_doc_visible, + is_stable && this.is_stable(def_id, name_binding.span), + ), + ); + } + } + } + }) + } -#[derive(Diagnostic)] -#[diag("extern crate `{$ident}` is private and cannot be re-exported", code = E0365)] -pub(crate) struct PrivateExternCrateReexport { - pub ident: Ident, - #[suggestion( - "consider making the `extern crate` item publicly accessible", - code = "pub ", - style = "verbose", - applicability = "maybe-incorrect" - )] - pub sugg: Span, -} + candidates + } -#[derive(Subdiagnostic)] -#[help("consider adding a `#[macro_export]` to the macro in the imported module")] -pub(crate) struct ConsiderAddingMacroExport { - #[primary_span] - pub(crate) span: Span, -} + fn is_stable(&self, did: DefId, span: Span) -> bool { + if did.is_local() { + return true; + } -#[derive(Subdiagnostic)] -#[suggestion( - "in case you want to use the macro within this crate only, reduce the visibility to `pub(crate)`", - code = "pub(crate)", - applicability = "maybe-incorrect" -)] -pub(crate) struct ConsiderMarkingAsPubCrate { - #[primary_span] - pub(crate) vis_span: Span, -} + match self.tcx.lookup_stability(did) { + Some(Stability { + level: StabilityLevel::Unstable { implied_by, .. }, feature, .. + }) => { + if span.allows_unstable(feature) { + true + } else if self.tcx.features().enabled(feature) { + true + } else if let Some(implied_by) = implied_by + && self.tcx.features().enabled(implied_by) + { + true + } else { + false + } + } + Some(_) => true, + None => false, + } + } -#[derive(Subdiagnostic)] -#[note("consider marking `{$ident}` as `pub` in the imported module")] -pub(crate) struct ConsiderMarkingAsPub { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + /// When name resolution fails, this method can be used to look up candidate + /// entities with the expected name. It allows filtering them using the + /// supplied predicate (which should be used to only accept the types of + /// definitions expected, e.g., traits). The lookup spans across all crates. + /// + /// N.B., the method does not look into imports, but this is not a problem, + /// since we report the definitions (thus, the de-aliased imports). + pub(crate) fn lookup_import_candidates( + &mut self, + lookup_ident: Ident, + namespace: Namespace, + parent_scope: &ParentScope<'ra>, + filter_fn: FilterFn, + ) -> Vec + where + FilterFn: Fn(Res) -> bool, + { + let crate_path = thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(kw::Crate))]; + let mut suggestions = self.lookup_import_candidates_from_module( + lookup_ident, + namespace, + parent_scope, + self.graph_root.to_module(), + crate_path, + &filter_fn, + ); + + if lookup_ident.span.at_least_rust_2018() { + for (ident, entry) in &self.extern_prelude { + if entry.span().from_expansion() { + // Idents are adjusted to the root context before being + // resolved in the extern prelude, so reporting this to the + // user is no help. This skips the injected + // `extern crate std` in the 2018 edition, which would + // otherwise cause duplicate suggestions. + continue; + } + let Some(crate_id) = + self.cstore_mut().maybe_process_path_extern(self.tcx, ident.name) + else { + continue; + }; + + let crate_def_id = crate_id.as_def_id(); + let crate_root = self.expect_module(crate_def_id); + + // Check if there's already an item in scope with the same name as the crate. + // If so, we have to disambiguate the potential import suggestions by making + // the paths *global* (i.e., by prefixing them with `::`). + let needs_disambiguation = + self.resolutions(parent_scope.module).borrow().iter().any( + |(key, name_resolution)| { + if key.ns == TypeNS + && key.ident == *ident + && let Some(decl) = name_resolution.borrow().best_decl() + { + match decl.res() { + // No disambiguation needed if the identically named item we + // found in scope actually refers to the crate in question. + Res::Def(_, def_id) => def_id != crate_def_id, + Res::PrimTy(_) => true, + _ => false, + } + } else { + false + } + }, + ); + let mut crate_path = ThinVec::new(); + if needs_disambiguation { + crate_path.push(ast::PathSegment::path_root(rustc_span::DUMMY_SP)); + } + crate_path.push(ast::PathSegment::from_ident(ident.orig(entry.span()))); + + suggestions.extend(self.lookup_import_candidates_from_module( + lookup_ident, + namespace, + parent_scope, + crate_root, + crate_path, + &filter_fn, + )); + } + } -#[derive(Diagnostic)] -#[diag("cannot glob-import all possible crates")] -pub(crate) struct CannotGlobImportAllCrates { - #[primary_span] - pub(crate) span: Span, -} + suggestions.retain(|suggestion| suggestion.is_stable || self.tcx.sess.is_nightly_build()); + suggestions + } -#[derive(Subdiagnostic)] -#[suggestion( - "you might have meant to write a const parameter here", - code = "const ", - style = "verbose" -)] -pub(crate) struct UnexpectedResChangeTyToConstParamSugg { - #[primary_span] - pub span: Span, - #[applicability] - pub applicability: Applicability, -} + pub(crate) fn unresolved_macro_suggestions( + &mut self, + err: &mut Diag<'_>, + macro_kind: MacroKind, + parent_scope: &ParentScope<'ra>, + ident: Ident, + krate: &Crate, + sugg_span: Option, + ) { + // Bring all unused `derive` macros into `macro_map` so we ensure they can be used for + // suggestions. + self.register_macros_for_all_crates(); + + let is_expected = + &|res: Res| res.macro_kinds().is_some_and(|k| k.contains(macro_kind.into())); + let suggestion = self.early_lookup_typo_candidate( + ScopeSet::Macro(macro_kind), + parent_scope, + ident, + is_expected, + ); + if !self.add_typo_suggestion(err, suggestion, ident.span) { + self.detect_derive_attribute(err, ident, parent_scope, sugg_span); + } -#[derive(Subdiagnostic)] -#[suggestion( - "you might have meant to introduce a const parameter `{$item_name}` on the {$item_location}", - code = "{snippet}", - applicability = "machine-applicable", - style = "verbose" -)] -pub(crate) struct UnexpectedMissingConstParameter { - #[primary_span] - pub span: Span, - pub snippet: String, - pub item_name: String, - pub item_location: String, -} + let import_suggestions = + self.lookup_import_candidates(ident, Namespace::MacroNS, parent_scope, is_expected); + let (span, found_use) = match parent_scope.module.nearest_parent_mod_node_id() { + DUMMY_NODE_ID => (None, FoundUse::No), + node_id => UsePlacementFinder::check(krate, node_id), + }; + show_candidates( + self.tcx, + err, + span, + &import_suggestions, + Instead::No, + found_use, + DiagMode::Normal, + vec![], + "", + ); + + if macro_kind == MacroKind::Bang && ident.name == sym::macro_rules { + let label_span = ident.span.shrink_to_hi(); + let mut spans = MultiSpan::from_span(label_span); + spans.push_span_label(label_span, "put a macro name here"); + err.subdiagnostic(MaybeMissingMacroRulesName { spans }); + return; + } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "you might have meant to write a const parameter here", - applicability = "has-placeholders", - style = "verbose" -)] -pub(crate) struct UnexpectedResChangeTyParamToConstParamSugg { - #[suggestion_part(code = "const ")] - pub before: Span, - #[suggestion_part(code = ": /* Type */")] - pub after: Span, -} + if macro_kind == MacroKind::Derive && (ident.name == sym::Send || ident.name == sym::Sync) { + err.subdiagnostic(ExplicitUnsafeTraits { span: ident.span, ident }); + return; + } -#[derive(Subdiagnostic)] -#[suggestion( - "if you meant to collect the rest of the slice in `{$ident}`, use the at operator", - code = "{snippet}", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct UnexpectedResUseAtOpInSlicePatWithRangeSugg { - #[primary_span] - pub span: Span, - pub ident: Ident, - pub snippet: String, -} + let unused_macro = self.unused_macros.iter().find_map(|(def_id, (_, unused_ident))| { + if unused_ident.name == ident.name { Some((def_id, unused_ident)) } else { None } + }); + + if let Some((def_id, unused_ident)) = unused_macro { + let scope = self.local_macro_def_scopes[&def_id]; + let parent_nearest = parent_scope.module.nearest_parent_mod(); + let unused_macro_kinds = self.local_macro_map[def_id].macro_kinds(); + if !unused_macro_kinds.contains(macro_kind.into()) { + match macro_kind { + MacroKind::Bang => { + err.subdiagnostic(MacroRulesNot::Func { span: unused_ident.span, ident }); + } + MacroKind::Attr => { + err.subdiagnostic(MacroRulesNot::Attr { span: unused_ident.span, ident }); + } + MacroKind::Derive => { + err.subdiagnostic(MacroRulesNot::Derive { span: unused_ident.span, ident }); + } + } + return; + } + if Some(parent_nearest) == scope.opt_def_id() { + err.subdiagnostic(MacroDefinedLater { span: unused_ident.span }); + err.subdiagnostic(MacroSuggMovePosition { span: ident.span, ident }); + return; + } + } -#[derive(Diagnostic)] -#[diag("an `extern crate` loading macros must be at the crate root", code = E0468)] -pub(crate) struct ExternCrateLoadingMacroNotAtCrateRoot { - #[primary_span] - pub(crate) span: Span, -} + if ident.name == kw::Default + && let ModuleKind::Def(DefKind::Enum, def_id, _, _) = parent_scope.module.kind + { + let span = self.def_span(def_id); + let source_map = self.tcx.sess.source_map(); + let head_span = source_map.guess_head_span(span); + err.subdiagnostic(ConsiderAddingADerive { + span: head_span.shrink_to_lo(), + suggestion: "#[derive(Default)]\n".to_string(), + }); + } + for ns in [Namespace::MacroNS, Namespace::TypeNS, Namespace::ValueNS] { + let Ok(binding) = self.cm().resolve_ident_in_scope_set( + ident, + ScopeSet::All(ns), + parent_scope, + None, + None, + None, + ) else { + continue; + }; + + let desc = match binding.res() { + Res::Def(DefKind::Macro(MacroKinds::BANG), _) => { + "a function-like macro".to_string() + } + Res::Def(DefKind::Macro(MacroKinds::ATTR), _) | Res::NonMacroAttr(..) => { + format!("an attribute: `#[{ident}]`") + } + Res::Def(DefKind::Macro(MacroKinds::DERIVE), _) => { + format!("a derive macro: `#[derive({ident})]`") + } + Res::Def(DefKind::Macro(kinds), _) => { + format!("{} {}", kinds.article(), kinds.descr()) + } + Res::ToolMod | Res::OpenMod(..) => { + // Don't confuse the user with tool modules or open modules. + continue; + } + Res::Def(DefKind::Trait, _) if macro_kind == MacroKind::Derive => { + "only a trait, without a derive macro".to_string() + } + res => format!( + "{} {}, not {} {}", + res.article(), + res.descr(), + macro_kind.article(), + macro_kind.descr_expected(), + ), + }; + if let crate::DeclKind::Import { import, .. } = binding.kind + && !import.span.is_dummy() + { + let note = diagnostics::IdentImporterHereButItIsDesc { + span: import.span, + imported_ident: ident, + imported_ident_desc: &desc, + }; + err.subdiagnostic(note); + // Silence the 'unused import' warning we might get, + // since this diagnostic already covers that import. + self.record_use(ident, binding, Used::Other); + return; + } + let note = diagnostics::IdentInScopeButItIsDesc { + imported_ident: ident, + imported_ident_desc: &desc, + }; + err.subdiagnostic(note); + return; + } -#[derive(Diagnostic)] -#[diag("`extern crate self;` requires renaming")] -pub(crate) struct ExternCrateSelfRequiresRenaming { - #[primary_span] - #[suggestion( - "rename the `self` crate to be able to import it", - code = "extern crate self as name;", - applicability = "has-placeholders" - )] - pub(crate) span: Span, -} + if self.macro_names.contains(&IdentKey::new(ident)) { + err.subdiagnostic(AddedMacroUse); + return; + } + } -#[derive(Diagnostic)] -#[diag("`{$name}` is already in scope")] -#[note("macro-expanded `#[macro_use]`s may not shadow existing macros (see RFC 1560)")] -pub(crate) struct MacroUseNameAlreadyInUse { - #[primary_span] - pub(crate) span: Span, - pub(crate) name: Symbol, -} + /// Given an attribute macro that failed to be resolved, look for `derive` macros that could + /// provide it, either as-is or with small typos. + fn detect_derive_attribute( + &self, + err: &mut Diag<'_>, + ident: Ident, + parent_scope: &ParentScope<'ra>, + sugg_span: Option, + ) { + // Find all of the `derive`s in scope and collect their corresponding declared + // attributes. + // FIXME: this only works if the crate that owns the macro that has the helper_attr + // has already been imported. + let mut derives = vec![]; + let mut all_attrs: UnordMap> = UnordMap::default(); + // We're collecting these in a hashmap, and handle ordering the output further down. + #[allow(rustc::potential_query_instability)] + for (def_id, ext) in self + .local_macro_map + .iter() + .map(|(local_id, ext)| (local_id.to_def_id(), ext)) + .chain(self.extern_macro_map.borrow().iter().map(|(id, d)| (*id, d))) + { + for helper_attr in &ext.helper_attrs { + let item_name = self.tcx.item_name(def_id); + all_attrs.entry(*helper_attr).or_default().push(item_name); + if helper_attr == &ident.name { + derives.push(item_name); + } + } + } + let kind = MacroKind::Derive.descr(); + if !derives.is_empty() { + // We found an exact match for the missing attribute in a `derive` macro. Suggest it. + let mut derives: Vec = derives.into_iter().map(|d| d.to_string()).collect(); + derives.sort(); + derives.dedup(); + let msg = match &derives[..] { + [derive] => format!(" `{derive}`"), + [start @ .., last] => format!( + "s {} and `{last}`", + start.iter().map(|d| format!("`{d}`")).collect::>().join(", ") + ), + [] => unreachable!("we checked for this to be non-empty 10 lines above!?"), + }; + let msg = format!( + "`{}` is an attribute that can be used by the {kind}{msg}, you might be \ + missing a `derive` attribute", + ident.name, + ); + let sugg_span = + if let ModuleKind::Def(DefKind::Enum, id, _, _) = parent_scope.module.kind { + let span = self.def_span(id); + if span.from_expansion() { + None + } else { + // For enum variants sugg_span is empty but we can get the enum's Span. + Some(span.shrink_to_lo()) + } + } else { + // For items this `Span` will be populated, everything else it'll be None. + sugg_span + }; + match sugg_span { + Some(span) => { + err.span_suggestion_verbose( + span, + msg, + format!("#[derive({})]\n", derives.join(", ")), + Applicability::MaybeIncorrect, + ); + } + None => { + err.note(msg); + } + } + } else { + // We didn't find an exact match. Look for close matches. If any, suggest fixing typo. + let all_attr_names = all_attrs.keys().map(|s| *s).into_sorted_stable_ord(); + if let Some(best_match) = find_best_match_for_name(&all_attr_names, ident.name, None) + && let Some(macros) = all_attrs.get(&best_match) + { + let mut macros: Vec = macros.into_iter().map(|d| d.to_string()).collect(); + macros.sort(); + macros.dedup(); + let msg = match ¯os[..] { + [] => return, + [name] => format!(" `{name}` accepts"), + [start @ .., end] => format!( + "s {} and `{end}` accept", + start.iter().map(|m| format!("`{m}`")).collect::>().join(", "), + ), + }; + let msg = format!("the {kind}{msg} the similarly named `{best_match}` attribute"); + err.span_suggestion_verbose( + ident.span, + msg, + best_match, + Applicability::MaybeIncorrect, + ); + } + } + } -#[derive(Diagnostic)] -#[diag("imported macro not found", code = E0469)] -pub(crate) struct ImportedMacroNotFound { - #[primary_span] - pub(crate) span: Span, -} + pub(crate) fn add_typo_suggestion( + &self, + err: &mut Diag<'_>, + suggestion: Option, + span: Span, + ) -> bool { + let suggestion = match suggestion { + None => return false, + // We shouldn't suggest underscore. + Some(suggestion) if suggestion.candidate == kw::Underscore => return false, + Some(suggestion) => suggestion, + }; + + let mut did_label_def_span = false; + + if let Some(def_span) = suggestion.res.opt_def_id().map(|def_id| self.def_span(def_id)) { + if span.overlaps(def_span) { + // Don't suggest typo suggestion for itself like in the following: + // error[E0423]: expected function, tuple struct or tuple variant, found struct `X` + // --> $DIR/unicode-string-literal-syntax-error-64792.rs:4:14 + // | + // LL | struct X {} + // | ----------- `X` defined here + // LL | + // LL | const Y: X = X("ö"); + // | -------------^^^^^^- similarly named constant `Y` defined here + // | + // help: use struct literal syntax instead + // | + // LL | const Y: X = X {}; + // | ^^^^ + // help: a constant with a similar name exists + // | + // LL | const Y: X = Y("ö"); + // | ^ + return false; + } + let span = self.tcx.sess.source_map().guess_head_span(def_span); + let candidate_descr = suggestion.res.descr(); + let candidate = suggestion.candidate; + let label = match suggestion.target { + SuggestionTarget::SimilarlyNamed => { + diagnostics::DefinedHere::SimilarlyNamed { span, candidate_descr, candidate } + } + SuggestionTarget::SingleItem => { + diagnostics::DefinedHere::SingleItem { span, candidate_descr, candidate } + } + }; + did_label_def_span = true; + err.subdiagnostic(label); + } -#[derive(Diagnostic)] -#[diag("`#[macro_escape]` is a deprecated synonym for `#[macro_use]`")] -pub(crate) struct MacroExternDeprecated { - #[primary_span] - pub(crate) span: Span, - #[help("try an outer attribute: `#[macro_use]`")] - pub inner_attribute: bool, -} + let (span, msg, sugg) = if let SuggestionTarget::SimilarlyNamed = suggestion.target + && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) + && let Some(span) = suggestion.span + && let Some(candidate) = suggestion.candidate.as_str().strip_prefix('_') + && snippet == candidate + { + let candidate = suggestion.candidate; + // When the suggested binding change would be from `x` to `_x`, suggest changing the + // original binding definition instead. (#60164) + let msg = format!( + "the leading underscore in `{candidate}` marks it as unused, consider renaming it to `{snippet}`" + ); + if !did_label_def_span { + err.span_label(span, format!("`{candidate}` defined here")); + } + (span, msg, snippet) + } else { + let msg = match suggestion.target { + SuggestionTarget::SimilarlyNamed => format!( + "{} {} with a similar name exists", + suggestion.res.article(), + suggestion.res.descr() + ), + SuggestionTarget::SingleItem => { + format!("maybe you meant this {}", suggestion.res.descr()) + } + }; + (span, msg, suggestion.candidate.to_ident_string()) + }; + err.span_suggestion_verbose(span, msg, sugg, Applicability::MaybeIncorrect); + true + } -#[derive(Diagnostic)] -#[diag("arguments to `macro_use` are not allowed here")] -pub(crate) struct ArgumentsMacroUseNotAllowed { - #[primary_span] - pub(crate) span: Span, -} + fn decl_description(&self, b: Decl<'_>, ident: Ident, scope: Scope<'_>) -> String { + let res = b.res(); + if b.span.is_dummy() || !self.tcx.sess.source_map().is_span_accessible(b.span) { + let (built_in, from) = match scope { + Scope::StdLibPrelude | Scope::MacroUsePrelude => ("", " from prelude"), + Scope::ExternPreludeFlags + if self.tcx.sess.opts.externs.get(ident.as_str()).is_some() + || matches!(res, Res::OpenMod(..)) => + { + ("", " passed with `--extern`") + } + _ => { + if matches!(res, Res::NonMacroAttr(..) | Res::PrimTy(..) | Res::ToolMod) { + // These already contain the "built-in" prefix or look bad with it. + ("", "") + } else { + (" built-in", "") + } + } + }; + + let a = if built_in.is_empty() { res.article() } else { "a" }; + format!("{a}{built_in} {thing}{from}", thing = res.descr()) + } else { + let introduced = if b.is_import_user_facing() { "imported" } else { "defined" }; + format!("the {thing} {introduced} here", thing = res.descr()) + } + } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "try renaming it with a name", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct UnnamedImportSugg { - #[suggestion_part(code = "{ident} as name")] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + fn ambiguity_diagnostic( + &self, + ambiguity_error: &AmbiguityError<'ra>, + ) -> diagnostics::Ambiguity { + let AmbiguityError { kind, ambig_vis, ident, b1, b2, scope1, scope2, .. } = + *ambiguity_error; + let extern_prelude_ambiguity = || { + // Note: b1 may come from a module scope, as an extern crate item in module. + matches!(scope2, Scope::ExternPreludeFlags) + && self + .extern_prelude + .get(&IdentKey::new(ident)) + .is_some_and(|entry| entry.item_decl.map(|(b, ..)| b) == Some(b1)) + }; + let (b1, b2, scope1, scope2, swapped) = if b2.span.is_dummy() && !b1.span.is_dummy() { + // We have to print the span-less alternative first, otherwise formatting looks bad. + (b2, b1, scope2, scope1, true) + } else { + (b1, b2, scope1, scope2, false) + }; + + let could_refer_to = |b: Decl<'_>, scope: Scope<'ra>, also: &str| { + let what = self.decl_description(b, ident, scope); + let note_msg = format!("`{ident}` could{also} refer to {what}"); + + let thing = b.res().descr(); + let mut help_msgs = Vec::new(); + if b.is_glob_import() + && (kind == AmbiguityKind::GlobVsGlob + || kind == AmbiguityKind::GlobVsExpanded + || kind == AmbiguityKind::GlobVsOuter && swapped != also.is_empty()) + { + help_msgs.push(format!( + "consider adding an explicit import of `{ident}` to disambiguate" + )) + } + if b.is_extern_crate() && ident.span.at_least_rust_2018() && !extern_prelude_ambiguity() + { + help_msgs.push(format!("use `::{ident}` to refer to this {thing} unambiguously")) + } -#[derive(Diagnostic)] -#[diag("imports need to be explicitly named")] -pub(crate) struct UnnamedImport { - #[primary_span] - pub(crate) span: Span, - #[subdiagnostic] - pub(crate) sugg: UnnamedImportSugg, -} + if kind != AmbiguityKind::GlobVsGlob { + if let Scope::ModuleNonGlobs(module, _) | Scope::ModuleGlobs(module, _) = scope { + if module == self.graph_root.to_module() { + help_msgs.push(format!( + "use `crate::{ident}` to refer to this {thing} unambiguously" + )); + } else if module.is_normal() { + help_msgs.push(format!( + "use `self::{ident}` to refer to this {thing} unambiguously" + )); + } + } + } -#[derive(Diagnostic)] -#[diag("macro-expanded `extern crate` items cannot shadow names passed with `--extern`")] -pub(crate) struct MacroExpandedExternCrateCannotShadowExternArguments { - #[primary_span] - pub(crate) span: Span, -} + ( + Spanned { node: note_msg, span: b.span }, + help_msgs + .iter() + .enumerate() + .map(|(i, help_msg)| { + let or = if i == 0 { "" } else { "or " }; + format!("{or}{help_msg}") + }) + .collect::>(), + ) + }; + let (b1_note, b1_help_msgs) = could_refer_to(b1, scope1, ""); + let (b2_note, b2_help_msgs) = could_refer_to(b2, scope2, " also"); + let help = if kind == AmbiguityKind::GlobVsGlob + && b1 + .parent_module + .and_then(|m| m.opt_def_id()) + .map(|d| !d.is_local()) + .unwrap_or_default() + { + Some(&[ + "consider updating this dependency to resolve this error", + "if updating the dependency does not resolve the problem report the problem to the author of the relevant crate", + ] as &[_]) + } else { + None + }; + + let ambig_vis = ambig_vis.map(|(vis1, vis2)| { + format!( + "{} or {}", + vis1.to_string(CRATE_DEF_ID, self.tcx), + vis2.to_string(CRATE_DEF_ID, self.tcx) + ) + }); + + diagnostics::Ambiguity { + ident, + help, + ambig_vis, + kind: kind.descr(), + b1_note, + b1_help_msgs, + b2_note, + b2_help_msgs, + is_error: false, + } + } -#[derive(Diagnostic)] -#[diag("`&` without an explicit lifetime name cannot be used here", code = E0637)] -pub(crate) struct ElidedAnonymousLifetimeReportError { - #[primary_span] - #[label("explicit lifetime name needed here")] - pub(crate) span: Span, - #[subdiagnostic] - pub(crate) suggestion: Option, -} + /// If the binding refers to a tuple struct constructor with fields, + /// returns the span of its fields. + fn ctor_fields_span(&self, decl: Decl<'_>) -> Option { + let DeclKind::Def(Res::Def(DefKind::Ctor(CtorOf::Struct, CtorKind::Fn), ctor_def_id)) = + decl.kind + else { + return None; + }; + + let def_id = self.tcx.parent(ctor_def_id); + self.field_idents(def_id)?.iter().map(|&f| f.span).reduce(Span::to) // None for `struct Foo()` + } -#[derive(Diagnostic)] -#[diag( - "associated type `Iterator::Item` is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type" -)] -pub(crate) struct LendingIteratorReportError { - #[primary_span] - pub(crate) lifetime: Span, - #[note( - "you can't create an `Iterator` that borrows each `Item` from itself, but you can instead create a new type that borrows your existing type and implement `Iterator` for that new type" - )] - pub(crate) ty: Span, -} + /// Returns the path segments (as symbols) of a module, including `kw::Crate` at the start. + /// For example, for `crate::foo::bar`, returns `[Crate, foo, bar]`. + /// Returns `None` for block modules that don't have a `DefId`. + fn module_path_names(&self, module: Module<'ra>) -> Option> { + let mut path = Vec::new(); + let mut def_id = module.opt_def_id()?; + while let Some(parent) = self.tcx.opt_parent(def_id) { + if let Some(name) = self.tcx.opt_item_name(def_id) { + path.push(name); + } + if parent.is_top_level_module() { + break; + } + def_id = parent; + } + path.reverse(); + path.insert(0, kw::Crate); + Some(path) + } -#[derive(Diagnostic)] -#[diag("missing lifetime in associated type")] -pub(crate) struct AnonymousLifetimeNonGatReportError { - #[primary_span] - #[label("this lifetime must come from the implemented type")] - pub(crate) lifetime: Span, - #[note( - "in the trait the associated type is declared without lifetime parameters, so using a borrowed type for them requires that lifetime to come from the implemented type" - )] - pub(crate) decl: MultiSpan, -} + /// Shortens a candidate import path to use `super::` (up to 1 level) or `self::` (same module) + /// relative to the current scope, if possible. Only applies to crate-local items and + /// only when the resulting path is actually shorter than the original. + fn shorten_candidate_path( + &self, + suggestion: &mut ImportSuggestion, + current_module: Module<'ra>, + ) { + const MAX_SUPER_PATH_ITEMS_IN_SUGGESTION: usize = 1; + + // Only shorten local items. + if suggestion.did.is_none_or(|did| !did.is_local()) { + return; + } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "consider introducing a higher-ranked lifetime here", - applicability = "machine-applicable" -)] -pub(crate) struct ElidedAnonymousLifetimeReportErrorSuggestion { - #[suggestion_part(code = "for<'a> ")] - pub(crate) lo: Span, - #[suggestion_part(code = "'a ")] - pub(crate) hi: Span, -} + // Build current module path: [Crate, foo, bar, ...]. + let Some(current_mod_path) = self.module_path_names(current_module) else { + return; + }; + + // Normalise candidate path: filter out `PathRoot` (`::`), and if the path + // doesn't start with `Crate`, prepend it (edition 2015 paths are relative + // to the crate root without an explicit `crate::` prefix). + let candidate_names = { + let filtered_segments: Vec<_> = suggestion + .path + .segments + .iter() + .filter(|segment| segment.ident.name != kw::PathRoot) + .collect(); + + let mut candidate_names: Vec = + filtered_segments.iter().map(|segment| segment.ident.name).collect(); + if candidate_names.first() != Some(&kw::Crate) { + candidate_names.insert(0, kw::Crate); + } + if candidate_names.len() < 2 { + return; + } + candidate_names + }; + + // The candidate's module path is everything except the last segment (the item name). + let candidate_mod_names = &candidate_names[..candidate_names.len() - 1]; + + // Find the longest common prefix between the current module and candidate module paths. + let common_prefix_length = current_mod_path + .iter() + .zip(candidate_mod_names.iter()) + .take_while(|(current, candidate)| current == candidate) + .count(); + + // Non-crate-local item; keep the full absolute path. + if common_prefix_length == 0 { + return; + } -#[derive(Diagnostic)] -#[diag("`'_` cannot be used here", code = E0637)] -pub(crate) struct ExplicitAnonymousLifetimeReportError { - #[primary_span] - #[label("`'_` is a reserved lifetime name")] - pub(crate) span: Span, -} + let super_count = current_mod_path.len() - common_prefix_length; -#[derive(Diagnostic)] -#[diag("implicit elided lifetime not allowed here", code = E0726)] -pub(crate) struct ImplicitElidedLifetimeNotAllowedHere { - #[primary_span] - pub(crate) span: Span, - #[subdiagnostic] - pub(crate) subdiag: ElidedLifetimeInPathSubdiag, -} + // At the crate root, `use` paths resolve from the crate root anyway, so we can + // drop the `crate::` prefix entirely instead of replacing it with `self::`. + let at_crate_root = current_mod_path.len() == 1; -#[derive(Diagnostic)] -#[diag("`'_` cannot be used here", code = E0637)] -#[help("use another lifetime specifier")] -pub(crate) struct UnderscoreLifetimeIsReserved { - #[primary_span] - #[label("`'_` is a reserved lifetime name")] - pub(crate) span: Span, -} + let mut new_segments = if super_count == 0 && at_crate_root { + ThinVec::new() + } else { + let prefix_keyword = match super_count { + 0 => kw::SelfLower, + 1..=MAX_SUPER_PATH_ITEMS_IN_SUGGESTION => kw::Super, + _ => return, // Too many `super` levels; keep the full absolute path. + }; + thin_vec![ast::PathSegment::from_ident(Ident::with_dummy_span(prefix_keyword),)] + }; + for &name in &candidate_names[common_prefix_length..] { + new_segments.push(ast::PathSegment::from_ident(Ident::with_dummy_span(name))); + } -#[derive(Diagnostic)] -#[diag("invalid lifetime parameter name: `{$lifetime}`", code = E0262)] -pub(crate) struct StaticLifetimeIsReserved { - #[primary_span] - #[label("'static is a reserved lifetime name")] - pub(crate) span: Span, - pub(crate) lifetime: Ident, -} + // Only apply if the result is strictly shorter than the original path. + if new_segments.len() >= suggestion.path.segments.len() { + return; + } -#[derive(Diagnostic)] -#[diag("variable `{$name}` is not bound in all patterns", code = E0408)] -pub(crate) struct VariableIsNotBoundInAllPatterns { - #[primary_span] - pub(crate) multispan: MultiSpan, - pub(crate) name: Ident, -} + suggestion.path = Path { span: suggestion.path.span, segments: new_segments, tokens: None }; + } -#[derive(Subdiagnostic, Debug, Clone)] -#[label("pattern doesn't bind `{$name}`")] -pub(crate) struct PatternDoesntBindName { - #[primary_span] - pub(crate) span: Span, - pub(crate) name: Ident, -} + fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { + let PrivacyError { + ident, + decl, + outermost_res, + parent_scope, + single_nested, + dedup_span, + ref source, + } = *privacy_error; + + let res = decl.res(); + let ctor_fields_span = self.ctor_fields_span(decl); + let plain_descr = res.descr().to_string(); + let nonimport_descr = + if ctor_fields_span.is_some() { plain_descr + " constructor" } else { plain_descr }; + let import_descr = nonimport_descr.clone() + " import"; + let get_descr = |b: Decl<'_>| if b.is_import() { &import_descr } else { &nonimport_descr }; + + // Print the primary message. + let ident_descr = get_descr(decl); + let mut err = + self.dcx().create_err(diagnostics::IsPrivate { span: ident.span, ident_descr, ident }); + + self.mention_default_field_values(source, ident, &mut err); + + let shown_candidates = if let Some((this_res, outer_ident)) = outermost_res { + let mut import_suggestions = self.lookup_import_candidates( + outer_ident, + this_res.ns().unwrap_or(Namespace::TypeNS), + &parent_scope, + &|res: Res| res == this_res, + ); + // Shorten candidate paths using `super::` or `self::` when possible. + for suggestion in &mut import_suggestions { + self.shorten_candidate_path(suggestion, parent_scope.module); + } + let point_to_def = !show_candidates( + self.tcx, + &mut err, + Some(dedup_span.until(outer_ident.span.shrink_to_hi())), + &import_suggestions, + Instead::Yes, + FoundUse::Yes, + DiagMode::Import { append: single_nested, unresolved_import: false }, + vec![], + "", + ); + // If we suggest importing a public re-export, don't point at the definition. + if point_to_def && ident.span != outer_ident.span { + let label = diagnostics::OuterIdentIsNotPubliclyReexported { + span: outer_ident.span, + outer_ident_descr: this_res.descr(), + outer_ident, + }; + err.subdiagnostic(label); + } + !point_to_def + } else { + false + }; + + let mut non_exhaustive = None; + // If an ADT is foreign and marked as `non_exhaustive`, then that's + // probably why we have the privacy error. + // Otherwise, point out if the struct has any private fields. + if let Some(def_id) = res.opt_def_id() + && !def_id.is_local() + && let Some(attr_span) = find_attr!(self.tcx, def_id, NonExhaustive(span) => *span) + { + non_exhaustive = Some(attr_span); + } else if let Some(span) = ctor_fields_span { + let label = diagnostics::ConstructorPrivateIfAnyFieldPrivate { span }; + err.subdiagnostic(label); + if let Res::Def(_, d) = res + && let Some(fields) = self.field_visibility_spans.get(&d) + { + let spans = fields.iter().map(|span| *span).collect(); + let sugg = diagnostics::ConsiderMakingTheFieldPublic { + spans, + number_of_fields: fields.len(), + }; + err.subdiagnostic(sugg); + } + } -#[derive(Subdiagnostic, Debug, Clone)] -#[label("variable not in all patterns")] -pub(crate) struct VariableNotInAllPatterns { - #[primary_span] - pub(crate) span: Span, -} + let mut sugg_paths: Vec<(Vec, bool)> = vec![]; + if let Some(mut def_id) = res.opt_def_id() { + // We can't use `def_path_str` in resolve. + let mut path = vec![def_id]; + while let Some(parent) = self.tcx.opt_parent(def_id) { + def_id = parent; + if !def_id.is_top_level_module() { + path.push(def_id); + } else { + break; + } + } + // We will only suggest importing directly if it is accessible through that path. + let path_names: Option> = path + .iter() + .rev() + .map(|def_id| { + self.tcx.opt_item_name(*def_id).map(|name| { + Ident::with_dummy_span(if def_id.is_top_level_module() { + kw::Crate + } else { + name + }) + }) + }) + .collect(); + if let Some(&def_id) = path.get(0) + && let Some(path) = path_names + { + if let Some(def_id) = def_id.as_local() { + if self.effective_visibilities.is_directly_public(def_id) { + sugg_paths.push((path, false)); + } + } else if self.is_accessible_from(self.tcx.visibility(def_id), parent_scope.module) + { + sugg_paths.push((path, false)); + } + } + } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "you might have meant to use the similarly named previously used binding `{$typo}`", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct PatternBindingTypo { - #[suggestion_part(code = "{typo}")] - pub(crate) spans: Vec, - pub(crate) typo: Symbol, -} + // Print the whole import chain to make it easier to see what happens. + let first_binding = decl; + let mut next_binding = Some(decl); + let mut next_ident = ident; + while let Some(binding) = next_binding { + let name = next_ident; + next_binding = match binding.kind { + _ if res == Res::Err => None, + DeclKind::Import { source_decl, import, .. } => match import.kind { + _ if source_decl.span.is_dummy() => None, + ImportKind::Single { source, .. } => { + next_ident = source; + Some(source_decl) + } + ImportKind::Glob { .. } + | ImportKind::MacroUse { .. } + | ImportKind::MacroExport => Some(source_decl), + ImportKind::ExternCrate { .. } => None, + }, + _ => None, + }; + + match binding.kind { + DeclKind::Import { source_decl, import, .. } => { + // Don't include `{{root}}` in suggestions - it's an internal symbol + // that should never be shown to users. + let path = import + .module_path + .iter() + .filter(|seg| seg.ident.name != kw::PathRoot) + .map(|seg| seg.ident.clone()) + .chain(std::iter::once(ident)) + .collect::>(); + let through_reexport = !matches!(source_decl.kind, DeclKind::Def(_)); + sugg_paths.push((path, through_reexport)); + } + DeclKind::Def(_) => {} + } + let first = binding == first_binding; + let def_span = self.tcx.sess.source_map().guess_head_span(binding.span); + let mut note_span = MultiSpan::from_span(def_span); + if !first && binding.vis().is_public() { + let desc = match binding.kind { + DeclKind::Import { .. } => "re-export", + _ => "directly", + }; + note_span.push_span_label(def_span, format!("you could import this {desc}")); + } + // Final step in the import chain, point out if the ADT is `non_exhaustive` + // which is probably why this privacy violation occurred. + if next_binding.is_none() + && let Some(span) = non_exhaustive + { + note_span.push_span_label( + span, + "cannot be constructed because it is `#[non_exhaustive]`", + ); + } + let note = diagnostics::NoteAndRefersToTheItemDefinedHere { + span: note_span, + binding_descr: get_descr(binding), + binding_name: name, + first, + dots: next_binding.is_some(), + }; + err.subdiagnostic(note); + } + // The suggestion replaces `dedup_span` with a path reaching the failing ident. + // That's valid only when + // 1) the failing ident is the imported leaf, otherwise `as` renames and trailing segments + // get dropped, and + // 2) the use isn't nested, otherwise `dedup_span` is one ident in `{...}`. + // + // See issue #156060. + let can_replace_use = !shown_candidates + && !single_nested + && !outermost_res.is_some_and(|(_, outer)| outer.span != ident.span); + if can_replace_use { + // We prioritize shorter paths, non-core imports and direct imports over the + // alternatives. + sugg_paths.sort_by_key(|(p, reexport)| (p.len(), p[0].name == sym::core, *reexport)); + for (sugg, reexport) in sugg_paths { + if sugg.len() <= 1 { + // A single path segment suggestion is wrong. This happens on circular + // imports. `tests/ui/imports/issue-55884-2.rs` + continue; + } + let path = join_path_idents(sugg); + let sugg = if reexport { + diagnostics::ImportIdent::ThroughReExport { span: dedup_span, ident, path } + } else { + diagnostics::ImportIdent::Directly { span: dedup_span, ident, path } + }; + err.subdiagnostic(sugg); + break; + } + } -#[derive(Diagnostic)] -#[diag("the name `{$name}` is defined multiple times")] -#[note("`{$name}` must be defined only once in the {$descr} namespace of this {$container}")] -pub(crate) struct NameDefinedMultipleTime { - #[primary_span] - pub(crate) span: Span, - pub(crate) name: Symbol, - pub(crate) descr: &'static str, - pub(crate) container: &'static str, - #[subdiagnostic] - pub(crate) label: NameDefinedMultipleTimeLabel, - #[subdiagnostic] - pub(crate) old_binding_label: Option, -} + err.emit(); + } -#[derive(Subdiagnostic)] -pub(crate) enum NameDefinedMultipleTimeLabel { - #[label("`{$name}` reimported here")] - Reimported { - #[primary_span] - span: Span, - name: Symbol, - }, - #[label("`{$name}` redefined here")] - Redefined { - #[primary_span] - span: Span, - name: Symbol, - }, -} + /// When a private field is being set that has a default field value, we suggest using `..` and + /// setting the value of that field implicitly with its default. + /// + /// If we encounter code like + /// ```text + /// struct Priv; + /// pub struct S { + /// pub field: Priv = Priv, + /// } + /// ``` + /// which is used from a place where `Priv` isn't accessible + /// ```text + /// let _ = S { field: m::Priv1 {} }; + /// // ^^^^^ private struct + /// ``` + /// we will suggest instead using the `default_field_values` syntax instead: + /// ```text + /// let _ = S { .. }; + /// ``` + fn mention_default_field_values( + &self, + source: &Option, + ident: Ident, + err: &mut Diag<'_>, + ) { + let Some(expr) = source else { return }; + let ast::ExprKind::Struct(struct_expr) = &expr.kind else { return }; + // We don't have to handle type-relative paths because they're forbidden in ADT + // expressions, but that would change with `#[feature(more_qualified_paths)]`. + let Some(segment) = struct_expr.path.segments.last() else { return }; + let Some(partial_res) = self.partial_res_map.get(&segment.id) else { return }; + let Some(Res::Def(_, def_id)) = partial_res.full_res() else { + return; + }; + let Some(default_fields) = self.field_defaults(def_id) else { return }; + if struct_expr.fields.is_empty() { + return; + } + let last_span = struct_expr.fields.iter().last().unwrap().span; + let mut iter = struct_expr.fields.iter().peekable(); + let mut prev: Option = None; + while let Some(field) = iter.next() { + if field.expr.span.overlaps(ident.span) { + err.span_label(field.ident.span, "while setting this field"); + if default_fields.contains(&field.ident.name) { + let sugg = if last_span == field.span { + vec![(field.span, "..".to_string())] + } else { + vec![ + ( + // Account for trailing commas and ensure we remove them. + match (prev, iter.peek()) { + (_, Some(next)) => field.span.with_hi(next.span.lo()), + (Some(prev), _) => field.span.with_lo(prev.hi()), + (None, None) => field.span, + }, + String::new(), + ), + (last_span.shrink_to_hi(), ", ..".to_string()), + ] + }; + err.multipart_suggestion( + format!( + "the type `{ident}` of field `{}` is private, but you can construct \ + the default value defined for it in `{}` using `..` in the struct \ + initializer expression", + field.ident, + self.tcx.item_name(def_id), + ), + sugg, + Applicability::MachineApplicable, + ); + break; + } + } + prev = Some(field.span); + } + } -#[derive(Subdiagnostic)] -pub(crate) enum NameDefinedMultipleTimeOldBindingLabel { - #[label("previous import of the {$old_kind} `{$name}` here")] - Import { - #[primary_span] - span: Span, - old_kind: &'static str, - name: Symbol, - }, - #[label("previous definition of the {$old_kind} `{$name}` here")] - Definition { - #[primary_span] - span: Span, - old_kind: &'static str, - name: Symbol, - }, -} + pub(crate) fn find_similarly_named_module_or_crate( + &self, + ident: Symbol, + current_module: Module<'ra>, + ) -> Option { + let mut candidates = self + .extern_prelude + .keys() + .map(|ident| ident.name) + .chain( + self.local_module_map + .iter() + .filter(|(_, module)| { + let module = module.to_module(); + current_module.is_ancestor_of(module) && current_module != module + }) + .flat_map(|(_, module)| module.name()), + ) + .chain( + self.extern_module_map + .borrow() + .iter() + .filter(|(_, module)| { + let module = module.to_module(); + current_module.is_ancestor_of(module) && current_module != module + }) + .flat_map(|(_, module)| module.name()), + ) + .filter(|c| !c.to_string().is_empty()) + .collect::>(); + candidates.sort(); + candidates.dedup(); + find_best_match_for_name(&candidates, ident, None).filter(|sugg| *sugg != ident) + } -#[derive(Diagnostic)] -#[diag("{$ident_descr} `{$ident}` is private", code = E0603)] -pub(crate) struct IsPrivate<'a> { - #[primary_span] - #[label("private {$ident_descr}")] - pub(crate) span: Span, - pub(crate) ident_descr: &'a str, - pub(crate) ident: Ident, -} + pub(crate) fn report_path_resolution_error( + &mut self, + path: &[Segment], + opt_ns: Option, // `None` indicates a module path in import + parent_scope: &ParentScope<'ra>, + ribs: Option<&PerNS>>>, + ignore_decl: Option>, + ignore_import: Option>, + module: Option>, + failed_segment_idx: usize, + ident: Ident, + diag_metadata: Option<&DiagMetadata<'_>>, + ) -> (String, String, Option) { + let is_last = failed_segment_idx == path.len() - 1; + let ns = if is_last { opt_ns.unwrap_or(TypeNS) } else { TypeNS }; + let module_def_id = match module { + Some(ModuleOrUniformRoot::Module(module)) => module.opt_def_id(), + _ => None, + }; + let scope = match &path[..failed_segment_idx] { + [.., prev] => { + if prev.ident.name == kw::PathRoot { + format!("the crate root") + } else { + format!("`{}`", prev.ident) + } + } + _ => format!("this scope"), + }; + let message = format!("cannot find `{ident}` in {scope}"); + + if module_def_id == Some(CRATE_DEF_ID.to_def_id()) { + let is_mod = |res| matches!(res, Res::Def(DefKind::Mod, _)); + let mut candidates = self.lookup_import_candidates(ident, TypeNS, parent_scope, is_mod); + candidates + .sort_by_cached_key(|c| (c.path.segments.len(), pprust::path_to_string(&c.path))); + if let Some(candidate) = candidates.get(0) { + let path = { + // remove the possible common prefix of the path + let len = candidate.path.segments.len(); + let start_index = (0..=failed_segment_idx.min(len - 1)) + .find(|&i| path[i].ident.name != candidate.path.segments[i].ident.name) + .unwrap_or_default(); + let segments = + (start_index..len).map(|s| candidate.path.segments[s].clone()).collect(); + Path { segments, span: Span::default(), tokens: None } + }; + ( + message, + String::from("unresolved import"), + Some(( + vec![(ident.span, pprust::path_to_string(&path))], + String::from("a similar path exists"), + Applicability::MaybeIncorrect, + )), + ) + } else if ident.name == sym::core { + ( + message, + format!("you might be missing crate `{ident}`"), + Some(( + vec![(ident.span, "std".to_string())], + "try using `std` instead of `core`".to_string(), + Applicability::MaybeIncorrect, + )), + ) + } else if ident.name == kw::Underscore { + ( + "invalid crate or module name `_`".to_string(), + "`_` is not a valid crate or module name".to_string(), + None, + ) + } else if self.tcx.sess.is_rust_2015() { + ( + format!("cannot find module or crate `{ident}` in {scope}"), + format!("use of unresolved module or unlinked crate `{ident}`"), + Some(( + vec![( + self.current_crate_outer_attr_insert_span, + format!("extern crate {ident};\n"), + )], + if was_invoked_from_cargo() { + format!( + "if you wanted to use a crate named `{ident}`, use `cargo add \ + {ident}` to add it to your `Cargo.toml` and import it in your \ + code", + ) + } else { + format!( + "you might be missing a crate named `{ident}`, add it to your \ + project and import it in your code", + ) + }, + Applicability::MaybeIncorrect, + )), + ) + } else { + (message, format!("could not find `{ident}` in the crate root"), None) + } + } else if failed_segment_idx > 0 { + let parent = path[failed_segment_idx - 1].ident.name; + let parent = match parent { + // ::foo is mounted at the crate root for 2015, and is the extern + // prelude for 2018+ + kw::PathRoot if self.tcx.sess.edition() > Edition::Edition2015 => { + "the list of imported crates".to_owned() + } + kw::PathRoot | kw::Crate => "the crate root".to_owned(), + _ => format!("`{parent}`"), + }; + + let mut msg = format!("could not find `{ident}` in {parent}"); + if ns == TypeNS || ns == ValueNS { + let ns_to_try = if ns == TypeNS { ValueNS } else { TypeNS }; + let binding = if let Some(module) = module { + self.cm() + .resolve_ident_in_module( + module, + ident, + ns_to_try, + parent_scope, + None, + ignore_decl, + ignore_import, + ) + .ok() + } else if let Some(ribs) = ribs + && let Some(TypeNS | ValueNS) = opt_ns + { + assert!(ignore_import.is_none()); + match self.resolve_ident_in_lexical_scope( + ident, + ns_to_try, + parent_scope, + None, + &ribs[ns_to_try], + ignore_decl, + diag_metadata, + ) { + // we found a locally-imported or available item/module + Some(LateDecl::Decl(binding)) => Some(binding), + _ => None, + } + } else { + self.cm() + .resolve_ident_in_scope_set( + ident, + ScopeSet::All(ns_to_try), + parent_scope, + None, + ignore_decl, + ignore_import, + ) + .ok() + }; + if let Some(binding) = binding { + msg = format!( + "expected {}, found {} `{ident}` in {parent}", + ns.descr(), + binding.res().descr(), + ); + }; + } + (message, msg, None) + } else if ident.name == kw::SelfUpper { + // As mentioned above, `opt_ns` being `None` indicates a module path in import. + // We can use this to improve a confusing error for, e.g. `use Self::Variant` in an + // impl + if opt_ns.is_none() { + (message, "`Self` cannot be used in imports".to_string(), None) + } else { + ( + message, + "`Self` is only available in impls, traits, and type definitions".to_string(), + None, + ) + } + } else if ident.name.as_str().chars().next().is_some_and(|c| c.is_ascii_uppercase()) { + // Check whether the name refers to an item in the value namespace. + let binding = if let Some(ribs) = ribs { + assert!(ignore_import.is_none()); + self.resolve_ident_in_lexical_scope( + ident, + ValueNS, + parent_scope, + None, + &ribs[ValueNS], + ignore_decl, + diag_metadata, + ) + } else { + None + }; + let match_span = match binding { + // Name matches a local variable. For example: + // ``` + // fn f() { + // let Foo: &str = ""; + // println!("{}", Foo::Bar); // Name refers to local + // // variable `Foo`. + // } + // ``` + Some(LateDecl::RibDef(Res::Local(id))) => { + Some((*self.pat_span_map.get(&id).unwrap(), "a", "local binding")) + } + // Name matches item from a local name binding + // created by `use` declaration. For example: + // ``` + // pub const Foo: &str = ""; + // + // mod submod { + // use super::Foo; + // println!("{}", Foo::Bar); // Name refers to local + // // binding `Foo`. + // } + // ``` + Some(LateDecl::Decl(name_binding)) => Some(( + name_binding.span, + name_binding.res().article(), + name_binding.res().descr(), + )), + _ => None, + }; + + let message = format!("cannot find type `{ident}` in {scope}"); + let label = if let Some((span, article, descr)) = match_span { + format!( + "`{ident}` is declared as {article} {descr} at `{}`, not a type", + self.tcx + .sess + .source_map() + .span_to_short_string(span, RemapPathScopeComponents::DIAGNOSTICS) + ) + } else { + format!("use of undeclared type `{ident}`") + }; + (message, label, None) + } else { + let mut suggestion = None; + if ident.name == sym::alloc { + suggestion = Some(( + vec![], + String::from("add `extern crate alloc` to use the `alloc` crate"), + Applicability::MaybeIncorrect, + )) + } -#[derive(Diagnostic)] -#[diag("generic arguments in macro path")] -pub(crate) struct GenericArgumentsInMacroPath { - #[primary_span] - pub(crate) span: Span, -} + suggestion = suggestion.or_else(|| { + self.find_similarly_named_module_or_crate(ident.name, parent_scope.module).map( + |sugg| { + ( + vec![(ident.span, sugg.to_string())], + String::from("there is a crate or module with a similar name"), + Applicability::MaybeIncorrect, + ) + }, + ) + }); + if let Ok(binding) = self.cm().resolve_ident_in_scope_set( + ident, + ScopeSet::All(ValueNS), + parent_scope, + None, + ignore_decl, + ignore_import, + ) { + let descr = binding.res().descr(); + let message = format!("cannot find module or crate `{ident}` in {scope}"); + (message, format!("{descr} `{ident}` is not a crate or module"), suggestion) + } else { + let suggestion = if suggestion.is_some() { + suggestion + } else if let Some(m) = self.undeclared_module_exists(ident) { + self.undeclared_module_suggest_declare(ident, m) + } else if was_invoked_from_cargo() { + Some(( + vec![], + format!( + "if you wanted to use a crate named `{ident}`, use `cargo add {ident}` \ + to add it to your `Cargo.toml`", + ), + Applicability::MaybeIncorrect, + )) + } else { + Some(( + vec![], + format!("you might be missing a crate named `{ident}`",), + Applicability::MaybeIncorrect, + )) + }; + let message = format!("cannot find module or crate `{ident}` in {scope}"); + ( + message, + format!("use of unresolved module or unlinked crate `{ident}`"), + suggestion, + ) + } + } + } -#[derive(Diagnostic)] -#[diag("attributes starting with `rustc` are reserved for use by the `rustc` compiler")] -pub(crate) struct AttributesStartingWithRustcAreReserved { - #[primary_span] - pub(crate) span: Span, -} + fn undeclared_module_suggest_declare( + &self, + ident: Ident, + path: std::path::PathBuf, + ) -> Option<(Vec<(Span, String)>, String, Applicability)> { + Some(( + vec![(self.current_crate_outer_attr_insert_span, format!("mod {ident};\n"))], + format!( + "to make use of source file {}, use `mod {ident}` \ + in this file to declare the module", + path.display() + ), + Applicability::MaybeIncorrect, + )) + } -#[derive(Diagnostic)] -#[diag( - "attributes containing a segment starting with `rustc` are reserved for use by the `rustc` compiler" -)] -pub(crate) struct AttributesContainingRustcAreReserved { - #[primary_span] - pub(crate) span: Span, -} + fn undeclared_module_exists(&self, ident: Ident) -> Option { + let map = self.tcx.sess.source_map(); + + let src = map.span_to_filename(ident.span).into_local_path()?; + let i = ident.as_str(); + // FIXME: add case where non parent using undeclared module (hard?) + let dir = src.parent()?; + let src = src.file_stem()?.to_str()?; + for file in [ + // …/x.rs + dir.join(i).with_extension("rs"), + // …/x/mod.rs + dir.join(i).join("mod.rs"), + ] { + if file.exists() { + return Some(file); + } + } + if !matches!(src, "main" | "lib" | "mod") { + for file in [ + // …/x/y.rs + dir.join(src).join(i).with_extension("rs"), + // …/x/y/mod.rs + dir.join(src).join(i).join("mod.rs"), + ] { + if file.exists() { + return Some(file); + } + } + } + None + } -#[derive(Diagnostic)] -#[diag("cannot use {$article} {$descr} through an import")] -pub(crate) struct CannotUseThroughAnImport { - #[primary_span] - pub(crate) span: Span, - pub(crate) article: &'static str, - pub(crate) descr: &'static str, - #[note("the {$descr} imported here")] - pub(crate) binding_span: Option, -} + /// Adds suggestions for a path that cannot be resolved. + #[instrument(level = "debug", skip(self, parent_scope))] + pub(crate) fn make_path_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + match path[..] { + // `{{root}}::ident::...` on both editions. + // On 2015 `{{root}}` is usually added implicitly. + [first, second, ..] + if first.ident.name == kw::PathRoot && !second.ident.is_path_segment_keyword() => {} + // `ident::...` on 2018. + [first, ..] + if first.ident.span.at_least_rust_2018() + && !first.ident.is_path_segment_keyword() => + { + // Insert a placeholder that's later replaced by `self`/`super`/etc. + path.insert(0, Segment::from_ident(Ident::dummy())); + } + _ => return None, + } -#[derive(Diagnostic)] -#[diag("name `{$ident}` is reserved in attribute namespace")] -pub(crate) struct NameReservedInAttributeNamespace { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Symbol, -} + self.make_missing_self_suggestion(path.clone(), parent_scope) + .or_else(|| self.make_missing_crate_suggestion(path.clone(), parent_scope)) + .or_else(|| self.make_missing_super_suggestion(path.clone(), parent_scope)) + .or_else(|| self.make_external_crate_suggestion(path, parent_scope)) + } -#[derive(Diagnostic)] -#[diag("cannot find a built-in macro with name `{$ident}`")] -pub(crate) struct CannotFindBuiltinMacroWithName { - #[primary_span] - pub(crate) span: Span, - pub(crate) ident: Ident, -} + /// Suggest a missing `self::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `self::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_self_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `self` and check if that is valid. + path[0].ident.name = kw::SelfLower; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { Some((path, None)) } else { None } + } -#[derive(Diagnostic)] -#[diag("tool `{$tool}` was already registered")] -pub(crate) struct ToolWasAlreadyRegistered { - #[primary_span] - pub(crate) span: Span, - pub(crate) tool: Ident, - #[label("already registered here")] - pub(crate) old_ident_span: Span, -} + /// Suggests a missing `crate::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `crate::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_crate_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `crate` and check if that is valid. + path[0].ident.name = kw::Crate; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { + Some(( + path, + Some( + "`use` statements changed in Rust 2018; read more at \ + " + .to_string(), + ), + )) + } else { + None + } + } -#[derive(Subdiagnostic)] -pub(crate) enum DefinedHere { - #[label("similarly named {$candidate_descr} `{$candidate}` defined here")] - SimilarlyNamed { - #[primary_span] - span: Span, - candidate_descr: &'static str, - candidate: Symbol, - }, - #[label("{$candidate_descr} `{$candidate}` defined here")] - SingleItem { - #[primary_span] - span: Span, - candidate_descr: &'static str, - candidate: Symbol, - }, -} + /// Suggests a missing `super::` if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foo::Bar; + /// | ^^^ did you mean `super::foo`? + /// ``` + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_missing_super_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + // Replace first ident with `crate` and check if that is valid. + path[0].ident.name = kw::Super; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?result); + if let PathResult::Module(..) = result { Some((path, None)) } else { None } + } -#[derive(Subdiagnostic)] -#[label("{$outer_ident_descr} `{$outer_ident}` is not publicly re-exported")] -pub(crate) struct OuterIdentIsNotPubliclyReexported { - #[primary_span] - pub(crate) span: Span, - pub(crate) outer_ident_descr: &'static str, - pub(crate) outer_ident: Ident, -} + /// Suggests a missing external crate name if that resolves to an correct module. + /// + /// ```text + /// | + /// LL | use foobar::Baz; + /// | ^^^^^^ did you mean `baz::foobar`? + /// ``` + /// + /// Used when importing a submodule of an external crate but missing that crate's + /// name as the first part of path. + #[instrument(level = "debug", skip(self, parent_scope))] + fn make_external_crate_suggestion( + &mut self, + mut path: Vec, + parent_scope: &ParentScope<'ra>, + ) -> Option<(Vec, Option)> { + if path[1].ident.span.is_rust_2015() { + return None; + } -#[derive(Subdiagnostic)] -#[label("a constructor is private if any of the fields is private")] -pub(crate) struct ConstructorPrivateIfAnyFieldPrivate { - #[primary_span] - pub(crate) span: Span, -} + // Sort extern crate names in *reverse* order to get + // 1) some consistent ordering for emitted diagnostics, and + // 2) `std` suggestions before `core` suggestions. + let mut extern_crate_names = + self.extern_prelude.keys().map(|ident| ident.name).collect::>(); + extern_crate_names.sort_by(|a, b| b.as_str().cmp(a.as_str())); + + for name in extern_crate_names.into_iter() { + // Replace first ident with a crate name and check if that is valid. + path[0].ident.name = name; + let result = self.cm().maybe_resolve_path(&path, None, parent_scope, None); + debug!(?path, ?name, ?result); + if let PathResult::Module(..) = result { + return Some((path, None)); + } + } -#[derive(Subdiagnostic)] -#[multipart_suggestion( - "{ $number_of_fields -> - [one] consider making the field publicly accessible - *[other] consider making the fields publicly accessible - }", - applicability = "maybe-incorrect", - style = "verbose" -)] -pub(crate) struct ConsiderMakingTheFieldPublic { - #[suggestion_part(code = "pub ")] - pub(crate) spans: Vec, - pub(crate) number_of_fields: usize, -} + None + } -#[derive(Subdiagnostic)] -pub(crate) enum ImportIdent { - #[suggestion( - "import `{$ident}` through the re-export", - code = "{path}", - applicability = "machine-applicable", - style = "verbose" - )] - ThroughReExport { - #[primary_span] - span: Span, - ident: Ident, - path: String, - }, - #[suggestion( - "import `{$ident}` directly", - code = "{path}", - applicability = "machine-applicable", - style = "verbose" - )] - Directly { - #[primary_span] - span: Span, + /// Suggests importing a macro from the root of the crate rather than a module within + /// the crate. + /// + /// ```text + /// help: a macro with this name exists at the root of the crate + /// | + /// LL | use issue_59764::makro; + /// | ^^^^^^^^^^^^^^^^^^ + /// | + /// = note: this could be because a macro annotated with `#[macro_export]` will be exported + /// at the root of the crate instead of the module where it is defined + /// ``` + pub(crate) fn check_for_module_export_macro( + &mut self, + import: Import<'ra>, + module: ModuleOrUniformRoot<'ra>, ident: Ident, - path: String, - }, -} - -#[derive(Subdiagnostic)] -#[note( - "{$first -> - [true] {$dots -> - [true] the {$binding_descr} `{$binding_name}` is defined here... - *[false] the {$binding_descr} `{$binding_name}` is defined here - } - *[false] {$dots -> - [true] ...and refers to the {$binding_descr} `{$binding_name}` which is defined here... - *[false] ...and refers to the {$binding_descr} `{$binding_name}` which is defined here - } - }" -)] -pub(crate) struct NoteAndRefersToTheItemDefinedHere<'a> { - #[primary_span] - pub(crate) span: MultiSpan, - pub(crate) binding_descr: &'a str, - pub(crate) binding_name: Ident, - pub(crate) first: bool, - pub(crate) dots: bool, -} - -#[derive(Subdiagnostic)] -#[suggestion("remove unnecessary import", code = "", applicability = "maybe-incorrect")] -pub(crate) struct RemoveUnnecessaryImport { - #[primary_span] - pub(crate) span: Span, -} - -#[derive(Subdiagnostic)] -#[suggestion( - "remove unnecessary import", - code = "", - applicability = "maybe-incorrect", - style = "tool-only" -)] -pub(crate) struct ToolOnlyRemoveUnnecessaryImport { - #[primary_span] - pub(crate) span: Span, -} - -#[derive(Subdiagnostic)] -#[note("`{$imported_ident}` is imported here, but it is {$imported_ident_desc}")] -pub(crate) struct IdentImporterHereButItIsDesc<'a> { - #[primary_span] - pub(crate) span: Span, - pub(crate) imported_ident: Ident, - pub(crate) imported_ident_desc: &'a str, -} - -#[derive(Subdiagnostic)] -#[note("`{$imported_ident}` is in scope, but it is {$imported_ident_desc}")] -pub(crate) struct IdentInScopeButItIsDesc<'a> { - pub(crate) imported_ident: Ident, - pub(crate) imported_ident_desc: &'a str, -} + ) -> Option<(Option, Option)> { + let ModuleOrUniformRoot::Module(mut crate_module) = module else { + return None; + }; -pub(crate) struct FoundItemConfigureOut { - pub(crate) span: Span, - pub(crate) item_was: ItemWas, -} + while let Some(parent) = crate_module.parent { + crate_module = parent; + } -pub(crate) enum ItemWas { - BehindFeature { feature: Symbol, span: Span }, - CfgOut { span: Span }, -} + if module == ModuleOrUniformRoot::Module(crate_module) { + // Don't make a suggestion if the import was already from the root of the crate. + return None; + } -impl Subdiagnostic for FoundItemConfigureOut { - fn add_to_diag(self, diag: &mut Diag<'_, G>) { - let mut multispan: MultiSpan = self.span.into(); - match self.item_was { - ItemWas::BehindFeature { feature, span } => { - let value = feature.into_diag_arg(&mut None); - let msg = msg!("the item is gated behind the `{$feature}` feature") - .arg("feature", value) - .format(); - multispan.push_span_label(span, msg); + let binding_key = BindingKey::new(IdentKey::new(ident), MacroNS); + let binding = self.resolution(crate_module, binding_key)?.best_decl()?; + let Res::Def(DefKind::Macro(kinds), _) = binding.res() else { + return None; + }; + if !kinds.contains(MacroKinds::BANG) { + return None; + } + let module_name = crate_module.name().unwrap_or(kw::Crate); + let import_snippet = match import.kind { + ImportKind::Single { source, target, .. } if source != target => { + format!("{source} as {target}") + } + _ => format!("{ident}"), + }; + + let mut corrections: Vec<(Span, String)> = Vec::new(); + if !import.is_nested() { + // Assume this is the easy case of `use issue_59764::foo::makro;` and just remove + // intermediate segments. + corrections.push((import.span, format!("{module_name}::{import_snippet}"))); + } else { + // Find the binding span (and any trailing commas and spaces). + // i.e. `use a::b::{c, d, e};` + // ^^^ + let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding( + self.tcx.sess, + import.span, + import.use_span, + ); + debug!(found_closing_brace, ?binding_span); + + let mut removal_span = binding_span; + + // If the binding span ended with a closing brace, as in the below example: + // i.e. `use a::b::{c, d};` + // ^ + // Then expand the span of characters to remove to include the previous + // binding's trailing comma. + // i.e. `use a::b::{c, d};` + // ^^^ + if found_closing_brace + && let Some(previous_span) = + extend_span_to_previous_binding(self.tcx.sess, binding_span) + { + debug!(?previous_span); + removal_span = removal_span.with_lo(previous_span.lo()); } - ItemWas::CfgOut { span } => { - multispan.push_span_label(span, msg!("the item is gated here")); + debug!(?removal_span); + + // Remove the `removal_span`. + corrections.push((removal_span, "".to_string())); + + // Find the span after the crate name and if it has nested imports immediately + // after the crate name already. + // i.e. `use a::b::{c, d};` + // ^^^^^^^^^ + // or `use a::{b, c, d}};` + // ^^^^^^^^^^^ + let (has_nested, after_crate_name) = + find_span_immediately_after_crate_name(self.tcx.sess, import.use_span); + debug!(has_nested, ?after_crate_name); + + let source_map = self.tcx.sess.source_map(); + + // Make sure this is actually crate-relative. + let is_definitely_crate = import + .module_path + .first() + .is_some_and(|f| f.ident.name != kw::SelfLower && f.ident.name != kw::Super); + + // Add the import to the start, with a `{` if required. + let start_point = source_map.start_point(after_crate_name); + if is_definitely_crate + && let Ok(start_snippet) = source_map.span_to_snippet(start_point) + { + corrections.push(( + start_point, + if has_nested { + // In this case, `start_snippet` must equal '{'. + format!("{start_snippet}{import_snippet}, ") + } else { + // In this case, add a `{`, then the moved import, then whatever + // was there before. + format!("{{{import_snippet}, {start_snippet}") + }, + )); + + // Add a `};` to the end if nested, matching the `{` added at the start. + if !has_nested { + corrections.push((source_map.end_point(after_crate_name), "};".to_string())); + } + } else { + // If the root import is module-relative, add the import separately + corrections.push(( + import.use_span.shrink_to_lo(), + format!("use {module_name}::{import_snippet};\n"), + )); } } - diag.span_note(multispan, msg!("found an item that was configured out")); + + let suggestion = Some(( + corrections, + String::from("a macro with this name exists at the root of the crate"), + Applicability::MaybeIncorrect, + )); + Some(( + suggestion, + Some( + "this could be because a macro annotated with `#[macro_export]` will be exported \ + at the root of the crate instead of the module where it is defined" + .to_string(), + ), + )) } -} -#[derive(Diagnostic)] -#[diag("item `{$name}` is an associated {$kind}, which doesn't match its trait `{$trait_path}`")] -pub(crate) struct TraitImplMismatch { - #[primary_span] - #[label("does not match trait")] - pub(crate) span: Span, - pub(crate) name: Ident, - pub(crate) kind: &'static str, - pub(crate) trait_path: String, - #[label("item in trait")] - pub(crate) trait_item_span: Span, -} + /// Finds a cfg-ed out item inside `module` with the matching name. + pub(crate) fn find_cfg_stripped(&self, err: &mut Diag<'_>, segment: &Symbol, module: DefId) { + let local_items; + let symbols = if module.is_local() { + local_items = self + .stripped_cfg_items + .iter() + .filter_map(|item| { + let parent_scope = self.local_modules.iter().find_map(|m| match m.kind { + ModuleKind::Def(_, def_id, node_id, _) if node_id == item.parent_scope => { + Some(def_id) + } + _ => None, + })?; + Some(StrippedCfgItem { parent_scope, ident: item.ident, cfg: item.cfg.clone() }) + }) + .collect::>(); + local_items.as_slice() + } else { + self.tcx.stripped_cfg_items(module.krate) + }; -#[derive(Diagnostic)] -#[diag("derive helper attribute is used before it is introduced")] -pub(crate) struct LegacyDeriveHelpers { - #[label("the attribute is introduced here")] - pub span: Span, -} + for &StrippedCfgItem { parent_scope, ident, ref cfg } in symbols { + if ident.name != *segment { + continue; + } -#[derive(Diagnostic)] -#[diag("unused extern crate")] -pub(crate) struct UnusedExternCrate { - #[label("unused")] - pub span: Span, - #[suggestion( - "remove the unused `extern crate`", - code = "", - applicability = "machine-applicable", - style = "verbose" - )] - pub removal_span: Span, -} + let parent_module = self.get_nearest_non_block_module(parent_scope).def_id(); + + fn comes_from_same_module_for_glob( + r: &Resolver<'_, '_>, + parent_module: DefId, + module: DefId, + visited: &mut FxHashMap, + ) -> bool { + if let Some(&cached) = visited.get(&parent_module) { + // this branch is prevent from being called recursively infinity, + // because there has some cycles in globs imports, + // see more spec case at `tests/ui/cfg/diagnostics-reexport-2.rs#reexport32` + return cached; + } + visited.insert(parent_module, false); + let mut res = false; + let m = r.expect_module(parent_module); + if m.is_local() { + for importer in m.glob_importers.borrow().iter() { + if let Some(next_parent_module) = importer.parent_scope.module.opt_def_id() + { + if next_parent_module == module + || comes_from_same_module_for_glob( + r, + next_parent_module, + module, + visited, + ) + { + res = true; + break; + } + } + } + } + visited.insert(parent_module, res); + res + } -#[derive(Diagnostic)] -#[diag("{$kind} `{$name}` from private dependency '{$krate}' is re-exported")] -pub(crate) struct ReexportPrivateDependency { - pub name: Symbol, - pub kind: &'static str, - pub krate: Symbol, -} + let comes_from_same_module = parent_module == module + || comes_from_same_module_for_glob( + self, + parent_module, + module, + &mut Default::default(), + ); + if !comes_from_same_module { + continue; + } -#[derive(Diagnostic)] -#[diag("unused label")] -pub(crate) struct UnusedLabel; + let item_was = if let CfgEntry::NameValue { value: Some(feature), .. } = cfg.0 { + diagnostics::ItemWas::BehindFeature { feature, span: cfg.1 } + } else { + diagnostics::ItemWas::CfgOut { span: cfg.1 } + }; + let note = diagnostics::FoundItemConfigureOut { span: ident.span, item_was }; + err.subdiagnostic(note); + } + } -#[derive(Diagnostic)] -#[diag("unused `#[macro_use]` import")] -pub(crate) struct UnusedMacroUse; + pub(crate) fn struct_ctor(&self, def_id: DefId) -> Option { + match def_id.as_local() { + Some(def_id) => self.struct_ctors.get(&def_id).cloned(), + None => { + self.cstore().ctor_untracked(self.tcx, def_id).map(|(ctor_kind, ctor_def_id)| { + let res = Res::Def(DefKind::Ctor(CtorOf::Struct, ctor_kind), ctor_def_id); + let vis = self.tcx.visibility(ctor_def_id); + let field_visibilities = self + .tcx + .associated_item_def_ids(def_id) + .iter() + .map(|&field_id| self.tcx.visibility(field_id)) + .collect(); + StructCtor { res, vis, field_visibilities } + }) + } + } + } +} -#[derive(Diagnostic)] -#[diag("applying the `#[macro_use]` attribute to an `extern crate` item is deprecated")] -#[help("remove it and import macros at use sites with a `use` item instead")] -pub(crate) struct MacroUseDeprecated; +/// Given a `binding_span` of a binding within a use statement: +/// +/// ```ignore (illustrative) +/// use foo::{a, b, c}; +/// // ^ +/// ``` +/// +/// then return the span until the next binding or the end of the statement: +/// +/// ```ignore (illustrative) +/// use foo::{a, b, c}; +/// // ^^^ +/// ``` +fn find_span_of_binding_until_next_binding( + sess: &Session, + binding_span: Span, + use_span: Span, +) -> (bool, Span) { + let source_map = sess.source_map(); + + // Find the span of everything after the binding. + // i.e. `a, e};` or `a};` + let binding_until_end = binding_span.with_hi(use_span.hi()); + + // Find everything after the binding but not including the binding. + // i.e. `, e};` or `};` + let after_binding_until_end = binding_until_end.with_lo(binding_span.hi()); + + // Keep characters in the span until we encounter something that isn't a comma or + // whitespace. + // i.e. `, ` or ``. + // + // Also note whether a closing brace character was encountered. If there + // was, then later go backwards to remove any trailing commas that are left. + let mut found_closing_brace = false; + let after_binding_until_next_binding = + source_map.span_take_while(after_binding_until_end, |&ch| { + if ch == '}' { + found_closing_brace = true; + } + ch == ' ' || ch == ',' + }); + + // Combine the two spans. + // i.e. `a, ` or `a`. + // + // Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };` + let span = binding_span.with_hi(after_binding_until_next_binding.hi()); + + (found_closing_brace, span) +} + +/// Given a `binding_span`, return the span through to the comma or opening brace of the previous +/// binding. +/// +/// ```ignore (illustrative) +/// use foo::a::{a, b, c}; +/// // ^^--- binding span +/// // | +/// // returned span +/// +/// use foo::{a, b, c}; +/// // --- binding span +/// ``` +fn extend_span_to_previous_binding(sess: &Session, binding_span: Span) -> Option { + let source_map = sess.source_map(); + + // `prev_source` will contain all of the source that came before the span. + // Then split based on a command and take the first (i.e. closest to our span) + // snippet. In the example, this is a space. + let prev_source = source_map.span_to_prev_source(binding_span).ok()?; + + let prev_comma = prev_source.rsplit(',').collect::>(); + let prev_starting_brace = prev_source.rsplit('{').collect::>(); + if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 { + return None; + } -#[derive(Diagnostic)] -#[diag("macro `{$ident}` is private")] -pub(crate) struct MacroIsPrivate { - pub ident: Ident, -} + let prev_comma = prev_comma.first().unwrap(); + let prev_starting_brace = prev_starting_brace.first().unwrap(); -#[derive(Diagnostic)] -#[diag("unused macro definition: `{$name}`")] -pub(crate) struct UnusedMacroDefinition { - pub name: Symbol, -} + // If the amount of source code before the comma is greater than + // the amount of source code before the starting brace then we've only + // got one item in the nested item (eg. `issue_52891::{self}`). + if prev_comma.len() > prev_starting_brace.len() { + return None; + } -#[derive(Diagnostic)] -#[diag("rule #{$n} of macro `{$name}` is never used")] -pub(crate) struct MacroRuleNeverUsed { - pub n: usize, - pub name: Symbol, -} + Some(binding_span.with_lo(BytePos( + // Take away the number of bytes for the characters we've found and an + // extra for the comma. + binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1, + ))) +} + +/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if +/// it is a nested use tree. +/// +/// ```ignore (illustrative) +/// use foo::a::{b, c}; +/// // ^^^^^^^^^^ -- false +/// +/// use foo::{a, b, c}; +/// // ^^^^^^^^^^ -- true +/// +/// use foo::{a, b::{c, d}}; +/// // ^^^^^^^^^^^^^^^ -- true +/// ``` +#[instrument(level = "debug", skip(sess))] +fn find_span_immediately_after_crate_name(sess: &Session, use_span: Span) -> (bool, Span) { + let source_map = sess.source_map(); + + // Using `use issue_59764::foo::{baz, makro};` as an example throughout.. + let mut num_colons = 0; + // Find second colon.. `use issue_59764:` + let until_second_colon = source_map.span_take_while(use_span, |c| { + if *c == ':' { + num_colons += 1; + } + !matches!(c, ':' if num_colons == 2) + }); + // Find everything after the second colon.. `foo::{baz, makro};` + let from_second_colon = use_span.with_lo(until_second_colon.hi() + BytePos(1)); + + let mut found_a_non_whitespace_character = false; + // Find the first non-whitespace character in `from_second_colon`.. `f` + let after_second_colon = source_map.span_take_while(from_second_colon, |c| { + if found_a_non_whitespace_character { + return false; + } + if !c.is_whitespace() { + found_a_non_whitespace_character = true; + } + true + }); -#[derive(Diagnostic)] -#[diag("`extern crate` is not idiomatic in the new edition")] -pub(crate) struct ExternCrateNotIdiomatic { - #[suggestion( - "convert it to a `use`", - style = "verbose", - code = "{code}", - applicability = "machine-applicable" - )] - pub span: Span, - pub code: &'static str, -} + // Find the first `{` in from_second_colon.. `foo::{` + let next_left_bracket = source_map.span_through_char(from_second_colon, '{'); -#[derive(Diagnostic)] -#[diag("cannot find macro `{$path}` in the current scope when looking from {$location}")] -#[help("import `macro_rules` with `use` to make it callable above its definition")] -pub(crate) struct OutOfScopeMacroCalls { - #[label("not found from {$location}")] - pub span: Span, - pub path: String, - pub location: String, + (next_left_bracket == after_second_colon, from_second_colon) } -#[derive(Diagnostic)] -#[diag( - "glob import doesn't reexport anything with visibility `{$import_vis}` because no imported item is public enough" -)] -pub(crate) struct RedundantImportVisibility { - #[note("the most public imported item is `{$max_vis}`")] - pub span: Span, - #[help("reduce the glob import's visibility or increase visibility of imported items")] - pub help: (), - pub import_vis: String, - pub max_vis: String, +/// A suggestion has already been emitted, change the wording slightly to clarify that both are +/// independent options. +enum Instead { + Yes, + No, } -#[derive(Diagnostic)] -#[diag("unknown diagnostic attribute")] -pub(crate) struct UnknownDiagnosticAttribute { - #[subdiagnostic] - pub help: Option, +/// Whether an existing place with an `use` item was found. +enum FoundUse { + Yes, + No, } -#[derive(Subdiagnostic)] -pub(crate) enum UnknownDiagnosticAttributeHelp { - #[suggestion( - "an attribute with a similar name exists", - style = "verbose", - code = "{typo_name}", - applicability = "machine-applicable" - )] - Typo { - #[primary_span] - span: Span, - typo_name: Symbol, +/// Whether a binding is part of a pattern or a use statement. Used for diagnostics. +pub(crate) enum DiagMode { + Normal, + /// The binding is part of a pattern + Pattern, + /// The binding is part of a use statement + Import { + /// `true` means diagnostics is for unresolved import + unresolved_import: bool, + /// `true` mean add the tips afterward for case `use a::{b,c}`, + /// rather than replacing within. + append: bool, }, - #[help("add `#![feature({$feature})]` to the crate attributes to enable")] - UseFeature { feature: Symbol }, } -// FIXME: Make this properly translatable. -pub(crate) struct Ambiguity { - pub ident: Ident, - pub ambig_vis: Option, - pub kind: &'static str, - pub help: Option<&'static [&'static str]>, - pub b1_note: Spanned, - pub b1_help_msgs: Vec, - pub b2_note: Spanned, - pub b2_help_msgs: Vec, - /// If false, then it's a lint, if true, then it's an error with the `E0659` error code. - pub is_error: bool, -} - -impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for Ambiguity { - fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> { - let Self { - ident, - ambig_vis, - kind, - help, - b1_note, - b1_help_msgs, - b2_note, - b2_help_msgs, - is_error, - } = self; +pub(crate) fn import_candidates( + tcx: TyCtxt<'_>, + err: &mut Diag<'_>, + // This is `None` if all placement locations are inside expansions + use_placement_span: Option, + candidates: &[ImportSuggestion], + mode: DiagMode, + append: &str, +) { + show_candidates( + tcx, + err, + use_placement_span, + candidates, + Instead::Yes, + FoundUse::Yes, + mode, + vec![], + append, + ); +} + +type PathString<'a> = (String, &'a str, Option, &'a Option, bool); + +/// When an entity with a given name is not available in scope, we search for +/// entities with that name in all crates. This method allows outputting the +/// results of this search in a programmer-friendly way. If any entities are +/// found and suggested, returns `true`, otherwise returns `false`. +fn show_candidates( + tcx: TyCtxt<'_>, + err: &mut Diag<'_>, + // This is `None` if all placement locations are inside expansions + use_placement_span: Option, + candidates: &[ImportSuggestion], + instead: Instead, + found_use: FoundUse, + mode: DiagMode, + path: Vec, + append: &str, +) -> bool { + if candidates.is_empty() { + return false; + } - let mut diag = Diag::new(dcx, level, "").with_span(ident.span); - if is_error { - diag.code(E0659); - } - if let Some(ambig_vis) = ambig_vis { - diag.primary_message(format!("ambiguous import visibility: {ambig_vis}")); - } else { - diag.primary_message(format!("`{}` is ambiguous", ident)); - diag.span_label(ident.span, "ambiguous name"); - } - diag.note(format!("ambiguous because of {}", kind)); - diag.span_note(b1_note.span, b1_note.node); - if let Some(help) = help { - for help in help { - diag.help(*help); + let mut showed = false; + let mut accessible_path_strings: Vec> = Vec::new(); + let mut inaccessible_path_strings: Vec> = Vec::new(); + + candidates.iter().for_each(|c| { + if c.accessible { + // Don't suggest `#[doc(hidden)]` items from other crates + if c.doc_visible { + accessible_path_strings.push(( + pprust::path_to_string(&c.path), + c.descr, + c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), + &c.note, + c.via_import, + )) } + } else { + inaccessible_path_strings.push(( + pprust::path_to_string(&c.path), + c.descr, + c.did.and_then(|did| Some(tcx.source_span(did.as_local()?))), + &c.note, + c.via_import, + )) } - for help_msg in b1_help_msgs { - diag.help(help_msg); - } - diag.span_note(b2_note.span, b2_note.node); - for help_msg in b2_help_msgs { - diag.help(help_msg); + }); + + // we want consistent results across executions, but candidates are produced + // by iterating through a hash map, so make sure they are ordered: + for path_strings in [&mut accessible_path_strings, &mut inaccessible_path_strings] { + path_strings.sort_by(|a, b| a.0.cmp(&b.0)); + path_strings.dedup_by(|a, b| a.0 == b.0); + let core_path_strings = + path_strings.extract_if(.., |p| p.0.starts_with("core::")).collect::>(); + let std_path_strings = + path_strings.extract_if(.., |p| p.0.starts_with("std::")).collect::>(); + let foreign_crate_path_strings = + path_strings.extract_if(.., |p| !p.0.starts_with("crate::")).collect::>(); + + // We list the `crate` local paths first. + // Then we list the `std`/`core` paths. + if std_path_strings.len() == core_path_strings.len() { + // Do not list `core::` paths if we are already listing the `std::` ones. + path_strings.extend(std_path_strings); + } else { + path_strings.extend(std_path_strings); + path_strings.extend(core_path_strings); } - diag + // List all paths from foreign crates last. + path_strings.extend(foreign_crate_path_strings); } -} -#[derive(Diagnostic)] -#[diag("lifetime parameter `{$ident}` never used")] -pub(crate) struct UnusedLifetime { - #[suggestion("elide the unused lifetime", code = "", applicability = "machine-applicable")] - pub deletion_span: Option, + if !accessible_path_strings.is_empty() { + let (determiner, kind, s, name, through) = + if let [(name, descr, _, _, via_import)] = &accessible_path_strings[..] { + ( + "this", + *descr, + "", + format!(" `{name}`"), + if *via_import { " through its public re-export" } else { "" }, + ) + } else { + // Get the unique item kinds and if there's only one, we use the right kind name + // instead of the more generic "items". + let kinds = accessible_path_strings + .iter() + .map(|(_, descr, _, _, _)| *descr) + .collect::>(); + let kind = if let Some(kind) = kinds.get_only() { kind } else { "item" }; + let s = if kind.ends_with('s') { "es" } else { "s" }; + + ("one of these", kind, s, String::new(), "") + }; + + let instead = if let Instead::Yes = instead { " instead" } else { "" }; + let mut msg = if let DiagMode::Pattern = mode { + format!( + "if you meant to match on {kind}{s}{instead}{name}, use the full path in the \ + pattern", + ) + } else { + format!("consider importing {determiner} {kind}{s}{through}{instead}") + }; - pub ident: Ident, -} + for note in accessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { + err.note(note.clone()); + } -#[derive(Diagnostic)] -#[diag("ambiguous glob re-exports")] -pub(crate) struct AmbiguousGlobReexports { - #[label("the name `{$name}` in the {$namespace} namespace is first re-exported here")] - pub first_reexport: Span, - #[label("but the name `{$name}` in the {$namespace} namespace is also re-exported here")] - pub duplicate_reexport: Span, + let append_candidates = |msg: &mut String, accessible_path_strings: Vec>| { + msg.push(':'); - pub name: String, - pub namespace: String, -} + for candidate in accessible_path_strings { + msg.push('\n'); + msg.push_str(&candidate.0); + } + }; + + if let Some(span) = use_placement_span { + let (add_use, trailing) = match mode { + DiagMode::Pattern => { + err.span_suggestions( + span, + msg, + accessible_path_strings.into_iter().map(|a| a.0), + Applicability::MaybeIncorrect, + ); + return true; + } + DiagMode::Import { .. } => ("", ""), + DiagMode::Normal => ("use ", ";\n"), + }; + for candidate in &mut accessible_path_strings { + // produce an additional newline to separate the new use statement + // from the directly following item. + let additional_newline = if let FoundUse::No = found_use + && let DiagMode::Normal = mode + { + "\n" + } else { + "" + }; + candidate.0 = + format!("{add_use}{}{append}{trailing}{additional_newline}", candidate.0); + } -#[derive(Diagnostic)] -#[diag("private item shadows public glob re-export")] -pub(crate) struct HiddenGlobReexports { - #[note( - "the name `{$name}` in the {$namespace} namespace is supposed to be publicly re-exported here" - )] - pub glob_reexport: Span, - #[note("but the private item here shadows it")] - pub private_item: Span, - - pub name: String, - pub namespace: String, -} + match mode { + DiagMode::Import { append: true, .. } => { + append_candidates(&mut msg, accessible_path_strings); + err.span_help(span, msg); + } + _ => { + err.span_suggestions_with_style( + span, + msg, + accessible_path_strings.into_iter().map(|a| a.0), + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + } + } -#[derive(Diagnostic)] -#[diag("the item `{$ident}` is imported redundantly")] -pub(crate) struct RedundantImport { - #[subdiagnostic] - pub subs: Vec, - pub ident: Ident, -} + if let [first, .., last] = &path[..] { + let sp = first.ident.span.until(last.ident.span); + // Our suggestion is empty, so make sure the span is not empty (or we'd ICE). + // Can happen for derive-generated spans. + if sp.can_be_used_for_suggestions() && !sp.is_empty() { + err.span_suggestion_verbose( + sp, + format!("if you import `{}`, refer to it directly", last.ident), + "", + Applicability::Unspecified, + ); + } + } + } else { + append_candidates(&mut msg, accessible_path_strings); + err.help(msg); + } + showed = true; + } + if !inaccessible_path_strings.is_empty() + && (!matches!(mode, DiagMode::Import { unresolved_import: false, .. })) + { + let prefix = + if let DiagMode::Pattern = mode { "you might have meant to match on " } else { "" }; + if let [(name, descr, source_span, note, _)] = &inaccessible_path_strings[..] { + let msg = format!( + "{prefix}{descr} `{name}`{} exists but is inaccessible", + if let DiagMode::Pattern = mode { ", which" } else { "" } + ); + + if let Some(source_span) = source_span { + let span = tcx.sess.source_map().guess_head_span(*source_span); + let mut multi_span = MultiSpan::from_span(span); + multi_span.push_span_label(span, "not accessible"); + err.span_note(multi_span, msg); + } else { + err.note(msg); + } + if let Some(note) = (*note).as_deref() { + err.note(note.to_string()); + } + } else { + let descr = inaccessible_path_strings + .iter() + .map(|&(_, descr, _, _, _)| descr) + .all_equal_value() + .unwrap_or("item"); + let plural_descr = + if descr.ends_with('s') { format!("{descr}es") } else { format!("{descr}s") }; + + let mut msg = format!("{prefix}these {plural_descr} exist but are inaccessible"); + let mut has_colon = false; + + let mut spans = Vec::new(); + for (name, _, source_span, _, _) in &inaccessible_path_strings { + if let Some(source_span) = source_span { + let span = tcx.sess.source_map().guess_head_span(*source_span); + spans.push((name, span)); + } else { + if !has_colon { + msg.push(':'); + has_colon = true; + } + msg.push('\n'); + msg.push_str(name); + } + } -#[derive(Subdiagnostic)] -pub(crate) enum RedundantImportSub { - #[label("the item `{$ident}` is already imported here")] - ImportedHere { - #[primary_span] - span: Span, - ident: Ident, - }, - #[label("the item `{$ident}` is already defined here")] - DefinedHere { - #[primary_span] - span: Span, - ident: Ident, - }, - #[label("the item `{$ident}` is already imported by the extern prelude")] - ImportedPrelude { - #[primary_span] - span: Span, - ident: Ident, - }, - #[label("the item `{$ident}` is already defined by the extern prelude")] - DefinedPrelude { - #[primary_span] - span: Span, - ident: Ident, - }, -} + let mut multi_span = MultiSpan::from_spans(spans.iter().map(|(_, sp)| *sp).collect()); + for (name, span) in spans { + multi_span.push_span_label(span, format!("`{name}`: not accessible")); + } -#[derive(Diagnostic)] -#[diag("unnecessary qualification")] -pub(crate) struct UnusedQualifications { - #[suggestion( - "remove the unnecessary path segments", - style = "verbose", - code = "", - applicability = "machine-applicable" - )] - pub removal_span: Span, -} + for note in inaccessible_path_strings.iter().flat_map(|cand| cand.3.as_ref()) { + err.note(note.clone()); + } -#[derive(Diagnostic)] -#[diag( - "{$elided -> - [true] `&` without an explicit lifetime name cannot be used here - *[false] `'_` cannot be used here - }" -)] -pub(crate) struct AssociatedConstElidedLifetime { - #[suggestion( - "use the `'static` lifetime", - style = "verbose", - code = "{code}", - applicability = "machine-applicable" - )] - pub span: Span, - - pub code: &'static str, - pub elided: bool, - #[note("cannot automatically infer `'static` because of other lifetimes in scope")] - pub lifetimes_in_scope: MultiSpan, + err.span_note(multi_span, msg); + } + showed = true; + } + showed } -#[derive(Diagnostic)] -#[diag("lifetime parameter `{$ident}` only used once")] -pub(crate) struct SingleUseLifetime { - #[label("this lifetime...")] - pub param_span: Span, - #[label("...is used only here")] - pub use_span: Span, - #[subdiagnostic] - pub suggestion: Option, - - pub ident: Ident, +#[derive(Debug)] +struct UsePlacementFinder { + target_module: NodeId, + first_legal_span: Option, + first_use_span: Option, +} + +impl UsePlacementFinder { + fn check(krate: &Crate, target_module: NodeId) -> (Option, FoundUse) { + let mut finder = + UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None }; + finder.visit_crate(krate); + if let Some(use_span) = finder.first_use_span { + (Some(use_span), FoundUse::Yes) + } else { + (finder.first_legal_span, FoundUse::No) + } + } } -#[derive(Subdiagnostic)] -#[multipart_suggestion("elide the single-use lifetime", applicability = "machine-applicable")] -pub(crate) struct SingleUseLifetimeSugg { - #[suggestion_part(code = "")] - pub deletion_span: Option, - #[suggestion_part(code = "{replace_lt}")] - pub use_span: Span, - - pub replace_lt: String, -} +impl<'tcx> Visitor<'tcx> for UsePlacementFinder { + fn visit_crate(&mut self, c: &Crate) { + if self.target_module == CRATE_NODE_ID { + let inject = c.spans.inject_use_span; + if is_span_suitable_for_use_injection(inject) { + self.first_legal_span = Some(inject); + } + self.first_use_span = search_for_any_use_in_items(&c.items); + } else { + visit::walk_crate(self, c); + } + } -#[derive(Diagnostic)] -#[diag( - "absolute paths must start with `self`, `super`, `crate`, or an external crate name in the 2018 edition" -)] -pub(crate) struct AbsPathWithModule { - #[subdiagnostic] - pub sugg: AbsPathWithModuleSugg, + fn visit_item(&mut self, item: &'tcx ast::Item) { + if self.target_module == item.id { + if let ItemKind::Mod(_, _, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind { + let inject = mod_spans.inject_use_span; + if is_span_suitable_for_use_injection(inject) { + self.first_legal_span = Some(inject); + } + self.first_use_span = search_for_any_use_in_items(items); + } + } else { + visit::walk_item(self, item); + } + } } -#[derive(Subdiagnostic)] -#[suggestion("use `crate`", code = "{replacement}")] -pub(crate) struct AbsPathWithModuleSugg { - #[primary_span] - pub span: Span, - #[applicability] - pub applicability: Applicability, - pub replacement: String, +#[derive(Default)] +struct BindingVisitor { + identifiers: Vec, + spans: FxHashMap>, } -#[derive(Diagnostic)] -#[diag("hidden lifetime parameters in types are deprecated")] -pub(crate) struct ElidedLifetimesInPaths { - #[subdiagnostic] - pub subdiag: rustc_errors::ElidedLifetimeInPathSubdiag, +impl<'tcx> Visitor<'tcx> for BindingVisitor { + fn visit_pat(&mut self, pat: &ast::Pat) { + if let ast::PatKind::Ident(_, ident, _) = pat.kind { + self.identifiers.push(ident.name); + self.spans.entry(ident.name).or_default().push(ident.span); + } + visit::walk_pat(self, pat); + } } -#[derive(Diagnostic)] -#[diag( - "{$num_snippets -> - [one] unused import: {$span_snippets} - *[other] unused imports: {$span_snippets} - }" -)] -pub(crate) struct UnusedImports { - #[subdiagnostic] - pub sugg: Option, - #[help("if this is a test module, consider adding a `#[cfg(test)]` to the containing module")] - pub test_module_span: Option, - - pub span_snippets: DiagArgValue, - pub num_snippets: usize, +fn search_for_any_use_in_items(items: &[Box]) -> Option { + for item in items { + if let ItemKind::Use(..) = item.kind + && is_span_suitable_for_use_injection(item.span) + { + let mut lo = item.span.lo(); + for attr in &item.attrs { + if attr.span.eq_ctxt(item.span) { + lo = std::cmp::min(lo, attr.span.lo()); + } + } + return Some(Span::new(lo, lo, item.span.ctxt(), item.span.parent())); + } + } + None } -#[derive(Subdiagnostic)] -pub(crate) enum UnusedImportsSugg { - #[suggestion( - "remove the whole `use` item", - applicability = "machine-applicable", - code = "", - style = "tool-only" - )] - RemoveWholeUse { - #[primary_span] - span: Span, - }, - #[multipart_suggestion( - "{$num_to_remove -> - [one] remove the unused import - *[other] remove the unused imports - }", - applicability = "machine-applicable", - style = "tool-only" - )] - RemoveImports { - #[suggestion_part(code = "")] - remove_spans: Vec, - num_to_remove: usize, - }, +fn is_span_suitable_for_use_injection(s: Span) -> bool { + // don't suggest placing a use before the prelude + // import or other generated ones + !s.from_expansion() } diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 49461651412b2..2e69405811db4 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -14,7 +14,7 @@ use rustc_span::{Ident, Span, kw, sym}; use smallvec::SmallVec; use tracing::{debug, instrument}; -use crate::errors::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; +use crate::diagnostics::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; use crate::hygiene::Macros20NormalizedSyntaxContext; use crate::imports::{Import, NameResolution}; use crate::late::{ @@ -25,7 +25,7 @@ use crate::{ AmbiguityError, AmbiguityKind, AmbiguityWarning, BindingKey, CmResolver, Decl, DeclKind, Determinacy, Finalize, IdentKey, ImportKind, ImportSummary, LateDecl, LocalModule, Module, ModuleKind, ModuleOrUniformRoot, ParentScope, PathResult, PrivacyError, Res, ResolutionError, - Resolver, Scope, ScopeSet, Segment, Stage, Symbol, Used, errors, + Resolver, Scope, ScopeSet, Segment, Stage, Symbol, Used, diagnostics, }; #[derive(Copy, Clone)] @@ -630,7 +630,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, lint_id, orig_ident_span, - errors::ProcMacroDeriveResolutionFallback { + diagnostics::ProcMacroDeriveResolutionFallback { span: orig_ident_span, ns_descr: ns.descr(), ident: ident.name, @@ -681,7 +681,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, lint_id, orig_ident_span, - errors::ProcMacroDeriveResolutionFallback { + diagnostics::ProcMacroDeriveResolutionFallback { span: orig_ident_span, ns_descr: ns.descr(), ident: ident.name, @@ -1988,7 +1988,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { record_segment_res(self.reborrow(), finalize, res, id); } else if res == Res::ToolMod && !is_last && opt_ns.is_some() { if binding.is_import() { - self.dcx().emit_err(errors::ToolModuleImported { + self.dcx().emit_err(diagnostics::ToolModuleImported { span: ident.span, import: binding.span, }); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index f23cb3dd003a9..a04a0bcfc0b7d 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -33,13 +33,13 @@ use rustc_span::{Ident, Span, Symbol, kw, sym}; use tracing::debug; use crate::Namespace::{self, *}; -use crate::diagnostics::{DiagMode, Suggestion, import_candidates}; -use crate::errors::{ +use crate::diagnostics::{ self, CannotBeReexportedCratePublic, CannotBeReexportedCratePublicNS, CannotBeReexportedPrivate, CannotBeReexportedPrivateNS, CannotDetermineImportResolution, CannotGlobImportAllCrates, ConsiderAddingMacroExport, ConsiderMarkingAsPub, ConsiderMarkingAsPubCrate, }; +use crate::errors::{DiagMode, Suggestion, import_candidates}; use crate::ref_mut::CmCell; use crate::{ AmbiguityError, BindingKey, CmResolver, Decl, DeclData, DeclKind, Determinacy, Finalize, @@ -1014,7 +1014,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { AMBIGUOUS_GLOB_REEXPORTS, import.root_id, import.root_span, - errors::AmbiguousGlobReexports { + diagnostics::AmbiguousGlobReexports { name: key.ident.name.to_string(), namespace: key.ns.descr().to_string(), first_reexport: import.root_span, @@ -1045,7 +1045,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { HIDDEN_GLOB_REEXPORTS, binding_id, binding.span, - errors::HiddenGlobReexports { + diagnostics::HiddenGlobReexports { name: key.ident.name.to_string(), namespace: key.ns.descr().to_owned(), glob_reexport: glob_decl.span, @@ -1069,7 +1069,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { EXPORTED_PRIVATE_DEPENDENCIES, binding_id, binding.span, - crate::errors::ReexportPrivateDependency { + crate::diagnostics::ReexportPrivateDependency { name: key.ident.name, kind: binding.res().descr(), krate: self.tcx.crate_name(reexported_def_id.krate), @@ -1462,7 +1462,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { UNUSED_IMPORTS, id, import.span, - crate::errors::RedundantImportVisibility { + crate::diagnostics::RedundantImportVisibility { span: import.span, help: (), max_vis: max_vis.to_string(def_id, self.tcx), @@ -1778,7 +1778,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { { let ImportKind::Single { id, .. } = import.kind else { unreachable!() }; let sugg = self.tcx.source_span(extern_crate_id).shrink_to_lo(); - let diagnostic = crate::errors::PrivateExternCrateReexport { ident, sugg }; + let diagnostic = crate::diagnostics::PrivateExternCrateReexport { ident, sugg }; return Some(BufferedEarlyLint { lint_id: LintId::of(PUB_USE_OF_PRIVATE_EXTERN_CRATE), node_id: id, @@ -1889,20 +1889,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .into_iter() .map(|(span, is_imported)| match (span.is_dummy(), is_imported) { (false, true) => { - errors::RedundantImportSub::ImportedHere { span, ident } + diagnostics::RedundantImportSub::ImportedHere { span, ident } } (false, false) => { - errors::RedundantImportSub::DefinedHere { span, ident } + diagnostics::RedundantImportSub::DefinedHere { span, ident } } (true, true) => { - errors::RedundantImportSub::ImportedPrelude { span, ident } + diagnostics::RedundantImportSub::ImportedPrelude { span, ident } } (true, false) => { - errors::RedundantImportSub::DefinedPrelude { span, ident } + diagnostics::RedundantImportSub::DefinedPrelude { span, ident } } }) .collect(); - errors::RedundantImport { subs, ident }.into_diag(dcx, level) + diagnostics::RedundantImport { subs, ident }.into_diag(dcx, level) }, ); return true; diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 42738118c271c..5ae2aaadffae7 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -42,7 +42,7 @@ use tracing::{debug, instrument, trace}; use crate::{ BindingError, BindingKey, Decl, DelegationFnSig, Finalize, IdentKey, LateDecl, LocalModule, Module, ModuleOrUniformRoot, ParentScope, PathResult, Res, ResolutionError, Resolver, Segment, - Stage, TyCtxt, UseError, Used, errors, path_names_to_string, rustdoc, with_owner, + Stage, TyCtxt, UseError, Used, path_names_to_string, rustdoc, with_owner, }; mod diagnostics; @@ -1916,7 +1916,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { lint::builtin::ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT, node_id, lifetime.ident.span, - crate::errors::AssociatedConstElidedLifetime { + crate::diagnostics::AssociatedConstElidedLifetime { elided, code, span: lt_span, @@ -1934,7 +1934,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { .. } = rib.kind { - Some(errors::ElidedAnonymousLifetimeReportErrorSuggestion { + Some(crate::diagnostics::ElidedAnonymousLifetimeReportErrorSuggestion { lo: span.shrink_to_lo(), hi: lifetime.ident.span.shrink_to_hi(), }) @@ -1956,10 +1956,12 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { trait_id, &["core", "iter", "traits", "iterator", "Iterator"], ) { - self.r.dcx().emit_err(errors::LendingIteratorReportError { - lifetime: lifetime.ident.span, - ty: ty.span, - }) + self.r.dcx().emit_err( + crate::diagnostics::LendingIteratorReportError { + lifetime: lifetime.ident.span, + ty: ty.span, + }, + ) } else { let decl = if !trait_id.is_local() && let Some(assoc) = self.diag_metadata.current_impl_item @@ -1983,7 +1985,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { DUMMY_SP.into() }; let mut err = self.r.dcx().create_err( - errors::AnonymousLifetimeNonGatReportError { + crate::diagnostics::AnonymousLifetimeNonGatReportError { lifetime: lifetime.ident.span, decl, }, @@ -1992,15 +1994,19 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { err.emit() } } else { - self.r.dcx().emit_err(errors::ElidedAnonymousLifetimeReportError { - span: lifetime.ident.span, - suggestion, - }) + self.r.dcx().emit_err( + crate::diagnostics::ElidedAnonymousLifetimeReportError { + span: lifetime.ident.span, + suggestion, + }, + ) } } else { - self.r.dcx().emit_err(errors::ExplicitAnonymousLifetimeReportError { - span: lifetime.ident.span, - }) + self.r.dcx().emit_err( + crate::diagnostics::ExplicitAnonymousLifetimeReportError { + span: lifetime.ident.span, + }, + ) }; self.record_lifetime_err(lifetime.id, guar); return; @@ -2284,11 +2290,12 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { !segment.has_generic_args, elided_lifetime_span, ); - let guar = - self.r.dcx().emit_err(errors::ImplicitElidedLifetimeNotAllowedHere { + let guar = self.r.dcx().emit_err( + crate::diagnostics::ImplicitElidedLifetimeNotAllowedHere { span: path_span, subdiag, - }); + }, + ); should_lint = false; for id in node_ids { @@ -2360,7 +2367,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { .downcast_ref::() .expect("expected a `Session`") .source_map(); - errors::ElidedLifetimesInPaths { + crate::diagnostics::ElidedLifetimesInPaths { subdiag: elided_lifetime_in_path_suggestion( source_map, expected_lifetimes, @@ -2775,7 +2782,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let report_error = |this: &Self, ns| { if this.should_report_errs() { let what = if ns == TypeNS { "type parameters" } else { "local variables" }; - this.r.dcx().emit_err(errors::ImportsCannotReferTo { span: ident.span, what }); + this.r.dcx().emit_err(crate::diagnostics::ImportsCannotReferTo { + span: ident.span, + what, + }); } }; @@ -3165,7 +3175,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { let guar = self .r .dcx() - .create_err(errors::UnderscoreLifetimeIsReserved { span: param.ident.span }) + .create_err(crate::diagnostics::UnderscoreLifetimeIsReserved { + span: param.ident.span, + }) .emit_unless_delay(is_raw_underscore_lifetime); // Record lifetime res, so lowering knows there is something fishy. self.record_lifetime_err(param.id, guar); @@ -3173,10 +3185,11 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } if param.ident.name == kw::StaticLifetime { - let guar = self.r.dcx().emit_err(errors::StaticLifetimeIsReserved { - span: param.ident.span, - lifetime: param.ident, - }); + let guar = + self.r.dcx().emit_err(crate::diagnostics::StaticLifetimeIsReserved { + span: param.ident.span, + lifetime: param.ident, + }); // Record lifetime res, so lowering knows there is something fishy. self.record_lifetime_err(param.id, guar); continue; @@ -4501,7 +4514,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { Visibility::Restricted(def_id), self.parent_scope.module, ) { - self.r.dcx().create_err(errors::RestrictionAncestorOnly(path.span)).emit(); + self.r + .dcx() + .create_err(crate::diagnostics::RestrictionAncestorOnly(path.span)) + .emit(); } } } @@ -5690,7 +5706,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { lint::builtin::UNUSED_LABELS, *id, *span, - errors::UnusedLabel, + crate::diagnostics::UnusedLabel, ); } }) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index f089bb170404f..3493d9a495033 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -32,7 +32,7 @@ use thin_vec::ThinVec; use tracing::debug; use super::NoConstantGenericsReason; -use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; +use crate::errors::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; use crate::late::{ AliasPossibility, LateResolutionVisitor, LifetimeBinderKind, LifetimeRes, LifetimeRibKind, LifetimeUseSet, QSelf, RibKind, @@ -40,7 +40,7 @@ use crate::late::{ use crate::ty::fast_reject::SimplifiedType; use crate::{ Finalize, Module, ModuleOrUniformRoot, ParentScope, PathResult, PathSource, Res, Resolver, - ScopeSet, Segment, errors, path_names_to_string, + ScopeSet, Segment, diagnostics, path_names_to_string, }; /// A field or associated item from self type suggested in case of resolution failure. @@ -1594,7 +1594,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { Side::Start => (segment.ident.span.between(range.span), " @ ".into()), Side::End => (range.span.to(segment.ident.span), format!("{} @ ..", segment.ident)), }; - err.subdiagnostic(errors::UnexpectedResUseAtOpInSlicePatWithRangeSugg { + err.subdiagnostic(diagnostics::UnexpectedResUseAtOpInSlicePatWithRangeSugg { span, ident: segment.ident, snippet, @@ -1758,7 +1758,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }) else { return; }; - err.subdiagnostic(errors::UnexpectedResChangeTyParamToConstParamSugg { + err.subdiagnostic(diagnostics::UnexpectedResChangeTyParamToConstParamSugg { before: span.shrink_to_lo(), after: span.shrink_to_hi(), }); @@ -1801,7 +1801,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { }); if let Some(param) = param { - err.subdiagnostic(errors::UnexpectedResChangeTyToConstParamSugg { + err.subdiagnostic(diagnostics::UnexpectedResChangeTyToConstParamSugg { span: param.shrink_to_lo(), applicability, }); @@ -3437,7 +3437,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { format!("corresponding const parameter on the type defined here",), ); - err.subdiagnostic(errors::UnexpectedMissingConstParameter { + err.subdiagnostic(diagnostics::UnexpectedMissingConstParameter { span: insert_span, snippet, item_name: format!("{}", target_ident), @@ -3662,7 +3662,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else { Some(deletion_span) }; - Some(errors::SingleUseLifetimeSugg { + Some(diagnostics::SingleUseLifetimeSugg { deletion_span, use_span, replace_lt, @@ -3670,7 +3670,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } else { None }; - errors::SingleUseLifetime { + diagnostics::SingleUseLifetime { suggestion, param_span: param_ident.span, use_span, @@ -3690,7 +3690,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { lint::builtin::UNUSED_LIFETIMES, param.id, param.ident.span, - errors::UnusedLifetime { deletion_span, ident: param.ident }, + diagnostics::UnusedLifetime { deletion_span, ident: param.ident }, ); } } @@ -3907,7 +3907,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { ) -> ErrorGuaranteed { self.r .dcx() - .create_err(errors::ParamInTyOfConstParam { + .create_err(diagnostics::ParamInTyOfConstParam { span: lifetime_ref.ident.span, name: lifetime_ref.ident.name, }) @@ -3926,20 +3926,20 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { NoConstantGenericsReason::IsEnumDiscriminant => self .r .dcx() - .create_err(errors::ParamInEnumDiscriminant { + .create_err(diagnostics::ParamInEnumDiscriminant { span: lifetime_ref.ident.span, name: lifetime_ref.ident.name, - param_kind: errors::ParamKindInEnumDiscriminant::Lifetime, + param_kind: diagnostics::ParamKindInEnumDiscriminant::Lifetime, }) .emit(), NoConstantGenericsReason::NonTrivialConstArg => { assert!(!self.r.tcx.features().generic_const_exprs()); self.r .dcx() - .create_err(errors::ParamInNonTrivialAnonConst { + .create_err(diagnostics::ParamInNonTrivialAnonConst { span: lifetime_ref.ident.span, name: lifetime_ref.ident.name, - param_kind: errors::ParamKindInNonTrivialAnonConst::Lifetime, + param_kind: diagnostics::ParamKindInNonTrivialAnonConst::Lifetime, help: self.r.tcx.sess.is_nightly_build(), is_gca: self.r.tcx.features().generic_const_args(), help_gca: self.r.tcx.features().generic_const_args(), diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 769466a112ff8..1bca5badc2d81 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -26,9 +26,9 @@ use std::ops::ControlFlow; use std::sync::Arc; use std::{fmt, mem}; -use diagnostics::{ImportSuggestion, LabelSuggestion, StructCtor, Suggestion}; +use diagnostics::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; use effective_visibilities::EffectiveVisibilitiesVisitor; -use errors::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; +use errors::{ImportSuggestion, LabelSuggestion, StructCtor, Suggestion}; use hygiene::Macros20NormalizedSyntaxContext; use imports::{Import, ImportData, ImportKind, NameResolution, PendingDecl}; use late::{ @@ -2273,7 +2273,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PRIVATE_MACRO_USE, import.root_id, ident.span, - errors::MacroIsPrivate { ident }, + diagnostics::MacroIsPrivate { ident }, ); } } diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 2a5c2e6c2df83..fade23f9cf308 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -35,7 +35,7 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; use crate::Namespace::*; use crate::def_collector::collect_definitions; -use crate::errors::{ +use crate::diagnostics::{ self, AddAsNonDerive, CannotDetermineMacroResolution, CannotFindIdentInThisScope, MacroExpectedFound, RemoveSurroundingDerive, }; @@ -138,7 +138,7 @@ pub fn registered_tools_ast( { for tool in tools { if let Some(old_tool) = registered_tools.replace(tool) { - dcx.emit_err(errors::ToolWasAlreadyRegistered { + dcx.emit_err(diagnostics::ToolWasAlreadyRegistered { span: tool.span, tool, old_ident_span: old_tool.span, @@ -348,7 +348,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { UNUSED_MACROS, node_id, ident.span, - errors::UnusedMacroDefinition { name: ident.name }, + diagnostics::UnusedMacroDefinition { name: ident.name }, ); // Do not report unused individual rules if the entire macro is unused self.unused_macro_rules.swap_remove(&node_id); @@ -368,7 +368,7 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { UNUSED_MACRO_RULES, node_id, rule_span, - errors::MacroRuleNeverUsed { n: arm_i + 1, name: ident.name }, + diagnostics::MacroRuleNeverUsed { n: arm_i + 1, name: ident.name }, ); } } @@ -621,15 +621,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // Report errors for the resolved macro. for (idx, segment) in path.segments.iter().enumerate() { if let Some(args) = &segment.args { - self.dcx().emit_err(errors::GenericArgumentsInMacroPath { span: args.span() }); + self.dcx().emit_err(diagnostics::GenericArgumentsInMacroPath { span: args.span() }); } if kind == MacroKind::Attr && segment.ident.as_str().starts_with("rustc") { if idx == 0 { - self.dcx().emit_err(errors::AttributesStartingWithRustcAreReserved { + self.dcx().emit_err(diagnostics::AttributesStartingWithRustcAreReserved { span: segment.ident.span, }); } else { - self.dcx().emit_err(errors::AttributesContainingRustcAreReserved { + self.dcx().emit_err(diagnostics::AttributesContainingRustcAreReserved { span: segment.ident.span, }); } @@ -641,7 +641,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(def_id) = def_id.as_local() { self.unused_macros.swap_remove(&def_id); if self.proc_macro_stubs.contains(&def_id) { - self.dcx().emit_err(errors::ProcMacroSameCrate { + self.dcx().emit_err(diagnostics::ProcMacroSameCrate { span: path.span, is_test: self.tcx.sess.is_test_crate(), }); @@ -740,9 +740,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if let Some(feature) = *feature && *attr == name { - break 'help Some(errors::UnknownDiagnosticAttributeHelp::UseFeature { - feature, - }); + break 'help Some( + diagnostics::UnknownDiagnosticAttributeHelp::UseFeature { feature }, + ); } } } @@ -755,7 +755,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { .collect::>(); find_best_match_for_name(&candidates, name, None).map(|typo_name| { - errors::UnknownDiagnosticAttributeHelp::Typo { span, typo_name } + diagnostics::UnknownDiagnosticAttributeHelp::Typo { span, typo_name } }) }; @@ -763,7 +763,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { UNKNOWN_DIAGNOSTIC_ATTRIBUTES, span, node_id, - errors::UnknownDiagnosticAttribute { help }, + diagnostics::UnknownDiagnosticAttribute { help }, ); } @@ -1061,7 +1061,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { LEGACY_DERIVE_HELPERS, node_id, ident.span, - errors::LegacyDeriveHelpers { span: binding.span }, + diagnostics::LegacyDeriveHelpers { span: binding.span }, ); } } @@ -1146,7 +1146,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) { if let Some(Res::NonMacroAttr(kind)) = res { if kind != NonMacroAttrKind::Tool && decl.is_none_or(|b| b.is_import()) { - self.dcx().emit_err(errors::CannotUseThroughAnImport { + self.dcx().emit_err(diagnostics::CannotUseThroughAnImport { span, article: kind.article(), descr: kind.descr(), @@ -1206,7 +1206,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { OUT_OF_SCOPE_MACRO_CALLS, path.span, node_id, - errors::OutOfScopeMacroCalls { + diagnostics::OutOfScopeMacroCalls { span: path.span, path: pprust::path_to_string(path), location, @@ -1222,7 +1222,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if name == sym::cfg || name == sym::cfg_attr { let macro_kinds = res.macro_kinds(); if macro_kinds.is_some() && sub_namespace_match(macro_kinds, Some(MacroKind::Attr)) { - self.dcx().emit_err(errors::NameReservedInAttributeNamespace { span, ident: name }); + self.dcx() + .emit_err(diagnostics::NameReservedInAttributeNamespace { span, ident: name }); } } } @@ -1257,7 +1258,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { // while still taking everything else from the source code. ext.kind = builtin_ext_kind.clone(); } else { - self.dcx().emit_err(errors::CannotFindBuiltinMacroWithName { span, ident }); + self.dcx().emit_err(diagnostics::CannotFindBuiltinMacroWithName { span, ident }); } } @@ -1284,7 +1285,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { PathResult::NonModule(..) | // HACK(Urgau): This shouldn't be necessary PathResult::Failed { is_error_from_last_segment: false, .. } => { - self.dcx().emit_err(errors::CfgAccessibleUnsure { span }); + self.dcx().emit_err(diagnostics::CfgAccessibleUnsure { span }); // If we get a partially resolved NonModule in one namespace, we should get the // same result in any other namespaces, so we can return early. From 4f6a600bfee99e574be6db687a18a8b905c25d15 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 16 Jun 2026 16:21:24 +0200 Subject: [PATCH 23/23] Rename `rustc_resolve/errors.rs` into `rustc_resolve/error_helper.rs` --- compiler/rustc_resolve/src/build_reduced_graph.rs | 2 +- compiler/rustc_resolve/src/{errors.rs => error_helper.rs} | 0 compiler/rustc_resolve/src/imports.rs | 2 +- compiler/rustc_resolve/src/late/diagnostics.rs | 2 +- compiler/rustc_resolve/src/lib.rs | 4 ++-- 5 files changed, 5 insertions(+), 5 deletions(-) rename compiler/rustc_resolve/src/{errors.rs => error_helper.rs} (100%) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index fdd194b526937..eb72ebee4e41b 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -31,7 +31,7 @@ use tracing::debug; use crate::Namespace::{MacroNS, TypeNS, ValueNS}; use crate::def_collector::DefCollector; -use crate::errors::StructCtor; +use crate::error_helper::StructCtor; use crate::imports::{ImportData, ImportKind, OnUnknownData}; use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef}; use crate::ref_mut::CmCell; diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/error_helper.rs similarity index 100% rename from compiler/rustc_resolve/src/errors.rs rename to compiler/rustc_resolve/src/error_helper.rs diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index a04a0bcfc0b7d..a6c7d2fd2c94e 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -39,7 +39,7 @@ use crate::diagnostics::{ CannotGlobImportAllCrates, ConsiderAddingMacroExport, ConsiderMarkingAsPub, ConsiderMarkingAsPubCrate, }; -use crate::errors::{DiagMode, Suggestion, import_candidates}; +use crate::error_helper::{DiagMode, Suggestion, import_candidates}; use crate::ref_mut::CmCell; use crate::{ AmbiguityError, BindingKey, CmResolver, Decl, DeclData, DeclKind, Determinacy, Finalize, diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 3493d9a495033..8994a7713c858 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -32,7 +32,7 @@ use thin_vec::ThinVec; use tracing::debug; use super::NoConstantGenericsReason; -use crate::errors::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; +use crate::error_helper::{ImportSuggestion, LabelSuggestion, TypoSuggestion}; use crate::late::{ AliasPossibility, LateResolutionVisitor, LifetimeBinderKind, LifetimeRes, LifetimeRibKind, LifetimeUseSet, QSelf, RibKind, diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 1bca5badc2d81..c7b4686fcd234 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -28,7 +28,7 @@ use std::{fmt, mem}; use diagnostics::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; use effective_visibilities::EffectiveVisibilitiesVisitor; -use errors::{ImportSuggestion, LabelSuggestion, StructCtor, Suggestion}; +use error_helper::{ImportSuggestion, LabelSuggestion, StructCtor, Suggestion}; use hygiene::Macros20NormalizedSyntaxContext; use imports::{Import, ImportData, ImportKind, NameResolution, PendingDecl}; use late::{ @@ -82,7 +82,7 @@ mod check_unused; mod def_collector; mod diagnostics; mod effective_visibilities; -mod errors; +mod error_helper; mod ident; mod imports; mod late;