-
Notifications
You must be signed in to change notification settings - Fork 2k
[red-knot] Add inlay type hints #17214
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
Merged
Merged
Changes from 17 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
51f1bc4
Initial progress in playground
MatthewMckee4 9a98afb
Remove redundant calls
MatthewMckee4 b110361
Fix build and filter some more types
MatthewMckee4 978cc48
Refactory and add more filter logic
MatthewMckee4 cb898fe
Fix build
MatthewMckee4 41f583e
Add knot server functionality for inlay hints
MatthewMckee4 0da803d
Add InlayHintVisitor to visit only the statements we want
MatthewMckee4 135d8a6
Add tests
MatthewMckee4 00eeaf5
Merge branch 'main' into inlay-hints
MatthewMckee4 bdb75b1
Fix build
MatthewMckee4 52106bc
Revert "Fix build"
MatthewMckee4 a135a6d
Fix build
MatthewMckee4 2d6b3d5
Fix build
MatthewMckee4 fad5abb
Add correct tuple inlay hints and update tests
MatthewMckee4 cef34a5
Add option to opt in to inlay hints
MatthewMckee4 c1c5434
run generate-all
MatthewMckee4 08cfce7
Update inlay_hints.rs
MatthewMckee4 d1d071a
Merge branch 'main' into inlay-hints
MatthewMckee4 313f76f
Apply changes as per review
MatthewMckee4 350ff11
run generate-all
MatthewMckee4 85377eb
Changes per review
MatthewMckee4 808a03a
Merge branch 'main' into inlay-hints
MatthewMckee4 b901496
Fix playground
MatthewMckee4 b2bc7d2
Update per review
MatthewMckee4 9e66656
Merge branch 'main' into inlay-hints
MatthewMckee4 e438ffa
Simplify tests
MichaReiser 70fb3e1
Show hint for all `ExprName::Store` locations
MichaReiser 59f3c7f
Extract helper in wasm code
MichaReiser File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,193 @@ | ||
| use crate::{Db, RangedValue}; | ||
| use red_knot_python_semantic::types::Type; | ||
| use red_knot_python_semantic::{HasType, SemanticModel}; | ||
| use ruff_db::files::{File, FileRange}; | ||
| use ruff_db::parsed::parsed_module; | ||
| use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor}; | ||
| use ruff_python_ast::{Expr, Stmt}; | ||
| use ruff_text_size::{Ranged, TextRange}; | ||
| use std::fmt; | ||
| use std::fmt::Formatter; | ||
|
|
||
| #[derive(Debug, Clone, Eq, PartialEq)] | ||
| pub enum InlayHintContent<'db> { | ||
| AssignStatement(Type<'db>), | ||
| FunctionReturnType(Type<'db>), | ||
| } | ||
|
|
||
| impl<'db> InlayHintContent<'db> { | ||
| pub const fn display(&self, db: &'db dyn Db) -> DisplayInlayHint<'_, 'db> { | ||
| DisplayInlayHint { db, hint: self } | ||
| } | ||
| } | ||
|
|
||
| pub struct DisplayInlayHint<'a, 'db> { | ||
| db: &'db dyn Db, | ||
| hint: &'a InlayHintContent<'db>, | ||
| } | ||
|
|
||
| impl fmt::Display for DisplayInlayHint<'_, '_> { | ||
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
| match self.hint { | ||
| InlayHintContent::AssignStatement(ty) => { | ||
| write!(f, ": {}", ty.display(self.db.upcast())) | ||
| } | ||
| InlayHintContent::FunctionReturnType(ty) => { | ||
| write!(f, " -> {}", ty.display(self.db.upcast())) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn inlay_hints(db: &dyn Db, file: File) -> Vec<RangedValue<InlayHintContent<'_>>> { | ||
| let mut visitor = InlayHintVisitor::new(db, file); | ||
|
|
||
| let ast = parsed_module(db.upcast(), file); | ||
|
|
||
| visitor.visit_body(ast.suite()); | ||
|
|
||
| let hints = visitor.hints().clone(); | ||
|
|
||
| hints | ||
| } | ||
|
|
||
| struct InlayHintVisitor<'db> { | ||
|
MatthewMckee4 marked this conversation as resolved.
|
||
| model: SemanticModel<'db>, | ||
| file: File, | ||
| hints: Vec<RangedValue<InlayHintContent<'db>>>, | ||
| } | ||
|
|
||
| impl<'db> InlayHintVisitor<'db> { | ||
| fn new(db: &'db dyn Db, file: File) -> Self { | ||
| Self { | ||
| model: SemanticModel::new(db.upcast(), file), | ||
| file, | ||
| hints: Vec::new(), | ||
| } | ||
| } | ||
|
|
||
| fn hints(&self) -> &Vec<RangedValue<InlayHintContent<'db>>> { | ||
| &self.hints | ||
| } | ||
|
|
||
| fn add_hint(&mut self, range: TextRange, ty: Type<'db>) { | ||
| self.hints.push(RangedValue { | ||
| range: FileRange::new(self.file, range), | ||
| value: InlayHintContent::AssignStatement(ty), | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| impl SourceOrderVisitor<'_> for InlayHintVisitor<'_> { | ||
| fn visit_stmt(&mut self, stmt: &Stmt) { | ||
| match stmt { | ||
| Stmt::Assign(assign) => { | ||
| let ty = assign.value.inferred_type(&self.model); | ||
| for target in &assign.targets { | ||
| match target { | ||
| Expr::Tuple(tuple) => { | ||
| for element in &tuple.elts { | ||
| let element_ty = element.inferred_type(&self.model); | ||
| self.add_hint(element.range(), element_ty); | ||
| } | ||
| } | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
| _ => { | ||
| self.add_hint(target.range(), ty); | ||
| } | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
| return; | ||
| } | ||
| // TODO | ||
| Stmt::FunctionDef(_) => {} | ||
| Stmt::For(_) => {} | ||
| Stmt::Expr(_) => {} | ||
| _ => {} | ||
| } | ||
|
|
||
| source_order::walk_stmt(self, stmt); | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| use red_knot_python_semantic::types::StringLiteralType; | ||
| use ruff_db::files::{system_path_to_file, File}; | ||
| use ruff_db::system::DbWithWritableSystem as _; | ||
| use ruff_text_size::TextSize; | ||
|
|
||
| use crate::db::tests::TestDb; | ||
|
|
||
| struct TestCase { | ||
| db: TestDb, | ||
| file: File, | ||
| } | ||
|
|
||
| fn test_case(content: impl AsRef<str>) -> TestCase { | ||
| let mut db = TestDb::new(); | ||
| db.write_file("test.py", content).unwrap(); | ||
|
|
||
| let file = system_path_to_file(&db, "test.py").unwrap(); | ||
|
|
||
| TestCase { db, file } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_assign_statement() { | ||
| let test_case = test_case("x = 1"); | ||
| let hints = get_inlay_hints(&test_case.db, test_case.file); | ||
| assert_eq!(hints.len(), 1); | ||
| assert_eq!( | ||
| hints[0].value, | ||
| InlayHintContent::AssignStatement(Type::IntLiteral(1)) | ||
| ); | ||
| assert_eq!( | ||
| hints[0].range, | ||
| FileRange::new( | ||
| test_case.file, | ||
| TextRange::new(TextSize::from(0), TextSize::from(1)) | ||
| ) | ||
| ); | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| #[test] | ||
| fn test_tuple_assignment() { | ||
| let test_case = test_case("x, y = (1, 'abc')"); | ||
| let hints = get_inlay_hints(&test_case.db, test_case.file); | ||
| assert_eq!(hints.len(), 2); | ||
| assert_eq!( | ||
| hints[0].value, | ||
| InlayHintContent::AssignStatement(Type::IntLiteral(1)) | ||
| ); | ||
| assert_eq!( | ||
| hints[1].value, | ||
| InlayHintContent::AssignStatement(Type::StringLiteral(StringLiteralType::new( | ||
| &test_case.db, | ||
| "abc" | ||
| ))) | ||
| ); | ||
| assert_eq!( | ||
| hints[0].range, | ||
| FileRange::new( | ||
| test_case.file, | ||
| TextRange::new(TextSize::from(0), TextSize::from(1)) | ||
| ) | ||
| ); | ||
| assert_eq!( | ||
| hints[1].range, | ||
| FileRange::new( | ||
| test_case.file, | ||
| TextRange::new(TextSize::from(3), TextSize::from(4)) | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_assign_statement_with_type_annotation() { | ||
| let test_case = test_case("x: int = 1"); | ||
| let hints = get_inlay_hints(&test_case.db, test_case.file); | ||
| assert_eq!(hints.len(), 0); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| mod diagnostic; | ||
| mod goto_type_definition; | ||
| mod hover; | ||
| mod inlay_hints; | ||
|
|
||
| pub(super) use diagnostic::DocumentDiagnosticRequestHandler; | ||
| pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler; | ||
| pub(super) use hover::HoverRequestHandler; | ||
| pub(super) use inlay_hints::InlayHintRequestHandler; |
78 changes: 78 additions & 0 deletions
78
crates/red_knot_server/src/server/api/requests/inlay_hints.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| use std::borrow::Cow; | ||
|
|
||
| use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler}; | ||
| use crate::server::client::Notifier; | ||
| use crate::DocumentSnapshot; | ||
| use lsp_types::request::InlayHintRequest; | ||
| use lsp_types::{InlayHintParams, Url}; | ||
| use red_knot_ide::get_inlay_hints; | ||
| use red_knot_project::Db; | ||
| use red_knot_project::ProjectDatabase; | ||
| use ruff_db::source::{line_index, source_text}; | ||
| use ruff_text_size::Ranged; | ||
|
|
||
| pub(crate) struct InlayHintRequestHandler; | ||
|
|
||
| impl RequestHandler for InlayHintRequestHandler { | ||
| type RequestType = InlayHintRequest; | ||
| } | ||
|
|
||
| impl BackgroundDocumentRequestHandler for InlayHintRequestHandler { | ||
| fn document_url(params: &InlayHintParams) -> Cow<Url> { | ||
| Cow::Borrowed(¶ms.text_document.uri) | ||
| } | ||
|
|
||
| fn run_with_snapshot( | ||
| snapshot: DocumentSnapshot, | ||
| db: ProjectDatabase, | ||
| _notifier: Notifier, | ||
| params: InlayHintParams, | ||
| ) -> crate::server::Result<Option<Vec<lsp_types::InlayHint>>> { | ||
| let Some(file) = snapshot.file(&db) else { | ||
| tracing::debug!("Failed to resolve file for {:?}", params); | ||
| return Ok(None); | ||
| }; | ||
|
|
||
| let editor_options = db | ||
| .project() | ||
| .metadata(&db) | ||
| .options() | ||
| .editor | ||
| .clone() | ||
| .unwrap_or_default(); | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
|
|
||
| if !editor_options.inlay_hints.unwrap_or(false) { | ||
| return Ok(None); | ||
| } | ||
|
|
||
| let inlay_hints = get_inlay_hints(&db, file); | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
|
|
||
| let index = line_index(&db, file); | ||
| let source = source_text(&db, file); | ||
|
|
||
| let inlay_hints = inlay_hints | ||
| .into_iter() | ||
| .map(|hint| { | ||
| let end = index.source_location(hint.range.range().end(), &source); | ||
|
|
||
| lsp_types::InlayHint { | ||
| position: lsp_types::Position { | ||
| line: u32::try_from(end.row.to_zero_indexed()) | ||
| .expect("row usize fits in u32"), | ||
| character: u32::try_from(end.column.to_zero_indexed()) | ||
| .expect("character usize fits in u32"), | ||
| }, | ||
| label: lsp_types::InlayHintLabel::String(hint.display(&db).to_string()), | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
| kind: None, | ||
|
MatthewMckee4 marked this conversation as resolved.
Outdated
|
||
| tooltip: None, | ||
| padding_left: None, | ||
| padding_right: None, | ||
| data: None, | ||
| text_edits: None, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Not for this PR but we want to use the |
||
| } | ||
| }) | ||
| .collect(); | ||
|
|
||
| Ok(Some(inlay_hints)) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.