From c6441d8f007ab550d4500f71902bcf24b6033204 Mon Sep 17 00:00:00 2001 From: Jakob Koschel Date: Fri, 23 Jan 2026 11:43:02 +0000 Subject: [PATCH 1/9] Fix sanitizer target builds on CI --- src/ci/docker/host-x86_64/dist-various-2/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile index 7c165e38f8f9d..323cd409a9785 100644 --- a/src/ci/docker/host-x86_64/dist-various-2/Dockerfile +++ b/src/ci/docker/host-x86_64/dist-various-2/Dockerfile @@ -113,7 +113,6 @@ ENV TARGETS=$TARGETS,wasm32-wasip1 ENV TARGETS=$TARGETS,wasm32-wasip1-threads ENV TARGETS=$TARGETS,wasm32-wasip2 ENV TARGETS=$TARGETS,wasm32v1-none -ENV TARGETS=$TARGETS,x86_64-unknown-linux-gnuasan ENV TARGETS=$TARGETS,x86_64-unknown-linux-gnux32 ENV TARGETS=$TARGETS,x86_64-fortanix-unknown-sgx ENV TARGETS=$TARGETS,nvptx64-nvidia-cuda @@ -126,6 +125,8 @@ ENV TARGETS=$TARGETS,i686-unknown-uefi ENV TARGETS=$TARGETS,x86_64-unknown-uefi ENV TARGETS=$TARGETS,riscv64gc-unknown-linux-musl +ENV TARGETS_SANITIZERS=x86_64-unknown-linux-gnuasan + # As per https://bugs.launchpad.net/ubuntu/+source/gcc-defaults/+bug/1300211 # we need asm in the search path for gcc-9 (for gnux32) but not in the search path of the # cross compilers. @@ -139,4 +140,4 @@ ENV RUST_CONFIGURE_ARGS --enable-extended --enable-lld --enable-llvm-bitcode-lin --musl-root-armv7=/musl-armv7 \ --musl-root-riscv64gc=/musl-riscv64gc -ENV SCRIPT python3 ../x.py dist --host='' --target $TARGETS +ENV SCRIPT python3 ../x.py dist --host='' --target $TARGETS && python3 ../x.py dist --host='' --set build.sanitizers=true --target $TARGETS_SANITIZERS From 4b8fc13da047092b2684f09cd5d879721bf2c180 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 21 Jan 2026 13:16:33 +1100 Subject: [PATCH 2/9] Remove `Deref` from `QueryCtxt` Explicitly writing `self.tcx` is easy enough, and lets us remove a bit of non-essential deref magic. --- compiler/rustc_query_impl/src/plumbing.rs | 39 +++++++++++------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 7479a992e2973..de11cd82eb1d9 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -35,6 +35,8 @@ use rustc_span::def_id::LOCAL_CRATE; use crate::QueryConfigRestored; +/// Implements [`QueryContext`] for use by [`rustc_query_system`], since that +/// crate does not have direct access to [`TyCtxt`]. #[derive(Copy, Clone)] pub struct QueryCtxt<'tcx> { pub tcx: TyCtxt<'tcx>, @@ -47,15 +49,6 @@ impl<'tcx> QueryCtxt<'tcx> { } } -impl<'tcx> std::ops::Deref for QueryCtxt<'tcx> { - type Target = TyCtxt<'tcx>; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.tcx - } -} - impl<'tcx> HasDepContext for QueryCtxt<'tcx> { type Deps = rustc_middle::dep_graph::DepsType; type DepContext = TyCtxt<'tcx>; @@ -69,14 +62,16 @@ impl<'tcx> HasDepContext for QueryCtxt<'tcx> { impl QueryContext for QueryCtxt<'_> { #[inline] fn jobserver_proxy(&self) -> &Proxy { - &*self.jobserver_proxy + &self.tcx.jobserver_proxy } #[inline] fn next_job_id(self) -> QueryJobId { QueryJobId( - NonZero::new(self.query_system.jobs.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) - .unwrap(), + NonZero::new( + self.tcx.query_system.jobs.fetch_add(1, std::sync::atomic::Ordering::Relaxed), + ) + .unwrap(), ) } @@ -113,7 +108,8 @@ impl QueryContext for QueryCtxt<'_> { self, prev_dep_node_index: SerializedDepNodeIndex, ) -> Option { - self.query_system + self.tcx + .query_system .on_disk_cache .as_ref() .and_then(|c| c.load_side_effect(self.tcx, prev_dep_node_index)) @@ -122,7 +118,7 @@ impl QueryContext for QueryCtxt<'_> { #[inline(never)] #[cold] fn store_side_effect(self, dep_node_index: DepNodeIndex, side_effect: QuerySideEffect) { - if let Some(c) = self.query_system.on_disk_cache.as_ref() { + if let Some(c) = self.tcx.query_system.on_disk_cache.as_ref() { c.store_side_effect(dep_node_index, side_effect) } } @@ -140,7 +136,9 @@ impl QueryContext for QueryCtxt<'_> { // as `self`, so we use `with_related_context` to relate the 'tcx lifetimes // when accessing the `ImplicitCtxt`. tls::with_related_context(self.tcx, move |current_icx| { - if depth_limit && !self.recursion_limit().value_within_limit(current_icx.query_depth) { + if depth_limit + && !self.tcx.recursion_limit().value_within_limit(current_icx.query_depth) + { self.depth_limit_error(token); } @@ -161,16 +159,16 @@ impl QueryContext for QueryCtxt<'_> { let query_map = self.collect_active_jobs(true).expect("failed to collect active queries"); let (info, depth) = job.find_dep_kind_root(query_map); - let suggested_limit = match self.recursion_limit() { + let suggested_limit = match self.tcx.recursion_limit() { Limit(0) => Limit(2), limit => limit * 2, }; - self.sess.dcx().emit_fatal(QueryOverflow { + self.tcx.sess.dcx().emit_fatal(QueryOverflow { span: info.job.span, note: QueryOverflowNote { desc: info.query.description, depth }, suggested_limit, - crate_name: self.crate_name(LOCAL_CRATE), + crate_name: self.tcx.crate_name(LOCAL_CRATE), }); } } @@ -367,7 +365,7 @@ pub(crate) fn encode_query_results<'a, 'tcx, Q>( Q: super::QueryConfigRestored<'tcx>, Q::RestoredValue: Encodable>, { - let _timer = qcx.profiler().generic_activity_with_arg("encode_query_results_for", query.name()); + let _timer = qcx.tcx.prof.generic_activity_with_arg("encode_query_results_for", query.name()); assert!(query.query_state(qcx).all_inactive()); let cache = query.query_cache(qcx); @@ -389,8 +387,7 @@ pub(crate) fn query_key_hash_verify<'tcx>( query: impl QueryConfig>, qcx: QueryCtxt<'tcx>, ) { - let _timer = - qcx.profiler().generic_activity_with_arg("query_key_hash_verify_for", query.name()); + let _timer = qcx.tcx.prof.generic_activity_with_arg("query_key_hash_verify_for", query.name()); let mut map = UnordMap::default(); From 97b05786e8e99ea81eeac106021cf34edc8fd696 Mon Sep 17 00:00:00 2001 From: LorrensP-2158466 Date: Fri, 31 Oct 2025 15:19:32 +0100 Subject: [PATCH 3/9] use CmResolver instead of &mut CmResolver --- compiler/rustc_resolve/src/ident.rs | 6 +++--- compiler/rustc_resolve/src/imports.rs | 2 +- compiler/rustc_resolve/src/lib.rs | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 79d08828ccc4a..78c6cf39b630d 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -58,7 +58,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { orig_ctxt: Span, derive_fallback_lint_id: Option, mut visitor: impl FnMut( - &mut CmResolver<'r, 'ra, 'tcx>, + CmResolver<'_, 'ra, 'tcx>, Scope<'ra>, UsePrelude, Span, @@ -165,7 +165,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if visit { let use_prelude = if use_prelude { UsePrelude::Yes } else { UsePrelude::No }; if let ControlFlow::Break(break_result) = - visitor(&mut self, scope, use_prelude, ctxt) + visitor(self.reborrow(), scope, use_prelude, ctxt) { return Some(break_result); } @@ -438,7 +438,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope, orig_ident.span, derive_fallback_lint_id, - |this, scope, use_prelude, ctxt| { + |mut this, scope, use_prelude, ctxt| { let ident = Ident::new(orig_ident.name, ctxt); // The passed `ctxt` is already normalized, so avoid expensive double normalization. let ident = Macros20NormalizedIdent(ident); diff --git a/compiler/rustc_resolve/src/imports.rs b/compiler/rustc_resolve/src/imports.rs index 016fc407daabe..f5933afd01343 100644 --- a/compiler/rustc_resolve/src/imports.rs +++ b/compiler/rustc_resolve/src/imports.rs @@ -893,7 +893,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; let mut indeterminate_count = 0; - self.per_ns_cm(|this, ns| { + self.per_ns_cm(|mut this, ns| { if !type_ns_only || ns == TypeNS { if bindings[ns].get() != PendingDecl::Pending { return; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 52b016b6bdca2..c60b7aa503138 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1831,13 +1831,13 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { f(self, MacroNS); } - fn per_ns_cm<'r, F: FnMut(&mut CmResolver<'r, 'ra, 'tcx>, Namespace)>( + fn per_ns_cm<'r, F: FnMut(CmResolver<'_, 'ra, 'tcx>, Namespace)>( mut self: CmResolver<'r, 'ra, 'tcx>, mut f: F, ) { - f(&mut self, TypeNS); - f(&mut self, ValueNS); - f(&mut self, MacroNS); + f(self.reborrow(), TypeNS); + f(self.reborrow(), ValueNS); + f(self, MacroNS); } fn is_builtin_macro(&self, res: Res) -> bool { @@ -1902,7 +1902,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } let scope_set = ScopeSet::All(TypeNS); - self.cm().visit_scopes(scope_set, parent_scope, ctxt, None, |this, scope, _, _| { + self.cm().visit_scopes(scope_set, parent_scope, ctxt, None, |mut this, scope, _, _| { match scope { Scope::ModuleNonGlobs(module, _) => { this.get_mut().traits_in_module(module, assoc_item, &mut found_traits); From 54fc546f203ac11b4a7b41171b4359844d0cd706 Mon Sep 17 00:00:00 2001 From: Usman Akinyemi Date: Sun, 18 Jan 2026 13:35:19 +0530 Subject: [PATCH 4/9] Recover from struct literals with placeholder or empty path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on earlier work by León Orell Valerian Liehr. Co-authored-by: León Orell Valerian Liehr Signed-off-by: Usman Akinyemi --- compiler/rustc_parse/messages.ftl | 10 ++++ compiler/rustc_parse/src/errors.rs | 19 ++++++++ compiler/rustc_parse/src/parser/expr.rs | 45 +++++++++++++++++ tests/ui/parser/bare-struct-body.stderr | 10 ++-- .../struct-lit-placeholder-or-empty-path.rs | 14 ++++++ ...truct-lit-placeholder-or-empty-path.stderr | 48 +++++++++++++++++++ .../struct-lit-placeholder-path.rs | 21 ++++++++ .../struct-lit-placeholder-path.stderr | 26 ++++++++++ 8 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 tests/ui/parser/struct-lit-placeholder-or-empty-path.rs create mode 100644 tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr create mode 100644 tests/ui/suggestions/struct-lit-placeholder-path.rs create mode 100644 tests/ui/suggestions/struct-lit-placeholder-path.stderr diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 1331d99c01ead..449d0b964fd48 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -822,9 +822,19 @@ parse_struct_literal_body_without_path = struct literal body without path .suggestion = you might have forgotten to add the struct literal inside the block +parse_struct_literal_body_without_path_late = + struct literal body without path + .label = struct name missing for struct literal + .suggestion = add the correct type + parse_struct_literal_not_allowed_here = struct literals are not allowed here .suggestion = surround the struct literal with parentheses +parse_struct_literal_placeholder_path = + placeholder `_` is not allowed for the path in struct literals + .label = not allowed in struct literals + .suggestion = replace it with the correct type + parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes .help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.) diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 60e4a240c85e9..42327c7e343d1 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -3684,3 +3684,22 @@ pub(crate) struct ImplReuseInherentImpl { #[primary_span] pub span: Span, } + +#[derive(Diagnostic)] +#[diag(parse_struct_literal_placeholder_path)] +pub(crate) struct StructLiteralPlaceholderPath { + #[primary_span] + #[label] + #[suggestion(applicability = "has-placeholders", code = "/* Type */", style = "verbose")] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag(parse_struct_literal_body_without_path_late)] +pub(crate) struct StructLiteralWithoutPathLate { + #[primary_span] + #[label] + pub span: Span, + #[suggestion(applicability = "has-placeholders", code = "/* Type */ ", style = "verbose")] + pub suggestion_span: Span, +} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 8835fba1adcdc..c31a4798b471e 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1468,6 +1468,9 @@ impl<'a> Parser<'a> { } else if this.check(exp!(OpenParen)) { this.parse_expr_tuple_parens(restrictions) } else if this.check(exp!(OpenBrace)) { + if let Some(expr) = this.maybe_recover_bad_struct_literal_path(false)? { + return Ok(expr); + } this.parse_expr_block(None, lo, BlockCheckMode::Default) } else if this.check(exp!(Or)) || this.check(exp!(OrOr)) { this.parse_expr_closure().map_err(|mut err| { @@ -1542,6 +1545,9 @@ impl<'a> Parser<'a> { } else if this.check_keyword(exp!(Let)) { this.parse_expr_let(restrictions) } else if this.eat_keyword(exp!(Underscore)) { + if let Some(expr) = this.maybe_recover_bad_struct_literal_path(true)? { + return Ok(expr); + } Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore)) } else if this.token_uninterpolated_span().at_least_rust_2018() { // `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly. @@ -3698,6 +3704,45 @@ impl<'a> Parser<'a> { } } + fn maybe_recover_bad_struct_literal_path( + &mut self, + is_underscore_entry_point: bool, + ) -> PResult<'a, Option>> { + if self.may_recover() + && self.check_noexpect(&token::OpenBrace) + && (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL) + && self.is_likely_struct_lit()) + { + let span = if is_underscore_entry_point { + self.prev_token.span + } else { + self.token.span.shrink_to_lo() + }; + + self.bump(); // { + let expr = self.parse_expr_struct( + None, + Path::from_ident(Ident::new(kw::Underscore, span)), + false, + )?; + + let guar = if is_underscore_entry_point { + self.dcx().create_err(errors::StructLiteralPlaceholderPath { span }).emit() + } else { + self.dcx() + .create_err(errors::StructLiteralWithoutPathLate { + span: expr.span, + suggestion_span: expr.span.shrink_to_lo(), + }) + .emit() + }; + + Ok(Some(self.mk_expr_err(expr.span, guar))) + } else { + Ok(None) + } + } + pub(super) fn parse_struct_fields( &mut self, pth: ast::Path, diff --git a/tests/ui/parser/bare-struct-body.stderr b/tests/ui/parser/bare-struct-body.stderr index 7d17ea59647ec..1b817e2b9ee20 100644 --- a/tests/ui/parser/bare-struct-body.stderr +++ b/tests/ui/parser/bare-struct-body.stderr @@ -21,14 +21,12 @@ LL | let x = { | _____________^ LL | | val: (), LL | | }; - | |_____^ + | |_____^ struct name missing for struct literal | -help: you might have forgotten to add the struct literal inside the block - | -LL ~ let x = { SomeStruct { -LL | val: (), -LL ~ } }; +help: add the correct type | +LL | let x = /* Type */ { + | ++++++++++ error[E0308]: mismatched types --> $DIR/bare-struct-body.rs:11:14 diff --git a/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs b/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs new file mode 100644 index 0000000000000..8f91eb68b2eb3 --- /dev/null +++ b/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs @@ -0,0 +1,14 @@ +fn main() { + let _ = {foo: (), bar: {} }; //~ ERROR struct literal body without path + //~| NOTE struct name missing for struct literal + //~| HELP add the correct type + let _ = _ {foo: (), bar: {} }; //~ ERROR placeholder `_` is not allowed for the path in struct literals + //~| NOTE not allowed in struct literals + //~| HELP replace it with the correct type + let _ = {foo: ()}; //~ ERROR struct literal body without path + //~| NOTE struct name missing for struct literal + //~| HELP add the correct type + let _ = _ {foo: ()}; //~ ERROR placeholder `_` is not allowed for the path in struct literals + //~| NOTE not allowed in struct literals + //~| HELP replace it with the correct type +} diff --git a/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr b/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr new file mode 100644 index 0000000000000..62a417aefc1ec --- /dev/null +++ b/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr @@ -0,0 +1,48 @@ +error: struct literal body without path + --> $DIR/struct-lit-placeholder-or-empty-path.rs:2:13 + | +LL | let _ = {foo: (), bar: {} }; + | ^^^^^^^^^^^^^^^^^^^ struct name missing for struct literal + | +help: add the correct type + | +LL | let _ = /* Type */ {foo: (), bar: {} }; + | ++++++++++ + +error: placeholder `_` is not allowed for the path in struct literals + --> $DIR/struct-lit-placeholder-or-empty-path.rs:5:13 + | +LL | let _ = _ {foo: (), bar: {} }; + | ^ not allowed in struct literals + | +help: replace it with the correct type + | +LL - let _ = _ {foo: (), bar: {} }; +LL + let _ = /* Type */ {foo: (), bar: {} }; + | + +error: struct literal body without path + --> $DIR/struct-lit-placeholder-or-empty-path.rs:8:13 + | +LL | let _ = {foo: ()}; + | ^^^^^^^^^ struct name missing for struct literal + | +help: add the correct type + | +LL | let _ = /* Type */ {foo: ()}; + | ++++++++++ + +error: placeholder `_` is not allowed for the path in struct literals + --> $DIR/struct-lit-placeholder-or-empty-path.rs:11:13 + | +LL | let _ = _ {foo: ()}; + | ^ not allowed in struct literals + | +help: replace it with the correct type + | +LL - let _ = _ {foo: ()}; +LL + let _ = /* Type */ {foo: ()}; + | + +error: aborting due to 4 previous errors + diff --git a/tests/ui/suggestions/struct-lit-placeholder-path.rs b/tests/ui/suggestions/struct-lit-placeholder-path.rs new file mode 100644 index 0000000000000..190440dd15697 --- /dev/null +++ b/tests/ui/suggestions/struct-lit-placeholder-path.rs @@ -0,0 +1,21 @@ +// Regression test for issue #98282. + +mod blah { + pub struct Stuff { x: i32 } + pub fn do_stuff(_: Stuff) {} +} + +fn main() { + blah::do_stuff(_ { x: 10 }); + //~^ ERROR placeholder `_` is not allowed for the path in struct literals + //~| NOTE not allowed in struct literals + //~| HELP replace it with the correct type +} + +#[cfg(FALSE)] +fn disabled() { + blah::do_stuff(_ { x: 10 }); + //~^ ERROR placeholder `_` is not allowed for the path in struct literals + //~| NOTE not allowed in struct literals + //~| HELP replace it with the correct type +} diff --git a/tests/ui/suggestions/struct-lit-placeholder-path.stderr b/tests/ui/suggestions/struct-lit-placeholder-path.stderr new file mode 100644 index 0000000000000..8cd24b6f268d1 --- /dev/null +++ b/tests/ui/suggestions/struct-lit-placeholder-path.stderr @@ -0,0 +1,26 @@ +error: placeholder `_` is not allowed for the path in struct literals + --> $DIR/struct-lit-placeholder-path.rs:9:20 + | +LL | blah::do_stuff(_ { x: 10 }); + | ^ not allowed in struct literals + | +help: replace it with the correct type + | +LL - blah::do_stuff(_ { x: 10 }); +LL + blah::do_stuff(/* Type */ { x: 10 }); + | + +error: placeholder `_` is not allowed for the path in struct literals + --> $DIR/struct-lit-placeholder-path.rs:17:20 + | +LL | blah::do_stuff(_ { x: 10 }); + | ^ not allowed in struct literals + | +help: replace it with the correct type + | +LL - blah::do_stuff(_ { x: 10 }); +LL + blah::do_stuff(/* Type */ { x: 10 }); + | + +error: aborting due to 2 previous errors + From 2b32446c7cdda434a4aed521dd6ec63891feb0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 25 Jan 2026 23:00:18 +0000 Subject: [PATCH 5/9] Suggest changing `iter`/`into_iter` when the other was meant When encountering a call to `iter` that should have been `into_iter` and vice-versa, provide a structured suggestion: ``` error[E0271]: type mismatch resolving ` as IntoIterator>::Item == &{integer}` --> $DIR/into_iter-when-iter-was-intended.rs:5:37 | LL | let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); | ----- ^^^^^^^^^^^^^^^^^^^^^ expected `&{integer}`, found integer | | | required by a bound introduced by this call | note: the method call chain might not have had the expected associated types --> $DIR/into_iter-when-iter-was-intended.rs:5:47 | LL | let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); | --------- ^^^^^^^^^^^ `IntoIterator::Item` is `{integer}` here | | | this expression has type `[{integer}; 3]` note: required by a bound in `std::iter::Iterator::chain` --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL help: consider not consuming the `[{integer}, 3]` to construct the `Iterator` | LL - let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); LL + let _a = [0, 1, 2].iter().chain([3, 4, 5].iter()); | ``` --- compiler/rustc_span/src/symbol.rs | 1 + .../src/error_reporting/traits/suggestions.rs | 49 +++++++++++++++++-- library/core/src/iter/traits/collect.rs | 1 + .../into_iter-when-iter-was-intended.fixed | 10 ++++ .../into_iter-when-iter-was-intended.rs | 10 ++++ .../into_iter-when-iter-was-intended.stderr | 48 ++++++++++++++++++ 6 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/ui/iterators/into_iter-when-iter-was-intended.fixed create mode 100644 tests/ui/iterators/into_iter-when-iter-was-intended.rs create mode 100644 tests/ui/iterators/into_iter-when-iter-was-intended.stderr diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 8fd228211f3c3..c4391c3ec4fc4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -270,6 +270,7 @@ symbols! { Into, IntoFuture, IntoIterator, + IntoIteratorItem, IoBufRead, IoLines, IoRead, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 1c08b5e331422..d54f3812350d9 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4390,6 +4390,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { param_env: ty::ParamEnv<'tcx>, path_segment: &hir::PathSegment<'_>, args: &[hir::Expr<'_>], + prev_ty: Ty<'_>, err: &mut Diag<'_, G>, ) { let tcx = self.tcx; @@ -4403,6 +4404,47 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let TypeError::Sorts(expected_found) = diff else { continue; }; + if tcx.is_diagnostic_item(sym::IntoIteratorItem, *def_id) + && path_segment.ident.name == sym::iter + && self.can_eq( + param_env, + Ty::new_ref( + tcx, + tcx.lifetimes.re_erased, + expected_found.found, + ty::Mutability::Not, + ), + *ty, + ) + && let [] = args + { + // Used `.iter()` when `.into_iter()` was likely meant. + err.span_suggestion_verbose( + path_segment.ident.span, + format!("consider consuming the `{prev_ty}` to construct the `Iterator`"), + "into_iter".to_string(), + Applicability::MachineApplicable, + ); + } + if tcx.is_diagnostic_item(sym::IntoIteratorItem, *def_id) + && path_segment.ident.name == sym::into_iter + && self.can_eq( + param_env, + expected_found.found, + Ty::new_ref(tcx, tcx.lifetimes.re_erased, *ty, ty::Mutability::Not), + ) + && let [] = args + { + // Used `.into_iter()` when `.iter()` was likely meant. + err.span_suggestion_verbose( + path_segment.ident.span, + format!( + "consider not consuming the `{prev_ty}` to construct the `Iterator`" + ), + "iter".to_string(), + Applicability::MachineApplicable, + ); + } if tcx.is_diagnostic_item(sym::IteratorItem, *def_id) && path_segment.ident.name == sym::map && self.can_eq(param_env, expected_found.found, *ty) @@ -4515,6 +4557,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { expr = rcvr_expr; let assocs_in_this_method = self.probe_assoc_types_at_expr(&type_diffs, span, prev_ty, expr.hir_id, param_env); + prev_ty = self.resolve_vars_if_possible( + typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx)), + ); self.look_for_iterator_item_mistakes( &assocs_in_this_method, typeck_results, @@ -4522,12 +4567,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { param_env, path_segment, args, + prev_ty, err, ); assocs.push(assocs_in_this_method); - prev_ty = self.resolve_vars_if_possible( - typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx)), - ); if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind && let hir::Path { res: Res::Local(hir_id), .. } = path diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index cdf81385bdafb..c3b9a0f0b7a4e 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -281,6 +281,7 @@ pub trait FromIterator: Sized { #[stable(feature = "rust1", since = "1.0.0")] pub trait IntoIterator { /// The type of the elements being iterated over. + #[rustc_diagnostic_item = "IntoIteratorItem"] #[stable(feature = "rust1", since = "1.0.0")] type Item; diff --git a/tests/ui/iterators/into_iter-when-iter-was-intended.fixed b/tests/ui/iterators/into_iter-when-iter-was-intended.fixed new file mode 100644 index 0000000000000..e841b1605f11a --- /dev/null +++ b/tests/ui/iterators/into_iter-when-iter-was-intended.fixed @@ -0,0 +1,10 @@ +//@ run-rustfix +//@ edition:2021 +// Suggest using the right `IntoIterator` method. #68095 +fn main() { + let _a = [0, 1, 2].iter().chain([3, 4, 5].iter()); //~ ERROR E0271 + let _b = [0, 1, 2].into_iter().chain([3, 4, 5].into_iter()); //~ ERROR E0271 + // These don't have appropriate suggestions yet. + // let c = [0, 1, 2].iter().chain([3, 4, 5]); + // let d = [0, 1, 2].iter().chain(vec![3, 4, 5]); +} diff --git a/tests/ui/iterators/into_iter-when-iter-was-intended.rs b/tests/ui/iterators/into_iter-when-iter-was-intended.rs new file mode 100644 index 0000000000000..8d4376aa0ae6d --- /dev/null +++ b/tests/ui/iterators/into_iter-when-iter-was-intended.rs @@ -0,0 +1,10 @@ +//@ run-rustfix +//@ edition:2021 +// Suggest using the right `IntoIterator` method. #68095 +fn main() { + let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); //~ ERROR E0271 + let _b = [0, 1, 2].into_iter().chain([3, 4, 5].iter()); //~ ERROR E0271 + // These don't have appropriate suggestions yet. + // let c = [0, 1, 2].iter().chain([3, 4, 5]); + // let d = [0, 1, 2].iter().chain(vec![3, 4, 5]); +} diff --git a/tests/ui/iterators/into_iter-when-iter-was-intended.stderr b/tests/ui/iterators/into_iter-when-iter-was-intended.stderr new file mode 100644 index 0000000000000..f26db9781b13a --- /dev/null +++ b/tests/ui/iterators/into_iter-when-iter-was-intended.stderr @@ -0,0 +1,48 @@ +error[E0271]: type mismatch resolving ` as IntoIterator>::Item == &{integer}` + --> $DIR/into_iter-when-iter-was-intended.rs:5:37 + | +LL | let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); + | ----- ^^^^^^^^^^^^^^^^^^^^^ expected `&{integer}`, found integer + | | + | required by a bound introduced by this call + | +note: the method call chain might not have had the expected associated types + --> $DIR/into_iter-when-iter-was-intended.rs:5:47 + | +LL | let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); + | --------- ^^^^^^^^^^^ `IntoIterator::Item` is `{integer}` here + | | + | this expression has type `[{integer}; 3]` +note: required by a bound in `std::iter::Iterator::chain` + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +help: consider not consuming the `[{integer}; 3]` to construct the `Iterator` + | +LL - let _a = [0, 1, 2].iter().chain([3, 4, 5].into_iter()); +LL + let _a = [0, 1, 2].iter().chain([3, 4, 5].iter()); + | + +error[E0271]: type mismatch resolving ` as IntoIterator>::Item == {integer}` + --> $DIR/into_iter-when-iter-was-intended.rs:6:42 + | +LL | let _b = [0, 1, 2].into_iter().chain([3, 4, 5].iter()); + | ----- ^^^^^^^^^^^^^^^^ expected integer, found `&{integer}` + | | + | required by a bound introduced by this call + | +note: the method call chain might not have had the expected associated types + --> $DIR/into_iter-when-iter-was-intended.rs:6:52 + | +LL | let _b = [0, 1, 2].into_iter().chain([3, 4, 5].iter()); + | --------- ^^^^^^ `IntoIterator::Item` is `&{integer}` here + | | + | this expression has type `[{integer}; 3]` +note: required by a bound in `std::iter::Iterator::chain` + --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL +help: consider consuming the `&[{integer}]` to construct the `Iterator` + | +LL | let _b = [0, 1, 2].into_iter().chain([3, 4, 5].into_iter()); + | +++++ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0271`. From dab7c0923e7b43c95c5a64259a4e020ae38827ed Mon Sep 17 00:00:00 2001 From: Boxy Date: Tue, 20 Jan 2026 14:26:30 +0000 Subject: [PATCH 6/9] Misc cleanups to borrowck crate --- .../src/type_check/canonical.rs | 15 +++- .../src/type_check/input_output.rs | 77 +++++++++++++------ compiler/rustc_borrowck/src/type_check/mod.rs | 23 +++--- compiler/rustc_middle/src/ty/util.rs | 5 +- .../normalization-generality-2.rs | 3 + .../normalization-generality.rs | 3 + .../higher-ranked/trait-bounds/issue-88446.rs | 3 + .../normalize-under-binder/issue-89436.rs | 3 + .../normalize-under-binder/issue-90638.rs | 3 + .../issue-112604-closure-output-normalize.rs | 3 + 10 files changed, 100 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/canonical.rs b/compiler/rustc_borrowck/src/type_check/canonical.rs index aece0bda34692..21ea2c70fbb33 100644 --- a/compiler/rustc_borrowck/src/type_check/canonical.rs +++ b/compiler/rustc_borrowck/src/type_check/canonical.rs @@ -343,8 +343,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { return; } - // FIXME: Ideally MIR types are normalized, but this is not always true. - let mir_ty = self.normalize(mir_ty, Locations::All(span)); + // This is a hack. `body.local_decls` are not necessarily normalized in the old + // solver due to not deeply normalizing in writeback. So we must re-normalize here. + // + // I am not sure of a test case where this actually matters. There is a similar + // hack in `equate_inputs_and_outputs` which does have associated test cases. + let mir_ty = match self.infcx.next_trait_solver() { + true => mir_ty, + false => self.normalize(mir_ty, Locations::All(span)), + }; let cause = ObligationCause::dummy_with_span(span); let param_env = self.infcx.param_env; @@ -353,6 +360,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::Boring, type_op::custom::CustomTypeOp::new( |ocx| { + // The `AscribeUserType` query would normally emit a wf + // obligation for the unnormalized user_ty here. This is + // where the "incorrectly skips the WF checks we normally do" + // happens let user_ty = ocx.normalize(&cause, param_env, user_ty); ocx.eq(&cause, param_env, user_ty, mir_ty)?; Ok(()) diff --git a/compiler/rustc_borrowck/src/type_check/input_output.rs b/compiler/rustc_borrowck/src/type_check/input_output.rs index f3b9dcc90a845..3bce78b4e2e2d 100644 --- a/compiler/rustc_borrowck/src/type_check/input_output.rs +++ b/compiler/rustc_borrowck/src/type_check/input_output.rs @@ -126,6 +126,31 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } + // FIXME(BoxyUwU): This should probably be part of a larger borrowck dev-guide chapter + // + /// Enforce that the types of the locals corresponding to the inputs and output of + /// the body are equal to those of the (normalized) signature. + /// + /// This is necessary for two reasons: + /// - Locals in the MIR all start out with `'erased` regions and then are replaced + /// with unconstrained nll vars. If we have a function returning `&'a u32` then + /// the local `_0: &'?10 u32` needs to have its region var equated with the nll + /// var representing `'a`. i.e. borrow check must uphold that `'?10 = 'a`. + /// - When computing the normalized signature we may introduce new unconstrained nll + /// vars due to higher ranked where clauses ([#136547]). We then wind up with implied + /// bounds involving these vars. + /// + /// For this reason it is important that we equate with the *normalized* signature + /// which was produced when computing implied bounds. If we do not do so then we will + /// wind up with implied bounds on nll vars which cannot actually be used as the nll + /// var never gets related to anything. + /// + /// For 'closure-like' bodies this function effectively relates the *inferred* signature + /// of the closure against the locals corresponding to the closure's inputs/output. It *does + /// not* relate the user provided types for the signature to the locals, this is handled + /// separately by: [`TypeChecker::check_signature_annotation`]. + /// + /// [#136547]: #[instrument(skip(self), level = "debug")] pub(super) fn equate_inputs_and_outputs(&mut self, normalized_inputs_and_output: &[Ty<'tcx>]) { let (&normalized_output_ty, normalized_input_tys) = @@ -173,38 +198,44 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } - // Return types are a bit more complex. They may contain opaque `impl Trait` types. - let mir_output_ty = self.body.local_decls[RETURN_PLACE].ty; + // Equate expected output ty with the type of the RETURN_PLACE in MIR + let mir_output_ty = self.body.return_ty(); let output_span = self.body.local_decls[RETURN_PLACE].source_info.span; self.equate_normalized_input_or_output(normalized_output_ty, mir_output_ty, output_span); } #[instrument(skip(self), level = "debug")] fn equate_normalized_input_or_output(&mut self, a: Ty<'tcx>, b: Ty<'tcx>, span: Span) { + if self.infcx.next_trait_solver() { + return self + .eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) + .unwrap_or_else(|terr| { + span_mirbug!( + self, + Location::START, + "equate_normalized_input_or_output: `{a:?}=={b:?}` failed with `{terr:?}`", + ); + }); + } + + // This is a hack. `body.local_decls` are not necessarily normalized in the old + // solver due to not deeply normalizing in writeback. So we must re-normalize here. + // + // However, in most cases normalizing is unnecessary so we only do so if it may be + // necessary for type equality to hold. This leads to some (very minor) performance + // wins. if let Err(_) = self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) { - // FIXME(jackh726): This is a hack. It's somewhat like - // `rustc_traits::normalize_after_erasing_regions`. Ideally, we'd - // like to normalize *before* inserting into `local_decls`, but - // doing so ends up causing some other trouble. let b = self.normalize(b, Locations::All(span)); - - // Note: if we have to introduce new placeholders during normalization above, then we - // won't have added those universes to the universe info, which we would want in - // `relate_tys`. - if let Err(terr) = - self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) - { - span_mirbug!( - self, - Location::START, - "equate_normalized_input_or_output: `{:?}=={:?}` failed with `{:?}`", - a, - b, - terr - ); - } - } + self.eq_types(a, b, Locations::All(span), ConstraintCategory::BoringNoLocation) + .unwrap_or_else(|terr| { + span_mirbug!( + self, + Location::START, + "equate_normalized_input_or_output: `{a:?}=={b:?}` failed with `{terr:?}`", + ); + }); + }; } } diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index d2464c7e99ee5..676b45e9974cb 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -123,16 +123,19 @@ pub(crate) fn type_check<'tcx>( known_type_outlives_obligations, } = free_region_relations::create(infcx, universal_regions, &mut constraints); - let pre_obligations = infcx.take_registered_region_obligations(); - assert!( - pre_obligations.is_empty(), - "there should be no incoming region obligations = {pre_obligations:#?}", - ); - let pre_assumptions = infcx.take_registered_region_assumptions(); - assert!( - pre_assumptions.is_empty(), - "there should be no incoming region assumptions = {pre_assumptions:#?}", - ); + { + // Scope these variables so it's clear they're not used later + let pre_obligations = infcx.take_registered_region_obligations(); + assert!( + pre_obligations.is_empty(), + "there should be no incoming region obligations = {pre_obligations:#?}", + ); + let pre_assumptions = infcx.take_registered_region_assumptions(); + assert!( + pre_assumptions.is_empty(), + "there should be no incoming region assumptions = {pre_assumptions:#?}", + ); + } debug!(?normalized_inputs_and_output); diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index 2797f2fcdb72e..c4212eee8e40b 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -609,9 +609,8 @@ impl<'tcx> TyCtxt<'tcx> { /// have the same `DefKind`. /// /// Note that closures have a `DefId`, but the closure *expression* also has a - /// `HirId` that is located within the context where the closure appears (and, sadly, - /// a corresponding `NodeId`, since those are not yet phased out). The parent of - /// the closure's `DefId` will also be the context where it appears. + /// `HirId` that is located within the context where the closure appears. The + /// parent of the closure's `DefId` will also be the context where it appears. pub fn is_closure_like(self, def_id: DefId) -> bool { matches!(self.def_kind(def_id), DefKind::Closure) } diff --git a/tests/ui/associated-types/normalization-generality-2.rs b/tests/ui/associated-types/normalization-generality-2.rs index 50287f9ee9b8e..2a50f7e449add 100644 --- a/tests/ui/associated-types/normalization-generality-2.rs +++ b/tests/ui/associated-types/normalization-generality-2.rs @@ -1,4 +1,7 @@ //@ build-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver // Ensures that we don't regress on "implementation is not general enough" when // normalizating under binders. Unlike `normalization-generality.rs`, this also produces diff --git a/tests/ui/associated-types/normalization-generality.rs b/tests/ui/associated-types/normalization-generality.rs index 35fcf53b64141..fca70bc7ec671 100644 --- a/tests/ui/associated-types/normalization-generality.rs +++ b/tests/ui/associated-types/normalization-generality.rs @@ -1,4 +1,7 @@ //@ build-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver // Ensures that we don't regress on "implementation is not general enough" when // normalizating under binders. diff --git a/tests/ui/higher-ranked/trait-bounds/issue-88446.rs b/tests/ui/higher-ranked/trait-bounds/issue-88446.rs index 0ca8387776a46..8e42465b929a4 100644 --- a/tests/ui/higher-ranked/trait-bounds/issue-88446.rs +++ b/tests/ui/higher-ranked/trait-bounds/issue-88446.rs @@ -1,4 +1,7 @@ //@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver trait Yokeable<'a> { type Output: 'a; diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs index d85c6999e26f3..226bd48f0e4e5 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-89436.rs @@ -1,4 +1,7 @@ //@ check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver #![allow(unused)] diff --git a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs index b3feda4a531f1..0dcef54ed69c6 100644 --- a/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs +++ b/tests/ui/higher-ranked/trait-bounds/normalize-under-binder/issue-90638.rs @@ -1,4 +1,7 @@ //@check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver trait Yokeable<'a>: 'static { type Output: 'a; diff --git a/tests/ui/nll/issue-112604-closure-output-normalize.rs b/tests/ui/nll/issue-112604-closure-output-normalize.rs index 117e1d91e3413..c999007599179 100644 --- a/tests/ui/nll/issue-112604-closure-output-normalize.rs +++ b/tests/ui/nll/issue-112604-closure-output-normalize.rs @@ -1,4 +1,7 @@ //@check-pass +//@ revisions: current next +//@ ignore-compare-mode-next-solver (explicit revisions) +//@[next] compile-flags: -Znext-solver use higher_kinded_types::*; mod higher_kinded_types { From 3757ce6d1f6b3f54fabaf87d4b2c9ac1db76e808 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 19 Jan 2026 13:35:05 +0100 Subject: [PATCH 7/9] Remove a string comparison and reduce number of clones in `Hierarchy::add_path` --- src/librustdoc/html/render/write_shared.rs | 52 ++++++++++++---------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index af97ae93a2b9a..42fc35ded7c6e 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -504,33 +504,37 @@ impl Hierarchy { fn add_path(self: &Rc, path: &Path) { let mut h = Rc::clone(self); - let mut elems = path + let mut components = path .components() - .filter_map(|s| match s { - Component::Normal(s) => Some(s.to_owned()), - Component::ParentDir => Some(OsString::from("..")), - _ => None, - }) + .filter(|component| matches!(component, Component::Normal(_) | Component::ParentDir)) .peekable(); - loop { - let cur_elem = elems.next().expect("empty file path"); - if cur_elem == ".." { - if let Some(parent) = h.parent.upgrade() { - h = parent; + + while let Some(component) = components.next() { + match component { + Component::Normal(s) => { + if components.peek().is_none() { + h.elems.borrow_mut().insert(s.to_owned()); + break; + } + let next_h = { + let mut children = h.children.borrow_mut(); + + if let Some(existing) = children.get(s) { + Rc::clone(existing) + } else { + let new_node = Rc::new(Self::with_parent(s.to_owned(), &h)); + children.insert(s.to_owned(), Rc::clone(&new_node)); + new_node + } + }; + h = next_h; } - continue; - } - if elems.peek().is_none() { - h.elems.borrow_mut().insert(cur_elem); - break; - } else { - let entry = Rc::clone( - h.children - .borrow_mut() - .entry(cur_elem.clone()) - .or_insert_with(|| Rc::new(Self::with_parent(cur_elem, &h))), - ); - h = entry; + Component::ParentDir => { + if let Some(parent) = h.parent.upgrade() { + h = parent; + } + } + _ => {} } } } From dd42a9f118e3d35928a5efccfca1ad692f13e93b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 19 Jan 2026 15:00:04 +0100 Subject: [PATCH 8/9] Replace regex with find calls --- src/librustdoc/html/render/write_shared.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 42fc35ded7c6e..aa7972449e551 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -26,7 +26,6 @@ use std::str::FromStr; use std::{fmt, fs}; use indexmap::IndexMap; -use regex::Regex; use rustc_ast::join_path_syms; use rustc_data_structures::flock; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; @@ -376,12 +375,15 @@ fn hack_get_external_crate_names( }; // this is only run once so it's fine not to cache it // !dot_matches_new_line: all crates on same line. greedy: match last bracket - let regex = Regex::new(r"\[.*\]").unwrap(); - let Some(content) = regex.find(&content) else { - return Err(Error::new("could not find crates list in crates.js", path)); - }; - let content: Vec = try_err!(serde_json::from_str(content.as_str()), &path); - Ok(content) + if let Some(start) = content.find('[') + && let Some(end) = content[start..].find(']') + { + let content: Vec = + try_err!(serde_json::from_str(&content[start..=start + end]), &path); + Ok(content) + } else { + Err(Error::new("could not find crates list in crates.js", path)) + } } #[derive(Serialize, Deserialize, Clone, Default, Debug)] From bd453118ee2ac0195e8b5eff653cde11b9ca66e4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 26 Jan 2026 14:59:44 +0100 Subject: [PATCH 9/9] Improve code --- src/librustdoc/html/render/write_shared.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index aa7972449e551..1b5dbeed8de80 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -511,6 +511,7 @@ impl Hierarchy { .filter(|component| matches!(component, Component::Normal(_) | Component::ParentDir)) .peekable(); + assert!(components.peek().is_some(), "empty file path"); while let Some(component) = components.next() { match component { Component::Normal(s) => { @@ -518,7 +519,7 @@ impl Hierarchy { h.elems.borrow_mut().insert(s.to_owned()); break; } - let next_h = { + h = { let mut children = h.children.borrow_mut(); if let Some(existing) = children.get(s) { @@ -529,12 +530,9 @@ impl Hierarchy { new_node } }; - h = next_h; } - Component::ParentDir => { - if let Some(parent) = h.parent.upgrade() { - h = parent; - } + Component::ParentDir if let Some(parent) = h.parent.upgrade() => { + h = parent; } _ => {} }