Skip to content

state: share document updates and transaction management with WASI hosts - #5455

Open
yeonwooson wants to merge 2 commits into
meta:mainfrom
yeonwooson:wasi/shared-document-state
Open

yeonwooson wants to merge 2 commits into
meta:mainfrom
yeonwooson:wasi/shared-document-state

Conversation

@yeonwooson

Copy link
Copy Markdown
Contributor

Summary

Contributor motivation

Expose existing helpers so that the WASI host can apply unsaved document edits, track versions, and manage query transactions. Moving them into shared state lets the WASI integration reuse the native implementation instead of maintaining a duplicate, helping keep editing behavior consistent across both hosts.

AI-assisted implementation summary

  • Extract document opening, UTF-16 text edits, language-ID preservation, and version bookkeeping into shared state, and have the native server call those helpers.
  • Move TransactionManager, including its existing cancellation/restoration regression, without changing its implementation.
  • Preserve existing native import paths through re-exports.

URI resolution, backwards-version warnings, rechecking, and notebook-specific handling stay in the native layer. This does not add a WASI guest or transport, or change TSP messages, analysis, dependencies, or lockfiles.

Follow-up to #5436. No issue auto-close is requested.

  • I am a human, and I fully understand the code I am submitting.

Test Plan

AI-assisted test report: refreshed against current upstream on Windows ARM64 with Rust 1.99.1 and WASI SDK 34.

  • PASS, 150 tests; 1 pre-existing ignored test: cargo test -p pyrefly --lib --locked --offline -- state::document::tests state::transaction_manager::tests test::tsp:: test::lsp::lsp_interaction::did_change:: test::lsp::lsp_interaction::notebook_sync:: test::lsp::lsp_interaction::io:: test::lsp::lsp_interaction::diagnostic::test_open_file_during_recheck test::lsp::lsp_interaction::diagnostic::test_edit_file_during_recheck --test-threads=2
  • PASS: cargo check -p pyrefly --lib --target wasm32-wasip1 --locked --offline
  • PASS: python test.py --mode cargo --no-test --no-tensor-shapes --no-conformance --no-jsonschema --no-extension

The tests cover whole-document and sequential UTF-16/CRLF edits, language IDs, backwards versions, reopening, missing-file and notebook rejection, and the existing transaction cancellation/restoration behavior. The native TSP, document-change, notebook-sync, and edit-during-recheck regressions also pass.

An additional local, out-of-tree Rust consumer was rebuilt for WASIp1 and executed using the public WASI shim. It exercised the shared public APIs for document edits, version/error state, and transaction creation. This smoke harness is not part of the PR.

The edit algorithm and transaction manager were mechanically checked against their original implementations. Full formatting left all six candidate files unchanged. Existing warnings remain; the upstream CI-flaky test_open_file_during_recheck stays ignored and is not counted as passed. The full native suite and Linux/macOS execution were not run.

Let portable hosts reuse native document mutations and query transactions
without importing the native LSP server. Keep URI handling and scheduling in
the native host, and preserve the existing edit and transaction algorithms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@meta-codesync

meta-codesync Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D128390074. (Because this pull request was imported automatically, there will not be any future comments.)

@github-actions

This comment has been minimized.

@kinto0 kinto0 self-assigned this Oct 29, 2026

@kinto0 kinto0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! some nits about where things go / abstractions

feel free to add parallel PRs that branch off of this if this merge process is going too slowly. I can figure out how to review them like that

Comment thread pyrefly/lib/state/document.rs Outdated

use crate::state::load::LspFile;

pub type OpenFiles = RwLock<HashMap<PathBuf, Arc<LspFile>>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should openfiles just be an alias to the hashmap? and any user of this can put it in a rwlock? even better, if we want to abstract this, we should make a real data structure with its own apis

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, applied the changes

);
}
}
pub use crate::state::transaction_manager::TransactionManager;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we don't remove this file entirely?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed it and updated so that shared transaction manager is imported directly

Comment thread pyrefly/lib/state/document.rs Outdated
* LICENSE file in the root directory of this source tree.
*/

//! Document buffers and text edits shared by native and portable hosts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this go in lsp/wasm/document.rs instead?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made that naming convention a long time ago. I guess at the end of your work everything should be in wasm and nothing will be in non_wasm and we can remove non_wasm?

Comment thread pyrefly/lib/state/document.rs Outdated

use crate::state::load::LspFile;

pub type OpenFiles = RwLock<HashMap<PathBuf, Arc<LspFile>>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is what my agent proposes instead:

pub struct OpenFile {
    pub contents: Arc<LspFile>,
    pub version: i32,
}

#[derive(Default)]
pub struct OpenFiles(RwLock<HashMap<PathBuf, OpenFile>>);

impl OpenFiles {
    pub fn open(&self, path: PathBuf, version: i32, contents: Arc<LspFile>);
    pub fn close(&self, path: &Path) -> Option<OpenFile>;
    pub fn get(&self, path: &Path) -> Option<Arc<LspFile>>;
    pub fn versions(&self) -> HashMap<PathBuf, i32>;
    /// Replaces an open file's contents. The new version is stored only if `f` succeeds,
    /// and contents and version change under one lock.
    pub fn update(
        &self,
        path: &Path,
        version: i32,
        f: impl FnOnce(&OpenFile) -> anyhow::Result<LspFile>,
    ) -> anyhow::Result<()>;
}

impl LspFile {
    /// Applies text edits and keeps the language ID. Returns an error for a notebook.
    pub fn apply_text_changes(&self, changes: Vec<TextDocumentContentChangeEvent>) -> anyhow::Result<LspFile>;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then the callsite becomes:

// textDocument/didChange
self.open_files.update(&path, version, |old| {
    if version < old.version { warn!("version went backwards ..."); }
    old.contents.apply_text_changes(params.content_changes)
})?;

// notebookDocument/didChange: same shape, but returns an error on a backwards version


Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented this structure and updated the callsites as well

Keep each open document's contents and version in one synchronized record so
updates publish both only on success. Use the portable LSP document module
and import the shared transaction manager directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added size/xl and removed size/xl labels Oct 29, 2026
@github-actions

Copy link
Copy Markdown

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants