From 2e46fb447a8dc1c0fbf0b03c06509fbd65e4b34a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 24 Sep 2026 13:00:28 +0200 Subject: [PATCH 01/18] Merge commit 'ffb7eef504d4d9507583825e8e411d283c4e357e' --- .github/workflows/ci.yml | 4 +++- .github/workflows/stdarch.yml | 6 ++++-- Readme.md | 2 +- build_system/src/abi_test.rs | 2 +- build_system/src/utils.rs | 6 +++--- doc/tips.md | 2 +- rust-toolchain => rust-toolchain.toml | 2 +- 7 files changed, 14 insertions(+), 10 deletions(-) rename rust-toolchain => rust-toolchain.toml (70%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f5cc409e363d..f37123c124c74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,9 @@ jobs: - name: Install packages # `llvm-14-tools` is needed to install the `FileCheck` binary which is used for asm tests. - run: sudo apt-get install ninja-build ripgrep llvm-14-tools llvm + run: | + sudo apt-get update + sudo apt-get install ninja-build ripgrep llvm-14-tools llvm - name: Install the libraries needed to build librsvg if: ${{ contains(matrix.commands, '--projects') }} diff --git a/.github/workflows/stdarch.yml b/.github/workflows/stdarch.yml index 17d6449c85e08..34499c6e7b522 100644 --- a/.github/workflows/stdarch.yml +++ b/.github/workflows/stdarch.yml @@ -96,14 +96,16 @@ jobs: if: ${{ !matrix.cargo_runner }} run: | # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. - ./y.sh test --release --stdarch-tests -- --skip test_tile_ --skip test__tile + # FIXME: remove --skip test_mm_srav_epi64 when it's fixed upstream. + ./y.sh test --release --stdarch-tests -- --skip test_tile_ --skip test__tile --skip test_mm_srav_epi64 - name: Run stdarch tests if: ${{ matrix.cargo_runner }} run: | # FIXME: these tests fail when the sysroot is compiled with LTO because of a missing symbol in proc-macro. # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. - STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ --skip test__tile + # FIXME: remove --skip test_mm_srav_epi64 when it's fixed upstream. + STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ --skip test__tile --skip test_mm_srav_epi64 # Summary job for the merge queue. # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! diff --git a/Readme.md b/Readme.md index 9a7c624c9bc22..14c0e3ad0f21c 100644 --- a/Readme.md +++ b/Readme.md @@ -154,7 +154,7 @@ If you compiled `cg_gcc` in debug mode (aka you didn't pass `--release` to `./y. You can do the same manually (although we don't recommend it): ```bash -$ LIBRARY_PATH="[gcc-path value]" LD_LIBRARY_PATH="[gcc-path value]" rustc +$(cat $CG_GCCJIT_DIR/rust-toolchain | grep 'channel' | cut -d '=' -f 2 | sed 's/"//g' | sed 's/ //g') -Cpanic=abort -Zcodegen-backend=$CG_GCCJIT_DIR/target/release/librustc_codegen_gcc.so --sysroot $CG_GCCJIT_DIR/build_sysroot/sysroot my_crate.rs +$ LIBRARY_PATH="[gcc-path value]" LD_LIBRARY_PATH="[gcc-path value]" rustc +$(cat $CG_GCCJIT_DIR/rust-toolchain.toml | grep 'channel' | cut -d '=' -f 2 | sed 's/"//g' | sed 's/ //g') -Cpanic=abort -Zcodegen-backend=$CG_GCCJIT_DIR/target/release/librustc_codegen_gcc.so --sysroot $CG_GCCJIT_DIR/build_sysroot/sysroot my_crate.rs ``` ## Environment variables diff --git a/build_system/src/abi_test.rs b/build_system/src/abi_test.rs index a85886d87f365..9fc06dc8dbc04 100644 --- a/build_system/src/abi_test.rs +++ b/build_system/src/abi_test.rs @@ -34,7 +34,7 @@ pub fn run() -> Result<(), String> { .map_err(|err| format!("Git clone failed with message: {err:?}!"))?; // Configure abi-cafe to use the exact same rustc version we use - this is crucial. // Otherwise, the concept of ABI compatibility becomes meanignless. - std::fs::copy("rust-toolchain", "clones/abi-cafe/rust-toolchain") + std::fs::copy("rust-toolchain.toml", "clones/abi-cafe/rust-toolchain.toml") .expect("Could not copy toolchain configs!"); // Get the backend path. // We will use the *debug* build of the backend - it has more checks enabled. diff --git a/build_system/src/utils.rs b/build_system/src/utils.rs index 4c67156a85fb2..dda44e4d16d41 100644 --- a/build_system/src/utils.rs +++ b/build_system/src/utils.rs @@ -242,9 +242,9 @@ fn rustc_version_info_inner( } pub fn get_toolchain() -> Result { - let content = match fs::read_to_string("rust-toolchain") { + let content = match fs::read_to_string("rust-toolchain.toml") { Ok(content) => content, - Err(_) => return Err("No `rust-toolchain` file found".to_string()), + Err(_) => return Err("No `rust-toolchain.toml` file found".to_string()), }; match content .split('\n') @@ -259,7 +259,7 @@ pub fn get_toolchain() -> Result { .next() { Some(toolchain) => Ok(toolchain.to_string()), - None => Err("Couldn't find `channel` in `rust-toolchain` file".to_string()), + None => Err("Couldn't find `channel` in `rust-toolchain.toml` file".to_string()), } } diff --git a/doc/tips.md b/doc/tips.md index dc40ee4d39952..28964f66403bf 100644 --- a/doc/tips.md +++ b/doc/tips.md @@ -41,7 +41,7 @@ COLLECT_NO_DEMANGLE=1 ### How to use a custom-build rustc * Build the stage2 compiler (`rustup toolchain link debug-current build/x86_64-unknown-linux-gnu/stage2`). - * Clean and rebuild the codegen with `debug-current` in the file `rust-toolchain`. + * Clean and rebuild the codegen with `debug-current` in the file `rust-toolchain.toml`. ### How to use a custom sysroot source path diff --git a/rust-toolchain b/rust-toolchain.toml similarity index 70% rename from rust-toolchain rename to rust-toolchain.toml index 0c81f7c7c7398..a7774784e3afa 100644 --- a/rust-toolchain +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-09-18" +channel = "nightly-2026-09-24" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] From 1ff212ebb3ce2fbca5228c1f09929a77323ecda9 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Thu, 24 Sep 2026 17:48:28 +0300 Subject: [PATCH 02/18] Support -Z merge-functions with gcc and add stack-protector asm tests This is a part of the stack-protector work. Adding support for merge-functions is not strictly needed, but it does prevent breakage if the GCC function merging logic changes. Not adding tests for -Z merge-functions since I don't see any such tests for the LLVM side of it. --- src/gcc_util.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gcc_util.rs b/src/gcc_util.rs index 0628171e488b3..4850a5fc5ca31 100644 --- a/src/gcc_util.rs +++ b/src/gcc_util.rs @@ -9,7 +9,9 @@ use rustc_codegen_ssa::target_features; use rustc_data_structures::smallvec::{SmallVec, smallvec}; use rustc_session::config::NATIVE_CPU; use rustc_session::{EarlySession, Session}; -use rustc_target::spec::{Arch, RelocModel, StackProbeType, StackProtector, Target}; +use rustc_target::spec::{ + Arch, MergeFunctions, RelocModel, StackProbeType, StackProtector, Target, +}; fn gcc_features_by_flags(sess: &EarlySession, features: &mut Vec) { target_features::retpoline_features_by_flags(sess, features); @@ -220,6 +222,13 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { StackProtector::None => (), } + match sess.merge_functions() { + MergeFunctions::Disabled | MergeFunctions::Trampolines => { + context.add_command_line_option("-fno-ipa-icf-functions"); + } + MergeFunctions::Aliases => {} + } + match sess.target.stack_probes { StackProbeType::None => (), StackProbeType::Inline | StackProbeType::InlineOrCall { .. } => { From 26c112b79ccd4d8a06996ebb32c5af3b927f1aba Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Thu, 24 Sep 2026 22:01:39 +0300 Subject: [PATCH 03/18] address comments by bjorn3 --- src/gcc_util.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gcc_util.rs b/src/gcc_util.rs index 4850a5fc5ca31..90d08d13a080a 100644 --- a/src/gcc_util.rs +++ b/src/gcc_util.rs @@ -223,10 +223,13 @@ pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { } match sess.merge_functions() { - MergeFunctions::Disabled | MergeFunctions::Trampolines => { + MergeFunctions::Disabled => { context.add_command_line_option("-fno-ipa-icf-functions"); } - MergeFunctions::Aliases => {} + // GCC always merges functions with trampolines rather than aliases, so we don't + // need to differentiate MergeFunctions::Trampolines and MergeFunctions::Aliases + // as trampolines are allowed either way. + MergeFunctions::Trampolines | MergeFunctions::Aliases => {} } match sess.target.stack_probes { From bab2d8322dedfd57844eed94a1e64561ec1ad479 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 24 Sep 2026 12:36:06 +0200 Subject: [PATCH 04/18] compiler: remove rustc_hir attr re-exports --- src/attributes.rs | 4 ++-- src/base.rs | 3 +-- src/callee.rs | 2 +- src/consts.rs | 2 +- src/lib.rs | 1 + src/mono_item.rs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/attributes.rs b/src/attributes.rs index 41db5e83bdcc9..2e9bf42874148 100644 --- a/src/attributes.rs +++ b/src/attributes.rs @@ -4,8 +4,8 @@ use gccjit::Function; #[cfg(feature = "master")] use rustc_abi::{CanonAbi, InterruptKind}; #[cfg(feature = "master")] -use rustc_hir::attrs::InlineAttr; -use rustc_hir::attrs::InstructionSetAttr; +use rustc_attr_ir::InlineAttr; +use rustc_attr_ir::InstructionSetAttr; #[cfg(feature = "master")] use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; #[cfg(feature = "master")] diff --git a/src/base.rs b/src/base.rs index 0dd46558ed796..07214096b3e49 100644 --- a/src/base.rs +++ b/src/base.rs @@ -4,11 +4,10 @@ use std::time::Instant; #[cfg(feature = "master")] use gccjit::VarAttribute; use gccjit::{CType, FunctionType, GlobalKind}; +use rustc_attr_ir::{AttributeKind, Linkage, find_attr}; use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; -use rustc_hir::attrs::{AttributeKind, Linkage}; -use rustc_hir::find_attr; use rustc_middle::dep_graph; #[cfg(feature = "master")] use rustc_middle::mono::Visibility; diff --git a/src/callee.rs b/src/callee.rs index d3f412180da55..21a0bbb4030bb 100644 --- a/src/callee.rs +++ b/src/callee.rs @@ -106,7 +106,7 @@ pub fn get_fn<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, instance: Instance<'tcx>) // This is a monomorphization of a generic function. if !(cx.tcx.sess.opts.share_generics() || tcx.codegen_instance_attrs(instance.def).inline - == rustc_hir::attrs::InlineAttr::Never) + == rustc_attr_ir::InlineAttr::Never) { // When not sharing generics, all instances are in the same // crate and have hidden visibility. diff --git a/src/consts.rs b/src/consts.rs index 8576dfe079163..c58ab6d2a9bef 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,10 +4,10 @@ use std::ops::Range; use gccjit::{FnAttribute, VarAttribute, Visibility}; use gccjit::{Function, GlobalKind, LValue, RValue, ToRValue, Type}; use rustc_abi::{self as abi, Align, HasDataLayout, Primitive, Size, WrappingRange}; +use rustc_attr_ir::Linkage; use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods, }; -use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::LOCAL_CRATE; use rustc_log::tracing::trace; diff --git a/src/lib.rs b/src/lib.rs index 7abbda4a37dc1..f30004c8561f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ extern crate rustc_abi; extern crate rustc_apfloat; extern crate rustc_ast; +extern crate rustc_attr_ir; extern crate rustc_codegen_ssa; extern crate rustc_data_structures; extern crate rustc_errors; diff --git a/src/mono_item.rs b/src/mono_item.rs index d8170fbb085a7..9aa015658fe42 100644 --- a/src/mono_item.rs +++ b/src/mono_item.rs @@ -1,8 +1,8 @@ use gccjit::Function; #[cfg(feature = "master")] use gccjit::{FnAttribute, GlobalKind, ToRValue, Type, VarAttribute}; +use rustc_attr_ir::Linkage; use rustc_codegen_ssa::traits::PreDefineCodegenMethods; -use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; From a048dfd2675aa01b49851d10571b1dd26ed78884 Mon Sep 17 00:00:00 2001 From: Jieyou Xu Date: Tue, 29 Sep 2026 19:05:46 +0800 Subject: [PATCH 05/18] chore: use r-l/r zulip linkifier for r-l/r rustfmt backport nominations --- triagebot.toml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/triagebot.toml b/triagebot.toml index bad75dc51d740..611f7724f5cae 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -952,53 +952,53 @@ message_on_add = "PR #{number} has been **accepted** for **stable** backport." [notify-zulip."beta-nominated".rustfmt] required_labels = ["T-rustfmt"] zulip_stream = 621384 # #t-rustfmt/backports -topic = "#{number}: beta-nominated" +topic = "rust#{number}: beta-nominated" message_on_add = [ """\ -@*T-rustfmt* @*T-rustfmt-contributors* PR #{number} "{title}" has been nominated +@*T-rustfmt* @*T-rustfmt-contributors* PR rust#{number} "{title}" has been nominated for beta backport. """, """\ -/poll Approve beta backport of #{number}? +/poll Approve beta backport of rust#{number}? approve decline don't know """, ] -message_on_remove = "PR #{number}'s beta-nomination has been removed." +message_on_remove = "PR rust#{number}'s beta-nomination has been removed." [notify-zulip."beta-accepted".rustfmt] required_labels = ["T-rustfmt"] zulip_stream = 621384 # #t-rustfmt/backports # Put it in the same thread as beta-nominated. -topic = "#{number}: beta-nominated" -message_on_add = "PR #{number} has been **accepted** for **beta** backport." +topic = "rust#{number}: beta-nominated" +message_on_add = "PR rust#{number} has been **accepted** for **beta** backport." [notify-zulip."stable-nominated".rustfmt] required_labels = ["T-rustfmt"] zulip_stream = 621384 # #t-rustfmt/backports -topic = "#{number}: stable-nominated" +topic = "rust#{number}: stable-nominated" message_on_add = [ """\ -@*T-rustfmt* @*T-rustfmt-contributors* PR #{number} "{title}" has been nominated +@*T-rustfmt* @*T-rustfmt-contributors* PR rust#{number} "{title}" has been nominated for stable backport. """, """\ -/poll Approve stable backport of #{number}? +/poll Approve stable backport of rust#{number}? approve approve (but does not justify new dot release on its own) decline don't know """, ] -message_on_remove = "PR #{number}'s stable-nomination has been removed." +message_on_remove = "PR rust#{number}'s stable-nomination has been removed." [notify-zulip."stable-accepted".rustfmt] required_labels = ["T-rustfmt"] zulip_stream = 621384 # #t-rustfmt/backports # Put it in the same thread as stable-nominated. -topic = "#{number}: stable-nominated" -message_on_add = "PR #{number} has been **accepted** for **stable** backport." +topic = "rust#{number}: stable-nominated" +message_on_add = "PR rust#{number} has been **accepted** for **stable** backport." [notify-zulip."A-edition-2021"] From e054cb5bb68db7af6b38ce5457495cacf58885d1 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 29 Sep 2026 11:12:52 -0400 Subject: [PATCH 06/18] Update gccjit.rs version --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c174628d0d188..6467f1a8c3763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,9 +56,9 @@ dependencies = [ [[package]] name = "gccjit" -version = "6.1.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d85b5754389edaad832ba320709a25086b3081a8c6c0fab2322965e5fb512b3" +checksum = "dfdb84ba432f6679794790de652bfbed86201893a30c03dc669c2bfe7424f472" dependencies = [ "gccjit_sys", ] diff --git a/Cargo.toml b/Cargo.toml index 02be6d56c2310..35046980f0695 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "6.1.0", features = ["dlopen"] } +gccjit = { version = "7.0.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/src/lib.rs b/src/lib.rs index 7abbda4a37dc1..4ce332fed5501 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -236,7 +236,7 @@ impl CodegenBackend for GccCodegenBackend { lto_supported: gccjit::is_lto_supported(), }); - gccjit::set_global_personality_function_name(b"rust_eh_personality\0"); + gccjit::set_global_personality_function_name(c"rust_eh_personality"); } #[cfg(not(feature = "master"))] From 768500b03ce921cb18853761304d41a1ec24822f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 27 Sep 2026 20:42:04 +0000 Subject: [PATCH 07/18] Add support for span context to `derive(Diagnostic)` Allow writing ```rust struct Diagnostic { #[primary_span] #[label("label text")] span: Span, #[context] context: Span, } ``` So that the code pointed at by `context` is shown in the rendered diagnostic without an underline. --- .../src/diagnostics/diagnostic_builder.rs | 7 +- compiler/rustc_macros/src/lib.rs | 1 + .../diagnostic-derive-inline.rs | 3 + .../diagnostic-derive-inline.stderr | 128 +++++++++--------- .../diagnostic-derive-pass.rs | 38 ++++++ 5 files changed, 115 insertions(+), 62 deletions(-) create mode 100644 tests/ui-fulldeps/session-diagnostic/diagnostic-derive-pass.rs diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs index cdff1280d69f9..f071028e5937d 100644 --- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs +++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs @@ -347,11 +347,16 @@ impl DiagnosticDeriveVariantBuilder { match (&attr.meta, name.as_str()) { (Meta::Path(_), "primary_span") => { report_error_if_not_applied_to_span(attr, &info)?; - return Ok(quote! { diag.span(#binding); }); } + (Meta::Path(_), "context") => { + report_error_if_not_applied_to_span(attr, &info)?; + return Ok(quote! { + diag.span_context(#binding); + }); + } (Meta::Path(_), "subdiagnostic") => { return Ok(quote! { diag.subdiagnostic(#binding); }); } diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index f632862dc4627..1cdb4e125aab1 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -190,6 +190,7 @@ decl_derive!( // field attributes primary_span, label, + context, subdiagnostic, suggestion)] => #[doc = "See "] diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs index 0956556bf5ef2..2351cda7b2195 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.rs @@ -130,6 +130,9 @@ struct MessageWrongType { #[primary_span] //~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan` foo: String, + #[context] + //~^ ERROR `#[context]` attribute can only be applied to fields of type `Span` or `MultiSpan` + bar: String, } #[derive(Diagnostic)] diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr index f077dc1221ced..0486ac91de8f5 100644 --- a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-inline.stderr @@ -138,26 +138,32 @@ error: derive(Diagnostic): the `#[primary_span]` attribute can only be applied t LL | #[primary_span] | ^ +error: derive(Diagnostic): the `#[context]` attribute can only be applied to fields of type `Span` or `MultiSpan` + --> $DIR/diagnostic-derive-inline.rs:133:5 + | +LL | #[context] + | ^ + error: derive(Diagnostic): `#[nonsense]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:138:5 + --> $DIR/diagnostic-derive-inline.rs:141:5 | LL | #[nonsense] | ^ error: derive(Diagnostic): the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` - --> $DIR/diagnostic-derive-inline.rs:155:5 + --> $DIR/diagnostic-derive-inline.rs:158:5 | LL | #[label("with a label")] | ^ error: derive(Diagnostic): `name` doesn't refer to a field on this type - --> $DIR/diagnostic-derive-inline.rs:163:46 + --> $DIR/diagnostic-derive-inline.rs:166:46 | LL | #[suggestion("with a suggestion", code = "{name}")] | ^^^^^^^^ error: invalid format string: expected `}` but string was terminated - --> $DIR/diagnostic-derive-inline.rs:168:10 + --> $DIR/diagnostic-derive-inline.rs:171:10 | LL | #[derive(Diagnostic)] | ^^^^^^^^^^ expected `}` in format string @@ -166,7 +172,7 @@ LL | #[derive(Diagnostic)] = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid format string: unmatched `}` found - --> $DIR/diagnostic-derive-inline.rs:178:10 + --> $DIR/diagnostic-derive-inline.rs:181:10 | LL | #[derive(Diagnostic)] | ^^^^^^^^^^ unmatched `}` in format string @@ -175,19 +181,19 @@ LL | #[derive(Diagnostic)] = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info) error: derive(Diagnostic): the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan` - --> $DIR/diagnostic-derive-inline.rs:198:5 + --> $DIR/diagnostic-derive-inline.rs:201:5 | LL | #[label("with a label")] | ^ error: derive(Diagnostic): suggestion without `code = "..."` - --> $DIR/diagnostic-derive-inline.rs:217:5 + --> $DIR/diagnostic-derive-inline.rs:220:5 | LL | #[suggestion("with a suggestion")] | ^ error: derive(Diagnostic): invalid nested attribute - --> $DIR/diagnostic-derive-inline.rs:225:39 + --> $DIR/diagnostic-derive-inline.rs:228:39 | LL | #[suggestion("with a suggestion", nonsense = "bar")] | ^^^^^^^^ @@ -195,13 +201,13 @@ LL | #[suggestion("with a suggestion", nonsense = "bar")] = help: only `style`, `code` and `applicability` are valid nested attributes error: derive(Diagnostic): suggestion without `code = "..."` - --> $DIR/diagnostic-derive-inline.rs:225:5 + --> $DIR/diagnostic-derive-inline.rs:228:5 | LL | #[suggestion("with a suggestion", nonsense = "bar")] | ^ error: derive(Diagnostic): invalid nested attribute - --> $DIR/diagnostic-derive-inline.rs:234:39 + --> $DIR/diagnostic-derive-inline.rs:237:39 | LL | #[suggestion("with a suggestion", msg = "bar")] | ^^^ @@ -209,13 +215,13 @@ LL | #[suggestion("with a suggestion", msg = "bar")] = help: only `style`, `code` and `applicability` are valid nested attributes error: derive(Diagnostic): suggestion without `code = "..."` - --> $DIR/diagnostic-derive-inline.rs:234:5 + --> $DIR/diagnostic-derive-inline.rs:237:5 | LL | #[suggestion("with a suggestion", msg = "bar")] | ^ error: derive(Diagnostic): wrong field type for suggestion - --> $DIR/diagnostic-derive-inline.rs:257:5 + --> $DIR/diagnostic-derive-inline.rs:260:5 | LL | #[suggestion("with a suggestion", code = "This is suggested code")] | ^ @@ -223,91 +229,91 @@ LL | #[suggestion("with a suggestion", code = "This is suggested code")] = help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)` error: derive(Diagnostic): attribute specified multiple times - --> $DIR/diagnostic-derive-inline.rs:273:24 + --> $DIR/diagnostic-derive-inline.rs:276:24 | LL | suggestion: (Span, Span, Applicability), | ^^^^ | note: previously specified here - --> $DIR/diagnostic-derive-inline.rs:273:18 + --> $DIR/diagnostic-derive-inline.rs:276:18 | LL | suggestion: (Span, Span, Applicability), | ^^^^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/diagnostic-derive-inline.rs:281:33 + --> $DIR/diagnostic-derive-inline.rs:284:33 | LL | suggestion: (Applicability, Applicability, Span), | ^^^^^^^^^^^^^ | note: previously specified here - --> $DIR/diagnostic-derive-inline.rs:281:18 + --> $DIR/diagnostic-derive-inline.rs:284:18 | LL | suggestion: (Applicability, Applicability, Span), | ^^^^^^^^^^^^^ error: derive(Diagnostic): `#[label = ...]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:288:5 + --> $DIR/diagnostic-derive-inline.rs:291:5 | LL | #[label = "bar"] | ^ error: derive(Diagnostic): attribute specified multiple times - --> $DIR/diagnostic-derive-inline.rs:387:5 + --> $DIR/diagnostic-derive-inline.rs:390:5 | LL | #[suggestion("with a suggestion", code = "...", applicability = "maybe-incorrect")] | ^ | note: previously specified here - --> $DIR/diagnostic-derive-inline.rs:389:24 + --> $DIR/diagnostic-derive-inline.rs:392:24 | LL | suggestion: (Span, Applicability), | ^^^^^^^^^^^^^ error: derive(Diagnostic): invalid applicability - --> $DIR/diagnostic-derive-inline.rs:395:69 + --> $DIR/diagnostic-derive-inline.rs:398:69 | LL | #[suggestion("with a suggestion", code = "...", applicability = "batman")] | ^^^^^^^^ error: derive(Diagnostic): the `#[help(...)]` attribute can only be applied to fields of type `Span`, `MultiSpan`, `bool` or `()` - --> $DIR/diagnostic-derive-inline.rs:458:5 + --> $DIR/diagnostic-derive-inline.rs:461:5 | LL | #[help("with a help")] | ^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/diagnostic-derive-inline.rs:467:29 + --> $DIR/diagnostic-derive-inline.rs:470:29 | LL | #[label("with a label", foo)] | ^^^ error: derive(Diagnostic): a diagnostic message must be the first argument to the attribute - --> $DIR/diagnostic-derive-inline.rs:475:29 + --> $DIR/diagnostic-derive-inline.rs:478:29 | LL | #[label("with a label", "and another one?")] | ^^^^^^^^^^^^^^^^^^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/diagnostic-derive-inline.rs:483:29 + --> $DIR/diagnostic-derive-inline.rs:486:29 | LL | #[label("with a label", foo = "...")] | ^^^ error: derive(Diagnostic): no nested attribute expected here - --> $DIR/diagnostic-derive-inline.rs:491:29 + --> $DIR/diagnostic-derive-inline.rs:494:29 | LL | #[label("with a label", foo("..."))] | ^^^ error: derive(Diagnostic): `#[error(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:511:1 + --> $DIR/diagnostic-derive-inline.rs:514:1 | LL | #[error("this is an example message", code = E0123)] | ^ error: derive(Diagnostic): diagnostic message not specified - --> $DIR/diagnostic-derive-inline.rs:511:1 + --> $DIR/diagnostic-derive-inline.rs:514:1 | LL | #[error("this is an example message", code = E0123)] | ^ @@ -315,13 +321,13 @@ LL | #[error("this is an example message", code = E0123)] = help: specify the message as the first argument to the `#[diag(...)]` attribute, such as `#[diag("Example error")]` error: derive(Diagnostic): `#[warn_(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:518:1 + --> $DIR/diagnostic-derive-inline.rs:521:1 | LL | #[warn_("this is an example message", code = E0123)] | ^ error: derive(Diagnostic): diagnostic message not specified - --> $DIR/diagnostic-derive-inline.rs:518:1 + --> $DIR/diagnostic-derive-inline.rs:521:1 | LL | #[warn_("this is an example message", code = E0123)] | ^ @@ -329,13 +335,13 @@ LL | #[warn_("this is an example message", code = E0123)] = help: specify the message as the first argument to the `#[diag(...)]` attribute, such as `#[diag("Example error")]` error: derive(Diagnostic): `#[lint(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:525:1 + --> $DIR/diagnostic-derive-inline.rs:528:1 | LL | #[lint("this is an example message", code = E0123)] | ^ error: derive(Diagnostic): diagnostic message not specified - --> $DIR/diagnostic-derive-inline.rs:525:1 + --> $DIR/diagnostic-derive-inline.rs:528:1 | LL | #[lint("this is an example message", code = E0123)] | ^ @@ -343,19 +349,19 @@ LL | #[lint("this is an example message", code = E0123)] = help: specify the message as the first argument to the `#[diag(...)]` attribute, such as `#[diag("Example error")]` error: derive(Diagnostic): attribute specified multiple times - --> $DIR/diagnostic-derive-inline.rs:534:53 + --> $DIR/diagnostic-derive-inline.rs:537:53 | LL | #[suggestion("with a suggestion", code = "...", code = ",,,")] | ^^^^ | note: previously specified here - --> $DIR/diagnostic-derive-inline.rs:534:39 + --> $DIR/diagnostic-derive-inline.rs:537:39 | LL | #[suggestion("with a suggestion", code = "...", code = ",,,")] | ^^^^ error: derive(Diagnostic): wrong types for suggestion - --> $DIR/diagnostic-derive-inline.rs:543:24 + --> $DIR/diagnostic-derive-inline.rs:546:24 | LL | suggestion: (Span, usize), | ^^^^^ @@ -363,7 +369,7 @@ LL | suggestion: (Span, usize), = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)` error: derive(Diagnostic): wrong types for suggestion - --> $DIR/diagnostic-derive-inline.rs:551:17 + --> $DIR/diagnostic-derive-inline.rs:554:17 | LL | suggestion: (Span,), | ^^^^^^^ @@ -371,13 +377,13 @@ LL | suggestion: (Span,), = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)` error: derive(Diagnostic): suggestion without `code = "..."` - --> $DIR/diagnostic-derive-inline.rs:558:5 + --> $DIR/diagnostic-derive-inline.rs:561:5 | LL | #[suggestion("with a suggestion")] | ^ error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:565:1 + --> $DIR/diagnostic-derive-inline.rs:568:1 | LL | #[multipart_suggestion("with a suggestion")] | ^ @@ -385,7 +391,7 @@ LL | #[multipart_suggestion("with a suggestion")] = help: consider creating a `Subdiagnostic` instead error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:568:1 + --> $DIR/diagnostic-derive-inline.rs:571:1 | LL | #[multipart_suggestion()] | ^ @@ -393,7 +399,7 @@ LL | #[multipart_suggestion()] = help: consider creating a `Subdiagnostic` instead error: derive(Diagnostic): `#[multipart_suggestion(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:572:5 + --> $DIR/diagnostic-derive-inline.rs:575:5 | LL | #[multipart_suggestion("with a suggestion")] | ^ @@ -401,7 +407,7 @@ LL | #[multipart_suggestion("with a suggestion")] = help: consider creating a `Subdiagnostic` instead error: derive(Diagnostic): `#[suggestion(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:580:1 + --> $DIR/diagnostic-derive-inline.rs:583:1 | LL | #[suggestion("with a suggestion", code = "...")] | ^ @@ -409,7 +415,7 @@ LL | #[suggestion("with a suggestion", code = "...")] = help: `#[label]` and `#[suggestion]` can only be applied to fields error: derive(Diagnostic): `#[label]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:589:1 + --> $DIR/diagnostic-derive-inline.rs:592:1 | LL | #[label] | ^ @@ -417,67 +423,67 @@ LL | #[label] = help: subdiagnostic message is missing error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:623:5 + --> $DIR/diagnostic-derive-inline.rs:626:5 | LL | #[subdiagnostic(bad)] | ^ error: derive(Diagnostic): `#[subdiagnostic = ...]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:631:5 + --> $DIR/diagnostic-derive-inline.rs:634:5 | LL | #[subdiagnostic = "bad"] | ^ error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:639:5 + --> $DIR/diagnostic-derive-inline.rs:642:5 | LL | #[subdiagnostic(bad, bad)] | ^ error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:647:5 + --> $DIR/diagnostic-derive-inline.rs:650:5 | LL | #[subdiagnostic("bad")] | ^ error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:655:5 + --> $DIR/diagnostic-derive-inline.rs:658:5 | LL | #[subdiagnostic(eager)] | ^ error: derive(Diagnostic): `#[subdiagnostic(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:676:5 + --> $DIR/diagnostic-derive-inline.rs:679:5 | LL | #[subdiagnostic(eager)] | ^ error: derive(Diagnostic): expected at least one string literal for `code(...)` - --> $DIR/diagnostic-derive-inline.rs:707:44 + --> $DIR/diagnostic-derive-inline.rs:710:44 | LL | #[suggestion("with a suggestion", code())] | ^ error: derive(Diagnostic): `code(...)` must contain only string literals - --> $DIR/diagnostic-derive-inline.rs:715:44 + --> $DIR/diagnostic-derive-inline.rs:718:44 | LL | #[suggestion("with a suggestion", code(foo))] | ^^^ error: unexpected token, expected `)` - --> $DIR/diagnostic-derive-inline.rs:715:44 + --> $DIR/diagnostic-derive-inline.rs:718:44 | LL | #[suggestion("with a suggestion", code(foo))] | ^^^ error: expected string literal - --> $DIR/diagnostic-derive-inline.rs:724:46 + --> $DIR/diagnostic-derive-inline.rs:727:46 | LL | #[suggestion("with a suggestion", code = 3)] | ^ error: derive(Diagnostic): `#[suggestion(...)]` is not a valid attribute - --> $DIR/diagnostic-derive-inline.rs:739:5 + --> $DIR/diagnostic-derive-inline.rs:742:5 | LL | #[suggestion("with a suggestion", code = "")] | ^ @@ -487,7 +493,7 @@ LL | #[suggestion("with a suggestion", code = "")] = help: to show a variable set of suggestions, use a `Vec` of `Subdiagnostic`s annotated with `#[suggestion(...)]` error: derive(Diagnostic): Variable `nosub` not found in diagnostic - --> $DIR/diagnostic-derive-inline.rs:751:8 + --> $DIR/diagnostic-derive-inline.rs:754:8 | LL | #[diag("does not exist: {$nosub}")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -501,13 +507,13 @@ LL | #[nonsense("this is an example message", code = E0123)] | ^^^^^^^^ error: cannot find attribute `nonsense` in this scope - --> $DIR/diagnostic-derive-inline.rs:138:7 + --> $DIR/diagnostic-derive-inline.rs:141:7 | LL | #[nonsense] | ^^^^^^^^ error: cannot find attribute `error` in this scope - --> $DIR/diagnostic-derive-inline.rs:511:3 + --> $DIR/diagnostic-derive-inline.rs:514:3 | LL | #[error("this is an example message", code = E0123)] | ^^^^^ @@ -519,7 +525,7 @@ LL | struct ErrorAttribute {} | error: cannot find attribute `warn_` in this scope - --> $DIR/diagnostic-derive-inline.rs:518:3 + --> $DIR/diagnostic-derive-inline.rs:521:3 | LL | #[warn_("this is an example message", code = E0123)] | ^^^^^ @@ -531,7 +537,7 @@ LL + #[warn("this is an example message", code = E0123)] | error: cannot find attribute `lint` in this scope - --> $DIR/diagnostic-derive-inline.rs:525:3 + --> $DIR/diagnostic-derive-inline.rs:528:3 | LL | #[lint("this is an example message", code = E0123)] | ^^^^ @@ -548,7 +554,7 @@ LL + #[lift("this is an example message", code = E0123)] | error: cannot find attribute `multipart_suggestion` in this scope - --> $DIR/diagnostic-derive-inline.rs:565:3 + --> $DIR/diagnostic-derive-inline.rs:568:3 | LL | #[multipart_suggestion("with a suggestion")] | ^^^^^^^^^^^^^^^^^^^^ @@ -560,7 +566,7 @@ LL | struct MultipartSuggestion { | error: cannot find attribute `multipart_suggestion` in this scope - --> $DIR/diagnostic-derive-inline.rs:568:3 + --> $DIR/diagnostic-derive-inline.rs:571:3 | LL | #[multipart_suggestion()] | ^^^^^^^^^^^^^^^^^^^^ @@ -572,12 +578,12 @@ LL | struct MultipartSuggestion { | error: cannot find attribute `multipart_suggestion` in this scope - --> $DIR/diagnostic-derive-inline.rs:572:7 + --> $DIR/diagnostic-derive-inline.rs:575:7 | LL | #[multipart_suggestion("with a suggestion")] | ^^^^^^^^^^^^^^^^^^^^ | = note: `multipart_suggestion` is an attribute that can be used by the derive macro `Subdiagnostic`, you might be missing a `derive` attribute -error: aborting due to 75 previous errors +error: aborting due to 76 previous errors diff --git a/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-pass.rs b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-pass.rs new file mode 100644 index 0000000000000..ea116b0f17fd6 --- /dev/null +++ b/tests/ui-fulldeps/session-diagnostic/diagnostic-derive-pass.rs @@ -0,0 +1,38 @@ +//@ check-pass +//@ compile-flags: -Ztrack-diagnostics + +// The proc_macro2 crate handles spans differently when on beta/stable release rather than nightly, +// changing the output of this test. Since Diagnostic is strictly internal to the compiler +// the test is just ignored on stable and beta: +//@ ignore-stage1 +//@ ignore-beta +//@ ignore-stable + +#![feature(rustc_private)] +#![crate_type = "lib"] + +extern crate rustc_span; +use rustc_span::Span; + +extern crate rustc_macros; +use rustc_macros::Diagnostic; + +extern crate rustc_errors; +use rustc_errors::ErrCode; + +extern crate rustc_session; + +extern crate core; + +// E0123 is no longer used, so we define our own constant here just for this test. +const E0123: ErrCode = ErrCode::from_u32(0123); + +#[derive(Diagnostic)] +#[diag("message", code = E0123)] +struct Diagnostic { + #[primary_span] + #[label("label text")] + span: Span, + #[context] + context: Span, +} From c5f17f6260e3927b5953b3fe033624dede72a58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 27 Sep 2026 21:32:29 +0000 Subject: [PATCH 08/18] Point at enclosing item(s) on unexpected item type --- .../rustc_hir_analysis/src/diagnostics.rs | 4 ++++ .../src/hir_ty_lowering/errors.rs | 23 +++++++++++++++++++ .../associated-consts/shadowed-const.stderr | 2 ++ 3 files changed, 29 insertions(+) diff --git a/compiler/rustc_hir_analysis/src/diagnostics.rs b/compiler/rustc_hir_analysis/src/diagnostics.rs index ff5f7dbb119f1..421f0b52d5a3b 100644 --- a/compiler/rustc_hir_analysis/src/diagnostics.rs +++ b/compiler/rustc_hir_analysis/src/diagnostics.rs @@ -40,6 +40,10 @@ pub(crate) struct AssocKindMismatch { #[primary_span] #[label("unexpected {$got}")] pub span: Span, + #[context] + pub item_span: Span, + #[context] + pub enclosing_span: Option, pub expected: &'static str, pub got: &'static str, #[label("expected a {$expected} because of this associated {$expected}")] diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index bee062b27e0f5..ae5553c592675 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -394,8 +394,31 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { (ident.span, None, assoc_tag, assoc_item.tag()) }; + let def_kind = tcx.def_kind(self.item_def_id()); + let item_span = tcx.def_span(self.item_def_id()).shrink_to_lo(); + let (item_span, enclosing_span) = match def_kind { + DefKind::AssocConst | DefKind::AssocFn | DefKind::AssocTy => ( + item_span, + Some(tcx.def_span(tcx.parent(self.item_def_id().into())).shrink_to_lo()), + ), + DefKind::OpaqueTy => { + let item_span = tcx.def_span(tcx.parent(self.item_def_id().into())).shrink_to_lo(); + let enclosing_span = + if let DefKind::AssocConst | DefKind::AssocFn | DefKind::AssocTy = + tcx.def_kind(tcx.parent(self.item_def_id().into())) + { + Some(tcx.def_span(tcx.parent(self.item_def_id().into())).shrink_to_lo()) + } else { + None + }; + (item_span, enclosing_span) + } + _ => (item_span, None), + }; self.dcx().emit_err(diagnostics::AssocKindMismatch { span, + item_span, + enclosing_span, expected: assoc_tag_str(expected), got: assoc_tag_str(got), expected_because_label, diff --git a/tests/ui/associated-consts/shadowed-const.stderr b/tests/ui/associated-consts/shadowed-const.stderr index 2db645b3c9662..4c105ddaf2974 100644 --- a/tests/ui/associated-consts/shadowed-const.stderr +++ b/tests/ui/associated-consts/shadowed-const.stderr @@ -1,6 +1,8 @@ error: expected type, found constant --> $DIR/shadowed-const.rs:19:20 | +LL | trait Baz3 { +LL | const BAR: usize; LL | const QUX: Self::BAR; | ^^^ unexpected constant | From 1ab22ea44d23a737ce1ecca38631664bf462421f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 27 Sep 2026 22:51:39 +0000 Subject: [PATCH 09/18] Show trait/impl for duplicate impl associated items ``` error[E0201]: duplicate definitions with name `Bar`: --> $DIR/associated-item-duplicate-names-3.rs:14:5 | LL | trait Foo { LL | type Bar; | --------- item in trait ... LL | impl Foo for Baz { LL | type Bar = i16; | --------------- previous definition here LL | type Bar = u16; | ^^^^^^^^^^^^^^^ duplicate definition ``` --- .../rustc_resolve/src/diagnostics/impls.rs | 22 ++++++++++++------- compiler/rustc_resolve/src/diagnostics/mod.rs | 4 ++++ compiler/rustc_resolve/src/late.rs | 4 ++++ compiler/rustc_resolve/src/lib.rs | 8 ++++++- .../associated-item-duplicate-names-3.stderr | 2 ++ .../associated-item-duplicate-names.stderr | 6 +++++ ...licate-definition-inside-trait-impl.stderr | 2 ++ tests/ui/delegation/glob-glob-conflict.stderr | 4 ++++ tests/ui/delegation/glob-non-fn.stderr | 2 ++ .../impl-reuse-non-reuse-items.stderr | 1 + tests/ui/error-codes/E0201.stderr | 6 +++++ tests/ui/hygiene/impl_items-2.stderr | 3 +++ .../rpitit-duplicate-associated-fn.stderr | 4 ++++ .../pin-ergonomics/pinned-drop-check.stderr | 4 ++++ tests/ui/traits/issue-8153.stderr | 2 ++ 15 files changed, 65 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 2ea2f52c696ba..4f7147b6f8367 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1407,14 +1407,20 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { trait_item_span, }) .with_code(code), - ResolutionError::TraitImplDuplicate { name, trait_item_span, old_span } => { - self.dcx().create_err(diagnostics::TraitImplDuplicate { - span, - name, - trait_item_span, - old_span, - }) - } + ResolutionError::TraitImplDuplicate { + name, + trait_item_span, + old_span, + trait_span, + impl_span, + } => self.dcx().create_err(diagnostics::TraitImplDuplicate { + span, + name, + trait_item_span, + trait_span, + impl_span, + old_span, + }), ResolutionError::InvalidAsmSym => { self.dcx().create_err(diagnostics::InvalidAsmSym { span }) } diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index ae4f7cabbe87f..75be11c2a7ce5 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -535,6 +535,10 @@ pub(crate) struct TraitImplDuplicate { pub(crate) old_span: Span, #[label("item in trait")] pub(crate) trait_item_span: Span, + #[context] + pub(crate) trait_span: Span, + #[context] + pub(crate) impl_span: Span, pub(crate) name: Ident, } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index e1787eadf5fb9..22ecc02d93c9e 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3882,12 +3882,16 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { match seen_trait_items.entry(id_in_trait) { Entry::Occupied(entry) => { + let trait_span = decl.parent_module.unwrap().span.shrink_to_lo(); + let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span; self.report_error( span, ResolutionError::TraitImplDuplicate { name: ident, old_span: *entry.get(), trait_item_span: decl.span, + trait_span, + impl_span, }, ); return; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 0a96becf993e9..efd6fe24e1900 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -325,7 +325,13 @@ enum ResolutionError<'ra> { code: ErrCode, }, /// Error E0201: multiple impl items for the same trait item. - TraitImplDuplicate { name: Ident, trait_item_span: Span, old_span: Span }, + TraitImplDuplicate { + name: Ident, + trait_item_span: Span, + old_span: Span, + trait_span: Span, + impl_span: Span, + }, /// Inline asm `sym` operand must refer to a `fn` or `static`. InvalidAsmSym, /// `self` used instead of `Self` in a generic parameter diff --git a/tests/ui/associated-item/associated-item-duplicate-names-3.stderr b/tests/ui/associated-item/associated-item-duplicate-names-3.stderr index 84a9da0998832..950ee878fb391 100644 --- a/tests/ui/associated-item/associated-item-duplicate-names-3.stderr +++ b/tests/ui/associated-item/associated-item-duplicate-names-3.stderr @@ -1,9 +1,11 @@ error[E0201]: duplicate definitions with name `Bar`: --> $DIR/associated-item-duplicate-names-3.rs:14:5 | +LL | trait Foo { LL | type Bar; | --------- item in trait ... +LL | impl Foo for Baz { LL | type Bar = i16; | --------------- previous definition here LL | type Bar = u16; diff --git a/tests/ui/associated-item/associated-item-duplicate-names.stderr b/tests/ui/associated-item/associated-item-duplicate-names.stderr index f89ea6e57cc43..b9c02a910d070 100644 --- a/tests/ui/associated-item/associated-item-duplicate-names.stderr +++ b/tests/ui/associated-item/associated-item-duplicate-names.stderr @@ -1,9 +1,11 @@ error[E0201]: duplicate definitions with name `Ty`: --> $DIR/associated-item-duplicate-names.rs:11:5 | +LL | trait Foo { LL | type Ty; | -------- item in trait ... +LL | impl Foo for () { LL | type Ty = (); | ------------- previous definition here LL | type Ty = usize; @@ -12,9 +14,13 @@ LL | type Ty = usize; error[E0201]: duplicate definitions with name `BAR`: --> $DIR/associated-item-duplicate-names.rs:13:5 | +LL | trait Foo { +LL | type Ty; LL | const BAR: u32; | --------------- item in trait ... +LL | impl Foo for () { +... LL | const BAR: u32 = 7; | ------------------- previous definition here LL | const BAR: u32 = 8; diff --git a/tests/ui/delegation/duplicate-definition-inside-trait-impl.stderr b/tests/ui/delegation/duplicate-definition-inside-trait-impl.stderr index 9e2dd8ea84ac9..bd877e24bf2e8 100644 --- a/tests/ui/delegation/duplicate-definition-inside-trait-impl.stderr +++ b/tests/ui/delegation/duplicate-definition-inside-trait-impl.stderr @@ -1,9 +1,11 @@ error[E0201]: duplicate definitions with name `foo`: --> $DIR/duplicate-definition-inside-trait-impl.rs:18:5 | +LL | trait Trait { LL | fn foo(&self) -> u32 { 0 } | -------------------------- item in trait ... +LL | impl Trait for S { LL | reuse to_reuse::foo { self } | ---------------------------- previous definition here LL | reuse Trait::foo; diff --git a/tests/ui/delegation/glob-glob-conflict.stderr b/tests/ui/delegation/glob-glob-conflict.stderr index 4ada3eb0efa69..73831c9e48e33 100644 --- a/tests/ui/delegation/glob-glob-conflict.stderr +++ b/tests/ui/delegation/glob-glob-conflict.stderr @@ -1,9 +1,11 @@ error[E0201]: duplicate definitions with name `method`: --> $DIR/glob-glob-conflict.rs:26:5 | +LL | trait Trait { LL | fn method(&self) -> u8; | ----------------------- item in trait ... +LL | impl Trait for u8 { LL | reuse Trait1::*; | ---------------- previous definition here LL | reuse Trait2::*; @@ -12,9 +14,11 @@ LL | reuse Trait2::*; error[E0201]: duplicate definitions with name `method`: --> $DIR/glob-glob-conflict.rs:32:5 | +LL | trait Trait { LL | fn method(&self) -> u8; | ----------------------- item in trait ... +LL | impl Trait for u16 { LL | reuse Trait1::*; | ---------------- previous definition here LL | reuse Trait1::*; diff --git a/tests/ui/delegation/glob-non-fn.stderr b/tests/ui/delegation/glob-non-fn.stderr index e1a4ffce5ac17..3a67933b13222 100644 --- a/tests/ui/delegation/glob-non-fn.stderr +++ b/tests/ui/delegation/glob-non-fn.stderr @@ -19,9 +19,11 @@ LL | reuse Trait::* { &self.0 } error[E0201]: duplicate definitions with name `method`: --> $DIR/glob-non-fn.rs:29:5 | +LL | trait Trait { LL | fn method(&self); | ----------------- item in trait ... +LL | impl Trait for Bad { LL | reuse Trait::* { &self.0 } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | | diff --git a/tests/ui/delegation/impl-reuse-non-reuse-items.stderr b/tests/ui/delegation/impl-reuse-non-reuse-items.stderr index 2a33982cc6730..e83b7d4e614db 100644 --- a/tests/ui/delegation/impl-reuse-non-reuse-items.stderr +++ b/tests/ui/delegation/impl-reuse-non-reuse-items.stderr @@ -19,6 +19,7 @@ LL | reuse impl Trait for S { &self.0 } error[E0201]: duplicate definitions with name `method`: --> $DIR/impl-reuse-non-reuse-items.rs:22:5 | +LL | trait Trait { LL | fn method(&self); | ----------------- item in trait ... diff --git a/tests/ui/error-codes/E0201.stderr b/tests/ui/error-codes/E0201.stderr index 608ff69177694..bd1ccc00fbd58 100644 --- a/tests/ui/error-codes/E0201.stderr +++ b/tests/ui/error-codes/E0201.stderr @@ -1,9 +1,13 @@ error[E0201]: duplicate definitions with name `baz`: --> $DIR/E0201.rs:17:5 | +LL | trait Baz { +LL | type Quux; LL | fn baz(&self) -> bool; | ---------------------- item in trait ... +LL | impl Baz for Foo { +... LL | fn baz(&self) -> bool { true } | ------------------------------ previous definition here LL | fn baz(&self) -> bool { self.0 > 5 } @@ -12,9 +16,11 @@ LL | fn baz(&self) -> bool { self.0 > 5 } error[E0201]: duplicate definitions with name `Quux`: --> $DIR/E0201.rs:18:5 | +LL | trait Baz { LL | type Quux; | ---------- item in trait ... +LL | impl Baz for Foo { LL | type Quux = u32; | ---------------- previous definition here ... diff --git a/tests/ui/hygiene/impl_items-2.stderr b/tests/ui/hygiene/impl_items-2.stderr index e7128f83e110b..723324c10294e 100644 --- a/tests/ui/hygiene/impl_items-2.stderr +++ b/tests/ui/hygiene/impl_items-2.stderr @@ -1,12 +1,15 @@ error[E0201]: duplicate definitions with name `foo`: --> $DIR/impl_items-2.rs:14:5 | +LL | trait Trait { LL | fn foo() {} | ----------- item in trait ... LL | fn foo() {} | ----------- previous definition here ... +LL | impl Trait for i32 { +LL | trait_impl!(); LL | fn foo() {} | ^^^^^^^^^^^ duplicate definition diff --git a/tests/ui/impl-trait/in-trait/rpitit-duplicate-associated-fn.stderr b/tests/ui/impl-trait/in-trait/rpitit-duplicate-associated-fn.stderr index faa65f45d33cb..5f715ff16fa85 100644 --- a/tests/ui/impl-trait/in-trait/rpitit-duplicate-associated-fn.stderr +++ b/tests/ui/impl-trait/in-trait/rpitit-duplicate-associated-fn.stderr @@ -42,9 +42,11 @@ LL | fn foo() -> impl Sized; error[E0201]: duplicate definitions with name `method`: --> $DIR/rpitit-duplicate-associated-fn.rs:12:5 | +LL | trait Bar { LL | fn method() -> impl Sized; | -------------------------- item in trait ... +LL | impl Bar for () { LL | / fn method() -> impl Sized { LL | | 42 LL | | } @@ -57,9 +59,11 @@ LL | | } error[E0201]: duplicate definitions with name `method`: --> $DIR/rpitit-duplicate-associated-fn.rs:25:5 | +LL | trait T { LL | fn method() -> impl Sized; | -------------------------- item in trait ... +LL | impl T for () { LL | / fn method() -> impl Sized { LL | | 42 LL | | } diff --git a/tests/ui/pin-ergonomics/pinned-drop-check.stderr b/tests/ui/pin-ergonomics/pinned-drop-check.stderr index 3dc6e18a9178e..6087d45dd2900 100644 --- a/tests/ui/pin-ergonomics/pinned-drop-check.stderr +++ b/tests/ui/pin-ergonomics/pinned-drop-check.stderr @@ -1,24 +1,28 @@ error[E0201]: duplicate definitions with name `pin_drop`: --> $DIR/pinned-drop-check.rs:196:9 | +LL | impl Drop for Foo { LL | fn drop(&pin mut self) {} | ------------------------- previous definition here LL | fn pin_drop(&pin mut self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definition | --> $SRC_DIR/core/src/ops/drop.rs:LL:COL + ::: $SRC_DIR/core/src/ops/drop.rs:LL:COL | = note: item in trait error[E0201]: duplicate definitions with name `pin_drop`: --> $DIR/pinned-drop-check.rs:201:9 | +LL | impl Drop for Bar { LL | fn drop(&pin mut self) {} | ------------------------- previous definition here LL | fn pin_drop(&pin mut self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definition | --> $SRC_DIR/core/src/ops/drop.rs:LL:COL + ::: $SRC_DIR/core/src/ops/drop.rs:LL:COL | = note: item in trait diff --git a/tests/ui/traits/issue-8153.stderr b/tests/ui/traits/issue-8153.stderr index 8882097af3390..cc0bf646d147c 100644 --- a/tests/ui/traits/issue-8153.stderr +++ b/tests/ui/traits/issue-8153.stderr @@ -1,9 +1,11 @@ error[E0201]: duplicate definitions with name `bar`: --> $DIR/issue-8153.rs:11:5 | +LL | trait Bar { LL | fn bar(&self) -> isize; | ----------------------- item in trait ... +LL | impl Bar for Foo { LL | fn bar(&self) -> isize {1} | -------------------------- previous definition here LL | fn bar(&self) -> isize {2} From be0d4321237e0bc04d4dc9112c00358d81c42307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 27 Sep 2026 22:59:34 +0000 Subject: [PATCH 10/18] Point at trait/impl on associated item mismatch ``` error[E0324]: item `RESOLVED_WRONG` is an associated method, which doesn't match its trait `MyTrait` --> $DIR/unresolved-trait-impl-item.rs:12:5 | LL | trait MyTrait { LL | async fn resolved(&self); LL | const RESOLVED_WRONG: u8 = 0; | ----------------------------- item in trait ... LL | impl MyTrait for i32 { ... LL | async fn RESOLVED_WRONG() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait ``` --- compiler/rustc_resolve/src/diagnostics/impls.rs | 4 ++++ compiler/rustc_resolve/src/diagnostics/mod.rs | 4 ++++ compiler/rustc_resolve/src/late.rs | 4 ++++ compiler/rustc_resolve/src/lib.rs | 2 ++ tests/ui/delegation/bad-resolve.stderr | 7 +++++++ tests/ui/delegation/glob-non-fn.stderr | 6 ++++++ tests/ui/delegation/impl-reuse-non-reuse-items.stderr | 4 ++++ tests/ui/privacy/unresolved-trait-impl-item.stderr | 4 ++++ tests/ui/span/impl-wrong-item-for-trait.stderr | 10 ++++++++++ 9 files changed, 45 insertions(+) diff --git a/compiler/rustc_resolve/src/diagnostics/impls.rs b/compiler/rustc_resolve/src/diagnostics/impls.rs index 4f7147b6f8367..f194690d82757 100644 --- a/compiler/rustc_resolve/src/diagnostics/impls.rs +++ b/compiler/rustc_resolve/src/diagnostics/impls.rs @@ -1397,6 +1397,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { code, trait_item_span, trait_path, + trait_span, + impl_span, } => self .dcx() .create_err(diagnostics::TraitImplMismatch { @@ -1405,6 +1407,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { kind, trait_path, trait_item_span, + trait_span, + impl_span, }) .with_code(code), ResolutionError::TraitImplDuplicate { diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index 75be11c2a7ce5..eb06ccb1c8ff4 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -1414,6 +1414,10 @@ pub(crate) struct TraitImplMismatch { pub(crate) trait_path: String, #[label("item in trait")] pub(crate) trait_item_span: Span, + #[context] + pub(crate) trait_span: Span, + #[context] + pub(crate) impl_span: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 22ecc02d93c9e..38afe52166d00 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3924,6 +3924,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } }; let trait_path = path_names_to_string(path); + let trait_span = decl.parent_module.unwrap().span.shrink_to_lo(); + let impl_span = self.current_trait_ref.as_ref().unwrap().1.path.span; self.report_error( span, ResolutionError::TraitImplMismatch { @@ -3932,6 +3934,8 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { code, trait_path, trait_item_span: decl.span, + trait_span, + impl_span, }, ); } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index efd6fe24e1900..334699fbdfbbb 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -323,6 +323,8 @@ enum ResolutionError<'ra> { trait_path: String, trait_item_span: Span, code: ErrCode, + trait_span: Span, + impl_span: Span, }, /// Error E0201: multiple impl items for the same trait item. TraitImplDuplicate { diff --git a/tests/ui/delegation/bad-resolve.stderr b/tests/ui/delegation/bad-resolve.stderr index 9740442e4b9f3..f0ff69b06e5fc 100644 --- a/tests/ui/delegation/bad-resolve.stderr +++ b/tests/ui/delegation/bad-resolve.stderr @@ -1,18 +1,25 @@ error[E0324]: item `C` is an associated method, which doesn't match its trait `Trait` --> $DIR/bad-resolve.rs:23:5 | +LL | trait Trait { LL | const C: u32 = 0; | ----------------- item in trait ... +LL | impl Trait for S { +LL | LL | reuse ::C; | ^^^^^^^^^^^^^^^^^^^^^^ does not match trait error[E0324]: item `Type` is an associated method, which doesn't match its trait `Trait` --> $DIR/bad-resolve.rs:26:5 | +LL | trait Trait { +LL | const C: u32 = 0; LL | type Type; | ---------- item in trait ... +LL | impl Trait for S { +... LL | reuse ::Type; | ^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait diff --git a/tests/ui/delegation/glob-non-fn.stderr b/tests/ui/delegation/glob-non-fn.stderr index 3a67933b13222..a09bc7a5aa27f 100644 --- a/tests/ui/delegation/glob-non-fn.stderr +++ b/tests/ui/delegation/glob-non-fn.stderr @@ -1,18 +1,24 @@ error[E0324]: item `CONST` is an associated method, which doesn't match its trait `Trait` --> $DIR/glob-non-fn.rs:29:5 | +LL | trait Trait { +LL | fn method(&self); LL | const CONST: u8; | ---------------- item in trait ... +LL | impl Trait for Bad { LL | reuse Trait::* { &self.0 } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait error[E0324]: item `Type` is an associated method, which doesn't match its trait `Trait` --> $DIR/glob-non-fn.rs:29:5 | +LL | trait Trait { +... LL | type Type; | ---------- item in trait ... +LL | impl Trait for Bad { LL | reuse Trait::* { &self.0 } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait diff --git a/tests/ui/delegation/impl-reuse-non-reuse-items.stderr b/tests/ui/delegation/impl-reuse-non-reuse-items.stderr index e83b7d4e614db..cc894d8ef4330 100644 --- a/tests/ui/delegation/impl-reuse-non-reuse-items.stderr +++ b/tests/ui/delegation/impl-reuse-non-reuse-items.stderr @@ -1,6 +1,8 @@ error[E0324]: item `CONST` is an associated method, which doesn't match its trait `Trait` --> $DIR/impl-reuse-non-reuse-items.rs:22:5 | +LL | trait Trait { +LL | fn method(&self); LL | const CONST: u8; | ---------------- item in trait ... @@ -10,6 +12,8 @@ LL | reuse impl Trait for S { &self.0 } error[E0324]: item `Type` is an associated method, which doesn't match its trait `Trait` --> $DIR/impl-reuse-non-reuse-items.rs:22:5 | +LL | trait Trait { +... LL | type Type; | ---------- item in trait ... diff --git a/tests/ui/privacy/unresolved-trait-impl-item.stderr b/tests/ui/privacy/unresolved-trait-impl-item.stderr index 588e47c26bcd1..3307d9114122b 100644 --- a/tests/ui/privacy/unresolved-trait-impl-item.stderr +++ b/tests/ui/privacy/unresolved-trait-impl-item.stderr @@ -10,9 +10,13 @@ LL | async fn unresolved(&self) {} error[E0324]: item `RESOLVED_WRONG` is an associated method, which doesn't match its trait `MyTrait` --> $DIR/unresolved-trait-impl-item.rs:12:5 | +LL | trait MyTrait { +LL | async fn resolved(&self); LL | const RESOLVED_WRONG: u8 = 0; | ----------------------------- item in trait ... +LL | impl MyTrait for i32 { +... LL | async fn RESOLVED_WRONG() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait diff --git a/tests/ui/span/impl-wrong-item-for-trait.stderr b/tests/ui/span/impl-wrong-item-for-trait.stderr index f919092f9ee92..e8066e224df0f 100644 --- a/tests/ui/span/impl-wrong-item-for-trait.stderr +++ b/tests/ui/span/impl-wrong-item-for-trait.stderr @@ -1,27 +1,37 @@ error[E0323]: item `bar` is an associated const, which doesn't match its trait `Foo` --> $DIR/impl-wrong-item-for-trait.rs:12:5 | +LL | trait Foo { LL | fn bar(&self); | -------------- item in trait ... +LL | impl Foo for FooConstForMethod { +LL | LL | const bar: u64 = 1; | ^^^^^^^^^^^^^^^^^^^ does not match trait error[E0324]: item `MY_CONST` is an associated method, which doesn't match its trait `Foo` --> $DIR/impl-wrong-item-for-trait.rs:22:5 | +LL | trait Foo { +LL | fn bar(&self); LL | const MY_CONST: u32; | -------------------- item in trait ... +LL | impl Foo for FooMethodForConst { +... LL | fn MY_CONST() {} | ^^^^^^^^^^^^^^^^ does not match trait error[E0325]: item `bar` is an associated type, which doesn't match its trait `Foo` --> $DIR/impl-wrong-item-for-trait.rs:30:5 | +LL | trait Foo { LL | fn bar(&self); | -------------- item in trait ... +LL | impl Foo for FooTypeForMethod { +LL | LL | type bar = u64; | ^^^^^^^^^^^^^^^ does not match trait From 246eb149ef7b489e2a94aaf0e78a0dc2f214dbfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 27 Sep 2026 23:24:17 +0000 Subject: [PATCH 11/18] On `impl` item signature mismatch, point at impl and trait ``` error: `impl` item signature doesn't match `trait` item signature --> $DIR/self-without-lifetime-constraint.rs:46:5 | LL | pub trait FromSql: Sized { LL | fn column_result(value: ValueRef<'_>) -> FromSqlResult; | -------------------------------------------------------------------- expected `fn(ValueRef<'1>) -> Result<(&'2 str, &'1 &'2 str), _>` ... LL | impl FromSql for &str { LL | fn column_result(value: ValueRef<'_>) -> FromSqlResult<&str, &&str> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(ValueRef<'1>) -> Result<(&'1 str, &'1 &'1 str), _>` | = note: expected signature `fn(ValueRef<'1>) -> Result<(&'2 str, &'1 &'2 str), FromSqlError>` found signature `fn(ValueRef<'1>) -> Result<(&'1 str, &'1 &'1 str), FromSqlError>` help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait` --> $DIR/self-without-lifetime-constraint.rs:42:60 | LL | fn column_result(value: ValueRef<'_>) -> FromSqlResult; | ^^^^ consider borrowing this type parameter in the trait ``` --- .../rustc_trait_selection/src/diagnostics.rs | 4 ++++ .../nice_region_error/trait_impl_difference.rs | 16 +++++++++++++--- ...fetime-mismatch-between-trait-and-impl.stderr | 2 ++ ...l-mismatch-elided-lifetime-issue-65866.stderr | 4 ++++ .../issue-75361-mismatched-impl.stderr | 4 ++++ tests/ui/self/elision/no-shadow-pin-self.stderr | 3 +++ .../param-without-lifetime-constraint.stderr | 2 ++ .../self-without-lifetime-constraint.stderr | 2 ++ 8 files changed, 34 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_trait_selection/src/diagnostics.rs b/compiler/rustc_trait_selection/src/diagnostics.rs index 80ffbba041ccd..a2bf84fb81419 100644 --- a/compiler/rustc_trait_selection/src/diagnostics.rs +++ b/compiler/rustc_trait_selection/src/diagnostics.rs @@ -1221,6 +1221,10 @@ pub(crate) struct TraitImplDiff { pub found_short: String, pub expected: String, pub found: String, + #[context] + pub trait_span: Span, + #[context] + pub impl_span: Span, } #[derive(Diagnostic)] diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/trait_impl_difference.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/trait_impl_difference.rs index 03baa5c2acbfb..43ac38005dba3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/trait_impl_difference.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/nice_region_error/trait_impl_difference.rs @@ -35,14 +35,21 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { ) = error.clone() && let (SubregionOrigin::Subtype(sup_trace), SubregionOrigin::Subtype(sub_trace)) = (&sup_origin, &sub_origin) - && let &ObligationCauseCode::CompareImplItem { trait_item_def_id, .. } = - sub_trace.cause.code() + && let &ObligationCauseCode::CompareImplItem { + trait_item_def_id, impl_item_def_id, .. + } = sub_trace.cause.code() && sub_trace.values == sup_trace.values && let ValuePairs::PolySigs(ExpectedFound { expected, found }) = sub_trace.values { // FIXME(compiler-errors): Don't like that this needs `Ty`s, but // all of the region highlighting machinery only deals with those. - let guar = self.emit_err(var_origin.span(), expected, found, trait_item_def_id); + let guar = self.emit_err( + var_origin.span(), + expected, + found, + trait_item_def_id, + impl_item_def_id.into(), + ); return Some(guar); } None @@ -54,6 +61,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { expected: ty::PolyFnSig<'tcx>, found: ty::PolyFnSig<'tcx>, trait_item_def_id: DefId, + impl_item_def_id: DefId, ) -> ErrorGuaranteed { let trait_sp = self.tcx().def_span(trait_item_def_id); @@ -135,6 +143,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { found, expected_short, found_short, + trait_span: self.tcx().def_span(self.tcx().parent(trait_item_def_id)).shrink_to_lo(), + impl_span: self.tcx().def_span(self.tcx().parent(impl_item_def_id)).shrink_to_lo(), }; let mut diag = self.tcx().dcx().create_err(diag); diff --git a/tests/ui/lifetimes/lifetime-mismatch-between-trait-and-impl.stderr b/tests/ui/lifetimes/lifetime-mismatch-between-trait-and-impl.stderr index 917d204367ffe..ad3500a6ff61d 100644 --- a/tests/ui/lifetimes/lifetime-mismatch-between-trait-and-impl.stderr +++ b/tests/ui/lifetimes/lifetime-mismatch-between-trait-and-impl.stderr @@ -1,9 +1,11 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/lifetime-mismatch-between-trait-and-impl.rs:6:5 | +LL | trait Foo { LL | fn foo<'a>(x: &i32, y: &'a i32) -> &'a i32; | ------------------------------------------- expected `fn(&'1 i32, &'a i32) -> &'a i32` ... +LL | impl Foo for () { LL | fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 i32, &'1 i32) -> &'1 i32` | diff --git a/tests/ui/lifetimes/trait-impl-mismatch-elided-lifetime-issue-65866.stderr b/tests/ui/lifetimes/trait-impl-mismatch-elided-lifetime-issue-65866.stderr index 4c4f6032b0f7b..cfe1e9353316f 100644 --- a/tests/ui/lifetimes/trait-impl-mismatch-elided-lifetime-issue-65866.stderr +++ b/tests/ui/lifetimes/trait-impl-mismatch-elided-lifetime-issue-65866.stderr @@ -1,9 +1,11 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:17:9 | +LL | trait Bar { LL | fn bar(&self, r: &mut Re); | -------------------------- expected `fn(&'1 Foo, &'2 mut Re<'3>)` ... +LL | impl Bar for Foo { LL | fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 Foo, &'2 mut Re<'1>)` | @@ -20,9 +22,11 @@ LL | fn bar(&self, r: &mut Re); error: `impl` item signature doesn't match `trait` item signature --> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:40:9 | +LL | trait Bar { LL | fn bar(&self, r: &mut Re); | ------------------------------ expected `fn(&'1 Foo, &'2 mut Re<'3, u8>)` ... +LL | impl Bar for Foo { LL | fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a, u8>) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 Foo, &'2 mut Re<'1, u8>)` | diff --git a/tests/ui/mismatched_types/issue-75361-mismatched-impl.stderr b/tests/ui/mismatched_types/issue-75361-mismatched-impl.stderr index 5c2278ddfc6af..09556076d787a 100644 --- a/tests/ui/mismatched_types/issue-75361-mismatched-impl.stderr +++ b/tests/ui/mismatched_types/issue-75361-mismatched-impl.stderr @@ -1,9 +1,13 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/issue-75361-mismatched-impl.rs:18:3 | +LL | pub trait Graph { +... LL | fn adjacent_edges(&self) -> Box>; | --------------------------------------------------------------------- expected `fn(&'1 T) -> Box<(dyn MyTrait + 'static)>` ... +LL | impl Graph for T { +... LL | fn adjacent_edges(&self) -> Box + '_> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 T) -> Box<(dyn MyTrait + '1)>` | diff --git a/tests/ui/self/elision/no-shadow-pin-self.stderr b/tests/ui/self/elision/no-shadow-pin-self.stderr index 23485e9de79b7..2b2559d5a4274 100644 --- a/tests/ui/self/elision/no-shadow-pin-self.stderr +++ b/tests/ui/self/elision/no-shadow-pin-self.stderr @@ -1,9 +1,12 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/no-shadow-pin-self.rs:10:5 | +LL | trait Trait { LL | fn method<'a>(self: Pin<&Self>, f: &'a u32) -> &'a u32 { | ------------------------------------------------------ expected `fn(Pin<&'1 Pin

>, &'a u32) -> &'a u32` ... +LL | impl

Trait for Pin

{ +LL | // This should not hide `&Self`, which would cause this to compile. LL | fn method(self: Pin<&Self>, f: &u32) -> &u32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(Pin<&'1 Pin

>, &'2 u32) -> &'1 u32` | diff --git a/tests/ui/traits/param-without-lifetime-constraint.stderr b/tests/ui/traits/param-without-lifetime-constraint.stderr index b8ec1fa5654a1..d5d83675a1e71 100644 --- a/tests/ui/traits/param-without-lifetime-constraint.stderr +++ b/tests/ui/traits/param-without-lifetime-constraint.stderr @@ -1,9 +1,11 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/param-without-lifetime-constraint.rs:14:5 | +LL | pub trait HaveRelationship { LL | fn get_relation(&self) -> To; | ----------------------------- expected `fn(&'1 Article) -> &'2 ProofReader` ... +LL | impl HaveRelationship<&ProofReader> for Article { LL | fn get_relation(&self) -> &ProofReader { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 Article) -> &'1 ProofReader` | diff --git a/tests/ui/traits/self-without-lifetime-constraint.stderr b/tests/ui/traits/self-without-lifetime-constraint.stderr index a82ff8eb0c269..34cf99b3f0512 100644 --- a/tests/ui/traits/self-without-lifetime-constraint.stderr +++ b/tests/ui/traits/self-without-lifetime-constraint.stderr @@ -1,9 +1,11 @@ error: `impl` item signature doesn't match `trait` item signature --> $DIR/self-without-lifetime-constraint.rs:46:5 | +LL | pub trait FromSql: Sized { LL | fn column_result(value: ValueRef<'_>) -> FromSqlResult; | -------------------------------------------------------------------- expected `fn(ValueRef<'1>) -> Result<(&'2 str, &'1 &'2 str), _>` ... +LL | impl FromSql for &str { LL | fn column_result(value: ValueRef<'_>) -> FromSqlResult<&str, &&str> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(ValueRef<'1>) -> Result<(&'1 str, &'1 &'1 str), _>` | From 852ac9e50be7f4d6f4033c5881fea5a4814eb804 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 29 Sep 2026 18:40:59 +0200 Subject: [PATCH 12/18] Update rust-toolchain version needed --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a7774784e3afa..63af0047e6e00 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-09-24" +channel = "nightly-2026-09-29" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] From 99055eee3a49e1bca06855cd3f066384728a370a Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 29 Sep 2026 18:26:29 +0100 Subject: [PATCH 13/18] Revert "Rollup merge of #120589 - devnexen:cpuaff_fbsd_upd, r=clarfonthey" This reverts commit 0a4206a71433bcad48d9447b080a874b1b06d54f, reversing changes made to 8f3a9a10173f6c52a20bb31e96ae903360733aaf. --- library/std/src/sys/thread/unix.rs | 40 +++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/library/std/src/sys/thread/unix.rs b/library/std/src/sys/thread/unix.rs index 11fe569f8e0a9..b7908dea2b5de 100644 --- a/library/std/src/sys/thread/unix.rs +++ b/library/std/src/sys/thread/unix.rs @@ -162,7 +162,6 @@ pub fn available_parallelism() -> io::Result> { target_os = "hurd", target_os = "linux", target_os = "aix", - target_os = "freebsd", target_vendor = "apple", target_os = "cygwin", target_os = "redox", @@ -175,17 +174,9 @@ pub fn available_parallelism() -> io::Result> { #[cfg(any(target_os = "android", target_os = "linux"))] { quota = cgroups::quota().max(1); - } - - #[cfg(any(target_os = "android", target_os = "linux", target_os = "freebsd"))] - { - #[cfg(not(target_os = "freebsd"))] - type Cpuset = libc::cpu_set_t; - #[cfg(target_os = "freebsd")] - type Cpuset = libc::cpuset_t; - let mut set: Cpuset = unsafe { mem::zeroed() }; + let mut set: libc::cpu_set_t = unsafe { mem::zeroed() }; unsafe { - if libc::sched_getaffinity(0, size_of::(), &mut set) == 0 { + if libc::sched_getaffinity(0, size_of::(), &mut set) == 0 { let count = libc::CPU_COUNT(&set) as usize; let count = count.min(quota); @@ -210,9 +201,34 @@ pub fn available_parallelism() -> io::Result> { } } } - any(target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd") => { + any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd", + target_os = "netbsd", + ) => { use crate::ptr; + #[cfg(target_os = "freebsd")] + { + let mut set: libc::cpuset_t = unsafe { mem::zeroed() }; + unsafe { + if libc::cpuset_getaffinity( + libc::CPU_LEVEL_WHICH, + libc::CPU_WHICH_PID, + -1, + size_of::(), + &mut set, + ) == 0 + { + let count = libc::CPU_COUNT(&set) as usize; + if count > 0 { + return Ok(NonZero::new_unchecked(count)); + } + } + } + } + #[cfg(target_os = "netbsd")] { unsafe { From 10448a35f3c8ac72c48bc970c70315199a134959 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 29 Sep 2026 13:56:37 -0400 Subject: [PATCH 14/18] Fix panic when casting from i128 to f16 --- src/int.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/int.rs b/src/int.rs index 4e4b911666143..de8762d1c06b4 100644 --- a/src/int.rs +++ b/src/int.rs @@ -954,6 +954,10 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { debug_assert!(value_type.dyncast_array().is_some()); let name_suffix = match self.type_kind(dest_typ) { + TypeKind::Half if dest_typ.is_compatible_with(self.type_f16()) => { + let value = self.int_to_float_cast(signed, value, self.float_type); + return self.context.new_cast(None, value, dest_typ); + } // cSpell:disable TypeKind::Float => "tisf", TypeKind::Double => "tidf", From b3cf6a588a32a09a10e286a193efcdd48f695dfc Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 29 Sep 2026 13:58:57 -0400 Subject: [PATCH 15/18] Install rustfmt stdarch CI workflow --- .github/workflows/stdarch.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/stdarch.yml b/.github/workflows/stdarch.yml index 34499c6e7b522..4afb74a1f8cdd 100644 --- a/.github/workflows/stdarch.yml +++ b/.github/workflows/stdarch.yml @@ -32,6 +32,10 @@ jobs: - name: Setup rust toolchain run: rustup show + # The stdarch-gen-common tests run rustfmt. + - name: Install rustfmt + run: rustup component add rustfmt + - name: Setup rust cache uses: Swatinem/rust-cache@v2 From 0317026d59e470f8ca48e96d557bb3320931ba6b Mon Sep 17 00:00:00 2001 From: Crystal Durham Date: Mon, 31 Aug 2026 15:05:37 -0500 Subject: [PATCH 16/18] impl std::fs::{Home|Media}Dirs also includes: - std::env::split_paths_ref - std::os::{darwin|unix|windows}::{Home|Media}DirsExt --- library/std/src/env.rs | 51 ++ library/std/src/fs.rs | 7 + library/std/src/fs/dirs.rs | 588 ++++++++++++++++++ library/std/src/os/darwin/fs.rs | 7 + library/std/src/os/darwin/fs/dirs.rs | 313 ++++++++++ library/std/src/os/unix/fs.rs | 7 + library/std/src/os/unix/fs/dirs.rs | 585 +++++++++++++++++ library/std/src/os/windows/fs.rs | 7 + library/std/src/os/windows/fs/dirs.rs | 309 +++++++++ library/std/src/sys/fs/common.rs | 8 + library/std/src/sys/fs/hermit.rs | 2 +- library/std/src/sys/fs/mod.rs | 7 +- library/std/src/sys/fs/motor.rs | 2 +- library/std/src/sys/fs/solid.rs | 2 +- library/std/src/sys/fs/uefi.rs | 2 +- library/std/src/sys/fs/unix.rs | 4 + library/std/src/sys/fs/unsupported.rs | 2 +- library/std/src/sys/fs/vexos.rs | 2 + library/std/src/sys/fs/windows.rs | 1 + library/std/src/sys/pal/windows/c.rs | 45 ++ .../std/src/sys/pal/windows/c/bindings.txt | 7 + .../std/src/sys/pal/windows/c/windows_sys.rs | 8 + library/std/src/sys/pal/windows/compat.rs | 108 +++- library/std/src/sys/paths/mod.rs | 18 +- library/std/src/sys/paths/uefi.rs | 14 + library/std/src/sys/paths/unix.rs | 24 +- library/std/src/sys/paths/unsupported.rs | 14 +- library/std/src/sys/paths/windows.rs | 14 + 28 files changed, 2140 insertions(+), 18 deletions(-) create mode 100644 library/std/src/fs/dirs.rs create mode 100644 library/std/src/os/darwin/fs/dirs.rs create mode 100644 library/std/src/os/unix/fs/dirs.rs create mode 100644 library/std/src/os/windows/fs/dirs.rs diff --git a/library/std/src/env.rs b/library/std/src/env.rs index 762ad071e967c..6e401113cb26e 100644 --- a/library/std/src/env.rs +++ b/library/std/src/env.rs @@ -502,6 +502,57 @@ impl fmt::Debug for SplitPaths<'_> { } } +/// An iterator that splits an environment variable into paths according to +/// platform-specific conventions. +/// +/// The iterator element type is &[Path]. +/// +/// This structure is created by [`env::split_paths_ref()`]. See its +/// documentation for more. +/// +/// [`env::split_paths_ref()`]: split_paths_ref +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[unstable(feature = "env_split_paths_ref", issue = "none")] +pub struct SplitPathsRef<'a> { + inner: paths_imp::SplitPathsRef<'a>, +} + +/// Parses input according to platform conventions for the `PATH` +/// environment variable. +/// +/// Unlike [`split_paths`], this function does not allocate and instead yields +/// [`Path`]s borrowed from `unparsed`. Returns `None` on platforms that may +/// require allocations to handle `PATH` splitting conventions. +/// +/// # Platform-specific behavior +/// +/// Returns `Some` on Unix platforms and `None` on all other platforms. +/// Note that this [may change in the future][changes]. +/// +/// [changes]: io#platform-specific-behavior +#[unstable(feature = "env_split_paths_ref", issue = "none")] +pub fn split_paths_ref + ?Sized>(unparsed: &T) -> Option> { + Some(SplitPathsRef { inner: paths_imp::split_paths_ref(unparsed.as_ref())? }) +} + +#[unstable(feature = "env_split_paths_ref", issue = "none")] +impl<'a> Iterator for SplitPathsRef<'a> { + type Item = &'a Path; + fn next(&mut self) -> Option<&'a Path> { + self.inner.next() + } + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +#[unstable(feature = "env_split_paths_ref", issue = "none")] +impl fmt::Debug for SplitPathsRef<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SplitPathsRef").finish_non_exhaustive() + } +} + /// The error type for operations on the `PATH` variable. Possibly returned from /// [`env::join_paths()`]. /// diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 8df809264a6dd..3a4f14c3306bc 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -49,6 +49,13 @@ use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner, fs as fs_imp}; use crate::time::SystemTime; use crate::{error, fmt}; +pub(crate) mod dirs; + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub use self::dirs::HomeDirs; +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub use self::dirs::MediaDirs; + /// An object providing access to an open file on the filesystem. /// /// An instance of a `File` can be read and/or written depending on what options diff --git a/library/std/src/fs/dirs.rs b/library/std/src/fs/dirs.rs new file mode 100644 index 0000000000000..f8b855c994ff2 --- /dev/null +++ b/library/std/src/fs/dirs.rs @@ -0,0 +1,588 @@ +use crate::path::{Path, PathBuf}; +use crate::sys::fs::{ExtraHomeDirs, ExtraMediaDirs}; + +/// Common user directory paths used for user-specific application files. +/// +/// It is not required that the user directories are accessible by the current +/// user, nor that there is a directory at that path. A robust application +/// should handle the case where user directories are incorrectly configured. +/// +/// Even when configured correctly, multiple paths may point to the same location. +/// You should not assume that a file written relative to one directory will not +/// conflict with the same relative path in a different home directory. +/// +/// # Platform-specific behavior +/// +/// As the filesystem conventions for discovering directories varies between +/// operating systems, constructors for `HomeDirs` that use the host platform's +/// conventions are provided as extension traits under the `std::os` module. +#[unstable(feature = "fs_home_dirs", issue = "162082")] +#[derive(Debug, Clone)] +pub struct HomeDirs { + pub(crate) cache: Option, + pub(crate) config: Option, + pub(crate) data: Option, + pub(crate) state: Option, + #[cfg_attr(not(unix), expect(dead_code, reason = "no extra home dirs"))] + pub(crate) extra: ExtraHomeDirs, +} + +/// Common user directory paths used for user-specific media files. +/// +/// It is not required that the media directories are accessible by the current +/// user, nor that there is a directory at that path. A robust application +/// should handle the case where media directories are incorrectly configured. +/// +/// Even when configured correctly, multiple paths may point to the same location. +/// You should not assume that a file written relative to one directory will not +/// conflict with the same relative path in a different media directory. +/// +/// # Platform-specific behavior +/// +/// As the filesystem conventions for discovering directories varies between +/// operating systems, constructors for `MediaDirs` that use the host platform's +/// conventions are provided as extension traits under the `std::os` module. +#[unstable(feature = "fs_media_dirs", issue = "162083")] +#[derive(Debug, Clone)] +pub struct MediaDirs { + pub(crate) desktop: Option, + pub(crate) documents: Option, + pub(crate) downloads: Option, + pub(crate) music: Option, + pub(crate) pictures: Option, + pub(crate) videos: Option, + #[cfg_attr(not(unix), expect(dead_code, reason = "no extra media dirs"))] + pub(crate) extra: ExtraMediaDirs, +} + +// NB: HomeDirs and MediaDirs intentionally do not implement Default. Self::empty() +// is a logical default, but users may also intuit the default to use default +// platform conventions. Omitting Default pushes users to explicitly choose. + +impl HomeDirs { + /// Create a known user directory set with no known directories. + /// + /// This is useful with the builder `set_*` methods to create a `HomeDirs` + /// with exactly the directories you want, without any other defaults. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn empty() -> Self { + Self { cache: None, config: None, data: None, state: None, extra: Default::default() } + } + + /// A base directory relative to which user-specific non-essential cache + /// data files should be stored. + /// + /// "Cache" files are temporary data that can be used to cache redundant + /// work of an application, but which can be discarded arbitrarily and + /// recreated as necessary. Files in this directory may potentially be + /// automatically purged any time they are not currently open, or they + /// may not, depending on system configuration. A robust application + /// should ensure that its caches do not grow without a reasonable bound. + /// + /// This is the same directory for all applications. Applications should + /// use a subdirectory for application-specific cache files. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `${XDG_CACHE_HOME:-$HOME/.cache}` | + /// | [Darwin] (macOS) | [`NSCachesDirectory`] (`$HOME/Library/Caches`) | + /// | [Windows] | [`{FOLDERID_LocalAppData}`] (`%LOCALAPPDATA%`) | + /// + /// Other paths can be configured via [`set_cache_home`](Self::set_cache_home). + /// + /// [XDG]: crate::os::unix::fs::HomeDirsExt + /// [Darwin]: crate::os::darwin::fs::HomeDirsExt + /// [Windows]: crate::os::windows::fs::HomeDirsExt + /// [`NSCachesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/cachesdirectory?language=objc + /// [`{FOLDERID_LocalAppData}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_localappdata + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn cache_home(&self) -> Option<&Path> { + self.cache.as_deref() + } + + /// A base directory relative to which user-specific configuration files + /// should be stored. + /// + /// "Config" files are configuration managed by the user, either by editing + /// the files directly or through a managing application. Configuration is + /// generally expected to be meaningful to the user and portable enough to + /// back up and synchronize across the same user's account on multiple + /// systems. + /// + /// This is the same directory for all applications. Applications should + /// use a subdirectory for application-specific configuration files. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `${XDG_CONFIG_HOME:-$HOME/.config}` | + /// | [Darwin] (macOS) | [`NSApplicationSupportDirectory`] (`$HOME/Library/Application Support`) | + /// | [Windows] | [`{FOLDERID_RoamingAppData}`] (`%APPDATA%`) | + /// + /// Other paths can be configured via [`set_config_home`](Self::set_config_home). + /// + /// [XDG]: crate::os::unix::fs::HomeDirsExt + /// [Darwin]: crate::os::darwin::fs::HomeDirsExt + /// [Windows]: crate::os::windows::fs::HomeDirsExt + /// [`NSApplicationSupportDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory?language=objc + /// [`{FOLDERID_RoamingAppData}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_roamingappdata + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn config_home(&self) -> Option<&Path> { + self.config.as_deref() + } + + /// A base directory relative to which user-specific data files should be + /// stored. + /// + /// "Data" files are application-specific data that is meaningful to the + /// user in some way and does not implicitly rely on system configuration + /// or details of how the application is installed otherwise irrelevant to + /// the user. As such, data makes sense to back up and synchronize between + /// the same user's account on multiple systems. If a file is specific to + /// a single machine, it's probably [state](Self::state_home). + /// + /// This is the same directory for all applications. Applications should + /// use a subdirectory for application-specific data files. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `${XDG_DATA_HOME:-$HOME/.local/share}` | + /// | [Darwin] (macOS) | [`NSApplicationSupportDirectory`] (`$HOME/Library/Application Support`) | + /// | [Windows] | [`{FOLDERID_RoamingAppData}`] (`%APPDATA%`) | + /// + /// Other paths can be configured via [`set_data_home`](Self::set_data_home). + /// + /// [XDG]: crate::os::unix::fs::HomeDirsExt + /// [Darwin]: crate::os::darwin::fs::HomeDirsExt + /// [Windows]: crate::os::windows::fs::HomeDirsExt + /// [`NSApplicationSupportDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory?language=objc + /// [`{FOLDERID_RoamingAppData}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_roamingappdata + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn data_home(&self) -> Option<&Path> { + self.data.as_deref() + } + + /// A base directory relative to which user-specific state files should be + /// stored. + /// + /// "State" files are data that should persist between application restarts, + /// but which is not important nor portable enough to the user to synchronize + /// between multiple systems like [data](Self::data_home) are. Common examples + /// include history (such as logs, recently used files, etc) and any current + /// state of the application that should be reused (such as view, layout, open + /// files, undo history, etc). + /// + /// This is the same directory for all applications. Applications should + /// use a subdirectory for application-specific state files. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `${XDG_STATE_HOME:-$HOME/.local/state}` | + /// | [Darwin] (macOS) | [`NSApplicationSupportDirectory`] (`$HOME/Library/Application Support`) | + /// | [Windows] | [`{FOLDERID_LocalAppData}`] (`%LOCALAPPDATA%`) | + /// + /// Other paths can be configured via [`set_state_home`](Self::set_state_home). + /// + /// [XDG]: crate::os::unix::fs::HomeDirsExt + /// [Darwin]: crate::os::darwin::fs::HomeDirsExt + /// [Windows]: crate::os::windows::fs::HomeDirsExt + /// [`NSApplicationSupportDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory?language=objc + /// [`{FOLDERID_LocalAppData}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_localappdata + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn state_home(&self) -> Option<&Path> { + self.state.as_deref() + } +} + +impl MediaDirs { + /// Create a known user directory set with no known directories. + /// + /// This is useful with the builder `set_*` methods to create a `MediaDirs` + /// with exactly the directories you want, without any other defaults. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn empty() -> Self { + Self { + desktop: None, + documents: None, + downloads: None, + music: None, + pictures: None, + videos: None, + extra: Default::default(), + } + } + + /// The OS-recognized user "Desktop" directory, often the `Desktop` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir DESKTOP` (`$HOME/Desktop`) | + /// | [Darwin] (macOS) | [`NSDesktopDirectory`] (`$HOME/Desktop`) | + /// | [Windows] | [`{FOLDERID_Desktop}`] (`%USERPROFILE%\Desktop`) | + /// + /// Other paths can be configured via [`set_desktop`](Self::set_desktop). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSDesktopDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/desktopdirectory?language=objc + /// [`{FOLDERID_Desktop}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_desktop + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn desktop(&self) -> Option<&Path> { + self.desktop.as_deref() + } + + /// The OS-recognized user "Documents" directory, often the `Documents` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir DOCUMENTS` (`$HOME/Documents`) | + /// | [Darwin] (macOS) | [`NSDocumentDirectory`] (`$HOME/Documents`) | + /// | [Windows] | [`{FOLDERID_Documents}`] (`%USERPROFILE%\Documents`) | + /// + /// Other paths can be configured via [`set_documents`](Self::set_documents). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSDocumentDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/documentdirectory?language=objc + /// [`{FOLDERID_Documents}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_documents + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn documents(&self) -> Option<&Path> { + self.documents.as_deref() + } + + /// The OS-recognized user "Downloads" directory, often the `Downloads` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir DOWNLOAD` (`$HOME/Downloads`) | + /// | [Darwin] (macOS) | [`NSDownloadsDirectory`] (`$HOME/Downloads`) | + /// | [Windows] | [`{FOLDERID_Downloads}`] (`%USERPROFILE%\Downloads`) | + /// + /// Other paths can be configured via [`set_downloads`](Self::set_downloads). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSDownloadsDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/downloadsdirectory?language=objc + /// [`{FOLDERID_Downloads}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_downloads + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn downloads(&self) -> Option<&Path> { + self.downloads.as_deref() + } + + /// The OS-recognized user "Music" directory, often the `Music` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir MUSIC` (`$HOME/Music`) | + /// | [Darwin] (macOS) | [`NSMusicDirectory`] (`$HOME/Music`) | + /// | [Windows] | [`{FOLDERID_Music}`] (`%USERPROFILE%\Music`) | + /// + /// Other paths can be configured via [`set_music`](Self::set_music). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSMusicDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/musicdirectory?language=objc + /// [`{FOLDERID_Music}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_music + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn music(&self) -> Option<&Path> { + self.music.as_deref() + } + + /// The OS-recognized user "Pictures" directory, often the `Pictures` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir PICTURES` (`$HOME/Pictures`) | + /// | [Darwin] (macOS) | [`NSPicturesDirectory`] (`$HOME/Pictures`) | + /// | [Windows] | [`{FOLDERID_Pictures}`] (`%USERPROFILE%\Pictures`) | + /// + /// Other paths can be configured via [`set_pictures`](Self::set_pictures). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSPicturesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/picturesdirectory?language=objc + /// [`{FOLDERID_Pictures}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_pictures + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn pictures(&self) -> Option<&Path> { + self.pictures.as_deref() + } + + /// The OS-recognized user "Videos" directory, often the `Videos` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + /// + /// # Platform-specific behavior + /// + /// When constructed using platform-specific conventions, the value is: + /// + /// | OS | Path | + /// | -- | ---- | + /// | [XDG] (Linux) | `xdg-user-dir VIDEOS` (`$HOME/Videos`) | + /// | [Darwin] (macOS) | [`NSMoviesDirectory`] (`$HOME/Movies`) | + /// | [Windows] | [`{FOLDERID_Videos}`] (`%USERPROFILE%\Videos`) | + /// + /// Other paths can be configured via [`set_videos`](Self::set_videos). + /// + /// [XDG]: crate::os::unix::fs::MediaDirsExt + /// [Darwin]: crate::os::darwin::fs::MediaDirsExt + /// [Windows]: crate::os::windows::fs::MediaDirsExt + /// [`NSMoviesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/moviesdirectory?language=objc + /// [`{FOLDERID_Videos}`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_videos + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn videos(&self) -> Option<&Path> { + self.videos.as_deref() + } +} + +impl HomeDirs { + /// Set the path for [Self::cache_home]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn set_cache_home(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "cache home directory path must be absolute"); + self.cache = Some(path); + self + } + + /// Set the path for [Self::config_home]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn set_config_home(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "config home directory path must be absolute"); + self.config = Some(path); + self + } + + /// Set the path for [Self::data_home]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn set_data_home(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "data home directory path must be absolute"); + self.data = Some(path); + self + } + + /// Set the path for [Self::state_home]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + pub fn set_state_home(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "state home directory path must be absolute"); + self.state = Some(path); + self + } +} + +impl MediaDirs { + /// Set the path for [Self::desktop]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_desktop(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "desktop directory path must be absolute"); + self.desktop = Some(path); + self + } + + /// Set the path for [Self::documents]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_documents(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "documents directory path must be absolute"); + self.documents = Some(path); + self + } + + /// Set the path for [Self::downloads]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_downloads(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "downloads directory path must be absolute"); + self.downloads = Some(path); + self + } + + /// Set the path for [Self::music]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_music(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "music directory path must be absolute"); + self.music = Some(path); + self + } + + /// Set the path for [Self::pictures]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_pictures(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "pictures directory path must be absolute"); + self.pictures = Some(path); + self + } + + /// Set the path for [Self::videos]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + pub fn set_videos(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "videos directory path must be absolute"); + self.videos = Some(path); + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_dir(what: &str) -> PathBuf { + crate::env::current_dir().unwrap().ancestors().last().unwrap().join(what) + } + + #[test] + fn test_home_dirs_field_hookup_matches() { + let mut dirs = HomeDirs::empty(); + + assert_eq!(dirs.config_home(), None); + assert_eq!(dirs.data_home(), None); + assert_eq!(dirs.state_home(), None); + assert_eq!(dirs.cache_home(), None); + + let config = test_dir("config"); + let data = test_dir("data"); + let state = test_dir("state"); + let cache = test_dir("cache"); + + dirs.set_config_home(config.clone()); + dirs.set_data_home(data.clone()); + dirs.set_state_home(state.clone()); + dirs.set_cache_home(cache.clone()); + + assert_eq!(dirs.config_home(), Some(config.as_ref())); + assert_eq!(dirs.data_home(), Some(data.as_ref())); + assert_eq!(dirs.state_home(), Some(state.as_ref())); + assert_eq!(dirs.cache_home(), Some(cache.as_ref())); + } + + #[test] + fn test_media_dirs_field_hookup_matches() { + let mut dirs = MediaDirs::empty(); + + assert_eq!(dirs.desktop(), None); + assert_eq!(dirs.documents(), None); + assert_eq!(dirs.downloads(), None); + assert_eq!(dirs.music(), None); + assert_eq!(dirs.pictures(), None); + assert_eq!(dirs.videos(), None); + + let desktop = test_dir("desktop"); + let documents = test_dir("documents"); + let downloads = test_dir("downloads"); + let music = test_dir("music"); + let pictures = test_dir("pictures"); + let videos = test_dir("videos"); + + dirs.set_desktop(desktop.clone()); + dirs.set_documents(documents.clone()); + dirs.set_downloads(downloads.clone()); + dirs.set_music(music.clone()); + dirs.set_pictures(pictures.clone()); + dirs.set_videos(videos.clone()); + + assert_eq!(dirs.desktop(), Some(desktop.as_ref())); + assert_eq!(dirs.documents(), Some(documents.as_ref())); + assert_eq!(dirs.downloads(), Some(downloads.as_ref())); + assert_eq!(dirs.music(), Some(music.as_ref())); + assert_eq!(dirs.pictures(), Some(pictures.as_ref())); + assert_eq!(dirs.videos(), Some(videos.as_ref())); + } +} diff --git a/library/std/src/os/darwin/fs.rs b/library/std/src/os/darwin/fs.rs index fc2869d6b13f6..b41391f19dccf 100644 --- a/library/std/src/os/darwin/fs.rs +++ b/library/std/src/os/darwin/fs.rs @@ -7,6 +7,13 @@ use crate::fs::{self, Metadata}; use crate::sys::{AsInner, AsInnerMut, IntoInner}; use crate::time::SystemTime; +mod dirs; + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub use dirs::HomeDirsExt; +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub use dirs::MediaDirsExt; + /// OS-specific extensions to [`fs::Metadata`]. /// /// [`fs::Metadata`]: crate::fs::Metadata diff --git a/library/std/src/os/darwin/fs/dirs.rs b/library/std/src/os/darwin/fs/dirs.rs new file mode 100644 index 0000000000000..6aaa910981b0b --- /dev/null +++ b/library/std/src/os/darwin/fs/dirs.rs @@ -0,0 +1,313 @@ +use crate::env; +use crate::fs::{HomeDirs, MediaDirs}; +use crate::io::{self, ErrorKind, const_error}; +use crate::path::PathBuf; + +/// Darwin-specific extensions to [`fs::HomeDirs`](HomeDirs). +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub impl(self) trait HomeDirsExt: Sized { + /// Load the standard user directory paths for the current user. + /// + /// On iOS, tvOS, watchOS, visionOS, and sandboxed macOS applications, + /// these directories are within the application's container. Outside + /// the sandbox, these are subdirectories of the `~/Library` directory on + /// macOS. + /// + /// The produced directory paths are not guaranteed to be the canonical + /// paths to the directories; they are allowed to be sandbox-redirected + /// paths as long as the directory is accessible there. + /// + /// The loaded common directories are: + /// + /// | `HomeDirs` | [`NSSearchPathDirectory`] | + /// | ---------- | ----------------------- | + /// | [`cache_home`] | [`NSCachesDirectory`] (`~/Library/Caches`) | + /// | [`config_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) | + /// | [`data_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) | + /// | [`state_home`] | [`NSApplicationSupportDirectory`] (`~/Library/Application Support`) | + /// + /// Note that the Application Support directory is used for the config, + /// data, and state directories. It is always possible for multiple user + /// directories to be configured to the same path, but this is the common + /// configuration on Apple platforms, making it even more important to not + /// assume files in different user directories cannot alias each other. + /// + /// # Errors + /// + /// Errors if the [user home](env::home_dir) cannot be determined. + // Errors due to the underlying sysdir(3) API should never occur, as + // - the user domain only has one directory for each search path; + // - the user domain always returns subdirectory paths of `~`; and + // - the username and OS defined path segments are always valid UTF-8. + /// + /// # Implementation-specific behavior + /// + /// Uses the `sysdir(3)` API from `libSystem` to discover the standard + /// user directories. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load additional common + /// directories not currently in this list. + /// + /// [`cache_home`]: HomeDirs::cache_home + /// [`config_home`]: HomeDirs::config_home + /// [`data_home`]: HomeDirs::data_home + /// [`state_home`]: HomeDirs::state_home + /// + /// [`NSSearchPathDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory?language=objc + /// [`NSCachesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/cachesdirectory?language=objc + /// [`NSApplicationSupportDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory?language=objc + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn sysdir() -> io::Result; +} + +/// Darwin-specific extensions to [`fs::MediaDirs`](MediaDirs). +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub impl(self) trait MediaDirsExt: Sized { + /// Load the standard user directory paths for the current user. + /// + /// The produced directory paths are not guaranteed to be the canonical + /// paths to the directories; they are allowed to be sandbox-redirected + /// paths as long as the directory is accessible there. + /// + /// The loaded common directories are: + /// + /// | `MediaDirs` | [`NSSearchPathDirectory`] | + /// | ---------- | ----------------------- | + /// | [`desktop`] | [`NSDesktopDirectory`] (`~/Desktop`) | + /// | [`documents`] | [`NSDocumentDirectory`] (`~/Documents`) | + /// | [`downloads`] | [`NSDownloadsDirectory`] (`~/Downloads`) | + /// | [`music`] | [`NSMusicDirectory`] (`~/Music`) | + /// | [`pictures`] | [`NSPicturesDirectory`] (`~/Pictures`) | + /// | [`videos`] | [`NSMoviesDirectory`] (`~/Movies`) | + /// + /// # Errors + /// + /// Errors if the the [user home](env::home_dir) cannot be determined. + // Errors due to the underlying sysdir(3) API should never occur, as + // - the user domain only has one directory for each search path; + // - the user domain always returns subdirectory paths of `~`; + // - the username plus OS defined path segments cannot exceed PATH_MAX; and + // - the username and OS defined path segments are always valid UTF-8. + /// + /// # Implementation-specific behavior + /// + /// Uses the `sysdir(3)` API from `libSystem` to discover the standard + /// user directories. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load additional common + /// directories not currently in this list. + /// + /// [`desktop`]: MediaDirs::desktop + /// [`documents`]: MediaDirs::documents + /// [`downloads`]: MediaDirs::downloads + /// [`music`]: MediaDirs::music + /// [`pictures`]: MediaDirs::pictures + /// [`videos`]: MediaDirs::videos + /// + /// [`NSSearchPathDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory?language=objc + /// [`NSDesktopDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/desktopdirectory?language=objc + /// [`NSDocumentDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/documentdirectory?language=objc + /// [`NSDownloadsDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/downloadsdirectory?language=objc + /// [`NSMusicDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/musicdirectory?language=objc + /// [`NSPicturesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/picturesdirectory?language=objc + /// [`NSMoviesDirectory`]: https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/moviesdirectory?language=objc + #[unstable(feature = "fs_media_dirs", issue = "162083")] + fn sysdir() -> io::Result; +} + +fn user_home() -> io::Result { + env::home_dir() + .filter(|p| p.is_absolute()) + .ok_or(const_error!(ErrorKind::InvalidData, "home path not absolute")) +} + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +#[cfg(target_vendor = "apple")] +impl HomeDirsExt for HomeDirs { + fn sysdir() -> io::Result { + use libc::sysdir_search_path_directory_t::*; + + let mut dirs = HomeDirs::empty(); + let home = user_home()?; + + let caches = sys::get_user_dir(&home, SYSDIR_DIRECTORY_CACHES)?; + let application_support = sys::get_user_dir(&home, SYSDIR_DIRECTORY_APPLICATION_SUPPORT)?; + + dirs.cache = caches; + // Apple puts config/data/state all in Application Support + dirs.config = application_support.clone(); + dirs.data = application_support.clone(); + dirs.state = application_support; + + Ok(dirs) + } +} + +#[unstable(feature = "fs_media_dirs", issue = "162083")] +#[cfg(target_vendor = "apple")] +impl MediaDirsExt for MediaDirs { + fn sysdir() -> io::Result { + use libc::sysdir_search_path_directory_t::*; + + let mut dirs = MediaDirs::empty(); + let home = user_home()?; + + let desktop = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DESKTOP)?; + let documents = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DOCUMENT)?; + let downloads = sys::get_user_dir(&home, SYSDIR_DIRECTORY_DOWNLOADS)?; + let movies = sys::get_user_dir(&home, SYSDIR_DIRECTORY_MOVIES)?; + let music = sys::get_user_dir(&home, SYSDIR_DIRECTORY_MUSIC)?; + let pictures = sys::get_user_dir(&home, SYSDIR_DIRECTORY_PICTURES)?; + + dirs.desktop = desktop; + dirs.documents = documents; + dirs.downloads = downloads; + dirs.music = music; + dirs.pictures = pictures; + dirs.videos = movies; + + Ok(dirs) + } +} + +/// Safer wrapper around the sysdir(3) API +#[cfg(target_vendor = "apple")] +mod sys { + use crate::ffi::{CStr, c_char}; + use crate::io::{self, ErrorKind, const_error}; + use crate::path::{Path, PathBuf}; + + /// Get the path for a system directory using `sysdir(3)`. + pub fn get_user_dir( + home: &Path, + kind: libc::sysdir_search_path_directory_t, + ) -> io::Result> { + use libc::sysdir_search_path_domain_mask_t::SYSDIR_DOMAIN_MASK_USER; + + // SAFETY: SYSDIR_DOMAIN_MASK_USER < SYSDIR_DOMAIN_MASK_ALL + let mut iter = unsafe { Iter::new(home, kind, SYSDIR_DOMAIN_MASK_USER) }; + let Some(path) = iter.next() else { + return Ok(None); + }; + let path = path?; + + if iter.next().is_some() { + // more than one path returned (shouldn't happen for SYSDIR_DOMAIN_MASK_USER) + return Err(const_error!( + ErrorKind::InvalidData, + "multiple paths returned for standard user directory", + )); + } + + Ok(Some(path)) + } + + struct Iter<'a> { + home: &'a Path, + state: libc::sysdir_search_path_enumeration_state, + } + + impl Drop for Iter<'_> { + fn drop(&mut self) { + for _ in self {} + } + } + + impl<'a> Iter<'a> { + // SAFETY: `mask` must be <= `SYSDIR_DOMAIN_MASK_ALL` + pub unsafe fn new( + home: &'a Path, + kind: libc::sysdir_search_path_directory_t, + mask: libc::sysdir_search_path_domain_mask_t, + ) -> Self { + // SAFETY: forwarded to the caller + let state = unsafe { libc::sysdir_start_search_path_enumeration(kind, mask) }; + Self { home, state } + } + } + + impl Iterator for Iter<'_> { + type Item = io::Result; + + fn next(&mut self) -> Option> { + let mut buf = [0u8; libc::PATH_MAX as usize]; + if self.state != 0 { + // SAFETY: `self.state` is nonzero and comes from prior sysdir_{start|get_next}_search_path_enumeration call + // SAFETY: sysdir_get_next_search_path_enumeration will write at most `PATH_MAX` bytes to `path` + self.state = unsafe { + libc::sysdir_get_next_search_path_enumeration( + self.state, + buf.as_mut_ptr() as *mut c_char, + ) + }; + } + + if self.state == 0 { + // exhausted + return None; + } + + let Ok(path) = CStr::from_bytes_until_nul(&buf) else { + // should be impossible given home-relative paths, but be defensive + return Some(Err(const_error!( + ErrorKind::InvalidData, + "standard user directory path too long", + ))); + }; + + let Ok(path) = path.to_str() else { + // should be impossible on a working system, but be defensive + return Some(Err(const_error!( + ErrorKind::InvalidData, + "standard user directory path not valid UTF-8", + ))); + }; + + // expand `~` shorthand + Some(match path { + "~" => Ok(self.home.into()), + _ if path.starts_with("~/") => Ok(self.home.join(&path[2..])), + _ if path.starts_with("~") => Err(const_error!( + ErrorKind::InvalidData, + "standard user directory relative to different user", + )), + _ => { + let path = PathBuf::from(path); + if path.is_relative() { + Err(const_error!( + ErrorKind::InvalidData, + "standard user directory path not absolute", + )) + } else { + Ok(path) + } + } + }) + } + } +} + +#[cfg(test)] +#[cfg(target_vendor = "apple")] +mod tests { + use super::*; + + #[test] + fn can_fetch_sysdir_paths() { + let dirs = HomeDirs::sysdir().unwrap(); + assert!(dirs.cache_home().is_some()); + assert!(dirs.config_home().is_some()); + assert!(dirs.data_home().is_some()); + assert!(dirs.state_home().is_some()); + + let dirs = MediaDirs::sysdir().unwrap(); + assert!(dirs.desktop().is_some()); + assert!(dirs.documents().is_some()); + assert!(dirs.downloads().is_some()); + assert!(dirs.music().is_some()); + assert!(dirs.pictures().is_some()); + assert!(dirs.videos().is_some()); + } +} diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index b93f94225070a..fcafe1c29797f 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -22,6 +22,13 @@ use crate::{io, sys}; #[cfg(test)] mod tests; +pub(crate) mod dirs; + +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub use dirs::MediaDirsExt; +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub use dirs::{HomeDirsExt, XdgDirs}; + /// Unix-specific extensions to [`fs::File`]. #[stable(feature = "file_offset", since = "1.15.0")] pub trait FileExt { diff --git a/library/std/src/os/unix/fs/dirs.rs b/library/std/src/os/unix/fs/dirs.rs new file mode 100644 index 0000000000000..5d8e13c7a730a --- /dev/null +++ b/library/std/src/os/unix/fs/dirs.rs @@ -0,0 +1,585 @@ +use crate::env::{self, SplitPathsRef}; +use crate::ffi::{OsStr, OsString}; +use crate::fs::{self, HomeDirs, MediaDirs}; +use crate::io::{self, ErrorKind, const_error}; +use crate::path::{Path, PathBuf}; + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ExtraHomeDirs { + runtime: Option, + config_path: Option, + data_path: Option, +} + +#[derive(Debug, Default, Clone)] +#[non_exhaustive] +pub struct ExtraMediaDirs { + templates: Option, +} + +/// XDG-specific extensions to [`fs::HomeDirs`](HomeDirs). +/// +/// The XDG conventions are defined by the Freedesktop.org project in the +/// [XDG Base Directory Specification][xdg-basedir]. These conventions have +/// been largely adopted by Linux distributions. +/// +/// The XDG conventions are written to be usable on any Unix-like filesystem, +/// thus this extension being provided in `os::unix` rather than `os::linux`. +/// However, while some tooling does use XDG conventions on macOS, note that +/// macOS has its own separate conventions for user directories. Consider +/// carefully what conventions your users will expect your application to +/// follow along with any legacy path compatibility you might need to support. +/// +/// [xdg-basedir]: https://specifications.freedesktop.org/basedir/ +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub impl(self) trait HomeDirsExt: Sized { + /// Load the user directory paths according to the + /// [XDG Base Directory Specification][xdg-basedir]. + /// + /// Each base directory path is set to the value of its corresponding + /// `XDG_*` environment variable (if it is set and non-empty), else to + /// the default value defined by the specification. + /// + /// | Field | Environment Variable | Default Value | + /// | ----- | -------------------- | ------------- | + /// | [`cache_home`] | `XDG_CACHE_HOME` | `$HOME/.cache` | + /// | [`config_home`] | `XDG_CONFIG_HOME` | `$HOME/.config` | + /// | [`data_home`] | `XDG_DATA_HOME` | `$HOME/.local/share` | + /// | [`state_home`] | `XDG_STATE_HOME` | `$HOME/.local/state` | + /// | [`runtime_home`] | `XDG_RUNTIME_DIR` | (see method docs) | + /// | [`config_dirs`] | `XDG_CONFIG_DIRS` | `/etc/xdg` | + /// | [`data_dirs`] | `XDG_DATA_DIRS` | `/usr/local/share/`, `/usr/share/` | + /// + /// Note that `$HOME` here means [`env::home_dir`], which uses + /// `$HOME` if set and non-empty, but falls back to the system password + /// database if it isn't set. + /// + /// All paths are required to be absolute. If a relative path is configured + /// by the environment, it is ignored and the default value is used instead. + /// + /// `config_dirs` and `data_dirs` are a list of delimited paths using the + /// [`env::split_paths`] delimiter. If some but not all paths in the list are + /// relative, those relative paths are ignored and the remaining absolute + /// paths are used. If there are no valid absolute paths, the default value + /// is used instead. + /// + /// # Errors + /// + /// Errors if the user's home directory cannot be determined. + /// + /// [xdg-basedir]: https://specifications.freedesktop.org/basedir/ + /// [`cache_home`]: HomeDirs::cache_home + /// [`config_home`]: HomeDirs::config_home + /// [`data_home`]: HomeDirs::data_home + /// [`state_home`]: HomeDirs::state_home + /// [`runtime_home`]: HomeDirsExt::runtime_home + /// [`config_dirs`]: HomeDirsExt::config_dirs + /// [`data_dirs`]: HomeDirsExt::data_dirs + /// [`env::split_paths`]: crate::env::split_paths + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn xdg() -> io::Result; + + /// A base directory relative to which user-specific runtime files + /// (such as sockets, named pipes, etc) should be stored. + /// + /// Files in this directory may be subjected to periodic clean-up. + /// Larger files should not be placed here, since it might reside in + /// runtime memory and cannot necessarily be swapped out to disk. + /// + /// This path does not have a default if not set. If it isn't set, + /// applications should fall back to a replacement directory with + /// similar capabilities and print a warning message. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn runtime_home(&self) -> Option<&Path>; + + /// A preference-ordered list of base directories to search for config + /// files *in addition to* [`config_home`]. + /// + /// The order of directories denotes their importance; the first directory + /// is the most important. Information defined relative to the more + /// important base directory takes precedent. [`config_home`] is not + /// necessarily present in this list, and is considered more important + /// than any base directory in this list. + /// + /// [`config_home`]: HomeDirs::config_home + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn config_dirs(&self) -> Option>; + + /// A preference-ordered list of base directories to search for data + /// files *in addition to* [`data_home`]. + /// + /// The order of directories denotes their importance; the first directory + /// is the most important. Information defined relative to the more + /// important base directory takes precedent. [`data_home`] is not + /// necessarily present in this list, and is considered more important + /// than any base directory in this list. + /// + /// [`data_home`]: HomeDirs::data_home + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn data_dirs(&self) -> Option>; + + /// Set the path for [Self::runtime_home]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self; + + /// Set the paths for [Self::config_dirs]. + /// + /// Takes one or more paths joined appropriately for the `PATH` environment + /// variable, as by [`env::join_paths`]. + /// + /// # Panics + /// + /// Panics if any of the provided paths are not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn set_config_dirs(&mut self, paths: OsString) -> &mut Self; + + /// Set the paths for [Self::data_dirs]. + /// + /// Takes one or more paths joined appropriately for the `PATH` environment + /// variable, as by [`env::join_paths`]. + /// + /// # Panics + /// + /// Panics if any of the provided paths are not absolute. + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn set_data_dirs(&mut self, paths: OsString) -> &mut Self; +} + +/// XDG-specific extensions to [`fs::MediaDirs`](MediaDirs). +/// +/// The XDG conventions are defined by the Freedesktop.org project through the +/// [xdg-user-dirs] tool. This configuration is generally present on desktop +/// Linux distributions, although adoption is less widespread than the base +/// directory specification. +/// +/// The XDG conventions are written to be usable on any Unix-like filesystem, +/// thus this extension being provided in `os::unix` rather than `os::linux`. +/// However, while some tooling does use XDG conventions on macOS, note that +/// macOS has its own separate conventions for user directories. Consider +/// carefully what conventions your users will expect your application to +/// follow along with any legacy path compatibility you might need to support. +/// +/// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/ +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub impl(self) trait MediaDirsExt: Sized { + /// Load the user directory paths according to the [xdg-user-dirs] tool. + /// + /// This directly reads and parses the `$XDG_CONFIG_HOME/user-dirs.dirs` + /// file as defined and maintained by the [xdg-user-dirs] tool. + /// + /// # Errors + /// + /// Errors if the user's home directory cannot be determined or if the + /// `$XDG_CONFIG_HOME/user-dirs.dirs` file cannot be read. + /// + /// # Implementation-specific behavior + /// + /// Only the format maintained by xdg-user-dirs-update is supported. Any + /// configuration that does not match the expected format will result in + /// loading an unspecified path or `None` for that directory. To be more + /// specific: + /// + /// - Any line not in the format of `XDG_{NAME}_DIR={path}` where `{NAME}` + /// is one of `DESKTOP`, `DOWNLOAD`, `TEMPLATES`, `PUBLICSHARE`, + /// `DOCUMENTS`, `MUSIC`, `PICTURES`, or `VIDEOS` is ignored. + /// - `{path}` must be a `"`-quoted shell-escaped path. + /// - `{path}` may only start with `/` or `$HOME/`. A home-relative path + /// is returned relative to [`env::home_dir`]; shell expansion is not + /// performed. + /// - A directory set to just `$HOME` marks it as removed, and results in + /// a `None` value for that path. + /// - If shell expansion syntax other than a leading `$HOME` is present, + /// the produced directory path is unspecified. This is invalid config + /// according to the xdg-user-dirs tooling. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load paths that we currently + /// ignore, such as path formats that are not canonically supported by + /// xdg-user-dirs but which may occur in manually-edited `user-dirs.dirs`. + /// + /// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/ + #[unstable(feature = "fs_media_dirs", issue = "162083")] + fn xdg() -> io::Result; + + /// The OS-privileged user "Templates" directory, often the `Templates` + /// folder in the user's home directory. + /// + /// As a media directory, this should typically be used as a default path + /// for file selection dialogs, not for automatically accessed file paths. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + fn templates(&self) -> Option<&Path>; + + /// Set the paths for [Self::templates]. + /// + /// # Panics + /// + /// Panics if the provided path is not absolute. + #[unstable(feature = "fs_media_dirs", issue = "162083")] + fn set_templates(&mut self, path: PathBuf) -> &mut Self; +} + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +#[derive(Debug)] +pub struct XdgDirs<'a>(SplitPathsRef<'a>); + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +impl HomeDirsExt for HomeDirs { + fn xdg() -> io::Result { + let mut dirs = HomeDirs::empty(); + let user_home = xdg::user_home()?; + + dirs.set_cache_home(xdg::dir_or_else(|| user_home.join(".cache"), "XDG_CACHE_HOME")); + dirs.set_config_home(xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME")); + dirs.set_data_home(xdg::dir_or_else(|| user_home.join(".local/share"), "XDG_DATA_HOME")); + dirs.set_state_home(xdg::dir_or_else(|| user_home.join(".local/state"), "XDG_STATE_HOME")); + if let Some(runtime) = xdg::dir("XDG_RUNTIME_DIR") { + dirs.set_runtime_home(runtime); + } + + dirs.set_config_dirs(xdg::dirs_or("/etc/xdg", "XDG_CONFIG_DIRS")); + dirs.set_data_dirs(xdg::dirs_or("/usr/local/share/:/usr/share/", "XDG_DATA_DIRS")); + + Ok(dirs) + } + + fn runtime_home(&self) -> Option<&Path> { + self.extra.runtime.as_deref() + } + + fn config_dirs(&self) -> Option> { + self.extra.config_path.as_deref().map(XdgDirs::new) + } + + fn data_dirs(&self) -> Option> { + self.extra.data_path.as_deref().map(XdgDirs::new) + } + + fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "runtime directory path must be absolute"); + self.extra.runtime = Some(path); + self + } + + fn set_config_dirs(&mut self, paths: OsString) -> &mut Self { + for path in split_paths_ref(&paths) { + assert!(path.is_absolute(), "config directory paths must be absolute"); + } + self.extra.config_path = Some(paths); + self + } + + fn set_data_dirs(&mut self, paths: OsString) -> &mut Self { + for path in split_paths_ref(&paths) { + assert!(path.is_absolute(), "data directory paths must be absolute"); + } + self.extra.data_path = Some(paths); + self + } +} + +#[unstable(feature = "fs_media_dirs", issue = "162083")] +impl MediaDirsExt for MediaDirs { + fn xdg() -> io::Result { + let user_home = xdg::user_home()?; + let config_home = xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME"); + + let spec = match fs::read(config_home.join("user-dirs.dirs")) { + Ok(spec) => spec, + Err(e) if e.kind() == ErrorKind::NotFound => { + return Err(const_error!( + ErrorKind::NotFound, + "missing `$XDG_CONFIG_HOME/user-dirs.dirs`", + )); + } + Err(e) => return Err(e), + }; + + Ok(xdg::parse_user_dirs(&spec, &user_home)) + } + + fn templates(&self) -> Option<&Path> { + self.extra.templates.as_deref() + } + + fn set_templates(&mut self, path: PathBuf) -> &mut Self { + assert!(path.is_absolute(), "templates directory path must be absolute"); + self.extra.templates = Some(path); + self + } +} + +impl<'a> XdgDirs<'a> { + fn new(paths: &'a OsStr) -> Self { + XdgDirs(split_paths_ref(paths)) + } +} + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +impl<'a> Iterator for XdgDirs<'a> { + type Item = &'a Path; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +fn split_paths_ref<'a>(paths: &'a OsStr) -> SplitPathsRef<'a> { + // returns Some on cfg(unix) + env::split_paths_ref(paths).unwrap() +} + +mod xdg { + use super::{MediaDirs, split_paths_ref}; + use crate::env::{self, join_paths, var_os}; + use crate::ffi::OsString; + use crate::io::{self, ErrorKind, const_error}; + use crate::ops::Deref; + use crate::os::unix::ffi::{OsStrExt, OsStringExt}; + use crate::path::{Path, PathBuf}; + + pub fn user_home() -> io::Result { + env::home_dir() + .filter(|p| p.is_absolute()) + .ok_or(const_error!(ErrorKind::InvalidData, "user home directory path not absolute")) + } + + pub fn dir(env: &str) -> Option { + var_os(env).filter(|s| !s.is_empty()).map(PathBuf::from).filter(|path| path.is_absolute()) + } + + pub fn dir_or_else(fallback: impl FnOnce() -> PathBuf, env: &str) -> PathBuf { + dir(env).unwrap_or_else(fallback) + } + + fn dirs(env: &str) -> Option { + let dirs = var_os(env).filter(|s| !s.is_empty())?; + if split_paths_ref(&dirs).all(|p| p.is_absolute()) { + return Some(dirs); + } + + struct DerefAsRef(pub T); + impl>, U: ?Sized> AsRef for DerefAsRef { + fn as_ref(&self) -> &U { + (*self.0).as_ref() + } + } + + let paths = split_paths_ref(&dirs).filter(|p| p.is_absolute()).map(DerefAsRef); + join_paths(paths).ok().filter(|s| !s.is_empty()) + } + + pub fn dirs_or(fallback: impl Into, env: &str) -> OsString { + dirs(env).unwrap_or_else(|| fallback.into()) + } + + pub fn parse_user_dirs<'a>(spec: &'a [u8], user_home: &Path) -> MediaDirs { + let mut dirs = MediaDirs::empty(); + + for (xdg, path) in + spec.split(|&b| b == b'\n').flat_map(|line| parse_user_dirs_line(line, user_home)) + { + // load the known user directories + match xdg { + b"XDG_DESKTOP_DIR" => dirs.desktop = path, + b"XDG_DOCUMENTS_DIR" => dirs.documents = path, + b"XDG_DOWNLOAD_DIR" => dirs.downloads = path, + b"XDG_MUSIC_DIR" => dirs.music = path, + b"XDG_PICTURES_DIR" => dirs.pictures = path, + b"XDG_VIDEOS_DIR" => dirs.videos = path, + b"XDG_TEMPLATES_DIR" => dirs.extra.templates = path, + b"XDG_PUBLICSHARE_DIR" => { + // we don't expose this directory yet as Windows also has a "public" directory + // and it's not yet clear if we want to expose this at the target-agnostic level + } + _ => { + // ignore unknown variable assignment + } + } + } + + dirs + } + + fn parse_user_dirs_line<'a>( + line: &'a [u8], + user_home: &Path, + ) -> Option<(&'a [u8], Option)> { + // trim whitespace + let trimmed = line.trim_ascii(); + // skip empty lines and comments + if trimmed.is_empty() || trimmed.starts_with(&[b'#']) { + return None; + } + + // only variable assignment lines are allowed; split on `=` + let mut split = trimmed.splitn(2, |&b| b == b'='); + // extract assignment parts; ignore lines not in this format + let var = split.next()?; + let val = split.next()?; + debug_assert_eq!(split.next(), None); + + // the path value is quoted; unquote it + let path = unquote(val, user_home)?; + + let path = Some(path) + // ignore non-absolute paths + .filter(|path| path.is_absolute()) + // setting to the home dir disables the directory configuration + .filter(|path| path != user_home); + + Some((var, path)) + } + + fn unquote(bytes: &[u8], user_home: &Path) -> Option { + let [b'"', rest @ .., b'"'] = bytes else { return None }; + + // setting to the home dir disables the directory configuration; + // if done symbolically, return None here and bypass later work + if matches!(rest, b"$HOME" | b"$HOME/") { + return None; + } + + let mut rest = rest; + let mut s = Vec::with_capacity(rest.len()); + + // expand leading $HOME + if rest.starts_with(b"$HOME/") { + s.extend_from_slice(user_home.as_os_str().as_bytes()); + if !user_home.has_trailing_sep() { + s.push(b'/'); + } + rest = &rest[6..]; + } + + loop { + let i = rest + .iter() + .position(|&b| matches!(b, b'"' | b'\\' | b'$' | b'`')) + .unwrap_or(rest.len()); + s.extend_from_slice(&rest[..i]); + match &rest[i..] { + [] => break, + [b'\\', c @ (b'"' | b'\\' | b'$' | b'`'), tail @ ..] => { + // supported escapes + s.push(*c); + rest = tail; + } + [b'"' | b'\\' | b'$' | b'`', ..] => { + // unsupported shell syntax + return None; + } + _ => { + if cfg!(debug_assertions) { + unreachable!() + } else { + return None; + } + } + } + } + + Some(PathBuf::from(OsString::from_vec(s))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_fetch_xdg_base_dirs() { + let dirs = HomeDirs::xdg().unwrap(); + + assert!(dirs.cache_home().is_some()); + assert!(dirs.config_home().is_some()); + assert!(dirs.data_home().is_some()); + assert!(dirs.state_home().is_some()); + // dirs.runtime() may not exist + assert!(dirs.config_dirs().is_some()); + assert!(dirs.data_dirs().is_some()); + } + + #[test] + fn can_fetch_xdg_media_dirs() { + let dirs = match MediaDirs::xdg() { + Ok(dirs) => dirs, + Err(e) if e.kind() == ErrorKind::NotFound => { + // xdg-user-dirs not initialized on this system, skip the test + return; + } + Err(e) => panic!("failed to fetch xdg user dirs: {e:?}"), + }; + + // even when user-dirs.dirs is present, directories may be unset, + // so we can't assert anything about the dir paths here + let _ = dirs; + } + + #[test] + fn test_well_user_dirs_parsing() { + const TEST_USERDIRS: &'static str = r#" +# This file is written by xdg-user-dirs-update +# If you want to change or add directories, just edit the line you're +# interested in. All local changes will be retained on the next run. +# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped +# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an +# absolute path. No other format is supported. +# +XDG_DESKTOP_DIR="$HOME/Desktop" +XDG_DOWNLOAD_DIR="$HOME/Downloads" +XDG_TEMPLATES_DIR="/pub/Templates" +XDG_PUBLICSHARE_DIR="/pub" +XDG_DOCUMENTS_DIR="$HOME/Documents" +XDG_MUSIC_DIR="$HOME/My \"Music\"" +XDG_PICTURES_DIR="$HOME/Pictures" +XDG_VIDEOS_DIR="$HOME" +"#; + + let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris")); + + assert_eq!(dirs.desktop(), Some("/home/ferris/Desktop".as_ref())); + assert_eq!(dirs.downloads(), Some("/home/ferris/Downloads".as_ref())); + assert_eq!(dirs.templates(), Some("/pub/Templates".as_ref())); + assert_eq!(dirs.documents(), Some("/home/ferris/Documents".as_ref())); + assert_eq!(dirs.music(), Some("/home/ferris/My \"Music\"".as_ref())); + assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures".as_ref())); + assert_eq!(dirs.videos(), None); + } + + #[test] + fn test_ill_user_dirs_parsing() { + const TEST_USERDIRS: &'static str = r#" +# unquoted +XDG_DESKTOP_DIR=$HOME/Desktop +# mixed quoting +XDG_DOWNLOAD_DIR="$HOME"/"Downloads" +# non-HOME env +XDG_TEMPLATES_DIR="/home/$USER/Templates" +# missing PUBLICSHARE +# command substitution +XDG_DOCUMENTS_DIR="`pwd`/Documents" +# non-symbolic homedir disabled +XDG_MUSIC_DIR="/home/ferris" +# set multiple times +XDG_PICTURES_DIR="$HOME/Pictures1" +XDG_PICTURES_DIR="$HOME/Pictures2" +# relative path +XDG_VIDEOS_DIR="Videos" +"#; + + let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris")); + + assert_eq!(dirs.desktop(), None); + assert_eq!(dirs.downloads(), None); + assert_eq!(dirs.templates(), None); + assert_eq!(dirs.documents(), None); + assert_eq!(dirs.music(), None); + assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures2".as_ref())); + assert_eq!(dirs.videos(), None); + } +} diff --git a/library/std/src/os/windows/fs.rs b/library/std/src/os/windows/fs.rs index 21560638c1d0f..f452de6de449f 100644 --- a/library/std/src/os/windows/fs.rs +++ b/library/std/src/os/windows/fs.rs @@ -11,6 +11,13 @@ use crate::sys::{AsInner, AsInnerMut, FromInner, IntoInner}; use crate::time::SystemTime; use crate::{io, sys}; +mod dirs; + +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub use dirs::HomeDirsExt; +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub use dirs::MediaDirsExt; + /// Windows-specific extensions to [`fs::File`]. #[stable(feature = "file_offset", since = "1.15.0")] pub trait FileExt { diff --git a/library/std/src/os/windows/fs/dirs.rs b/library/std/src/os/windows/fs/dirs.rs new file mode 100644 index 0000000000000..52710375e6eb2 --- /dev/null +++ b/library/std/src/os/windows/fs/dirs.rs @@ -0,0 +1,309 @@ +use crate::fs::{HomeDirs, MediaDirs}; +use crate::io::{ErrorKind, const_error}; +use crate::path::PathBuf; +use crate::{env, io}; + +/// Windows-specific extensions to [`fs::HomeDirs`](HomeDirs). +#[unstable(feature = "fs_home_dirs", issue = "162082")] +pub impl(self) trait HomeDirsExt: Sized { + /// Load the known user folder paths from environment variables. + /// + /// The loaded known folders are: + /// + /// | `HomeDirs` | Environment Variable | + /// | ---------- | -------------------- | + /// | [`cache_home`] | `%LOCALAPPDATA%` (`%USERPROFILE%\AppData\Local`) | + /// | [`config_home`] | `%APPDATA%` (`%USERPROFILE%\AppData\Roaming`) | + /// | [`data_home`] | `%APPDATA%` (`%USERPROFILE%\AppData\Roaming`) | + /// | [`state_home`] | `%LOCALAPPDATA%` (`%USERPROFILE%\AppData\Local`) | + /// + /// Note that caches/state are both put in `AppData\Local`, and config/data + /// in `AppData\Roaming`. It is always possible for multiple user directories + /// to be configured to the same path, but this is the common configuration + /// on Windows platforms, making it even more important to not assume files + /// in different user directories cannot alias each other. + /// + /// # Errors + /// + /// Errors if `%APPDATA%` or `%LOCALAPPDATA%` are not set to absolute paths. + /// + /// # Implementation-specific behavior + /// + /// Windows keeps these environment variables updated to contain the paths + /// to the configured folder path, but it is possible for the environment + /// variables to not match the underlying system, such as when the user or + /// a program modifies the environment directly, or if the configuration + /// changed after the environment block was copied from the system. + /// + /// Unlike [`known_folders`](Self::known_folders), this does not require + /// `Shell32.dll` and thus does not require the overhead of linking in + /// DLLs that may result in Windows considering the application as a + /// graphical application. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load additional common + /// directories not currently in this list. + /// + /// [`cache_home`]: HomeDirs::cache_home + /// [`config_home`]: HomeDirs::config_home + /// [`data_home`]: HomeDirs::data_home + /// [`state_home`]: HomeDirs::state_home + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn appdata_env() -> io::Result; + + /// Load the known user folder paths using the [Known Folders] API. + /// + /// The loaded known folders are: + /// + /// | `HomeDirs` | [`KNOWNFOLDERID`] | + /// | ---------- | ----------------- | + /// | [`cache_home`] | [`FOLDERID_LocalAppData`] (`%LOCALAPPDATA%`) | + /// | [`config_home`] | [`FOLDERID_RoamingAppData`] (`%APPDATA%`) | + /// | [`data_home`] | [`FOLDERID_RoamingAppData`] (`%APPDATA%`) | + /// | [`state_home`] | [`FOLDERID_LocalAppData`] (`%LOCALAPPDATA%`) | + /// + /// Note that caches/state are both put in LocalAppData, and config/data + /// in RoamingAppData. It is always possible for multiple user directories + /// to be configured to the same path, but this is the common configuration + /// on Windows platforms, making it even more important to not assume files + /// in different user directories cannot alias each other. + /// + /// # Errors + /// + /// Errors if the underlying system discovery API returns an error. The + /// lack of a configured path is not considered an error and results in a + /// `None` value. + /// + /// # Implementation-specific behavior + /// + /// Calls [`SHGetKnownFolderPath`] for the current user once for each known + /// folder. Does not create the folder if missing. + /// + /// COM should be initialized on the thread that calls this function or + /// else it may return unexpected errors. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load additional common + /// directories not currently in this list. + /// + /// [Known Folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders + /// [`SHGetKnownFolderPath`]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + /// [`KNOWNFOLDERID`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid + /// [`FOLDERID_LocalAppData`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_localappdata + /// [`FOLDERID_RoamingAppData`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_roamingappdata + /// [`cache_home`]: HomeDirs::cache_home + /// [`config_home`]: HomeDirs::config_home + /// [`data_home`]: HomeDirs::data_home + /// [`state_home`]: HomeDirs::state_home + #[unstable(feature = "fs_home_dirs", issue = "162082")] + fn known_folders() -> io::Result; +} + +/// Windows-specific extensions to [`fs::MediaDirs`](MediaDirs). +#[unstable(feature = "fs_media_dirs", issue = "162083")] +pub impl(self) trait MediaDirsExt: Sized { + /// Load the known user folder paths using the [Known Folders] API. + /// + /// The loaded known folders are: + /// + /// | `MediaDirs` | [`KNOWNFOLDERID`] | + /// | ---------- | ----------------- | + /// | [`desktop`] | [`FOLDERID_Desktop`] (`%USERPROFILE%\Desktop`) | + /// | [`documents`] | [`FOLDERID_Documents`] (`%USERPROFILE%\Documents`) | + /// | [`downloads`] | [`FOLDERID_Downloads`] (`%USERPROFILE%\Downloads`) | + /// | [`music`] | [`FOLDERID_Music`] (`%USERPROFILE%\Music`) | + /// | [`pictures`] | [`FOLDERID_Pictures`] (`%USERPROFILE%\Pictures`) | + /// | [`videos`] | [`FOLDERID_Videos`] (`%USERPROFILE%\Videos`) | + /// + /// # Errors + /// + /// Errors if the underlying system discovery API returns an error. The + /// lack of a configured path is not considered an error and results in a + /// `None` value. + /// + /// # Implementation-specific behavior + /// + /// Calls [`SHGetKnownFolderPath`] for the current user once for each known + /// folder. Does not create the folder if missing. + /// + /// COM should be initialized on the thread that calls this function or + /// else it may return unexpected errors. + /// + /// This behavior may change in the future. One example change that we + /// explicitly reserve the right to make is to load additional common + /// directories not currently in this list. + /// + /// [Known Folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders + /// [`SHGetKnownFolderPath`]: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + /// [`KNOWNFOLDERID`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid + /// [`FOLDERID_Desktop`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_desktop + /// [`FOLDERID_Documents`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_documents + /// [`FOLDERID_Downloads`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_downloads + /// [`FOLDERID_Music`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_music + /// [`FOLDERID_Pictures`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_pictures + /// [`FOLDERID_Videos`]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#folderid_videos + /// [`desktop`]: MediaDirs::desktop + /// [`documents`]: MediaDirs::documents + /// [`downloads`]: MediaDirs::downloads + /// [`music`]: MediaDirs::music + /// [`pictures`]: MediaDirs::pictures + /// [`videos`]: MediaDirs::videos + #[unstable(feature = "fs_media_dirs", issue = "162083")] + fn known_folders() -> io::Result; +} + +#[cfg(windows)] +#[unstable(feature = "fs_home_dirs", issue = "162082")] +impl HomeDirsExt for HomeDirs { + fn appdata_env() -> io::Result { + let roaming_app_data = env::var_os("APPDATA") + .map(PathBuf::from) + .filter(|p| p.is_absolute()) + .ok_or(const_error!(ErrorKind::InvalidData, "non-absolute %APPDATA%"))?; + let local_app_data = env::var_os("LOCALAPPDATA") + .map(PathBuf::from) + .filter(|p| p.is_absolute()) + .ok_or(const_error!(ErrorKind::InvalidData, "non-absolute %LOCALAPPDATA%"))?; + + // AppData/Local -- system-local, doesn't make sense to sync to another + // AppData/Roaming -- data that makes sense to sync across machines + + let mut dirs = HomeDirs::empty(); + + dirs.cache = Some(local_app_data.clone()); + dirs.config = Some(roaming_app_data.clone()); + dirs.data = Some(roaming_app_data); + dirs.state = Some(local_app_data); + + Ok(dirs) + } + + fn known_folders() -> io::Result { + use crate::sys::c; + + let local_app_data = sys::get_known_folder_path(&c::FOLDERID_LocalAppData)?; + let roaming_app_data = sys::get_known_folder_path(&c::FOLDERID_RoamingAppData)?; + + // AppData/Local -- system-local, doesn't make sense to sync to another + // AppData/Roaming -- data that makes sense to sync across machines + + let mut dirs = HomeDirs::empty(); + + dirs.cache = local_app_data.clone(); + dirs.config = roaming_app_data.clone(); + dirs.data = roaming_app_data; + dirs.state = local_app_data; + + Ok(dirs) + } +} + +#[cfg(windows)] +#[unstable(feature = "fs_media_dirs", issue = "162083")] +impl MediaDirsExt for MediaDirs { + fn known_folders() -> io::Result { + use crate::sys::c; + + let desktop = sys::get_known_folder_path(&c::FOLDERID_Desktop)?; + let documents = sys::get_known_folder_path(&c::FOLDERID_Documents)?; + let downloads = sys::get_known_folder_path(&c::FOLDERID_Downloads)?; + let music = sys::get_known_folder_path(&c::FOLDERID_Music)?; + let pictures = sys::get_known_folder_path(&c::FOLDERID_Pictures)?; + let videos = sys::get_known_folder_path(&c::FOLDERID_Videos)?; + + let mut dirs = MediaDirs::empty(); + + dirs.desktop = desktop; + dirs.documents = documents; + dirs.downloads = downloads; + dirs.music = music; + dirs.pictures = pictures; + dirs.videos = videos; + + Ok(dirs) + } +} + +#[cfg(windows)] +mod sys { + use crate::io::{self, ErrorKind, const_error}; + use crate::path::PathBuf; + use crate::sys::{c, os2path}; + use crate::{ptr, slice}; + + /// Retrieve a known folder path from the Windows API. + pub fn get_known_folder_path(id: &c::GUID) -> io::Result> { + // Get the known folder path. hToken = NULL requests the current user + // scope, and we set KF_FLAG_DONT_VERIFY because it's a bit faster and + // we don't guarantee that the directories at the paths exist. + let mut pszPath = ptr::null_mut(); + // SAFETY: rfid/ppszPath are valid pointers, flags are appropriate, and + // a NULL hToken is supported by SHGetKnownFolderPath. + let hr = unsafe { + c::SHGetKnownFolderPath( + /* rfid */ id, + /* dwFlags */ c::KF_FLAG_DONT_VERIFY as _, + /* hToken */ ptr::null_mut(), + /* ppszPath */ &mut pszPath, + ) + }; + + let result = match hr { + c::S_OK => { + // SAFETY: pszPath was populated by a successful call to SHGetKnownFolderPath + // and is valid up to and including its nul terminator + let len = unsafe { c::lstrlenW(pszPath) }; + // SAFETY: *pszPath is valid up to and including its nul terminator + Ok(Some(os2path(unsafe { slice::from_raw_parts(pszPath, len as usize) }))) + } + c::E_FAIL => { + // This known folder id exists but does not have a path + if cfg!(debug_assertions) { + unreachable!("should not call get_known_folder_path on a virtual folder"); + } else { + Err(const_error!( + ErrorKind::InvalidInput, + "virtual known folders do not have paths" + )) + } + } + c::E_INVALIDARG => { + // This known folder id is not present on the system + Ok(None) + } + _ => { + // Miscellaneous error + Err(io::Error::from_raw_os_error(hr)) + } + }; + + // SAFETY: The caller is responsible for freeing the path returned by + // SHGetKnownFolderPath by calling CoTaskMemFree, whether it + // succeeds or not. + unsafe { c::CoTaskMemFree(pszPath.cast()) }; + + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_fetch_known_folder_paths() { + let dirs = HomeDirs::known_folders().unwrap(); + assert!(dirs.cache_home().is_some()); + assert!(dirs.config_home().is_some()); + assert!(dirs.data_home().is_some()); + assert!(dirs.state_home().is_some()); + + let dirs = MediaDirs::known_folders().unwrap(); + assert!(dirs.desktop().is_some()); + assert!(dirs.documents().is_some()); + assert!(dirs.downloads().is_some()); + assert!(dirs.music().is_some()); + assert!(dirs.pictures().is_some()); + assert!(dirs.videos().is_some()); + } +} diff --git a/library/std/src/sys/fs/common.rs b/library/std/src/sys/fs/common.rs index 17b98a4506544..6f16946b79914 100644 --- a/library/std/src/sys/fs/common.rs +++ b/library/std/src/sys/fs/common.rs @@ -111,3 +111,11 @@ impl fmt::Debug for Dir { f.debug_struct("Dir").field("path", &self.path).finish() } } + +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct ExtraHomeDirs {} + +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct ExtraMediaDirs {} diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 33feee4fc1c97..89f6989b9adbe 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -11,7 +11,7 @@ use crate::os::hermit::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, Raw use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::fd::FileDesc; -pub use crate::sys::fs::common::{Dir, copy, exists}; +pub use crate::sys::fs::common::{Dir, ExtraHomeDirs, ExtraMediaDirs, copy, exists}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::io::DEFAULT_BUF_SIZE; use crate::sys::time::SystemTime; diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index b2666eb2a3da9..93b05b9859d28 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -47,6 +47,9 @@ cfg_select! { mod vexos; use vexos as imp; } + target_vendor = "apple" => { + mod darwin; + } _ => { mod unsupported; use unsupported as imp; @@ -61,8 +64,8 @@ pub fn with_native_path(path: &Path, f: &dyn Fn(&Path) -> io::Result) -> i } pub use imp::{ - Dir, DirBuilder, DirEntry, File, FileAttr, FilePermissions, FileTimes, FileType, OpenOptions, - ReadDir, + Dir, DirBuilder, DirEntry, ExtraHomeDirs, ExtraMediaDirs, File, FileAttr, FilePermissions, + FileTimes, FileType, OpenOptions, ReadDir, }; pub fn read_dir(path: &Path) -> io::Result { diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 938d1537790a1..91cd90a6b0cb3 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -4,7 +4,7 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd}; use crate::path::{Path, PathBuf}; use crate::sys::fd::FileDesc; -pub use crate::sys::fs::common::{Dir, exists}; +pub use crate::sys::fs::common::{Dir, ExtraHomeDirs, ExtraMediaDirs, exists}; use crate::sys::time::SystemTime; use crate::sys::{ AsInner, AsInnerMut, FromInner, IntoInner, io_slices, io_slices_mut, map_motor_error, diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index bd963b1d2f038..dfdcacdcdf277 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -9,7 +9,7 @@ use crate::os::raw::{c_int, c_short}; use crate::os::solid::ffi::OsStrExt; use crate::path::{Path, PathBuf}; use crate::sync::Arc; -pub use crate::sys::fs::common::{Dir, exists}; +pub use crate::sys::fs::common::{Dir, ExtraHomeDirs, ExtraMediaDirs, exists}; use crate::sys::helpers::ignore_notfound; use crate::sys::pal::{abi, error}; use crate::sys::time::SystemTime; diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 08473e245cc8e..5d0c219586e0e 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -6,7 +6,7 @@ use crate::fs::TryLockError; use crate::hash::Hash; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; -pub use crate::sys::fs::common::{Dir, copy, remove_dir_all}; +pub use crate::sys::fs::common::{Dir, ExtraHomeDirs, ExtraMediaDirs, copy, remove_dir_all}; use crate::sys::pal::{helpers, unsupported}; use crate::sys::time::SystemTime; diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 613397e6903c1..691c491c6f7e5 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -50,6 +50,8 @@ use crate::fmt::{self, Write as _}; use crate::fs::TryLockError; use crate::io::{self, BorrowedCursor, Error, IoSlice, IoSliceMut, SeekFrom}; use crate::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd}; +#[cfg(not(target_os = "wasi"))] +pub use crate::os::unix::fs::dirs::{ExtraHomeDirs, ExtraMediaDirs}; #[cfg(target_family = "unix")] use crate::os::unix::prelude::*; #[cfg(target_os = "wasi")] @@ -58,6 +60,8 @@ use crate::path::{Path, PathBuf}; use crate::sync::Arc; use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::exists; +#[cfg(target_os = "wasi")] +pub use crate::sys::fs::common::{ExtraHomeDirs, ExtraMediaDirs}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::time::SystemTime; #[cfg(all(target_os = "linux", target_env = "gnu"))] diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 512af27401dfe..26677c7c7410a 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -4,7 +4,7 @@ use crate::fs::TryLockError; use crate::hash::{Hash, Hasher}; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; -pub use crate::sys::fs::common::Dir; +pub use crate::sys::fs::common::{Dir, ExtraHomeDirs, ExtraMediaDirs}; use crate::sys::time::SystemTime; use crate::sys::unsupported; diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 1357787c44c77..5043a4daa83b4 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -16,6 +16,8 @@ pub use unsupported_fs::{ symlink, unlink, }; +pub use crate::sys::fs::common::{ExtraHomeDirs, ExtraMediaDirs}; + /// VEXos file descriptor. /// /// This stores an opaque pointer to a [FatFs file object structure] managed by VEXos diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index b703ad9bd0ae8..0db5a8b9f9bfb 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -11,6 +11,7 @@ use crate::os::windows::io::{AsHandle, BorrowedHandle}; use crate::os::windows::prelude::*; use crate::path::{Path, PathBuf}; use crate::sync::Arc; +pub use crate::sys::fs::common::{ExtraHomeDirs, ExtraMediaDirs}; use crate::sys::handle::Handle; use crate::sys::pal::api::{self, WinError, set_file_information_by_handle}; use crate::sys::pal::{IoResult, fill_utf16_buf, to_u16s, truncate_utf16_at_nul}; diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs index 432cd0425ac11..25031968a7e02 100644 --- a/library/std/src/sys/pal/windows/c.rs +++ b/library/std/src/sys/pal/windows/c.rs @@ -154,6 +154,9 @@ pub const MB_ERR_INVALID_CHARS: u32 = cu(windows_sys::MB_ERR_INVALID_CHARS); pub const CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: u32 = cu(windows_sys::CREATE_WAITABLE_TIMER_HIGH_RESOLUTION); +// dwFlags --LoadLibraryExA +pub const LOAD_LIBRARY_SEARCH_SYSTEM32: u32 = cu(windows_sys::LOAD_LIBRARY_SEARCH_SYSTEM32); + // dwCreationDisposition -- CreateFile pub const CREATE_NEW: u32 = cu(windows_sys::CREATE_NEW); pub const OPEN_ALWAYS: u32 = cu(windows_sys::OPEN_ALWAYS); @@ -197,6 +200,16 @@ pub const WAIT_OBJECT_0: u32 = windows_sys::WAIT_OBJECT_0.cast_unsigned(); // LPPROGRESS_ROUTINE return value (DWORD) pub const PROGRESS_CONTINUE: u32 = cu(windows_sys::PROGRESS_CONTINUE); +// KNOWNFOLDERIDs (GUID) +pub const FOLDERID_Desktop: GUID = GUID::from_u128(0xb4bfcc3a_db2c_424c_b029_7fe99a87c641); +pub const FOLDERID_Documents: GUID = GUID::from_u128(0xfdd39ad0_238f_46af_adb4_6c85480369c7); +pub const FOLDERID_Downloads: GUID = GUID::from_u128(0x374de290_123f_4565_9164_39c4925e467b); +pub const FOLDERID_LocalAppData: GUID = GUID::from_u128(0xf1b32785_6fba_4fcf_9d55_7b8e7f157091); +pub const FOLDERID_Music: GUID = GUID::from_u128(0x4bd8d571_6d19_48d3_be97_422220080e43); +pub const FOLDERID_Pictures: GUID = GUID::from_u128(0x33e28130_4e1e_4676_835a_98395c3bc3bb); +pub const FOLDERID_RoamingAppData: GUID = GUID::from_u128(0x3eb685db_65f9_4cf6_a03a_e3ef65729f3d); +pub const FOLDERID_Videos: GUID = GUID::from_u128(0x18989b1d_99b5_455b_841c_ab7c74e4ddfc); + // Errors pub const ERROR_SUCCESS: u32 = cu(windows_sys::ERROR_SUCCESS); pub const ERROR_ACCESS_DENIED: u32 = cu(windows_sys::ERROR_ACCESS_DENIED); @@ -280,6 +293,17 @@ pub fn nt_success(status: NTSTATUS) -> bool { status >= 0 } +impl GUID { + pub const fn from_u128(uuid: u128) -> Self { + Self { + data1: (uuid >> 96) as u32, + data2: (uuid >> 80 & 0xffff) as u16, + data3: (uuid >> 64 & 0xffff) as u16, + data4: (uuid as u64).to_be_bytes(), + } + } +} + impl OBJECT_ATTRIBUTES { pub fn with_length() -> Self { Self { @@ -459,3 +483,24 @@ cfg_select! { unsafe extern "C" { pub fn atexit(cb: unsafe extern "C" fn()) -> c_int; } + +// Functions in DLLs that we want to lazily load +compat_fn_with_fallback! { + // Avoid eagerly linking shell32.dll, which marks the loading application as graphical. + #[lazy] + pub static SHELL32: &CStr = c"shell32"; + + pub fn SHGetKnownFolderPath(rfid : *const KNOWNFOLDERID, dwflags : u32, htoken : HANDLE, ppszpath : *mut PWSTR) -> HRESULT { + unsafe { SetLastError(ERROR_CALL_NOT_IMPLEMENTED as u32); E_NOTIMPL } + } +} + +compat_fn_with_fallback! { + // Only used with SHGetKnownFolderPath, so avoid eager load overhead. + #[lazy] + pub static OLE32: &CStr = c"ole32"; + + pub fn CoTaskMemFree(pv : *mut core::ffi::c_void) -> () { + // without OLE32 there's no COM alloc, so no-op COM free is fine + } +} diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt index ebe4b8e128352..70af36f27907e 100644 --- a/library/std/src/sys/pal/windows/c/bindings.txt +++ b/library/std/src/sys/pal/windows/c/bindings.txt @@ -252,6 +252,8 @@ DNS_ERROR_ZONESCOPE_IS_REFERENCED DUPLICATE_CLOSE_SOURCE DUPLICATE_SAME_ACCESS DuplicateHandle +E_FAIL +E_INVALIDARG E_NOTIMPL ENABLE_AUTO_POSITION ENABLE_ECHO_INPUT @@ -2260,8 +2262,12 @@ IPV6_MREQ IPV6_MULTICAST_LOOP IPV6_V6ONLY IsThreadAFiber +KF_FLAG_DONT_VERIFY +KNOWNFOLDERID LINGER listen +LOAD_LIBRARY_SEARCH_SYSTEM32 +LoadLibraryExA LocalFree LOCKFILE_EXCLUSIVE_LOCK LOCKFILE_FAIL_IMMEDIATELY @@ -2350,6 +2356,7 @@ ReleaseSRWLockShared RemoveDirectoryW RtlGenRandom RtlNtStatusToDosError +S_OK SD_BOTH SD_RECEIVE SD_SEND diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs index 34c4407296dcf..0c865494a6e7c 100644 --- a/library/std/src/sys/pal/windows/c/windows_sys.rs +++ b/library/std/src/sys/pal/windows/c/windows_sys.rs @@ -71,6 +71,7 @@ windows_link::link!("kernel32.dll" "system" fn InitOnceBeginInitialize(lpinitonc windows_link::link!("kernel32.dll" "system" fn InitOnceComplete(lpinitonce : LPINIT_ONCE, dwflags : u32, lpcontext : *const core::ffi::c_void) -> BOOL); windows_link::link!("kernel32.dll" "system" fn InitializeProcThreadAttributeList(lpattributelist : *mut _PROC_THREAD_ATTRIBUTE_LIST, dwattributecount : u32, dwflags : u32, lpsize : *mut usize) -> BOOL); windows_link::link!("kernel32.dll" "system" fn IsThreadAFiber() -> BOOL); +windows_link::link!("kernel32.dll" "system" fn LoadLibraryExA(lplibfilename : PCSTR, hfile : HANDLE, dwflags : u32) -> HMODULE); windows_link::link!("kernel32.dll" "system" fn LocalFree(hmem : HLOCAL) -> HLOCAL); windows_link::link!("kernel32.dll" "system" fn LockFileEx(hfile : HANDLE, dwflags : u32, dwreserved : u32, nnumberofbytestolocklow : u32, nnumberofbytestolockhigh : u32, lpoverlapped : *mut OVERLAPPED) -> BOOL); windows_link::link!("kernel32.dll" "system" fn MoveFileExW(lpexistingfilename : PCWSTR, lpnewfilename : PCWSTR, dwflags : u32) -> BOOL); @@ -2433,6 +2434,8 @@ impl Default for EXCEPTION_RECORD { } pub const EXCEPTION_STACK_OVERFLOW: i32 = -1073741571; pub const EXTENDED_STARTUPINFO_PRESENT: i32 = 524288; +pub const E_FAIL: HRESULT = 0x80004005_u32 as _; +pub const E_INVALIDARG: HRESULT = 0x80070057_u32 as _; pub const E_NOTIMPL: HRESULT = 0x80004001_u32 as _; pub const ExceptionCollidedUnwind: EXCEPTION_DISPOSITION = 3; pub const ExceptionContinueExecution: EXCEPTION_DISPOSITION = 0; @@ -2925,7 +2928,11 @@ impl Default for IP_MREQ { pub const IP_MULTICAST_LOOP: i32 = 11; pub const IP_MULTICAST_TTL: i32 = 10; pub const IP_TTL: i32 = 4; +pub const KF_FLAG_DONT_VERIFY: KNOWN_FOLDER_FLAG = 16384; +pub type KNOWNFOLDERID = GUID; +pub type KNOWN_FOLDER_FLAG = u32; pub type LINGER = linger; +pub const LOAD_LIBRARY_SEARCH_SYSTEM32: i32 = 2048; pub const LOCKFILE_EXCLUSIVE_LOCK: i32 = 2; pub const LOCKFILE_FAIL_IMMEDIATELY: i32 = 1; pub type LPBYTE = *mut u8; @@ -3295,6 +3302,7 @@ pub struct SYSTEM_INFO_0_0 { pub wProcessorArchitecture: u16, pub wReserved: u16, } +pub const S_OK: HRESULT = 0x0_u32 as _; pub const TCP_NODELAY: i32 = 1; pub const TIMER_ALL_ACCESS: i32 = 2031619; pub const TIMER_MODIFY_STATE: i32 = 2; diff --git a/library/std/src/sys/pal/windows/compat.rs b/library/std/src/sys/pal/windows/compat.rs index c465ceb2301ce..7e9f5bd108729 100644 --- a/library/std/src/sys/pal/windows/compat.rs +++ b/library/std/src/sys/pal/windows/compat.rs @@ -20,7 +20,8 @@ //! importing the same function unnecessarily. use crate::ffi::{CStr, c_void}; -use crate::ptr::NonNull; +use crate::ptr::{self, NonNull}; +use crate::sync::LazyLock; use crate::sys::c; // This uses a static initializer to preload some imported functions. @@ -118,6 +119,23 @@ impl Module { } } + /// Try to load a module handle. + /// + /// # SAFETY + /// + /// This should only be use for modules that exist for the lifetime of std. + pub unsafe fn load(name: &CStr) -> Option { + // SAFETY: A CStr is always null terminated. + unsafe { + let module = c::LoadLibraryExA( + /* lpLibFileName */ name.as_ptr().cast::(), + /* hReserved */ ptr::null_mut(), + /* dwFlags */ c::LOAD_LIBRARY_SEARCH_SYSTEM32, + ); + NonNull::new(module).map(Self) + } + } + // Try to get the address of a function. pub fn proc_address(self, name: &CStr) -> Option> { unsafe { @@ -131,6 +149,33 @@ impl Module { } } +unsafe impl Send for Module {} +unsafe impl Sync for Module {} + +type LazyLoadModule = impl FnOnce() -> Option + Send + Sync + 'static; + +#[define_opaque(LazyLoadModule)] +const fn lazy_load_module(name: &'static CStr) -> LazyLoadModule { + move || unsafe { Module::load(name) } +} + +/// Represents a loaded module. +/// +/// Note that the modules std depends on must not be unloaded. +/// Therefore a `Module` is always valid for the lifetime of std. +pub(in crate::sys) struct LazyModule(LazyLock, LazyLoadModule>); +impl LazyModule { + /// Create a lazy-loaded handle to a module. + pub const unsafe fn new(name: &'static CStr) -> Self { + Self(LazyLock::new(lazy_load_module(name))) + } + + /// Get the module handle, loading it if necessary. + pub fn load(&self) -> Option { + *LazyLock::force(&self.0) + } +} + /// Load a function or use a fallback implementation if that fails. macro_rules! compat_fn_with_fallback { (pub static $module:ident: &CStr = $name:expr; $( @@ -193,6 +238,67 @@ macro_rules! compat_fn_with_fallback { #[allow(unused)] $(#[$meta])* $vis use $symbol::call as $symbol; + )*); + (#[lazy] pub static $module:ident: &CStr = $name:expr; $( + $(#[$meta:meta])* + $vis:vis fn $symbol:ident($($argname:ident: $argtype:ty),*) -> $rettype:ty $fallback_body:block + )*) => ( + pub static $module: &CStr = $name; + $( + $(#[$meta])* + pub mod $symbol { + #[allow(unused_imports)] + use super::*; + use crate::mem; + use crate::ffi::CStr; + use crate::sync::atomic::{Atomic, AtomicPtr, Ordering}; + use crate::sys::compat::{LazyModule, Module}; + + type F = unsafe extern "system" fn($($argtype),*) -> $rettype; + + /// `PTR` contains a function pointer to one of three functions. + /// It starts with the `load` function. + /// When that is called it attempts to load the requested symbol. + /// If it succeeds, `PTR` is set to the address of that symbol. + /// If it fails, then `PTR` is set to `fallback`. + static PTR: Atomic<*mut c_void> = AtomicPtr::new(load as unsafe extern "system" fn($($argname: $argtype),*) -> $rettype as *mut _); + + unsafe extern "system" fn load($($argname: $argtype),*) -> $rettype { + static MODULE: LazyModule = unsafe { LazyModule::new($module) }; + unsafe { + let func = load_from_module(MODULE.load()); + func($($argname),*) + } + } + + fn load_from_module(module: Option) -> F { + unsafe { + static SYMBOL_NAME: &CStr = ansi_str!(sym $symbol); + if let Some(f) = module.and_then(|m| m.proc_address(SYMBOL_NAME)) { + PTR.store(f.as_ptr(), Ordering::Relaxed); + mem::transmute(f) + } else { + PTR.store(fallback as unsafe extern "system" fn($($argname: $argtype),*) -> $rettype as *mut _, Ordering::Relaxed); + fallback + } + } + } + + #[allow(unused_variables)] + unsafe extern "system" fn fallback($($argname: $argtype),*) -> $rettype { + $fallback_body + } + + #[inline(always)] + pub unsafe fn call($($argname: $argtype),*) -> $rettype { + unsafe { + let func: F = mem::transmute(PTR.load(Ordering::Relaxed)); + func($($argname),*) + } + } + } + $(#[$meta])* + $vis use $symbol::call as $symbol; )*) } diff --git a/library/std/src/sys/paths/mod.rs b/library/std/src/sys/paths/mod.rs index 69c814ef06c06..865d06ae8644e 100644 --- a/library/std/src/sys/paths/mod.rs +++ b/library/std/src/sys/paths/mod.rs @@ -6,7 +6,8 @@ cfg_select! { mod imp { pub use super::hermit::{getcwd, temp_dir}; pub use super::unsupported::{ - JoinPathsError, SplitPaths, chdir, current_exe, home_dir, join_paths, split_paths, + JoinPathsError, SplitPaths, SplitPathsRef, chdir, current_exe, home_dir, + join_paths, split_paths, split_paths_ref, }; } } @@ -16,8 +17,8 @@ cfg_select! { mod unsupported; mod imp { pub use super::motor::{ - JoinPathsError, SplitPaths, chdir, current_exe, getcwd, home_dir, join_paths, - split_paths, temp_dir, + JoinPathsError, SplitPaths, SplitPathsRef, chdir, current_exe, getcwd, home_dir, + join_paths, split_paths, split_paths_ref, temp_dir, }; } } @@ -28,8 +29,8 @@ cfg_select! { mod imp { pub use super::sgx::chdir; pub use super::unsupported::{ - JoinPathsError, SplitPaths, current_exe, getcwd, home_dir, join_paths, split_paths, - temp_dir, + JoinPathsError, SplitPaths, SplitPathsRef, current_exe, getcwd, home_dir, + join_paths, split_paths, split_paths_ref, temp_dir, }; } } @@ -47,7 +48,8 @@ cfg_select! { mod unsupported; mod imp { pub use super::unsupported::{ - JoinPathsError, SplitPaths, current_exe, home_dir, join_paths, split_paths, + JoinPathsError, SplitPaths, SplitPathsRef, current_exe, home_dir, join_paths, + split_paths, split_paths_ref, }; pub use super::wasi::{chdir, getcwd, temp_dir}; } @@ -63,6 +65,6 @@ cfg_select! { } pub use imp::{ - JoinPathsError, SplitPaths, chdir, current_exe, getcwd, home_dir, join_paths, split_paths, - temp_dir, + JoinPathsError, SplitPaths, SplitPathsRef, chdir, current_exe, getcwd, home_dir, join_paths, + split_paths, split_paths_ref, temp_dir, }; diff --git a/library/std/src/sys/paths/uefi.rs b/library/std/src/sys/paths/uefi.rs index 7fddcfdff7724..d9dcd45267935 100644 --- a/library/std/src/sys/paths/uefi.rs +++ b/library/std/src/sys/paths/uefi.rs @@ -1,6 +1,7 @@ use r_efi::efi::protocols::{device_path, loaded_image_device_path}; use crate::ffi::{OsStr, OsString}; +use crate::marker::PhantomData; use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; use crate::path::{self, PathBuf}; use crate::sys::pal::{helpers, unsupported_err}; @@ -71,6 +72,19 @@ impl<'a> Iterator for SplitPaths<'a> { } } +pub struct SplitPathsRef<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths_ref(_unparsed: &OsStr) -> Option> { + None +} + +impl<'a> Iterator for SplitPathsRef<'a> { + type Item = &'a path::Path; + fn next(&mut self) -> Option<&'a path::Path> { + self.0 + } +} + #[derive(Debug)] pub struct JoinPathsError; diff --git a/library/std/src/sys/paths/unix.rs b/library/std/src/sys/paths/unix.rs index e023451b13c8c..cee799c36c79b 100644 --- a/library/std/src/sys/paths/unix.rs +++ b/library/std/src/sys/paths/unix.rs @@ -6,7 +6,7 @@ use libc::{c_char, c_int, c_void}; use crate::ffi::{CStr, OsStr, OsString}; use crate::os::unix::prelude::*; -use crate::path::{self, PathBuf}; +use crate::path::{self, Path, PathBuf}; use crate::sys::helpers::run_path_with_cstr; use crate::sys::pal::cvt; use crate::{fmt, io, iter, mem, ptr, slice, str}; @@ -76,6 +76,28 @@ pub fn split_paths(unparsed: &OsStr) -> SplitPaths<'_> { unparsed.as_bytes().split(is_separator).map(into_pathbuf) } +pub type SplitPathsRef<'a> = iter::Map< + slice::Split<'a, u8, impl FnMut(&u8) -> bool + 'static>, + impl FnMut(&[u8]) -> &'_ Path + 'static, +>; + +pub fn split_paths_ref(unparsed: &OsStr) -> Option> { + #[define_opaque(SplitPathsRef)] + fn split_paths_ref(unparsed: &OsStr) -> SplitPathsRef<'_> { + fn is_separator(&b: &u8) -> bool { + b == PATH_SEPARATOR + } + + fn into_path(part: &[u8]) -> &Path { + Path::new(OsStr::from_bytes(part)) + } + + unparsed.as_bytes().split(is_separator).map(into_path) + } + + Some(split_paths_ref(unparsed)) +} + #[derive(Debug)] pub struct JoinPathsError; diff --git a/library/std/src/sys/paths/unsupported.rs b/library/std/src/sys/paths/unsupported.rs index 024830a254fd7..204d6b0897e20 100644 --- a/library/std/src/sys/paths/unsupported.rs +++ b/library/std/src/sys/paths/unsupported.rs @@ -1,6 +1,6 @@ use crate::ffi::{OsStr, OsString}; use crate::marker::PhantomData; -use crate::path::{self, PathBuf}; +use crate::path::{self, Path, PathBuf}; use crate::sys::pal::unsupported; use crate::{fmt, io}; @@ -13,11 +13,16 @@ pub fn chdir(_: &path::Path) -> io::Result<()> { } pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); +pub struct SplitPathsRef<'a>(!, PhantomData<&'a ()>); pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { panic!("unsupported") } +pub fn split_paths_ref(_unparsed: &OsStr) -> Option> { + None +} + impl<'a> Iterator for SplitPaths<'a> { type Item = PathBuf; fn next(&mut self) -> Option { @@ -25,6 +30,13 @@ impl<'a> Iterator for SplitPaths<'a> { } } +impl<'a> Iterator for SplitPathsRef<'a> { + type Item = &'a Path; + fn next(&mut self) -> Option<&'a Path> { + self.0 + } +} + #[derive(Debug)] pub struct JoinPathsError; diff --git a/library/std/src/sys/paths/windows.rs b/library/std/src/sys/paths/windows.rs index 9b9316b2658c0..350a942124694 100644 --- a/library/std/src/sys/paths/windows.rs +++ b/library/std/src/sys/paths/windows.rs @@ -3,6 +3,7 @@ #![allow(nonstandard_style)] use crate::ffi::{OsStr, OsString}; +use crate::marker::PhantomData; use crate::os::windows::ffi::EncodeWide; use crate::os::windows::prelude::*; use crate::path::{self, PathBuf}; @@ -56,6 +57,19 @@ impl<'a> Iterator for SplitPaths<'a> { } } +pub struct SplitPathsRef<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths_ref(_unparsed: &OsStr) -> Option> { + None +} + +impl<'a> Iterator for SplitPathsRef<'a> { + type Item = &'a path::Path; + fn next(&mut self) -> Option<&'a path::Path> { + self.0 + } +} + #[derive(Debug)] pub struct JoinPathsError; From 7d0907e746d13a832c36f134e9be9e1dc167b496 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 29 Sep 2026 20:32:01 +0200 Subject: [PATCH 17/18] arch::breakpoint: update documentation --- library/core/src/arch.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/core/src/arch.rs b/library/core/src/arch.rs index 737a643ef8659..a08901b19ecb4 100644 --- a/library/core/src/arch.rs +++ b/library/core/src/arch.rs @@ -65,9 +65,12 @@ pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))? /// to a trapping instruction (e.g. an undefined instruction) instead, or to some other form of /// target-specific abort that may or may not support convenient resumption. /// -/// The precise behavior and the precise instruction generated are not guaranteed, except that in -/// normal execution with no debug tooling involved this will not continue executing. +/// The precise behavior is not guaranteed, it depends on the architecture and operating system. +/// Not all architectures guarantee that a breakpoint instruction interrupts execution in the absence +/// of a debugger, and not all operating systems and execution environments guarantee that such an +/// interrupt aborts the current process. /// +/// The precise instruction is guaranteed only on the following targets: /// - On x86 targets, this produces an `int3` instruction. /// - On aarch64 targets, this produces a `brk #0xf000` instruction. // When stabilizing this, update the comment on `core::intrinsics::breakpoint`. From 707f1c5d9dd4ea1b6a611d3b940a75c9f62750ba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 29 Sep 2026 21:20:45 +0200 Subject: [PATCH 18/18] update breakpoint asm test based on proposal by tgross35 --- library/core/src/arch.rs | 1 + tests/assembly-llvm/breakpoint.rs | 35 ++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/library/core/src/arch.rs b/library/core/src/arch.rs index a08901b19ecb4..04fc16f50085b 100644 --- a/library/core/src/arch.rs +++ b/library/core/src/arch.rs @@ -73,6 +73,7 @@ pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))? /// The precise instruction is guaranteed only on the following targets: /// - On x86 targets, this produces an `int3` instruction. /// - On aarch64 targets, this produces a `brk #0xf000` instruction. +// When adding more items above, also add cases to the test in `tests/assembly-llvm/breakpoint.rs`. // When stabilizing this, update the comment on `core::intrinsics::breakpoint`. #[unstable(feature = "breakpoint", issue = "133724")] #[inline(always)] diff --git a/tests/assembly-llvm/breakpoint.rs b/tests/assembly-llvm/breakpoint.rs index d119b68e899ba..39ee26a4cf5d5 100644 --- a/tests/assembly-llvm/breakpoint.rs +++ b/tests/assembly-llvm/breakpoint.rs @@ -1,15 +1,30 @@ -//@ revisions: aarch64 x86_64 +//! Verify that the breakpoint operation emits the instructions we guarantee. + +//@ add-minicore //@ assembly-output: emit-asm -//@[aarch64] only-aarch64 -//@[x86_64] only-x86_64 +//@ revisions: AARCH64 I686 X86_64 +//@ [AARCH64] compile-flags: --target aarch64-unknown-linux-gnu +//@ [AARCH64] needs-llvm-components: aarch64 +//@ [I686] compile-flags: --target i686-unknown-linux-gnu +//@ [I686] needs-llvm-components: x86 +//@ [X86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@ [X86_64] needs-llvm-components: x86 -#![feature(breakpoint)] #![crate_type = "lib"] +#![feature(no_core, lang_items, intrinsics, rustc_attrs)] +#![no_core] + +extern crate minicore; + +#[rustc_intrinsic] +#[rustc_nounwind] +fn breakpoint(); -// CHECK-LABEL: use_bp -// aarch64: brk #0xf000 -// x86_64: int3 -#[inline(never)] -pub fn use_bp() { - core::arch::breakpoint(); +// CHECK-LABEL: call_breakpoint +// AARCH64: brk #0xf000 +// I686: int3 +// X86_64: int3 +#[unsafe(no_mangle)] +pub fn call_breakpoint() { + breakpoint(); }