From 399dbc074b0b2dff78a5f268aca7b6fe576d0545 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 19 Mar 2024 13:05:50 +0100 Subject: [PATCH] internal: Enforce utf8 paths --- Cargo.lock | 7 + Cargo.toml | 1 + crates/flycheck/src/lib.rs | 8 +- crates/ide-diagnostics/Cargo.toml | 1 + .../src/handlers/unlinked_file.rs | 7 +- crates/ide/src/doc_links.rs | 16 +- crates/ide/src/doc_links/tests.rs | 30 +-- crates/ide/src/lib.rs | 6 +- crates/load-cargo/Cargo.toml | 1 + crates/load-cargo/src/lib.rs | 2 +- crates/paths/Cargo.toml | 6 +- crates/paths/src/lib.rs | 232 ++++++++++++------ crates/proc-macro-api/Cargo.toml | 2 +- crates/proc-macro-api/src/msg.rs | 12 +- crates/proc-macro-api/src/process.rs | 4 +- crates/proc-macro-srv/src/dylib.rs | 47 ++-- crates/proc-macro-srv/src/lib.rs | 24 +- crates/project-model/Cargo.toml | 2 +- crates/project-model/src/build_scripts.rs | 19 +- crates/project-model/src/cargo_workspace.rs | 17 +- crates/project-model/src/lib.rs | 5 +- crates/project-model/src/project_json.rs | 17 +- crates/project-model/src/sysroot.rs | 6 +- crates/project-model/src/tests.rs | 20 +- crates/project-model/src/workspace.rs | 3 +- crates/rust-analyzer/Cargo.toml | 3 +- crates/rust-analyzer/src/bin/main.rs | 2 +- .../rust-analyzer/src/cli/analysis_stats.rs | 2 +- crates/rust-analyzer/src/cli/diagnostics.rs | 2 +- crates/rust-analyzer/src/cli/lsif.rs | 2 +- crates/rust-analyzer/src/cli/scip.rs | 12 +- crates/rust-analyzer/src/config.rs | 35 ++- .../rust-analyzer/src/diagnostics/to_proto.rs | 5 +- crates/rust-analyzer/src/handlers/request.rs | 8 +- .../src/integrated_benchmarks.rs | 6 +- crates/rust-analyzer/src/lsp/to_proto.rs | 15 +- crates/rust-analyzer/src/main_loop.rs | 3 +- crates/rust-analyzer/src/reload.rs | 16 +- crates/rust-analyzer/tests/crate_graph.rs | 2 +- crates/rust-analyzer/tests/slow-tests/main.rs | 12 +- .../rust-analyzer/tests/slow-tests/support.rs | 6 +- .../rust-analyzer/tests/slow-tests/testdir.rs | 22 +- crates/toolchain/Cargo.toml | 3 +- crates/toolchain/src/lib.rs | 43 ++-- crates/vfs-notify/src/lib.rs | 6 +- crates/vfs/src/vfs_path.rs | 2 +- 46 files changed, 383 insertions(+), 319 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8e3dc338f..ce2cccf53a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,6 +732,7 @@ dependencies = [ "ide-db", "itertools", "once_cell", + "paths", "serde_json", "stdx", "syntax", @@ -931,6 +932,7 @@ dependencies = [ "hir-expand", "ide-db", "itertools", + "paths", "proc-macro-api", "project-model", "span", @@ -1225,6 +1227,9 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "paths" version = "0.0.0" +dependencies = [ + "camino", +] [[package]] name = "percent-encoding" @@ -1598,6 +1603,7 @@ dependencies = [ "oorandom", "parking_lot", "parser", + "paths", "proc-macro-api", "profile", "project-model", @@ -2024,6 +2030,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" name = "toolchain" version = "0.0.0" dependencies = [ + "camino", "home", ] diff --git a/Cargo.toml b/Cargo.toml index e940741009..d9343d2b96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ anyhow = "1.0.75" arrayvec = "0.7.4" bitflags = "2.4.1" cargo_metadata = "0.18.1" +camino = "1.1.6" chalk-solve = { version = "0.96.0", default-features = false } chalk-ir = "0.96.0" chalk-recursive = { version = "0.96.0", default-features = false } diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index f8efb52022..4ee86954ac 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs @@ -8,10 +8,10 @@ #![warn(rust_2018_idioms, unused_lifetimes)] -use std::{fmt, io, path::PathBuf, process::Command, time::Duration}; +use std::{fmt, io, process::Command, time::Duration}; use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use serde::Deserialize; @@ -53,7 +53,7 @@ pub enum FlycheckConfig { extra_args: Vec, extra_env: FxHashMap, ansi_color_output: bool, - target_dir: Option, + target_dir: Option, }, CustomCommand { command: String, @@ -363,7 +363,7 @@ impl FlycheckActor { }); cmd.arg("--manifest-path"); - cmd.arg(self.root.join("Cargo.toml").as_os_str()); + cmd.arg(self.root.join("Cargo.toml")); for target in target_triples { cmd.args(["--target", target.as_str()]); diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml index 8ccea99e9e..edd0500933 100644 --- a/crates/ide-diagnostics/Cargo.toml +++ b/crates/ide-diagnostics/Cargo.toml @@ -26,6 +26,7 @@ text-edit.workspace = true cfg.workspace = true hir.workspace = true ide-db.workspace = true +paths.workspace = true [dev-dependencies] expect-test = "1.4.0" diff --git a/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/crates/ide-diagnostics/src/handlers/unlinked_file.rs index becc24ab21..b9327f8556 100644 --- a/crates/ide-diagnostics/src/handlers/unlinked_file.rs +++ b/crates/ide-diagnostics/src/handlers/unlinked_file.rs @@ -8,6 +8,7 @@ use ide_db::{ source_change::SourceChange, RootDatabase, }; +use paths::Utf8Component; use syntax::{ ast::{self, edit::IndentLevel, HasModuleItem, HasName}, AstNode, TextRange, @@ -84,10 +85,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, file_id: FileId) -> Option> { // try resolving the relative difference of the paths as inline modules let mut current = root_module; - for ele in rel.as_ref().components() { + for ele in rel.as_utf8_path().components() { let seg = match ele { - std::path::Component::Normal(seg) => seg.to_str()?, - std::path::Component::RootDir => continue, + Utf8Component::Normal(seg) => seg, + Utf8Component::RootDir => continue, // shouldn't occur _ => continue 'crates, }; diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index d10bdca50d..64b4ccc5bd 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -5,8 +5,6 @@ mod tests; mod intra_doc_links; -use std::ffi::OsStr; - use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions}; use stdx::format_to; @@ -134,8 +132,8 @@ pub(crate) fn remove_links(markdown: &str) -> String { pub(crate) fn external_docs( db: &RootDatabase, FilePosition { file_id, offset }: FilePosition, - target_dir: Option<&OsStr>, - sysroot: Option<&OsStr>, + target_dir: Option<&str>, + sysroot: Option<&str>, ) -> Option { let sema = &Semantics::new(db); let file = sema.parse(file_id).syntax().clone(); @@ -331,8 +329,8 @@ fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>) fn get_doc_links( db: &RootDatabase, def: Definition, - target_dir: Option<&OsStr>, - sysroot: Option<&OsStr>, + target_dir: Option<&str>, + sysroot: Option<&str>, ) -> DocumentationLinks { let join_url = |base_url: Option, path: &str| -> Option { base_url.and_then(|url| url.join(path).ok()) @@ -479,15 +477,13 @@ fn map_links<'e>( fn get_doc_base_urls( db: &RootDatabase, def: Definition, - target_dir: Option<&OsStr>, - sysroot: Option<&OsStr>, + target_dir: Option<&str>, + sysroot: Option<&str>, ) -> (Option, Option) { let local_doc = target_dir - .and_then(|path| path.to_str()) .and_then(|path| Url::parse(&format!("file:///{path}/")).ok()) .and_then(|it| it.join("doc/").ok()); let system_doc = sysroot - .and_then(|it| it.to_str()) .map(|sysroot| format!("file:///{sysroot}/share/doc/rust/html/")) .and_then(|it| Url::parse(&it).ok()); diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs index 60e8d29a71..ac44908a90 100644 --- a/crates/ide/src/doc_links/tests.rs +++ b/crates/ide/src/doc_links/tests.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, iter}; +use std::iter; use expect_test::{expect, Expect}; use hir::Semantics; @@ -18,10 +18,10 @@ use crate::{ fn check_external_docs( ra_fixture: &str, - target_dir: Option<&OsStr>, + target_dir: Option<&str>, expect_web_url: Option, expect_local_url: Option, - sysroot: Option<&OsStr>, + sysroot: Option<&str>, ) { let (analysis, position) = fixture::position(ra_fixture); let links = analysis.external_docs(position, target_dir, sysroot).unwrap(); @@ -127,10 +127,10 @@ fn external_docs_doc_builtin_type() { //- /main.rs crate:foo let x: u3$02 = 0; "#, - Some(OsStr::new("/home/user/project")), + Some("/home/user/project"), Some(expect![[r#"https://doc.rust-lang.org/nightly/core/primitive.u32.html"#]]), Some(expect![[r#"file:///sysroot/share/doc/rust/html/core/primitive.u32.html"#]]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } @@ -143,10 +143,10 @@ use foo$0::Foo; //- /lib.rs crate:foo pub struct Foo; "#, - Some(OsStr::new("/home/user/project")), + Some("/home/user/project"), Some(expect![[r#"https://docs.rs/foo/*/foo/index.html"#]]), Some(expect![[r#"file:///home/user/project/doc/foo/index.html"#]]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } @@ -157,10 +157,10 @@ fn external_docs_doc_url_std_crate() { //- /main.rs crate:std use self$0; "#, - Some(OsStr::new("/home/user/project")), + Some("/home/user/project"), Some(expect!["https://doc.rust-lang.org/stable/std/index.html"]), Some(expect!["file:///sysroot/share/doc/rust/html/std/index.html"]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } @@ -171,10 +171,10 @@ fn external_docs_doc_url_struct() { //- /main.rs crate:foo pub struct Fo$0o; "#, - Some(OsStr::new("/home/user/project")), + Some("/home/user/project"), Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]), Some(expect![[r#"file:///home/user/project/doc/foo/struct.Foo.html"#]]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } @@ -185,10 +185,10 @@ fn external_docs_doc_url_windows_backslash_path() { //- /main.rs crate:foo pub struct Fo$0o; "#, - Some(OsStr::new(r"C:\Users\user\project")), + Some(r"C:\Users\user\project"), Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]), Some(expect![[r#"file:///C:/Users/user/project/doc/foo/struct.Foo.html"#]]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } @@ -199,10 +199,10 @@ fn external_docs_doc_url_windows_slash_path() { //- /main.rs crate:foo pub struct Fo$0o; "#, - Some(OsStr::new(r"C:/Users/user/project")), + Some("C:/Users/user/project"), Some(expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]]), Some(expect![[r#"file:///C:/Users/user/project/doc/foo/struct.Foo.html"#]]), - Some(OsStr::new("/sysroot")), + Some("/sysroot"), ); } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 1c57f4f8f6..11eaa88c5c 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -58,8 +58,6 @@ mod view_item_tree; mod view_memory_layout; mod view_mir; -use std::ffi::OsStr; - use cfg::CfgOptions; use fetch_crates::CrateInfo; use hir::ChangeWithProcMacros; @@ -511,8 +509,8 @@ impl Analysis { pub fn external_docs( &self, position: FilePosition, - target_dir: Option<&OsStr>, - sysroot: Option<&OsStr>, + target_dir: Option<&str>, + sysroot: Option<&str>, ) -> Cancellable { self.with_db(|db| { doc_links::external_docs(db, position, target_dir, sysroot).unwrap_or_default() diff --git a/crates/load-cargo/Cargo.toml b/crates/load-cargo/Cargo.toml index 05412e176b..48e84a7b25 100644 --- a/crates/load-cargo/Cargo.toml +++ b/crates/load-cargo/Cargo.toml @@ -20,6 +20,7 @@ tracing.workspace = true hir-expand.workspace = true ide-db.workspace = true +paths.workspace = true proc-macro-api.workspace = true project-model.workspace = true span.workspace = true diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs index fe680e47fe..bb88500a0f 100644 --- a/crates/load-cargo/src/lib.rs +++ b/crates/load-cargo/src/lib.rs @@ -38,7 +38,7 @@ pub fn load_workspace_at( load_config: &LoadCargoConfig, progress: &dyn Fn(String), ) -> anyhow::Result<(RootDatabase, vfs::Vfs, Option)> { - let root = AbsPathBuf::assert(std::env::current_dir()?.join(root)); + let root = AbsPathBuf::assert_utf8(std::env::current_dir()?.join(root)); let root = ProjectManifest::discover_single(&root)?; let mut workspace = ProjectWorkspace::load(root, cargo_config, progress)?; diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml index 3d8752b5a8..59a4ad9a25 100644 --- a/crates/paths/Cargo.toml +++ b/crates/paths/Cargo.toml @@ -12,10 +12,14 @@ rust-version.workspace = true doctest = false [dependencies] +camino.workspace = true # Adding this dep sadly puts a lot of rust-analyzer crates after the # serde-derive crate. Even though we don't activate the derive feature here, # someone else in the crate graph certainly does! # serde.workspace = true +[features] +serde1 = ["camino/serde1"] + [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index a63d251c20..3ffe4e11e9 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs @@ -1,4 +1,4 @@ -//! Thin wrappers around `std::path`, distinguishing between absolute and +//! Thin wrappers around `std::path`/`camino::path`, distinguishing between absolute and //! relative paths. #![warn(rust_2018_idioms, unused_lifetimes)] @@ -7,16 +7,24 @@ use std::{ borrow::Borrow, ffi::OsStr, fmt, ops, - path::{Component, Path, PathBuf}, + path::{Path, PathBuf}, }; -/// Wrapper around an absolute [`PathBuf`]. +pub use camino::*; + +/// Wrapper around an absolute [`Utf8PathBuf`]. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct AbsPathBuf(PathBuf); +pub struct AbsPathBuf(Utf8PathBuf); + +impl From for Utf8PathBuf { + fn from(AbsPathBuf(path_buf): AbsPathBuf) -> Utf8PathBuf { + path_buf + } +} impl From for PathBuf { fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf { - path_buf + path_buf.into() } } @@ -27,9 +35,21 @@ impl ops::Deref for AbsPathBuf { } } +impl AsRef for AbsPathBuf { + fn as_ref(&self) -> &Utf8Path { + self.0.as_path() + } +} + +impl AsRef for AbsPathBuf { + fn as_ref(&self) -> &OsStr { + self.0.as_ref() + } +} + impl AsRef for AbsPathBuf { fn as_ref(&self) -> &Path { - self.0.as_path() + self.0.as_ref() } } @@ -45,9 +65,9 @@ impl Borrow for AbsPathBuf { } } -impl TryFrom for AbsPathBuf { - type Error = PathBuf; - fn try_from(path_buf: PathBuf) -> Result { +impl TryFrom for AbsPathBuf { + type Error = Utf8PathBuf; + fn try_from(path_buf: Utf8PathBuf) -> Result { if !path_buf.is_absolute() { return Err(path_buf); } @@ -55,10 +75,20 @@ impl TryFrom for AbsPathBuf { } } -impl TryFrom<&str> for AbsPathBuf { +impl TryFrom for AbsPathBuf { type Error = PathBuf; - fn try_from(path: &str) -> Result { - AbsPathBuf::try_from(PathBuf::from(path)) + fn try_from(path_buf: PathBuf) -> Result { + if !path_buf.is_absolute() { + return Err(path_buf); + } + Ok(AbsPathBuf(Utf8PathBuf::from_path_buf(path_buf)?)) + } +} + +impl TryFrom<&str> for AbsPathBuf { + type Error = Utf8PathBuf; + fn try_from(path: &str) -> Result { + AbsPathBuf::try_from(Utf8PathBuf::from(path)) } } @@ -74,19 +104,31 @@ impl AbsPathBuf { /// # Panics /// /// Panics if `path` is not absolute. - pub fn assert(path: PathBuf) -> AbsPathBuf { + pub fn assert(path: Utf8PathBuf) -> AbsPathBuf { AbsPathBuf::try_from(path) - .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display())) + .unwrap_or_else(|path| panic!("expected absolute path, got {}", path)) + } + + /// Wrap the given absolute path in `AbsPathBuf` + /// + /// # Panics + /// + /// Panics if `path` is not absolute. + pub fn assert_utf8(path: PathBuf) -> AbsPathBuf { + AbsPathBuf::assert( + Utf8PathBuf::from_path_buf(path) + .unwrap_or_else(|path| panic!("expected utf8 path, got {}", path.display())), + ) } /// Coerces to an `AbsPath` slice. /// - /// Equivalent of [`PathBuf::as_path`] for `AbsPathBuf`. + /// Equivalent of [`Utf8PathBuf::as_path`] for `AbsPathBuf`. pub fn as_path(&self) -> &AbsPath { AbsPath::assert(self.0.as_path()) } - /// Equivalent of [`PathBuf::pop`] for `AbsPathBuf`. + /// Equivalent of [`Utf8PathBuf::pop`] for `AbsPathBuf`. /// /// Note that this won't remove the root component, so `self` will still be /// absolute. @@ -97,18 +139,30 @@ impl AbsPathBuf { impl fmt::Display for AbsPathBuf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0.display(), f) + fmt::Display::fmt(&self.0, f) } } -/// Wrapper around an absolute [`Path`]. +/// Wrapper around an absolute [`Utf8Path`]. #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] #[repr(transparent)] -pub struct AbsPath(Path); +pub struct AbsPath(Utf8Path); + +impl AsRef for AbsPath { + fn as_ref(&self) -> &Utf8Path { + &self.0 + } +} impl AsRef for AbsPath { fn as_ref(&self) -> &Path { - &self.0 + self.0.as_ref() + } +} + +impl AsRef for AbsPath { + fn as_ref(&self) -> &OsStr { + self.0.as_ref() } } @@ -120,9 +174,9 @@ impl ToOwned for AbsPath { } } -impl<'a> TryFrom<&'a Path> for &'a AbsPath { - type Error = &'a Path; - fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> { +impl<'a> TryFrom<&'a Utf8Path> for &'a AbsPath { + type Error = &'a Utf8Path; + fn try_from(path: &'a Utf8Path) -> Result<&'a AbsPath, &'a Utf8Path> { if !path.is_absolute() { return Err(path); } @@ -136,24 +190,24 @@ impl AbsPath { /// # Panics /// /// Panics if `path` is not absolute. - pub fn assert(path: &Path) -> &AbsPath { + pub fn assert(path: &Utf8Path) -> &AbsPath { assert!(path.is_absolute()); - unsafe { &*(path as *const Path as *const AbsPath) } + unsafe { &*(path as *const Utf8Path as *const AbsPath) } } - /// Equivalent of [`Path::parent`] for `AbsPath`. + /// Equivalent of [`Utf8Path::parent`] for `AbsPath`. pub fn parent(&self) -> Option<&AbsPath> { self.0.parent().map(AbsPath::assert) } - /// Equivalent of [`Path::join`] for `AbsPath` with an additional normalize step afterwards. - pub fn absolutize(&self, path: impl AsRef) -> AbsPathBuf { + /// Equivalent of [`Utf8Path::join`] for `AbsPath` with an additional normalize step afterwards. + pub fn absolutize(&self, path: impl AsRef) -> AbsPathBuf { self.join(path).normalize() } - /// Equivalent of [`Path::join`] for `AbsPath`. - pub fn join(&self, path: impl AsRef) -> AbsPathBuf { - self.as_ref().join(path).try_into().unwrap() + /// Equivalent of [`Utf8Path::join`] for `AbsPath`. + pub fn join(&self, path: impl AsRef) -> AbsPathBuf { + Utf8Path::join(self.as_ref(), path).try_into().unwrap() } /// Normalize the given path: @@ -172,7 +226,7 @@ impl AbsPath { AbsPathBuf(normalize_path(&self.0)) } - /// Equivalent of [`Path::to_path_buf`] for `AbsPath`. + /// Equivalent of [`Utf8Path::to_path_buf`] for `AbsPath`. pub fn to_path_buf(&self) -> AbsPathBuf { AbsPathBuf::try_from(self.0.to_path_buf()).unwrap() } @@ -181,7 +235,7 @@ impl AbsPath { panic!("We explicitly do not provide canonicalization API, as that is almost always a wrong solution, see #14430") } - /// Equivalent of [`Path::strip_prefix`] for `AbsPath`. + /// Equivalent of [`Utf8Path::strip_prefix`] for `AbsPath`. /// /// Returns a relative path. pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> { @@ -195,57 +249,61 @@ impl AbsPath { } pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { - Some(( - self.file_stem()?.to_str()?, - self.extension().and_then(|extension| extension.to_str()), - )) + Some((self.file_stem()?, self.extension())) } // region:delegate-methods - // Note that we deliberately don't implement `Deref` here. + // Note that we deliberately don't implement `Deref` here. // - // The problem with `Path` is that it directly exposes convenience IO-ing - // methods. For example, `Path::exists` delegates to `fs::metadata`. + // The problem with `Utf8Path` is that it directly exposes convenience IO-ing + // methods. For example, `Utf8Path::exists` delegates to `fs::metadata`. // // For `AbsPath`, we want to make sure that this is a POD type, and that all // IO goes via `fs`. That way, it becomes easier to mock IO when we need it. - pub fn file_name(&self) -> Option<&OsStr> { + pub fn file_name(&self) -> Option<&str> { self.0.file_name() } - pub fn extension(&self) -> Option<&OsStr> { + pub fn extension(&self) -> Option<&str> { self.0.extension() } - pub fn file_stem(&self) -> Option<&OsStr> { + pub fn file_stem(&self) -> Option<&str> { self.0.file_stem() } pub fn as_os_str(&self) -> &OsStr { self.0.as_os_str() } + pub fn as_str(&self) -> &str { + self.0.as_str() + } #[deprecated(note = "use Display instead")] - pub fn display(&self) -> std::path::Display<'_> { - self.0.display() + pub fn display(&self) -> ! { + unimplemented!() } #[deprecated(note = "use std::fs::metadata().is_ok() instead")] - pub fn exists(&self) -> bool { - self.0.exists() + pub fn exists(&self) -> ! { + unimplemented!() + } + + pub fn components(&self) -> Utf8Components<'_> { + self.0.components() } // endregion:delegate-methods } impl fmt::Display for AbsPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.0.display(), f) + fmt::Display::fmt(&self.0, f) } } -/// Wrapper around a relative [`PathBuf`]. +/// Wrapper around a relative [`Utf8PathBuf`]. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct RelPathBuf(PathBuf); +pub struct RelPathBuf(Utf8PathBuf); -impl From for PathBuf { - fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf { +impl From for Utf8PathBuf { + fn from(RelPathBuf(path_buf): RelPathBuf) -> Utf8PathBuf { path_buf } } @@ -257,15 +315,21 @@ impl ops::Deref for RelPathBuf { } } -impl AsRef for RelPathBuf { - fn as_ref(&self) -> &Path { +impl AsRef for RelPathBuf { + fn as_ref(&self) -> &Utf8Path { self.0.as_path() } } -impl TryFrom for RelPathBuf { - type Error = PathBuf; - fn try_from(path_buf: PathBuf) -> Result { +impl AsRef for RelPathBuf { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} + +impl TryFrom for RelPathBuf { + type Error = Utf8PathBuf; + fn try_from(path_buf: Utf8PathBuf) -> Result { if !path_buf.is_relative() { return Err(path_buf); } @@ -274,65 +338,79 @@ impl TryFrom for RelPathBuf { } impl TryFrom<&str> for RelPathBuf { - type Error = PathBuf; - fn try_from(path: &str) -> Result { - RelPathBuf::try_from(PathBuf::from(path)) + type Error = Utf8PathBuf; + fn try_from(path: &str) -> Result { + RelPathBuf::try_from(Utf8PathBuf::from(path)) } } impl RelPathBuf { /// Coerces to a `RelPath` slice. /// - /// Equivalent of [`PathBuf::as_path`] for `RelPathBuf`. + /// Equivalent of [`Utf8PathBuf::as_path`] for `RelPathBuf`. pub fn as_path(&self) -> &RelPath { RelPath::new_unchecked(self.0.as_path()) } } -/// Wrapper around a relative [`Path`]. +/// Wrapper around a relative [`Utf8Path`]. #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] #[repr(transparent)] -pub struct RelPath(Path); +pub struct RelPath(Utf8Path); + +impl AsRef for RelPath { + fn as_ref(&self) -> &Utf8Path { + &self.0 + } +} impl AsRef for RelPath { fn as_ref(&self) -> &Path { - &self.0 + self.0.as_ref() } } impl RelPath { /// Creates a new `RelPath` from `path`, without checking if it is relative. - pub fn new_unchecked(path: &Path) -> &RelPath { - unsafe { &*(path as *const Path as *const RelPath) } + pub fn new_unchecked(path: &Utf8Path) -> &RelPath { + unsafe { &*(path as *const Utf8Path as *const RelPath) } } - /// Equivalent of [`Path::to_path_buf`] for `RelPath`. + /// Equivalent of [`Utf8Path::to_path_buf`] for `RelPath`. pub fn to_path_buf(&self) -> RelPathBuf { RelPathBuf::try_from(self.0.to_path_buf()).unwrap() } + + pub fn as_utf8_path(&self) -> &Utf8Path { + self.as_ref() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } } /// Taken from -fn normalize_path(path: &Path) -> PathBuf { +fn normalize_path(path: &Utf8Path) -> Utf8PathBuf { let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().copied() { + let mut ret = if let Some(c @ Utf8Component::Prefix(..)) = components.peek().copied() { components.next(); - PathBuf::from(c.as_os_str()) + Utf8PathBuf::from(c.as_str()) } else { - PathBuf::new() + Utf8PathBuf::new() }; for component in components { match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); + Utf8Component::Prefix(..) => unreachable!(), + Utf8Component::RootDir => { + ret.push(component.as_str()); } - Component::CurDir => {} - Component::ParentDir => { + Utf8Component::CurDir => {} + Utf8Component::ParentDir => { ret.pop(); } - Component::Normal(c) => { + Utf8Component::Normal(c) => { ret.push(c); } } diff --git a/crates/proc-macro-api/Cargo.toml b/crates/proc-macro-api/Cargo.toml index 978ad15560..f30f6a0f23 100644 --- a/crates/proc-macro-api/Cargo.toml +++ b/crates/proc-macro-api/Cargo.toml @@ -23,7 +23,7 @@ snap = "1.1.0" indexmap = "2.1.0" # local deps -paths.workspace = true +paths = { workspace = true, features = ["serde1"] } tt.workspace = true stdx.workspace = true text-size.workspace = true diff --git a/crates/proc-macro-api/src/msg.rs b/crates/proc-macro-api/src/msg.rs index aa5aff455f..ad0e1f187b 100644 --- a/crates/proc-macro-api/src/msg.rs +++ b/crates/proc-macro-api/src/msg.rs @@ -1,11 +1,9 @@ //! Defines messages for cross-process message passing based on `ndjson` wire protocol pub(crate) mod flat; -use std::{ - io::{self, BufRead, Write}, - path::PathBuf, -}; +use std::io::{self, BufRead, Write}; +use paths::Utf8PathBuf; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::ProcMacroKind; @@ -27,7 +25,7 @@ pub const CURRENT_API_VERSION: u32 = RUST_ANALYZER_SPAN_SUPPORT; #[derive(Debug, Serialize, Deserialize)] pub enum Request { /// Since [`NO_VERSION_CHECK_VERSION`] - ListMacros { dylib_path: PathBuf }, + ListMacros { dylib_path: Utf8PathBuf }, /// Since [`NO_VERSION_CHECK_VERSION`] ExpandMacro(Box), /// Since [`VERSION_CHECK_VERSION`] @@ -89,7 +87,7 @@ pub struct ExpandMacro { /// Possible attributes for the attribute-like macros. pub attributes: Option, - pub lib: PathBuf, + pub lib: Utf8PathBuf, /// Environment variables to set during macro expansion. pub env: Vec<(String, String)>, @@ -273,7 +271,7 @@ mod tests { macro_body: FlatTree::new(&tt, CURRENT_API_VERSION, &mut span_data_table), macro_name: Default::default(), attributes: None, - lib: std::env::current_dir().unwrap(), + lib: Utf8PathBuf::from_path_buf(std::env::current_dir().unwrap()).unwrap(), env: Default::default(), current_dir: Default::default(), has_global_spans: ExpnGlobals { diff --git a/crates/proc-macro-api/src/process.rs b/crates/proc-macro-api/src/process.rs index 72f95643c8..35d48a1554 100644 --- a/crates/proc-macro-api/src/process.rs +++ b/crates/proc-macro-api/src/process.rs @@ -175,7 +175,7 @@ fn mk_child( env: &FxHashMap, null_stderr: bool, ) -> io::Result { - let mut cmd = Command::new(path.as_os_str()); + let mut cmd = Command::new(path); cmd.envs(env) .env("RUST_ANALYZER_INTERNALS_DO_NOT_USE", "this is unstable") .stdin(Stdio::piped()) @@ -183,7 +183,7 @@ fn mk_child( .stderr(if null_stderr { Stdio::null() } else { Stdio::inherit() }); if cfg!(windows) { let mut path_var = std::ffi::OsString::new(); - path_var.push(path.parent().unwrap().parent().unwrap().as_os_str()); + path_var.push(path.parent().unwrap().parent().unwrap()); path_var.push("\\bin;"); path_var.push(std::env::var_os("PATH").unwrap_or_default()); cmd.env("PATH", path_var); diff --git a/crates/proc-macro-srv/src/dylib.rs b/crates/proc-macro-srv/src/dylib.rs index 52b4cced5f..22c34ff167 100644 --- a/crates/proc-macro-srv/src/dylib.rs +++ b/crates/proc-macro-srv/src/dylib.rs @@ -1,16 +1,11 @@ //! Handles dynamic library loading for proc macro -use std::{ - fmt, - fs::File, - io, - path::{Path, PathBuf}, -}; +use std::{fmt, fs::File, io}; use libloading::Library; use memmap2::Mmap; use object::Object; -use paths::AbsPath; +use paths::{AbsPath, Utf8Path, Utf8PathBuf}; use proc_macro::bridge; use proc_macro_api::{read_dylib_info, ProcMacroKind}; @@ -26,7 +21,7 @@ fn is_derive_registrar_symbol(symbol: &str) -> bool { symbol.contains(NEW_REGISTRAR_SYMBOL) } -fn find_registrar_symbol(file: &Path) -> io::Result> { +fn find_registrar_symbol(file: &Utf8Path) -> io::Result> { let file = File::open(file)?; let buffer = unsafe { Mmap::map(&file)? }; @@ -62,12 +57,12 @@ fn find_registrar_symbol(file: &Path) -> io::Result> { /// /// It seems that on Windows that behaviour is default, so we do nothing in that case. #[cfg(windows)] -fn load_library(file: &Path) -> Result { +fn load_library(file: &Utf8Path) -> Result { unsafe { Library::new(file) } } #[cfg(unix)] -fn load_library(file: &Path) -> Result { +fn load_library(file: &Utf8Path) -> Result { use libloading::os::unix::Library as UnixLibrary; use std::os::raw::c_int; @@ -116,14 +111,14 @@ struct ProcMacroLibraryLibloading { } impl ProcMacroLibraryLibloading { - fn open(file: &Path) -> Result { + fn open(file: &Utf8Path) -> Result { let symbol_name = find_registrar_symbol(file)?.ok_or_else(|| { - invalid_data_err(format!("Cannot find registrar symbol in file {}", file.display())) + invalid_data_err(format!("Cannot find registrar symbol in file {file}")) })?; - let abs_file: &AbsPath = file.try_into().map_err(|_| { - invalid_data_err(format!("expected an absolute path, got {}", file.display())) - })?; + let abs_file: &AbsPath = file + .try_into() + .map_err(|_| invalid_data_err(format!("expected an absolute path, got {file}")))?; let version_info = read_dylib_info(abs_file)?; let lib = load_library(file).map_err(invalid_data_err)?; @@ -138,10 +133,10 @@ pub struct Expander { } impl Expander { - pub fn new(lib: &Path) -> Result { + pub fn new(lib: &Utf8Path) -> Result { // Some libraries for dynamic loading require canonicalized path even when it is // already absolute - let lib = lib.canonicalize()?; + let lib = lib.canonicalize_utf8()?; let lib = ensure_file_with_lock_free_access(&lib)?; @@ -176,30 +171,26 @@ impl Expander { /// Copy the dylib to temp directory to prevent locking in Windows #[cfg(windows)] -fn ensure_file_with_lock_free_access(path: &Path) -> io::Result { +fn ensure_file_with_lock_free_access(path: &Utf8Path) -> io::Result { use std::collections::hash_map::RandomState; - use std::ffi::OsString; use std::hash::{BuildHasher, Hasher}; if std::env::var("RA_DONT_COPY_PROC_MACRO_DLL").is_ok() { return Ok(path.to_path_buf()); } - let mut to = std::env::temp_dir(); + let mut to = Utf8PathBuf::from_path_buf(std::env::temp_dir()).unwrap(); let file_name = path.file_name().ok_or_else(|| { - io::Error::new( - io::ErrorKind::InvalidInput, - format!("File path is invalid: {}", path.display()), - ) + io::Error::new(io::ErrorKind::InvalidInput, format!("File path is invalid: {path}")) })?; // Generate a unique number by abusing `HashMap`'s hasher. // Maybe this will also "inspire" a libs team member to finally put `rand` in libstd. let t = RandomState::new().build_hasher().finish(); - let mut unique_name = OsString::from(t.to_string()); - unique_name.push(file_name); + let mut unique_name = t.to_string(); + unique_name.push_str(file_name); to.push(unique_name); std::fs::copy(path, &to).unwrap(); @@ -207,6 +198,6 @@ fn ensure_file_with_lock_free_access(path: &Path) -> io::Result { } #[cfg(unix)] -fn ensure_file_with_lock_free_access(path: &Path) -> io::Result { - Ok(path.to_path_buf()) +fn ensure_file_with_lock_free_access(path: &Utf8Path) -> io::Result { + Ok(path.to_owned()) } diff --git a/crates/proc-macro-srv/src/lib.rs b/crates/proc-macro-srv/src/lib.rs index 831632c64c..d7e0a708b1 100644 --- a/crates/proc-macro-srv/src/lib.rs +++ b/crates/proc-macro-srv/src/lib.rs @@ -33,12 +33,11 @@ use std::{ collections::{hash_map::Entry, HashMap}, env, ffi::OsString, - fs, - path::{Path, PathBuf}, - thread, + fs, thread, time::SystemTime, }; +use paths::{Utf8Path, Utf8PathBuf}; use proc_macro_api::{ msg::{ self, deserialize_span_data_index_map, serialize_span_data_index_map, ExpnGlobals, @@ -81,7 +80,7 @@ impl ProcMacroSrvSpan for Span { #[derive(Default)] pub struct ProcMacroSrv { - expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>, + expanders: HashMap<(Utf8PathBuf, SystemTime), dylib::Expander>, span_mode: SpanMode, } @@ -149,23 +148,22 @@ impl ProcMacroSrv { pub fn list_macros( &mut self, - dylib_path: &Path, + dylib_path: &Utf8Path, ) -> Result, String> { let expander = self.expander(dylib_path)?; Ok(expander.list_macros()) } - fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> { + fn expander(&mut self, path: &Utf8Path) -> Result<&dylib::Expander, String> { let time = fs::metadata(path) .and_then(|it| it.modified()) - .map_err(|err| format!("Failed to get file metadata for {}: {err}", path.display()))?; + .map_err(|err| format!("Failed to get file metadata for {path}: {err}",))?; Ok(match self.expanders.entry((path.to_path_buf(), time)) { - Entry::Vacant(v) => { - v.insert(dylib::Expander::new(path).map_err(|err| { - format!("Cannot create expander for {}: {err}", path.display()) - })?) - } + Entry::Vacant(v) => v.insert( + dylib::Expander::new(path) + .map_err(|err| format!("Cannot create expander for {path}: {err}",))?, + ), Entry::Occupied(e) => e.into_mut(), }) } @@ -306,6 +304,6 @@ impl Drop for EnvSnapshot { mod tests; #[cfg(test)] -pub fn proc_macro_test_dylib_path() -> std::path::PathBuf { +pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf { proc_macro_test::PROC_MACRO_TEST_LOCATION.into() } diff --git a/crates/project-model/Cargo.toml b/crates/project-model/Cargo.toml index 924a4a89e2..139689975d 100644 --- a/crates/project-model/Cargo.toml +++ b/crates/project-model/Cargo.toml @@ -26,7 +26,7 @@ itertools.workspace = true # local deps base-db.workspace = true cfg.workspace = true -paths.workspace = true +paths = { workspace = true, features = ["serde1"] } stdx.workspace = true toolchain.workspace = true diff --git a/crates/project-model/src/build_scripts.rs b/crates/project-model/src/build_scripts.rs index 709fc03717..d40eb26063 100644 --- a/crates/project-model/src/build_scripts.rs +++ b/crates/project-model/src/build_scripts.rs @@ -77,7 +77,7 @@ impl WorkspaceBuildScripts { cmd.args(&config.extra_args); cmd.arg("--manifest-path"); - cmd.arg(workspace_root.join("Cargo.toml").as_os_str()); + cmd.arg(workspace_root.join("Cargo.toml")); if let Some(target_dir) = &config.target_dir { cmd.arg("--target-dir").arg(target_dir); @@ -354,16 +354,11 @@ impl WorkspaceBuildScripts { } // cargo_metadata crate returns default (empty) path for // older cargos, which is not absolute, so work around that. - let out_dir = mem::take(&mut message.out_dir).into_os_string(); - if !out_dir.is_empty() { - let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir)); + let out_dir = mem::take(&mut message.out_dir); + if !out_dir.as_str().is_empty() { + let out_dir = AbsPathBuf::assert(out_dir); // inject_cargo_env(package, package_build_data); - // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() - if let Some(out_dir) = - out_dir.as_os_str().to_str().map(|s| s.to_owned()) - { - data.envs.push(("OUT_DIR".to_owned(), out_dir)); - } + data.envs.push(("OUT_DIR".to_owned(), out_dir.as_str().to_owned())); data.out_dir = Some(out_dir); data.cfgs = cfgs; } @@ -377,8 +372,8 @@ impl WorkspaceBuildScripts { if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) { - let filename = AbsPathBuf::assert(PathBuf::from(&filename)); - data.proc_macro_dylib_path = Some(filename); + let filename = AbsPath::assert(filename); + data.proc_macro_dylib_path = Some(filename.to_owned()); } } }); diff --git a/crates/project-model/src/cargo_workspace.rs b/crates/project-model/src/cargo_workspace.rs index 53b41ea1e8..4f6bac08f6 100644 --- a/crates/project-model/src/cargo_workspace.rs +++ b/crates/project-model/src/cargo_workspace.rs @@ -1,14 +1,13 @@ //! See [`CargoWorkspace`]. use std::ops; -use std::path::PathBuf; use std::str::from_utf8; use anyhow::Context; use base_db::Edition; use cargo_metadata::{CargoOpt, MetadataCommand}; use la_arena::{Arena, Idx}; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use serde::Deserialize; use serde_json::from_value; @@ -100,7 +99,7 @@ pub struct CargoConfig { pub invocation_strategy: InvocationStrategy, pub invocation_location: InvocationLocation, /// Optional path to use instead of `target` when building - pub target_dir: Option, + pub target_dir: Option, } pub type Package = Idx; @@ -262,7 +261,7 @@ impl CargoWorkspace { } } } - meta.current_dir(current_dir.as_os_str()); + meta.current_dir(current_dir); let mut other_options = vec![]; // cargo metadata only supports a subset of flags of what cargo usually accepts, and usually @@ -351,7 +350,7 @@ impl CargoWorkspace { id: id.repr.clone(), name, version, - manifest: AbsPathBuf::assert(manifest_path.into()).try_into().unwrap(), + manifest: AbsPathBuf::assert(manifest_path).try_into().unwrap(), targets: Vec::new(), is_local, is_member, @@ -370,7 +369,7 @@ impl CargoWorkspace { let tgt = targets.alloc(TargetData { package: pkg, name, - root: AbsPathBuf::assert(src_path.into()), + root: AbsPathBuf::assert(src_path), kind: TargetKind::new(&kind), required_features, }); @@ -393,11 +392,9 @@ impl CargoWorkspace { packages[source].active_features.extend(node.features); } - let workspace_root = - AbsPathBuf::assert(PathBuf::from(meta.workspace_root.into_os_string())); + let workspace_root = AbsPathBuf::assert(meta.workspace_root); - let target_directory = - AbsPathBuf::assert(PathBuf::from(meta.target_directory.into_os_string())); + let target_directory = AbsPathBuf::assert(meta.target_directory); CargoWorkspace { packages, targets, workspace_root, target_directory } } diff --git a/crates/project-model/src/lib.rs b/crates/project-model/src/lib.rs index 5b91f5d805..51bb4fe303 100644 --- a/crates/project-model/src/lib.rs +++ b/crates/project-model/src/lib.rs @@ -126,9 +126,8 @@ impl ProjectManifest { entities .filter_map(Result::ok) .map(|it| it.path().join("Cargo.toml")) - .filter(|it| it.exists()) - .map(AbsPathBuf::assert) - .filter_map(|it| it.try_into().ok()) + .map(AbsPathBuf::try_from) + .filter_map(|it| it.ok()?.try_into().ok()) .collect() } } diff --git a/crates/project-model/src/project_json.rs b/crates/project-model/src/project_json.rs index fba0aaa8ce..0b6e8caf5d 100644 --- a/crates/project-model/src/project_json.rs +++ b/crates/project-model/src/project_json.rs @@ -51,10 +51,9 @@ use base_db::{CrateDisplayName, CrateId, CrateName, Dependency, Edition}; use la_arena::RawIdx; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use serde::{de, Deserialize}; -use std::path::PathBuf; use crate::cfg_flag::CfgFlag; @@ -113,7 +112,7 @@ impl ProjectJson { .unwrap_or_else(|| root_module.starts_with(base)); let (include, exclude) = match crate_data.source { Some(src) => { - let absolutize = |dirs: Vec| { + let absolutize = |dirs: Vec| { dirs.into_iter().map(absolutize_on_base).collect::>() }; (absolutize(src.include_dirs), absolutize(src.exclude_dirs)) @@ -176,15 +175,15 @@ impl ProjectJson { #[derive(Deserialize, Debug, Clone)] pub struct ProjectJsonData { - sysroot: Option, - sysroot_src: Option, + sysroot: Option, + sysroot_src: Option, crates: Vec, } #[derive(Deserialize, Debug, Clone)] struct CrateData { display_name: Option, - root_module: PathBuf, + root_module: Utf8PathBuf, edition: EditionData, #[serde(default)] version: Option, @@ -194,7 +193,7 @@ struct CrateData { target: Option, #[serde(default)] env: FxHashMap, - proc_macro_dylib_path: Option, + proc_macro_dylib_path: Option, is_workspace_member: Option, source: Option, #[serde(default)] @@ -238,8 +237,8 @@ struct DepData { #[derive(Deserialize, Debug, Clone)] struct CrateSource { - include_dirs: Vec, - exclude_dirs: Vec, + include_dirs: Vec, + exclude_dirs: Vec, } fn deserialize_crate_name<'de, D>(de: D) -> std::result::Result diff --git a/crates/project-model/src/sysroot.rs b/crates/project-model/src/sysroot.rs index 3127bae8b0..1142d6243d 100644 --- a/crates/project-model/src/sysroot.rs +++ b/crates/project-model/src/sysroot.rs @@ -4,13 +4,13 @@ //! but we can't process `.rlib` and need source code instead. The source code //! is typically installed with `rustup component add rust-src` command. -use std::{env, fs, iter, ops, path::PathBuf, process::Command, sync::Arc}; +use std::{env, fs, iter, ops, process::Command, sync::Arc}; use anyhow::{format_err, Result}; use base_db::CrateName; use itertools::Itertools; use la_arena::{Arena, Idx}; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8PathBuf}; use rustc_hash::FxHashMap; use toolchain::{probe_for_binary, Tool}; @@ -419,7 +419,7 @@ fn discover_sysroot_dir( rustc.current_dir(current_dir).args(["--print", "sysroot"]); tracing::debug!("Discovering sysroot by {:?}", rustc); let stdout = utf8_stdout(rustc)?; - Ok(AbsPathBuf::assert(PathBuf::from(stdout))) + Ok(AbsPathBuf::assert(Utf8PathBuf::from(stdout))) } fn discover_sysroot_src_dir(sysroot_path: &AbsPathBuf) -> Option { diff --git a/crates/project-model/src/tests.rs b/crates/project-model/src/tests.rs index b9b1b701f6..fc0b507b33 100644 --- a/crates/project-model/src/tests.rs +++ b/crates/project-model/src/tests.rs @@ -1,12 +1,9 @@ -use std::{ - ops::Deref, - path::{Path, PathBuf}, -}; +use std::ops::Deref; use base_db::{CrateGraph, FileId, ProcMacroPaths}; use cfg::{CfgAtom, CfgDiff}; use expect_test::{expect_file, ExpectFile}; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8Path, Utf8PathBuf}; use rustc_hash::FxHashMap; use serde::de::DeserializeOwned; use triomphe::Arc; @@ -113,17 +110,16 @@ fn replace_root(s: &mut String, direction: bool) { fn replace_fake_sys_root(s: &mut String) { let fake_sysroot_path = get_test_path("fake-sysroot"); let fake_sysroot_path = if cfg!(windows) { - let normalized_path = - fake_sysroot_path.to_str().expect("expected str").replace('\\', r#"\\"#); + let normalized_path = fake_sysroot_path.as_str().replace('\\', r#"\\"#); format!(r#"{}\\"#, normalized_path) } else { - format!("{}/", fake_sysroot_path.to_str().expect("expected str")) + format!("{}/", fake_sysroot_path.as_str()) }; *s = s.replace(&fake_sysroot_path, "$FAKESYSROOT$") } -fn get_test_path(file: &str) -> PathBuf { - let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")); +fn get_test_path(file: &str) -> Utf8PathBuf { + let base = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")); base.join("test_data").join(file) } @@ -139,7 +135,7 @@ fn get_fake_sysroot() -> Sysroot { fn rooted_project_json(data: ProjectJsonData) -> ProjectJson { let mut root = "$ROOT$".to_owned(); replace_root(&mut root, true); - let path = Path::new(&root); + let path = Utf8Path::new(&root); let base = AbsPath::assert(path); ProjectJson::new(base, data) } @@ -268,7 +264,7 @@ fn smoke_test_real_sysroot_cargo() { let cargo_workspace = CargoWorkspace::new(meta); let sysroot = Ok(Sysroot::discover( - AbsPath::assert(Path::new(env!("CARGO_MANIFEST_DIR"))), + AbsPath::assert(Utf8Path::new(env!("CARGO_MANIFEST_DIR"))), &Default::default(), true, ) diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs index 8b4d6d9941..c6642efca2 100644 --- a/crates/project-model/src/workspace.rs +++ b/crates/project-model/src/workspace.rs @@ -1173,7 +1173,6 @@ fn detached_files_to_crate_graph( }; let display_name = detached_file .file_stem() - .and_then(|os_str| os_str.to_str()) .map(|file_stem| CrateDisplayName::from_canonical_name(file_stem.to_owned())); let detached_file_crate = crate_graph.add_crate_root( file_id, @@ -1555,7 +1554,7 @@ fn inject_cargo_env(package: &PackageData, env: &mut Env) { // CARGO_BIN_NAME, CARGO_BIN_EXE_ let manifest_dir = package.manifest.parent(); - env.set("CARGO_MANIFEST_DIR", manifest_dir.as_os_str().to_string_lossy().into_owned()); + env.set("CARGO_MANIFEST_DIR", manifest_dir.as_str().to_owned()); // Not always right, but works for common cases. env.set("CARGO", "cargo".into()); diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index e6984d6f41..6d70124188 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -43,6 +43,7 @@ nohash-hasher.workspace = true always-assert = "0.2.0" walkdir = "2.3.2" semver.workspace = true +memchr = "2.7.1" cfg.workspace = true flycheck.workspace = true @@ -63,7 +64,7 @@ parser.workspace = true toolchain.workspace = true vfs-notify.workspace = true vfs.workspace = true -memchr = "2.7.1" +paths.workspace = true [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e747ec87b1..78920f3aba 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -190,7 +190,7 @@ fn run_server() -> anyhow::Result<()> { Some(it) => it, None => { let cwd = env::current_dir()?; - AbsPathBuf::assert(cwd) + AbsPathBuf::assert_utf8(cwd) } }; diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 5c474908e7..c636f7494a 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -70,7 +70,7 @@ impl flags::AnalysisStats { let mut db_load_sw = self.stop_watch(); - let path = AbsPathBuf::assert(env::current_dir()?.join(&self.path)); + let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(&self.path)); let manifest = ProjectManifest::discover_single(&path)?; let mut workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index bd2646126d..79d6226deb 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -16,7 +16,7 @@ impl flags::Diagnostics { let cargo_config = CargoConfig { sysroot: Some(RustLibSource::Discover), ..Default::default() }; let with_proc_macro_server = if let Some(p) = &self.proc_macro_srv { - let path = vfs::AbsPathBuf::assert(std::env::current_dir()?.join(p)); + let path = vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(p)); ProcMacroServerChoice::Explicit(path) } else { ProcMacroServerChoice::Sysroot diff --git a/crates/rust-analyzer/src/cli/lsif.rs b/crates/rust-analyzer/src/cli/lsif.rs index f3f5ec1ebd..3ff9be7102 100644 --- a/crates/rust-analyzer/src/cli/lsif.rs +++ b/crates/rust-analyzer/src/cli/lsif.rs @@ -283,7 +283,7 @@ impl flags::Lsif { with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: false, }; - let path = AbsPathBuf::assert(env::current_dir()?.join(self.path)); + let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path)); let manifest = ProjectManifest::discover_single(&path)?; let workspace = ProjectWorkspace::load(manifest, &cargo_config, no_progress)?; diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs index 1061a433a5..aef2c1be22 100644 --- a/crates/rust-analyzer/src/cli/scip.rs +++ b/crates/rust-analyzer/src/cli/scip.rs @@ -27,7 +27,8 @@ impl flags::Scip { with_proc_macro_server: ProcMacroServerChoice::Sysroot, prefill_caches: true, }; - let root = vfs::AbsPathBuf::assert(std::env::current_dir()?.join(&self.path)).normalize(); + let root = + vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize(); let mut config = crate::config::Config::new( root.clone(), @@ -63,12 +64,7 @@ impl flags::Scip { special_fields: Default::default(), }) .into(), - project_root: format!( - "file://{}", - root.as_os_str() - .to_str() - .ok_or(anyhow::format_err!("Unable to normalize project_root path"))? - ), + project_root: format!("file://{root}"), text_document_encoding: scip_types::TextEncoding::UTF8.into(), special_fields: Default::default(), }; @@ -216,7 +212,7 @@ fn get_relative_filepath( rootpath: &vfs::AbsPathBuf, file_id: ide::FileId, ) -> Option { - Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_ref().to_str()?.to_owned()) + Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned()) } // SCIP Ranges have a (very large) optimization that ranges if they are on the same line diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index cbf1524659..e6b60f6906 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -7,11 +7,7 @@ //! configure the server itself, feature flags are passed into analysis, and //! tweak things like automatic insertion of `()` in completions. -use std::{ - fmt, iter, - ops::Not, - path::{Path, PathBuf}, -}; +use std::{fmt, iter, ops::Not}; use cfg::{CfgAtom, CfgDiff}; use flycheck::FlycheckConfig; @@ -27,6 +23,7 @@ use ide_db::{ }; use itertools::Itertools; use lsp_types::{ClientCapabilities, MarkupKind}; +use paths::{Utf8Path, Utf8PathBuf}; use project_model::{ CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, }; @@ -327,7 +324,7 @@ config_data! { /// These directories will be ignored by rust-analyzer. They are /// relative to the workspace root, and globs are not supported. You may /// also need to add the folders to Code's `files.watcherExclude`. - files_excludeDirs: Vec = "[]", + files_excludeDirs: Vec = "[]", /// Controls file watching implementation. files_watcher: FilesWatcherDef = "\"client\"", @@ -516,7 +513,7 @@ config_data! { /// This config takes a map of crate names with the exported proc-macro names to ignore as values. procMacro_ignored: FxHashMap, Box<[Box]>> = "{}", /// Internal config, path to proc-macro server executable. - procMacro_server: Option = "null", + procMacro_server: Option = "null", /// Exclude imports from find-all-references. references_excludeImports: bool = "false", @@ -864,7 +861,7 @@ impl Config { } let mut errors = Vec::new(); self.detached_files = - get_field::>(&mut json, &mut errors, "detachedFiles", None, "[]") + get_field::>(&mut json, &mut errors, "detachedFiles", None, "[]") .into_iter() .map(AbsPathBuf::assert) .collect(); @@ -956,7 +953,7 @@ impl Config { pub fn has_linked_projects(&self) -> bool { !self.data.linkedProjects.is_empty() } - pub fn linked_manifests(&self) -> impl Iterator + '_ { + pub fn linked_manifests(&self) -> impl Iterator + '_ { self.data.linkedProjects.iter().filter_map(|it| match it { ManifestOrProjectJson::Manifest(p) => Some(&**p), ManifestOrProjectJson::ProjectJson(_) => None, @@ -1410,9 +1407,11 @@ impl Config { } } - fn target_dir_from_config(&self) -> Option { + fn target_dir_from_config(&self) -> Option { self.data.cargo_targetDir.as_ref().and_then(|target_dir| match target_dir { - TargetDirectory::UseSubdirectory(true) => Some(PathBuf::from("target/rust-analyzer")), + TargetDirectory::UseSubdirectory(true) => { + Some(Utf8PathBuf::from("target/rust-analyzer")) + } TargetDirectory::UseSubdirectory(false) => None, TargetDirectory::Directory(dir) if dir.is_relative() => Some(dir.clone()), TargetDirectory::Directory(_) => None, @@ -1951,7 +1950,7 @@ where #[derive(Deserialize, Debug, Clone)] #[serde(untagged)] enum ManifestOrProjectJson { - Manifest(PathBuf), + Manifest(Utf8PathBuf), ProjectJson(ProjectJsonData), } @@ -2134,7 +2133,7 @@ pub enum MemoryLayoutHoverRenderKindDef { #[serde(untagged)] pub enum TargetDirectory { UseSubdirectory(bool), - Directory(PathBuf), + Directory(Utf8PathBuf), } macro_rules! _config_data { @@ -2263,7 +2262,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "type": "array", "items": { "type": "string" }, }, - "Vec" => set! { + "Vec" => set! { "type": "array", "items": { "type": "string" }, }, @@ -2291,7 +2290,7 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "Option" => set! { "type": ["null", "string"], }, - "Option" => set! { + "Option" => set! { "type": ["null", "string"], }, "Option" => set! { @@ -2774,7 +2773,7 @@ mod tests { .unwrap(); assert_eq!(config.data.cargo_targetDir, Some(TargetDirectory::UseSubdirectory(true))); assert!( - matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(PathBuf::from("target/rust-analyzer"))) + matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("target/rust-analyzer"))) ); } @@ -2793,10 +2792,10 @@ mod tests { .unwrap(); assert_eq!( config.data.cargo_targetDir, - Some(TargetDirectory::Directory(PathBuf::from("other_folder"))) + Some(TargetDirectory::Directory(Utf8PathBuf::from("other_folder"))) ); assert!( - matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(PathBuf::from("other_folder"))) + matches!(config.flycheck(), FlycheckConfig::CargoCommand { target_dir, .. } if target_dir == Some(Utf8PathBuf::from("other_folder"))) ); } } diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 6e6cc53c25..7c4deac93f 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -519,14 +519,13 @@ fn clippy_code_description(code: Option<&str>) -> Option (None, None), }; - let sysroot = sysroot.map(|p| p.root().as_os_str()); - let target_dir = cargo.map(|cargo| cargo.target_directory()).map(|p| p.as_os_str()); + let sysroot = sysroot.map(|p| p.root().as_str()); + let target_dir = cargo.map(|cargo| cargo.target_directory()).map(|p| p.as_str()); let Ok(remote_urls) = snap.analysis.external_docs(position, target_dir, sysroot) else { return if snap.config.local_docs() { @@ -2043,7 +2043,7 @@ fn run_rustfmt( cmd } RustfmtConfig::CustomCommand { command, args } => { - let cmd = PathBuf::from(&command); + let cmd = Utf8PathBuf::from(&command); let workspace = CargoTargetSpec::for_file(snap, file_id)?; let mut cmd = match workspace { Some(spec) => { diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 1c5a862c70..2731e845f3 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -52,7 +52,7 @@ fn integrated_highlighting_benchmark() { let file_id = { let file = workspace_to_load.join(file); - let path = VfsPath::from(AbsPathBuf::assert(file)); + let path = VfsPath::from(AbsPathBuf::assert_utf8(file)); vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}")) }; @@ -112,7 +112,7 @@ fn integrated_completion_benchmark() { let file_id = { let file = workspace_to_load.join(file); - let path = VfsPath::from(AbsPathBuf::assert(file)); + let path = VfsPath::from(AbsPathBuf::assert_utf8(file)); vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}")) }; @@ -274,7 +274,7 @@ fn integrated_diagnostics_benchmark() { let file_id = { let file = workspace_to_load.join(file); - let path = VfsPath::from(AbsPathBuf::assert(file)); + let path = VfsPath::from(AbsPathBuf::assert_utf8(file)); vfs.file_id(&path).unwrap_or_else(|| panic!("can't find virtual file for {path}")) }; diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index 44f0b1c80f..d8bb12528b 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1,7 +1,7 @@ //! Conversion of rust-analyzer specific types to lsp_types equivalents. use std::{ iter::once, - mem, path, + mem, sync::atomic::{AtomicU32, Ordering}, }; @@ -15,6 +15,7 @@ use ide::{ }; use ide_db::{rust_doc::format_docs, FxHasher}; use itertools::Itertools; +use paths::{Utf8Component, Utf8Prefix}; use semver::VersionReq; use serde_json::to_value; use vfs::AbsPath; @@ -816,9 +817,9 @@ pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. pub(crate) fn url_from_abs_path(path: &AbsPath) -> lsp_types::Url { let url = lsp_types::Url::from_file_path(path).unwrap(); - match path.as_ref().components().next() { - Some(path::Component::Prefix(prefix)) - if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) => + match path.components().next() { + Some(Utf8Component::Prefix(prefix)) + if matches!(prefix.kind(), Utf8Prefix::Disk(_) | Utf8Prefix::VerbatimDisk(_)) => { // Need to lowercase driver letter } @@ -2730,12 +2731,12 @@ struct ProcMacro { #[test] #[cfg(target_os = "windows")] fn test_lowercase_drive_letter() { - use std::path::Path; + use paths::Utf8Path; - let url = url_from_abs_path(Path::new("C:\\Test").try_into().unwrap()); + let url = url_from_abs_path(Utf8Path::new("C:\\Test").try_into().unwrap()); assert_eq!(url.to_string(), "file:///c:/Test"); - let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap()); + let url = url_from_abs_path(Utf8Path::new(r#"\\localhost\C$\my_dir"#).try_into().unwrap()); assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); } } diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 00e82b5c28..d602506939 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -714,10 +714,9 @@ impl GlobalState { message += &format!( ": {}", match dir.strip_prefix(self.config.root_path()) { - Some(relative_path) => relative_path.as_ref(), + Some(relative_path) => relative_path.as_utf8_path(), None => dir.as_ref(), } - .display() ); } diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index c2725e1fad..967904a01a 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -185,10 +185,8 @@ impl GlobalState { message.push_str( "`rust-analyzer.linkedProjects` have been specified, which may be incorrect. Specified project paths:\n", ); - message.push_str(&format!( - " {}", - self.config.linked_manifests().map(|it| it.display()).format("\n ") - )); + message + .push_str(&format!(" {}", self.config.linked_manifests().format("\n "))); if self.config.has_linked_project_jsons() { message.push_str("\nAdditionally, one or more project jsons are specified") } @@ -739,7 +737,7 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) const IMPLICIT_TARGET_FILES: &[&str] = &["build.rs", "src/main.rs", "src/lib.rs"]; const IMPLICIT_TARGET_DIRS: &[&str] = &["src/bin", "examples", "tests", "benches"]; - let file_name = match path.file_name().unwrap_or_default().to_str() { + let file_name = match path.file_name() { Some(it) => it, None => return false, }; @@ -754,18 +752,18 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) // .cargo/config{.toml} if path.extension().unwrap_or_default() != "rs" { let is_cargo_config = matches!(file_name, "config.toml" | "config") - && path.parent().map(|parent| parent.as_ref().ends_with(".cargo")).unwrap_or(false); + && path.parent().map(|parent| parent.as_str().ends_with(".cargo")).unwrap_or(false); return is_cargo_config; } - if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_ref().ends_with(it)) { + if IMPLICIT_TARGET_FILES.iter().any(|it| path.as_str().ends_with(it)) { return true; } let parent = match path.parent() { Some(it) => it, None => return false, }; - if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_ref().ends_with(it)) { + if IMPLICIT_TARGET_DIRS.iter().any(|it| parent.as_str().ends_with(it)) { return true; } if file_name == "main.rs" { @@ -773,7 +771,7 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) Some(it) => it, None => return false, }; - if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_ref().ends_with(it)) { + if IMPLICIT_TARGET_DIRS.iter().any(|it| grand_parent.as_str().ends_with(it)) { return true; } } diff --git a/crates/rust-analyzer/tests/crate_graph.rs b/crates/rust-analyzer/tests/crate_graph.rs index efd42fadf7..cf38032b94 100644 --- a/crates/rust-analyzer/tests/crate_graph.rs +++ b/crates/rust-analyzer/tests/crate_graph.rs @@ -60,7 +60,7 @@ fn get_fake_sysroot() -> Sysroot { let sysroot_path = get_fake_sysroot_path(); // there's no `libexec/` directory with a `proc-macro-srv` binary in that // fake sysroot, so we give them both the same path: - let sysroot_dir = AbsPathBuf::assert(sysroot_path); + let sysroot_dir = AbsPathBuf::assert_utf8(sysroot_path); let sysroot_src_dir = sysroot_dir.clone(); Sysroot::load(sysroot_dir, Some(Ok(sysroot_src_dir)), false) } diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs index 960f5b531d..cb43619262 100644 --- a/crates/rust-analyzer/tests/slow-tests/main.rs +++ b/crates/rust-analyzer/tests/slow-tests/main.rs @@ -917,7 +917,7 @@ fn resolve_proc_macro() { } let sysroot = project_model::Sysroot::discover_no_source( - &AbsPathBuf::assert(std::env::current_dir().unwrap()), + &AbsPathBuf::assert_utf8(std::env::current_dir().unwrap()), &Default::default(), ) .unwrap(); @@ -1002,7 +1002,7 @@ pub fn foo(_input: TokenStream) -> TokenStream { }, "procMacro": { "enable": true, - "server": proc_macro_server_path.as_path().as_ref(), + "server": proc_macro_server_path.as_path().as_str(), } })) .root("foo") @@ -1039,7 +1039,7 @@ fn test_will_rename_files_same_level() { let tmp_dir = TestDir::new(); let tmp_dir_path = tmp_dir.path().to_owned(); - let tmp_dir_str = tmp_dir_path.to_str().unwrap(); + let tmp_dir_str = tmp_dir_path.as_str(); let base_path = PathBuf::from(format!("file://{tmp_dir_str}")); let code = r#" @@ -1084,7 +1084,7 @@ use crate::old_folder::nested::foo as bar; "documentChanges": [ { "textDocument": { - "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), + "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").as_str().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), "version": null }, "edits": [ @@ -1141,7 +1141,7 @@ use crate::old_folder::nested::foo as bar; "documentChanges": [ { "textDocument": { - "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), + "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").as_str().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), "version": null }, "edits": [ @@ -1162,7 +1162,7 @@ use crate::old_folder::nested::foo as bar; }, { "textDocument": { - "uri": format!("file://{}", tmp_dir_path.join("src").join("old_folder").join("nested.rs").to_str().unwrap().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), + "uri": format!("file://{}", tmp_dir_path.join("src").join("old_folder").join("nested.rs").as_str().to_owned().replace("C:\\", "/c:/").replace('\\', "/")), "version": null }, "edits": [ diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index 1d831b8b10..8bbe6ff372 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -1,7 +1,6 @@ use std::{ cell::{Cell, RefCell}, fs, - path::{Path, PathBuf}, sync::Once, time::Duration, }; @@ -9,6 +8,7 @@ use std::{ use crossbeam_channel::{after, select, Receiver}; use lsp_server::{Connection, Message, Notification, Request}; use lsp_types::{notification::Exit, request::Shutdown, TextDocumentIdentifier, Url}; +use paths::{Utf8Path, Utf8PathBuf}; use rust_analyzer::{config::Config, lsp, main_loop}; use serde::Serialize; use serde_json::{json, to_string_pretty, Value}; @@ -21,7 +21,7 @@ use crate::testdir::TestDir; pub(crate) struct Project<'a> { fixture: &'a str, tmp_dir: Option, - roots: Vec, + roots: Vec, config: serde_json::Value, root_dir_contains_symlink: bool, } @@ -359,7 +359,7 @@ impl Server { self.client.sender.send(Message::Notification(not)).unwrap(); } - pub(crate) fn path(&self) -> &Path { + pub(crate) fn path(&self) -> &Utf8Path { self.dir.path() } } diff --git a/crates/rust-analyzer/tests/slow-tests/testdir.rs b/crates/rust-analyzer/tests/slow-tests/testdir.rs index b3ee7fa3d0..d113bd5127 100644 --- a/crates/rust-analyzer/tests/slow-tests/testdir.rs +++ b/crates/rust-analyzer/tests/slow-tests/testdir.rs @@ -1,11 +1,12 @@ use std::{ fs, io, - path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}, }; +use paths::{Utf8Path, Utf8PathBuf}; + pub(crate) struct TestDir { - path: PathBuf, + path: Utf8PathBuf, keep: bool, } @@ -51,10 +52,13 @@ impl TestDir { #[cfg(target_os = "windows")] std::os::windows::fs::symlink_dir(path, &symlink_path).unwrap(); - return TestDir { path: symlink_path, keep: false }; + return TestDir { + path: Utf8PathBuf::from_path_buf(symlink_path).unwrap(), + keep: false, + }; } - return TestDir { path, keep: false }; + return TestDir { path: Utf8PathBuf::from_path_buf(path).unwrap(), keep: false }; } panic!("Failed to create a temporary directory") } @@ -64,7 +68,7 @@ impl TestDir { self.keep = true; self } - pub(crate) fn path(&self) -> &Path { + pub(crate) fn path(&self) -> &Utf8Path { &self.path } } @@ -79,7 +83,7 @@ impl Drop for TestDir { let actual_path = filetype.is_symlink().then(|| fs::read_link(&self.path).unwrap()); if let Some(actual_path) = actual_path { - remove_dir_all(&actual_path).unwrap_or_else(|err| { + remove_dir_all(Utf8Path::from_path(&actual_path).unwrap()).unwrap_or_else(|err| { panic!( "failed to remove temporary link to directory {}: {err}", actual_path.display() @@ -88,18 +92,18 @@ impl Drop for TestDir { } remove_dir_all(&self.path).unwrap_or_else(|err| { - panic!("failed to remove temporary directory {}: {err}", self.path.display()) + panic!("failed to remove temporary directory {}: {err}", self.path) }); } } #[cfg(not(windows))] -fn remove_dir_all(path: &Path) -> io::Result<()> { +fn remove_dir_all(path: &Utf8Path) -> io::Result<()> { fs::remove_dir_all(path) } #[cfg(windows)] -fn remove_dir_all(path: &Path) -> io::Result<()> { +fn remove_dir_all(path: &Utf8Path) -> io::Result<()> { for _ in 0..99 { if fs::remove_dir_all(path).is_ok() { return Ok(()); diff --git a/crates/toolchain/Cargo.toml b/crates/toolchain/Cargo.toml index f9b120772f..c85efd432b 100644 --- a/crates/toolchain/Cargo.toml +++ b/crates/toolchain/Cargo.toml @@ -13,6 +13,7 @@ doctest = false [dependencies] home = "0.5.4" +camino.workspace = true [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs index a77fed585a..b577723612 100644 --- a/crates/toolchain/src/lib.rs +++ b/crates/toolchain/src/lib.rs @@ -2,10 +2,9 @@ #![warn(rust_2018_idioms, unused_lifetimes)] -use std::{ - env, iter, - path::{Path, PathBuf}, -}; +use std::{env, iter, path::PathBuf}; + +use camino::{Utf8Path, Utf8PathBuf}; #[derive(Copy, Clone)] pub enum Tool { @@ -16,7 +15,7 @@ pub enum Tool { } impl Tool { - pub fn proxy(self) -> Option { + pub fn proxy(self) -> Option { cargo_proxy(self.name()) } @@ -33,7 +32,7 @@ impl Tool { /// example: for cargo, this tries all paths in $PATH with appended `cargo`, returning the /// first that exists /// 4) If all else fails, we just try to use the executable name directly - pub fn prefer_proxy(self) -> PathBuf { + pub fn prefer_proxy(self) -> Utf8PathBuf { invoke(&[cargo_proxy, lookup_as_env_var, lookup_in_path], self.name()) } @@ -50,11 +49,11 @@ impl Tool { /// example: for cargo, this tries $CARGO_HOME/bin/cargo, or ~/.cargo/bin/cargo if $CARGO_HOME is unset. /// It seems that this is a reasonable place to try for cargo, rustc, and rustup /// 4) If all else fails, we just try to use the executable name directly - pub fn path(self) -> PathBuf { + pub fn path(self) -> Utf8PathBuf { invoke(&[lookup_as_env_var, lookup_in_path, cargo_proxy], self.name()) } - pub fn path_in(self, path: &Path) -> Option { + pub fn path_in(self, path: &Utf8Path) -> Option { probe_for_binary(path.join(self.name())) } @@ -68,42 +67,50 @@ impl Tool { } } -fn invoke(list: &[fn(&str) -> Option], executable: &str) -> PathBuf { +fn invoke(list: &[fn(&str) -> Option], executable: &str) -> Utf8PathBuf { list.iter().find_map(|it| it(executable)).unwrap_or_else(|| executable.into()) } /// Looks up the binary as its SCREAMING upper case in the env variables. -fn lookup_as_env_var(executable_name: &str) -> Option { - env::var_os(executable_name.to_ascii_uppercase()).map(Into::into) +fn lookup_as_env_var(executable_name: &str) -> Option { + env::var_os(executable_name.to_ascii_uppercase()) + .map(PathBuf::from) + .map(Utf8PathBuf::try_from) + .and_then(Result::ok) } /// Looks up the binary in the cargo home directory if it exists. -fn cargo_proxy(executable_name: &str) -> Option { +fn cargo_proxy(executable_name: &str) -> Option { let mut path = get_cargo_home()?; path.push("bin"); path.push(executable_name); probe_for_binary(path) } -fn get_cargo_home() -> Option { +fn get_cargo_home() -> Option { if let Some(path) = env::var_os("CARGO_HOME") { - return Some(path.into()); + return Utf8PathBuf::try_from(PathBuf::from(path)).ok(); } if let Some(mut path) = home::home_dir() { path.push(".cargo"); - return Some(path); + return Utf8PathBuf::try_from(path).ok(); } None } -fn lookup_in_path(exec: &str) -> Option { +fn lookup_in_path(exec: &str) -> Option { let paths = env::var_os("PATH").unwrap_or_default(); - env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe_for_binary) + env::split_paths(&paths) + .map(|path| path.join(exec)) + .map(PathBuf::from) + .map(Utf8PathBuf::try_from) + .filter_map(Result::ok) + .find_map(probe_for_binary) } -pub fn probe_for_binary(path: PathBuf) -> Option { +pub fn probe_for_binary(path: Utf8PathBuf) -> Option { let with_extension = match env::consts::EXE_EXTENSION { "" => None, it => Some(path.with_extension(it)), diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs index 15a0ea5409..94fd6cb78c 100644 --- a/crates/vfs-notify/src/lib.rs +++ b/crates/vfs-notify/src/lib.rs @@ -13,7 +13,7 @@ use std::fs; use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; -use paths::{AbsPath, AbsPathBuf}; +use paths::{AbsPath, AbsPathBuf, Utf8Path}; use vfs::loader; use walkdir::WalkDir; @@ -205,7 +205,7 @@ impl NotifyActor { if !entry.file_type().is_dir() { return true; } - let path = AbsPath::assert(entry.path()); + let path = AbsPath::assert(Utf8Path::from_path(entry.path()).unwrap()); root == path || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path) }); @@ -214,7 +214,7 @@ impl NotifyActor { let depth = entry.depth(); let is_dir = entry.file_type().is_dir(); let is_file = entry.file_type().is_file(); - let abs_path = AbsPathBuf::assert(entry.into_path()); + let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); if depth < 2 && is_dir { self.send(make_message(abs_path.clone())); } diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index 52ada32bdf..2d3fb9d88c 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs @@ -326,7 +326,7 @@ impl VirtualPath { } fn strip_prefix(&self, base: &VirtualPath) -> Option<&RelPath> { - <_ as AsRef>::as_ref(&self.0) + <_ as AsRef>::as_ref(&self.0) .strip_prefix(&base.0) .ok() .map(RelPath::new_unchecked)