From aa7d812f348da3e330cf3b99ee4a5ec373f2a38b Mon Sep 17 00:00:00 2001 From: cyrgani Date: Tue, 17 Mar 2026 09:27:25 +0000 Subject: [PATCH 01/11] remove `internal_impls_macro` feature --- library/core/src/lib.rs | 1 - library/core/src/marker.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index e12c43068245a..60da6662de849 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -107,7 +107,6 @@ #![feature(core_intrinsics)] #![feature(coverage_attribute)] #![feature(disjoint_bitor)] -#![feature(internal_impls_macro)] #![feature(link_cfg)] #![feature(offset_of_enum)] #![feature(panic_internals)] diff --git a/library/core/src/marker.rs b/library/core/src/marker.rs index becac514284e3..c79e8fc4060c1 100644 --- a/library/core/src/marker.rs +++ b/library/core/src/marker.rs @@ -52,9 +52,6 @@ use crate::pin::UnsafePinned; /// u32, /// } /// ``` -#[unstable(feature = "internal_impls_macro", issue = "none")] -// Allow implementations of `UnsizedConstParamTy` even though std cannot use that feature. -#[allow_internal_unstable(const_param_ty_trait)] macro marker_impls { ( $(#[$($meta:tt)*])* $Trait:ident for $({$($bounds:tt)*})? $T:ty $(, $($rest:tt)*)? ) => { $(#[$($meta)*])* impl< $($($bounds)*)? > $Trait for $T {} From e350a56cf9d7a013e1d1e52ab306bfc19324653a Mon Sep 17 00:00:00 2001 From: cyrgani Date: Tue, 24 Feb 2026 13:48:09 +0000 Subject: [PATCH 02/11] move features into the correct section --- library/core/src/lib.rs | 2 +- library/std/src/lib.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index 60da6662de849..8a9decd33b1c1 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -107,7 +107,6 @@ #![feature(core_intrinsics)] #![feature(coverage_attribute)] #![feature(disjoint_bitor)] -#![feature(link_cfg)] #![feature(offset_of_enum)] #![feature(panic_internals)] #![feature(pattern_type_macro)] @@ -143,6 +142,7 @@ #![feature(intra_doc_pointers)] #![feature(intrinsics)] #![feature(lang_items)] +#![feature(link_cfg)] #![feature(link_llvm_intrinsics)] #![feature(macro_metavar_expr)] #![feature(macro_metavar_expr_concat)] diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 6fcb28edc7d84..c8c8a6c897142 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -275,9 +275,7 @@ #![feature(cfg_sanitizer_cfi)] #![feature(cfg_target_thread_local)] #![feature(cfi_encoding)] -#![feature(const_default)] #![feature(const_trait_impl)] -#![feature(core_float_math)] #![feature(decl_macro)] #![feature(deprecated_suggestion)] #![feature(doc_cfg)] @@ -287,16 +285,11 @@ #![feature(f16)] #![feature(f128)] #![feature(ffi_const)] -#![feature(formatting_options)] -#![feature(funnel_shifts)] #![feature(intra_doc_pointers)] -#![feature(iter_advance_by)] -#![feature(iter_next_chunk)] #![feature(lang_items)] #![feature(link_cfg)] #![feature(linkage)] #![feature(macro_metavar_expr_concat)] -#![feature(maybe_uninit_fill)] #![feature(min_specialization)] #![feature(must_not_suspend)] #![feature(needs_panic_runtime)] @@ -314,7 +307,6 @@ #![feature(try_blocks)] #![feature(try_trait_v2)] #![feature(type_alias_impl_trait)] -#![feature(uint_carryless_mul)] // tidy-alphabetical-end // // Library features (core): @@ -325,6 +317,8 @@ #![feature(char_internals)] #![feature(clone_to_uninit)] #![feature(const_convert)] +#![feature(const_default)] +#![feature(core_float_math)] #![feature(core_intrinsics)] #![feature(core_io_borrowed_buf)] #![feature(cstr_display)] @@ -340,13 +334,18 @@ #![feature(float_minimum_maximum)] #![feature(fmt_internals)] #![feature(fn_ptr_trait)] +#![feature(formatting_options)] +#![feature(funnel_shifts)] #![feature(generic_atomic)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] #![feature(hint_must_use)] #![feature(int_from_ascii)] #![feature(ip)] +#![feature(iter_advance_by)] +#![feature(iter_next_chunk)] #![feature(maybe_uninit_array_assume_init)] +#![feature(maybe_uninit_fill)] #![feature(panic_can_unwind)] #![feature(panic_internals)] #![feature(pin_coerce_unsized_trait)] @@ -364,6 +363,7 @@ #![feature(sync_unsafe_cell)] #![feature(temporary_niche_types)] #![feature(ub_checks)] +#![feature(uint_carryless_mul)] #![feature(used_with_arg)] // tidy-alphabetical-end // From c8a2c46eda3d8a8cc0dbe92002543ab5867586a3 Mon Sep 17 00:00:00 2001 From: Jynn Nelson Date: Mon, 16 Mar 2026 14:33:39 +0100 Subject: [PATCH 03/11] bootstrap: Optionally print a backtrace if a command fails --- src/bootstrap/src/utils/exec.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs index b264c961b6593..f9b0d2dbb2095 100644 --- a/src/bootstrap/src/utils/exec.rs +++ b/src/bootstrap/src/utils/exec.rs @@ -7,6 +7,7 @@ //! relevant to command execution in the bootstrap process. This includes settings such as //! dry-run mode, verbosity level, and failure behavior. +use std::backtrace::{Backtrace, BacktraceStatus}; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::fmt::{Debug, Formatter}; @@ -930,6 +931,16 @@ Executed at: {executed_at}"#, if stderr.captures() { writeln!(error_message, "\n--- STDERR vvv\n{}", output.stderr().trim()).unwrap(); } + let backtrace = if exec_ctx.verbosity > 1 { + Backtrace::force_capture() + } else if matches!(command.failure_behavior, BehaviorOnFailure::Ignore) { + Backtrace::disabled() + } else { + Backtrace::capture() + }; + if matches!(backtrace.status(), BacktraceStatus::Captured) { + writeln!(error_message, "\n--- BACKTRACE vvv\n{backtrace}").unwrap(); + } match command.failure_behavior { BehaviorOnFailure::DelayFail => { From 45b22efe06630703e20de19c589299e6a592469e Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Wed, 18 Mar 2026 21:29:45 +0100 Subject: [PATCH 04/11] tests: Activate must_not_suspend test for MutexGuard dropped before await The test pass in `nightly-2023-09-24` but fail in `nightly-2023-09-23`: $ rustc +nightly-2023-09-23 --edition 2018 tests/ui/lint/must_not_suspend/mutex-guard-dropped-before-await.rs error: `MutexGuard` held across a suspend point, but should not be --> tests/ui/lint/must_not_suspend/mutex-guard-dropped-before-await.rs:13:9 | 13 | let lock = foo.lock().unwrap(); | ^^^^ ... 18 | bar().await; | ----- the value is held across this suspend point | --- src/tools/tidy/src/issues.txt | 1 - .../{issue-89562.rs => mutex-guard-dropped-before-await.rs} | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) rename tests/ui/lint/must_not_suspend/{issue-89562.rs => mutex-guard-dropped-before-await.rs} (67%) diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 668a8abf1d8bb..a9dc449afa835 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -1476,7 +1476,6 @@ ui/lint/issue-97094.rs ui/lint/issue-99387.rs ui/lint/let_underscore/issue-119696-err-on-fn.rs ui/lint/let_underscore/issue-119697-extra-let.rs -ui/lint/must_not_suspend/issue-89562.rs ui/lint/unused/issue-103320-must-use-ops.rs ui/lint/unused/issue-104397.rs ui/lint/unused/issue-105061-array-lint.rs diff --git a/tests/ui/lint/must_not_suspend/issue-89562.rs b/tests/ui/lint/must_not_suspend/mutex-guard-dropped-before-await.rs similarity index 67% rename from tests/ui/lint/must_not_suspend/issue-89562.rs rename to tests/ui/lint/must_not_suspend/mutex-guard-dropped-before-await.rs index 99a548130720f..d1c48c3c51457 100644 --- a/tests/ui/lint/must_not_suspend/issue-89562.rs +++ b/tests/ui/lint/must_not_suspend/mutex-guard-dropped-before-await.rs @@ -1,9 +1,13 @@ +//! Regression test for + //@ edition:2018 //@ run-pass +#![feature(must_not_suspend)] +#![deny(must_not_suspend)] + use std::sync::Mutex; -// Copied from the issue. Allow-by-default for now, so run-pass pub async fn foo() { let foo = Mutex::new(1); let lock = foo.lock().unwrap(); From bef4b476c17685077a7f2cf680743ab8bc2cd11b Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Thu, 19 Mar 2026 09:22:03 +0100 Subject: [PATCH 05/11] Updates derive_where and removes workaround --- Cargo.lock | 4 ++-- compiler/rustc_type_ir/Cargo.toml | 2 +- compiler/rustc_type_ir/src/binder.rs | 20 +++----------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98567f858e9f1..539f66ffb3bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,9 +1140,9 @@ version = "0.1.96" [[package]] name = "derive-where" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" dependencies = [ "proc-macro2", "quote", diff --git a/compiler/rustc_type_ir/Cargo.toml b/compiler/rustc_type_ir/Cargo.toml index 10c53c881a362..b00f9eebb8266 100644 --- a/compiler/rustc_type_ir/Cargo.toml +++ b/compiler/rustc_type_ir/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" # tidy-alphabetical-start arrayvec = { version = "0.7", default-features = false } bitflags = "2.4.1" -derive-where = "1.2.7" +derive-where = "1.6.1" ena = "0.14.4" indexmap = "2.0.0" rustc-hash = "2.0.0" diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index fc8b39f7c01f0..0b0f0fd2f4249 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -26,11 +26,7 @@ use crate::{self as ty, DebruijnIndex, Interner, UniverseIndex}; /// for more details. /// /// `Decodable` and `Encodable` are implemented for `Binder` using the `impl_binder_encode_decode!` macro. -// FIXME(derive-where#136): Need to use separate `derive_where` for -// `Copy` and `Ord` to prevent the emitted `Clone` and `PartialOrd` -// impls from incorrectly relying on `T: Copy` and `T: Ord`. -#[derive_where(Copy; I: Interner, T: Copy)] -#[derive_where(Clone, Hash, PartialEq, Debug; I: Interner, T)] +#[derive_where(Clone, Copy, Hash, PartialEq, Debug; I: Interner, T)] #[derive(GenericTypeVisitable)] #[cfg_attr(feature = "nightly", derive(HashStable_NoContext))] pub struct Binder { @@ -365,12 +361,7 @@ impl TypeVisitor for ValidateBoundVars { /// `instantiate`. /// /// See for more details. -// FIXME(derive-where#136): Need to use separate `derive_where` for -// `Copy` and `Ord` to prevent the emitted `Clone` and `PartialOrd` -// impls from incorrectly relying on `T: Copy` and `T: Ord`. -#[derive_where(Ord; I: Interner, T: Ord)] -#[derive_where(Copy; I: Interner, T: Copy)] -#[derive_where(Clone, PartialOrd, PartialEq, Hash, Debug; I: Interner, T)] +#[derive_where(Clone, Copy, PartialOrd, Ord, PartialEq, Hash, Debug; I: Interner, T)] #[derive(GenericTypeVisitable)] #[cfg_attr( feature = "nightly", @@ -964,12 +955,7 @@ pub enum BoundVarIndexKind { /// The "placeholder index" fully defines a placeholder region, type, or const. Placeholders are /// identified by both a universe, as well as a name residing within that universe. Distinct bound /// regions/types/consts within the same universe simply have an unknown relationship to one -// FIXME(derive-where#136): Need to use separate `derive_where` for -// `Copy` and `Ord` to prevent the emitted `Clone` and `PartialOrd` -// impls from incorrectly relying on `T: Copy` and `T: Ord`. -#[derive_where(Ord; I: Interner, T: Ord)] -#[derive_where(Copy; I: Interner, T: Copy)] -#[derive_where(Clone, PartialOrd, PartialEq, Eq, Hash; I: Interner, T)] +#[derive_where(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash; I: Interner, T)] #[derive(TypeVisitable_Generic, TypeFoldable_Generic)] #[cfg_attr( feature = "nightly", From 74776c4008a668e70733e16d8cf8519f3551e388 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 19 Mar 2026 11:06:21 +1100 Subject: [PATCH 06/11] Rewrite `query_ensure_result`. It currently uses chaining which is concise but hard to read. There are up to four implicit matches occurring after the call to `execute_query_fn`. - The first `map` on `Option>>`. - The second `map` on `Option>`. - The third `map` on `Result`. - The `unwrap_or` on `Option>`. This commit rewrites it to use at most two matches. - An explicit match on `Option>>`. - An explicit match on `Result`. This is easier to read. It's also more efficient, though the code isn't hot enough for that to matter. --- compiler/rustc_middle/src/query/inner.rs | 43 +++++++++++++++--------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_middle/src/query/inner.rs b/compiler/rustc_middle/src/query/inner.rs index c2525d26d4c67..402c448a1fa3a 100644 --- a/compiler/rustc_middle/src/query/inner.rs +++ b/compiler/rustc_middle/src/query/inner.rs @@ -77,23 +77,34 @@ where C: QueryCache>>, Result: Erasable, { + let convert = |value: Erased>| -> Result<(), ErrorGuaranteed> { + match erase::restore_val(value) { + Ok(_) => Ok(()), + Err(guar) => Err(guar), + } + }; + match try_get_cached(tcx, &query.cache, key) { - Some(value) => erase::restore_val(value).map(drop), - None => (query.execute_query_fn)( - tcx, - DUMMY_SP, - key, - QueryMode::Ensure { ensure_mode: EnsureMode::Ok }, - ) - .map(erase::restore_val) - .map(|value| value.map(drop)) - // Either we actually executed the query, which means we got a full `Result`, - // or we can just assume the query succeeded, because it was green in the - // incremental cache. If it is green, that means that the previous compilation - // that wrote to the incremental cache compiles successfully. That is only - // possible if the cache entry was `Ok(())`, so we emit that here, without - // actually encoding the `Result` in the cache or loading it from there. - .unwrap_or(Ok(())), + Some(value) => convert(value), + None => { + match (query.execute_query_fn)( + tcx, + DUMMY_SP, + key, + QueryMode::Ensure { ensure_mode: EnsureMode::Ok }, + ) { + // We executed the query. Convert the successful result. + Some(res) => convert(res), + + // Reaching here means we didn't execute the query, but we can just assume the + // query succeeded, because it was green in the incremental cache. If it is green, + // that means that the previous compilation that wrote to the incremental cache + // compiles successfully. That is only possible if the cache entry was `Ok(())`, so + // we emit that here, without actually encoding the `Result` in the cache or + // loading it from there. + None => Ok(()), + } + } } } From cec0a68d6a69a9c29efc4055f7215dff1c9de103 Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:32:36 +0900 Subject: [PATCH 07/11] Resolve paths for `impl` restrictions using `smart_resolve_path` --- compiler/rustc_resolve/src/errors.rs | 4 +++ compiler/rustc_resolve/src/late.rs | 43 +++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 63ffdbc37f7d1..a73362eb805b2 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -561,6 +561,10 @@ pub(crate) struct ExpectedModuleFound { #[diag("cannot determine resolution for the visibility", code = E0578)] pub(crate) struct Indeterminate(#[primary_span] pub(crate) Span); +#[derive(Diagnostic)] +#[diag("trait implementation can only be restricted to ancestor modules")] +pub(crate) struct RestrictionAncestorOnly(#[primary_span] pub(crate) Span); + #[derive(Diagnostic)] #[diag("cannot use a tool module through an import")] pub(crate) struct ToolModuleImported { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index c7b0cb192433e..94de55c247115 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -453,6 +453,8 @@ pub(crate) enum PathSource<'a, 'ast, 'ra> { DefineOpaques, /// Resolving a macro Macro, + /// Paths for module or crate root. Used for restrictions. + Module, } impl PathSource<'_, '_, '_> { @@ -461,7 +463,8 @@ impl PathSource<'_, '_, '_> { PathSource::Type | PathSource::Trait(_) | PathSource::Struct(_) - | PathSource::DefineOpaques => TypeNS, + | PathSource::DefineOpaques + | PathSource::Module => TypeNS, PathSource::Expr(..) | PathSource::Pat | PathSource::TupleStruct(..) @@ -486,7 +489,8 @@ impl PathSource<'_, '_, '_> { | PathSource::DefineOpaques | PathSource::Delegation | PathSource::PreciseCapturingArg(..) - | PathSource::Macro => false, + | PathSource::Macro + | PathSource::Module => false, } } @@ -529,6 +533,7 @@ impl PathSource<'_, '_, '_> { PathSource::ReturnTypeNotation | PathSource::Delegation => "function", PathSource::PreciseCapturingArg(..) => "type or const parameter", PathSource::Macro => "macro", + PathSource::Module => "module", } } @@ -627,6 +632,7 @@ impl PathSource<'_, '_, '_> { ), PathSource::PreciseCapturingArg(MacroNS) => false, PathSource::Macro => matches!(res, Res::Def(DefKind::Macro(_), _)), + PathSource::Module => matches!(res, Res::Def(DefKind::Mod, _)), } } @@ -647,6 +653,12 @@ impl PathSource<'_, '_, '_> { (PathSource::PreciseCapturingArg(..), true) => E0799, (PathSource::PreciseCapturingArg(..), false) => E0800, (PathSource::Macro, _) => E0425, + // FIXME: There is no dedicated error code for this case yet. + // E0577 already covers the same situation for visibilities, + // so we reuse it here for now. It may make sense to generalize + // it for restrictions in the future. + (PathSource::Module, true) => E0577, + (PathSource::Module, false) => E0433, } } } @@ -2175,7 +2187,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { | PathSource::Type | PathSource::PreciseCapturingArg(..) | PathSource::ReturnTypeNotation - | PathSource::Macro => false, + | PathSource::Macro + | PathSource::Module => false, PathSource::Expr(..) | PathSource::Pat | PathSource::Struct(_) @@ -2801,7 +2814,10 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.diag_metadata.current_impl_items = None; } - ItemKind::Trait(box Trait { generics, bounds, items, .. }) => { + ItemKind::Trait(box Trait { generics, bounds, items, impl_restriction, .. }) => { + // resolve paths for `impl` restrictions + self.resolve_impl_restriction_path(impl_restriction); + // Create a new rib for the trait-wide type parameters. self.with_generic_param_rib( &generics.params, @@ -4390,6 +4406,25 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } + fn resolve_impl_restriction_path(&mut self, restriction: &'ast ast::ImplRestriction) { + match &restriction.kind { + ast::RestrictionKind::Unrestricted => (), + ast::RestrictionKind::Restricted { path, id, shorthand: _ } => { + self.smart_resolve_path(*id, &None, path, PathSource::Module); + if let Some(res) = self.r.partial_res_map[&id].full_res() + && let Some(def_id) = res.opt_def_id() + { + if !self.r.is_accessible_from( + Visibility::Restricted(def_id), + self.parent_scope.module, + ) { + self.r.dcx().create_err(errors::RestrictionAncestorOnly(path.span)).emit(); + } + } + } + } + } + // High-level and context dependent path resolution routine. // Resolves the path and records the resolution into definition map. // If resolution fails tries several techniques to find likely From 4a60dae793e07440c32ec2d5ef71bf58cf1df7a9 Mon Sep 17 00:00:00 2001 From: CoCo-Japan-pan <115922543+CoCo-Japan-pan@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:43:32 +0900 Subject: [PATCH 08/11] Add UI tests for path resolution errors of `impl` restrictions --- .../restriction_resolution_errors.rs | 85 +++++++++++ .../restriction_resolution_errors.stderr | 140 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 tests/ui/impl-restriction/restriction_resolution_errors.rs create mode 100644 tests/ui/impl-restriction/restriction_resolution_errors.stderr diff --git a/tests/ui/impl-restriction/restriction_resolution_errors.rs b/tests/ui/impl-restriction/restriction_resolution_errors.rs new file mode 100644 index 0000000000000..b36f2cf9bdfba --- /dev/null +++ b/tests/ui/impl-restriction/restriction_resolution_errors.rs @@ -0,0 +1,85 @@ +#![feature(impl_restriction)] +#![expect(incomplete_features)] + +pub mod a { + pub enum E {} + pub mod d {} + pub mod b { + pub mod c {} + + // We do not use crate-relative paths here, since we follow the + // "uniform paths" approach used for type/expression paths. + pub impl(in a::b) trait T1 {} //~ ERROR cannot find module or crate `a` in this scope [E0433] + + pub impl(in ::std) trait T2 {} //~ ERROR trait implementation can only be restricted to ancestor modules + + pub impl(in self::c) trait T3 {} //~ ERROR trait implementation can only be restricted to ancestor modules + + pub impl(in super::d) trait T4 {} //~ ERROR trait implementation can only be restricted to ancestor modules + + pub impl(in crate::c) trait T5 {} //~ ERROR cannot find module `c` in the crate root [E0433] + + pub impl(in super::E) trait T6 {} //~ ERROR expected module, found enum `super::E` [E0577] + + pub impl(in super::super::super) trait T7 {} //~ ERROR too many leading `super` keywords [E0433] + + // OK paths + pub impl(crate) trait T8 {} + pub impl(self) trait T9 {} + pub impl(super) trait T10 {} + pub impl(in crate::a) trait T11 {} + pub impl(in super::super) trait T12 {} + + // Check if we can resolve paths referring to modules declared later. + pub impl(in self::f) trait L1 {} //~ ERROR trait implementation can only be restricted to ancestor modules + + pub impl(in super::G) trait L2 {} //~ ERROR expected module, found enum `super::G` [E0577] + + pub impl(in super::h) trait L3 {} //~ ERROR trait implementation can only be restricted to ancestor modules + + pub mod f {} + } + + pub enum G {} + pub mod h {} +} + +pub impl(in crate::a) trait T13 {} //~ ERROR trait implementation can only be restricted to ancestor modules + +pub impl(in crate::a::E) trait T14 {} //~ ERROR expected module, found enum `crate::a::E` [E0577] + +pub impl(crate) trait T15 {} +pub impl(self) trait T16 {} + +pub impl(super) trait T17 {} //~ ERROR too many leading `super` keywords [E0433] + +// Check if we can resolve paths referring to modules declared later. +pub impl(in crate::j) trait L4 {} //~ ERROR trait implementation can only be restricted to ancestor modules + +pub impl(in crate::I) trait L5 {} //~ ERROR expected module, found enum `crate::I` [E0577] + +pub enum I {} +pub mod j {} + +// Check if we can resolve `use`d paths. +mod m1 { + pub impl(in crate::m2) trait U1 {} // OK +} + +use m1 as m2; + +mod m3 { + mod m4 { + pub impl(in crate::m2) trait U2 {} //~ ERROR trait implementation can only be restricted to ancestor modules + pub impl(in m6) trait U3 {} // OK + pub impl(in m6::m5) trait U4 {} //~ ERROR trait implementation can only be restricted to ancestor modules + pub impl(in m7) trait U5 {} //~ ERROR expected module, found enum `m7` [E0577] + + use crate::m3 as m6; + use crate::m3::E as m7; + } + mod m5 {} + pub enum E {} +} + +fn main() {} diff --git a/tests/ui/impl-restriction/restriction_resolution_errors.stderr b/tests/ui/impl-restriction/restriction_resolution_errors.stderr new file mode 100644 index 0000000000000..540803285c1b5 --- /dev/null +++ b/tests/ui/impl-restriction/restriction_resolution_errors.stderr @@ -0,0 +1,140 @@ +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:14:21 + | +LL | pub impl(in ::std) trait T2 {} + | ^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:16:21 + | +LL | pub impl(in self::c) trait T3 {} + | ^^^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:18:21 + | +LL | pub impl(in super::d) trait T4 {} + | ^^^^^^^^ + +error[E0433]: too many leading `super` keywords + --> $DIR/restriction_resolution_errors.rs:24:35 + | +LL | pub impl(in super::super::super) trait T7 {} + | ^^^^^ there are too many leading `super` keywords + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:34:21 + | +LL | pub impl(in self::f) trait L1 {} + | ^^^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:38:21 + | +LL | pub impl(in super::h) trait L3 {} + | ^^^^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:47:13 + | +LL | pub impl(in crate::a) trait T13 {} + | ^^^^^^^^ + +error[E0433]: too many leading `super` keywords + --> $DIR/restriction_resolution_errors.rs:54:10 + | +LL | pub impl(super) trait T17 {} + | ^^^^^ there are too many leading `super` keywords + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:57:13 + | +LL | pub impl(in crate::j) trait L4 {} + | ^^^^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:73:21 + | +LL | pub impl(in crate::m2) trait U2 {} + | ^^^^^^^^^ + +error: trait implementation can only be restricted to ancestor modules + --> $DIR/restriction_resolution_errors.rs:75:21 + | +LL | pub impl(in m6::m5) trait U4 {} + | ^^^^^^ + +error[E0433]: cannot find module or crate `a` in this scope + --> $DIR/restriction_resolution_errors.rs:12:21 + | +LL | pub impl(in a::b) trait T1 {} + | ^ use of unresolved module or unlinked crate `a` + | +help: there is a crate or module with a similar name + | +LL - pub impl(in a::b) trait T1 {} +LL + pub impl(in c::b) trait T1 {} + | +help: consider importing this module + | +LL + use a; + | + +error[E0433]: cannot find module `c` in the crate root + --> $DIR/restriction_resolution_errors.rs:20:28 + | +LL | pub impl(in crate::c) trait T5 {} + | ^ not found in the crate root + +error[E0577]: expected module, found enum `super::E` + --> $DIR/restriction_resolution_errors.rs:22:21 + | +LL | pub impl(in super::E) trait T6 {} + | ^^^^^^^^ not a module + +error[E0577]: expected module, found enum `super::G` + --> $DIR/restriction_resolution_errors.rs:36:21 + | +LL | pub impl(in super::G) trait L2 {} + | ^^^^^^^^ not a module + +error[E0577]: expected module, found enum `crate::a::E` + --> $DIR/restriction_resolution_errors.rs:49:13 + | +LL | pub mod b { + | --------- similarly named module `b` defined here +... +LL | pub impl(in crate::a::E) trait T14 {} + | ^^^^^^^^^^^ + | +help: a module with a similar name exists + | +LL - pub impl(in crate::a::E) trait T14 {} +LL + pub impl(in crate::a::b) trait T14 {} + | + +error[E0577]: expected module, found enum `crate::I` + --> $DIR/restriction_resolution_errors.rs:59:13 + | +LL | pub mod a { + | --------- similarly named module `a` defined here +... +LL | pub impl(in crate::I) trait L5 {} + | ^^^^^^^^ + | +help: a module with a similar name exists + | +LL - pub impl(in crate::I) trait L5 {} +LL + pub impl(in crate::a) trait L5 {} + | + +error[E0577]: expected module, found enum `m7` + --> $DIR/restriction_resolution_errors.rs:76:21 + | +LL | pub impl(in m7) trait U5 {} + | ^^ not a module + +error: aborting due to 18 previous errors + +Some errors have detailed explanations: E0433, E0577. +For more information about an error, try `rustc --explain E0433`. From 082cdf7c48245088b3384e8a573e60b8dda14d9d Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 10:27:40 +0000 Subject: [PATCH 09/11] Preserve braces around `self` in use tree pretty printing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AST pretty printer strips braces from single-item `use` sub-groups, simplifying `use foo::{Bar}` to `use foo::Bar`. However, when the single item is `self`, this produces `use foo::self` which is not valid Rust (E0429) — the grammar requires `use foo::{self}`. This affects both `stringify!` and `rustc -Zunpretty=expanded`, causing `cargo expand` output to be unparseable when a crate uses `use path::{self}` imports (a common pattern in the ecosystem). The fix checks whether the single nested item's path starts with `self` before stripping braces. If so, the braces are preserved. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state/item.rs | 8 +++++++- tests/pretty/use-self-braces.rs | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/pretty/use-self-braces.rs diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 3407feb3dcc3a..e997fdf498202 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -881,7 +881,13 @@ impl<'a> State<'a> { } if items.is_empty() { self.word("{}"); - } else if let [(item, _)] = items.as_slice() { + } else if let [(item, _)] = items.as_slice() + && !item + .prefix + .segments + .first() + .is_some_and(|seg| seg.ident.name == rustc_span::symbol::kw::SelfLower) + { self.print_use_tree(item); } else { let cb = self.cbox(INDENT_UNIT); diff --git a/tests/pretty/use-self-braces.rs b/tests/pretty/use-self-braces.rs new file mode 100644 index 0000000000000..8db2568943616 --- /dev/null +++ b/tests/pretty/use-self-braces.rs @@ -0,0 +1,10 @@ +//@ pp-exact +//@ edition:2021 + +#![allow(unused_imports)] + +// Braces around `self` must be preserved, because `use foo::self` is not valid Rust. +use std::io::{self}; +use std::fmt::{self, Debug}; + +fn main() {} From c77768590dbc2e63acf853ab86177de3fb4e5bfb Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 10:27:40 +0000 Subject: [PATCH 10/11] Fix whitespace after fragment specifiers in macro pretty printing When a macro-generating-macro captures fragment specifier tokens (like `$x:ident`) as `tt` metavariables and replays them before a keyword (like `where`), the pretty printer concatenates them into an invalid fragment specifier (e.g. `$x:identwhere` instead of `$x:ident where`). This happens because `tt` captures preserve the original token spacing. When the fragment specifier name (e.g. `ident`) was originally the last token before a closing delimiter, it retains `JointHidden` spacing. The `print_tts` function only checks `space_between` for `Spacing::Alone` tokens, so `JointHidden` tokens skip the space check entirely, causing adjacent identifier-like tokens to merge. The fix adds a check in `print_tts` to insert a space between adjacent identifier-like tokens (identifiers and keywords) regardless of the original spacing, preventing them from being concatenated into invalid tokens. This is similar to the existing `space_between` mechanism that prevents token merging for `Spacing::Alone` tokens, extended to also handle `Joint`/`JointHidden` cases where two identifier-like tokens would merge. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state.rs | 20 ++++++++++++++++ .../macro-fragment-specifier-whitespace.pp | 24 +++++++++++++++++++ .../macro-fragment-specifier-whitespace.rs | 19 +++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 tests/pretty/macro-fragment-specifier-whitespace.pp create mode 100644 tests/pretty/macro-fragment-specifier-whitespace.rs diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 4ba5dc541342a..2ff730e3331c8 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -329,6 +329,19 @@ fn print_crate_inner<'a>( /// - #63896: `#[allow(unused,` must be printed rather than `#[allow(unused ,` /// - #73345: `#[allow(unused)]` must be printed rather than `# [allow(unused)]` /// +/// Returns `true` if both token trees are identifier-like tokens that would +/// merge into a single token if printed without a space between them. +/// E.g. `ident` + `where` would merge into `identwhere`. +fn idents_would_merge(tt1: &TokenTree, tt2: &TokenTree) -> bool { + fn is_ident_like(tt: &TokenTree) -> bool { + matches!( + tt, + TokenTree::Token(Token { kind: token::Ident(..) | token::NtIdent(..), .. }, _,) + ) + } + is_ident_like(tt1) && is_ident_like(tt2) +} + fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool { use Delimiter::*; use TokenTree::{Delimited as Del, Token as Tok}; @@ -794,6 +807,13 @@ pub trait PrintState<'a>: std::ops::Deref + std::ops::Dere if let Some(next) = iter.peek() { if spacing == Spacing::Alone && space_between(tt, next) { self.space(); + } else if spacing != Spacing::Alone && idents_would_merge(tt, next) { + // When tokens from macro `tt` captures preserve their + // original `Joint`/`JointHidden` spacing, adjacent + // identifier-like tokens can be concatenated without a + // space (e.g. `$x:identwhere`). Insert a space to + // prevent this. + self.space(); } } } diff --git a/tests/pretty/macro-fragment-specifier-whitespace.pp b/tests/pretty/macro-fragment-specifier-whitespace.pp new file mode 100644 index 0000000000000..ee5a0f7a7c056 --- /dev/null +++ b/tests/pretty/macro-fragment-specifier-whitespace.pp @@ -0,0 +1,24 @@ +#![feature(prelude_import)] +#![no_std] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; +//@ pretty-mode:expanded +//@ pp-exact:macro-fragment-specifier-whitespace.pp + +// Test that fragment specifier names in macro definitions are properly +// separated from the following keyword/identifier token when pretty-printed. +// This is a regression test for a bug where `$x:ident` followed by `where` +// was pretty-printed as `$x:identwhere` (an invalid fragment specifier). + +macro_rules! outer { + ($d:tt $($params:tt)*) => + { + #[macro_export] macro_rules! inner + { ($($params)* where $d($rest:tt)*) => {}; } + }; +} +#[macro_export] +macro_rules! inner { ($x:ident where $ ($rest : tt)*) => {}; } + +fn main() {} diff --git a/tests/pretty/macro-fragment-specifier-whitespace.rs b/tests/pretty/macro-fragment-specifier-whitespace.rs new file mode 100644 index 0000000000000..54c6debd9a275 --- /dev/null +++ b/tests/pretty/macro-fragment-specifier-whitespace.rs @@ -0,0 +1,19 @@ +//@ pretty-mode:expanded +//@ pp-exact:macro-fragment-specifier-whitespace.pp + +// Test that fragment specifier names in macro definitions are properly +// separated from the following keyword/identifier token when pretty-printed. +// This is a regression test for a bug where `$x:ident` followed by `where` +// was pretty-printed as `$x:identwhere` (an invalid fragment specifier). + +macro_rules! outer { + ($d:tt $($params:tt)*) => { + #[macro_export] + macro_rules! inner { + ($($params)* where $d($rest:tt)*) => {}; + } + }; +} +outer!($ $x:ident); + +fn main() {} From e9740a4be5cf4e89accacae0bd868557d942d247 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 10:27:40 +0000 Subject: [PATCH 11/11] Insert space after float literal ending with `.` in pretty printer The pretty printer was collapsing unsuffixed float literals (like `0.`) with adjacent dot tokens, producing ambiguous or invalid output: - `0. ..45.` (range) became `0...45.` - `0. ..=360.` (inclusive range) became `0...=360.` - `0. .to_string()` (method call) became `0..to_string()` The fix inserts a space after an unsuffixed float literal ending with `.` when it appears as the start expression of a range operator or the receiver of a method call or field access, preventing the trailing dot from merging with the following `.`, `..`, or `..=` token. This is skipped when the expression is already parenthesized, since the closing `)` naturally provides separation. Signed-off-by: Andrew V. Teylu --- .../rustc_ast_pretty/src/pprust/state/expr.rs | 48 ++++++++++++++----- tests/pretty/float-trailing-dot.rs | 8 ++++ 2 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 tests/pretty/float-trailing-dot.rs diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 9b4ff2b63bd45..0c5928765bfa5 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -260,12 +260,15 @@ impl<'a> State<'a> { // // loop { break x; }.method(); // - self.print_expr_cond_paren( - receiver, - receiver.precedence() < ExprPrecedence::Unambiguous, - fixup.leftmost_subexpression_with_dot(), - ); + let needs_paren = receiver.precedence() < ExprPrecedence::Unambiguous; + self.print_expr_cond_paren(receiver, needs_paren, fixup.leftmost_subexpression_with_dot()); + // If the receiver is an unsuffixed float literal like `0.`, insert + // a space so the `.` of the method call doesn't merge with the + // trailing dot: `0. .method()` instead of `0..method()`. + if !needs_paren && expr_ends_with_dot(receiver) { + self.word(" "); + } self.word("."); self.print_ident(segment.ident); if let Some(args) = &segment.args { @@ -658,11 +661,15 @@ impl<'a> State<'a> { ); } ast::ExprKind::Field(expr, ident) => { + let needs_paren = expr.precedence() < ExprPrecedence::Unambiguous; self.print_expr_cond_paren( expr, - expr.precedence() < ExprPrecedence::Unambiguous, + needs_paren, fixup.leftmost_subexpression_with_dot(), ); + if !needs_paren && expr_ends_with_dot(expr) { + self.word(" "); + } self.word("."); self.print_ident(*ident); } @@ -685,11 +692,15 @@ impl<'a> State<'a> { let fake_prec = ExprPrecedence::LOr; if let Some(e) = start { let start_fixup = fixup.leftmost_subexpression_with_operator(true); - self.print_expr_cond_paren( - e, - start_fixup.precedence(e) < fake_prec, - start_fixup, - ); + let needs_paren = start_fixup.precedence(e) < fake_prec; + self.print_expr_cond_paren(e, needs_paren, start_fixup); + // If the start expression is a float literal ending with + // `.`, we need a space before `..` or `..=` so that the + // dots don't merge. E.g. `0. ..45.` must not become + // `0...45.`. + if !needs_paren && expr_ends_with_dot(e) { + self.word(" "); + } } match limits { ast::RangeLimits::HalfOpen => self.word(".."), @@ -1025,3 +1036,18 @@ fn reconstruct_format_args_template_string(pieces: &[FormatArgsPiece]) -> String template.push('"'); template } + +/// Returns `true` if the printed representation of this expression ends with +/// a `.` character — specifically, an unsuffixed float literal like `0.` or +/// `45.`. This is used to insert whitespace before range operators (`..`, +/// `..=`) so that the dots don't merge (e.g. `0. ..45.` instead of `0...45.`). +fn expr_ends_with_dot(expr: &ast::Expr) -> bool { + match &expr.kind { + ast::ExprKind::Lit(token_lit) => { + token_lit.kind == token::Float + && token_lit.suffix.is_none() + && token_lit.symbol.as_str().ends_with('.') + } + _ => false, + } +} diff --git a/tests/pretty/float-trailing-dot.rs b/tests/pretty/float-trailing-dot.rs new file mode 100644 index 0000000000000..63934bb8eaaa7 --- /dev/null +++ b/tests/pretty/float-trailing-dot.rs @@ -0,0 +1,8 @@ +//@ pp-exact + +fn main() { + let _ = 0. ..45.; + let _ = 0. ..=360.; + let _ = 0. ..; + let _ = 0. .to_string(); +}