diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2baf5c095..d146046e22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,19 @@ jobs: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] # 1.34 is the MSRV, and the other versions add new features through build.rs. + # The nightly versions are used to ensure that build.rs's version detection is compatible + # with them: # nightly-2022-06-17 is the last toolchain before `try_reserve_2` was stabilized. - rust-version: [ 1.34, 1.44, 1.56, nightly-2022-06-17, 1.63, stable ] + # nightly-2022-12-14 is the last toolchain before `path_buf_deref_mut` was stabilized. + rust-version: + - 1.34 + - 1.44 + - 1.56 + - nightly-2022-06-17 + - 1.63 + - nightly-2022-12-14 + - 1.68 + - stable exclude: # These versions started failing with "archive member 'lib.rmeta' with length 26456 is not # mach-o or llvm bitcode file". @@ -58,6 +69,13 @@ jobs: - uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ matrix.rust-version }} + - name: Disable sparse registries on nightly-2022-12-14 + # Sparse registries are experimental on this nightly, but are enabled by + # dtolnay/rust-toolchain. + if: matrix.rust-version == 'nightly-2022-12-14' + shell: bash + run: | + echo CARGO_REGISTRIES_CRATES_IO_PROTOCOL=git >> $GITHUB_ENV - name: Build the library run: cargo build - name: Test @@ -70,3 +88,19 @@ jobs: # Some optional features are not compatible with earlier versions if: ${{ matrix.rust-version == 'stable' }} run: cargo test --workspace --all-features + + miri: + name: Check unsafe code against miri + runs-on: ubuntu-latest + env: + RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: nightly + components: rust-src, miri + - name: Test the library + run: cargo miri test + env: + MIRIFLAGS: -Zmiri-disable-isolation diff --git a/CHANGELOG.md b/CHANGELOG.md index a859cd9f6c..c1c34c88ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Implement `DerefMut` for `Utf8PathBuf` on Rust 1.68 and above. + ## [1.1.3] - 2023-02-21 ### Added diff --git a/build.rs b/build.rs index 7f5cbdf9b6..c24fc8e616 100644 --- a/build.rs +++ b/build.rs @@ -24,13 +24,22 @@ fn main() { if compiler.minor >= 56 { println!("cargo:rustc-cfg=shrink_to"); } - // Stable and beta 1.63 have a stable try_reserve_2. + // NOTE: the below checks use == rather than `matches!`. This is because `matches!` isn't stable + // on Rust 1.34. + // try_reserve_2 was added in a 1.63 nightly. if (compiler.minor >= 63 && (compiler.channel == ReleaseChannel::Stable || compiler.channel == ReleaseChannel::Beta)) || compiler.minor >= 64 { println!("cargo:rustc-cfg=try_reserve_2"); } + // path_buf_deref_mut was added in a 1.68 nightly. + if (compiler.minor >= 68 + && (compiler.channel == ReleaseChannel::Stable || compiler.channel == ReleaseChannel::Beta)) + || compiler.minor >= 69 + { + println!("cargo:rustc-cfg=path_buf_deref_mut"); + } } struct Compiler { diff --git a/src/lib.rs b/src/lib.rs index a212c72938..b0af1f4874 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -484,6 +484,13 @@ impl Deref for Utf8PathBuf { } } +#[cfg(path_buf_deref_mut)] +impl std::ops::DerefMut for Utf8PathBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { Utf8Path::assume_utf8_mut(&mut self.0) } + } +} + impl fmt::Debug for Utf8PathBuf { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&**self, f) @@ -1457,6 +1464,11 @@ impl Utf8Path { // *const Path to a *const Utf8Path is valid. &*(path as *const Path as *const Utf8Path) } + + #[cfg(path_buf_deref_mut)] + unsafe fn assume_utf8_mut(path: &mut Path) -> &mut Utf8Path { + &mut *(path as *mut Path as *mut Utf8Path) + } } impl Clone for Box { diff --git a/src/tests.rs b/src/tests.rs index 305b760910..e84c227af9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -44,3 +44,11 @@ where { let _ = orig.into(); } + +#[cfg(path_buf_deref_mut)] +#[test] +fn test_deref_mut() { + // This test is mostly for miri. + let mut path_buf = Utf8PathBuf::from("foobar"); + let _: &mut Utf8Path = &mut path_buf; +}