diff --git a/Cargo.lock b/Cargo.lock index 4c2d822079cd6..638b2e3c9eabe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6799,6 +6799,7 @@ dependencies = [ "once_cell", "serde", "sha2 0.9.9", + "vfs", "walkdir", ] @@ -6810,6 +6811,7 @@ dependencies = [ "bcs", "clap", "codespan-reporting", + "dunce", "hex", "move-binary-format", "move-borrow-graph", @@ -6821,11 +6823,13 @@ dependencies = [ "move-ir-types", "move-symbol-pool", "once_cell", + "pathdiff", "petgraph 0.5.1", "regex", "serde", "stacker", "tempfile", + "vfs", ] [[package]] @@ -15482,6 +15486,12 @@ dependencies = [ "nom", ] +[[package]] +name = "vfs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4fe92cfc1bad19c19925d5eee4b30584dbbdee4ff10183b261acccbef74e2d" + [[package]] name = "vsimd" version = "0.8.0" diff --git a/external-crates/move/Cargo.lock b/external-crates/move/Cargo.lock index cf4fc81cb4107..49afc352046f7 100644 --- a/external-crates/move/Cargo.lock +++ b/external-crates/move/Cargo.lock @@ -1560,6 +1560,7 @@ dependencies = [ "serde_json", "tempfile", "url", + "vfs", ] [[package]] @@ -1688,6 +1689,7 @@ dependencies = [ "once_cell", "serde", "sha2", + "vfs", "walkdir", ] @@ -1700,6 +1702,7 @@ dependencies = [ "clap 4.4.1", "codespan-reporting", "datatest-stable", + "dunce", "hex", "move-binary-format", "move-borrow-graph", @@ -1712,11 +1715,13 @@ dependencies = [ "move-stdlib", "move-symbol-pool", "once_cell", + "pathdiff", "petgraph", "regex", "serde", "stacker", "tempfile", + "vfs", ] [[package]] @@ -2505,6 +2510,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.2.0" @@ -3759,6 +3770,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vfs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e4fe92cfc1bad19c19925d5eee4b30584dbbdee4ff10183b261acccbef74e2d" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/external-crates/move/Cargo.toml b/external-crates/move/Cargo.toml index 55a39c32d6327..b636259447435 100644 --- a/external-crates/move/Cargo.toml +++ b/external-crates/move/Cargo.toml @@ -61,6 +61,7 @@ once_cell = "1.7.2" ouroboros = "0.17.2" parking_lot = "0.11.1" paste = "1.0.5" +pathdiff = "0.2.1" petgraph = "0.5.1" phf = { version = "0.11", features = ["macros"] } plotters = { version = "0.3.0", default_features = false, features = ["evcxr", "line_series", "histogram"]} @@ -104,6 +105,7 @@ tui = "0.17.0" uint = "0.9.4" url = "2.2.2" variant_count = "1.1.0" +vfs = "0.10.0" walkdir = "2.3.1" whoami = { version = "1.2.1" } x25519-dalek = { version = "0.1.0", package = "x25519-dalek-fiat", default-features = false, features = ["std", "u64_backend"] } diff --git a/external-crates/move/crates/move-analyzer/Cargo.toml b/external-crates/move/crates/move-analyzer/Cargo.toml index 7c0e6279d1e63..87e1119b6cd4c 100644 --- a/external-crates/move/crates/move-analyzer/Cargo.toml +++ b/external-crates/move/crates/move-analyzer/Cargo.toml @@ -19,6 +19,7 @@ lsp-types.workspace = true serde_json.workspace = true tempfile.workspace = true url.workspace = true +vfs.workspace = true clap.workspace = true crossbeam.workspace = true move-command-line-common.workspace = true diff --git a/external-crates/move/crates/move-analyzer/src/bin/move-analyzer.rs b/external-crates/move/crates/move-analyzer/src/bin/move-analyzer.rs index 1a3b0e51b5047..6cdffc8a5325a 100644 --- a/external-crates/move/crates/move-analyzer/src/bin/move-analyzer.rs +++ b/external-crates/move/crates/move-analyzer/src/bin/move-analyzer.rs @@ -18,12 +18,11 @@ use std::{ }; use move_analyzer::{ - completion::on_completion_request, - context::Context, - symbols, - vfs::{on_text_document_sync_notification, VirtualFileSystem}, + completion::on_completion_request, context::Context, symbols, + vfs::on_text_document_sync_notification, }; use url::Url; +use vfs::{impls::memory::MemoryFS, VfsPath}; #[derive(Parser)] #[clap(author, version, about)] @@ -48,9 +47,9 @@ fn main() { let (connection, io_threads) = Connection::stdio(); let symbols = Arc::new(Mutex::new(symbols::empty_symbols())); - let mut context = Context { + let ide_files_root: VfsPath = MemoryFS::new().into(); + let context = Context { connection, - files: VirtualFileSystem::default(), symbols: symbols.clone(), }; @@ -125,7 +124,12 @@ fn main() { .unwrap_or(false); } - symbolicator_runner = symbols::SymbolicatorRunner::new(symbols.clone(), diag_sender, lint); + symbolicator_runner = symbols::SymbolicatorRunner::new( + ide_files_root.clone(), + symbols.clone(), + diag_sender, + lint, + ); // If initialization information from the client contains a path to the directory being // opened, try to initialize symbols before sending response to the client. Do not bother @@ -134,7 +138,9 @@ fn main() { // to be available right after the client is initialized. if let Some(uri) = initialize_params.root_uri { if let Some(p) = symbols::SymbolicatorRunner::root_dir(&uri.to_file_path().unwrap()) { - if let Ok((Some(new_symbols), _)) = symbols::get_symbols(p.as_path(), lint) { + if let Ok((Some(new_symbols), _)) = + symbols::get_symbols(ide_files_root.clone(), p.as_path(), lint) + { let mut old_symbols = symbols.lock().unwrap(); (*old_symbols).merge(new_symbols); } @@ -189,7 +195,8 @@ fn main() { }, } }, - Err(error) => eprintln!("symbolicator message error: {:?}", error), + Err(error) => + eprintln!("symbolicator message error: {:?}", error), } }, recv(context.connection.receiver) -> message => { @@ -199,7 +206,7 @@ fn main() { // a chance of completing pending requests (but should not accept new requests // either which is handled inside on_requst) - instead it quits after receiving // the exit notification from the client, which is handled below - shutdown_req_received = on_request(&context, &request, shutdown_req_received); + shutdown_req_received = on_request(&context, &request, ide_files_root.clone(), shutdown_req_received); } Ok(Message::Response(response)) => on_response(&context, &response), Ok(Message::Notification(notification)) => { @@ -210,7 +217,7 @@ fn main() { // It ought to, especially once it begins processing requests that may // take a long time to respond to. } - _ => on_notification(&mut context, &symbolicator_runner, ¬ification), + _ => on_notification(ide_files_root.clone(), &symbolicator_runner, ¬ification), } } Err(error) => eprintln!("IDE message error: {:?}", error), @@ -228,7 +235,12 @@ fn main() { /// The reason why this information is also passed as an argument is that according to the LSP /// spec, if any additional requests are received after shutdownd then the LSP implementation /// should respond with a particular type of error. -fn on_request(context: &Context, request: &Request, shutdown_request_received: bool) -> bool { +fn on_request( + context: &Context, + request: &Request, + ide_files_root: VfsPath, + shutdown_request_received: bool, +) -> bool { if shutdown_request_received { let response = lsp_server::Response::new_err( request.id.clone(), @@ -245,9 +257,12 @@ fn on_request(context: &Context, request: &Request, shutdown_request_received: b return true; } match request.method.as_str() { - lsp_types::request::Completion::METHOD => { - on_completion_request(context, request, &context.symbols.lock().unwrap()) - } + lsp_types::request::Completion::METHOD => on_completion_request( + context, + request, + ide_files_root.clone(), + &context.symbols.lock().unwrap(), + ), lsp_types::request::GotoDefinition::METHOD => { symbols::on_go_to_def_request(context, request, &context.symbols.lock().unwrap()); } @@ -286,7 +301,7 @@ fn on_response(_context: &Context, _response: &Response) { } fn on_notification( - context: &mut Context, + ide_files_root: VfsPath, symbolicator_runner: &symbols::SymbolicatorRunner, notification: &Notification, ) { @@ -295,11 +310,7 @@ fn on_notification( | lsp_types::notification::DidChangeTextDocument::METHOD | lsp_types::notification::DidSaveTextDocument::METHOD | lsp_types::notification::DidCloseTextDocument::METHOD => { - on_text_document_sync_notification( - &mut context.files, - symbolicator_runner, - notification, - ) + on_text_document_sync_notification(ide_files_root, symbolicator_runner, notification) } _ => eprintln!("handle notification '{}' from client", notification.method), } diff --git a/external-crates/move/crates/move-analyzer/src/completion.rs b/external-crates/move/crates/move-analyzer/src/completion.rs index d762d46fbef4e..7fe9afb26b156 100644 --- a/external-crates/move/crates/move-analyzer/src/completion.rs +++ b/external-crates/move/crates/move-analyzer/src/completion.rs @@ -15,6 +15,7 @@ use move_compiler::{ }; use move_symbol_pool::Symbol; use std::{collections::HashSet, path::PathBuf}; +use vfs::VfsPath; /// Constructs an `lsp_types::CompletionItem` with the given `label` and `kind`. fn completion_item(label: &str, kind: CompletionItemKind) -> CompletionItem { @@ -149,7 +150,12 @@ fn get_cursor_token(buffer: &str, position: &Position) -> Option { /// Sends the given connection a response to a completion request. /// /// The completions returned depend upon where the user's cursor is positioned. -pub fn on_completion_request(context: &Context, request: &Request, symbols: &Symbols) { +pub fn on_completion_request( + context: &Context, + request: &Request, + ide_files: VfsPath, + symbols: &Symbols, +) { eprintln!("handling completion request"); let parameters = serde_json::from_value::(request.params.clone()) .expect("could not deserialize completion request"); @@ -160,38 +166,40 @@ pub fn on_completion_request(context: &Context, request: &Request, symbols: &Sym .uri .to_file_path() .unwrap(); - let buffer = context.files.get(&path); - if buffer.is_none() { - eprintln!( - "Could not read '{:?}' when handling completion request", - path - ); + let mut buffer = String::new(); + if let Ok(mut f) = ide_files.join(path.to_string_lossy()).unwrap().open_file() { + if f.read_to_string(&mut buffer).is_err() { + eprintln!( + "Could not read '{:?}' when handling completion request", + path + ); + } } - // The completion items we provide depend upon where the user's cursor is positioned. - let cursor = - buffer.and_then(|buf| get_cursor_token(buf, ¶meters.text_document_position.position)); - let mut items = vec![]; - match cursor { - Some(Tok::Colon) => { - items.extend_from_slice(&primitive_types()); - } - Some(Tok::Period) | Some(Tok::ColonColon) => { - // `.` or `::` must be followed by identifiers, which are added to the completion items - // below. - } - _ => { - // If the user's cursor is positioned anywhere other than following a `.`, `:`, or `::`, - // offer them Move's keywords, operators, and builtins as completion items. - items.extend_from_slice(&keywords()); - items.extend_from_slice(&builtins()); + if !buffer.is_empty() { + let cursor = get_cursor_token(buffer.as_str(), ¶meters.text_document_position.position); + match cursor { + Some(Tok::Colon) => { + items.extend_from_slice(&primitive_types()); + } + Some(Tok::Period) | Some(Tok::ColonColon) => { + // `.` or `::` must be followed by identifiers, which are added to the completion items + // below. + } + _ => { + // If the user's cursor is positioned anywhere other than following a `.`, `:`, or `::`, + // offer them Move's keywords, operators, and builtins as completion items. + items.extend_from_slice(&keywords()); + items.extend_from_slice(&builtins()); + } } - } - - if let Some(buffer) = &buffer { - let identifiers = identifiers(buffer, symbols, &path); + let identifiers = identifiers(buffer.as_str(), symbols, &path); items.extend_from_slice(&identifiers); + } else { + // no file content + items.extend_from_slice(&keywords()); + items.extend_from_slice(&builtins()); } let result = serde_json::to_value(items).expect("could not serialize completion response"); diff --git a/external-crates/move/crates/move-analyzer/src/context.rs b/external-crates/move/crates/move-analyzer/src/context.rs index 36b3eaf0abe8a..edd17efb21f7d 100644 --- a/external-crates/move/crates/move-analyzer/src/context.rs +++ b/external-crates/move/crates/move-analyzer/src/context.rs @@ -2,7 +2,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{symbols::Symbols, vfs::VirtualFileSystem}; +use crate::symbols::Symbols; use lsp_server::Connection; use std::sync::{Arc, Mutex}; @@ -10,8 +10,6 @@ use std::sync::{Arc, Mutex}; pub struct Context { /// The connection with the language server's client. pub connection: Connection, - /// The files that the language server is providing information about. - pub files: VirtualFileSystem, /// Symbolication information pub symbols: Arc>, } diff --git a/external-crates/move/crates/move-analyzer/src/symbols.rs b/external-crates/move/crates/move-analyzer/src/symbols.rs index baa580a3b5529..3ffa20db9d1ce 100644 --- a/external-crates/move/crates/move-analyzer/src/symbols.rs +++ b/external-crates/move/crates/move-analyzer/src/symbols.rs @@ -81,6 +81,12 @@ use std::{ }; use tempfile::tempdir; use url::Url; +#[cfg(test)] +use vfs::impls::memory::MemoryFS; +use vfs::{ + impls::{overlay::OverlayFS, physical::PhysicalFS}, + VfsPath, +}; use move_command_line_common::files::FileHash; use move_compiler::{ @@ -96,7 +102,10 @@ use move_compiler::{ PASS_PARSER, PASS_TYPING, }; use move_ir_types::location::*; -use move_package::compilation::build_plan::BuildPlan; +use move_package::{ + compilation::build_plan::BuildPlan, resolution::resolution_graph::ResolvedGraph, + source_package::parsed_manifest::FileName, +}; use move_symbol_pool::Symbol; /// Enabling/disabling the language server reporting readiness to support go-to-def and @@ -652,6 +661,7 @@ impl SymbolicatorRunner { /// Create a new runner pub fn new( + ide_files_root: VfsPath, symbols: Arc>, sender: Sender>>>, lint: bool, @@ -711,7 +721,8 @@ impl SymbolicatorRunner { continue; } eprintln!("symbolication started"); - match get_symbols(root_dir.unwrap().as_path(), lint) { + match get_symbols(ide_files_root.clone(), root_dir.unwrap().as_path(), lint) + { Ok((symbols_opt, lsp_diagnostics)) => { eprintln!("symbolication finished"); if let Some(new_symbols) = symbols_opt { @@ -949,6 +960,7 @@ impl Symbols { /// actually (re)computed and the diagnostics are returned, the old symbolic information should /// be retained even if it's getting out-of-date. pub fn get_symbols( + ide_files_root: VfsPath, pkg_path: &Path, lint: bool, ) -> Result<(Option, BTreeMap>)> { @@ -966,20 +978,23 @@ pub fn get_symbols( // vector as the writer let resolution_graph = build_config.resolution_graph_for_package(pkg_path, &mut Vec::new())?; + let physical_root = VfsPath::new(PhysicalFS::new("/")); + let overlay_fs_root = VfsPath::new(OverlayFS::new(&[ + ide_files_root.clone(), + physical_root.clone(), + ])); + // get source files to be able to correlate positions (in terms of byte offsets) with actual // file locations (in terms of line/column numbers) - let source_files = &resolution_graph.file_sources(); + let source_files = file_sources(&resolution_graph, overlay_fs_root.clone()); let mut files = SimpleFiles::new(); let mut file_id_mapping = HashMap::new(); let mut file_id_to_lines = HashMap::new(); let mut file_name_mapping = BTreeMap::new(); - for (fhash, (fname, source)) in source_files { + for (fhash, (fname, source)) in &source_files { let id = files.add(*fname, source.clone()); file_id_mapping.insert(*fhash, id); - file_name_mapping.insert( - *fhash, - dunce::canonicalize(fname.as_str()).unwrap_or_else(|_| PathBuf::from(fname.as_str())), - ); + file_name_mapping.insert(*fhash, PathBuf::from(fname.as_str())); let lines: Vec = source.lines().map(String::from).collect(); file_id_to_lines.insert(id, lines); } @@ -988,7 +1003,8 @@ pub fn get_symbols( let mut parsed_ast = None; let mut typed_ast = None; let mut diagnostics = None; - build_plan.compile_with_driver(&mut std::io::sink(), |compiler| { + build_plan.compile_with_driver(&mut std::io::sink(), |mut compiler| { + compiler = compiler.set_vfs_root(overlay_fs_root.clone()); // extract expansion AST let (files, compilation_result) = compiler.run::()?; let (_, compiler) = match compilation_result { @@ -1087,7 +1103,7 @@ pub fn get_symbols( let cloned_defs = defs.clone(); let path = file_name_mapping.get(&cloned_defs.fhash.clone()).unwrap(); file_mods - .entry(dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())) + .entry(path.to_path_buf()) .or_insert_with(BTreeSet::new) .insert(cloned_defs); @@ -1162,6 +1178,48 @@ pub fn get_symbols( Ok((Some(symbols), ide_diagnostics)) } +fn file_sources( + resolved_graph: &ResolvedGraph, + overlay_fs: VfsPath, +) -> BTreeMap { + resolved_graph + .package_table + .iter() + .flat_map(|(_, rpkg)| { + rpkg.get_sources(&resolved_graph.build_options) + .unwrap() + .iter() + .map(|f| { + // dunce does a better job of canonicalization on Windows + let fname = dunce::canonicalize(f.as_str()) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|_| f.to_string()); + let mut contents = String::new(); + // there is a fair number of unwraps here but if we can't read the files + // that by all accounts should be in the file system, then there is not much + // we can do so it's better to fail so that we can investigate + let mut vfs_file = overlay_fs + .join(fname.as_str()) + .unwrap() + .open_file() + .unwrap(); + let _ = vfs_file.read_to_string(&mut contents); + let fhash = FileHash::new(&contents); + // write to top layer of the overlay file system so that the content + // is immutable for the duration of compliation and symbolication + let mut vfs_file = overlay_fs + .join(fname.as_str()) + .unwrap() + .create_file() + .unwrap(); + let _ = vfs_file.write_all(contents.as_bytes()); + (fhash, (Symbol::from(fname), contents)) + }) + .collect::>() + }) + .collect() +} + /// Produces module ident string of the form pkg_name::module_name to be used as a map key. /// It's important that these are consistent between parsing AST and typed AST, fn parsing_mod_ident_to_map_key(mod_ident: &P::ModuleIdent_) -> String { @@ -3468,7 +3526,8 @@ fn docstring_test() { path.push("tests/symbols"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -3738,7 +3797,8 @@ fn symbols_test() { path.push("tests/symbols"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -4778,7 +4838,8 @@ fn const_test() { path.push("tests/symbols"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5017,7 +5078,8 @@ fn imports_test() { path.push("tests/symbols"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5218,7 +5280,8 @@ fn module_access_test() { path.push("tests/symbols"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5372,7 +5435,8 @@ fn parse_error_test() { path.push("tests/parse-error"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5456,7 +5520,8 @@ fn parse_error_with_deps_test() { path.push("tests/parse-error-dep"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5504,7 +5569,8 @@ fn pretype_error_test() { path.push("tests/pre-type-error"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5538,7 +5604,8 @@ fn pretype_error_with_deps_test() { path.push("tests/pre-type-error-dep"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5635,7 +5702,8 @@ fn dot_call_test() { path.push("tests/move-2024"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); @@ -5964,7 +6032,8 @@ fn mod_ident_uniform_test() { path.push("tests/mod-ident-uniform"); - let (symbols_opt, _) = get_symbols(path.as_path(), false).unwrap(); + let ide_files_layer: VfsPath = MemoryFS::new().into(); + let (symbols_opt, _) = get_symbols(ide_files_layer, path.as_path(), false).unwrap(); let symbols = symbols_opt.unwrap(); let mut fpath = path.clone(); diff --git a/external-crates/move/crates/move-analyzer/src/vfs.rs b/external-crates/move/crates/move-analyzer/src/vfs.rs index 07733757290e2..23e6c9e21631b 100644 --- a/external-crates/move/crates/move-analyzer/src/vfs.rs +++ b/external-crates/move/crates/move-analyzer/src/vfs.rs @@ -16,7 +16,8 @@ use lsp_types::{ notification::Notification as _, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, }; -use std::path::PathBuf; +use std::{io::Write, path::PathBuf}; +use vfs::VfsPath; /// A mapping from identifiers (file names, potentially, but not necessarily) to their contents. #[derive(Debug, Default)] @@ -49,46 +50,141 @@ impl VirtualFileSystem { /// Updates the given virtual file system based on the text document sync notification that was sent. pub fn on_text_document_sync_notification( - files: &mut VirtualFileSystem, + ide_files_root: VfsPath, symbolicator_runner: &symbols::SymbolicatorRunner, notification: &Notification, ) { + fn vfs_file_create( + ide_files: &VfsPath, + file_path: PathBuf, + first_access: bool, + ) -> Option> { + let Some(vfs_path) = ide_files.join(file_path.to_string_lossy()).ok() else { + eprintln!( + "Could not construct file path for file creation at {:?}", + file_path + ); + return None; + }; + if first_access { + // create all directories on first access, otherwise file creation will fail + let _ = vfs_path.parent().create_dir(); + } + let Some(vfs_file) = vfs_path.create_file().ok() else { + eprintln!("Could not create file at {:?}", vfs_path); + return None; + }; + Some(vfs_file) + } + + fn vfs_file_remove(ide_files: &VfsPath, file_path: PathBuf) { + let Some(vfs_path) = ide_files.join(file_path.to_string_lossy()).ok() else { + eprintln!( + "Could not construct file path for file removal at {:?}", + file_path + ); + return; + }; + if vfs_path.remove_file().is_err() { + eprintln!("Could not remove file at {:?}", vfs_path); + }; + } + eprintln!("text document notification"); match notification.method.as_str() { lsp_types::notification::DidOpenTextDocument::METHOD => { let parameters = serde_json::from_value::(notification.params.clone()) .expect("could not deserialize notification"); - files.update( - parameters.text_document.uri.to_file_path().unwrap(), - ¶meters.text_document.text, - ); - symbolicator_runner.run(parameters.text_document.uri.to_file_path().unwrap()); + let Some(file_path) = parameters.text_document.uri.to_file_path().ok() else { + eprintln!( + "Could not create file path from URI {:?}", + parameters.text_document.uri + ); + return; + }; + let Some(mut vfs_file) = vfs_file_create( + &ide_files_root, + file_path.clone(), + /* first_access */ true, + ) else { + return; + }; + if vfs_file + .write_all(parameters.text_document.text.as_bytes()) + .is_ok() + { + symbolicator_runner.run(file_path); + } } lsp_types::notification::DidChangeTextDocument::METHOD => { let parameters = serde_json::from_value::(notification.params.clone()) .expect("could not deserialize notification"); - files.update( - parameters.text_document.uri.to_file_path().unwrap(), - ¶meters.content_changes.last().unwrap().text, - ); + + let Some(file_path) = parameters.text_document.uri.to_file_path().ok() else { + eprintln!( + "Could not create file path from URI {:?}", + parameters.text_document.uri + ); + return; + }; + let Some(mut vfs_file) = vfs_file_create( + &ide_files_root, + file_path.clone(), + /* first_access */ false, + ) else { + return; + }; + let Some(changes) = parameters.content_changes.last() else { + eprintln!("Could not read last opened file change"); + return; + }; + if vfs_file.write_all(changes.text.as_bytes()).is_ok() { + symbolicator_runner.run(file_path); + } } lsp_types::notification::DidSaveTextDocument::METHOD => { let parameters = serde_json::from_value::(notification.params.clone()) .expect("could not deserialize notification"); - files.update( - parameters.text_document.uri.to_file_path().unwrap(), - ¶meters.text.unwrap(), - ); - symbolicator_runner.run(parameters.text_document.uri.to_file_path().unwrap()); + let Some(file_path) = parameters.text_document.uri.to_file_path().ok() else { + eprintln!( + "Could not create file path from URI {:?}", + parameters.text_document.uri + ); + return; + }; + let Some(mut vfs_file) = vfs_file_create( + &ide_files_root, + file_path.clone(), + /* first_access */ false, + ) else { + return; + }; + let Some(content) = parameters.text else { + eprintln!("Could not read saved file change"); + return; + }; + if vfs_file.write_all(content.as_bytes()).is_err() { + // try to remove file from the file system and schedule symbolicator to pick up + // changes from the file system + vfs_file_remove(&ide_files_root, file_path.clone()); + symbolicator_runner.run(file_path); + } } lsp_types::notification::DidCloseTextDocument::METHOD => { let parameters = serde_json::from_value::(notification.params.clone()) .expect("could not deserialize notification"); - files.remove(¶meters.text_document.uri.to_file_path().unwrap()); + let Some(file_path) = parameters.text_document.uri.to_file_path().ok() else { + eprintln!( + "Could not create file path from URI {:?}", + parameters.text_document.uri + ); + return; + }; + vfs_file_remove(&ide_files_root, file_path.clone()); } _ => eprintln!("invalid notification '{}'", notification.method), } diff --git a/external-crates/move/crates/move-cli/tests/build_tests/build_with_dep_warnings/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/build_with_dep_warnings/args.exp index 2d39bf3c4672d..9d2992249b6aa 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/build_with_dep_warnings/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/build_with_dep_warnings/args.exp @@ -15,7 +15,7 @@ B0: Command `build -p dep`: BUILDING SomeDep warning[W09002]: unused variable - ┌─ ./sources/has_warning.move:2:20 + ┌─ sources/has_warning.move:2:20 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' diff --git a/external-crates/move/crates/move-cli/tests/build_tests/build_with_warnings/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/build_with_warnings/args.exp index 5a76cc2f31892..b92cd868e3c14 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/build_with_warnings/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/build_with_warnings/args.exp @@ -1,7 +1,7 @@ Command `build`: BUILDING Test warning[W09002]: unused variable - ┌─ ./sources/m.move:2:16 + ┌─ sources/m.move:2:16 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' @@ -20,7 +20,7 @@ B0: } } warning[W09002]: unused variable - ┌─ ./sources/m.move:2:16 + ┌─ sources/m.move:2:16 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' @@ -34,7 +34,7 @@ BUILDING Test Command `build --warnings-are-errors`: BUILDING Test error[E09002]: unused variable - ┌─ ./sources/m.move:2:16 + ┌─ sources/m.move:2:16 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' diff --git a/external-crates/move/crates/move-cli/tests/build_tests/include_exclude_stdlib/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/include_exclude_stdlib/args.exp index 5620f9e5d8cf6..94c5717481f44 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/include_exclude_stdlib/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/include_exclude_stdlib/args.exp @@ -1,13 +1,13 @@ Command `build -v`: BUILDING build_include_exclude_stdlib error[E03002]: unbound module - ┌─ ./sources/UseSigner.move:2:7 + ┌─ sources/UseSigner.move:2:7 │ 2 │ use std::signer; │ ^^^^^^^^^^^ Invalid 'use'. Unbound module: 'std::signer' warning[W09002]: unused variable - ┌─ ./sources/UseSigner.move:4:16 + ┌─ sources/UseSigner.move:4:16 │ 4 │ public fun f(account: &signer): address { │ ^^^^^^^ Unused parameter 'account'. Consider removing or prefixing with an underscore: '_account' @@ -15,7 +15,7 @@ warning[W09002]: unused variable = This warning can be suppressed with '#[allow(unused_variable)]' applied to the 'module' or module member ('const', 'fun', or 'struct') error[E03002]: unbound module - ┌─ ./sources/UseSigner.move:5:5 + ┌─ sources/UseSigner.move:5:5 │ 5 │ signer::address_of(account) │ ^^^^^^ Unbound module alias 'signer' diff --git a/external-crates/move/crates/move-cli/tests/build_tests/migration/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/migration/args.exp index e1cac37867c4b..68a31b88a4c4d 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/migration/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/migration/args.exp @@ -15,8 +15,8 @@ BUILDING A The following changes will be made. ============================================================ ---- ./sources/mod0.move -+++ ./sources/mod0.move +--- sources/mod0.move ++++ sources/mod0.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } @@ -32,8 +32,8 @@ The following changes will be made. @@ -12,1 +12,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod1.move -+++ ./sources/mod1.move +--- sources/mod1.move ++++ sources/mod1.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -43,8 +43,8 @@ The following changes will be made. @@ -5,1 +5,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod2.move -+++ ./sources/mod2.move +--- sources/mod2.move ++++ sources/mod2.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -57,8 +57,8 @@ The following changes will be made. @@ -15,1 +15,1 @@ - let x = 5; let y = 10; + let mut x = 5; let mut y = 10; ---- ./sources/mod3_4.move -+++ ./sources/mod3_4.move +--- sources/mod3_4.move ++++ sources/mod3_4.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } @@ -83,8 +83,8 @@ The following changes will be made. @@ -24,1 +24,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod5.move -+++ ./sources/mod5.move +--- sources/mod5.move ++++ sources/mod5.move @@ -3,1 +3,1 @@ - struct UID { } + public struct UID { } @@ -97,8 +97,8 @@ The following changes will be made. @@ -15,1 +15,1 @@ - let i = 0; + let mut i = 0; ---- ./tests/test0.move -+++ ./tests/test0.move +--- tests/test0.move ++++ tests/test0.move @@ -5,1 +5,1 @@ - struct R has store { } + public struct R has store { } @@ -130,12 +130,12 @@ The following changes will be made. ============================================================ Apply changes? (Y/n) -Updating "./sources/mod0.move" . . . -Updating "./sources/mod1.move" . . . -Updating "./sources/mod2.move" . . . -Updating "./sources/mod3_4.move" . . . -Updating "./sources/mod5.move" . . . -Updating "./tests/test0.move" . . . +Updating "sources/mod0.move" . . . +Updating "sources/mod1.move" . . . +Updating "sources/mod2.move" . . . +Updating "sources/mod3_4.move" . . . +Updating "sources/mod5.move" . . . +Updating "tests/test0.move" . . . Changes complete Wrote patchfile out to: ./migration.patch diff --git a/external-crates/move/crates/move-cli/tests/build_tests/migration/expected_migration.patch b/external-crates/move/crates/move-cli/tests/build_tests/migration/expected_migration.patch index 6ff471d4a5034..d012eec25da8d 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/migration/expected_migration.patch +++ b/external-crates/move/crates/move-cli/tests/build_tests/migration/expected_migration.patch @@ -1,5 +1,5 @@ ---- ./sources/mod0.move -+++ ./sources/mod0.move +--- sources/mod0.move ++++ sources/mod0.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } @@ -15,8 +15,8 @@ @@ -12,1 +12,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod1.move -+++ ./sources/mod1.move +--- sources/mod1.move ++++ sources/mod1.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -26,8 +26,8 @@ @@ -5,1 +5,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod2.move -+++ ./sources/mod2.move +--- sources/mod2.move ++++ sources/mod2.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -40,8 +40,8 @@ @@ -15,1 +15,1 @@ - let x = 5; let y = 10; + let mut x = 5; let mut y = 10; ---- ./sources/mod3_4.move -+++ ./sources/mod3_4.move +--- sources/mod3_4.move ++++ sources/mod3_4.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } @@ -66,8 +66,8 @@ @@ -24,1 +24,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod5.move -+++ ./sources/mod5.move +--- sources/mod5.move ++++ sources/mod5.move @@ -3,1 +3,1 @@ - struct UID { } + public struct UID { } @@ -80,8 +80,8 @@ @@ -15,1 +15,1 @@ - let i = 0; + let mut i = 0; ---- ./tests/test0.move -+++ ./tests/test0.move +--- tests/test0.move ++++ tests/test0.move @@ -5,1 +5,1 @@ - struct R has store { } + public struct R has store { } diff --git a/external-crates/move/crates/move-cli/tests/build_tests/migration/migration.patch b/external-crates/move/crates/move-cli/tests/build_tests/migration/migration.patch index 1479e65951bca..8cfd330dff5b9 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/migration/migration.patch +++ b/external-crates/move/crates/move-cli/tests/build_tests/migration/migration.patch @@ -1,5 +1,5 @@ ---- ./sources/mod0.move -+++ ./sources/mod0.move +--- sources/mod0.move ++++ sources/mod0.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } @@ -15,8 +15,8 @@ @@ -12,1 +12,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod1.move -+++ ./sources/mod1.move +--- sources/mod1.move ++++ sources/mod1.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -26,8 +26,8 @@ @@ -5,1 +5,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod2.move -+++ ./sources/mod2.move +--- sources/mod2.move ++++ sources/mod2.move @@ -3,1 +3,1 @@ - public fun t(x: u64, yip: u64, s: S): u64 { + public fun t(mut x: u64, mut yip: u64, s: S): u64 { @@ -37,8 +37,8 @@ @@ -5,1 +5,1 @@ - let S { f: fin } = s; + let S { f: mut fin } = s; ---- ./sources/mod3_4.move -+++ ./sources/mod3_4.move +--- sources/mod3_4.move ++++ sources/mod3_4.move @@ -2,1 +2,1 @@ - struct S { f: u64 } + public struct S { f: u64 } diff --git a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_addresses/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_addresses/args.exp index 803ff6baf0db9..b691721f8b230 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_addresses/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_addresses/args.exp @@ -1,12 +1,12 @@ Command `build -v`: BUILDING Defn error[E04001]: restricted visibility - ┌─ ./sources/B.move:3:31 + ┌─ sources/B.move:3:31 │ 3 │ public fun usage(): u64 { defn::definition() } │ ^^^^^^^^^^^^^^^^^^ Invalid call to 'public(package)' visible function 'A::defn::definition' │ - ┌─ ./sources/A.move:2:5 + ┌─ sources/A.move:2:5 │ 2 │ public(package) fun definition(): u64 { 0 } │ --------------- A 'public(package)' function can only be called from the same address and package as module 'A::defn' in package 'Defn'. This call is from address 'B' in package 'Defn' diff --git a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_both/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_both/args.exp index 0836ea9e9eff9..ac4370df95366 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_both/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_both/args.exp @@ -2,12 +2,12 @@ Command `build -v`: INCLUDING DEPENDENCY Defn BUILDING Usage error[E04001]: restricted visibility - ┌─ ./sources/B.move:3:31 + ┌─ sources/B.move:3:31 │ 3 │ public fun usage(): u64 { defn::definition() } │ ^^^^^^^^^^^^^^^^^^ Invalid call to 'public(package)' visible function 'A::defn::definition' │ - ┌─ ./defn/sources/A.move:2:5 + ┌─ defn/sources/A.move:2:5 │ 2 │ public(package) fun definition(): u64 { 0 } │ --------------- A 'public(package)' function can only be called from the same address and package as module 'A::defn' in package 'Defn'. This call is from address 'B' in package 'Usage' diff --git a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_packages/args.exp b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_packages/args.exp index 99527ba85b421..aa25a35f5c16e 100644 --- a/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_packages/args.exp +++ b/external-crates/move/crates/move-cli/tests/build_tests/public_package_different_packages/args.exp @@ -2,12 +2,12 @@ Command `build -v`: INCLUDING DEPENDENCY Defn BUILDING Usage error[E04001]: restricted visibility - ┌─ ./sources/A.move:3:31 + ┌─ sources/A.move:3:31 │ 3 │ public fun usage(): u64 { defn::definition() } │ ^^^^^^^^^^^^^^^^^^ Invalid call to 'public(package)' visible function 'A::defn::definition' │ - ┌─ ./defn/sources/A.move:2:5 + ┌─ defn/sources/A.move:2:5 │ 2 │ public(package) fun definition(): u64 { 0 } │ --------------- A 'public(package)' function can only be called from the same address and package as module 'A::defn' in package 'Defn'. This call is from address 'A' in package 'Usage' diff --git a/external-crates/move/crates/move-cli/tests/move_unit_tests/test_with_warnings/args.exp b/external-crates/move/crates/move-cli/tests/move_unit_tests/test_with_warnings/args.exp index ca36c02d3d3cd..a778288d852c8 100644 --- a/external-crates/move/crates/move-cli/tests/move_unit_tests/test_with_warnings/args.exp +++ b/external-crates/move/crates/move-cli/tests/move_unit_tests/test_with_warnings/args.exp @@ -6,7 +6,7 @@ Running Move unit tests [ PASS ] 0x42::m::nop Test result: OK. Total tests: 2; passed: 2; failed: 0 warning[W09002]: unused variable - ┌─ ./sources/m.move:2:16 + ┌─ sources/m.move:2:16 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' @@ -31,7 +31,7 @@ Command `test --warnings-are-errors`: INCLUDING DEPENDENCY MoveStdlib BUILDING Test error[E09002]: unused variable - ┌─ ./sources/m.move:2:16 + ┌─ sources/m.move:2:16 │ 2 │ public fun foo(x: u64): u64 { │ ^ Unused parameter 'x'. Consider removing or prefixing with an underscore: '_x' diff --git a/external-crates/move/crates/move-cli/tests/sandbox_tests/named_address_conflicts_in_error/args.exp b/external-crates/move/crates/move-cli/tests/sandbox_tests/named_address_conflicts_in_error/args.exp index 60515cf6ea77d..2928a1124066e 100644 --- a/external-crates/move/crates/move-cli/tests/sandbox_tests/named_address_conflicts_in_error/args.exp +++ b/external-crates/move/crates/move-cli/tests/sandbox_tests/named_address_conflicts_in_error/args.exp @@ -2,14 +2,14 @@ Command `build`: INCLUDING DEPENDENCY Dep BUILDING use_named_address error[E04007]: incompatible types - ┌─ ./sources/example.move:3:9 + ┌─ sources/example.move:3:9 │ 2 │ public fun next(): b::m::Y { │ ------- Expected: '(b=0x41)::m::Y' 3 │ b::m::x() │ ^^^^^^^^^ Invalid return expression │ - ┌─ ./dep/sources/m.move:4:21 + ┌─ dep/sources/m.move:4:21 │ 4 │ public fun x(): X { │ - Given: '(a=0x41)::m::X' diff --git a/external-crates/move/crates/move-cli/tests/sandbox_tests/use_named_address/args.exp b/external-crates/move/crates/move-cli/tests/sandbox_tests/use_named_address/args.exp index 0b5689a09564b..aa0bd1399f7d6 100644 --- a/external-crates/move/crates/move-cli/tests/sandbox_tests/use_named_address/args.exp +++ b/external-crates/move/crates/move-cli/tests/sandbox_tests/use_named_address/args.exp @@ -1,11 +1,11 @@ Command `sandbox publish`: error[E02001]: duplicate declaration, item, or annotation - ┌─ ./sources/M_no_named.move:1:14 + ┌─ sources/M_no_named.move:1:14 │ 1 │ module 0x42::M { │ ^ Duplicate definition for module '0x42::M' │ - ┌─ ./sources/M.move:1:11 + ┌─ sources/M.move:1:11 │ 1 │ module A::M { │ - Module previously defined here, with 'A::M' diff --git a/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_multi_module_publish/args.exp b/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_multi_module_publish/args.exp index 97bc3e8beed02..57866b117f21d 100644 --- a/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_multi_module_publish/args.exp +++ b/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_multi_module_publish/args.exp @@ -1,7 +1,7 @@ Command `sandbox publish --bundle --override-ordering M --override-ordering N`: Invalid multi-module publishing: VMError with status MISSING_DEPENDENCY at location Module ModuleId { address: 0000000000000000000000000000000000000000000000000000000000000042, name: Identifier("M") } at index 0 for function handle error[E02007]: invalid 'fun' declaration - ┌─ ./sources/example.move:3:16 + ┌─ sources/example.move:3:16 │ 3 │ native fun create_signer(addr: address): signer; │ ^^^^^^^^^^^^^ Missing implementation for the native function M::create_signer diff --git a/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_publish/args.exp b/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_publish/args.exp index 30ab2c9e38cf6..8dfbeb744f6d6 100644 --- a/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_publish/args.exp +++ b/external-crates/move/crates/move-cli/tests/sandbox_tests/verify_native_functions_in_publish/args.exp @@ -1,6 +1,6 @@ Command `sandbox publish`: error[E02007]: invalid 'fun' declaration - ┌─ ./sources/example.move:3:16 + ┌─ sources/example.move:3:16 │ 3 │ native fun create_signer(addr: address): signer; │ ^^^^^^^^^^^^^ Missing implementation for the native function M::create_signer diff --git a/external-crates/move/crates/move-command-line-common/Cargo.toml b/external-crates/move/crates/move-command-line-common/Cargo.toml index 0ae5d0ba0bef4..1427e00c99e51 100644 --- a/external-crates/move/crates/move-command-line-common/Cargo.toml +++ b/external-crates/move/crates/move-command-line-common/Cargo.toml @@ -19,5 +19,6 @@ num-bigint.workspace = true once_cell.workspace = true serde.workspace = true dirs-next.workspace = true +vfs.workspace = true move-core-types.workspace = true diff --git a/external-crates/move/crates/move-command-line-common/src/files.rs b/external-crates/move/crates/move-command-line-common/src/files.rs index 957cb23f1770a..48f629e144982 100644 --- a/external-crates/move/crates/move-command-line-common/src/files.rs +++ b/external-crates/move/crates/move-command-line-common/src/files.rs @@ -6,6 +6,7 @@ use anyhow::{anyhow, bail, *}; use serde::{Deserialize, Serialize}; use sha2::Digest; use std::{collections::BTreeMap, path::Path}; +use vfs::{error::VfsErrorKind, VfsPath, VfsResult}; /// Result of sha256 hash of a file's contents. #[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] @@ -176,3 +177,86 @@ pub fn verify_and_create_named_address_mapping Ok(mapping) } + +//************************************************************************************************** +// Virtual file system support +//************************************************************************************************** + +/// Determine if the virtual path at `vfs_path` exists distinguishing between whether the path did +/// not exist, or if there were other errors in determining if the path existed. +/// It implements the same functionality as try_exists above but for the virtual file system +pub fn try_exists_vfs(vfs_path: &VfsPath) -> VfsResult { + use VfsResult as R; + match vfs_path.metadata() { + R::Ok(_) => R::Ok(true), + R::Err(e) if matches!(e.kind(), &VfsErrorKind::FileNotFound) => R::Ok(false), + R::Err(e) => R::Err(e), + } +} + +/// - For each directory in `paths`, it will return all files that satisfy the predicate +/// - Any file explicitly passed in `paths`, it will include that file in the result, regardless +/// of the file extension +/// It implements the same functionality as find_filenames above but for the virtual file system +pub fn find_filenames_vfs bool>( + paths: &[VfsPath], + mut is_file_desired: Predicate, +) -> anyhow::Result> { + let mut result = vec![]; + + for p in paths { + if !try_exists_vfs(p)? { + anyhow::bail!("No such file or directory '{}'", p.as_str()) + } + if p.is_file()? && is_file_desired(p) { + result.push(p.clone()); + continue; + } + if !p.is_dir()? { + continue; + } + for entry in p.walk_dir()?.filter_map(|e| e.ok()) { + if !entry.is_file()? || !is_file_desired(&entry) { + continue; + } + + result.push(entry); + } + } + Ok(result) +} + +/// - For each directory in `paths`, it will return all files with the `MOVE_EXTENSION` found +/// recursively in that directory +/// - If `keep_specified_files` any file explicitly passed in `paths`, will be added to the result +/// Otherwise, they will be discarded +/// It implements the same functionality as find_move_filenames above but for the virtual file +/// system +pub fn find_move_filenames_vfs( + paths: &[VfsPath], + keep_specified_files: bool, +) -> anyhow::Result> { + if keep_specified_files { + let mut file_paths = vec![]; + let mut other_paths = vec![]; + for p in paths { + if p.is_file()? { + file_paths.push(p.clone()); + } else { + other_paths.push(p.clone()); + } + } + file_paths.extend(find_filenames_vfs(&other_paths, |path| { + path.extension() + .map(|e| e.as_str() == MOVE_EXTENSION) + .unwrap_or(false) + })?); + Ok(file_paths) + } else { + find_filenames_vfs(paths, |path| { + path.extension() + .map(|e| e.as_str() == MOVE_EXTENSION) + .unwrap_or(false) + }) + } +} diff --git a/external-crates/move/crates/move-compiler/Cargo.toml b/external-crates/move/crates/move-compiler/Cargo.toml index dad2f235cd606..aec6534aacd75 100644 --- a/external-crates/move/crates/move-compiler/Cargo.toml +++ b/external-crates/move/crates/move-compiler/Cargo.toml @@ -10,14 +10,17 @@ license = "Apache-2.0" [dependencies] anyhow.workspace = true codespan-reporting.workspace = true +dunce.workspace = true hex.workspace = true regex.workspace = true clap.workspace = true petgraph.workspace = true tempfile.workspace = true once_cell.workspace = true +pathdiff.workspace = true serde.workspace = true stacker.workspace = true +vfs.workspace = true bcs.workspace = true diff --git a/external-crates/move/crates/move-compiler/src/command_line/compiler.rs b/external-crates/move/crates/move-compiler/src/command_line/compiler.rs index 9f5356a07ed23..df20de5fb42cb 100644 --- a/external-crates/move/crates/move-compiler/src/command_line/compiler.rs +++ b/external-crates/move/crates/move-compiler/src/command_line/compiler.rs @@ -15,26 +15,30 @@ use crate::{ expansion, hlir, interface_generator, naming, parser, parser::{comments::*, *}, shared::{ - CompilationEnv, Flags, IndexedPackagePath, NamedAddressMap, NamedAddressMaps, - NumericalAddress, PackageConfig, PackagePaths, + CompilationEnv, Flags, IndexedPhysicalPackagePath, IndexedVfsPackagePath, NamedAddressMap, + NamedAddressMaps, NumericalAddress, PackageConfig, PackagePaths, }, to_bytecode, typing::{self, visitor::TypingVisitorObj}, unit_test, }; use move_command_line_common::files::{ - extension_equals, find_filenames, MOVE_COMPILED_EXTENSION, MOVE_EXTENSION, SOURCE_MAP_EXTENSION, + find_filenames_vfs, MOVE_COMPILED_EXTENSION, MOVE_EXTENSION, SOURCE_MAP_EXTENSION, }; use move_core_types::language_storage::ModuleId as CompiledModuleId; use move_symbol_pool::Symbol; +use pathdiff::diff_paths; use std::{ collections::BTreeMap, fs, - fs::File, io::{Read, Write}, - path::{Path, PathBuf}, + path::PathBuf, +}; +use vfs::{ + impls::{memory::MemoryFS, physical::PhysicalFS}, + path::VfsFileType, + VfsPath, }; -use tempfile::NamedTempFile; //************************************************************************************************** // Definitions @@ -42,8 +46,8 @@ use tempfile::NamedTempFile; pub struct Compiler<'a> { maps: NamedAddressMaps, - targets: Vec, - deps: Vec, + targets: Vec, + deps: Vec, interface_files_dir_opt: Option, pre_compiled_lib: Option<&'a FullyCompiledProgram>, compiled_module_named_address_mapping: BTreeMap, @@ -54,6 +58,8 @@ pub struct Compiler<'a> { known_warning_filters: Vec<(/* Prefix */ Option, Vec)>, package_configs: BTreeMap, default_config: Option, + /// Root path of the virtual file system. + vfs_root: Option, } pub struct SteppedCompiler<'a, const P: Pass> { @@ -114,7 +120,7 @@ impl<'a> Compiler<'a> { maps: &mut NamedAddressMaps, package_configs: &mut BTreeMap, all_pkgs: Vec, impl Into>>, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let mut idx_paths = vec![]; for PackagePaths { name, @@ -135,7 +141,7 @@ impl<'a> Compiler<'a> { .map(|(k, v)| (k.into(), v)) .collect::(), ); - idx_paths.extend(paths.into_iter().map(|path| IndexedPackagePath { + idx_paths.extend(paths.into_iter().map(|path| IndexedPhysicalPackagePath { package: name, path: path.into(), named_address_map: idx, @@ -161,6 +167,7 @@ impl<'a> Compiler<'a> { known_warning_filters: vec![], package_configs, default_config: None, + vfs_root: None, }) } @@ -259,16 +266,40 @@ impl<'a> Compiler<'a> { self } + pub fn set_vfs_root(mut self, vfs_root: VfsPath) -> Self { + assert!(self.vfs_root.is_none()); + self.vfs_root = Some(vfs_root); + self + } + pub fn run( self, ) -> anyhow::Result<( FilesSourceText, Result<(CommentMap, SteppedCompiler<'a, TARGET>), Diagnostics>, )> { + /// Path relativization after parsing is needed as paths are initially canonicalized when + /// converted to virtual file system paths and would show up as absolute in the test output + /// which wouldn't be machine-agnostic. We need to relativize using `vfs_root` beacuse it + /// was also used during canonicalization and might have altered path prefix in a + /// non-standard way (e.g., this can happen on Windows). + fn relativize_path(vsf_root: &VfsPath, path: Symbol) -> Symbol { + let Some(current_dir) = std::env::current_dir().ok() else { + return path; + }; + let Ok(current_dir_vfs) = vsf_root.join(current_dir.to_string_lossy()) else { + return path; + }; + let Some(new_path) = diff_paths(path.to_string(), current_dir_vfs.as_str()) else { + return path; + }; + Symbol::from(new_path.to_string_lossy().to_string()) + } + let Self { maps, targets, - mut deps, + deps, interface_files_dir_opt, pre_compiled_lib, compiled_module_named_address_mapping, @@ -278,7 +309,23 @@ impl<'a> Compiler<'a> { known_warning_filters, package_configs, default_config, + vfs_root, } = self; + let vfs_root = match vfs_root { + Some(p) => p, + None => VfsPath::new(PhysicalFS::new("/")), + }; + + let targets = targets + .into_iter() + .map(|p| Ok(p.to_vfs_path(&vfs_root)?)) + .collect::, anyhow::Error>>()?; + + let mut deps = deps + .into_iter() + .map(|p| Ok(p.to_vfs_path(&vfs_root)?)) + .collect::, anyhow::Error>>()?; + generate_interface_files_for_deps( &mut deps, interface_files_dir_opt, @@ -293,8 +340,12 @@ impl<'a> Compiler<'a> { compilation_env.add_custom_known_filters(prefix, filters)?; } - let (source_text, pprog, comments) = - with_large_stack!(parse_program(&mut compilation_env, maps, targets, deps))?; + let (mut source_text, pprog, comments) = + with_large_stack!(parse_program(&mut compilation_env, maps, targets, deps,))?; + + source_text + .iter_mut() + .for_each(|(_, (path, _))| *path = relativize_path(&vfs_root, *path)); let res: Result<_, Diagnostics> = SteppedCompiler::new_at_parser(compilation_env, pre_compiled_lib, pprog) @@ -671,7 +722,7 @@ pub fn output_compiled_units( } fn generate_interface_files_for_deps( - deps: &mut Vec, + deps: &mut Vec, interface_files_dir_opt: Option, module_to_named_address: &BTreeMap, ) -> anyhow::Result<()> { @@ -684,32 +735,39 @@ fn generate_interface_files_for_deps( } pub fn generate_interface_files( - mv_file_locations: &mut [IndexedPackagePath], + mv_file_locations: &mut [IndexedVfsPackagePath], interface_files_dir_opt: Option, module_to_named_address: &BTreeMap, separate_by_hash: bool, -) -> anyhow::Result> { +) -> anyhow::Result> { let mv_files = { let mut v = vec![]; let (mv_magic_files, other_file_locations): (Vec<_>, Vec<_>) = mv_file_locations.iter().cloned().partition(|s| { - Path::new(s.path.as_str()).is_file() && has_compiled_module_magic_number(&s.path) + let is_file = s + .path + .metadata() + .map(|d| d.file_type == VfsFileType::File) + .unwrap_or(false); + is_file && has_compiled_module_magic_number(&s.path) }); v.extend(mv_magic_files); - for IndexedPackagePath { + for IndexedVfsPackagePath { package, path, named_address_map, } in other_file_locations { v.extend( - find_filenames(&[path.as_str()], |path| { - extension_equals(path, MOVE_COMPILED_EXTENSION) + find_filenames_vfs(&[path], |path| { + path.extension() + .map(|e| e.as_str() == MOVE_COMPILED_EXTENSION) + .unwrap_or(false) })? .into_iter() - .map(|path| IndexedPackagePath { + .map(|path| IndexedVfsPackagePath { package, - path: path.into(), + path, named_address_map, }), ); @@ -733,8 +791,10 @@ pub fn generate_interface_files( let mut hasher = DefaultHasher::new(); mv_files.len().hash(&mut hasher); HASH_DELIM.hash(&mut hasher); - for IndexedPackagePath { path, .. } in &mv_files { - std::fs::read(path.as_str())?.hash(&mut hasher); + for IndexedVfsPackagePath { path, .. } in &mv_files { + let mut buf = vec![]; + path.open_file()?.read_to_end(&mut buf)?; + buf.hash(&mut hasher); HASH_DELIM.hash(&mut hasher); } @@ -745,47 +805,44 @@ pub fn generate_interface_files( interface_sub_dir }; + // interface files for dependencies are generated into a separate in-memory virtual file + // system (`deps_out_vfs`) and subsequently read by the parser (input for interface + // generation is still read from the "regular" virtual file system, that is `vfs`) + let deps_out_vfs = VfsPath::new(MemoryFS::new()); let mut result = vec![]; - for IndexedPackagePath { - path, + for IndexedVfsPackagePath { package, + path, named_address_map, } in mv_files { let (id, interface_contents) = interface_generator::write_file_to_string(module_to_named_address, &path)?; let addr_dir = dir_path!(all_addr_dir.clone(), format!("{}", id.address())); - let file_path = file_path!(addr_dir.clone(), format!("{}", id.name()), MOVE_EXTENSION); - result.push(IndexedPackagePath { - path: Symbol::from(file_path.clone().into_os_string().into_string().unwrap()), + let file_path = Symbol::from( + file_path!(addr_dir.clone(), format!("{}", id.name()), MOVE_EXTENSION) + .to_string_lossy() + .to_string(), + ); + let vfs_path = deps_out_vfs.join(file_path)?; + vfs_path.parent().create_dir_all()?; + vfs_path + .create_file()? + .write_all(interface_contents.as_bytes())?; + + result.push(IndexedVfsPackagePath { package, + path: vfs_path, named_address_map, }); - // it's possible some files exist but not others due to multithreaded environments - if separate_by_hash && Path::new(&file_path).is_file() { - continue; - } - - std::fs::create_dir_all(&addr_dir)?; - - let mut tmp = NamedTempFile::new_in(addr_dir)?; - tmp.write_all(interface_contents.as_bytes())?; - - // it's possible some files exist but not others due to multithreaded environments - // Check for the file existing and then safely move the tmp file there if - // it does not - if separate_by_hash && Path::new(&file_path).is_file() { - continue; - } - std::fs::rename(tmp.path(), file_path)?; } Ok(result) } -fn has_compiled_module_magic_number(path: &str) -> bool { +fn has_compiled_module_magic_number(path: &VfsPath) -> bool { use move_binary_format::file_format_common::BinaryConstants; - let mut file = match File::open(path) { + let mut file = match path.open_file() { Err(_) => return false, Ok(f) => f, }; diff --git a/external-crates/move/crates/move-compiler/src/interface_generator.rs b/external-crates/move/crates/move-compiler/src/interface_generator.rs index a41f146d2ded7..53710836b5567 100644 --- a/external-crates/move/crates/move-compiler/src/interface_generator.rs +++ b/external-crates/move/crates/move-compiler/src/interface_generator.rs @@ -13,7 +13,8 @@ use move_binary_format::{ }, }; use move_core_types::language_storage::ModuleId; -use std::{collections::BTreeMap, fs}; +use std::collections::BTreeMap; +use vfs::VfsPath; pub const NATIVE_INTERFACE: &str = "native_interface"; @@ -34,13 +35,16 @@ macro_rules! push { /// Additionally, it returns the module id (address+name) of the module that was deserialized pub fn write_file_to_string( named_address_mapping: &BTreeMap>, - compiled_module_file_input_path: &str, + compiled_module_file_input_path: &VfsPath, ) -> Result<(ModuleId, String)> { - let file_contents = fs::read(compiled_module_file_input_path)?; + let mut file_contents = vec![]; + compiled_module_file_input_path + .open_file()? + .read_to_end(&mut file_contents)?; let module = CompiledModule::deserialize_with_defaults(&file_contents).map_err(|e| { anyhow!( "Unable to deserialize module at '{}': {}", - compiled_module_file_input_path, + compiled_module_file_input_path.as_str(), e ) })?; diff --git a/external-crates/move/crates/move-compiler/src/parser/mod.rs b/external-crates/move/crates/move-compiler/src/parser/mod.rs index 6afa7b86d3cad..0c6c26edcbba6 100644 --- a/external-crates/move/crates/move-compiler/src/parser/mod.rs +++ b/external-crates/move/crates/move-compiler/src/parser/mod.rs @@ -13,47 +13,46 @@ pub(crate) mod verification_attribute_filter; use crate::{ diagnostics::FilesSourceText, parser::{self, ast::PackageDefinition, syntax::parse_file_string}, - shared::{CompilationEnv, IndexedPackagePath, NamedAddressMaps}, + shared::{CompilationEnv, IndexedVfsPackagePath, NamedAddressMaps}, }; use anyhow::anyhow; use comments::*; -use move_command_line_common::files::{find_move_filenames, FileHash}; +use move_command_line_common::files::{find_move_filenames_vfs, FileHash}; use move_symbol_pool::Symbol; -use std::{ - collections::{BTreeSet, HashMap}, - fs::File, - io::Read, -}; +use std::collections::{BTreeSet, HashMap}; +use vfs::VfsPath; +/// Parses program's targets and dependencies, both of which are read from different virtual file +/// systems (vfs and deps_out_vfs, respectively). pub(crate) fn parse_program( compilation_env: &mut CompilationEnv, named_address_maps: NamedAddressMaps, - targets: Vec, - deps: Vec, + targets: Vec, + deps: Vec, ) -> anyhow::Result<(FilesSourceText, parser::ast::Program, CommentMap)> { fn find_move_filenames_with_address_mapping( - paths_with_mapping: Vec, - ) -> anyhow::Result> { + paths_with_mapping: Vec, + ) -> anyhow::Result> { let mut res = vec![]; - for IndexedPackagePath { + for IndexedVfsPackagePath { package, path, named_address_map: named_address_mapping, } in paths_with_mapping { res.extend( - find_move_filenames(&[path.as_str()], true)? + find_move_filenames_vfs(&[path], true)? .into_iter() - .map(|s| IndexedPackagePath { + .map(|s| IndexedVfsPackagePath { package, - path: Symbol::from(s), + path: s, named_address_map: named_address_mapping, }), ); } // sort the filenames so errors about redefinitions, or other inter-file conflicts, are // deterministic - res.sort_by(|p1, p2| p1.path.cmp(&p2.path)); + res.sort_by(|p1, p2| p1.path.as_str().cmp(p2.path.as_str())); Ok(res) } @@ -65,13 +64,13 @@ pub(crate) fn parse_program( let mut source_comments = CommentMap::new(); let mut lib_definitions = Vec::new(); - for IndexedPackagePath { + for IndexedVfsPackagePath { package, path, named_address_map, } in targets { - let (defs, comments, file_hash) = parse_file(compilation_env, &mut files, path, package)?; + let (defs, comments, file_hash) = parse_file(&path, compilation_env, &mut files, package)?; source_definitions.extend(defs.into_iter().map(|def| PackageDefinition { package, named_address_map, @@ -80,13 +79,13 @@ pub(crate) fn parse_program( source_comments.insert(file_hash, comments); } - for IndexedPackagePath { + for IndexedVfsPackagePath { package, path, named_address_map, } in deps { - let (defs, _, _) = parse_file(compilation_env, &mut files, path, package)?; + let (defs, _, _) = parse_file(&path, compilation_env, &mut files, package)?; lib_definitions.extend(defs.into_iter().map(|def| PackageDefinition { package, named_address_map, @@ -104,31 +103,23 @@ pub(crate) fn parse_program( fn ensure_targets_deps_dont_intersect( compilation_env: &CompilationEnv, - targets: &[IndexedPackagePath], - deps: &mut Vec, + targets: &[IndexedVfsPackagePath], + deps: &mut Vec, ) -> anyhow::Result<()> { - /// Canonicalize a file path. - fn canonicalize(path: &Symbol) -> String { - let p = path.as_str(); - match std::fs::canonicalize(p) { - Ok(s) => s.to_string_lossy().to_string(), - Err(_) => p.to_owned(), - } - } let target_set = targets .iter() - .map(|p| canonicalize(&p.path)) + .map(|p| p.path.as_str().to_owned()) .collect::>(); let dep_set = deps .iter() - .map(|p| canonicalize(&p.path)) + .map(|p| p.path.as_str().to_owned()) .collect::>(); let intersection = target_set.intersection(&dep_set).collect::>(); if intersection.is_empty() { return Ok(()); } if compilation_env.flags().sources_shadow_deps() { - deps.retain(|p| !intersection.contains(&&canonicalize(&p.path))); + deps.retain(|p| !intersection.contains(&&p.path.as_str().to_owned())); return Ok(()); } let all_files = intersection @@ -143,20 +134,19 @@ fn ensure_targets_deps_dont_intersect( } fn parse_file( + path: &VfsPath, compilation_env: &mut CompilationEnv, files: &mut FilesSourceText, - fname: Symbol, package: Option, ) -> anyhow::Result<( Vec, MatchedFileCommentMap, FileHash, )> { - let mut f = File::open(fname.as_str()) - .map_err(|err| std::io::Error::new(err.kind(), format!("{}: {}", err, fname)))?; let mut source_buffer = String::new(); - f.read_to_string(&mut source_buffer)?; + path.open_file()?.read_to_string(&mut source_buffer)?; let file_hash = FileHash::new(&source_buffer); + let fname = Symbol::from(path.as_str()); let buffer = match verify_string(file_hash, &source_buffer) { Err(ds) => { compilation_env.add_diags(ds); diff --git a/external-crates/move/crates/move-compiler/src/shared/mod.rs b/external-crates/move/crates/move-compiler/src/shared/mod.rs index c3adde7fe1162..6a1e7dd66d3ad 100644 --- a/external-crates/move/crates/move-compiler/src/shared/mod.rs +++ b/external-crates/move/crates/move-compiler/src/shared/mod.rs @@ -27,6 +27,7 @@ use std::{ rc::Rc, sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}, }; +use vfs::{VfsError, VfsPath}; pub mod ast_debug; pub mod known_attributes; @@ -203,13 +204,6 @@ pub struct PackagePaths = Symbol, NamedAddress: Into pub named_address_map: BTreeMap, } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct IndexedPackagePath { - pub package: Option, - pub path: Symbol, - pub named_address_map: NamedAddressMapIndex, -} - /// None for the default 'allow'. /// Some(prefix) for a custom set of warnings, e.g. 'allow(lint(_))'. pub type FilterPrefix = Option; @@ -866,3 +860,48 @@ macro_rules! process_binops { } pub(crate) use process_binops; + +//************************************************************************************************** +// Virtual file system support +//************************************************************************************************** + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IndexedPackagePath

{ + pub package: Option, + pub path: P, + pub named_address_map: NamedAddressMapIndex, +} + +pub type IndexedPhysicalPackagePath = IndexedPackagePath; + +pub type IndexedVfsPackagePath = IndexedPackagePath; + +pub fn vfs_path_from_str(path: String, vfs_path: &VfsPath) -> Result { + // we need to canonicalized paths for virtual file systems as some of them (e.g., implementation + // of the physical one) cannot handle relative paths + fn canonicalize(p: String) -> String { + // dunce's version of canonicalize does a better job on Windows + match dunce::canonicalize(&p) { + Ok(s) => s.to_string_lossy().to_string(), + Err(_) => p, + } + } + + vfs_path.join(canonicalize(path)) +} + +impl IndexedPhysicalPackagePath { + pub fn to_vfs_path(self, vfs_root: &VfsPath) -> Result { + let IndexedPhysicalPackagePath { + package, + path, + named_address_map, + } = self; + + Ok(IndexedVfsPackagePath { + package, + path: vfs_path_from_str(path.to_string(), vfs_root)?, + named_address_map, + }) + } +}