From a11fbeec1fc0ad1b8c8814dd2fa4704c4d5e8e0e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 7 Mar 2025 17:09:12 +0100 Subject: [PATCH 01/26] Small code improvement in rustdoc hidden stripper --- src/librustdoc/passes/strip_hidden.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs index bcdca862862d4..692ce21d6cfbc 100644 --- a/src/librustdoc/passes/strip_hidden.rs +++ b/src/librustdoc/passes/strip_hidden.rs @@ -91,19 +91,21 @@ impl DocFolder for Stripper<'_, '_> { if let clean::ImportItem(clean::Import { source, .. }) = &i.kind && let Some(source_did) = source.did - && let Some(import_def_id) = i.def_id().and_then(|def_id| def_id.as_local()) { - let reexports = reexport_chain(self.tcx, import_def_id, source_did); + if self.tcx.is_doc_hidden(source_did) { + return None; + } else if let Some(import_def_id) = i.def_id().and_then(|def_id| def_id.as_local()) { + let reexports = reexport_chain(self.tcx, import_def_id, source_did); - // Check if any reexport in the chain has a hidden source - let has_hidden_source = reexports - .iter() - .filter_map(|reexport| reexport.id()) - .any(|reexport_did| self.tcx.is_doc_hidden(reexport_did)) - || self.tcx.is_doc_hidden(source_did); + // Check if any reexport in the chain has a hidden source + let has_hidden_source = reexports + .iter() + .filter_map(|reexport| reexport.id()) + .any(|reexport_did| self.tcx.is_doc_hidden(reexport_did)); - if has_hidden_source { - return None; + if has_hidden_source { + return None; + } } } From e89c84913690bad837b658ade502cb0af7df768b Mon Sep 17 00:00:00 2001 From: xizheyin Date: Mon, 17 Mar 2025 20:40:01 +0800 Subject: [PATCH 02/26] Clean up librustdoc::html::render to be better encapsulated Signed-off-by: xizheyin --- src/librustdoc/formats/cache.rs | 14 ++--- src/librustdoc/html/render/mod.rs | 93 ++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 2648641e53e75..2a612fa9b8cda 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -574,20 +574,20 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It ); let aliases = item.attrs.get_doc_aliases(); let deprecation = item.deprecation(tcx); - let index_item = IndexItem { - ty: item.type_(), - defid: Some(defid), + let index_item = IndexItem::new( + item.type_(), + Some(defid), name, path, desc, - parent: parent_did, - parent_idx: None, - exact_path: None, + parent_did, + None, + None, impl_id, search_type, aliases, deprecation, - }; + ); cache.search_index.push(index_item); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 2237e0f987bc5..b9a2e65b09076 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -13,6 +13,9 @@ //! is cloned per-thread and contains information about what is currently being //! rendered. //! +//! The main entry point to the rendering system is the implementation of +//! `FormatRenderer` on `Context`. +//! //! In order to speed up rendering (mostly because of markdown rendering), the //! rendering process has been parallelized. This parallelization is only //! exposed through the `crate` method on the context, and then also from the @@ -90,7 +93,7 @@ pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display { /// Specifies whether rendering directly implemented trait items or ones from a certain Deref /// impl. #[derive(Copy, Clone, Debug)] -pub(crate) enum AssocItemRender<'a> { +enum AssocItemRender<'a> { All, DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool }, } @@ -98,7 +101,7 @@ pub(crate) enum AssocItemRender<'a> { /// For different handling of associated items from the Deref target of a type rather than the type /// itself. #[derive(Copy, Clone, PartialEq)] -pub(crate) enum RenderMode { +enum RenderMode { Normal, ForDeref { mut_: bool }, } @@ -110,23 +113,55 @@ pub(crate) enum RenderMode { /// by hand to a large JS file at the end of cache-creation. #[derive(Debug)] pub(crate) struct IndexItem { - pub(crate) ty: ItemType, - pub(crate) defid: Option, - pub(crate) name: Symbol, - pub(crate) path: String, - pub(crate) desc: String, - pub(crate) parent: Option, - pub(crate) parent_idx: Option, - pub(crate) exact_path: Option, - pub(crate) impl_id: Option, - pub(crate) search_type: Option, - pub(crate) aliases: Box<[Symbol]>, - pub(crate) deprecation: Option, + ty: ItemType, + defid: Option, + name: Symbol, + path: String, + desc: String, + parent: Option, + parent_idx: Option, + exact_path: Option, + impl_id: Option, + search_type: Option, + aliases: Box<[Symbol]>, + deprecation: Option, +} + +impl IndexItem { + pub fn new( + ty: ItemType, + defid: Option, + name: Symbol, + path: String, + desc: String, + parent: Option, + parent_idx: Option, + exact_path: Option, + impl_id: Option, + search_type: Option, + aliases: Box<[Symbol]>, + deprecation: Option, + ) -> Self { + Self { + ty, + defid, + name, + path, + desc, + parent, + parent_idx, + exact_path, + impl_id, + search_type, + aliases, + deprecation, + } + } } /// A type used for the search index. #[derive(Debug, Eq, PartialEq)] -pub(crate) struct RenderType { +struct RenderType { id: Option, generics: Option>, bindings: Option)>>, @@ -137,7 +172,7 @@ impl RenderType { // The contents of the lists are always integers in self-terminating hex // form, handled by `RenderTypeId::write_to_string`, so no commas are // needed to separate the items. - pub fn write_to_string(&self, string: &mut String) { + fn write_to_string(&self, string: &mut String) { fn write_optional_id(id: Option, string: &mut String) { // 0 is a sentinel, everything else is one-indexed match id { @@ -177,7 +212,7 @@ impl RenderType { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(crate) enum RenderTypeId { +enum RenderTypeId { DefId(DefId), Primitive(clean::PrimitiveType), AssociatedType(Symbol), @@ -186,7 +221,7 @@ pub(crate) enum RenderTypeId { } impl RenderTypeId { - pub fn write_to_string(&self, string: &mut String) { + fn write_to_string(&self, string: &mut String) { let id: i32 = match &self { // 0 is a sentinel, everything else is one-indexed // concrete type @@ -209,7 +244,7 @@ pub(crate) struct IndexItemFunctionType { } impl IndexItemFunctionType { - pub fn write_to_string<'a>( + fn write_to_string<'a>( &'a self, string: &mut String, backref_queue: &mut VecDeque<&'a IndexItemFunctionType>, @@ -309,7 +344,7 @@ impl ItemEntry { } impl ItemEntry { - pub(crate) fn print(&self) -> impl fmt::Display { + fn print(&self) -> impl fmt::Display { fmt::from_fn(move |f| write!(f, "{}", self.url, Escape(&self.name))) } } @@ -760,7 +795,7 @@ fn short_item_info( // Render the list of items inside one of the sections "Trait Implementations", // "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages). -pub(crate) fn render_impls( +fn render_impls( cx: &Context<'_>, mut w: impl Write, impls: &[&Impl], @@ -1201,7 +1236,7 @@ impl<'a> AssocItemLink<'a> { } } -pub fn write_section_heading( +fn write_section_heading( title: &str, id: &str, extra_class: Option<&str>, @@ -1226,7 +1261,7 @@ fn write_impl_section_heading(title: &str, id: &str) -> impl fmt::Display { write_section_heading(title, id, None, "") } -pub(crate) fn render_all_impls( +fn render_all_impls( mut w: impl Write, cx: &Context<'_>, containing_item: &clean::Item, @@ -1473,10 +1508,7 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> } } -pub(crate) fn notable_traits_button( - ty: &clean::Type, - cx: &Context<'_>, -) -> Option { +fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option { if ty.is_unit() { // Very common fast path. return None; @@ -1588,10 +1620,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { (format!("{:#}", ty.print(cx)), out) } -pub(crate) fn notable_traits_json<'a>( - tys: impl Iterator, - cx: &Context<'_>, -) -> String { +fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { let mut mp: Vec<(String, String)> = tys.map(|ty| notable_traits_decl(ty, cx)).collect(); mp.sort_by(|(name1, _html1), (name2, _html2)| name1.cmp(name2)); struct NotableTraitsMap(Vec<(String, String)>); @@ -2171,7 +2200,7 @@ fn render_rightside( }) } -pub(crate) fn render_impl_summary( +fn render_impl_summary( cx: &Context<'_>, i: &Impl, parent: &clean::Item, From abe6e88623976e36dab2f4c4fa9f01444d3b411a Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Wed, 19 Mar 2025 02:19:33 +0000 Subject: [PATCH 03/26] Allow drivers to supply a list of extra symbols to intern --- compiler/rustc_driver_impl/src/lib.rs | 1 + compiler/rustc_hir/src/tests.rs | 2 +- compiler/rustc_interface/src/interface.rs | 5 +++ compiler/rustc_interface/src/tests.rs | 2 +- compiler/rustc_interface/src/util.rs | 31 +++++++++++++------ compiler/rustc_macros/src/symbols.rs | 19 ++++++++---- compiler/rustc_metadata/src/rmeta/decoder.rs | 2 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 2 +- .../rustc_middle/src/query/on_disk_cache.rs | 4 +-- compiler/rustc_span/src/lib.rs | 15 ++++++--- compiler/rustc_span/src/symbol.rs | 24 +++++++------- compiler/rustc_span/src/symbol/tests.rs | 2 +- src/librustdoc/core.rs | 1 + src/librustdoc/doctest.rs | 1 + src/tools/clippy/Cargo.toml | 1 + .../src/attrs/deprecated_cfg_attr.rs | 4 +-- .../src/attrs/useless_attribute.rs | 4 +-- .../src/doc/needless_doctest_main.rs | 2 +- src/tools/clippy/clippy_utils/src/lib.rs | 4 ++- src/tools/clippy/clippy_utils/src/sym.rs | 23 ++++++++++++++ src/tools/clippy/src/driver.rs | 1 + tests/ui-fulldeps/run-compiler-twice.rs | 1 + 22 files changed, 104 insertions(+), 47 deletions(-) create mode 100644 src/tools/clippy/clippy_utils/src/sym.rs diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index 8ede6e413368f..3aa9a75d89343 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -259,6 +259,7 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send)) hash_untracked_state: None, register_lints: None, override_queries: None, + extra_symbols: Vec::new(), make_codegen_backend: None, registry: diagnostics_registry(), using_internal_features: &USING_INTERNAL_FEATURES, diff --git a/compiler/rustc_hir/src/tests.rs b/compiler/rustc_hir/src/tests.rs index 0837444ffdbe5..18c2bfdac8ce1 100644 --- a/compiler/rustc_hir/src/tests.rs +++ b/compiler/rustc_hir/src/tests.rs @@ -17,7 +17,7 @@ fn def_path_hash_depends_on_crate_id() { // the crate by changing the crate disambiguator (e.g. via bumping the // crate's version number). - create_session_globals_then(Edition::Edition2024, None, || { + create_session_globals_then(Edition::Edition2024, &[], None, || { let id0 = StableCrateId::new(Symbol::intern("foo"), false, vec!["1".to_string()], ""); let id1 = StableCrateId::new(Symbol::intern("foo"), false, vec!["2".to_string()], ""); diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 3f87b1a547be5..5717a2b8743aa 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -340,6 +340,10 @@ pub struct Config { /// the list of queries. pub override_queries: Option, + /// An extra set of symbols to add to the symbol interner, the symbol indices + /// will start at [`PREDEFINED_SYMBOLS_COUNT`](rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT) + pub extra_symbols: Vec<&'static str>, + /// This is a callback from the driver that is called to create a codegen backend. /// /// Has no uses within this repository, but is used by bjorn3 for "the @@ -401,6 +405,7 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se &early_dcx, config.opts.edition, config.opts.unstable_opts.threads, + &config.extra_symbols, SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, |current_gcx| { // The previous `early_dcx` can't be reused here because it doesn't diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index b44be1710edf7..903cda3cb5b55 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -53,7 +53,7 @@ where checksum_hash_kind, }); - rustc_span::create_session_globals_then(DEFAULT_EDITION, sm_inputs, || { + rustc_span::create_session_globals_then(DEFAULT_EDITION, &[], sm_inputs, || { let temps_dir = sessopts.unstable_opts.temps_dir.as_deref().map(PathBuf::from); let io = CompilerIO { input: Input::Str { name: FileName::Custom(String::new()), input: String::new() }, diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 5cccab893bb35..fceaa6577ab1b 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -117,6 +117,7 @@ fn run_in_thread_with_globals R + Send, R: Send>( thread_stack_size: usize, edition: Edition, sm_inputs: SourceMapInputs, + extra_symbols: &[&'static str], f: F, ) -> R { // The "thread pool" is a single spawned thread in the non-parallel @@ -134,9 +135,12 @@ fn run_in_thread_with_globals R + Send, R: Send>( // name contains null bytes. let r = builder .spawn_scoped(s, move || { - rustc_span::create_session_globals_then(edition, Some(sm_inputs), || { - f(CurrentGcx::new()) - }) + rustc_span::create_session_globals_then( + edition, + extra_symbols, + Some(sm_inputs), + || f(CurrentGcx::new()), + ) }) .unwrap() .join(); @@ -152,6 +156,7 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, thread_builder_diag: &EarlyDiagCtxt, edition: Edition, threads: usize, + extra_symbols: &[&'static str], sm_inputs: SourceMapInputs, f: F, ) -> R { @@ -168,12 +173,18 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, let registry = sync::Registry::new(std::num::NonZero::new(threads).unwrap()); if !sync::is_dyn_thread_safe() { - return run_in_thread_with_globals(thread_stack_size, edition, sm_inputs, |current_gcx| { - // Register the thread for use with the `WorkerLocal` type. - registry.register(); - - f(current_gcx) - }); + return run_in_thread_with_globals( + thread_stack_size, + edition, + sm_inputs, + extra_symbols, + |current_gcx| { + // Register the thread for use with the `WorkerLocal` type. + registry.register(); + + f(current_gcx) + }, + ); } let current_gcx = FromDyn::from(CurrentGcx::new()); @@ -217,7 +228,7 @@ pub(crate) fn run_in_thread_pool_with_globals R + Send, // pool. Upon creation, each worker thread created gets a copy of the // session globals in TLS. This is possible because `SessionGlobals` impls // `Send` in the parallel compiler. - rustc_span::create_session_globals_then(edition, Some(sm_inputs), || { + rustc_span::create_session_globals_then(edition, extra_symbols, Some(sm_inputs), || { rustc_span::with_session_globals(|session_globals| { let session_globals = FromDyn::from(session_globals); builder diff --git a/compiler/rustc_macros/src/symbols.rs b/compiler/rustc_macros/src/symbols.rs index 37200f62eb5a2..9fd62e898a425 100644 --- a/compiler/rustc_macros/src/symbols.rs +++ b/compiler/rustc_macros/src/symbols.rs @@ -295,10 +295,14 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { } let symbol_digits_base = entries.map["0"].idx; - let preinterned_symbols_count = entries.len(); + let predefined_symbols_count = entries.len(); let output = quote! { const SYMBOL_DIGITS_BASE: u32 = #symbol_digits_base; - const PREINTERNED_SYMBOLS_COUNT: u32 = #preinterned_symbols_count; + + /// The number of predefined symbols; this is the the first index for + /// extra pre-interned symbols in an Interner created via + /// [`Interner::with_extra_symbols`]. + pub const PREDEFINED_SYMBOLS_COUNT: u32 = #predefined_symbols_count; #[doc(hidden)] #[allow(non_upper_case_globals)] @@ -315,10 +319,13 @@ fn symbols_with_errors(input: TokenStream) -> (TokenStream, Vec) { } impl Interner { - pub(crate) fn fresh() -> Self { - Interner::prefill(&[ - #prefill_stream - ]) + /// Creates an `Interner` with the predefined symbols from the `symbols!` macro and + /// any extra symbols provided by external drivers such as Clippy + pub(crate) fn with_extra_symbols(extra_symbols: &[&'static str]) -> Self { + Interner::prefill( + &[#prefill_stream], + extra_symbols, + ) } } }; diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index f6cf218db9d0b..0c3a6096f955f 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -564,7 +564,7 @@ impl<'a, 'tcx> SpanDecoder for DecodeContext<'a, 'tcx> { } SYMBOL_PREINTERNED => { let symbol_index = self.read_u32(); - Symbol::new_from_decoded(symbol_index) + Symbol::new(symbol_index) } _ => unreachable!(), } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 7ab3d432bdf81..308f3e933f8b9 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -202,7 +202,7 @@ impl<'a, 'tcx> SpanEncoder for EncodeContext<'a, 'tcx> { fn encode_symbol(&mut self, symbol: Symbol) { // if symbol preinterned, emit tag and symbol index - if symbol.is_preinterned() { + if symbol.is_predefined() { self.opaque.emit_u8(SYMBOL_PREINTERNED); self.opaque.emit_u32(symbol.as_u32()); } else { diff --git a/compiler/rustc_middle/src/query/on_disk_cache.rs b/compiler/rustc_middle/src/query/on_disk_cache.rs index 14e3ce8bef6b2..9745a69a5a20b 100644 --- a/compiler/rustc_middle/src/query/on_disk_cache.rs +++ b/compiler/rustc_middle/src/query/on_disk_cache.rs @@ -675,7 +675,7 @@ impl<'a, 'tcx> SpanDecoder for CacheDecoder<'a, 'tcx> { } SYMBOL_PREINTERNED => { let symbol_index = self.read_u32(); - Symbol::new_from_decoded(symbol_index) + Symbol::new(symbol_index) } _ => unreachable!(), } @@ -892,7 +892,7 @@ impl<'a, 'tcx> SpanEncoder for CacheEncoder<'a, 'tcx> { // copy&paste impl from rustc_metadata fn encode_symbol(&mut self, symbol: Symbol) { // if symbol preinterned, emit tag and symbol index - if symbol.is_preinterned() { + if symbol.is_predefined() { self.encoder.emit_u8(SYMBOL_PREINTERNED); self.encoder.emit_u32(symbol.as_u32()); } else { diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index f19d4d9f3624e..432b1b86142b3 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -116,9 +116,13 @@ pub struct SessionGlobals { } impl SessionGlobals { - pub fn new(edition: Edition, sm_inputs: Option) -> SessionGlobals { + pub fn new( + edition: Edition, + extra_symbols: &[&'static str], + sm_inputs: Option, + ) -> SessionGlobals { SessionGlobals { - symbol_interner: symbol::Interner::fresh(), + symbol_interner: symbol::Interner::with_extra_symbols(extra_symbols), span_interner: Lock::new(span_encoding::SpanInterner::default()), metavar_spans: Default::default(), hygiene_data: Lock::new(hygiene::HygieneData::new(edition)), @@ -129,6 +133,7 @@ impl SessionGlobals { pub fn create_session_globals_then( edition: Edition, + extra_symbols: &[&'static str], sm_inputs: Option, f: impl FnOnce() -> R, ) -> R { @@ -137,7 +142,7 @@ pub fn create_session_globals_then( "SESSION_GLOBALS should never be overwritten! \ Use another thread if you need another SessionGlobals" ); - let session_globals = SessionGlobals::new(edition, sm_inputs); + let session_globals = SessionGlobals::new(edition, extra_symbols, sm_inputs); SESSION_GLOBALS.set(&session_globals, f) } @@ -156,7 +161,7 @@ where F: FnOnce(&SessionGlobals) -> R, { if !SESSION_GLOBALS.is_set() { - let session_globals = SessionGlobals::new(edition, None); + let session_globals = SessionGlobals::new(edition, &[], None); SESSION_GLOBALS.set(&session_globals, || SESSION_GLOBALS.with(f)) } else { SESSION_GLOBALS.with(f) @@ -172,7 +177,7 @@ where /// Default edition, no source map. pub fn create_default_session_globals_then(f: impl FnOnce() -> R) -> R { - create_session_globals_then(edition::DEFAULT_EDITION, None, f) + create_session_globals_then(edition::DEFAULT_EDITION, &[], None, f) } // If this ever becomes non thread-local, `decode_syntax_context` diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 8a8bec35d8194..a17568c249c8b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2506,15 +2506,10 @@ rustc_index::newtype_index! { } impl Symbol { - const fn new(n: u32) -> Self { + pub const fn new(n: u32) -> Self { Symbol(SymbolIndex::from_u32(n)) } - /// for use in Decoder only - pub fn new_from_decoded(n: u32) -> Self { - Self::new(n) - } - /// Maps a string to its interned representation. #[rustc_diagnostic_item = "SymbolIntern"] pub fn intern(string: &str) -> Self { @@ -2600,11 +2595,14 @@ struct InternerInner { } impl Interner { - fn prefill(init: &[&'static str]) -> Self { - Interner(Lock::new(InternerInner { - arena: Default::default(), - strings: init.iter().copied().collect(), - })) + fn prefill(init: &[&'static str], extra: &[&'static str]) -> Self { + let strings = FxIndexSet::from_iter(init.iter().copied().chain(extra.iter().copied())); + assert_eq!( + strings.len(), + init.len() + extra.len(), + "`init` or `extra` contain duplicate symbols", + ); + Interner(Lock::new(InternerInner { arena: Default::default(), strings })) } #[inline] @@ -2730,8 +2728,8 @@ impl Symbol { } /// Is this symbol was interned in compiler's `symbols!` macro - pub fn is_preinterned(self) -> bool { - self.as_u32() < PREINTERNED_SYMBOLS_COUNT + pub fn is_predefined(self) -> bool { + self.as_u32() < PREDEFINED_SYMBOLS_COUNT } } diff --git a/compiler/rustc_span/src/symbol/tests.rs b/compiler/rustc_span/src/symbol/tests.rs index c6aa7627b2b54..660d0d7179afa 100644 --- a/compiler/rustc_span/src/symbol/tests.rs +++ b/compiler/rustc_span/src/symbol/tests.rs @@ -3,7 +3,7 @@ use crate::create_default_session_globals_then; #[test] fn interner_tests() { - let i = Interner::prefill(&[]); + let i = Interner::prefill(&[], &[]); // first one is zero: assert_eq!(i.intern("dog"), Symbol::new(0)); // re-use gets the same entry: diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index c47e42670c909..c4dea79370d1f 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -321,6 +321,7 @@ pub(crate) fn create_config( (rustc_interface::DEFAULT_QUERY_PROVIDERS.typeck)(tcx, def_id) }; }), + extra_symbols: Vec::new(), make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), ice_file: None, diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index a2808bddb3acc..88eaa52c6deba 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -191,6 +191,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions hash_untracked_state: None, register_lints: Some(Box::new(crate::lint::register_lints)), override_queries: None, + extra_symbols: Vec::new(), make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), ice_file: None, diff --git a/src/tools/clippy/Cargo.toml b/src/tools/clippy/Cargo.toml index c4588002dc990..6c0b3af573dbe 100644 --- a/src/tools/clippy/Cargo.toml +++ b/src/tools/clippy/Cargo.toml @@ -25,6 +25,7 @@ path = "src/driver.rs" [dependencies] clippy_config = { path = "clippy_config" } clippy_lints = { path = "clippy_lints" } +clippy_utils = { path = "clippy_utils" } rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } tempfile = { version = "3.3", optional = true } termize = "0.1" diff --git a/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs b/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs index cd38aed26a3e0..7fab97d3ea146 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/deprecated_cfg_attr.rs @@ -1,10 +1,10 @@ use super::{Attribute, DEPRECATED_CFG_ATTR, DEPRECATED_CLIPPY_CFG_ATTR, unnecessary_clippy_cfg}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, MsrvStack}; +use clippy_utils::sym; use rustc_ast::AttrStyle; use rustc_errors::Applicability; use rustc_lint::EarlyContext; -use rustc_span::sym; pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) { // check cfg_attr @@ -18,7 +18,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &MsrvStack) { && msrv.meets(msrvs::TOOL_ATTRIBUTES) // check for `rustfmt_skip` and `rustfmt::skip` && let Some(skip_item) = &items[1].meta_item() - && (skip_item.has_name(sym!(rustfmt_skip)) + && (skip_item.has_name(sym::rustfmt_skip) || skip_item .path .segments diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs index e3e081ce08e9f..1cb43ab02a305 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs @@ -2,10 +2,10 @@ use super::USELESS_ATTRIBUTE; use super::utils::{is_lint_level, is_word, namespace_and_lint}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, first_line_of_span}; +use clippy_utils::sym; use rustc_ast::{Attribute, Item, ItemKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, LintContext}; -use rustc_span::sym; pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use)); @@ -61,7 +61,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { if is_word(lint, sym::unused_imports) && skip_unused_imports { return; } - if is_word(lint, sym!(unused_extern_crates)) { + if is_word(lint, sym::unused_extern_crates) { return; } }, diff --git a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs index 3008082c2329d..bf98b1337bc0e 100644 --- a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs +++ b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs @@ -38,7 +38,7 @@ pub fn check( // of all `#[test]` attributes in not ignored code examples fn check_code_sample(code: String, edition: Edition, ignore: bool) -> (bool, Vec>) { rustc_driver::catch_fatal_errors(|| { - rustc_span::create_session_globals_then(edition, None, || { + rustc_span::create_session_globals_then(edition, &[], None, || { let mut test_attr_spans = vec![]; let filename = FileName::anon_source_code(&code); diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index eb4e1a7722f3c..12f7d6416e3e2 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -3,6 +3,7 @@ #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] +#![feature(macro_metavar_expr)] #![feature(macro_metavar_expr_concat)] #![feature(let_chains)] #![feature(never_type)] @@ -74,6 +75,7 @@ pub mod qualify_min_const_fn; pub mod source; pub mod str_utils; pub mod sugg; +pub mod sym; pub mod ty; pub mod usage; pub mod visitors; @@ -125,7 +127,7 @@ use rustc_middle::ty::{ use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; -use rustc_span::{InnerSpan, Span, sym}; +use rustc_span::{InnerSpan, Span}; use visitors::{Visitable, for_each_unconsumed_temporary}; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; diff --git a/src/tools/clippy/clippy_utils/src/sym.rs b/src/tools/clippy/clippy_utils/src/sym.rs new file mode 100644 index 0000000000000..1b098ea073a51 --- /dev/null +++ b/src/tools/clippy/clippy_utils/src/sym.rs @@ -0,0 +1,23 @@ +#![allow(non_upper_case_globals)] + +use rustc_span::symbol::{Symbol, PREDEFINED_SYMBOLS_COUNT}; + +pub use rustc_span::sym::*; + +macro_rules! generate { + ($($sym:ident,)*) => { + /// To be supplied to [`rustc_interface::Config`] + pub const EXTRA_SYMBOLS: &[&str] = &[ + $(stringify!($sym),)* + ]; + + $( + pub const $sym: Symbol = Symbol::new(PREDEFINED_SYMBOLS_COUNT + ${index()}); + )* + }; +} + +generate! { + rustfmt_skip, + unused_extern_crates, +} diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs index e4092bcd10564..df9c4e8e6ae9a 100644 --- a/src/tools/clippy/src/driver.rs +++ b/src/tools/clippy/src/driver.rs @@ -160,6 +160,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { clippy_lints::register_lints(lint_store, conf); clippy_lints::register_pre_expansion_lints(lint_store, conf); })); + config.extra_symbols = clippy_utils::sym::EXTRA_SYMBOLS.into(); // FIXME: #4825; This is required, because Clippy lints that are based on MIR have to be // run on the unoptimized MIR. On the other hand this results in some false negatives. If diff --git a/tests/ui-fulldeps/run-compiler-twice.rs b/tests/ui-fulldeps/run-compiler-twice.rs index ffc19b138a573..fa651baa7bc8f 100644 --- a/tests/ui-fulldeps/run-compiler-twice.rs +++ b/tests/ui-fulldeps/run-compiler-twice.rs @@ -70,6 +70,7 @@ fn compile(code: String, output: PathBuf, sysroot: PathBuf, linker: Option<&Path hash_untracked_state: None, register_lints: None, override_queries: None, + extra_symbols: Vec::new(), make_codegen_backend: None, registry: rustc_driver::diagnostics_registry(), using_internal_features: &rustc_driver::USING_INTERNAL_FEATURES, From f9770e742ad3853d2a472e422b00cdbe8578faae Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 24 Mar 2025 21:55:14 +0100 Subject: [PATCH 04/26] Test linking and running no_std binaries --- tests/ui/no_std/simple-runs.rs | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/ui/no_std/simple-runs.rs diff --git a/tests/ui/no_std/simple-runs.rs b/tests/ui/no_std/simple-runs.rs new file mode 100644 index 0000000000000..8931ac7ed11be --- /dev/null +++ b/tests/ui/no_std/simple-runs.rs @@ -0,0 +1,41 @@ +//! Check that `no_std` binaries can link and run without depending on `libstd`. + +//@ run-pass +//@ compile-flags: -Cpanic=abort +//@ ignore-wasm different `main` convention + +#![no_std] +#![no_main] + +use core::ffi::{c_char, c_int}; +use core::panic::PanicInfo; + +// # Linux +// +// Linking `libc` is required by crt1.o, otherwise the linker fails with: +// > /usr/bin/ld: in function `_start': undefined reference to `__libc_start_main' +// +// # Apple +// +// Linking `libSystem` is required, otherwise the linker fails with: +// > ld: dynamic executables or dylibs must link with libSystem.dylib +// +// With the new linker introduced in Xcode 15, the error is instead: +// > Undefined symbols: "dyld_stub_binder", referenced from: +// +// This _can_ be worked around by raising the deployment target with +// MACOSX_DEPLOYMENT_TARGET=13.0, though it's a bit hard to test that while +// still allowing the test suite to support running with older Xcode versions. +#[cfg_attr(all(not(target_vendor = "apple"), unix), link(name = "c"))] +#[cfg_attr(target_vendor = "apple", link(name = "System"))] +extern "C" {} + +#[panic_handler] +fn panic_handler(_info: &PanicInfo<'_>) -> ! { + loop {} +} + +#[no_mangle] +extern "C" fn main(_argc: c_int, _argv: *const *const c_char) -> c_int { + 0 +} From b8c4c163f0e2e3438eb474c6b0ae5fa545061aa9 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 5 Apr 2025 20:37:56 +0000 Subject: [PATCH 05/26] Suppress missing field error when autoderef bottoms out in infer --- compiler/rustc_hir_typeck/src/expr.rs | 9 +++++++-- tests/ui/typeck/issue-65611.rs | 1 - tests/ui/typeck/issue-65611.stderr | 11 ++--------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 45ab8e03db578..64465b609aded 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2915,8 +2915,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } // We failed to check the expression, report an error. - // Emits an error if we deref an infer variable, like calling `.field` on a base type of &_. - self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false)); + // Emits an error if we deref an infer variable, like calling `.field` on a base type + // of `&_`. We can also use this to suppress unnecessary "missing field" errors that + // will follow ambiguity errors. + let final_ty = self.structurally_resolve_type(autoderef.span(), autoderef.final_ty(false)); + if let ty::Error(_) = final_ty.kind() { + return final_ty; + } if let Some((adjustments, did)) = private_candidate { // (#90483) apply adjustments to avoid ExprUseVisitor from diff --git a/tests/ui/typeck/issue-65611.rs b/tests/ui/typeck/issue-65611.rs index 7645311496d8c..0dae75927a86d 100644 --- a/tests/ui/typeck/issue-65611.rs +++ b/tests/ui/typeck/issue-65611.rs @@ -58,6 +58,5 @@ fn main() { let mut buffer = ArrayVec::new(); let x = buffer.last().unwrap().0.clone(); //~^ ERROR type annotations needed - //~| ERROR no field `0` on type `&_` buffer.reverse(); } diff --git a/tests/ui/typeck/issue-65611.stderr b/tests/ui/typeck/issue-65611.stderr index 2278450a6d8eb..52f0f0cffff9b 100644 --- a/tests/ui/typeck/issue-65611.stderr +++ b/tests/ui/typeck/issue-65611.stderr @@ -4,13 +4,6 @@ error[E0282]: type annotations needed LL | let x = buffer.last().unwrap().0.clone(); | ^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for type parameter `T` -error[E0609]: no field `0` on type `&_` - --> $DIR/issue-65611.rs:59:36 - | -LL | let x = buffer.last().unwrap().0.clone(); - | ^ unknown field - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -Some errors have detailed explanations: E0282, E0609. -For more information about an error, try `rustc --explain E0282`. +For more information about this error, try `rustc --explain E0282`. From 34e97592f4dc8f4e44a309e3bf69cdcea22fc047 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 8 Apr 2025 12:23:01 +1000 Subject: [PATCH 06/26] compiletest: Trim whitespace from environment variable names --- src/tools/compiletest/src/header.rs | 21 ++++++++--------- src/tools/compiletest/src/runtest.rs | 8 +++---- .../ui/compiletest-self-test/trim-env-name.rs | 23 +++++++++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 tests/ui/compiletest-self-test/trim-env-name.rs diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index a0178f4bcc576..e3adbb66dd9e3 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -441,7 +441,7 @@ impl TestProps { ln, UNSET_EXEC_ENV, &mut self.unset_exec_env, - |r| r, + |r| r.trim().to_owned(), ); config.push_name_value_directive( ln, @@ -453,7 +453,7 @@ impl TestProps { ln, UNSET_RUSTC_ENV, &mut self.unset_rustc_env, - |r| r, + |r| r.trim().to_owned(), ); config.push_name_value_directive( ln, @@ -979,16 +979,13 @@ impl Config { fn parse_env(nv: String) -> (String, String) { // nv is either FOO or FOO=BAR - let mut strs: Vec = nv.splitn(2, '=').map(str::to_owned).collect(); - - match strs.len() { - 1 => (strs.pop().unwrap(), String::new()), - 2 => { - let end = strs.pop().unwrap(); - (strs.pop().unwrap(), end) - } - n => panic!("Expected 1 or 2 strings, not {}", n), - } + // FIXME(Zalathar): The form without `=` seems to be unused; should + // we drop support for it? + let (name, value) = nv.split_once('=').unwrap_or((&nv, "")); + // Trim whitespace from the name, so that `//@ exec-env: FOO=BAR` + // sees the name as `FOO` and not ` FOO`. + let name = name.trim(); + (name.to_owned(), value.to_owned()) } fn parse_pp_exact(&self, line: &str, testfile: &Path) -> Option { diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index c8a60b68da8b2..70d07b5f13232 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -971,16 +971,16 @@ impl<'test> TestCx<'test> { delete_after_success: bool, ) -> ProcRes { let prepare_env = |cmd: &mut Command| { - for key in &self.props.unset_exec_env { - cmd.env_remove(key); - } - for (key, val) in &self.props.exec_env { cmd.env(key, val); } for (key, val) in env_extra { cmd.env(key, val); } + + for key in &self.props.unset_exec_env { + cmd.env_remove(key); + } }; let proc_res = match &*self.config.target { diff --git a/tests/ui/compiletest-self-test/trim-env-name.rs b/tests/ui/compiletest-self-test/trim-env-name.rs new file mode 100644 index 0000000000000..0cb6efe9f7658 --- /dev/null +++ b/tests/ui/compiletest-self-test/trim-env-name.rs @@ -0,0 +1,23 @@ +//@ edition: 2024 +//@ revisions: set unset +//@ run-pass +//@ ignore-cross-compile (assume that non-cross targets have working env vars) +//@ rustc-env: MY_RUSTC_ENV = my-rustc-value +//@ exec-env: MY_EXEC_ENV = my-exec-value +//@[unset] unset-rustc-env: MY_RUSTC_ENV +//@[unset] unset-exec-env: MY_EXEC_ENV + +// Check that compiletest trims whitespace from environment variable names +// specified in `rustc-env` and `exec-env` directives, so that +// `//@ exec-env: FOO=bar` sees the name as `FOO` and not ` FOO`. +// +// Values are currently not trimmed. +// +// Since this is a compiletest self-test, only run it on non-cross targets, +// to avoid having to worry about weird targets that don't support env vars. + +fn main() { + let is_set = cfg!(set); + assert_eq!(option_env!("MY_RUSTC_ENV"), is_set.then_some(" my-rustc-value")); + assert_eq!(std::env::var("MY_EXEC_ENV").ok().as_deref(), is_set.then_some(" my-exec-value")); +} From a0d9c8726678fdc6a43d51e388e1a92e388d5fc2 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 14:34:47 +0200 Subject: [PATCH 07/26] Inline `calc_default_binding_mode` --- compiler/rustc_hir_typeck/src/pat.rs | 158 +++++++++++---------------- 1 file changed, 66 insertions(+), 92 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 8641348bffbbe..e6e262279cd9f 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -163,9 +163,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { enum AdjustMode { /// Peel off all immediate reference types. Peel, - /// Reset binding mode to the initial mode. - /// Used for destructuring assignment, where we don't want any match ergonomics. - Reset, /// Pass on the input binding mode and expected type. Pass, } @@ -322,6 +319,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { #[instrument(level = "debug", skip(self, pat_info))] fn check_pat(&self, pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, pat_info: PatInfo<'tcx>) { let PatInfo { binding_mode, max_ref_mutbl, top_info: ti, current_depth, .. } = pat_info; + #[cfg(debug_assertions)] + if binding_mode == ByRef::Yes(Mutability::Mut) + && max_ref_mutbl != MutblCap::Mut + && self.downgrade_mut_inside_shared() + { + span_bug!(pat.span, "Pattern mutability cap violated!"); + } let path_res = match pat.kind { PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, span }) => { @@ -330,8 +334,65 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }; let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res)); - let (expected, binding_mode, max_ref_mutbl) = - self.calc_default_binding_mode(pat, expected, binding_mode, adjust_mode, max_ref_mutbl); + let (expected, binding_mode, max_ref_mutbl) = match adjust_mode { + // When we perform destructuring assignment, we disable default match bindings, which + // are unintuitive in this context. + _ if !pat.default_binding_modes => (expected, ByRef::No, MutblCap::Mut), + AdjustMode::Pass => (expected, binding_mode, max_ref_mutbl), + // Peel off as many immediately nested `& mut?` from the expected type as possible + // and return the new expected type and binding default binding mode. + // The adjustments vector, if non-empty is stored in a table. + AdjustMode::Peel => { + let mut binding_mode = binding_mode; + let mut max_ref_mutbl = max_ref_mutbl; + let mut expected = self.try_structurally_resolve_type(pat.span, expected); + // Peel off as many `&` or `&mut` from the scrutinee type as possible. For example, + // for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches + // the `Some(5)` which is not of type Ref. + // + // For each ampersand peeled off, update the binding mode and push the original + // type into the adjustments vector. + // + // See the examples in `ui/match-defbm*.rs`. + let mut pat_adjustments = vec![]; + while let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { + debug!("inspecting {:?}", expected); + + debug!("current discriminant is Ref, inserting implicit deref"); + // Preserve the reference type. We'll need it later during THIR lowering. + pat_adjustments.push(expected); + + expected = self.try_structurally_resolve_type(pat.span, inner_ty); + binding_mode = ByRef::Yes(match binding_mode { + // If default binding mode is by value, make it `ref` or `ref mut` + // (depending on whether we observe `&` or `&mut`). + ByRef::No | + // When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`). + ByRef::Yes(Mutability::Mut) => inner_mutability, + // Once a `ref`, always a `ref`. + // This is because a `& &mut` cannot mutate the underlying value. + ByRef::Yes(Mutability::Not) => Mutability::Not, + }); + } + + if self.downgrade_mut_inside_shared() { + binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); + } + if binding_mode == ByRef::Yes(Mutability::Not) { + max_ref_mutbl = MutblCap::Not; + } + + if !pat_adjustments.is_empty() { + debug!("default binding mode is now {:?}", binding_mode); + self.typeck_results + .borrow_mut() + .pat_adjustments_mut() + .insert(pat.hir_id, pat_adjustments); + } + + (expected, binding_mode, max_ref_mutbl) + } + }; let pat_info = PatInfo { binding_mode, max_ref_mutbl, @@ -437,39 +498,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `regions-relate-bound-regions-on-closures-to-inference-variables.rs`, } - /// Compute the new expected type and default binding mode from the old ones - /// as well as the pattern form we are currently checking. - fn calc_default_binding_mode( - &self, - pat: &'tcx Pat<'tcx>, - expected: Ty<'tcx>, - def_br: ByRef, - adjust_mode: AdjustMode, - max_ref_mutbl: MutblCap, - ) -> (Ty<'tcx>, ByRef, MutblCap) { - #[cfg(debug_assertions)] - if def_br == ByRef::Yes(Mutability::Mut) - && max_ref_mutbl != MutblCap::Mut - && self.downgrade_mut_inside_shared() - { - span_bug!(pat.span, "Pattern mutability cap violated!"); - } - match adjust_mode { - AdjustMode::Pass => (expected, def_br, max_ref_mutbl), - AdjustMode::Reset => (expected, ByRef::No, MutblCap::Mut), - AdjustMode::Peel => self.peel_off_references(pat, expected, def_br, max_ref_mutbl), - } - } - /// How should the binding mode and expected type be adjusted? /// /// When the pattern is a path pattern, `opt_path_res` must be `Some(res)`. fn calc_adjust_mode(&self, pat: &'tcx Pat<'tcx>, opt_path_res: Option) -> AdjustMode { - // When we perform destructuring assignment, we disable default match bindings, which are - // unintuitive in this context. - if !pat.default_binding_modes { - return AdjustMode::Reset; - } match &pat.kind { // Type checking these product-like types successfully always require // that the expected type be of those types and not reference types. @@ -526,64 +558,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - /// Peel off as many immediately nested `& mut?` from the expected type as possible - /// and return the new expected type and binding default binding mode. - /// The adjustments vector, if non-empty is stored in a table. - fn peel_off_references( - &self, - pat: &'tcx Pat<'tcx>, - expected: Ty<'tcx>, - mut def_br: ByRef, - mut max_ref_mutbl: MutblCap, - ) -> (Ty<'tcx>, ByRef, MutblCap) { - let mut expected = self.try_structurally_resolve_type(pat.span, expected); - // Peel off as many `&` or `&mut` from the scrutinee type as possible. For example, - // for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches - // the `Some(5)` which is not of type Ref. - // - // For each ampersand peeled off, update the binding mode and push the original - // type into the adjustments vector. - // - // See the examples in `ui/match-defbm*.rs`. - let mut pat_adjustments = vec![]; - while let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { - debug!("inspecting {:?}", expected); - - debug!("current discriminant is Ref, inserting implicit deref"); - // Preserve the reference type. We'll need it later during THIR lowering. - pat_adjustments.push(expected); - - expected = self.try_structurally_resolve_type(pat.span, inner_ty); - def_br = ByRef::Yes(match def_br { - // If default binding mode is by value, make it `ref` or `ref mut` - // (depending on whether we observe `&` or `&mut`). - ByRef::No | - // When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`). - ByRef::Yes(Mutability::Mut) => inner_mutability, - // Once a `ref`, always a `ref`. - // This is because a `& &mut` cannot mutate the underlying value. - ByRef::Yes(Mutability::Not) => Mutability::Not, - }); - } - - if self.downgrade_mut_inside_shared() { - def_br = def_br.cap_ref_mutability(max_ref_mutbl.as_mutbl()); - } - if def_br == ByRef::Yes(Mutability::Not) { - max_ref_mutbl = MutblCap::Not; - } - - if !pat_adjustments.is_empty() { - debug!("default binding mode is now {:?}", def_br); - self.typeck_results - .borrow_mut() - .pat_adjustments_mut() - .insert(pat.hir_id, pat_adjustments); - } - - (expected, def_br, max_ref_mutbl) - } - fn check_pat_expr_unadjusted(&self, lt: &'tcx hir::PatExpr<'tcx>) -> Ty<'tcx> { let ty = match <.kind { rustc_hir::PatExprKind::Lit { lit, negated } => { From 9f57903e9cdafda3e32ce0e3472ad7fe2a9c1e3a Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 14:38:16 +0200 Subject: [PATCH 08/26] Insert adjustments incrementally --- compiler/rustc_hir_typeck/src/pat.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index e6e262279cd9f..70eb21ca3560f 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -354,13 +354,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // type into the adjustments vector. // // See the examples in `ui/match-defbm*.rs`. - let mut pat_adjustments = vec![]; while let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { debug!("inspecting {:?}", expected); debug!("current discriminant is Ref, inserting implicit deref"); // Preserve the reference type. We'll need it later during THIR lowering. - pat_adjustments.push(expected); + self.typeck_results + .borrow_mut() + .pat_adjustments_mut() + .entry(pat.hir_id) + .or_default() + .push(expected); expected = self.try_structurally_resolve_type(pat.span, inner_ty); binding_mode = ByRef::Yes(match binding_mode { @@ -382,13 +386,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { max_ref_mutbl = MutblCap::Not; } - if !pat_adjustments.is_empty() { - debug!("default binding mode is now {:?}", binding_mode); - self.typeck_results - .borrow_mut() - .pat_adjustments_mut() - .insert(pat.hir_id, pat_adjustments); - } + debug!("default binding mode is now {:?}", binding_mode); (expected, binding_mode, max_ref_mutbl) } From 19950b52c70a5989c4937c5a91e1184439bdad5c Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 14:52:52 +0200 Subject: [PATCH 09/26] Turn the peeling loop into a recursive call --- compiler/rustc_hir_typeck/src/pat.rs | 80 ++++++++++++++++------------ 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 70eb21ca3560f..ca51a28c4be85 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -318,6 +318,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// Conversely, inside this module, `check_pat_top` should never be used. #[instrument(level = "debug", skip(self, pat_info))] fn check_pat(&self, pat: &'tcx Pat<'tcx>, expected: Ty<'tcx>, pat_info: PatInfo<'tcx>) { + let opt_path_res = match pat.kind { + PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, span }) => { + Some(self.resolve_ty_and_res_fully_qualified_call(qpath, *hir_id, *span)) + } + _ => None, + }; + let adjust_mode = self.calc_adjust_mode(pat, opt_path_res.map(|(res, ..)| res)); + self.check_pat_inner(pat, opt_path_res, adjust_mode, expected, pat_info); + } + + // Helper to avoid resolving the same path pattern several times. + fn check_pat_inner( + &self, + pat: &'tcx Pat<'tcx>, + opt_path_res: Option<(Res, Option>, &'tcx [hir::PathSegment<'tcx>])>, + adjust_mode: AdjustMode, + expected: Ty<'tcx>, + pat_info: PatInfo<'tcx>, + ) { let PatInfo { binding_mode, max_ref_mutbl, top_info: ti, current_depth, .. } = pat_info; #[cfg(debug_assertions)] if binding_mode == ByRef::Yes(Mutability::Mut) @@ -327,34 +346,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span_bug!(pat.span, "Pattern mutability cap violated!"); } - let path_res = match pat.kind { - PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, span }) => { - Some(self.resolve_ty_and_res_fully_qualified_call(qpath, *hir_id, *span)) - } - _ => None, - }; - let adjust_mode = self.calc_adjust_mode(pat, path_res.map(|(res, ..)| res)); let (expected, binding_mode, max_ref_mutbl) = match adjust_mode { // When we perform destructuring assignment, we disable default match bindings, which // are unintuitive in this context. _ if !pat.default_binding_modes => (expected, ByRef::No, MutblCap::Mut), AdjustMode::Pass => (expected, binding_mode, max_ref_mutbl), - // Peel off as many immediately nested `& mut?` from the expected type as possible - // and return the new expected type and binding default binding mode. - // The adjustments vector, if non-empty is stored in a table. + // Peel an immediately nested `& mut?` from the expected type if possible and return the + // new expected type and binding default binding mode. AdjustMode::Peel => { - let mut binding_mode = binding_mode; - let mut max_ref_mutbl = max_ref_mutbl; - let mut expected = self.try_structurally_resolve_type(pat.span, expected); - // Peel off as many `&` or `&mut` from the scrutinee type as possible. For example, - // for `match &&&mut Some(5)` the loop runs three times, aborting when it reaches - // the `Some(5)` which is not of type Ref. - // - // For each ampersand peeled off, update the binding mode and push the original - // type into the adjustments vector. + let expected = self.try_structurally_resolve_type(pat.span, expected); + // Peel off a `&` or `&mut` from the scrutinee type. For each ampersand peeled off, + // update the binding mode and push the original type into the adjustments vector. // - // See the examples in `ui/match-defbm*.rs`. - while let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { + // See the examples in `tests/ui/rfcs/rfc-2005-default-binding-mode`. + if let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { debug!("inspecting {:?}", expected); debug!("current discriminant is Ref, inserting implicit deref"); @@ -366,8 +371,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .or_default() .push(expected); - expected = self.try_structurally_resolve_type(pat.span, inner_ty); - binding_mode = ByRef::Yes(match binding_mode { + let mut binding_mode = ByRef::Yes(match binding_mode { // If default binding mode is by value, make it `ref` or `ref mut` // (depending on whether we observe `&` or `&mut`). ByRef::No | @@ -377,18 +381,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // This is because a `& &mut` cannot mutate the underlying value. ByRef::Yes(Mutability::Not) => Mutability::Not, }); - } - if self.downgrade_mut_inside_shared() { - binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); - } - if binding_mode == ByRef::Yes(Mutability::Not) { - max_ref_mutbl = MutblCap::Not; + if self.downgrade_mut_inside_shared() { + binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); + } + let mut max_ref_mutbl = max_ref_mutbl; + if binding_mode == ByRef::Yes(Mutability::Not) { + max_ref_mutbl = MutblCap::Not; + } + debug!("default binding mode is now {:?}", binding_mode); + let pat_info = PatInfo { binding_mode, max_ref_mutbl, ..pat_info }; + return self.check_pat_inner( + pat, + opt_path_res, + adjust_mode, + inner_ty, + pat_info, + ); + } else { + (expected, binding_mode, max_ref_mutbl) } - - debug!("default binding mode is now {:?}", binding_mode); - - (expected, binding_mode, max_ref_mutbl) } }; let pat_info = PatInfo { @@ -409,7 +421,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pat.hir_id, *span, qpath, - path_res.unwrap(), + opt_path_res.unwrap(), expected, &pat_info.top_info, ); From 38adb9931cd934fefe2a2f65ca0071612c9ec583 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 15:08:55 +0200 Subject: [PATCH 10/26] Reorganize `check_pat_inner` --- compiler/rustc_hir_typeck/src/pat.rs | 118 ++++++++++++++------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index ca51a28c4be85..8f01bb1c297c6 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -337,7 +337,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, pat_info: PatInfo<'tcx>, ) { - let PatInfo { binding_mode, max_ref_mutbl, top_info: ti, current_depth, .. } = pat_info; + let PatInfo { mut binding_mode, mut max_ref_mutbl, current_depth, .. } = pat_info; #[cfg(debug_assertions)] if binding_mode == ByRef::Yes(Mutability::Mut) && max_ref_mutbl != MutblCap::Mut @@ -346,72 +346,76 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span_bug!(pat.span, "Pattern mutability cap violated!"); } - let (expected, binding_mode, max_ref_mutbl) = match adjust_mode { + if !pat.default_binding_modes { // When we perform destructuring assignment, we disable default match bindings, which // are unintuitive in this context. - _ if !pat.default_binding_modes => (expected, ByRef::No, MutblCap::Mut), - AdjustMode::Pass => (expected, binding_mode, max_ref_mutbl), - // Peel an immediately nested `& mut?` from the expected type if possible and return the - // new expected type and binding default binding mode. - AdjustMode::Peel => { - let expected = self.try_structurally_resolve_type(pat.span, expected); - // Peel off a `&` or `&mut` from the scrutinee type. For each ampersand peeled off, - // update the binding mode and push the original type into the adjustments vector. - // - // See the examples in `tests/ui/rfcs/rfc-2005-default-binding-mode`. - if let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() { - debug!("inspecting {:?}", expected); - - debug!("current discriminant is Ref, inserting implicit deref"); - // Preserve the reference type. We'll need it later during THIR lowering. - self.typeck_results - .borrow_mut() - .pat_adjustments_mut() - .entry(pat.hir_id) - .or_default() - .push(expected); - - let mut binding_mode = ByRef::Yes(match binding_mode { - // If default binding mode is by value, make it `ref` or `ref mut` - // (depending on whether we observe `&` or `&mut`). - ByRef::No | - // When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`). - ByRef::Yes(Mutability::Mut) => inner_mutability, - // Once a `ref`, always a `ref`. - // This is because a `& &mut` cannot mutate the underlying value. - ByRef::Yes(Mutability::Not) => Mutability::Not, - }); - - if self.downgrade_mut_inside_shared() { - binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); - } - let mut max_ref_mutbl = max_ref_mutbl; - if binding_mode == ByRef::Yes(Mutability::Not) { - max_ref_mutbl = MutblCap::Not; - } - debug!("default binding mode is now {:?}", binding_mode); - let pat_info = PatInfo { binding_mode, max_ref_mutbl, ..pat_info }; - return self.check_pat_inner( - pat, - opt_path_res, - adjust_mode, - inner_ty, - pat_info, - ); - } else { - (expected, binding_mode, max_ref_mutbl) - } - } + binding_mode = ByRef::No; + max_ref_mutbl = MutblCap::Mut; + }; + // Resolve type if needed. + let expected = if let AdjustMode::Peel = adjust_mode + && pat.default_binding_modes + { + self.try_structurally_resolve_type(pat.span, expected) + } else { + expected }; + let old_pat_info = pat_info; let pat_info = PatInfo { binding_mode, max_ref_mutbl, - top_info: ti, - decl_origin: pat_info.decl_origin, current_depth: current_depth + 1, + top_info: old_pat_info.top_info, + decl_origin: old_pat_info.decl_origin, }; let ty = match pat.kind { + // Peel off a `&` or `&mut` from the scrutinee type. See the examples in + // `tests/ui/rfcs/rfc-2005-default-binding-mode`. + _ if let AdjustMode::Peel = adjust_mode + && pat.default_binding_modes + && let ty::Ref(_, inner_ty, inner_mutability) = *expected.kind() => + { + debug!("inspecting {:?}", expected); + + debug!("current discriminant is Ref, inserting implicit deref"); + // Preserve the reference type. We'll need it later during THIR lowering. + self.typeck_results + .borrow_mut() + .pat_adjustments_mut() + .entry(pat.hir_id) + .or_default() + .push(expected); + + binding_mode = ByRef::Yes(match binding_mode { + // If default binding mode is by value, make it `ref` or `ref mut` + // (depending on whether we observe `&` or `&mut`). + ByRef::No | + // When `ref mut`, stay a `ref mut` (on `&mut`) or downgrade to `ref` (on `&`). + ByRef::Yes(Mutability::Mut) => inner_mutability, + // Once a `ref`, always a `ref`. + // This is because a `& &mut` cannot mutate the underlying value. + ByRef::Yes(Mutability::Not) => Mutability::Not, + }); + + if self.downgrade_mut_inside_shared() { + binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); + } + if binding_mode == ByRef::Yes(Mutability::Not) { + max_ref_mutbl = MutblCap::Not; + } + debug!("default binding mode is now {:?}", binding_mode); + + // Use the old pat info to keep `current_depth` to its old value. + let new_pat_info = PatInfo { binding_mode, max_ref_mutbl, ..old_pat_info }; + return self.check_pat_inner( + pat, + opt_path_res, + adjust_mode, + inner_ty, + new_pat_info, + ); + } PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected, // We allow any type here; we ensure that the type is uninhabited during match checking. PatKind::Never => expected, From 6588018fb413efe0476773982355ba772671bd85 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 15:10:47 +0200 Subject: [PATCH 11/26] Return a type from `check_pat_inner` --- compiler/rustc_hir_typeck/src/pat.rs | 104 +++++++++++++-------------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index 8f01bb1c297c6..c635c2c3f2d22 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -325,7 +325,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { _ => None, }; let adjust_mode = self.calc_adjust_mode(pat, opt_path_res.map(|(res, ..)| res)); - self.check_pat_inner(pat, opt_path_res, adjust_mode, expected, pat_info); + let ty = self.check_pat_inner(pat, opt_path_res, adjust_mode, expected, pat_info); + self.write_ty(pat.hir_id, ty); + + // (note_1): In most of the cases where (note_1) is referenced + // (literals and constants being the exception), we relate types + // using strict equality, even though subtyping would be sufficient. + // There are a few reasons for this, some of which are fairly subtle + // and which cost me (nmatsakis) an hour or two debugging to remember, + // so I thought I'd write them down this time. + // + // 1. There is no loss of expressiveness here, though it does + // cause some inconvenience. What we are saying is that the type + // of `x` becomes *exactly* what is expected. This can cause unnecessary + // errors in some cases, such as this one: + // + // ``` + // fn foo<'x>(x: &'x i32) { + // let a = 1; + // let mut z = x; + // z = &a; + // } + // ``` + // + // The reason we might get an error is that `z` might be + // assigned a type like `&'x i32`, and then we would have + // a problem when we try to assign `&a` to `z`, because + // the lifetime of `&a` (i.e., the enclosing block) is + // shorter than `'x`. + // + // HOWEVER, this code works fine. The reason is that the + // expected type here is whatever type the user wrote, not + // the initializer's type. In this case the user wrote + // nothing, so we are going to create a type variable `Z`. + // Then we will assign the type of the initializer (`&'x i32`) + // as a subtype of `Z`: `&'x i32 <: Z`. And hence we + // will instantiate `Z` as a type `&'0 i32` where `'0` is + // a fresh region variable, with the constraint that `'x : '0`. + // So basically we're all set. + // + // Note that there are two tests to check that this remains true + // (`regions-reassign-{match,let}-bound-pointer.rs`). + // + // 2. An outdated issue related to the old HIR borrowck. See the test + // `regions-relate-bound-regions-on-closures-to-inference-variables.rs`, } // Helper to avoid resolving the same path pattern several times. @@ -336,7 +379,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { adjust_mode: AdjustMode, expected: Ty<'tcx>, pat_info: PatInfo<'tcx>, - ) { + ) -> Ty<'tcx> { let PatInfo { mut binding_mode, mut max_ref_mutbl, current_depth, .. } = pat_info; #[cfg(debug_assertions)] if binding_mode == ByRef::Yes(Mutability::Mut) @@ -369,7 +412,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { decl_origin: old_pat_info.decl_origin, }; - let ty = match pat.kind { + match pat.kind { // Peel off a `&` or `&mut` from the scrutinee type. See the examples in // `tests/ui/rfcs/rfc-2005-default-binding-mode`. _ if let AdjustMode::Peel = adjust_mode @@ -408,13 +451,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Use the old pat info to keep `current_depth` to its old value. let new_pat_info = PatInfo { binding_mode, max_ref_mutbl, ..old_pat_info }; - return self.check_pat_inner( - pat, - opt_path_res, - adjust_mode, - inner_ty, - new_pat_info, - ); + // Recurse with the new expected type. + self.check_pat_inner(pat, opt_path_res, adjust_mode, inner_ty, new_pat_info) } PatKind::Missing | PatKind::Wild | PatKind::Err(_) => expected, // We allow any type here; we ensure that the type is uninhabited during match checking. @@ -465,51 +503,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { PatKind::Slice(before, slice, after) => { self.check_pat_slice(pat.span, before, slice, after, expected, pat_info) } - }; - - self.write_ty(pat.hir_id, ty); - - // (note_1): In most of the cases where (note_1) is referenced - // (literals and constants being the exception), we relate types - // using strict equality, even though subtyping would be sufficient. - // There are a few reasons for this, some of which are fairly subtle - // and which cost me (nmatsakis) an hour or two debugging to remember, - // so I thought I'd write them down this time. - // - // 1. There is no loss of expressiveness here, though it does - // cause some inconvenience. What we are saying is that the type - // of `x` becomes *exactly* what is expected. This can cause unnecessary - // errors in some cases, such as this one: - // - // ``` - // fn foo<'x>(x: &'x i32) { - // let a = 1; - // let mut z = x; - // z = &a; - // } - // ``` - // - // The reason we might get an error is that `z` might be - // assigned a type like `&'x i32`, and then we would have - // a problem when we try to assign `&a` to `z`, because - // the lifetime of `&a` (i.e., the enclosing block) is - // shorter than `'x`. - // - // HOWEVER, this code works fine. The reason is that the - // expected type here is whatever type the user wrote, not - // the initializer's type. In this case the user wrote - // nothing, so we are going to create a type variable `Z`. - // Then we will assign the type of the initializer (`&'x i32`) - // as a subtype of `Z`: `&'x i32 <: Z`. And hence we - // will instantiate `Z` as a type `&'0 i32` where `'0` is - // a fresh region variable, with the constraint that `'x : '0`. - // So basically we're all set. - // - // Note that there are two tests to check that this remains true - // (`regions-reassign-{match,let}-bound-pointer.rs`). - // - // 2. An outdated issue related to the old HIR borrowck. See the test - // `regions-relate-bound-regions-on-closures-to-inference-variables.rs`, + } } /// How should the binding mode and expected type be adjusted? From f458151b7aa773a46dfce8c58aa41de9ed697c5e Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 6 Apr 2025 15:48:07 +0200 Subject: [PATCH 12/26] Remove redundant assignment --- compiler/rustc_hir_typeck/src/pat.rs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/pat.rs b/compiler/rustc_hir_typeck/src/pat.rs index c635c2c3f2d22..fbc783c050904 100644 --- a/compiler/rustc_hir_typeck/src/pat.rs +++ b/compiler/rustc_hir_typeck/src/pat.rs @@ -380,21 +380,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Ty<'tcx>, pat_info: PatInfo<'tcx>, ) -> Ty<'tcx> { - let PatInfo { mut binding_mode, mut max_ref_mutbl, current_depth, .. } = pat_info; #[cfg(debug_assertions)] - if binding_mode == ByRef::Yes(Mutability::Mut) - && max_ref_mutbl != MutblCap::Mut + if pat_info.binding_mode == ByRef::Yes(Mutability::Mut) + && pat_info.max_ref_mutbl != MutblCap::Mut && self.downgrade_mut_inside_shared() { span_bug!(pat.span, "Pattern mutability cap violated!"); } - if !pat.default_binding_modes { - // When we perform destructuring assignment, we disable default match bindings, which - // are unintuitive in this context. - binding_mode = ByRef::No; - max_ref_mutbl = MutblCap::Mut; - }; // Resolve type if needed. let expected = if let AdjustMode::Peel = adjust_mode && pat.default_binding_modes @@ -404,13 +397,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected }; let old_pat_info = pat_info; - let pat_info = PatInfo { - binding_mode, - max_ref_mutbl, - current_depth: current_depth + 1, - top_info: old_pat_info.top_info, - decl_origin: old_pat_info.decl_origin, - }; + let pat_info = PatInfo { current_depth: old_pat_info.current_depth + 1, ..old_pat_info }; match pat.kind { // Peel off a `&` or `&mut` from the scrutinee type. See the examples in @@ -430,7 +417,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .or_default() .push(expected); - binding_mode = ByRef::Yes(match binding_mode { + let mut binding_mode = ByRef::Yes(match pat_info.binding_mode { // If default binding mode is by value, make it `ref` or `ref mut` // (depending on whether we observe `&` or `&mut`). ByRef::No | @@ -441,6 +428,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ByRef::Yes(Mutability::Not) => Mutability::Not, }); + let mut max_ref_mutbl = pat_info.max_ref_mutbl; if self.downgrade_mut_inside_shared() { binding_mode = binding_mode.cap_ref_mutability(max_ref_mutbl.as_mutbl()); } From 12311ef8ac93028f55fae69cb2e3caabab93ad49 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 8 Apr 2025 16:21:52 -0700 Subject: [PATCH 13/26] fix title of offset_of_enum feature --- src/doc/unstable-book/src/language-features/offset-of-enum.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doc/unstable-book/src/language-features/offset-of-enum.md b/src/doc/unstable-book/src/language-features/offset-of-enum.md index 1960d6299eb00..78c0d87f639ed 100644 --- a/src/doc/unstable-book/src/language-features/offset-of-enum.md +++ b/src/doc/unstable-book/src/language-features/offset-of-enum.md @@ -1,4 +1,4 @@ -# `offset_of_slice` +# `offset_of_enum` The tracking issue for this feature is: [#120141] From f151ceadfe19a4c12c1381468cc8c516f3be9f39 Mon Sep 17 00:00:00 2001 From: Manuel Drehwald Date: Tue, 8 Apr 2025 21:54:34 -0400 Subject: [PATCH 14/26] emit a better error message for using the macro incorrectly --- compiler/rustc_builtin_macros/src/autodiff.rs | 2 +- tests/ui/autodiff/autodiff_illegal.rs | 4 ++-- tests/ui/autodiff/autodiff_illegal.stderr | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/autodiff.rs b/compiler/rustc_builtin_macros/src/autodiff.rs index 351413dea493c..4161829480d38 100644 --- a/compiler/rustc_builtin_macros/src/autodiff.rs +++ b/compiler/rustc_builtin_macros/src/autodiff.rs @@ -234,7 +234,7 @@ mod llvm_enzyme { let meta_item_vec: ThinVec = match meta_item.kind { ast::MetaItemKind::List(ref vec) => vec.clone(), _ => { - dcx.emit_err(errors::AutoDiffInvalidApplication { span: item.span() }); + dcx.emit_err(errors::AutoDiffMissingConfig { span: item.span() }); return vec![item]; } }; diff --git a/tests/ui/autodiff/autodiff_illegal.rs b/tests/ui/autodiff/autodiff_illegal.rs index 2f2cd8d93532f..a916bd8b857b9 100644 --- a/tests/ui/autodiff/autodiff_illegal.rs +++ b/tests/ui/autodiff/autodiff_illegal.rs @@ -63,7 +63,7 @@ fn dummy() { // Malformed, where args? #[autodiff] pub fn f7(x: f64) { - //~^ ERROR autodiff must be applied to function + //~^ ERROR autodiff requires at least a name and mode unimplemented!() } @@ -77,7 +77,7 @@ pub fn f8(x: f64) { // Invalid attribute syntax #[autodiff = ""] pub fn f9(x: f64) { - //~^ ERROR autodiff must be applied to function + //~^ ERROR autodiff requires at least a name and mode unimplemented!() } diff --git a/tests/ui/autodiff/autodiff_illegal.stderr b/tests/ui/autodiff/autodiff_illegal.stderr index 3752b27e7dd1b..b119f61b8ae66 100644 --- a/tests/ui/autodiff/autodiff_illegal.stderr +++ b/tests/ui/autodiff/autodiff_illegal.stderr @@ -62,7 +62,7 @@ error: autodiff must be applied to function LL | let add_one_v2 = |x: u32| -> u32 { x + 1 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: autodiff must be applied to function +error: autodiff requires at least a name and mode --> $DIR/autodiff_illegal.rs:65:1 | LL | / pub fn f7(x: f64) { @@ -80,7 +80,7 @@ LL | | unimplemented!() LL | | } | |_^ -error: autodiff must be applied to function +error: autodiff requires at least a name and mode --> $DIR/autodiff_illegal.rs:79:1 | LL | / pub fn f9(x: f64) { From f419b18d16be992b5db21e0008aa7be16f92803f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 9 Apr 2025 09:30:51 +1000 Subject: [PATCH 15/26] Return early on an error path in `parse_item_impl`. Currently the code continues, using an empty path, but it doesn't need to. --- compiler/rustc_parse/src/parser/item.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3647bf2c378ee..915a30e5469e6 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -602,21 +602,13 @@ impl<'a> Parser<'a> { let polarity = self.parse_polarity(); // Parse both types and traits as a type, then reinterpret if necessary. - let err_path = |span| ast::Path::from_ident(Ident::new(kw::Empty, span)); let ty_first = if self.token.is_keyword(kw::For) && self.look_ahead(1, |t| t != &token::Lt) { let span = self.prev_token.span.between(self.token.span); - self.dcx().emit_err(errors::MissingTraitInTraitImpl { + return Err(self.dcx().create_err(errors::MissingTraitInTraitImpl { span, for_span: span.to(self.token.span), - }); - - P(Ty { - kind: TyKind::Path(None, err_path(span)), - span, - id: DUMMY_NODE_ID, - tokens: None, - }) + })); } else { self.parse_ty_with_generics_recovery(&generics)? }; @@ -671,7 +663,7 @@ impl<'a> Parser<'a> { span: ty_first.span, }); } - err_path(ty_first.span) + ast::Path::from_ident(Ident::new(kw::Empty, ty_first.span)) } }; let trait_ref = TraitRef { path, ref_id: ty_first.id }; From 7ae5c7f32dfc85c0e57949dee1c54bd1c6586e37 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 9 Apr 2025 14:16:22 +1000 Subject: [PATCH 16/26] Avoid an empty trait name in impl blocks. `resolve_ident_in_lexical_scope` checks for an empty name. Why is this necessary? Because `parse_item_impl` can produce an `impl` block with an empty trait name in some cases. This is pretty gross and very non-obvious. This commit avoids the use of the empty trait name. In one case the trait name is instead pulled from `TyKind::ImplTrait`, which prevents the output for `tests/ui/impl-trait/extra-impl-in-trait-impl.rs` from changing. In the other case we just fail the parse and don't try to recover. I think losing error recovery in this obscure case is worth the code cleanup. This change affects `tests/ui/parser/impl-parsing.rs`, which is split in two, and the obsolete `..` syntax cases are removed (they are tested elsewhere). --- compiler/rustc_parse/src/parser/item.rs | 9 +++--- compiler/rustc_resolve/src/ident.rs | 3 -- tests/ui/parser/impl-parsing-2.rs | 4 +++ tests/ui/parser/impl-parsing-2.stderr | 18 ++++++++++++ tests/ui/parser/impl-parsing.rs | 5 ---- tests/ui/parser/impl-parsing.stderr | 37 ++----------------------- 6 files changed, 29 insertions(+), 47 deletions(-) create mode 100644 tests/ui/parser/impl-parsing-2.rs create mode 100644 tests/ui/parser/impl-parsing-2.stderr diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 915a30e5469e6..0650181634075 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -649,6 +649,7 @@ impl<'a> Parser<'a> { other => { if let TyKind::ImplTrait(_, bounds) = other && let [bound] = bounds.as_slice() + && let GenericBound::Trait(poly_trait_ref) = bound { // Suggest removing extra `impl` keyword: // `impl impl Default for Wrapper` @@ -658,12 +659,12 @@ impl<'a> Parser<'a> { extra_impl_kw, impl_trait_span: ty_first.span, }); + poly_trait_ref.trait_ref.path.clone() } else { - self.dcx().emit_err(errors::ExpectedTraitInTraitImplFoundType { - span: ty_first.span, - }); + return Err(self.dcx().create_err( + errors::ExpectedTraitInTraitImplFoundType { span: ty_first.span }, + )); } - ast::Path::from_ident(Ident::new(kw::Empty, ty_first.span)) } }; let trait_ref = TraitRef { path, ref_id: ty_first.id }; diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 5f0a2a597e9b4..180d6af219d11 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -296,9 +296,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { ) -> Option> { assert!(ns == TypeNS || ns == ValueNS); let orig_ident = ident; - if ident.name == kw::Empty { - return Some(LexicalScopeBinding::Res(Res::Err)); - } let (general_span, normalized_span) = if ident.name == kw::SelfUpper { // FIXME(jseyfried) improve `Self` hygiene let empty_span = ident.span.with_ctxt(SyntaxContext::root()); diff --git a/tests/ui/parser/impl-parsing-2.rs b/tests/ui/parser/impl-parsing-2.rs new file mode 100644 index 0000000000000..7a71217b21c59 --- /dev/null +++ b/tests/ui/parser/impl-parsing-2.rs @@ -0,0 +1,4 @@ +impl ! {} // OK + +default unsafe FAIL //~ ERROR expected item, found keyword `unsafe` +//~^ ERROR `default` is not followed by an item diff --git a/tests/ui/parser/impl-parsing-2.stderr b/tests/ui/parser/impl-parsing-2.stderr new file mode 100644 index 0000000000000..45e2c428242d6 --- /dev/null +++ b/tests/ui/parser/impl-parsing-2.stderr @@ -0,0 +1,18 @@ +error: `default` is not followed by an item + --> $DIR/impl-parsing-2.rs:3:1 + | +LL | default unsafe FAIL + | ^^^^^^^ the `default` qualifier + | + = note: only `fn`, `const`, `type`, or `impl` items may be prefixed by `default` + +error: expected item, found keyword `unsafe` + --> $DIR/impl-parsing-2.rs:3:9 + | +LL | default unsafe FAIL + | ^^^^^^ expected item + | + = note: for a full list of items that can appear in modules, see + +error: aborting due to 2 previous errors + diff --git a/tests/ui/parser/impl-parsing.rs b/tests/ui/parser/impl-parsing.rs index 80ce888557078..7692a81dd42cd 100644 --- a/tests/ui/parser/impl-parsing.rs +++ b/tests/ui/parser/impl-parsing.rs @@ -2,9 +2,4 @@ impl ! {} // OK impl ! where u8: Copy {} // OK impl Trait Type {} //~ ERROR missing `for` in a trait impl -impl Trait .. {} //~ ERROR missing `for` in a trait impl impl ?Sized for Type {} //~ ERROR expected a trait, found type -impl ?Sized for .. {} //~ ERROR expected a trait, found type - -default unsafe FAIL //~ ERROR expected item, found keyword `unsafe` -//~^ ERROR `default` is not followed by an item diff --git a/tests/ui/parser/impl-parsing.stderr b/tests/ui/parser/impl-parsing.stderr index 6a24a9453e631..b2512120dc87c 100644 --- a/tests/ui/parser/impl-parsing.stderr +++ b/tests/ui/parser/impl-parsing.stderr @@ -9,44 +9,11 @@ help: add `for` here LL | impl Trait for Type {} | +++ -error: missing `for` in a trait impl - --> $DIR/impl-parsing.rs:5:11 - | -LL | impl Trait .. {} - | ^ - | -help: add `for` here - | -LL | impl Trait for .. {} - | +++ - error: expected a trait, found type - --> $DIR/impl-parsing.rs:6:6 + --> $DIR/impl-parsing.rs:5:6 | LL | impl ?Sized for Type {} | ^^^^^^ -error: expected a trait, found type - --> $DIR/impl-parsing.rs:7:6 - | -LL | impl ?Sized for .. {} - | ^^^^^^ - -error: `default` is not followed by an item - --> $DIR/impl-parsing.rs:9:1 - | -LL | default unsafe FAIL - | ^^^^^^^ the `default` qualifier - | - = note: only `fn`, `const`, `type`, or `impl` items may be prefixed by `default` - -error: expected item, found keyword `unsafe` - --> $DIR/impl-parsing.rs:9:9 - | -LL | default unsafe FAIL - | ^^^^^^ expected item - | - = note: for a full list of items that can appear in modules, see - -error: aborting due to 6 previous errors +error: aborting due to 2 previous errors From b2aa9d0620bbfa09f67c7bbfae2dd553d39bc90a Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 8 Apr 2025 10:52:16 +0000 Subject: [PATCH 17/26] Remove some dead or leftover code related to rustc-intrinsic abi removal --- .../src/error_codes/E0622.md | 4 ++- compiler/rustc_error_codes/src/lib.rs | 2 +- .../rustc_hir_analysis/src/check/check.rs | 11 ------- .../rustc_hir_analysis/src/check/intrinsic.rs | 33 +++++-------------- tests/ui/error-codes/E0622.rs | 14 -------- tests/ui/error-codes/E0622.stderr | 9 ----- 6 files changed, 13 insertions(+), 60 deletions(-) delete mode 100644 tests/ui/error-codes/E0622.rs delete mode 100644 tests/ui/error-codes/E0622.stderr diff --git a/compiler/rustc_error_codes/src/error_codes/E0622.md b/compiler/rustc_error_codes/src/error_codes/E0622.md index e6ff949d3e9f9..9b8131a061e39 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0622.md +++ b/compiler/rustc_error_codes/src/error_codes/E0622.md @@ -1,8 +1,10 @@ +#### Note: this error code is no longer emitted by the compiler. + An intrinsic was declared without being a function. Erroneous code example: -```compile_fail,E0622 +```no_run #![feature(intrinsics)] #![allow(internal_features)] diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs index dfeef5a957d69..2488d870899ce 100644 --- a/compiler/rustc_error_codes/src/lib.rs +++ b/compiler/rustc_error_codes/src/lib.rs @@ -397,7 +397,7 @@ E0618: 0618, E0619: 0619, E0620: 0620, E0621: 0621, -E0622: 0622, +E0622: 0622, // REMOVED: rustc-intrinsic ABI was removed E0623: 0623, E0624: 0624, E0625: 0625, diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index e3ed20e1b318d..2e0e57ba88791 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -719,7 +719,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) { def_id, tcx.def_ident_span(def_id).unwrap(), i.name, - ExternAbi::Rust, ) } } @@ -787,16 +786,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) { for item in items { let def_id = item.id.owner_id.def_id; - if tcx.has_attr(def_id, sym::rustc_intrinsic) { - intrinsic::check_intrinsic_type( - tcx, - item.id.owner_id.def_id, - item.span, - item.ident.name, - abi, - ); - } - let generics = tcx.generics_of(def_id); let own_counts = generics.own_counts(); if generics.own_params.len() - own_counts.lifetimes != 0 { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 0bf9e127989f0..d62ca7e198773 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -1,9 +1,8 @@ //! Type-checking for the `#[rustc_intrinsic]` intrinsics that the compiler exposes. use rustc_abi::ExternAbi; -use rustc_errors::codes::*; -use rustc_errors::{DiagMessage, struct_span_code_err}; -use rustc_hir::{self as hir, Safety}; +use rustc_errors::DiagMessage; +use rustc_hir::{self as hir}; use rustc_middle::bug; use rustc_middle::traits::{ObligationCause, ObligationCauseCode}; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -26,17 +25,10 @@ fn equate_intrinsic_type<'tcx>( sig: ty::PolyFnSig<'tcx>, ) { let (generics, span) = match tcx.hir_node_by_def_id(def_id) { - hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn { generics, .. }, .. }) - | hir::Node::ForeignItem(hir::ForeignItem { - kind: hir::ForeignItemKind::Fn(_, _, generics), - .. - }) => (tcx.generics_of(def_id), generics.span), - _ => { - struct_span_code_err!(tcx.dcx(), span, E0622, "intrinsic must be a function") - .with_span_label(span, "expected a function") - .emit(); - return; + hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn { generics, .. }, .. }) => { + (tcx.generics_of(def_id), generics.span) } + _ => tcx.dcx().span_bug(span, "intrinsic must be a function"), }; let own_counts = generics.own_counts(); @@ -70,13 +62,7 @@ fn equate_intrinsic_type<'tcx>( } /// Returns the unsafety of the given intrinsic. -pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hir::Safety { - let has_safe_attr = if tcx.has_attr(intrinsic_id, sym::rustc_intrinsic) { - tcx.fn_sig(intrinsic_id).skip_binder().safety() - } else { - // Old-style intrinsics are never safe - Safety::Unsafe - }; +fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hir::Safety { let is_in_list = match tcx.item_name(intrinsic_id.into()) { // When adding a new intrinsic to this list, // it's usually worth updating that intrinsic's documentation @@ -148,7 +134,7 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - _ => hir::Safety::Unsafe, }; - if has_safe_attr != is_in_list { + if tcx.fn_sig(intrinsic_id).skip_binder().safety() != is_in_list { tcx.dcx().struct_span_err( tcx.def_span(intrinsic_id), DiagMessage::from(format!( @@ -163,12 +149,11 @@ pub fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) - /// Remember to add all intrinsics here, in `compiler/rustc_codegen_llvm/src/intrinsic.rs`, /// and in `library/core/src/intrinsics.rs`. -pub fn check_intrinsic_type( +pub(crate) fn check_intrinsic_type( tcx: TyCtxt<'_>, intrinsic_id: LocalDefId, span: Span, intrinsic_name: Symbol, - abi: ExternAbi, ) { let generics = tcx.generics_of(intrinsic_id); let param = |n| { @@ -706,7 +691,7 @@ pub fn check_intrinsic_type( }; (n_tps, 0, n_cts, inputs, output, safety) }; - let sig = tcx.mk_fn_sig(inputs, output, false, safety, abi); + let sig = tcx.mk_fn_sig(inputs, output, false, safety, ExternAbi::Rust); let sig = ty::Binder::bind_with_vars(sig, bound_vars); equate_intrinsic_type(tcx, span, intrinsic_id, n_tps, n_lts, n_cts, sig) } diff --git a/tests/ui/error-codes/E0622.rs b/tests/ui/error-codes/E0622.rs deleted file mode 100644 index 0c2a4f226d80c..0000000000000 --- a/tests/ui/error-codes/E0622.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![feature(intrinsics)] - -extern "C" { - - #[rustc_intrinsic] - pub static atomic_singlethreadfence_seqcst: unsafe extern "C" fn(); - //~^ ERROR intrinsic must be a function [E0622] -} - -fn main() { - unsafe { - atomic_singlethreadfence_seqcst(); - } -} diff --git a/tests/ui/error-codes/E0622.stderr b/tests/ui/error-codes/E0622.stderr deleted file mode 100644 index c0aea542af04e..0000000000000 --- a/tests/ui/error-codes/E0622.stderr +++ /dev/null @@ -1,9 +0,0 @@ -error[E0622]: intrinsic must be a function - --> $DIR/E0622.rs:6:5 - | -LL | pub static atomic_singlethreadfence_seqcst: unsafe extern "C" fn(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected a function - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0622`. From cf685653131bf76a88457eb8a88ee91c0d9fa821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 9 Apr 2025 13:48:39 +0200 Subject: [PATCH 18/26] Temporarily leave the review rotation --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index 756536dc2e7b9..64c85bbb38110 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1123,6 +1123,7 @@ cc = ["@ZuseZ4"] warn_non_default_branch.enable = true contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html" users_on_vacation = [ + "fmease", "jyn514", "saethlin", "Noratrieb", From 32c8f7dbcf17e8a97a3a99e112e86a604d12f0a2 Mon Sep 17 00:00:00 2001 From: xizheyin Date: Wed, 9 Apr 2025 20:51:52 +0800 Subject: [PATCH 19/26] librustdoc: remove IndexItem::new, use previous fields constructor Signed-off-by: xizheyin --- src/librustdoc/formats/cache.rs | 14 ++++---- src/librustdoc/html/render/mod.rs | 56 +++++++------------------------ 2 files changed, 19 insertions(+), 51 deletions(-) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 2a612fa9b8cda..2648641e53e75 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -574,20 +574,20 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It ); let aliases = item.attrs.get_doc_aliases(); let deprecation = item.deprecation(tcx); - let index_item = IndexItem::new( - item.type_(), - Some(defid), + let index_item = IndexItem { + ty: item.type_(), + defid: Some(defid), name, path, desc, - parent_did, - None, - None, + parent: parent_did, + parent_idx: None, + exact_path: None, impl_id, search_type, aliases, deprecation, - ); + }; cache.search_index.push(index_item); } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index b9a2e65b09076..ad6fa110ae883 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -113,50 +113,18 @@ enum RenderMode { /// by hand to a large JS file at the end of cache-creation. #[derive(Debug)] pub(crate) struct IndexItem { - ty: ItemType, - defid: Option, - name: Symbol, - path: String, - desc: String, - parent: Option, - parent_idx: Option, - exact_path: Option, - impl_id: Option, - search_type: Option, - aliases: Box<[Symbol]>, - deprecation: Option, -} - -impl IndexItem { - pub fn new( - ty: ItemType, - defid: Option, - name: Symbol, - path: String, - desc: String, - parent: Option, - parent_idx: Option, - exact_path: Option, - impl_id: Option, - search_type: Option, - aliases: Box<[Symbol]>, - deprecation: Option, - ) -> Self { - Self { - ty, - defid, - name, - path, - desc, - parent, - parent_idx, - exact_path, - impl_id, - search_type, - aliases, - deprecation, - } - } + pub(crate) ty: ItemType, + pub(crate) defid: Option, + pub(crate) name: Symbol, + pub(crate) path: String, + pub(crate) desc: String, + pub(crate) parent: Option, + pub(crate) parent_idx: Option, + pub(crate) exact_path: Option, + pub(crate) impl_id: Option, + pub(crate) search_type: Option, + pub(crate) aliases: Box<[Symbol]>, + pub(crate) deprecation: Option, } /// A type used for the search index. From 67ff33666f3f59578c2ca7e337fd55129439edb2 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 9 Apr 2025 12:03:28 -0400 Subject: [PATCH 20/26] saethlin is back from vacation --- triagebot.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/triagebot.toml b/triagebot.toml index 756536dc2e7b9..8573e62a567e2 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1124,7 +1124,6 @@ warn_non_default_branch.enable = true contributing_url = "https://rustc-dev-guide.rust-lang.org/getting-started.html" users_on_vacation = [ "jyn514", - "saethlin", "Noratrieb", ] From 50d0ce1b42d67ce98b2ccac55d22a2ff8abe3273 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Fri, 21 Feb 2025 21:00:43 -0800 Subject: [PATCH 21/26] Ensure `swap_nonoverlapping` is really always untyped --- library/core/src/ptr/mod.rs | 136 +++++++++++------- library/coretests/tests/ptr.rs | 36 +++++ tests/assembly/x86_64-typed-swap.rs | 28 ++++ tests/codegen/simd/swap-simd-types.rs | 8 +- tests/codegen/swap-large-types.rs | 76 ++++++---- tests/codegen/swap-small-types.rs | 78 +++++++--- .../consts/missing_span_in_backtrace.stderr | 6 +- 7 files changed, 272 insertions(+), 96 deletions(-) diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index ea53da78d3bd2..2357ba23aa0d2 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -398,6 +398,7 @@ use crate::cmp::Ordering; use crate::intrinsics::const_eval_select; use crate::marker::FnPtr; use crate::mem::{self, MaybeUninit, SizedTypeProperties}; +use crate::num::NonZero; use crate::{fmt, hash, intrinsics, ub_checks}; mod alignment; @@ -1094,51 +1095,25 @@ pub const unsafe fn swap_nonoverlapping(x: *mut T, y: *mut T, count: usize) { // are pointers inside `T` we will copy them in one go rather than trying to copy a part // of a pointer (which would not work). // SAFETY: Same preconditions as this function - unsafe { swap_nonoverlapping_simple_untyped(x, y, count) } + unsafe { swap_nonoverlapping_const(x, y, count) } } else { - macro_rules! attempt_swap_as_chunks { - ($ChunkTy:ty) => { - if align_of::() >= align_of::<$ChunkTy>() - && size_of::() % size_of::<$ChunkTy>() == 0 - { - let x: *mut $ChunkTy = x.cast(); - let y: *mut $ChunkTy = y.cast(); - let count = count * (size_of::() / size_of::<$ChunkTy>()); - // SAFETY: these are the same bytes that the caller promised were - // ok, just typed as `MaybeUninit`s instead of as `T`s. - // The `if` condition above ensures that we're not violating - // alignment requirements, and that the division is exact so - // that we don't lose any bytes off the end. - return unsafe { swap_nonoverlapping_simple_untyped(x, y, count) }; - } - }; + // Going though a slice here helps codegen know the size fits in `isize` + let slice = slice_from_raw_parts_mut(x, count); + // SAFETY: This is all readable from the pointer, meaning it's one + // allocated object, and thus cannot be more than isize::MAX bytes. + let bytes = unsafe { mem::size_of_val_raw::<[T]>(slice) }; + if let Some(bytes) = NonZero::new(bytes) { + // SAFETY: These are the same ranges, just expressed in a different + // type, so they're still non-overlapping. + unsafe { swap_nonoverlapping_bytes(x.cast(), y.cast(), bytes) }; } - - // Split up the slice into small power-of-two-sized chunks that LLVM is able - // to vectorize (unless it's a special type with more-than-pointer alignment, - // because we don't want to pessimize things like slices of SIMD vectors.) - if align_of::() <= size_of::() - && (!size_of::().is_power_of_two() - || size_of::() > size_of::() * 2) - { - attempt_swap_as_chunks!(usize); - attempt_swap_as_chunks!(u8); - } - - // SAFETY: Same preconditions as this function - unsafe { swap_nonoverlapping_simple_untyped(x, y, count) } } ) } /// Same behavior and safety conditions as [`swap_nonoverlapping`] -/// -/// LLVM can vectorize this (at least it can for the power-of-two-sized types -/// `swap_nonoverlapping` tries to use) so no need to manually SIMD it. #[inline] -const unsafe fn swap_nonoverlapping_simple_untyped(x: *mut T, y: *mut T, count: usize) { - let x = x.cast::>(); - let y = y.cast::>(); +const unsafe fn swap_nonoverlapping_const(x: *mut T, y: *mut T, count: usize) { let mut i = 0; while i < count { // SAFETY: By precondition, `i` is in-bounds because it's below `n` @@ -1147,26 +1122,91 @@ const unsafe fn swap_nonoverlapping_simple_untyped(x: *mut T, y: *mut T, coun // and it's distinct from `x` since the ranges are non-overlapping let y = unsafe { y.add(i) }; - // If we end up here, it's because we're using a simple type -- like - // a small power-of-two-sized thing -- or a special type with particularly - // large alignment, particularly SIMD types. - // Thus, we're fine just reading-and-writing it, as either it's small - // and that works well anyway or it's special and the type's author - // presumably wanted things to be done in the larger chunk. - // SAFETY: we're only ever given pointers that are valid to read/write, // including being aligned, and nothing here panics so it's drop-safe. unsafe { - let a: MaybeUninit = read(x); - let b: MaybeUninit = read(y); - write(x, b); - write(y, a); + // Note that it's critical that these use `copy_nonoverlapping`, + // rather than `read`/`write`, to avoid #134713 if T has padding. + let mut temp = MaybeUninit::::uninit(); + copy_nonoverlapping(x, temp.as_mut_ptr(), 1); + copy_nonoverlapping(y, x, 1); + copy_nonoverlapping(temp.as_ptr(), y, 1); } i += 1; } } +// Don't let MIR inline this, because we really want it to keep its noalias metadata +#[rustc_no_mir_inline] +#[inline] +fn swap_chunk(x: &mut MaybeUninit<[u8; N]>, y: &mut MaybeUninit<[u8; N]>) { + let a = *x; + let b = *y; + *x = b; + *y = a; +} + +#[inline] +unsafe fn swap_nonoverlapping_bytes(x: *mut u8, y: *mut u8, bytes: NonZero) { + // Same as `swap_nonoverlapping::<[u8; N]>`. + unsafe fn swap_nonoverlapping_chunks( + x: *mut MaybeUninit<[u8; N]>, + y: *mut MaybeUninit<[u8; N]>, + chunks: NonZero, + ) { + let chunks = chunks.get(); + for i in 0..chunks { + // SAFETY: i is in [0, chunks) so the adds and dereferences are in-bounds. + unsafe { swap_chunk(&mut *x.add(i), &mut *y.add(i)) }; + } + } + + // Same as `swap_nonoverlapping_bytes`, but accepts at most 1+2+4=7 bytes + #[inline] + unsafe fn swap_nonoverlapping_short(x: *mut u8, y: *mut u8, bytes: NonZero) { + // Tail handling for auto-vectorized code sometimes has element-at-a-time behaviour, + // see . + // By swapping as different sizes, rather than as a loop over bytes, + // we make sure not to end up with, say, seven byte-at-a-time copies. + + let bytes = bytes.get(); + let mut i = 0; + macro_rules! swap_prefix { + ($($n:literal)+) => {$( + if (bytes & $n) != 0 { + // SAFETY: `i` can only have the same bits set as those in bytes, + // so these `add`s are in-bounds of `bytes`. But the bit for + // `$n` hasn't been set yet, so the `$n` bytes that `swap_chunk` + // will read and write are within the usable range. + unsafe { swap_chunk::<$n>(&mut*x.add(i).cast(), &mut*y.add(i).cast()) }; + i |= $n; + } + )+}; + } + swap_prefix!(4 2 1); + debug_assert_eq!(i, bytes); + } + + const CHUNK_SIZE: usize = size_of::<*const ()>(); + let bytes = bytes.get(); + + let chunks = bytes / CHUNK_SIZE; + let tail = bytes % CHUNK_SIZE; + if let Some(chunks) = NonZero::new(chunks) { + // SAFETY: this is bytes/CHUNK_SIZE*CHUNK_SIZE bytes, which is <= bytes, + // so it's within the range of our non-overlapping bytes. + unsafe { swap_nonoverlapping_chunks::(x.cast(), y.cast(), chunks) }; + } + if let Some(tail) = NonZero::new(tail) { + const { assert!(CHUNK_SIZE <= 8) }; + let delta = chunks * CHUNK_SIZE; + // SAFETY: the tail length is below CHUNK SIZE because of the remainder, + // and CHUNK_SIZE is at most 8 by the const assert, so tail <= 7 + unsafe { swap_nonoverlapping_short(x.add(delta), y.add(delta), tail) }; + } +} + /// Moves `src` into the pointed `dst`, returning the previous `dst` value. /// /// Neither value is dropped. diff --git a/library/coretests/tests/ptr.rs b/library/coretests/tests/ptr.rs index 6091926084a35..cc5f7946863a6 100644 --- a/library/coretests/tests/ptr.rs +++ b/library/coretests/tests/ptr.rs @@ -984,3 +984,39 @@ fn test_ptr_metadata_in_const() { assert_eq!(SLICE_META, 3); assert_eq!(DYN_META.size_of(), 42); } + +// See +const fn ptr_swap_nonoverlapping_is_untyped_inner() { + #[repr(C)] + struct HasPadding(usize, u8); + + let buf1: [usize; 2] = [1000, 2000]; + let buf2: [usize; 2] = [3000, 4000]; + + // HasPadding and [usize; 2] have the same size and alignment, + // so swap_nonoverlapping should treat them the same + assert!(size_of::() == size_of::<[usize; 2]>()); + assert!(align_of::() == align_of::<[usize; 2]>()); + + let mut b1 = buf1; + let mut b2 = buf2; + // Safety: b1 and b2 are distinct local variables, + // with the same size and alignment as HasPadding. + unsafe { + std::ptr::swap_nonoverlapping( + b1.as_mut_ptr().cast::(), + b2.as_mut_ptr().cast::(), + 1, + ); + } + assert!(b1[0] == buf2[0]); + assert!(b1[1] == buf2[1]); + assert!(b2[0] == buf1[0]); + assert!(b2[1] == buf1[1]); +} + +#[test] +fn test_ptr_swap_nonoverlapping_is_untyped() { + ptr_swap_nonoverlapping_is_untyped_inner(); + const { ptr_swap_nonoverlapping_is_untyped_inner() }; +} diff --git a/tests/assembly/x86_64-typed-swap.rs b/tests/assembly/x86_64-typed-swap.rs index dfd6ee565bccb..a6753011d3620 100644 --- a/tests/assembly/x86_64-typed-swap.rs +++ b/tests/assembly/x86_64-typed-swap.rs @@ -51,3 +51,31 @@ pub fn swap_simd(x: &mut __m128, y: &mut __m128) { // CHECK-NEXT: retq swap(x, y) } + +// CHECK-LABEL: swap_string: +#[no_mangle] +pub fn swap_string(x: &mut String, y: &mut String) { + // CHECK-NOT: mov + // CHECK-COUNT-4: movups + // CHECK-NOT: mov + // CHECK-COUNT-4: movq + // CHECK-NOT: mov + swap(x, y) +} + +// CHECK-LABEL: swap_44_bytes: +#[no_mangle] +pub fn swap_44_bytes(x: &mut [u8; 44], y: &mut [u8; 44]) { + // Ensure we do better than a long run of byte copies, + // see + + // CHECK-NOT: movb + // CHECK-COUNT-8: movups{{.+}}xmm + // CHECK-NOT: movb + // CHECK-COUNT-4: movq + // CHECK-NOT: movb + // CHECK-COUNT-4: movl + // CHECK-NOT: movb + // CHECK: retq + swap(x, y) +} diff --git a/tests/codegen/simd/swap-simd-types.rs b/tests/codegen/simd/swap-simd-types.rs index 69767d0a75580..c063cc683a616 100644 --- a/tests/codegen/simd/swap-simd-types.rs +++ b/tests/codegen/simd/swap-simd-types.rs @@ -23,8 +23,8 @@ pub fn swap_single_m256(x: &mut __m256, y: &mut __m256) { #[no_mangle] pub fn swap_m256_slice(x: &mut [__m256], y: &mut [__m256]) { // CHECK-NOT: alloca - // CHECK: load <8 x float>{{.+}}align 32 - // CHECK: store <8 x float>{{.+}}align 32 + // CHECK-COUNT-2: load <4 x i64>{{.+}}align 32 + // CHECK-COUNT-2: store <4 x i64>{{.+}}align 32 if x.len() == y.len() { x.swap_with_slice(y); } @@ -34,7 +34,7 @@ pub fn swap_m256_slice(x: &mut [__m256], y: &mut [__m256]) { #[no_mangle] pub fn swap_bytes32(x: &mut [u8; 32], y: &mut [u8; 32]) { // CHECK-NOT: alloca - // CHECK: load <32 x i8>{{.+}}align 1 - // CHECK: store <32 x i8>{{.+}}align 1 + // CHECK-COUNT-2: load <4 x i64>{{.+}}align 1 + // CHECK-COUNT-2: store <4 x i64>{{.+}}align 1 swap(x, y) } diff --git a/tests/codegen/swap-large-types.rs b/tests/codegen/swap-large-types.rs index 49a41bb14692f..08c486affd949 100644 --- a/tests/codegen/swap-large-types.rs +++ b/tests/codegen/swap-large-types.rs @@ -12,6 +12,16 @@ type KeccakBuffer = [[u64; 5]; 5]; // to stack for large types, which is completely unnecessary as the lack of // overlap means we can just do whatever fits in registers at a time. +// The tests here (after the first one showing that the problem still exists) +// are less about testing *exactly* what the codegen is, and more about testing +// 1) That things are swapped directly from one argument to the other, +// never going through stack along the way, and +// 2) That we're doing the swapping for big things using large vector types, +// rather then `i64` or `<8 x i8>` (or, even worse, `i8`) at a time. +// +// (There are separate tests for intrinsics::typed_swap_nonoverlapping that +// check that it, as an intrinsic, are emitting exactly what it should.) + // CHECK-LABEL: @swap_basic #[no_mangle] pub fn swap_basic(x: &mut KeccakBuffer, y: &mut KeccakBuffer) { @@ -26,55 +36,55 @@ pub fn swap_basic(x: &mut KeccakBuffer, y: &mut KeccakBuffer) { } } -// This test verifies that the library does something smarter, and thus -// doesn't need any scratch space on the stack. - // CHECK-LABEL: @swap_std #[no_mangle] pub fn swap_std(x: &mut KeccakBuffer, y: &mut KeccakBuffer) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i64> - // CHECK: store <{{[0-9]+}} x i64> + // CHECK: load <{{2|4}} x i64> + // CHECK: store <{{2|4}} x i64> swap(x, y) } -// Verify that types with usize alignment are swapped via vectored usizes, -// not falling back to byte-level code. - // CHECK-LABEL: @swap_slice #[no_mangle] pub fn swap_slice(x: &mut [KeccakBuffer], y: &mut [KeccakBuffer]) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i64> - // CHECK: store <{{[0-9]+}} x i64> + // CHECK: load <{{2|4}} x i64> + // CHECK: store <{{2|4}} x i64> if x.len() == y.len() { x.swap_with_slice(y); } } -// But for a large align-1 type, vectorized byte copying is what we want. - type OneKilobyteBuffer = [u8; 1024]; // CHECK-LABEL: @swap_1kb_slices #[no_mangle] pub fn swap_1kb_slices(x: &mut [OneKilobyteBuffer], y: &mut [OneKilobyteBuffer]) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i8> - // CHECK: store <{{[0-9]+}} x i8> + + // CHECK-NOT: load i32 + // CHECK-NOT: store i32 + // CHECK-NOT: load i16 + // CHECK-NOT: store i16 + // CHECK-NOT: load i8 + // CHECK-NOT: store i8 + + // CHECK: load <{{2|4}} x i64>{{.+}}align 1, + // CHECK: store <{{2|4}} x i64>{{.+}}align 1, + + // CHECK-NOT: load i32 + // CHECK-NOT: store i32 + // CHECK-NOT: load i16 + // CHECK-NOT: store i16 + // CHECK-NOT: load i8 + // CHECK-NOT: store i8 + if x.len() == y.len() { x.swap_with_slice(y); } } -// This verifies that the 2×read + 2×write optimizes to just 3 memcpys -// for an unusual type like this. It's not clear whether we should do anything -// smarter in Rust for these, so for now it's fine to leave these up to the backend. -// That's not as bad as it might seem, as for example, LLVM will lower the -// memcpys below to VMOVAPS on YMMs if one enables the AVX target feature. -// Eventually we'll be able to pass `align_of::` to a const generic and -// thus pick a smarter chunk size ourselves without huge code duplication. - #[repr(align(64))] pub struct BigButHighlyAligned([u8; 64 * 3]); @@ -82,9 +92,25 @@ pub struct BigButHighlyAligned([u8; 64 * 3]); #[no_mangle] pub fn swap_big_aligned(x: &mut BigButHighlyAligned, y: &mut BigButHighlyAligned) { // CHECK-NOT: call void @llvm.memcpy - // CHECK: call void @llvm.memcpy.{{.+}}(ptr noundef nonnull align 64 dereferenceable(192) - // CHECK: call void @llvm.memcpy.{{.+}}(ptr noundef nonnull align 64 dereferenceable(192) - // CHECK: call void @llvm.memcpy.{{.+}}(ptr noundef nonnull align 64 dereferenceable(192) + // CHECK-NOT: load i32 + // CHECK-NOT: store i32 + // CHECK-NOT: load i16 + // CHECK-NOT: store i16 + // CHECK-NOT: load i8 + // CHECK-NOT: store i8 + + // CHECK-COUNT-2: load <{{2|4}} x i64>{{.+}}align 64, + // CHECK-COUNT-2: store <{{2|4}} x i64>{{.+}}align 64, + + // CHECK-COUNT-2: load <{{2|4}} x i64>{{.+}}align 32, + // CHECK-COUNT-2: store <{{2|4}} x i64>{{.+}}align 32, + + // CHECK-NOT: load i32 + // CHECK-NOT: store i32 + // CHECK-NOT: load i16 + // CHECK-NOT: store i16 + // CHECK-NOT: load i8 + // CHECK-NOT: store i8 // CHECK-NOT: call void @llvm.memcpy swap(x, y) } diff --git a/tests/codegen/swap-small-types.rs b/tests/codegen/swap-small-types.rs index 76bb853e64238..ffa573c9a43ab 100644 --- a/tests/codegen/swap-small-types.rs +++ b/tests/codegen/swap-small-types.rs @@ -1,5 +1,6 @@ //@ compile-flags: -Copt-level=3 -Z merge-functions=disabled //@ only-x86_64 +//@ min-llvm-version: 20 #![crate_type = "lib"] @@ -27,13 +28,19 @@ pub fn swap_rgb48_manually(x: &mut RGB48, y: &mut RGB48) { pub fn swap_rgb48(x: &mut RGB48, y: &mut RGB48) { // CHECK-NOT: alloca - // Whether `i8` is the best for this is unclear, but - // might as well record what's actually happening right now. - - // CHECK: load i8 - // CHECK: load i8 - // CHECK: store i8 - // CHECK: store i8 + // Swapping `i48` might be cleaner in LLVM-IR here, but `i32`+`i16` isn't bad, + // and is closer to the assembly it generates anyway. + + // CHECK-NOT: load{{ }} + // CHECK: load i32{{.+}}align 2 + // CHECK-NEXT: load i32{{.+}}align 2 + // CHECK-NEXT: store i32{{.+}}align 2 + // CHECK-NEXT: store i32{{.+}}align 2 + // CHECK: load i16{{.+}}align 2 + // CHECK-NEXT: load i16{{.+}}align 2 + // CHECK-NEXT: store i16{{.+}}align 2 + // CHECK-NEXT: store i16{{.+}}align 2 + // CHECK-NOT: store{{ }} swap(x, y) } @@ -76,30 +83,49 @@ pub fn swap_slices<'a>(x: &mut &'a [u32], y: &mut &'a [u32]) { swap(x, y) } -// LLVM doesn't vectorize a loop over 3-byte elements, -// so we chunk it down to bytes and loop over those instead. type RGB24 = [u8; 3]; // CHECK-LABEL: @swap_rgb24_slices #[no_mangle] pub fn swap_rgb24_slices(x: &mut [RGB24], y: &mut [RGB24]) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i8> - // CHECK: store <{{[0-9]+}} x i8> + + // CHECK: mul nuw nsw i64 %{{x|y}}.1, 3 + + // CHECK: load <{{[0-9]+}} x i64> + // CHECK: store <{{[0-9]+}} x i64> + + // CHECK-COUNT-2: load i32 + // CHECK-COUNT-2: store i32 + // CHECK-COUNT-2: load i16 + // CHECK-COUNT-2: store i16 + // CHECK-COUNT-2: load i8 + // CHECK-COUNT-2: store i8 if x.len() == y.len() { x.swap_with_slice(y); } } -// This one has a power-of-two size, so we iterate over it directly type RGBA32 = [u8; 4]; // CHECK-LABEL: @swap_rgba32_slices #[no_mangle] pub fn swap_rgba32_slices(x: &mut [RGBA32], y: &mut [RGBA32]) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i32> - // CHECK: store <{{[0-9]+}} x i32> + + // Because the size in bytes in a multiple of 4, we can skip the smallest sizes. + + // CHECK: load <{{[0-9]+}} x i64> + // CHECK: store <{{[0-9]+}} x i64> + + // CHECK-COUNT-2: load i32 + // CHECK-COUNT-2: store i32 + + // CHECK-NOT: load i16 + // CHECK-NOT: store i16 + // CHECK-NOT: load i8 + // CHECK-NOT: store i8 + if x.len() == y.len() { x.swap_with_slice(y); } @@ -113,8 +139,8 @@ const _: () = assert!(!std::mem::size_of::().is_power_of_two()); #[no_mangle] pub fn swap_string_slices(x: &mut [String], y: &mut [String]) { // CHECK-NOT: alloca - // CHECK: load <{{[0-9]+}} x i64> - // CHECK: store <{{[0-9]+}} x i64> + // CHECK: load <{{[0-9]+}} x i64>{{.+}}, align 8, + // CHECK: store <{{[0-9]+}} x i64>{{.+}}, align 8, if x.len() == y.len() { x.swap_with_slice(y); } @@ -130,6 +156,26 @@ pub struct Packed { #[no_mangle] pub fn swap_packed_structs(x: &mut Packed, y: &mut Packed) { // CHECK-NOT: alloca + + // CHECK-NOT: load + // CHECK-NOT: store + + // CHECK: %[[A:.+]] = load i64, ptr %x, align 1, + // CHECK-NEXT: %[[B:.+]] = load i64, ptr %y, align 1, + // CHECK-NEXT: store i64 %[[B]], ptr %x, align 1, + // CHECK-NEXT: store i64 %[[A]], ptr %y, align 1, + + // CHECK-NOT: load + // CHECK-NOT: store + + // CHECK: %[[C:.+]] = load i8, ptr %[[X8:.+]], align 1, + // CHECK-NEXT: %[[D:.+]] = load i8, ptr %[[Y8:.+]], align 1, + // CHECK-NEXT: store i8 %[[D]], ptr %[[X8]], align 1, + // CHECK-NEXT: store i8 %[[C]], ptr %[[Y8]], align 1, + + // CHECK-NOT: load + // CHECK-NOT: store + // CHECK: ret void swap(x, y) } diff --git a/tests/ui/consts/missing_span_in_backtrace.stderr b/tests/ui/consts/missing_span_in_backtrace.stderr index 2f3a65302bd57..aad3d76dd2697 100644 --- a/tests/ui/consts/missing_span_in_backtrace.stderr +++ b/tests/ui/consts/missing_span_in_backtrace.stderr @@ -12,10 +12,10 @@ note: inside `swap_nonoverlapping::>` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL note: inside `swap_nonoverlapping::compiletime::>` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::swap_nonoverlapping_simple_untyped::>` - --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL -note: inside `std::ptr::read::>>` +note: inside `std::ptr::swap_nonoverlapping_const::>` --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside `copy_nonoverlapping::>` + --> $SRC_DIR/core/src/intrinsics/mod.rs:LL:COL = help: this code performed an operation that depends on the underlying bytes representing a pointer = help: the absolute address of a pointer is not known at compile-time, so such operations are not supported = note: this error originates in the macro `$crate::intrinsics::const_eval_select` which comes from the expansion of the macro `const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info) From b06a88ff61acb08dd4f7502908485a3eaa74d0a6 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Sat, 22 Feb 2025 13:12:38 -0800 Subject: [PATCH 22/26] Add a Miri test for 134713 --- ...ssue-134713-swap_nonoverlapping_untyped.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/tools/miri/tests/pass/issues/issue-134713-swap_nonoverlapping_untyped.rs diff --git a/src/tools/miri/tests/pass/issues/issue-134713-swap_nonoverlapping_untyped.rs b/src/tools/miri/tests/pass/issues/issue-134713-swap_nonoverlapping_untyped.rs new file mode 100644 index 0000000000000..a1da60ce65c4d --- /dev/null +++ b/src/tools/miri/tests/pass/issues/issue-134713-swap_nonoverlapping_untyped.rs @@ -0,0 +1,30 @@ +use std::mem::{size_of, align_of}; + +// See + +#[repr(C)] +struct Foo(usize, u8); + +fn main() { + let buf1: [usize; 2] = [1000, 2000]; + let buf2: [usize; 2] = [3000, 4000]; + + // Foo and [usize; 2] have the same size and alignment, + // so swap_nonoverlapping should treat them the same + assert_eq!(size_of::(), size_of::<[usize; 2]>()); + assert_eq!(align_of::(), align_of::<[usize; 2]>()); + + let mut b1 = buf1; + let mut b2 = buf2; + // Safety: b1 and b2 are distinct local variables, + // with the same size and alignment as Foo. + unsafe { + std::ptr::swap_nonoverlapping( + b1.as_mut_ptr().cast::(), + b2.as_mut_ptr().cast::(), + 1, + ); + } + assert_eq!(b1, buf2); + assert_eq!(b2, buf1); +} From 8b227a42fa2833581cd70cdc186ccc34e251a74e Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Wed, 9 Apr 2025 12:51:31 -0400 Subject: [PATCH 23/26] rustdoc: Enable Markdown extensions when looking for doctests We should enable these to avoid misinterpreting uses of the extended syntax as code blocks. This happens in practice with multi-paragraph footnotes, as discovered in #139064. --- src/librustdoc/html/markdown.rs | 2 +- tests/rustdoc-ui/multi-par-footnote.rs | 18 ++++++++++++++++++ tests/rustdoc-ui/multi-par-footnote.stdout | 5 +++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/rustdoc-ui/multi-par-footnote.rs create mode 100644 tests/rustdoc-ui/multi-par-footnote.stdout diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 334a7a86989a8..44134bda5ea39 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -721,7 +721,7 @@ pub(crate) fn find_codes( extra_info: Option<&ExtraInfo<'_>>, include_non_rust: bool, ) { - let mut parser = Parser::new(doc).into_offset_iter(); + let mut parser = Parser::new_ext(doc, main_body_opts()).into_offset_iter(); let mut prev_offset = 0; let mut nb_lines = 0; let mut register_header = None; diff --git a/tests/rustdoc-ui/multi-par-footnote.rs b/tests/rustdoc-ui/multi-par-footnote.rs new file mode 100644 index 0000000000000..bb6a85db0dbac --- /dev/null +++ b/tests/rustdoc-ui/multi-par-footnote.rs @@ -0,0 +1,18 @@ +//@ check-pass +//@ compile-flags:--test +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +// Regression test for #139064. + +/// Example +/// +/// Footnote with multiple paragraphs[^multiple] +/// +/// [^multiple]: +/// One +/// +/// Two +/// +/// Three +pub fn add(left: u64, right: u64) -> u64 { + left + right +} diff --git a/tests/rustdoc-ui/multi-par-footnote.stdout b/tests/rustdoc-ui/multi-par-footnote.stdout new file mode 100644 index 0000000000000..7326c0a25a069 --- /dev/null +++ b/tests/rustdoc-ui/multi-par-footnote.stdout @@ -0,0 +1,5 @@ + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + From 63dcac8423750369ee251e2bad0945ad765eaf5c Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 9 Apr 2025 10:44:49 -0700 Subject: [PATCH 24/26] skip `tests/codegen/swap-small-types` when debug assertions are on In `swap_nonoverlapping_short` there's a new `debug_assert!`, and if that's enabled then the `alloca`s don't optimize out. --- tests/codegen/swap-small-types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/codegen/swap-small-types.rs b/tests/codegen/swap-small-types.rs index ffa573c9a43ab..7aa613ae9c227 100644 --- a/tests/codegen/swap-small-types.rs +++ b/tests/codegen/swap-small-types.rs @@ -1,6 +1,7 @@ //@ compile-flags: -Copt-level=3 -Z merge-functions=disabled //@ only-x86_64 //@ min-llvm-version: 20 +//@ ignore-std-debug-assertions (`ptr::swap_nonoverlapping` has one which blocks some optimizations) #![crate_type = "lib"] From 7103aea39ab4a73f074a035eb4416e223bf4cde3 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 9 Apr 2025 12:59:37 -0700 Subject: [PATCH 25/26] Tracking issue template: fine-grained information on style update status Inspired by some of the communication issues around the stabilization of `let`-chains, give more fine-grained information about the status of updating style for any new syntax. This does not change the process or blockers in any way; it only *documents* the current state in the tracking issue. For instance, in the case of `let`-chains, we would have checked the boxes for "Style team decision" and "(non-blocking) Formatting has been implemented", and not checked the box for the style guide. That would have then provided better supporting information for any decisions. --- .github/ISSUE_TEMPLATE/tracking_issue.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/tracking_issue.md b/.github/ISSUE_TEMPLATE/tracking_issue.md index 3a9d8408b3c9d..aedc15a54c274 100644 --- a/.github/ISSUE_TEMPLATE/tracking_issue.md +++ b/.github/ISSUE_TEMPLATE/tracking_issue.md @@ -41,7 +41,10 @@ for larger features an implementation could be broken up into multiple PRs. - [ ] Implement the RFC (cc @rust-lang/XXX -- can anyone write up mentoring instructions?) - [ ] Adjust documentation ([see instructions on rustc-dev-guide][doc-guide]) -- [ ] Formatting for new syntax has been added to the [Style Guide] ([nightly-style-procedure]) +- [ ] Style updates for any new syntax ([nightly-style-procedure]) + - [ ] Style team decision on new formatting + - [ ] Formatting for new syntax has been added to the [Style Guide] + - [ ] (non-blocking) Formatting has been implemented in `rustfmt` - [ ] Stabilization PR ([see instructions on rustc-dev-guide][stabilization-guide]) [stabilization-guide]: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html#stabilization-pr From d21ee7ef32731f8aaa280075c993a75d9e72af9e Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Wed, 9 Apr 2025 20:32:31 +0000 Subject: [PATCH 26/26] Update `compiler-builtins` to 0.1.153 Includes the following changes: * Avoid OOB access in `memcpy` and `memmove` [1] * Enable intrinsics on AVR [2] * `libm` updates to avoid using `core::arch` vector intrinsics [3] [1]: https://github.com/rust-lang/compiler-builtins/pull/799 [2]: https://github.com/rust-lang/compiler-builtins/pull/791 [3]: https://github.com/rust-lang/compiler-builtins/pull/814 --- library/Cargo.lock | 4 ++-- library/alloc/Cargo.toml | 2 +- library/std/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/Cargo.lock b/library/Cargo.lock index ad634e9f794a4..d035ca6c91f53 100644 --- a/library/Cargo.lock +++ b/library/Cargo.lock @@ -67,9 +67,9 @@ dependencies = [ [[package]] name = "compiler_builtins" -version = "0.1.152" +version = "0.1.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2153cf213eb259361567720ce55f6446f17acd0ccca87fb6dc05360578228a58" +checksum = "926ef6a360c15a911023352fd6969c51605d70495406f735beb1ca0257448e59" dependencies = [ "cc", "rustc-std-workspace-core", diff --git a/library/alloc/Cargo.toml b/library/alloc/Cargo.toml index 1d2dd1e60819f..ee8cb9d25a393 100644 --- a/library/alloc/Cargo.toml +++ b/library/alloc/Cargo.toml @@ -16,7 +16,7 @@ bench = false [dependencies] core = { path = "../core", public = true } -compiler_builtins = { version = "=0.1.152", features = ['rustc-dep-of-std'] } +compiler_builtins = { version = "=0.1.153", features = ['rustc-dep-of-std'] } [features] compiler-builtins-mem = ['compiler_builtins/mem'] diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index 176da603d58d7..6b70ff764d7a4 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -18,7 +18,7 @@ cfg-if = { version = "1.0", features = ['rustc-dep-of-std'] } panic_unwind = { path = "../panic_unwind", optional = true } panic_abort = { path = "../panic_abort" } core = { path = "../core", public = true } -compiler_builtins = { version = "=0.1.152" } +compiler_builtins = { version = "=0.1.153" } unwind = { path = "../unwind" } hashbrown = { version = "0.15", default-features = false, features = [ 'rustc-dep-of-std',