ruff/crates/ty_python_semantic
Aria Desires 66d233134f
[ty] Implement lsp support for string annotations (#21577)
Fixes https://github.com/astral-sh/ty/issues/1009

## Summary

This adds support for:

* semantic-tokens (syntax highlighting)
* goto-type **(partially implemented, but want to land as-is)**
* goto-declaration
* goto-definition (falls out of goto-declaration)
* hover **(limited by goto-type)**
* find-references
* rename-references (falls out of find-references)

There are 3 major things being introduced here:

* `TypeInferenceBuilder::string_annotations` is a `FxHashSet` of exprs
which were determined to be string annotations during inference. It's
bubbled up in `extras` to hopefully minimize the overhead as in most
contexts it's empty.
* Very happy to hear if this is too hacky and if I should do something
better, but it's IMO important that we get an authoritative answer on
whether something is a string annotation or not.
* `SemanticModel::enter_string_annotation` checks if the expr was marked
by `TypeInferenceBuilder::string_annotations` and then parses the subast
and produces a sub-SemanticModel that sets
`SemanticModel::in_string_annotation_expr`. This expr will be used by
the model whenever we need to query e.g. the scope of the current
expression (otherwise the code will constantly panic as the subast nodes
are not in the current File's AST)
* This hazard consequently encouraged me to refactor a bunch of code to
replace uses of file/db with SemanticModel to minimize hazards (it is no
longer as safe to randomly materialize a SemanticModel in the middle of
analysis, you need to thread through the one you have in case it has
`in_string_annotation_expr` set).
* `GotoTarget::StringAnnotationSubexpr` (and a semantic-tokens impl)
which involves invoking `SemanticModel::enter_string_annotation` before
invoking the same kind of subroutine a normal expression would.
* goto-type (and consequently displaying the type in hover) is the main
hole here, because we can only get the type iff the string annotation is
the entire subexpression (i.e. we can get the type of `"int"` but not
the parts of `"int | str"`). This is shippable IMO.

## Test Plan

Messed around in IDE, wrote a ton of tests.
2025-11-25 13:31:04 +00:00
..
resources [ty] Add 'remove unused ignore comment' code action (#21582) 2025-11-25 08:08:21 -05:00
src [ty] Implement lsp support for string annotations (#21577) 2025-11-25 13:31:04 +00:00
tests [ty] Limit shown import paths to at most 5 unless ty runs with `-v` (#20912) 2025-10-16 13:18:09 +02:00
Cargo.toml [ty] Add 'remove unused ignore comment' code action (#21582) 2025-11-25 08:08:21 -05:00
build.rs Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00
mdtest.py [ty] Press 'enter' to rerun all mdtests (#21427) 2025-11-13 15:34:17 +01:00
mdtest.py.lock Rename Red Knot (#17820) 2025-05-03 19:49:15 +02:00