Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[red-knot] Support files outside of any workspace #13041

Merged
merged 2 commits into from
Aug 23, 2024

Conversation

dhruvmanila
Copy link
Member

@dhruvmanila dhruvmanila commented Aug 22, 2024

Summary

This PR adds basic support for files outside of any workspace in the red knot server.

This also limits the red knot server to only work in a single workspace. The server will not start if there are multiple workspaces.

Test Plan

Screen.Recording.2024-08-22.at.16.26.18.mov

@dhruvmanila dhruvmanila added the red-knot Multi-file analysis & type inference label Aug 22, 2024
Base automatically changed from dhruv/remove-notebook-sync to main August 22, 2024 09:25
@dhruvmanila dhruvmanila force-pushed the dhruv/non-workspace-files branch 2 times, most recently from 65b6671 to f5eac02 Compare August 22, 2024 09:37
Comment on lines +87 to +90
let db = match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
};
Copy link
Member Author

Choose a reason for hiding this comment

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

I want this entire logic in the Session but the borrow checker always complains when I do something like:

        self.workspaces
            .range_mut(..=path.as_ref().to_path_buf())
            .next_back()
            .map(|(_, db)| db)
			.unwrap_or_else(|| self.default_workspace_db_mut())

There are two mutable references:

error[E0500]: closure requires unique access to `*self` but it is already borrowed
   --> crates/red_knot_server/src/session.rs:104:29
    |
97  |           &mut self,
    |           - let's call the lifetime of this reference `'1`
...
100 |           self.workspaces
    |           ---------------
    |           |
    |  _________borrow occurs here
    | |
101 | |             .range_mut(..=path.as_ref().to_path_buf())
102 | |             .next_back()
103 | |             .map(|(_, db)| db)
104 | |             .unwrap_or_else(|| self.default_workspace_db_mut())
    | |_____________________________^^_----___________________________- returning this value requires that `self.workspaces` is borrowed for `'1`
    |                               |  |
    |                               |  second borrow occurs due to use of `*self` in closure
    |                               closure construction occurs here

For more information about this error, try `rustc --explain E0500`.

Copy link
Member

Choose a reason for hiding this comment

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

This seems to work

    pub(crate) fn workspace_db_for_path_or_default(&self, path: impl AsRef<Path>) -> &RootDatabase {
        self.workspace_db_for_path(path)
            .unwrap_or_else(|| self.default_workspace_db())
    }

Copy link
Member

Choose a reason for hiding this comment

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

This also compiles fine for me...

    pub(crate) fn workspace_db_for_path(&self, path: impl AsRef<Path>) -> &RootDatabase {
        self.workspaces
            .range(..=path.as_ref().to_path_buf())
            .next_back()
            .map(|(_, db)| db)
            .unwrap_or_else(|| self.default_workspace_db())
    }

Not sure if I'm doing something wrong. But I think you face the problem with workspace_db_for_path_mut?

Copy link
Member

@MichaReiser MichaReiser Aug 22, 2024

Choose a reason for hiding this comment

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

But yeah, not sure why the mut case doesn't work. Sounds like a borrow checker limitation? Maybe @BurntSushi has an idea

I tried to hint rust as best as I can but it just won't believe that we only return one mutable borrow

        let workspace = self
            .workspaces
            .range_mut(..=path.as_ref().to_path_buf())
            .next_back();

        if let Some((_, db)) = workspace {
            return db;
        }
        drop(workspace);

        return self.workspaces.values_mut().next().unwrap();

Copy link
Member Author

@dhruvmanila dhruvmanila Aug 22, 2024

Choose a reason for hiding this comment

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

Not sure if I'm doing something wrong. But I think you face the problem with workspace_db_for_path_mut?

Yes, it's for the mutable version.

I tried to with as many hints as possible but it just won't believe that we only return one mutable borrow

Yeah, I tried a couple of them myself, all raised the same error

Copy link
Member

Choose a reason for hiding this comment

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

Nothing is immediately popping out at me here. This might be something that is fixed by Polonius. Specifically, this might be the problem you're hitting? https://blog.rust-lang.org/inside-rust/2023/10/06/polonius-update.html#background-on-polonius

