From 2e46fb447a8dc1c0fbf0b03c06509fbd65e4b34a Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 24 Sep 2026 13:00:28 +0200 Subject: [PATCH 01/21] 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/21] 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/21] 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/21] 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/21] 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 e2809b481fff432f53ebb0ac9390da17030eec8e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:05:10 +0200 Subject: [PATCH 06/21] Remove #[linkage = "internal"] Rustc already defaults to this for regular items whenever possible. Overriding it would only lead to linker errors. And for depending on the exact codegen unit partitioning rustc uses, so there it is a bad idea to use it too. --- compiler/rustc_attr_parsing/src/attributes/link_attrs.rs | 3 --- compiler/rustc_span/src/symbol.rs | 1 - .../linkage-attr-does-not-panic-llvm-issue-33992.rs | 3 --- tests/ui/linkage-attr/linkage3.stderr | 2 +- tests/ui/lint/inline-exported.rs | 4 ---- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index f4d0fa35e3f47..bb5160db76ee8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -688,7 +688,6 @@ impl SingleAttributeParser for LinkageParser { "common", "extern_weak", "external", - "internal", "linkonce", "linkonce_odr", "weak", @@ -714,7 +713,6 @@ impl SingleAttributeParser for LinkageParser { sym::common => Linkage::Common, sym::extern_weak => Linkage::ExternalWeak, sym::external => Linkage::External, - sym::internal => Linkage::Internal, sym::linkonce => Linkage::LinkOnceAny, sym::linkonce_odr => Linkage::LinkOnceODR, sym::weak => Linkage::WeakAny, @@ -728,7 +726,6 @@ impl SingleAttributeParser for LinkageParser { sym::common, sym::extern_weak, sym::external, - sym::internal, sym::linkonce, sym::linkonce_odr, sym::weak, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3debf3248ff80..a7dbf7dc5e035 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1176,7 +1176,6 @@ symbols! { integer_max, integer_min, integral, - internal, internal_eq_trait_method_impls, internal_features, interrupt, diff --git a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs index 0717a2d5a6c9e..d80236cae7c6d 100644 --- a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs +++ b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs @@ -7,9 +7,6 @@ #[linkage = "external"] pub static TEST2: bool = true; -#[linkage = "internal"] -pub static TEST3: bool = true; - #[cfg(not(target_env = "msvc"))] #[linkage = "linkonce"] pub static TEST4: bool = true; diff --git a/tests/ui/linkage-attr/linkage3.stderr b/tests/ui/linkage-attr/linkage3.stderr index aa25cd39ba291..45555eef3d120 100644 --- a/tests/ui/linkage-attr/linkage3.stderr +++ b/tests/ui/linkage-attr/linkage3.stderr @@ -4,7 +4,7 @@ error[E0539]: malformed `linkage` attribute input LL | #[linkage = "foo"] | ^^^^^^^^^^----- | | - | valid arguments are `available_externally`, `common`, `extern_weak`, `external`, `internal`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` + | valid arguments are `available_externally`, `common`, `extern_weak`, `external`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` error: aborting due to 1 previous error diff --git a/tests/ui/lint/inline-exported.rs b/tests/ui/lint/inline-exported.rs index 6a23cd58236ff..bacbd128cd632 100644 --- a/tests/ui/lint/inline-exported.rs +++ b/tests/ui/lint/inline-exported.rs @@ -22,7 +22,3 @@ fn external_linkage() {} #[inline] fn normal() {} - -#[inline] -#[linkage = "internal"] // not exported -fn internal_linkage() {} From 561aa2d705fa5996d9be09e1900faa35ffc29d6e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:08:18 +0200 Subject: [PATCH 07/21] Remove #[linkage = "external"] Rustc already defaults to this for #[no_mangle]/#[export_name] items. There is no reason to explicitly use it. --- .../rustc_attr_parsing/src/attributes/link_attrs.rs | 3 --- tests/codegen-llvm/direct-access-external-data.rs | 10 ---------- tests/run-make/linkage-attr-on-static/bar.rs | 2 +- .../run-make/naked-symbol-visibility/a_rust_dylib.rs | 11 ----------- tests/run-make/naked-symbol-visibility/rmake.rs | 3 --- tests/ui/asm/naked-functions/naked-functions.rs | 2 +- tests/ui/linkage-attr/auxiliary/def_external.rs | 5 ----- tests/ui/linkage-attr/auxiliary/def_weak.rs | 5 +++++ .../linkage-attr-does-not-panic-llvm-issue-33992.rs | 3 --- tests/ui/linkage-attr/linkage-import.rs | 6 +++--- tests/ui/linkage-attr/linkage3.stderr | 2 +- tests/ui/linkage-attr/linkage4.rs | 2 +- tests/ui/linkage-attr/linkage4.stderr | 2 +- tests/ui/lint/inline-exported.rs | 4 ++-- 14 files changed, 15 insertions(+), 45 deletions(-) delete mode 100644 tests/ui/linkage-attr/auxiliary/def_external.rs create mode 100644 tests/ui/linkage-attr/auxiliary/def_weak.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index bb5160db76ee8..4796556819b4a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -687,7 +687,6 @@ impl SingleAttributeParser for LinkageParser { "available_externally", "common", "extern_weak", - "external", "linkonce", "linkonce_odr", "weak", @@ -712,7 +711,6 @@ impl SingleAttributeParser for LinkageParser { sym::available_externally => Linkage::AvailableExternally, sym::common => Linkage::Common, sym::extern_weak => Linkage::ExternalWeak, - sym::external => Linkage::External, sym::linkonce => Linkage::LinkOnceAny, sym::linkonce_odr => Linkage::LinkOnceODR, sym::weak => Linkage::WeakAny, @@ -725,7 +723,6 @@ impl SingleAttributeParser for LinkageParser { sym::available_externally, sym::common, sym::extern_weak, - sym::external, sym::linkonce, sym::linkonce_odr, sym::weak, diff --git a/tests/codegen-llvm/direct-access-external-data.rs b/tests/codegen-llvm/direct-access-external-data.rs index a151bb6012e1e..43ee290d3a058 100644 --- a/tests/codegen-llvm/direct-access-external-data.rs +++ b/tests/codegen-llvm/direct-access-external-data.rs @@ -22,15 +22,6 @@ unsafe extern "C" { // When "linkage" is used, we generate an indirection global. // Check dso_local is still applied to the actual global. - // CHECK: @EXTERNAL = external - // DEFAULT-SAME: dso_local - // PIE-NOT: dso_local - // DIRECT-SAME: dso_local - // INDIRECT-NOT: dso_local - // CHECK-SAME: global i8 - #[linkage = "external"] - safe static EXTERNAL: *const u32; - // CHECK: @WEAK = extern_weak // DEFAULT-SAME: dso_local // PIE-NOT: dso_local @@ -44,6 +35,5 @@ unsafe extern "C" { #[no_mangle] pub fn refer() { core::hint::black_box(VAR); - core::hint::black_box(EXTERNAL); core::hint::black_box(WEAK); } diff --git a/tests/run-make/linkage-attr-on-static/bar.rs b/tests/run-make/linkage-attr-on-static/bar.rs index 68607cbb65721..cbab76ba6f403 100644 --- a/tests/run-make/linkage-attr-on-static/bar.rs +++ b/tests/run-make/linkage-attr-on-static/bar.rs @@ -1,7 +1,7 @@ #![feature(linkage)] #[no_mangle] -#[linkage = "external"] +#[linkage = "weak"] static BAZ: i32 = 21; #[link(name = "foo", kind = "static")] diff --git a/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs index ce787f83ade6d..8d699a665ae24 100644 --- a/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs +++ b/tests/run-make/naked-symbol-visibility/a_rust_dylib.rs @@ -51,17 +51,6 @@ pub extern "C" fn public_naked_generic() -> u32 { naked_asm!("mov rax, {}", "ret", const T::COUNT) } -#[linkage = "external"] -extern "C" fn vanilla_external_linkage() -> u32 { - 42 -} - -#[unsafe(naked)] -#[linkage = "external"] -extern "C" fn naked_external_linkage() -> u32 { - naked_asm!("mov rax, 42", "ret") -} - #[cfg(not(windows))] #[linkage = "weak"] extern "C" fn vanilla_weak_linkage() -> u32 { diff --git a/tests/run-make/naked-symbol-visibility/rmake.rs b/tests/run-make/naked-symbol-visibility/rmake.rs index 69317c872f895..1acbe11dc4ae4 100644 --- a/tests/run-make/naked-symbol-visibility/rmake.rs +++ b/tests/run-make/naked-symbol-visibility/rmake.rs @@ -29,9 +29,6 @@ fn main() { // -Zshare-generics. global_function(&rdylib, "public_naked_generic"); - global_function(&rdylib, "vanilla_external_linkage"); - global_function(&rdylib, "naked_external_linkage"); - // FIXME: make this work on windows (gnu and msvc). See the PR // https://github.com/rust-lang/rust/pull/128362 for some approaches // that don't work diff --git a/tests/ui/asm/naked-functions/naked-functions.rs b/tests/ui/asm/naked-functions/naked-functions.rs index d994b1151514a..e7da65a169074 100644 --- a/tests/ui/asm/naked-functions/naked-functions.rs +++ b/tests/ui/asm/naked-functions/naked-functions.rs @@ -224,7 +224,7 @@ pub extern "C" fn compatible_doc_attributes() { naked_asm!("", options(raw)); } -#[linkage = "external"] +#[linkage = "weak"] #[unsafe(naked)] pub extern "C" fn compatible_linkage() { naked_asm!("", options(raw)); diff --git a/tests/ui/linkage-attr/auxiliary/def_external.rs b/tests/ui/linkage-attr/auxiliary/def_external.rs deleted file mode 100644 index 2300930e513af..0000000000000 --- a/tests/ui/linkage-attr/auxiliary/def_external.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![feature(linkage)] -#![crate_type = "lib"] - -#[linkage="external"] -pub static EXTERN: u32 = 0; diff --git a/tests/ui/linkage-attr/auxiliary/def_weak.rs b/tests/ui/linkage-attr/auxiliary/def_weak.rs new file mode 100644 index 0000000000000..a8940f73c5536 --- /dev/null +++ b/tests/ui/linkage-attr/auxiliary/def_weak.rs @@ -0,0 +1,5 @@ +#![feature(linkage)] +#![crate_type = "lib"] + +#[linkage = "weak"] +pub static WEAK: u32 = 0; diff --git a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs index d80236cae7c6d..a7209b71c7809 100644 --- a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs +++ b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs @@ -4,9 +4,6 @@ #![feature(linkage)] -#[linkage = "external"] -pub static TEST2: bool = true; - #[cfg(not(target_env = "msvc"))] #[linkage = "linkonce"] pub static TEST4: bool = true; diff --git a/tests/ui/linkage-attr/linkage-import.rs b/tests/ui/linkage-attr/linkage-import.rs index 838d1fc29a238..51fcf1c133f32 100644 --- a/tests/ui/linkage-attr/linkage-import.rs +++ b/tests/ui/linkage-attr/linkage-import.rs @@ -1,8 +1,8 @@ //@ build-pass -//@ aux-build:def_external.rs +//@ aux-build:def_weak.rs -extern crate def_external as dep; +extern crate def_weak as dep; fn main() { - println!("{:p}", &dep::EXTERN); + println!("{:p}", &dep::WEAK); } diff --git a/tests/ui/linkage-attr/linkage3.stderr b/tests/ui/linkage-attr/linkage3.stderr index 45555eef3d120..4a85c39009517 100644 --- a/tests/ui/linkage-attr/linkage3.stderr +++ b/tests/ui/linkage-attr/linkage3.stderr @@ -4,7 +4,7 @@ error[E0539]: malformed `linkage` attribute input LL | #[linkage = "foo"] | ^^^^^^^^^^----- | | - | valid arguments are `available_externally`, `common`, `extern_weak`, `external`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` + | valid arguments are `available_externally`, `common`, `extern_weak`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` error: aborting due to 1 previous error diff --git a/tests/ui/linkage-attr/linkage4.rs b/tests/ui/linkage-attr/linkage4.rs index 3b935f9723dc1..25df81189d8f5 100644 --- a/tests/ui/linkage-attr/linkage4.rs +++ b/tests/ui/linkage-attr/linkage4.rs @@ -1,4 +1,4 @@ -#[linkage = "external"] +#[linkage = "weak"] static foo: isize = 0; //~^^ ERROR: the `linkage` attribute is experimental and not portable diff --git a/tests/ui/linkage-attr/linkage4.stderr b/tests/ui/linkage-attr/linkage4.stderr index a5970d100581a..2f04ceee5199d 100644 --- a/tests/ui/linkage-attr/linkage4.stderr +++ b/tests/ui/linkage-attr/linkage4.stderr @@ -1,7 +1,7 @@ error[E0658]: the `linkage` attribute is experimental and not portable across platforms --> $DIR/linkage4.rs:1:3 | -LL | #[linkage = "external"] +LL | #[linkage = "weak"] | ^^^^^^^ | = note: see issue #29603 for more information diff --git a/tests/ui/lint/inline-exported.rs b/tests/ui/lint/inline-exported.rs index bacbd128cd632..25ca5f0633079 100644 --- a/tests/ui/lint/inline-exported.rs +++ b/tests/ui/lint/inline-exported.rs @@ -17,8 +17,8 @@ fn export_name() {} #[inline] //~^ ERROR: `#[inline]` is ignored on externally exported functions -#[linkage = "external"] -fn external_linkage() {} +#[linkage = "weak"] +fn weak_linkage() {} #[inline] fn normal() {} From 7cf948066f586693e09cd5c0402a22afe0d3213e Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:20:51 +0200 Subject: [PATCH 08/21] Remove #[linkage = "common"] A common definition is like a weak definition except that it must be a zero-initialized static and when merging two common symbols with the same name, the size and alignment are set to the higher of both symbols. This is used for tentative definitions in C and doesn't have any reason to exist outside of that. This behavior doesn't work across dylibs and common symbols have inconsistent behavior across linkers [1]. It is also fragile to rely on getting the largest size of all common symbol definitions as a (possibly smaller) global definition can override it. Link: https://maskray.me/blog/all-about-common-symbols [1] --- compiler/rustc_attr_ir/src/data_structures.rs | 1 - .../rustc_attr_ir/src/encode_cross_crate.rs | 1 - .../src/attributes/link_attrs.rs | 3 --- compiler/rustc_codegen_gcc/src/base.rs | 19 ++++------------- compiler/rustc_codegen_gcc/src/consts.rs | 21 ++----------------- compiler/rustc_codegen_llvm/src/base.rs | 1 - .../rustc_codegen_ssa/src/mir/naked_asm.rs | 1 - .../rustc_monomorphize/src/partitioning.rs | 1 - compiler/rustc_span/src/symbol.rs | 1 - .../codegen_attr_on_required_trait_method.rs | 2 +- ...degen_attr_on_required_trait_method.stderr | 2 +- .../common-linkage-non-zero-init.rs | 17 --------------- .../common-linkage-non-zero-init.stderr | 3 --- ...ge-attr-does-not-panic-llvm-issue-33992.rs | 2 +- tests/ui/linkage-attr/linkage3.stderr | 2 +- 15 files changed, 10 insertions(+), 67 deletions(-) delete mode 100644 tests/ui/linkage-attr/common-linkage-non-zero-init.rs delete mode 100644 tests/ui/linkage-attr/common-linkage-non-zero-init.stderr diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 712ed41ae7759..55c0bedb9c39c 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -283,7 +283,6 @@ impl StrippedCfgItem { #[derive(StableHash, PrintAttribute)] pub enum Linkage { AvailableExternally, - Common, ExternalWeak, External, Internal, diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 66fbbc1e3cbf7..b6733e8c1db54 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -265,7 +265,6 @@ impl AttributeKind { Linkage(linkage, _) => match *linkage { crate::Linkage::Internal => false, crate::Linkage::AvailableExternally - | crate::Linkage::Common | crate::Linkage::External | crate::Linkage::ExternalWeak | crate::Linkage::LinkOnceAny diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 4796556819b4a..ed911a63eb7fe 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -685,7 +685,6 @@ impl SingleAttributeParser for LinkageParser { ]); const TEMPLATE: AttributeTemplate = template!(NameValueStr: [ "available_externally", - "common", "extern_weak", "linkonce", "linkonce_odr", @@ -709,7 +708,6 @@ impl SingleAttributeParser for LinkageParser { // and don't have to be, LLVM treats them as no-ops. let linkage = match value { sym::available_externally => Linkage::AvailableExternally, - sym::common => Linkage::Common, sym::extern_weak => Linkage::ExternalWeak, sym::linkonce => Linkage::LinkOnceAny, sym::linkonce_odr => Linkage::LinkOnceODR, @@ -721,7 +719,6 @@ impl SingleAttributeParser for LinkageParser { name_value.value_span, &[ sym::available_externally, - sym::common, sym::extern_weak, sym::linkonce, sym::linkonce_odr, diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs index 07214096b3e49..acf5c75e4d8cd 100644 --- a/compiler/rustc_codegen_gcc/src/base.rs +++ b/compiler/rustc_codegen_gcc/src/base.rs @@ -55,19 +55,14 @@ pub fn global_linkage_to_gcc(linkage: Linkage) -> GlobalKind { | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR - | Linkage::ExternalWeak - | Linkage::Common => GlobalKind::Exported, + | Linkage::ExternalWeak => GlobalKind::Exported, } } /// The attribute a global *definition* needs on top of its [`GlobalKind`] to get this linkage. #[cfg(feature = "master")] pub fn global_linkage_attribute<'gcc>(linkage: Linkage) -> Option> { - match linkage { - Linkage::Common => Some(VarAttribute::Common), - _ if linkage_needs_weak_attribute(linkage) => Some(VarAttribute::Weak), - _ => None, - } + if linkage_needs_weak_attribute(linkage) { Some(VarAttribute::Weak) } else { None } } /// The type of a function *definition* with an explicit `#[linkage]`. @@ -85,17 +80,12 @@ pub fn linkage_to_gcc(linkage: Linkage) -> FunctionType { | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR - | Linkage::ExternalWeak - | Linkage::Common => FunctionType::Exported, + | Linkage::ExternalWeak => FunctionType::Exported, } } /// Whether a definition with this linkage must carry the `weak` attribute, so that a strong /// definition in another object file wins over it instead of clashing with it. -/// -/// `common` is in here for functions only: GCC honours that attribute on a variable, but drops it -/// on a function, so a common function falls back to weak. Globals go through -/// `global_linkage_attribute` instead. #[cfg(feature = "master")] pub fn linkage_needs_weak_attribute(linkage: Linkage) -> bool { match linkage { @@ -103,8 +93,7 @@ pub fn linkage_needs_weak_attribute(linkage: Linkage) -> bool { | Linkage::LinkOnceODR | Linkage::WeakAny | Linkage::WeakODR - | Linkage::ExternalWeak - | Linkage::Common => true, + | Linkage::ExternalWeak => true, Linkage::External | Linkage::AvailableExternally | Linkage::Internal => false, } } diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index c58ab6d2a9bef..0784064547e0f 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -13,8 +13,7 @@ use rustc_hir::def_id::LOCAL_CRATE; use rustc_log::tracing::trace; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::interpret::{ - self, Allocation, ConstAllocation, CtfeProvenance, ErrorHandled, Scalar as InterpScalar, - read_target_uint, + self, ConstAllocation, CtfeProvenance, ErrorHandled, Scalar as InterpScalar, read_target_uint, }; use rustc_middle::mono::MonoItem; use rustc_middle::ty::layout::LayoutOf; @@ -113,12 +112,7 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { // NOTE: Alignment from attributes has already been applied to the allocation. set_global_alignment(self, global, alloc.align); - // A common symbol is storage the linker allocates and zero-fills, so giving the definition - // an initializer — even an all-zero one — takes it back out of `.comm`. A non-zero one is - // kept: the symbol is then an ordinary definition, which is what GCC does with it too. - if attrs.linkage != Some(Linkage::Common) || !is_zero_initializer(alloc) { - global.global_set_initializer_rvalue(value); - } + global.global_set_initializer_rvalue(value); // As an optimization, all shared statics which do not have interior // mutability are placed into read-only memory. @@ -458,17 +452,6 @@ pub(crate) fn const_alloc_to_gcc_uncached<'gcc>( cx.const_struct(&llvals, true) } -/// Whether this allocation is all zeroes, and so needs no initializer to be spelled out. -fn is_zero_initializer(alloc: &Allocation) -> bool { - alloc.provenance().ptrs().is_empty() - // This `inspect` is okay: it is within the bounds of the allocation, there is no provenance - // to misread, and it does not affect interpreter execution. - && alloc - .inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.size().bytes_usize()) - .iter() - .all(|&byte| byte == 0) -} - fn codegen_static_initializer<'gcc, 'tcx>( cx: &CodegenCx<'gcc, 'tcx>, def_id: DefId, diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index cf278694399f3..77cb37edec770 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -237,7 +237,6 @@ pub(crate) fn linkage_to_llvm(linkage: Linkage) -> llvm::Linkage { Linkage::WeakODR => llvm::Linkage::WeakODRLinkage, Linkage::Internal => llvm::Linkage::InternalLinkage, Linkage::ExternalWeak => llvm::Linkage::ExternalWeakLinkage, - Linkage::Common => llvm::Linkage::CommonLinkage, } } diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 2fa6f3fc06550..5433b9da6f2b6 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -228,7 +228,6 @@ fn prefix_and_suffix<'tcx>( // LTO can fail when internal linkage is used. emit_fatal("naked functions may not have internal linkage") } - Linkage::Common => emit_fatal("Functions may not have common linkage"), Linkage::AvailableExternally => { // this would make the function equal an extern definition emit_fatal("Functions may not have available_externally linkage") diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index 43ce9634cc203..16df895b0f728 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -1252,7 +1252,6 @@ fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> MonoItemPartitio Linkage::WeakODR => "WeakODR", Linkage::Internal => "Internal", Linkage::ExternalWeak => "ExternalWeak", - Linkage::Common => "Common", }; output.push('['); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index a7dbf7dc5e035..a003f20e3512d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -671,7 +671,6 @@ symbols! { cold_path, collapse_debuginfo, column, - common, compare_bytes, compare_exchange, compare_exchange_weak, diff --git a/tests/ui/attributes/codegen_attr_on_required_trait_method.rs b/tests/ui/attributes/codegen_attr_on_required_trait_method.rs index 737ebf7f2b144..2ea4e0b5a395c 100644 --- a/tests/ui/attributes/codegen_attr_on_required_trait_method.rs +++ b/tests/ui/attributes/codegen_attr_on_required_trait_method.rs @@ -11,7 +11,7 @@ trait Test { //~^ ERROR cannot be used on required trait methods [unused_attributes] //~| WARN previously accepted fn method2(&self); - #[linkage = "common"] + #[linkage = "weak"] //~^ ERROR `linkage` attribute cannot be used on required trait methods fn method3(&self); #[track_caller] diff --git a/tests/ui/attributes/codegen_attr_on_required_trait_method.stderr b/tests/ui/attributes/codegen_attr_on_required_trait_method.stderr index b5452ba2882c0..729231956337d 100644 --- a/tests/ui/attributes/codegen_attr_on_required_trait_method.stderr +++ b/tests/ui/attributes/codegen_attr_on_required_trait_method.stderr @@ -1,7 +1,7 @@ error: the `linkage` attribute cannot be used on required trait methods --> $DIR/codegen_attr_on_required_trait_method.rs:14:7 | -LL | #[linkage = "common"] +LL | #[linkage = "weak"] | ^^^^^^^ | = help: the `linkage` attribute can be applied to foreign functions, foreign statics, functions with a body, and statics diff --git a/tests/ui/linkage-attr/common-linkage-non-zero-init.rs b/tests/ui/linkage-attr/common-linkage-non-zero-init.rs deleted file mode 100644 index 512616251c2f7..0000000000000 --- a/tests/ui/linkage-attr/common-linkage-non-zero-init.rs +++ /dev/null @@ -1,17 +0,0 @@ -//@ build-fail -//@ failure-status: 101 -//@ known-bug: #109681 -//@ ignore-wasm32 this appears to SIGABRT on wasm, not fail cleanly -//@ compile-flags: -Z verify-llvm-ir -//@ ignore-backends: gcc - -// This test verifies that we continue to hit the LLVM error for common linkage with non-zero -// initializers, since it generates invalid LLVM IR. -// Linkages are internal features marked as perma-unstable, so we don't need to fix the issue -// for now. -#![crate_type="lib"] -#![feature(linkage)] - -#[linkage = "common"] -#[no_mangle] -pub static TEST: bool = true; diff --git a/tests/ui/linkage-attr/common-linkage-non-zero-init.stderr b/tests/ui/linkage-attr/common-linkage-non-zero-init.stderr deleted file mode 100644 index 93015bb2bac12..0000000000000 --- a/tests/ui/linkage-attr/common-linkage-non-zero-init.stderr +++ /dev/null @@ -1,3 +0,0 @@ -'common' global must have a zero initializer! -ptr @TEST -rustc-LLVM ERROR: Broken module found, compilation aborted! diff --git a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs index a7209b71c7809..e1ad2405b1df8 100644 --- a/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs +++ b/tests/ui/linkage-attr/linkage-attr-does-not-panic-llvm-issue-33992.rs @@ -1,6 +1,6 @@ //@ run-pass //@ ignore-apple -//@ ignore-wasm32 common linkage not implemented right now +//@ ignore-wasm32 linkonce linkage not working right now #![feature(linkage)] diff --git a/tests/ui/linkage-attr/linkage3.stderr b/tests/ui/linkage-attr/linkage3.stderr index 4a85c39009517..616f80df09c8b 100644 --- a/tests/ui/linkage-attr/linkage3.stderr +++ b/tests/ui/linkage-attr/linkage3.stderr @@ -4,7 +4,7 @@ error[E0539]: malformed `linkage` attribute input LL | #[linkage = "foo"] | ^^^^^^^^^^----- | | - | valid arguments are `available_externally`, `common`, `extern_weak`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` + | valid arguments are `available_externally`, `extern_weak`, `linkonce`, `linkonce_odr`, `weak` or `weak_odr` error: aborting due to 1 previous error From e054cb5bb68db7af6b38ce5457495cacf58885d1 Mon Sep 17 00:00:00 2001 From: Antoni Boucher Date: Tue, 29 Sep 2026 11:12:52 -0400 Subject: [PATCH 09/21] 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 10/21] 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 11/21] 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 12/21] 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 13/21] 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 14/21] 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 15/21] 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 16/21] 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 17/21] 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 18/21] 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 19/21] 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 20/21] 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 21/21] 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(); }