Skip to content

Comments

[red-knot] Add inlay type hints#17214

Merged
MichaReiser merged 28 commits intoastral-sh:mainfrom
MatthewMckee4:inlay-hints
Apr 10, 2025
Merged

[red-knot] Add inlay type hints#17214
MichaReiser merged 28 commits intoastral-sh:mainfrom
MatthewMckee4:inlay-hints

Conversation

@MatthewMckee4
Copy link
Contributor

@MatthewMckee4 MatthewMckee4 commented Apr 4, 2025

Summary

Fixes #17205

Test Plan

Add tests to red_knot_ide/src/inlay_hints.rs

simplescreenrecorder-2025-04-09_22.14.38.mp4
simplescreenrecorder-2025-04-09_22.21.35.mp4

@github-actions
Copy link
Contributor

github-actions bot commented Apr 4, 2025

mypy_primer results

No ecosystem changes detected ✅

@MatthewMckee4
Copy link
Contributor Author

Currently this adds type hints to almost everything, need to work on that

@MatthewMckee4
Copy link
Contributor Author

Also need to make sure we dont add type hints when there is already one there

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Apr 4, 2025
@MatthewMckee4
Copy link
Contributor Author

@MichaReiser suggested the following

I think I'd take an approach closer to rust analyzer:

  • Write a custom SourceOrderVisitor that traverses the AST
  • enter_node returns TraverseSignal::Skip if the node isn't in the queried range
  • Compute the hints in visit_* methods but only for the nodes you want by using node.inferred_type (because we never want to show declared types?)

https://github.com/rust-lang/rust-analyzer/blob/15d87419f1a123d8f888d608129c3ce3ff8f13d4/crates/ide/src/inlay_hints.rs#L224-L283

@MatthewMckee4 MatthewMckee4 marked this pull request as ready for review April 5, 2025 15:10
@MatthewMckee4
Copy link
Contributor Author

Im not sure how to work with settings to be able to enable/disable this

@MichaReiser
Copy link
Member

Did you push your latest changes? The current approach doesn't use a custom visitor. Could you add a test plan so that I get a sense for how the feature works?

@MatthewMckee4
Copy link
Contributor Author

Ive not implemented a custom visitor yet, ill get on that. red_knot_python_semantic/src/visitor.rs?

@MatthewMckee4 MatthewMckee4 marked this pull request as draft April 5, 2025 15:46
@MatthewMckee4
Copy link
Contributor Author

image
image

Does the first option look good?

@InSyncWithFoo
Copy link
Contributor

@MatthewMckee4 The second is more pleasing to the eyes, but I guess the hints won't be as nice for something like (a1, *a2) = a.

@MatthewMckee4
Copy link
Contributor Author

@InSyncWithFoo Yeah the hints dont change from the second image with (a1, *a2) = a

@MichaReiser
Copy link
Member

I find rust-analyzer's inlay type hints useful in two situations:

I don't think this PR implements 2.

From the LSP specification

/**
* Optional text edits that are performed when accepting this inlay hint.
*
* Note that edits are expected to change the document so that the inlay
* hint (or its nearest variant) is now part of the document and the inlay
* hint itself is now obsolete.
*
* Depending on the client capability inlayHint.resolveSupport clients
* might resolve this property late using the resolve request.
*/
textEdits?: TextEdit[];

That means, we could generalize the type. Although I'm not quiet sure what the rules would be ;) But I don't think this should block this PR

Co-authored-by: Micha Reiser <[email protected]>
Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

This is great. We should respect the range provided by the editor and I don't think the options approach is what we want, let's remove that for now and always enable inlay hints.

Could you update the test plan with a recording of the playground and LSP?

Comment on lines 67 to 71
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
text_edits: None,
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Not for this PR but we want to use the InlayHintResolveRequest to lazily resolve the tooltip and text edits. It may be worth adding a comment, see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint_resolve

@MatthewMckee4
Copy link
Contributor Author

I might not be able to get to this for the rest of the day, if you want to have a go at updating the tests or the range then go for it!

@MichaReiser
Copy link
Member

I might not be able to get to this for the rest of the day, if you want to have a go at updating the tests or the range then go for it!

There's no hurry and the chances for merge conflicts are very low. Take your time.

@MatthewMckee4 MatthewMckee4 changed the title [red-knot] Add (optional) inlay type hints [red-knot] Add inlay type hints Apr 7, 2025
Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

This looks good. I've a few smaller nit comments that would be great to address beforehand.

Please, also updated your PR summary with a test plan for both playground and the vs code extension (record a short video, similar to what I did with on hover)

Comment on lines 86 to 98
fn handle_assign_expr(&mut self, expr: &Expr) {
match expr {
Expr::Tuple(tuple) => {
for element in &tuple.elts {
self.handle_assign_expr(element);
}
}
_ => {
let ty = expr.inferred_type(&self.model);
self.add_hint(expr.range(), ty);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we could use the same approach as in SemanticIndexBuilder where we have a state variable on InlayHintVisitor indicating if it is currently visiting an assignment.

Then, inside visit_expr, add an inlay hint for every ExprName in a Store context.

This has the benefit that the visitor doesn't traverse the assignment targets multiple times.

Copy link
Contributor Author

@MatthewMckee4 MatthewMckee4 Apr 9, 2025

Choose a reason for hiding this comment

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

I believe we only traverse (and call inferred_type) on assignments like

a = b = 1

Then, inside visit_expr, add an inlay hint for every ExprName in a Store context.

How would we get the type in visit_expr?

Comment on lines 192 to 197
for hint in hints {
let end_position = (hint.range.end().to_u32() as usize) + offset;
let hint_str = format!("[{}]", hint.display(&self.db));
buf.insert_str(end_position, &hint_str);
offset += hint_str.len();
}
Copy link
Member

Choose a reason for hiding this comment

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

Oh, I like how you render the "hints" inline!

I'm happy to go with this for now. An alternative would be to use the Diagnostic again and render an annotation at hint.range.end where the primary message is the hinted type.

Copy link
Member

Choose a reason for hiding this comment

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

I think I've a slight preference to using diagnostics but that doesn't need to block this PR and can be done as a follow-up. My main reasoning is that other developers would need to look at both the test and the implementation to understand that the test infra adds the brackets ([]) and the implementation adds the : and ->. The diagnostics has the benefit to say that "here is where this hint will be added".

Comment on lines 391 to +399

#[wasm_bindgen]
impl Range {
#[wasm_bindgen(constructor)]
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
}

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is needed, unless you want the JS client to create Range instances.

For the rust code, using Range { start, end } should do it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i dont think it was working as it was doing some sort of instance check on range

@carljm carljm removed their request for review April 9, 2025 15:00
@sharkdp sharkdp removed their request for review April 9, 2025 17:02
@MichaReiser MichaReiser enabled auto-merge (squash) April 10, 2025 09:19
@MichaReiser MichaReiser merged commit 10e4412 into astral-sh:main Apr 10, 2025
21 checks passed
@MatthewMckee4 MatthewMckee4 deleted the inlay-hints branch April 19, 2025 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[red-knot] Add (optional) inlay type hints

6 participants