Copy link
Member

@MichaReiser MichaReiser Aug 22, 2024

Choose a reason for hiding this comment

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

Thanks. Yes, that might be it.

An alternative solution could be to accept a closure that applies the mutation to the db. Not as nice but hopefully that works?

Otherwise transmute your way to happiness 😂

Copy link
Member Author

@dhruvmanila dhruvmanila Aug 23, 2024

Choose a reason for hiding this comment

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

An alternative solution could be to accept a closure that applies the mutation to the db. Not as nice but hopefully that works?

Huh, this works but it gets a little bit complicated because we need to move the path to the ChangedEvent (and also that the path could be system or virtual). I'll keep the existing logic for now with a TODO to link to this discussion.

Comment on lines -69 to +71
let line = line.parse::<u32>().unwrap_or_default();
let line = line.parse::<u32>().unwrap_or_default().saturating_sub(1);
let column = column.parse::<u32>().unwrap_or_default();
(
Range::new(
Position::new(line.saturating_sub(1), column.saturating_sub(1)),
Position::new(line, column.saturating_sub(1)),
Copy link
Member Author

Choose a reason for hiding this comment

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

This is just to make sure the diagnostics are highlighted for at most one character. Without this, it would spill over to the next line.

Copy link
Contributor

github-actions bot commented Aug 22, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Some(db) => db,
None => session.default_workspace_db_mut(),
};
db.apply_changes(vec![ChangeEvent::file_created(path)], None);
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if file_created is the correct event here. The analyzer might already have seen the file when traversing the file system. I think it is only virtual files which would be "created".

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think that's correct. I think we might not need to emit any event here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Or maybe it's required when publishing diagnostics for clients that doesn't support pull diagnostics. I'm thinking of adding a new event kind like Opened(SystemPathBuf).

let db = session
.workspace_db_for_path(path.as_std_path())
.map(RootDatabase::snapshot);
let db = match session.workspace_db_for_path(path.as_std_path()) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We may want to consider using system path in the LSP as well to remove the back and forth between different types

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think that should be done after adding virtual file support because then there will be two paths.

Comment on lines +87 to +90
let db = match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
};
Copy link
Member

Choose a reason for hiding this comment

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

This seems to work

    pub(crate) fn workspace_db_for_path_or_default(&self, path: impl AsRef<Path>) -> &RootDatabase {
        self.workspace_db_for_path(path)
            .unwrap_or_else(|| self.default_workspace_db())
    }

Comment on lines +87 to +90
let db = match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
};
Copy link
Member

Choose a reason for hiding this comment

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

This also compiles fine for me...

    pub(crate) fn workspace_db_for_path(&self, path: impl AsRef<Path>) -> &RootDatabase {
        self.workspaces
            .range(..=path.as_ref().to_path_buf())
            .next_back()
            .map(|(_, db)| db)
            .unwrap_or_else(|| self.default_workspace_db())
    }

Not sure if I'm doing something wrong. But I think you face the problem with workspace_db_for_path_mut?

Comment on lines +87 to +90
let db = match session.workspace_db_for_path(path.as_std_path()) {
Some(db) => db.snapshot(),
None => session.default_workspace_db().snapshot(),
};
Copy link
Member

@MichaReiser MichaReiser Aug 22, 2024

Choose a reason for hiding this comment

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

But yeah, not sure why the mut case doesn't work. Sounds like a borrow checker limitation? Maybe @BurntSushi has an idea

I tried to hint rust as best as I can but it just won't believe that we only return one mutable borrow

        let workspace = self
            .workspaces
            .range_mut(..=path.as_ref().to_path_buf())
            .next_back();

        if let Some((_, db)) = workspace {
            return db;
        }
        drop(workspace);

        return self.workspaces.values_mut().next().unwrap();

@dhruvmanila dhruvmanila enabled auto-merge (squash) August 23, 2024 06:49
@dhruvmanila dhruvmanila merged commit c73a7bb into main Aug 23, 2024
16 checks passed
@dhruvmanila dhruvmanila deleted the dhruv/non-workspace-files branch August 23, 2024 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants