Repository navigation
Resolve identifiers inside quoted annotations in the LSP. - #5243
Arham-Begani wants to merge 2 commits into
Conversation
Adds tests that show the current behavior for names inside quoted annotations such as `x: "list[Foo]"`. The checker resolves these names, but the language server treats the whole annotation as a string: hover shows only the bare type, go-to-definition finds nothing (and the name in `list["Foo"]` resolves to `list.__getitem__`), and references and rename skip occurrences inside strings. This is the bug in meta#4740. The next commit fixes it. Also covers strings that must stay plain strings: ordinary string values, `Literal["..."]` values and malformed annotations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01769iN16mLYKGKoWiouacg6
Part of meta#4740. The binder parses forward-reference strings such as `x: "list[Foo]"` and binds the names inside them at their real source ranges, but the language server located the symbol under the cursor in the original AST, where the annotation is a single string literal. Hover, go-to-definition, references and rename therefore saw a string instead of `Foo`, and `list["Foo"]` even jumped to `list.__getitem__`. When the cursor is inside a string the binder parsed as a type expression, `identifier_at` and `find_definition` now resolve against the parsed expression, and the local reference scan also looks inside such strings. A string only counts if a name in its parse has a binding key, so ordinary strings, `Literal["..."]` values and malformed annotations stay plain strings. Strings with escapes or implicit concatenation are skipped because their contents don't map exactly onto the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01769iN16mLYKGKoWiouacg6
|
Hi @Arham-Begani! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
There was a problem hiding this comment.
Thanks! This looks pretty good to me. Unfortunately, our PR auto-import hook doesn't run for PRs that start out with the CLA unsigned; could you rebase? That should trigger an import so I can merge.
Since this needs a rebase anyway, here are some suggestions to reduce some minor code duplication (AI-generated, so feel free to push back on anything that doesn't make sense):
type_from_expression_at_implalready finds a string literal among the nodes at the cursor, re-parses it withAst::parse_type_literal(the binder's own helper), and callslocate_node_in. The PR adds a second copy of that pattern inforward_ref_atinstead of sharing it.- It also adds
parse_forward_ref, which calls ruff'sparse_type_annotationdirectly. The "simple only" restriction is a real reason to differ, but it leaves two forward-reference parsers in one file.
Summary
Part of #4740: hover, go-to-definition, references and rename inside quoted annotations. Semantic highlighting is not covered here (see #3615).
The rest of this description was drafted with an AI assistant (Claude Code).
The binder already parses forward-reference strings such as
x: "list[Foo]"and binds the names inside them at their real source ranges. The LSP, however, located the cursor in the original AST, where the annotation is a single string literal. As a result:type[Foo], without the name or docstringlist["Foo"]it jumped tolist.__getitem__The PR has two commits:
// BUG:comments.Approach (all in
pyrefly/lib/state/lsp.rs):forward_ref_atparses the string under the cursor. It uses the parse only if a name in it has aKey::BoundName. Only strings the binder parsed as types have such keys, so ordinary strings,Literal["..."]values and malformed annotations stay strings.identifier_atandfind_definitionresolve against that parsed expression. This fixes hover, definition, declaration and type definition, includingTYPE_CHECKINGimports and qualified names like"lib.Helper".The local variable reference scan also descends into strings that mention the name. This fixes references, rename and document highlight. References to imported names already included string occurrences through the solver's index.
Only strings whose contents map exactly onto the source (
AnnotationKind::Simple) are handled. Strings with escapes or implicit concatenation are skipped, because positions inside them can't be mapped exactly.I am a human, and I fully understand the code I am submitting.
Test Plan
New tests:
definition::goto_def_inside_quoted_annotation: simple,TYPE_CHECKINGimport, nested subscript,list["Model"], and both parts of"lib.Helper"definition::goto_def_ignores_strings_that_are_not_type_expressions:x = "Model",Literal["Model"]and a malformed"list[Model"resolve to nothing, before and after the fixhover::hover_inside_quoted_annotation: after the fix, hover inside quotes is identical to the unquoted hoverlocal_find_refs::references_inside_quoted_annotationsrename::test_rename_from_inside_quoted_annotationCommands run:
python3 test.py --no-test --no-tensor-shapes --no-conformance --no-jsonschema --no-extensioncargo test -p pyrefly --libcargo test -p pyrefly --test pyrefly_laziness_testscargo clippy --releaseType-checker behavior is unchanged, so conformance output is unaffected.