Skip to content

fix Inlay hint insertion should consider imports/use qualified paths to auto import correctly #317#1576

Open
asukaminato0721 wants to merge 6 commits into
facebook:mainfrom
asukaminato0721:317
Open

fix Inlay hint insertion should consider imports/use qualified paths to auto import correctly #317#1576
asukaminato0721 wants to merge 6 commits into
facebook:mainfrom
asukaminato0721:317

Conversation

@asukaminato0721

Copy link
Copy Markdown
Contributor

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.

@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.

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

Comment thread crates/pyrefly_types/src/display.rs
Comment thread pyrefly/lib/state/lsp.rs Outdated
Comment thread pyrefly/lib/state/lsp.rs Outdated
Comment thread pyrefly/lib/state/lsp.rs Outdated
Comment thread pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs Outdated
Comment thread pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs Outdated

@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.

looks like the insertion text no longer matches the type

Comment thread pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs Outdated
@kinto0 kinto0 removed their assignment Nov 20, 2025
@jvansch1 jvansch1 removed their assignment Nov 20, 2025
Comment thread pyrefly/lib/test/lsp/lsp_interaction/inlay_hint.rs Outdated
@asukaminato0721 asukaminato0721 marked this pull request as draft November 22, 2025 06:06
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@asukaminato0721

asukaminato0721 commented Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

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.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 12, 2026 19:32
Copilot AI review requested due to automatic review settings February 12, 2026 19:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 TypeDisplayContext and introduced a new ImportTracker utility.
  • Extended inlay hints to include import_edits, and updated the non-wasm LSP server to emit multiple TextEdits 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_modules relies on self.modules, but the only place that inserts into modules is maybe_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 bypass maybe_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 a QName is formatted (e.g. in fmt_qname) and ensuring any manual module-prefix writes also insert into modules.
        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.

Comment thread pyrefly/lib/lsp/wasm/inlay_hints.rs
Comment thread pyrefly/lib/lsp/non_wasm/server.rs Outdated
Comment on lines +3808 to +3812
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,

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

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

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.

Suggested change
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,
Copilot uses AI. Check for mistakes.
Comment thread pyrefly/lib/lsp/wasm/inlay_hints.rs Outdated
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 force-pushed the 317 branch 2 times, most recently from ddbc0a6 to daea5bd Compare March 2, 2026 19:40
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added size/xl and removed size/xl labels May 20, 2026
@github-actions

This comment has been minimized.

@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. ✅

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