From 139d6ba054a1a4cc5fe64981ad46fd5547bd4916 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 30 Jan 2025 16:51:08 +0000 Subject: [PATCH 1/5] set rustc dylib on manually constructed rustc command Signed-off-by: onur-ozkan --- src/bootstrap/src/core/builder/cargo.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 1b413dcb07efb..79a65aedd9edb 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -653,7 +653,10 @@ impl Builder<'_> { // Build proc macros both for the host and the target unless proc-macros are not // supported by the target. if target != compiler.host && cmd_kind != Kind::Check { - let error = command(self.rustc(compiler)) + let mut rustc_cmd = command(self.rustc(compiler)); + self.add_rustc_lib_path(compiler, &mut rustc_cmd); + + let error = rustc_cmd .arg("--target") .arg(target.rustc_target_arg()) .arg("--print=file-names") @@ -661,6 +664,7 @@ impl Builder<'_> { .arg("-") .run_capture(self) .stderr(); + let not_supported = error .lines() .any(|line| line.contains("unsupported crate type `proc-macro`")); From 4d42046719835a201599354b1a5a82d7bd00ab45 Mon Sep 17 00:00:00 2001 From: Veera Date: Fri, 31 Jan 2025 06:09:29 +0000 Subject: [PATCH 2/5] CompileTest: Add Directives to Ignore `arm-unknown-*` Tests --- src/tools/compiletest/src/directive-list.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/compiletest/src/directive-list.rs b/src/tools/compiletest/src/directive-list.rs index 71496444660f5..a7ac875d0a36c 100644 --- a/src/tools/compiletest/src/directive-list.rs +++ b/src/tools/compiletest/src/directive-list.rs @@ -39,6 +39,10 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-android", "ignore-apple", "ignore-arm", + "ignore-arm-unknown-linux-gnueabi", + "ignore-arm-unknown-linux-gnueabihf", + "ignore-arm-unknown-linux-musleabi", + "ignore-arm-unknown-linux-musleabihf", "ignore-avr", "ignore-beta", "ignore-cdb", From 56795fb77a69bc61836537175564455067adada6 Mon Sep 17 00:00:00 2001 From: Flakebi Date: Thu, 2 Jan 2025 15:19:43 +0100 Subject: [PATCH 3/5] Add amdgpu target Add target and compile the amdgpu llvm backend. --- compiler/rustc_target/src/spec/mod.rs | 2 + .../src/spec/targets/amdgcn_amd_amdhsa.rs | 51 ++++++++ config.example.toml | 2 +- src/bootstrap/download-ci-llvm-stamp | 2 +- src/bootstrap/src/core/build_steps/llvm.rs | 2 +- src/doc/rustc/src/SUMMARY.md | 1 + src/doc/rustc/src/platform-support.md | 2 + .../src/platform-support/amdgcn-amd-amdhsa.md | 111 ++++++++++++++++++ src/tools/build-manifest/src/main.rs | 1 + tests/assembly/targets/targets-amdgpu.rs | 21 ++++ .../amdgpu-require-explicit-cpu.nocpu.stderr | 4 + tests/ui/amdgpu-require-explicit-cpu.rs | 17 +++ .../exhaustive-names-values.empty_cfg.stderr | 2 +- .../exhaustive-names-values.feature.stderr | 2 +- .../exhaustive-names-values.full.stderr | 2 +- tests/ui/check-cfg/well-known-values.stderr | 8 +- 16 files changed, 220 insertions(+), 10 deletions(-) create mode 100644 compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs create mode 100644 src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md create mode 100644 tests/assembly/targets/targets-amdgpu.rs create mode 100644 tests/ui/amdgpu-require-explicit-cpu.nocpu.stderr create mode 100644 tests/ui/amdgpu-require-explicit-cpu.rs diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 3fc7a07fb9139..d7241ddecec6d 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1908,6 +1908,8 @@ supported_targets! { ("nvptx64-nvidia-cuda", nvptx64_nvidia_cuda), + ("amdgcn-amd-amdhsa", amdgcn_amd_amdhsa), + ("xtensa-esp32-none-elf", xtensa_esp32_none_elf), ("xtensa-esp32-espidf", xtensa_esp32_espidf), ("xtensa-esp32s2-none-elf", xtensa_esp32s2_none_elf), diff --git a/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs b/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs new file mode 100644 index 0000000000000..bb488c350c23e --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/amdgcn_amd_amdhsa.rs @@ -0,0 +1,51 @@ +use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, Target, TargetOptions}; + +pub(crate) fn target() -> Target { + Target { + arch: "amdgpu".into(), + data_layout: "e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-p7:160:256:256:32-p8:128:128-p9:192:256:256:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7:8:9".into(), + llvm_target: "amdgcn-amd-amdhsa".into(), + metadata: crate::spec::TargetMetadata { + description: Some("AMD GPU".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(false), + }, + pointer_width: 64, + + options: TargetOptions { + os: "amdhsa".into(), + vendor: "amd".into(), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("rust-lld".into()), + + // There are many CPUs, one for each hardware generation. + // Require to set one explicitly as there is no good default. + need_explicit_cpu: true, + + max_atomic_width: Some(64), + + // Unwinding on GPUs is not useful. + panic_strategy: PanicStrategy::Abort, + + // amdgpu backend does not support libcalls. + no_builtins: true, + simd_types_indirect: false, + + // Allow `cdylib` crate type. + dynamic_linking: true, + only_cdylib: true, + executables: false, + dll_prefix: "".into(), + dll_suffix: ".elf".into(), + + // The LLVM backend does not support stack canaries for this target + supports_stack_protector: false, + + // Force LTO, object linking does not yet work with amdgpu. + requires_lto: true, + + ..Default::default() + }, + } +} diff --git a/config.example.toml b/config.example.toml index 04e7310e6bc34..7f6bbf2799288 100644 --- a/config.example.toml +++ b/config.example.toml @@ -108,7 +108,7 @@ # the resulting rustc being unable to compile for the disabled architectures. # # To add support for new targets, see https://rustc-dev-guide.rust-lang.org/building/new-target.html. -#targets = "AArch64;ARM;BPF;Hexagon;LoongArch;MSP430;Mips;NVPTX;PowerPC;RISCV;Sparc;SystemZ;WebAssembly;X86" +#targets = "AArch64;AMDGPU;ARM;BPF;Hexagon;LoongArch;MSP430;Mips;NVPTX;PowerPC;RISCV;Sparc;SystemZ;WebAssembly;X86" # LLVM experimental targets to build support for. These targets are specified in # the same format as above, but since these targets are experimental, they are diff --git a/src/bootstrap/download-ci-llvm-stamp b/src/bootstrap/download-ci-llvm-stamp index 42cecbf5df9bb..d2c0d380fb40b 100644 --- a/src/bootstrap/download-ci-llvm-stamp +++ b/src/bootstrap/download-ci-llvm-stamp @@ -1,4 +1,4 @@ Change this file to make users of the `download-ci-llvm` configuration download a new version of LLVM from CI, even if the LLVM submodule hasn’t changed. -Last change is for: https://github.com/rust-lang/rust/pull/129788 +Last change is for: https://github.com/rust-lang/rust/pull/134740 diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 3cf25373b8963..f855be5a33d64 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -331,7 +331,7 @@ impl Step for Llvm { let llvm_targets = match &builder.config.llvm_targets { Some(s) => s, None => { - "AArch64;ARM;BPF;Hexagon;LoongArch;MSP430;Mips;NVPTX;PowerPC;RISCV;\ + "AArch64;AMDGPU;ARM;BPF;Hexagon;LoongArch;MSP430;Mips;NVPTX;PowerPC;RISCV;\ Sparc;SystemZ;WebAssembly;X86" } }; diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 670e4bd1be68b..f78d3d4aee830 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -29,6 +29,7 @@ - [\*-apple-watchos](platform-support/apple-watchos.md) - [\*-apple-visionos](platform-support/apple-visionos.md) - [aarch64-nintendo-switch-freestanding](platform-support/aarch64-nintendo-switch-freestanding.md) + - [amdgcn-amd-amdhsa](platform-support/amdgcn-amd-amdhsa.md) - [armeb-unknown-linux-gnueabi](platform-support/armeb-unknown-linux-gnueabi.md) - [arm-none-eabi](platform-support/arm-none-eabi.md) - [armv4t-none-eabi](platform-support/armv4t-none-eabi.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 8227dfa043e3a..01ac9f5458644 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -272,6 +272,7 @@ target | std | host | notes `aarch64_be-unknown-linux-gnu` | ✓ | ✓ | ARM64 Linux (big-endian) `aarch64_be-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (big-endian, ILP32 ABI) [`aarch64_be-unknown-netbsd`](platform-support/netbsd.md) | ✓ | ✓ | ARM64 NetBSD (big-endian) +[`amdgcn-amd-amdhsa`](platform-support/amdgcn-amd-amdhsa.md) | * | | `-Ctarget-cpu=gfx...` to specify [the AMD GPU] to compile for [`arm64_32-apple-watchos`](platform-support/apple-watchos.md) | ✓ | | Arm Apple WatchOS 64-bit with 32-bit pointers [`arm64e-apple-darwin`](platform-support/arm64e-apple-darwin.md) | ✓ | ✓ | ARM64e Apple Darwin [`arm64e-apple-ios`](platform-support/arm64e-apple-ios.md) | ✓ | | ARM64e Apple iOS @@ -432,3 +433,4 @@ target | std | host | notes [`xtensa-esp32s3-none-elf`](platform-support/xtensa.md) | * | | Xtensa ESP32-S3 [runs on NVIDIA GPUs]: https://github.com/japaric-archived/nvptx#targets +[the AMD GPU]: https://llvm.org/docs/AMDGPUUsage.html#processors diff --git a/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md new file mode 100644 index 0000000000000..0b2f798e66de1 --- /dev/null +++ b/src/doc/rustc/src/platform-support/amdgcn-amd-amdhsa.md @@ -0,0 +1,111 @@ +# `amdgcn-amd-amdhsa` + +**Tier: 3** + +AMD GPU target for compute/HSA (Heterogeneous System Architecture). + +## Target maintainers + +- [@Flakebi](https://github.com/Flakebi) + +## Requirements + +AMD GPUs can be targeted via cross-compilation. +Supported GPUs depend on the LLVM version that is used by Rust. +In general, most GPUs starting from gfx7 (Sea Islands/CI) are supported as compilation targets, though older GPUs are not supported by the latest host runtime. +Details about supported GPUs can be found in [LLVM’s documentation] and [ROCm documentation]. + +Binaries can be loaded by [HIP] or by the HSA runtime implemented in [ROCR-Runtime]. +The format of binaries is a linked ELF. + +Binaries must be built with no-std. +They can use `core` and `alloc` (`alloc` only if an allocator is supplied). +At least one function needs to use the `"gpu-kernel"` calling convention and should be marked with `no_mangle` for simplicity. +Functions using the `"gpu-kernel"` calling convention are kernel entrypoints and can be used from the host runtime. + +## Building the target + +The target is included in rustc. + +## Building Rust programs + +The amdgpu target supports many hardware generations, which need different binaries. +The generations are exposed as different target-cpus in the backend. +As there are many, Rust does not ship pre-compiled libraries for this target. +Therefore, you have to build your own copy of `core` by using `cargo -Zbuild-std=core` or similar. + +To build a binary, create a no-std library: +```rust,ignore (platform-specific) +// src/lib.rs +#![feature(abi_gpu_kernel)] +#![no_std] + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +pub extern "gpu-kernel" fn kernel(/* Arguments */) { + // Code +} +``` + +Build the library as `cdylib`: +```toml +# Cargo.toml +[lib] +crate-type = ["cdylib"] + +[profile.dev] +lto = true # LTO must be explicitly enabled for now +[profile.release] +lto = true +``` + +The target-cpu must be from the list [supported by LLVM] (or printed with `rustc --target amdgcn-amd-amdhsa --print target-cpus`). +The GPU version on the current system can be found e.g. with [`rocminfo`]. + +Example `.cargo/config.toml` file to set the target and GPU generation: +```toml +# .cargo/config.toml +[build] +target = "amdgcn-amd-amdhsa" +rustflags = ["-Ctarget-cpu=gfx1100"] + +[unstable] +build-std = ["core"] # Optional: "alloc" +``` + +## Running Rust programs + +To run a binary on an AMD GPU, a host runtime is needed. +On Linux and Windows, [HIP] can be used to load and run binaries. +Example code on how to load a compiled binary and run it is available in [ROCm examples]. + +On Linux, binaries can also run through the HSA runtime as implemented in [ROCR-Runtime]. + + + + + +## Additional information + +More information can be found on the [LLVM page for amdgpu]. + +[LLVM’s documentation]: https://llvm.org/docs/AMDGPUUsage.html#processors +[ROCm documentation]: https://rocmdocs.amd.com +[HIP]: https://rocm.docs.amd.com/projects/HIP/ +[ROCR-Runtime]: https://github.com/ROCm/ROCR-Runtime +[supported by LLVM]: https://llvm.org/docs/AMDGPUUsage.html#processors +[LLVM page for amdgpu]: https://llvm.org/docs/AMDGPUUsage.html +[`rocminfo`]: https://github.com/ROCm/rocminfo +[ROCm examples]: https://github.com/ROCm/rocm-examples/tree/ca8ef5b6f1390176616cd1c18fbc98785cbc73f6/HIP-Basic/module_api diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index feec2a7444f87..835a7d8e80fad 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -67,6 +67,7 @@ static TARGETS: &[&str] = &[ "aarch64-unknown-none-softfloat", "aarch64-unknown-redox", "aarch64-unknown-uefi", + "amdgcn-amd-amdhsa", "arm64e-apple-darwin", "arm64e-apple-ios", "arm64e-apple-tvos", diff --git a/tests/assembly/targets/targets-amdgpu.rs b/tests/assembly/targets/targets-amdgpu.rs new file mode 100644 index 0000000000000..501eed2e24783 --- /dev/null +++ b/tests/assembly/targets/targets-amdgpu.rs @@ -0,0 +1,21 @@ +//@ assembly-output: emit-asm +// ignore-tidy-linelength +//@ revisions: amdgcn_amd_amdhsa +//@ [amdgcn_amd_amdhsa] compile-flags: --target amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 +//@ [amdgcn_amd_amdhsa] needs-llvm-components: amdgpu + +// Sanity-check that each target can produce assembly code. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +#[lang = "sized"] +trait Sized {} + +pub fn test() -> u8 { + 42 +} + +// CHECK: .version diff --git a/tests/ui/amdgpu-require-explicit-cpu.nocpu.stderr b/tests/ui/amdgpu-require-explicit-cpu.nocpu.stderr new file mode 100644 index 0000000000000..7480a8ed38f15 --- /dev/null +++ b/tests/ui/amdgpu-require-explicit-cpu.nocpu.stderr @@ -0,0 +1,4 @@ +error: target requires explicitly specifying a cpu with `-C target-cpu` + +error: aborting due to 1 previous error + diff --git a/tests/ui/amdgpu-require-explicit-cpu.rs b/tests/ui/amdgpu-require-explicit-cpu.rs new file mode 100644 index 0000000000000..46778a1094fab --- /dev/null +++ b/tests/ui/amdgpu-require-explicit-cpu.rs @@ -0,0 +1,17 @@ +//@ revisions: nocpu cpu +//@ no-prefer-dynamic +//@ compile-flags: --crate-type=cdylib --target=amdgcn-amd-amdhsa +//@ needs-llvm-components: amdgpu +//@ needs-rust-lld +//@[nocpu] error-pattern: target requires explicitly specifying a cpu +//@[nocpu] build-fail +//@[cpu] compile-flags: -Ctarget-cpu=gfx900 +//@[cpu] build-pass + +#![feature(no_core, lang_items)] +#![no_core] + +#[lang="sized"] +trait Sized {} + +pub fn foo() {} diff --git a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr index 7e77ba63a127f..e7b8f35505761 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.empty_cfg.stderr @@ -14,7 +14,7 @@ warning: unexpected `cfg` condition value: `value` LL | #[cfg(target_vendor = "value")] | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` + = note: expected values for `target_vendor` are: `amd`, `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition name: `feature` diff --git a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr index 10302f0a7e46a..93b5414cc3f2c 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.feature.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.feature.stderr @@ -15,7 +15,7 @@ warning: unexpected `cfg` condition value: `value` LL | #[cfg(target_vendor = "value")] | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` + = note: expected values for `target_vendor` are: `amd`, `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `unk` diff --git a/tests/ui/check-cfg/exhaustive-names-values.full.stderr b/tests/ui/check-cfg/exhaustive-names-values.full.stderr index 10302f0a7e46a..93b5414cc3f2c 100644 --- a/tests/ui/check-cfg/exhaustive-names-values.full.stderr +++ b/tests/ui/check-cfg/exhaustive-names-values.full.stderr @@ -15,7 +15,7 @@ warning: unexpected `cfg` condition value: `value` LL | #[cfg(target_vendor = "value")] | ^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` + = note: expected values for `target_vendor` are: `amd`, `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `unk` diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr index ffebd7e553198..6421cb8f2c2e7 100644 --- a/tests/ui/check-cfg/well-known-values.stderr +++ b/tests/ui/check-cfg/well-known-values.stderr @@ -138,7 +138,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_arch = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_arch` are: `aarch64`, `arm`, `arm64ec`, `avr`, `bpf`, `csky`, `hexagon`, `loongarch64`, `m68k`, `mips`, `mips32r6`, `mips64`, `mips64r6`, `msp430`, `nvptx64`, `powerpc`, `powerpc64`, `riscv32`, `riscv64`, `s390x`, `sparc`, `sparc64`, `wasm32`, `wasm64`, `x86`, `x86_64`, and `xtensa` + = note: expected values for `target_arch` are: `aarch64`, `amdgpu`, `arm`, `arm64ec`, `avr`, `bpf`, `csky`, `hexagon`, `loongarch64`, `m68k`, `mips`, `mips32r6`, `mips64`, `mips64r6`, `msp430`, `nvptx64`, `powerpc`, `powerpc64`, `riscv32`, `riscv64`, `s390x`, `sparc`, `sparc64`, `wasm32`, `wasm64`, `x86`, `x86_64`, and `xtensa` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -201,7 +201,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_os = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` + = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -230,7 +230,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_vendor = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` + = note: expected values for `target_vendor` are: `amd`, `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `mti`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, and `wrs` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -274,7 +274,7 @@ LL | #[cfg(target_os = "linuz")] // testing that we suggest `linux` | | | help: there is a expected value with a similar name: `"linux"` | - = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` + = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` = note: see for more information about checking conditional configuration warning: 28 warnings emitted From 860476f6e0b400994caa09878646d4d9fd91b7b5 Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Thu, 30 Jan 2025 09:13:21 +0100 Subject: [PATCH 4/5] Update encode_utf16 to mention it is native endian --- library/alloc/src/string.rs | 16 ++++++++-------- library/core/src/char/methods.rs | 6 +++--- library/core/src/str/mod.rs | 3 ++- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 0c9535dfaa628..b29f740ef0f2a 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -712,8 +712,8 @@ impl String { } } - /// Decode a UTF-16–encoded vector `v` into a `String`, returning [`Err`] - /// if `v` contains any invalid data. + /// Decode a native endian UTF-16–encoded vector `v` into a `String`, + /// returning [`Err`] if `v` contains any invalid data. /// /// # Examples /// @@ -745,8 +745,8 @@ impl String { Ok(ret) } - /// Decode a UTF-16–encoded slice `v` into a `String`, replacing - /// invalid data with [the replacement character (`U+FFFD`)][U+FFFD]. + /// Decode a native endian UTF-16–encoded slice `v` into a `String`, + /// replacing invalid data with [the replacement character (`U+FFFD`)][U+FFFD]. /// /// Unlike [`from_utf8_lossy`] which returns a [`Cow<'a, str>`], /// `from_utf16_lossy` returns a `String` since the UTF-16 to UTF-8 @@ -777,8 +777,8 @@ impl String { .collect() } - /// Decode a UTF-16LE–encoded vector `v` into a `String`, returning [`Err`] - /// if `v` contains any invalid data. + /// Decode a UTF-16LE–encoded vector `v` into a `String`, + /// returning [`Err`] if `v` contains any invalid data. /// /// # Examples /// @@ -852,8 +852,8 @@ impl String { } } - /// Decode a UTF-16BE–encoded vector `v` into a `String`, returning [`Err`] - /// if `v` contains any invalid data. + /// Decode a UTF-16BE–encoded vector `v` into a `String`, + /// returning [`Err`] if `v` contains any invalid data. /// /// # Examples /// diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs index fb8a740aced13..ccfdbf0eb704d 100644 --- a/library/core/src/char/methods.rs +++ b/library/core/src/char/methods.rs @@ -92,7 +92,7 @@ impl char { #[stable(feature = "assoc_char_consts", since = "1.52.0")] pub const UNICODE_VERSION: (u8, u8, u8) = crate::unicode::UNICODE_VERSION; - /// Creates an iterator over the UTF-16 encoded code points in `iter`, + /// Creates an iterator over the native endian UTF-16 encoded code points in `iter`, /// returning unpaired surrogates as `Err`s. /// /// # Examples @@ -704,7 +704,7 @@ impl char { unsafe { from_utf8_unchecked_mut(encode_utf8_raw(self as u32, dst)) } } - /// Encodes this character as UTF-16 into the provided `u16` buffer, + /// Encodes this character as native endian UTF-16 into the provided `u16` buffer, /// and then returns the subslice of the buffer that contains the encoded character. /// /// # Panics @@ -1828,7 +1828,7 @@ pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(dst.as_mut_ptr(), len) } } -/// Encodes a raw `u32` value as UTF-16 into the provided `u16` buffer, +/// Encodes a raw `u32` value as native endian UTF-16 into the provided `u16` buffer, /// and then returns the subslice of the buffer that contains the encoded character. /// /// Unlike `char::encode_utf16`, this method also handles codepoints in the surrogate range. diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 8a473b398bb5f..39fa6c1a25fe9 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -1108,7 +1108,8 @@ impl str { LinesAny(self.lines()) } - /// Returns an iterator of `u16` over the string encoded as UTF-16. + /// Returns an iterator of `u16` over the string encoded + /// as native endian UTF-16 (without byte-order mark). /// /// # Examples /// From 8e9422f94e6717721ffbad50862fcef9822c2537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 31 Jan 2025 20:36:44 +0000 Subject: [PATCH 5/5] Make comma separated lists of anything easier to make for errors Provide a new function `listify`, meant to be used in cases similar to `pluralize!`. When you have a slice of arbitrary elements that need to be presented to the user, `listify` allows you to turn that into a list of comma separated strings. This reduces a lot of redundant logic that happens often in diagnostics. --- .../rustc_borrowck/src/diagnostics/mod.rs | 20 ++++---- compiler/rustc_builtin_macros/src/format.rs | 18 ++++---- compiler/rustc_errors/src/lib.rs | 14 +----- .../src/hir_ty_lowering/errors.rs | 46 ++++--------------- compiler/rustc_hir_typeck/src/demand.rs | 20 ++++---- compiler/rustc_hir_typeck/src/expr.rs | 35 +++++--------- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 12 +++-- .../src/fn_ctxt/suggestions.rs | 14 ++---- compiler/rustc_lint_defs/src/lib.rs | 17 +++++++ compiler/rustc_middle/src/ty/diagnostics.rs | 16 +++---- compiler/rustc_privacy/src/lib.rs | 24 ++-------- 11 files changed, 88 insertions(+), 148 deletions(-) diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs index bd6f77156ca99..d13b4bde5765f 100644 --- a/compiler/rustc_borrowck/src/diagnostics/mod.rs +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use rustc_abi::{FieldIdx, VariantIdx}; use rustc_data_structures::fx::FxIndexMap; -use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan}; +use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, listify}; use rustc_hir::def::{CtorKind, Namespace}; use rustc_hir::{self as hir, CoroutineKind, LangItem}; use rustc_index::IndexSlice; @@ -29,7 +29,7 @@ use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use rustc_trait_selection::error_reporting::traits::call_kind::{CallDesugaringKind, call_kind}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{ - FulfillmentErrorCode, type_known_to_meet_bound_modulo_regions, + FulfillmentError, FulfillmentErrorCode, type_known_to_meet_bound_modulo_regions, }; use tracing::debug; @@ -1436,17 +1436,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> { error.obligation.predicate, ) } - [errors @ .., last] => { + _ => { format!( "you could `clone` the value and consume it, if the \ - following trait bounds could be satisfied: \ - {} and `{}`", - errors - .iter() - .map(|e| format!("`{}`", e.obligation.predicate)) - .collect::>() - .join(", "), - last.obligation.predicate, + following trait bounds could be satisfied: {}", + listify(&errors, |e: &FulfillmentError<'tcx>| format!( + "`{}`", + e.obligation.predicate + )) + .unwrap(), ) } }; diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 7c746bd719f31..90447da66807b 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -8,7 +8,9 @@ use rustc_ast::{ token, }; use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans}; +use rustc_errors::{ + Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans, listify, pluralize, +}; use rustc_expand::base::*; use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId}; @@ -975,15 +977,11 @@ fn report_invalid_references( } else { MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect()) }; - let arg_list = if let &[index] = &indexes[..] { - format!("argument {index}") - } else { - let tail = indexes.pop().unwrap(); - format!( - "arguments {head} and {tail}", - head = indexes.into_iter().map(|i| i.to_string()).collect::>().join(", ") - ) - }; + let arg_list = format!( + "argument{} {}", + pluralize!(indexes.len()), + listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default() + ); e = ecx.dcx().struct_span_err( span, format!("invalid reference to positional {arg_list} ({num_args_desc})"), diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 549729548f537..a6458c9ffdcdb 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -65,7 +65,7 @@ pub use rustc_error_messages::{ SubdiagMessage, fallback_fluent_bundle, fluent_bundle, }; use rustc_lint_defs::LintExpectationId; -pub use rustc_lint_defs::{Applicability, pluralize}; +pub use rustc_lint_defs::{Applicability, listify, pluralize}; use rustc_macros::{Decodable, Encodable}; pub use rustc_span::ErrorGuaranteed; pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker}; @@ -1999,18 +1999,6 @@ pub fn a_or_an(s: &str) -> &'static str { } } -/// Grammatical tool for displaying messages to end users in a nice form. -/// -/// Take a list ["a", "b", "c"] and output a display friendly version "a, b and c" -pub fn display_list_with_comma_and(v: &[T]) -> String { - match v { - [] => "".to_string(), - [a] => a.to_string(), - [a, b] => format!("{a} and {b}"), - [a, v @ ..] => format!("{a}, {}", display_list_with_comma_and(v)), - } -} - #[derive(Clone, Copy, PartialEq, Hash, Debug)] pub enum TerminalUrl { No, 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 36b214b6ae734..79aa2f4b8ccd2 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -3,7 +3,7 @@ use rustc_data_structures::sorted_map::SortedMap; use rustc_data_structures::unord::UnordMap; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, MultiSpan, pluralize, struct_span_code_err, + Applicability, Diag, ErrorGuaranteed, MultiSpan, listify, pluralize, struct_span_code_err, }; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -808,14 +808,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { .map(|(trait_, mut assocs)| { assocs.sort(); let trait_ = trait_.print_trait_sugared(); - format!("{} in `{trait_}`", match &assocs[..] { - [] => String::new(), - [only] => format!("`{only}`"), - [assocs @ .., last] => format!( - "{} and `{last}`", - assocs.iter().map(|a| format!("`{a}`")).collect::>().join(", ") - ), - }) + format!( + "{} in `{trait_}`", + listify(&assocs[..], |a| format!("`{a}`")).unwrap_or_default() + ) }) .collect::>(); names.sort(); @@ -1075,18 +1071,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } }) .collect(); - let this_type = match &types_and_spans[..] { - [.., _, (last, _)] => format!( - "{} and {last}", - types_and_spans[..types_and_spans.len() - 1] - .iter() - .map(|(x, _)| x.as_str()) - .intersperse(", ") - .collect::() - ), - [(only, _)] => only.to_string(), - [] => bug!("expected one segment to deny"), - }; + let this_type = listify(&types_and_spans, |(t, _)| t.to_string()) + .expect("expected one segment to deny"); let arg_spans: Vec = segments .clone() @@ -1102,21 +1088,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ProhibitGenericsArg::Infer => kinds.push("generic"), }); - let (kind, s) = match kinds[..] { - [.., _, last] => ( - format!( - "{} and {last}", - kinds[..kinds.len() - 1] - .iter() - .map(|&x| x) - .intersperse(", ") - .collect::() - ), - "s", - ), - [only] => (only.to_string(), ""), - [] => bug!("expected at least one generic to prohibit"), - }; + let s = pluralize!(kinds.len()); + let kind = + listify(&kinds, |k| k.to_string()).expect("expected at least one generic to prohibit"); let last_span = *arg_spans.last().unwrap(); let span: MultiSpan = arg_spans.into(); let mut err = struct_span_code_err!( diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index bc0766705854a..85e949952f8de 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1,4 +1,4 @@ -use rustc_errors::{Applicability, Diag, MultiSpan}; +use rustc_errors::{Applicability, Diag, MultiSpan, listify}; use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::intravisit::Visitor; @@ -1016,18 +1016,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }, self.tcx.def_path_str(candidate.item.container_id(self.tcx)) ), - [.., last] if other_methods_in_scope.len() < 5 => { + _ if other_methods_in_scope.len() < 5 => { format!( - "the methods of the same name on {} and `{}`", - other_methods_in_scope[..other_methods_in_scope.len() - 1] - .iter() - .map(|c| format!( - "`{}`", - self.tcx.def_path_str(c.item.container_id(self.tcx)) - )) - .collect::>() - .join(", "), - self.tcx.def_path_str(last.item.container_id(self.tcx)) + "the methods of the same name on {}", + listify( + &other_methods_in_scope[..other_methods_in_scope.len() - 1], + |c| format!("`{}`", self.tcx.def_path_str(c.item.container_id(self.tcx))) + ) + .unwrap_or_default(), ) } _ => format!( diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index f79667e59bae1..0582a7dcee171 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -11,7 +11,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::unord::UnordMap; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, pluralize, + Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize, struct_span_code_err, }; use rustc_hir::def::{CtorKind, DefKind, Res}; @@ -2190,13 +2190,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if !missing_mandatory_fields.is_empty() { let s = pluralize!(missing_mandatory_fields.len()); - let fields: Vec<_> = - missing_mandatory_fields.iter().map(|f| format!("`{f}`")).collect(); - let fields = match &fields[..] { - [] => unreachable!(), - [only] => only.to_string(), - [start @ .., last] => format!("{} and {last}", start.join(", ")), - }; + let fields = listify(&missing_mandatory_fields, |f| format!("`{f}`")).unwrap(); self.dcx() .struct_span_err( span.shrink_to_hi(), @@ -2553,25 +2547,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .partition(|field| field.2); err.span_labels(used_private_fields.iter().map(|(_, span, _)| *span), "private field"); if !remaining_private_fields.is_empty() { - let remaining_private_fields_len = remaining_private_fields.len(); - let names = match &remaining_private_fields - .iter() - .map(|(name, _, _)| name) - .collect::>()[..] - { - _ if remaining_private_fields_len > 6 => String::new(), - [name] => format!("`{name}` "), - [names @ .., last] => { - let names = names.iter().map(|name| format!("`{name}`")).collect::>(); - format!("{} and `{last}` ", names.join(", ")) - } - [] => bug!("expected at least one private field to report"), + let names = if remaining_private_fields.len() > 6 { + String::new() + } else { + format!( + "{} ", + listify(&remaining_private_fields, |(name, _, _)| format!("`{name}`")) + .expect("expected at least one private field to report") + ) }; err.note(format!( "{}private field{s} {names}that {were} not provided", if used_fields.is_empty() { "" } else { "...and other " }, - s = pluralize!(remaining_private_fields_len), - were = pluralize!("was", remaining_private_fields_len), + s = pluralize!(remaining_private_fields.len()), + were = pluralize!("was", remaining_private_fields.len()), )); } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index fb5fc109ce467..bbe653714962e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -4,8 +4,7 @@ use itertools::Itertools; use rustc_data_structures::fx::FxIndexSet; use rustc_errors::codes::*; use rustc_errors::{ - Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, a_or_an, - display_list_with_comma_and, pluralize, + Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, a_or_an, listify, pluralize, }; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; @@ -2462,7 +2461,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { param.span, format!( "{} need{} to match the {} type of this parameter", - display_list_with_comma_and(&other_param_matched_names), + listify(&other_param_matched_names, |n| n.to_string()) + .unwrap_or_default(), pluralize!(if other_param_matched_names.len() == 1 { 0 } else { @@ -2477,7 +2477,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!( "this parameter needs to match the {} type of {}", matched_ty, - display_list_with_comma_and(&other_param_matched_names), + listify(&other_param_matched_names, |n| n.to_string()) + .unwrap_or_default(), ), ); } @@ -2523,7 +2524,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { generic_param.span, format!( "{} {} reference this parameter `{}`", - display_list_with_comma_and(¶m_idents_matching), + listify(¶m_idents_matching, |n| n.to_string()) + .unwrap_or_default(), if param_idents_matching.len() == 2 { "both" } else { "all" }, generic_param.name.ident().name, ), diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 60f9265bfcc0d..beddbf971fd3e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -4,7 +4,7 @@ use core::iter; use hir::def_id::LocalDefId; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::packed::Pu128; -use rustc_errors::{Applicability, Diag, MultiSpan}; +use rustc_errors::{Applicability, Diag, MultiSpan, listify}; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::lang_items::LangItem; use rustc_hir::{ @@ -1836,16 +1836,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { error.obligation.predicate, )); } - [errors @ .., last] => { + _ => { diag.help(format!( "`Clone` is not implemented because the following trait bounds \ - could not be satisfied: {} and `{}`", - errors - .iter() - .map(|e| format!("`{}`", e.obligation.predicate)) - .collect::>() - .join(", "), - last.obligation.predicate, + could not be satisfied: {}", + listify(&errors, |e| format!("`{}`", e.obligation.predicate)) + .unwrap(), )); } } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 3f1e98556ef0c..7ffe4e4e4901c 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -42,6 +42,23 @@ macro_rules! pluralize { }; } +/// Grammatical tool for displaying messages to end users in a nice form. +/// +/// Take a list of items and a function to turn those items into a `String`, and output a display +/// friendly comma separated list of those items. +// FIXME(estebank): this needs to be changed to go through the translation machinery. +pub fn listify(list: &[T], fmt: impl Fn(&T) -> String) -> Option { + Some(match list { + [only] => fmt(&only), + [others @ .., last] => format!( + "{} and {}", + others.iter().map(|i| fmt(i)).collect::>().join(", "), + fmt(&last), + ), + [] => return None, + }) +} + /// Indicates the confidence in the correctness of a suggestion. /// /// All suggestions are marked with an `Applicability`. Tools use the applicability of a suggestion diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs index b4b66a8133a33..cb218a27e6237 100644 --- a/compiler/rustc_middle/src/ty/diagnostics.rs +++ b/compiler/rustc_middle/src/ty/diagnostics.rs @@ -5,7 +5,7 @@ use std::ops::ControlFlow; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{ - Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, pluralize, + Applicability, Diag, DiagArgValue, IntoDiagArg, into_diag_arg_using_display, listify, pluralize, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; @@ -362,11 +362,8 @@ pub fn suggest_constraining_type_params<'a>( let n = trait_names.len(); let stable = if all_stable { "" } else { "unstable " }; let trait_ = if all_known { format!("trait{}", pluralize!(n)) } else { String::new() }; - format!("{stable}{trait_}{}", match &trait_names[..] { - [t] => format!(" {t}"), - [ts @ .., last] => format!(" {} and {last}", ts.join(", ")), - [] => return false, - },) + let Some(trait_names) = listify(&trait_names, |n| n.to_string()) else { return false }; + format!("{stable}{trait_} {trait_names}") } else { // We're more explicit when there's a mix of stable and unstable traits. let mut trait_names = constraints @@ -378,10 +375,9 @@ pub fn suggest_constraining_type_params<'a>( .collect::>(); trait_names.sort(); trait_names.dedup(); - match &trait_names[..] { - [t] => t.to_string(), - [ts @ .., last] => format!("{} and {last}", ts.join(", ")), - [] => return false, + match listify(&trait_names, |t| t.to_string()) { + Some(names) => names, + None => return false, } }; let constraint = constraint.join(" + "); diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index d19df08519d26..3842b7035e537 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -24,7 +24,7 @@ use rustc_ast::MacroDef; use rustc_ast::visit::{VisitorResult, try_visit}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::intern::Interned; -use rustc_errors::MultiSpan; +use rustc_errors::{MultiSpan, listify}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId, LocalModDefId}; use rustc_hir::intravisit::{self, InferKind, Visitor}; @@ -958,29 +958,15 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { // | ^^ field `gamma` is private # `fields.2` is `false` // Get the list of all private fields for the main message. - let field_names: Vec<_> = fields.iter().map(|(name, _, _)| name).collect(); - let field_names = match &field_names[..] { - [] => return, - [name] => format!("`{name}`"), - [fields @ .., last] => format!( - "{} and `{last}`", - fields.iter().map(|f| format!("`{f}`")).collect::>().join(", "), - ), - }; + let Some(field_names) = listify(&fields[..], |(n, _, _)| format!("`{n}`")) else { return }; let span: MultiSpan = fields.iter().map(|(_, span, _)| *span).collect::>().into(); // Get the list of all private fields when pointing at the `..rest`. let rest_field_names: Vec<_> = fields.iter().filter(|(_, _, is_present)| !is_present).map(|(n, _, _)| n).collect(); let rest_len = rest_field_names.len(); - let rest_field_names = match &rest_field_names[..] { - [] => String::new(), - [name] => format!("`{name}`"), - [fields @ .., last] => format!( - "{} and `{last}`", - fields.iter().map(|f| format!("`{f}`")).collect::>().join(", "), - ), - }; + let rest_field_names = + listify(&rest_field_names[..], |n| format!("`{n}`")).unwrap_or_default(); // Get all the labels for each field or `..rest` in the primary MultiSpan. let labels = fields .iter() @@ -1005,7 +991,7 @@ impl<'tcx> NamePrivacyVisitor<'tcx> { } else { None }, - field_names: field_names.clone(), + field_names, variant_descr: def.variant_descr(), def_path_str: self.tcx.def_path_str(def.did()), labels,