Repository navigation
Conversation
Typing `a.` on a TypedDict instance offers only the mapping methods, so the keys can only be discovered by switching to `a["`. These tests pin the behavior requested in meta#5478. Each key is offered after the dot, and accepting it rewrites the access into `a["key"]`, replacing the dot and any typed prefix. Inherited and NotRequired/ReadOnly keys are included, keys that are not identifiers are quoted correctly, dict methods still appear, and plain classes and the TypedDict class object get no rewriting items.
TypedDict keys can only be read with a subscript, so after `a.` users saw the mapping methods but none of the keys they usually want (meta#5478). Attribute completion now also offers each key of a TypedDict base. The item carries a text edit that replaces the dot and any typed prefix with `["key"]`, so accepting `f1` after `a.` produces `a["f1"]`. The keys come from the same TypedDict field lookup that subscript key completion uses, so inherited, NotRequired and ReadOnly keys are all included, and the regular attribute completions are still added alongside them. The filter text starts with the text the edit replaces (the dot), since clients match it against what was typed from the start of the edit. Keys are quoted with JSON string escaping, which is also a valid Python string literal, so keys that are not identifiers stay correct. The items are skipped when the base and attribute are on different lines, because a completion text edit must stay on one line.
This comment has been minimized.
This comment has been minimized.
|
This pull request has been imported. If you are a Meta employee, you can view this in D128918542. (Because this pull request was imported automatically, there will not be any future comments.) |
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
stroxler
left a comment
There was a problem hiding this comment.
Thanks!
Overall this looks pretty good to me. AI code review caught a few edge cases we should clean up before merging though:
-
Parenthesized receivers produce broken syntax — [dict_completions.rs:1112–1113](
)pyrefly/pyrefly/lib/state/lsp/dict_completions.rs
Lines 1112 to 1113 in f23386e
Acceptingfieldafter(a).fiproduces(a["field"], deleting the closing parenthesis.base_rangeexcludes grouping parentheses, so its end is not necessarily immediately before the dot. Start the edit at the actual attribute dot, preserving parentheses. -
Rewriting inside f-strings breaks Python <3.12 — [dict_completions.rs:1124–1132](
)pyrefly/pyrefly/lib/state/lsp/dict_completions.rs
Lines 1124 to 1132 in f23386e
Completing insidef"{a.fi}"producesf"{a["field"]}". JSON quoting is valid for a standalone Python string, but this is invalid before PEP 701. Choose quoting compatible with the enclosing f-string, or suppress unsafe rewrites on older Python versions. -
Value patterns receive invalid subscript rewrites — [completion.rs:1227–1233](
)pyrefly/pyrefly/lib/lsp/wasm/completion.rs
Lines 1227 to 1233 in f23386e
Completingcase a.fi:producescase a["field"]:, but subscripts are not allowed in patterns. Skip these items inPatternMatchValuecontexts.
Otherwise, the feature placement is sensible and the non-TypedDict fast path stays cheap.
Verification: Codex and Claude independently reviewed the change. Existing tests passed; additional applied-edit probes reproduced all three issues. Rust check, build, and lint passed. Validation was Linux-only; no review was posted and no source changes were retained.
Summary
Fixes #5478
Typing
a.on a TypedDict instance now also offers the TypedDict's keys. Accepting one rewrites the access into a subscript, soa.followed byf1becomesa["f1"].Each key item has a text edit that replaces everything from the end of the base expression through the attribute name (the dot and any typed prefix) with
["key"]. ItsfilterTextstarts with that same text (.f1), because clients match the filter text against what was typed from the start of the edit, and that includes the dot.fastapi_endpoint_support.rsuses the same pattern.The keys come from
collect_typed_dict_keys, which subscript key completion already uses, so inherited,NotRequiredandReadOnlykeys are all included. The regular attribute completions (get,keys, ...) are still offered next to them. Keys are quoted withserde_json::to_string, which always gives a valid Python string literal, so functional-syntax keys like"two words"work too. The items are skipped when the base and the attribute are on different lines, since a completion text edit has to stay on one line.Two behaviours you may want changed:
A | Nonegets them too. This matches whata[already does.CompletionItemKind::Fieldwith the value type as detail, the same as subscript key completion. They sort alongside the attributes by name.Test Plan
Added five tests in
pyrefly/lib/test/lsp/completion.rs. They cover the issue example (exact range,newTextandfilterText, with dict methods still present), a typed prefix (a.fi), inherited/NotRequired/ReadOnlykeys, quoting of non-identifier keys, and no rewrite items for a plain class instance or the TypedDict class object. The first commit adds only the tests. Four of them fail on it, and the fifth is a negative check that passes either way.I also ran
target/release/pyrefly lsp(from the bench-profile build above) over stdio with the issue's example and requested completion right aftera.:copy,get,items,keys,pop,setdefault,updateandvaluescame back in the same response. I did not run the Scrut or tensor-shape suites, and I only tested on macOS.