mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
red-knot: VfsFile
input ingredient and a Vfs
(#11802)
This commit is contained in:
parent
db8f2c2d9f
commit
93973b96cb
9 changed files with 1195 additions and 26 deletions
143
Cargo.lock
generated
143
Cargo.lock
generated
|
@ -133,6 +133,12 @@ version = "1.0.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "1.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argfile"
|
name = "argfile"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -214,6 +220,12 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "camino"
|
||||||
|
version = "1.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cast"
|
name = "cast"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -361,10 +373,10 @@ version = "4.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -605,7 +617,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim 0.10.0",
|
"strsim 0.10.0",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -616,7 +628,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -742,6 +754,16 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eyre"
|
||||||
|
version = "0.6.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||||
|
dependencies = [
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.0.2"
|
version = "2.0.2"
|
||||||
|
@ -879,6 +901,21 @@ dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashlink"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -981,6 +1018,12 @@ dependencies = [
|
||||||
"rust-stemmers",
|
"rust-stemmers",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indenter"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.2.6"
|
version = "2.2.6"
|
||||||
|
@ -1086,7 +1129,7 @@ dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1212,7 +1255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
|
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1976,6 +2019,19 @@ dependencies = [
|
||||||
"seahash",
|
"seahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff_db"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"camino",
|
||||||
|
"countme",
|
||||||
|
"dashmap",
|
||||||
|
"filetime",
|
||||||
|
"rustc-hash",
|
||||||
|
"salsa-2022",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_dev"
|
name = "ruff_dev"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -2120,7 +2176,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"ruff_python_trivia",
|
"ruff_python_trivia",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2493,6 +2549,36 @@ version = "1.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "salsa-2022"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/salsa-rs/salsa.git?rev=05b4e3ebdcdc47730cdd359e7e97fb2470527279#05b4e3ebdcdc47730cdd359e7e97fb2470527279"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap",
|
||||||
|
"crossbeam",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"dashmap",
|
||||||
|
"hashlink",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"rustc-hash",
|
||||||
|
"salsa-2022-macros",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "salsa-2022-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/salsa-rs/salsa.git?rev=05b4e3ebdcdc47730cdd359e7e97fb2470527279#05b4e3ebdcdc47730cdd359e7e97fb2470527279"
|
||||||
|
dependencies = [
|
||||||
|
"eyre",
|
||||||
|
"heck 0.4.1",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -2523,7 +2609,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_derive_internals",
|
"serde_derive_internals",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2572,7 +2658,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2583,7 +2669,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2605,7 +2691,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2646,7 +2732,7 @@ dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2742,11 +2828,11 @@ version = "0.26.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2755,6 +2841,17 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.66"
|
version = "2.0.66"
|
||||||
|
@ -2819,7 +2916,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2830,7 +2927,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
"test-case-core",
|
"test-case-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2851,7 +2948,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2963,7 +3060,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3193,7 +3290,7 @@ checksum = "9881bea7cbe687e36c9ab3b778c36cd0487402e270304e8b1296d5085303c1a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3278,7 +3375,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3312,7 +3409,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -3345,7 +3442,7 @@ checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3614,7 +3711,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -42,6 +42,7 @@ bincode = { version = "1.3.3" }
|
||||||
bitflags = { version = "2.5.0" }
|
bitflags = { version = "2.5.0" }
|
||||||
bstr = { version = "1.9.1" }
|
bstr = { version = "1.9.1" }
|
||||||
cachedir = { version = "0.3.1" }
|
cachedir = { version = "0.3.1" }
|
||||||
|
camino = { version = "1.1.7" }
|
||||||
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
chrono = { version = "0.4.35", default-features = false, features = ["clock"] }
|
||||||
clap = { version = "4.5.3", features = ["derive"] }
|
clap = { version = "4.5.3", features = ["derive"] }
|
||||||
clap_complete_command = { version = "0.5.1" }
|
clap_complete_command = { version = "0.5.1" }
|
||||||
|
@ -79,7 +80,9 @@ libc = { version = "0.2.153" }
|
||||||
libcst = { version = "1.1.0", default-features = false }
|
libcst = { version = "1.1.0", default-features = false }
|
||||||
log = { version = "0.4.17" }
|
log = { version = "0.4.17" }
|
||||||
lsp-server = { version = "0.7.6" }
|
lsp-server = { version = "0.7.6" }
|
||||||
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = ["proposed"] }
|
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
|
||||||
|
"proposed",
|
||||||
|
] }
|
||||||
matchit = { version = "0.8.1" }
|
matchit = { version = "0.8.1" }
|
||||||
memchr = { version = "2.7.1" }
|
memchr = { version = "2.7.1" }
|
||||||
mimalloc = { version = "0.1.39" }
|
mimalloc = { version = "0.1.39" }
|
||||||
|
@ -100,13 +103,16 @@ rand = { version = "0.8.5" }
|
||||||
rayon = { version = "1.10.0" }
|
rayon = { version = "1.10.0" }
|
||||||
regex = { version = "1.10.2" }
|
regex = { version = "1.10.2" }
|
||||||
rustc-hash = { version = "1.1.0" }
|
rustc-hash = { version = "1.1.0" }
|
||||||
|
salsa = { git = "https://github.com/salsa-rs/salsa.git", package = "salsa-2022", rev = "05b4e3ebdcdc47730cdd359e7e97fb2470527279" }
|
||||||
schemars = { version = "0.8.16" }
|
schemars = { version = "0.8.16" }
|
||||||
seahash = { version = "4.1.0" }
|
seahash = { version = "4.1.0" }
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
serde-wasm-bindgen = { version = "0.6.4" }
|
serde-wasm-bindgen = { version = "0.6.4" }
|
||||||
serde_json = { version = "1.0.113" }
|
serde_json = { version = "1.0.113" }
|
||||||
serde_test = { version = "1.0.152" }
|
serde_test = { version = "1.0.152" }
|
||||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
serde_with = { version = "3.6.0", default-features = false, features = [
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
shellexpand = { version = "3.0.0" }
|
shellexpand = { version = "3.0.0" }
|
||||||
similar = { version = "2.4.0", features = ["inline"] }
|
similar = { version = "2.4.0", features = ["inline"] }
|
||||||
smallvec = { version = "1.13.2" }
|
smallvec = { version = "1.13.2" }
|
||||||
|
@ -131,7 +137,12 @@ unicode_names2 = { version = "1.2.2" }
|
||||||
unicode-normalization = { version = "0.1.23" }
|
unicode-normalization = { version = "0.1.23" }
|
||||||
ureq = { version = "2.9.6" }
|
ureq = { version = "2.9.6" }
|
||||||
url = { version = "2.5.0" }
|
url = { version = "2.5.0" }
|
||||||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "macro-diagnostics", "js"] }
|
uuid = { version = "1.6.1", features = [
|
||||||
|
"v4",
|
||||||
|
"fast-rng",
|
||||||
|
"macro-diagnostics",
|
||||||
|
"js",
|
||||||
|
] }
|
||||||
walkdir = { version = "2.3.2" }
|
walkdir = { version = "2.3.2" }
|
||||||
wasm-bindgen = { version = "0.2.92" }
|
wasm-bindgen = { version = "0.2.92" }
|
||||||
wasm-bindgen-test = { version = "0.3.42" }
|
wasm-bindgen-test = { version = "0.3.42" }
|
||||||
|
|
20
crates/ruff_db/Cargo.toml
Normal file
20
crates/ruff_db/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "ruff_db"
|
||||||
|
version = "0.0.0"
|
||||||
|
publish = false
|
||||||
|
authors = { workspace = true }
|
||||||
|
edition = { workspace = true }
|
||||||
|
rust-version = { workspace = true }
|
||||||
|
homepage = { workspace = true }
|
||||||
|
documentation = { workspace = true }
|
||||||
|
repository = { workspace = true }
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
camino = { workspace = true }
|
||||||
|
countme = { workspace = true }
|
||||||
|
dashmap = { workspace = true }
|
||||||
|
filetime = { workspace = true }
|
||||||
|
salsa = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
270
crates/ruff_db/src/file_system.rs
Normal file
270
crates/ruff_db/src/file_system.rs
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
use filetime::FileTime;
|
||||||
|
|
||||||
|
pub use memory::MemoryFileSystem;
|
||||||
|
pub use os::OsFileSystem;
|
||||||
|
|
||||||
|
mod memory;
|
||||||
|
mod os;
|
||||||
|
|
||||||
|
pub type Result<T> = std::io::Result<T>;
|
||||||
|
|
||||||
|
/// A file system that can be used to read and write files.
|
||||||
|
///
|
||||||
|
/// The file system is agnostic to the actual storage medium, it could be a real file system, a combination
|
||||||
|
/// of a real file system and an in-memory file system in the case of an LSP where unsaved changes are stored in memory,
|
||||||
|
/// or an all in-memory file system for testing.
|
||||||
|
pub trait FileSystem {
|
||||||
|
/// Reads the metadata of the file or directory at `path`.
|
||||||
|
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata>;
|
||||||
|
|
||||||
|
/// Reads the content of the file at `path`.
|
||||||
|
fn read(&self, path: &FileSystemPath) -> Result<String>;
|
||||||
|
|
||||||
|
/// Returns `true` if `path` exists.
|
||||||
|
fn exists(&self, path: &FileSystemPath) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO support untitled files for the LSP use case. Wrap a `str` and `String`
|
||||||
|
// The main question is how `as_std_path` would work for untitled files, that can only exist in the LSP case
|
||||||
|
// but there's no compile time guarantee that a [`OsFileSystem`] never gets an untitled file path.
|
||||||
|
|
||||||
|
/// Path to a file or directory stored in [`FileSystem`].
|
||||||
|
///
|
||||||
|
/// The path is guaranteed to be valid UTF-8.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub struct FileSystemPath(Utf8Path);
|
||||||
|
|
||||||
|
impl FileSystemPath {
|
||||||
|
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
|
||||||
|
let path = path.as_ref();
|
||||||
|
// SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
|
||||||
|
// *const Utf8Path to a *const FsPath is valid.
|
||||||
|
unsafe { &*(path as *const Utf8Path as *const FileSystemPath) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the path to an owned [`FileSystemPathBuf`].
|
||||||
|
pub fn to_path_buf(&self) -> FileSystemPathBuf {
|
||||||
|
FileSystemPathBuf(self.0.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path as a string slice.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the std path for the file.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_std_path(&self) -> &Path {
|
||||||
|
self.0.as_std_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Owned path to a file or directory stored in [`FileSystem`].
|
||||||
|
///
|
||||||
|
/// The path is guaranteed to be valid UTF-8.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Eq, PartialEq, Clone, Hash)]
|
||||||
|
pub struct FileSystemPathBuf(Utf8PathBuf);
|
||||||
|
|
||||||
|
impl Default for FileSystemPathBuf {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystemPathBuf {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Utf8PathBuf::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_path(&self) -> &FileSystemPath {
|
||||||
|
FileSystemPath::new(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<FileSystemPath> for FileSystemPathBuf {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &FileSystemPath {
|
||||||
|
self.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<FileSystemPath> for FileSystemPath {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &FileSystemPath {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<FileSystemPath> for str {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &FileSystemPath {
|
||||||
|
FileSystemPath::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<FileSystemPath> for String {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &FileSystemPath {
|
||||||
|
FileSystemPath::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for FileSystemPath {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.0.as_std_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for FileSystemPathBuf {
|
||||||
|
type Target = FileSystemPath;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FileSystemPath {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FileSystemPath {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FileSystemPathBuf {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for FileSystemPathBuf {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Metadata {
|
||||||
|
revision: FileRevision,
|
||||||
|
permissions: Option<u32>,
|
||||||
|
file_type: FileType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metadata {
|
||||||
|
pub fn revision(&self) -> FileRevision {
|
||||||
|
self.revision
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn permissions(&self) -> Option<u32> {
|
||||||
|
self.permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_type(&self) -> FileType {
|
||||||
|
self.file_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A number representing the revision of a file.
|
||||||
|
///
|
||||||
|
/// Two revisions that don't compare equal signify that the file has been modified.
|
||||||
|
/// Revisions aren't guaranteed to be monotonically increasing or in any specific order.
|
||||||
|
///
|
||||||
|
/// Possible revisions are:
|
||||||
|
/// * The last modification time of the file.
|
||||||
|
/// * The hash of the file's content.
|
||||||
|
/// * The revision as it comes from an external system, for example the LSP.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct FileRevision(u128);
|
||||||
|
|
||||||
|
impl FileRevision {
|
||||||
|
pub fn new(value: u128) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn zero() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_u128(self) -> u128 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u128> for FileRevision {
|
||||||
|
fn from(value: u128) -> Self {
|
||||||
|
FileRevision(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for FileRevision {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
FileRevision(u128::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FileTime> for FileRevision {
|
||||||
|
fn from(value: FileTime) -> Self {
|
||||||
|
let seconds = value.seconds() as u128;
|
||||||
|
let seconds = seconds << 64;
|
||||||
|
let nanos = u128::from(value.nanoseconds());
|
||||||
|
|
||||||
|
FileRevision(seconds | nanos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
|
pub enum FileType {
|
||||||
|
File,
|
||||||
|
Directory,
|
||||||
|
Symlink,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileType {
|
||||||
|
pub const fn is_file(self) -> bool {
|
||||||
|
matches!(self, FileType::File)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_directory(self) -> bool {
|
||||||
|
matches!(self, FileType::Directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_symlink(self) -> bool {
|
||||||
|
matches!(self, FileType::Symlink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::file_system::FileRevision;
|
||||||
|
use filetime::FileTime;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revision_from_file_time() {
|
||||||
|
let file_time = FileTime::now();
|
||||||
|
let revision = FileRevision::from(file_time);
|
||||||
|
|
||||||
|
let revision = revision.as_u128();
|
||||||
|
|
||||||
|
let nano = revision & 0xFFFF_FFFF_FFFF_FFFF;
|
||||||
|
let seconds = revision >> 64;
|
||||||
|
|
||||||
|
assert_eq!(file_time.nanoseconds(), nano as u32);
|
||||||
|
assert_eq!(file_time.seconds(), seconds as i64);
|
||||||
|
}
|
||||||
|
}
|
136
crates/ruff_db/src/file_system/memory.rs
Normal file
136
crates/ruff_db/src/file_system/memory.rs
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
use crate::file_system::{
|
||||||
|
FileSystem, FileSystemPath, FileSystemPathBuf, FileType, Metadata, Result,
|
||||||
|
};
|
||||||
|
use crate::FxDashMap;
|
||||||
|
use dashmap::mapref::one::RefMut;
|
||||||
|
use filetime::FileTime;
|
||||||
|
use rustc_hash::FxHasher;
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// In memory file system.
|
||||||
|
///
|
||||||
|
/// Only intended for testing purposes. Directories aren't yet supported.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MemoryFileSystem {
|
||||||
|
inner: Arc<MemoryFileSystemInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryFileSystem {
|
||||||
|
pub fn snapshot(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the files to the file system.
|
||||||
|
pub fn write_files<P, C>(&self, files: impl IntoIterator<Item = (P, C)>)
|
||||||
|
where
|
||||||
|
P: AsRef<FileSystemPath>,
|
||||||
|
C: ToString,
|
||||||
|
{
|
||||||
|
for (path, content) in files {
|
||||||
|
self.write_file(path.as_ref(), content.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a new file in the file system
|
||||||
|
pub fn write_file(&self, path: &FileSystemPath, content: String) {
|
||||||
|
let mut entry = self.entry_or_insert(path);
|
||||||
|
let value = entry.value_mut();
|
||||||
|
|
||||||
|
value.content = content;
|
||||||
|
value.last_modified = FileTime::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the permissions of the file at `path`.
|
||||||
|
///
|
||||||
|
/// Creates a new file with an empty content if the file doesn't exist.
|
||||||
|
pub fn set_permissions(&self, path: &FileSystemPath, permissions: u32) {
|
||||||
|
let mut entry = self.entry_or_insert(path);
|
||||||
|
let value = entry.value_mut();
|
||||||
|
value.permission = permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the last modified time of the file at `path` to now.
|
||||||
|
///
|
||||||
|
/// Creates a new file with an empty content if the file doesn't exist.
|
||||||
|
pub fn touch(&self, path: &FileSystemPath) {
|
||||||
|
let mut entry = self.entry_or_insert(path);
|
||||||
|
let value = entry.value_mut();
|
||||||
|
|
||||||
|
value.last_modified = FileTime::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_or_insert(
|
||||||
|
&self,
|
||||||
|
path: &FileSystemPath,
|
||||||
|
) -> RefMut<FileSystemPathBuf, FileData, BuildHasherDefault<FxHasher>> {
|
||||||
|
self.inner
|
||||||
|
.files
|
||||||
|
.entry(path.to_path_buf())
|
||||||
|
.or_insert_with(|| FileData {
|
||||||
|
content: String::new(),
|
||||||
|
last_modified: FileTime::now(),
|
||||||
|
permission: 0o755,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystem for MemoryFileSystem {
|
||||||
|
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata> {
|
||||||
|
let entry = self
|
||||||
|
.inner
|
||||||
|
.files
|
||||||
|
.get(&path.to_path_buf())
|
||||||
|
.ok_or_else(|| std::io::Error::new(ErrorKind::NotFound, "File not found"))?;
|
||||||
|
|
||||||
|
let value = entry.value();
|
||||||
|
|
||||||
|
Ok(Metadata {
|
||||||
|
revision: value.last_modified.into(),
|
||||||
|
permissions: Some(value.permission),
|
||||||
|
file_type: FileType::File,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, path: &FileSystemPath) -> Result<String> {
|
||||||
|
let entry = self
|
||||||
|
.inner
|
||||||
|
.files
|
||||||
|
.get(&path.to_path_buf())
|
||||||
|
.ok_or_else(|| std::io::Error::new(ErrorKind::NotFound, "File not found"))?;
|
||||||
|
|
||||||
|
let value = entry.value();
|
||||||
|
|
||||||
|
Ok(value.content.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&self, path: &FileSystemPath) -> bool {
|
||||||
|
self.inner.files.contains_key(&path.to_path_buf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for MemoryFileSystem {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut map = f.debug_map();
|
||||||
|
|
||||||
|
for entry in self.inner.files.iter() {
|
||||||
|
map.entry(entry.key(), entry.value());
|
||||||
|
}
|
||||||
|
map.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MemoryFileSystemInner {
|
||||||
|
files: FxDashMap<FileSystemPathBuf, FileData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FileData {
|
||||||
|
content: String,
|
||||||
|
last_modified: FileTime,
|
||||||
|
permission: u32,
|
||||||
|
}
|
51
crates/ruff_db/src/file_system/os.rs
Normal file
51
crates/ruff_db/src/file_system/os.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::file_system::{FileSystem, FileSystemPath, FileType, Metadata, Result};
|
||||||
|
use filetime::FileTime;
|
||||||
|
|
||||||
|
pub struct OsFileSystem;
|
||||||
|
|
||||||
|
impl OsFileSystem {
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn permissions(metadata: &std::fs::Metadata) -> Option<u32> {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
|
Some(metadata.permissions().mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn permissions(_metadata: &std::fs::Metadata) -> Option<u32> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileSystem for OsFileSystem {
|
||||||
|
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata> {
|
||||||
|
let metadata = path.as_std_path().metadata()?;
|
||||||
|
let last_modified = FileTime::from_last_modification_time(&metadata);
|
||||||
|
|
||||||
|
Ok(Metadata {
|
||||||
|
revision: last_modified.into(),
|
||||||
|
permissions: Self::permissions(&metadata),
|
||||||
|
file_type: metadata.file_type().into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, path: &FileSystemPath) -> Result<String> {
|
||||||
|
std::fs::read_to_string(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&self, path: &FileSystemPath) -> bool {
|
||||||
|
path.as_std_path().exists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::fs::FileType> for FileType {
|
||||||
|
fn from(file_type: std::fs::FileType) -> Self {
|
||||||
|
if file_type.is_file() {
|
||||||
|
FileType::File
|
||||||
|
} else if file_type.is_dir() {
|
||||||
|
FileType::Directory
|
||||||
|
} else {
|
||||||
|
FileType::Symlink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
crates/ruff_db/src/lib.rs
Normal file
126
crates/ruff_db/src/lib.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
use std::hash::BuildHasherDefault;
|
||||||
|
|
||||||
|
use rustc_hash::FxHasher;
|
||||||
|
use salsa::DbWithJar;
|
||||||
|
|
||||||
|
use crate::file_system::{FileSystem, FileSystemPath};
|
||||||
|
use crate::vfs::{VendoredPath, Vfs, VfsFile};
|
||||||
|
|
||||||
|
pub mod file_system;
|
||||||
|
pub mod vfs;
|
||||||
|
|
||||||
|
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||||
|
|
||||||
|
#[salsa::jar(db=Db)]
|
||||||
|
pub struct Jar(VfsFile);
|
||||||
|
|
||||||
|
/// Database that gives access to the virtual filesystem, source code, and parsed AST.
|
||||||
|
pub trait Db: DbWithJar<Jar> {
|
||||||
|
/// Interns a file system path and returns a salsa `File` ingredient.
|
||||||
|
///
|
||||||
|
/// The operation is guaranteed to always succeed, even if the path doesn't exist, isn't accessible, or if the path points to a directory.
|
||||||
|
/// In these cases, a file with status [`FileStatus::Deleted`](vfs::FileStatus::Deleted) is returned.
|
||||||
|
fn file(&self, path: &FileSystemPath) -> VfsFile
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.vfs().file(self, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interns a vendored file path. Returns `None` if no such vendored file exists and `Some` otherwise.
|
||||||
|
fn vendored_file(&self, path: &VendoredPath) -> Option<VfsFile>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.vfs().vendored(self, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_system(&self) -> &dyn FileSystem;
|
||||||
|
|
||||||
|
fn vfs(&self) -> &Vfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for upcasting a reference to a base trait object.
|
||||||
|
pub trait Upcast<T: ?Sized> {
|
||||||
|
fn upcast(&self) -> &T;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::file_system::{FileSystem, MemoryFileSystem};
|
||||||
|
use crate::vfs::{VendoredPathBuf, Vfs};
|
||||||
|
use crate::{Db, Jar};
|
||||||
|
|
||||||
|
/// Database that can be used for testing.
|
||||||
|
///
|
||||||
|
/// Uses an in memory filesystem and it stubs out the vendored files by default.
|
||||||
|
#[salsa::db(Jar)]
|
||||||
|
pub struct TestDb {
|
||||||
|
storage: salsa::Storage<Self>,
|
||||||
|
vfs: Vfs,
|
||||||
|
file_system: MemoryFileSystem,
|
||||||
|
events: std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestDb {
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut vfs = Vfs::default();
|
||||||
|
vfs.stub_vendored::<VendoredPathBuf, String>([]);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
storage: salsa::Storage::default(),
|
||||||
|
file_system: MemoryFileSystem::default(),
|
||||||
|
events: std::sync::Arc::default(),
|
||||||
|
vfs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn file_system(&self) -> &MemoryFileSystem {
|
||||||
|
&self.file_system
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn events(&self) -> std::sync::Arc<std::sync::Mutex<Vec<salsa::Event>>> {
|
||||||
|
self.events.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_system_mut(&mut self) -> &mut MemoryFileSystem {
|
||||||
|
&mut self.file_system
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vfs_mut(&mut self) -> &mut Vfs {
|
||||||
|
&mut self.vfs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Db for TestDb {
|
||||||
|
fn file_system(&self) -> &dyn FileSystem {
|
||||||
|
&self.file_system
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vfs(&self) -> &Vfs {
|
||||||
|
&self.vfs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl salsa::Database for TestDb {
|
||||||
|
fn salsa_event(&self, event: salsa::Event) {
|
||||||
|
tracing::trace!("event: {:?}", event);
|
||||||
|
let mut events = self.events.lock().unwrap();
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl salsa::ParallelDatabase for TestDb {
|
||||||
|
fn snapshot(&self) -> salsa::Snapshot<Self> {
|
||||||
|
salsa::Snapshot::new(Self {
|
||||||
|
storage: self.storage.snapshot(),
|
||||||
|
file_system: self.file_system.snapshot(),
|
||||||
|
vfs: self.vfs.snapshot(),
|
||||||
|
events: self.events.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
318
crates/ruff_db/src/vfs.rs
Normal file
318
crates/ruff_db/src/vfs.rs
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use countme::Count;
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
|
||||||
|
pub use path::{VendoredPath, VendoredPathBuf, VfsPath};
|
||||||
|
|
||||||
|
use crate::file_system::{FileRevision, FileSystemPath};
|
||||||
|
use crate::{Db, FxDashMap};
|
||||||
|
|
||||||
|
mod path;
|
||||||
|
|
||||||
|
/// Virtual file system that supports files from different sources.
|
||||||
|
///
|
||||||
|
/// The [`Vfs`] supports accessing files from:
|
||||||
|
///
|
||||||
|
/// * The file system
|
||||||
|
/// * Vendored files that are part of the distributed Ruff binary
|
||||||
|
///
|
||||||
|
/// ## Why do both the [`Vfs`] and [`FileSystem`](crate::FileSystem) trait exist?
|
||||||
|
///
|
||||||
|
/// It would have been an option to define [`FileSystem`](crate::FileSystem) in a way that all its operation accept
|
||||||
|
/// a [`VfsPath`]. This would have allowed to unify most of [`Vfs`] and [`FileSystem`](crate::FileSystem). The reason why they are
|
||||||
|
/// separate is that not all operations are supported for all [`VfsPath`]s:
|
||||||
|
///
|
||||||
|
/// * The only relevant operations for [`VendoredPath`]s are testing for existence and reading the content.
|
||||||
|
/// * The vendored file system is immutable and doesn't support writing nor does it require watching for changes.
|
||||||
|
/// * There's no requirement to walk the vendored typesystem.
|
||||||
|
///
|
||||||
|
/// The other reason is that most operations know if they are working with vendored or file system paths.
|
||||||
|
/// Requiring them to convert the path to an `VfsPath` to test if the file exist is cumbersome.
|
||||||
|
///
|
||||||
|
/// The main downside of the approach is that vendored files needs their own stubbing mechanism.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Vfs {
|
||||||
|
inner: Arc<VfsInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct VfsInner {
|
||||||
|
/// Lookup table that maps the path to a salsa interned [`VfsFile`] instance.
|
||||||
|
///
|
||||||
|
/// The map also stores entries for files that don't exist on the file system. This is necessary
|
||||||
|
/// so that queries that depend on the existence of a file are re-executed when the file is created.
|
||||||
|
///
|
||||||
|
files_by_path: FxDashMap<VfsPath, VfsFile>,
|
||||||
|
vendored: VendoredVfs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vfs {
|
||||||
|
/// Creates a new [`Vfs`] instance where the vendored files are stubbed out.
|
||||||
|
pub fn with_stubbed_vendored() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(VfsInner {
|
||||||
|
vendored: VendoredVfs::Stubbed(FxDashMap::default()),
|
||||||
|
..VfsInner::default()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks up a file by its path.
|
||||||
|
///
|
||||||
|
/// For a non-existing file, creates a new salsa [`VfsFile`] ingredient and stores it for future lookups.
|
||||||
|
///
|
||||||
|
/// The operation always succeeds even if the path doesn't exist on disk, isn't accessible or if the path points to a directory.
|
||||||
|
/// In these cases, a file with status [`FileStatus::Deleted`] is returned.
|
||||||
|
pub fn file(&self, db: &dyn Db, path: &FileSystemPath) -> VfsFile {
|
||||||
|
*self
|
||||||
|
.inner
|
||||||
|
.files_by_path
|
||||||
|
.entry(VfsPath::FileSystem(path.to_path_buf()))
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let metadata = db.file_system().metadata(path);
|
||||||
|
|
||||||
|
match metadata {
|
||||||
|
Ok(metadata) if metadata.file_type().is_file() => VfsFile::new(
|
||||||
|
db,
|
||||||
|
VfsPath::FileSystem(path.to_path_buf()),
|
||||||
|
metadata.permissions(),
|
||||||
|
metadata.revision(),
|
||||||
|
FileStatus::Exists,
|
||||||
|
Count::default(),
|
||||||
|
),
|
||||||
|
_ => VfsFile::new(
|
||||||
|
db,
|
||||||
|
VfsPath::FileSystem(path.to_path_buf()),
|
||||||
|
None,
|
||||||
|
FileRevision::zero(),
|
||||||
|
FileStatus::Deleted,
|
||||||
|
Count::default(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookups a vendored file by its path. Returns `Some` if a vendored file for the given path
|
||||||
|
/// exists and `None` otherwise.
|
||||||
|
pub fn vendored(&self, db: &dyn Db, path: &VendoredPath) -> Option<VfsFile> {
|
||||||
|
let file = match self
|
||||||
|
.inner
|
||||||
|
.files_by_path
|
||||||
|
.entry(VfsPath::Vendored(path.to_path_buf()))
|
||||||
|
{
|
||||||
|
Entry::Occupied(entry) => *entry.get(),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
let revision = self.inner.vendored.revision(path)?;
|
||||||
|
|
||||||
|
let file = VfsFile::new(
|
||||||
|
db,
|
||||||
|
VfsPath::Vendored(path.to_path_buf()),
|
||||||
|
Some(0o444),
|
||||||
|
revision,
|
||||||
|
FileStatus::Exists,
|
||||||
|
Count::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
entry.insert(file);
|
||||||
|
|
||||||
|
file
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stubs out the vendored files with the given content.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
/// If there are pending snapshots referencing this `Vfs` instance.
|
||||||
|
pub fn stub_vendored<P, S>(&mut self, vendored: impl IntoIterator<Item = (P, S)>)
|
||||||
|
where
|
||||||
|
P: AsRef<VendoredPath>,
|
||||||
|
S: ToString,
|
||||||
|
{
|
||||||
|
let inner = Arc::get_mut(&mut self.inner).unwrap();
|
||||||
|
|
||||||
|
let stubbed = FxDashMap::default();
|
||||||
|
|
||||||
|
for (path, content) in vendored {
|
||||||
|
stubbed.insert(path.as_ref().to_path_buf(), content.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.vendored = VendoredVfs::Stubbed(stubbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a salsa like snapshot of the files. The instances share
|
||||||
|
/// the same path to file mapping.
|
||||||
|
pub fn snapshot(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, db: &dyn Db, path: &VfsPath) -> String {
|
||||||
|
match path {
|
||||||
|
VfsPath::FileSystem(path) => db.file_system().read(path).unwrap_or_default(),
|
||||||
|
|
||||||
|
VfsPath::Vendored(vendored) => db
|
||||||
|
.vfs()
|
||||||
|
.inner
|
||||||
|
.vendored
|
||||||
|
.read(vendored)
|
||||||
|
.expect("Vendored file to exist"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Vfs {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut map = f.debug_map();
|
||||||
|
|
||||||
|
for entry in self.inner.files_by_path.iter() {
|
||||||
|
map.entry(entry.key(), entry.value());
|
||||||
|
}
|
||||||
|
map.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::input]
|
||||||
|
pub struct VfsFile {
|
||||||
|
/// The path of the file.
|
||||||
|
#[id]
|
||||||
|
#[return_ref]
|
||||||
|
pub path: VfsPath,
|
||||||
|
|
||||||
|
/// The unix permissions of the file. Only supported on unix systems. Always `None` on Windows
|
||||||
|
/// or when the file has been deleted.
|
||||||
|
pub permissions: Option<u32>,
|
||||||
|
|
||||||
|
/// The file revision. A file has changed if the revisions don't compare equal.
|
||||||
|
pub revision: FileRevision,
|
||||||
|
|
||||||
|
/// The status of the file.
|
||||||
|
///
|
||||||
|
/// Salsa doesn't support deleting inputs. The only way to signal to the depending queries that
|
||||||
|
/// the file has been deleted is to change the status to `Deleted`.
|
||||||
|
pub status: FileStatus,
|
||||||
|
|
||||||
|
/// Counter that counts the number of created file instances and active file instances.
|
||||||
|
/// Only enabled in debug builds.
|
||||||
|
#[allow(unused)]
|
||||||
|
count: Count<VfsFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsFile {
|
||||||
|
/// Reads the content of the file into a [`String`].
|
||||||
|
///
|
||||||
|
/// Reading the same file multiple times isn't guaranteed to return the same content. It's possible
|
||||||
|
/// that the file has been modified in between the reads. It's even possible that a file that
|
||||||
|
/// is considered to exist has been deleted in the meantime. If this happens, then the method returns
|
||||||
|
/// an empty string, which is the closest to the content that the file contains now. Returning
|
||||||
|
/// an empty string shouldn't be a problem because the query will be re-executed as soon as the
|
||||||
|
/// changes are applied to the database.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) fn read(&self, db: &dyn Db) -> String {
|
||||||
|
let path = self.path(db);
|
||||||
|
|
||||||
|
if path.is_file_system_path() {
|
||||||
|
// Add a dependency on the revision to ensure the operation gets re-executed when the file changes.
|
||||||
|
let _ = self.revision(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.vfs().read(db, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum FileStatus {
|
||||||
|
/// The file exists.
|
||||||
|
Exists,
|
||||||
|
|
||||||
|
/// The file was deleted, didn't exist to begin with or the path isn't a file.
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
enum VendoredVfs {
|
||||||
|
#[default]
|
||||||
|
Real,
|
||||||
|
Stubbed(FxDashMap<VendoredPathBuf, String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VendoredVfs {
|
||||||
|
fn revision(&self, path: &VendoredPath) -> Option<FileRevision> {
|
||||||
|
match self {
|
||||||
|
VendoredVfs::Real => todo!(),
|
||||||
|
VendoredVfs::Stubbed(stubbed) => stubbed
|
||||||
|
.contains_key(&path.to_path_buf())
|
||||||
|
.then_some(FileRevision::new(1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, path: &VendoredPath) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
VendoredVfs::Real => todo!(),
|
||||||
|
VendoredVfs::Stubbed(stubbed) => stubbed.get(&path.to_path_buf()).as_deref().cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::file_system::{FileRevision, FileSystemPath};
|
||||||
|
use crate::tests::TestDb;
|
||||||
|
use crate::vfs::{FileStatus, VendoredPath};
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_system_existing_file() {
|
||||||
|
let mut db = TestDb::new();
|
||||||
|
|
||||||
|
db.file_system_mut()
|
||||||
|
.write_files([("test.py", "print('Hello world')")]);
|
||||||
|
|
||||||
|
let test = db.file(FileSystemPath::new("test.py"));
|
||||||
|
|
||||||
|
assert_eq!(test.status(&db), FileStatus::Exists);
|
||||||
|
assert_eq!(test.permissions(&db), Some(0o755));
|
||||||
|
assert_ne!(test.revision(&db), FileRevision::zero());
|
||||||
|
assert_eq!(&test.read(&db), "print('Hello world')");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_system_non_existing_file() {
|
||||||
|
let db = TestDb::new();
|
||||||
|
|
||||||
|
let test = db.file(FileSystemPath::new("test.py"));
|
||||||
|
|
||||||
|
assert_eq!(test.status(&db), FileStatus::Deleted);
|
||||||
|
assert_eq!(test.permissions(&db), None);
|
||||||
|
assert_eq!(test.revision(&db), FileRevision::zero());
|
||||||
|
assert_eq!(&test.read(&db), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stubbed_vendored_file() {
|
||||||
|
let mut db = TestDb::new();
|
||||||
|
|
||||||
|
db.vfs_mut()
|
||||||
|
.stub_vendored([("test.py", "def foo() -> str")]);
|
||||||
|
|
||||||
|
let test = db
|
||||||
|
.vendored_file(VendoredPath::new("test.py"))
|
||||||
|
.expect("Vendored file to exist.");
|
||||||
|
|
||||||
|
assert_eq!(test.status(&db), FileStatus::Exists);
|
||||||
|
assert_eq!(test.permissions(&db), Some(0o444));
|
||||||
|
assert_ne!(test.revision(&db), FileRevision::zero());
|
||||||
|
assert_eq!(&test.read(&db), "def foo() -> str");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stubbed_vendored_file_non_existing() {
|
||||||
|
let db = TestDb::new();
|
||||||
|
|
||||||
|
assert_eq!(db.vendored_file(VendoredPath::new("test.py")), None);
|
||||||
|
}
|
||||||
|
}
|
140
crates/ruff_db/src/vfs/path.rs
Normal file
140
crates/ruff_db/src/vfs/path.rs
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use camino::{Utf8Path, Utf8PathBuf};
|
||||||
|
|
||||||
|
use crate::file_system::{FileSystemPath, FileSystemPathBuf};
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub struct VendoredPath(Utf8Path);
|
||||||
|
|
||||||
|
impl VendoredPath {
|
||||||
|
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
|
||||||
|
let path = path.as_ref();
|
||||||
|
// SAFETY: VendoredPath is marked as #[repr(transparent)] so the conversion from a
|
||||||
|
// *const Utf8Path to a *const VendoredPath is valid.
|
||||||
|
unsafe { &*(path as *const Utf8Path as *const VendoredPath) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_path_buf(&self) -> VendoredPathBuf {
|
||||||
|
VendoredPathBuf(self.0.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
self.0.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
|
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||||
|
|
||||||
|
impl Default for VendoredPathBuf {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VendoredPathBuf {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Utf8PathBuf::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_path(&self) -> &VendoredPath {
|
||||||
|
VendoredPath::new(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<VendoredPath> for VendoredPathBuf {
|
||||||
|
fn as_ref(&self) -> &VendoredPath {
|
||||||
|
self.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<VendoredPath> for VendoredPath {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &VendoredPath {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<VendoredPath> for str {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &VendoredPath {
|
||||||
|
VendoredPath::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<VendoredPath> for String {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &VendoredPath {
|
||||||
|
VendoredPath::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<Path> for VendoredPath {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &Path {
|
||||||
|
self.0.as_std_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for VendoredPathBuf {
|
||||||
|
type Target = VendoredPath;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Path to a file.
|
||||||
|
///
|
||||||
|
/// The path abstracts that files in Ruff can come from different sources:
|
||||||
|
///
|
||||||
|
/// * a file stored on disk
|
||||||
|
/// * a vendored file that ships as part of the ruff binary
|
||||||
|
/// * Future: A virtual file that references a slice of another file. For example, the CSS code in a python file.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub enum VfsPath {
|
||||||
|
/// Path that points to a file on disk.
|
||||||
|
FileSystem(FileSystemPathBuf),
|
||||||
|
Vendored(VendoredPathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsPath {
|
||||||
|
/// Create a new path to a file on the file system.
|
||||||
|
#[must_use]
|
||||||
|
pub fn file_system(path: impl AsRef<FileSystemPath>) -> Self {
|
||||||
|
VfsPath::FileSystem(path.as_ref().to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `Some` if the path is a file system path that points to a path on disk.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_file_system_path_buf(self) -> Option<FileSystemPathBuf> {
|
||||||
|
match self {
|
||||||
|
VfsPath::FileSystem(path) => Some(path),
|
||||||
|
VfsPath::Vendored(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the path is a file system path that points to a path on disk.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_file_system_path(&self) -> bool {
|
||||||
|
matches!(self, VfsPath::FileSystem(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yields the underlying [`str`] slice.
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
VfsPath::FileSystem(path) => path.as_str(),
|
||||||
|
VfsPath::Vendored(path) => path.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for VfsPath {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue