fix Inlay hint insertion should consider imports/use qualified paths to auto import correctly #317#1576
fix Inlay hint insertion should consider imports/use qualified paths to auto import correctly #317#1576asukaminato0721 wants to merge 6 commits into
Conversation
kinto0
left a comment
There was a problem hiding this comment.
awesome work! a few suggestions + it looks like there are some merge conflicts. it might also be worth coordinating with @jvansch1 over discord to make sure you're not stepping on each other's toes since he's editing the same file
kinto0
left a comment
There was a problem hiding this comment.
looks like the insertion text no longer matches the type
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Inlay hint labels now render with module qualifiers (e.g. typing.Literal) while skipping builtins and the current module, and respecting import X as alias. When the module isn’t imported, the inlay hint now includes extra text edits to insert import <module> alongside the hint insertion. LSP tests updated to expect the qualified label parts. |
There was a problem hiding this comment.
Pull request overview
This PR aims to make inlay-hint type annotation insertion robust by emitting module-qualified type names and attaching the necessary import edits so accepting a hint results in valid code (addressing #317).
Changes:
- Added module reference tracking to
TypeDisplayContextand introduced a newImportTrackerutility. - Extended inlay hints to include
import_edits, and updated the non-wasm LSP server to emit multipleTextEdits for a single hint acceptance. - Updated playground/WASM plumbing and LSP regression tests to reflect module-qualified label parts (e.g.
typing.Literal).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pyrefly/lib/test/lsp/lsp_interaction/notebook_inlay_hint.rs | Updates notebook inlay-hint expectations to include typing. qualifiers. |
| pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs | Updates inlay-hint label-part expectations for qualified typing/typing_extensions names. |
| pyrefly/lib/test/lsp/inlay_hint.rs | Adjusts generated inlay-hint report output to match qualified labels. |
| pyrefly/lib/state/mod.rs | Exposes the new import_tracker module. |
| pyrefly/lib/state/import_tracker.rs | Introduces ImportTracker and helpers for computing missing-module imports. |
| pyrefly/lib/playground.rs | Updates playground inlay-hint label concatenation with the new hint shape. |
| pyrefly/lib/lsp/wasm/inlay_hints.rs | Adds import_edits to hint payload; renders type hints with locations and computes missing imports. |
| pyrefly/lib/lsp/non_wasm/server.rs | Emits multiple TextEdits (annotation insertion + import edits) for insertable inlay hints. |
| crates/pyrefly_types/src/display.rs | Adds referenced_modules plumbing to TypeDisplayContext and records modules during formatting. |
Comments suppressed due to low confidence (1)
crates/pyrefly_types/src/display.rs:295
TypeDisplayContext::referenced_modulesrelies onself.modules, but the only place that inserts intomodulesismaybe_fmt_with_module. Most module-qualified output (e.g.QName::fmt_with_module/fmt_with_location) and several hard-coded prefixes like"typing."in the type formatter bypassmaybe_fmt_with_module, so those modules will never be recorded. That means downstream auto-import decisions can be incorrect/missing even when the rendered text contains module qualifiers. Consider recording the module whenever aQNameis formatted (e.g. infmt_qname) and ensuring any manual module-prefix writes also insert intomodules.
self.modules
.borrow_mut()
.insert(ModuleName::from_str(module));
if self.always_display_module_name {
output.write_str(module)?;
output.write_str(".")?;
output.write_str(name)
} else {
output.write_str(name)
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (import_pos, import_text) in hint_data.import_edits { | ||
| let import_position = info.to_lsp_position(import_pos); | ||
| edits.push(TextEdit { | ||
| range: Range::new(import_position, import_position), | ||
| new_text: import_text, |
There was a problem hiding this comment.
When hint_data.import_edits contains multiple inserts at the same position (currently likely, since import_regular_import_edit uses the first statement start), sending them as separate TextEdits can produce reversed/non-deterministic import ordering depending on how the client applies same-range edits. Consider merging import edits into a single TextEdit per insertion position (stable sort by module name), or otherwise ensuring a deterministic ordering strategy for same-position edits.
| for (import_pos, import_text) in hint_data.import_edits { | |
| let import_position = info.to_lsp_position(import_pos); | |
| edits.push(TextEdit { | |
| range: Range::new(import_position, import_position), | |
| new_text: import_text, | |
| // Merge import edits that target the same position into a single TextEdit | |
| let mut grouped_import_edits: Vec<(Position, Vec<String>)> = Vec::new(); | |
| for (import_pos, import_text) in hint_data.import_edits { | |
| let import_position = info.to_lsp_position(import_pos); | |
| if let Some((_, texts)) = grouped_import_edits | |
| .iter_mut() | |
| .find(|(pos, _)| *pos == import_position) | |
| { | |
| texts.push(import_text); | |
| } else { | |
| grouped_import_edits.push((import_position, vec![import_text])); | |
| } | |
| } | |
| for (import_position, texts) in grouped_import_edits { | |
| let mut new_text = String::new(); | |
| for text in texts { | |
| new_text.push_str(&text); | |
| } | |
| edits.push(TextEdit { | |
| range: Range::new(import_position, import_position), | |
| new_text, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ddbc0a6 to
daea5bd
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
fix part of #317
Type pretty-printing now tracks every module name it emits, so we can tell the client which imports a hint depends on; TypeDisplayContext stores the module set in a RefCell, exposes it via referenced_modules, and records each module.name combination as it formats the text
The LSP transaction layer wraps each hint in a new InlayHintWithEdits struct, renders annotations with fully qualified names, applies any local aliases (e.g., import typing as t), and bundles the necessary import edits by consulting the ImportTracker. This ensures that accepting a type hint also inserts import typing when the module hasn’t been brought into scope.
Downstream consumers now understand the richer hint payload: the server translates InlayHintWithEdits into multiple textEdits, the playground/WASM bindings expose the new label format, and the inlay-hint regression tests cover both standard and notebook scenarios to verify the extra import edits.