mirror of
https://github.com/astral-sh/ruff.git
synced 2026-05-06 08:56:57 -04:00
[ty] handle Annotated metadata in semantic tokens (#24890)
Fixes https://github.com/astral-sh/ty/issues/3362 ## Summary Fix semantic token classification for `typing.Annotated` metadata. ## Test Plan Added test.
This commit is contained in:
@@ -14,9 +14,6 @@
|
||||
//!
|
||||
//! TODO: Need to handle semantic tokens within quoted annotations.
|
||||
//!
|
||||
//! TODO: Need to properly handle Annotated expressions. All type arguments other
|
||||
//! than the first should be treated as value expressions, not as type expressions.
|
||||
//!
|
||||
//! TODO: Properties (or perhaps more generally, descriptor objects?) should be
|
||||
//! classified as property tokens rather than just variables.
|
||||
//!
|
||||
@@ -625,6 +622,35 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr_with_type_form(&mut self, expr: &Expr, in_type_form: bool) {
|
||||
let prev_in_type_form = self.in_type_form;
|
||||
self.in_type_form = in_type_form;
|
||||
self.visit_expr(expr);
|
||||
self.in_type_form = prev_in_type_form;
|
||||
}
|
||||
|
||||
fn visit_value_expression(&mut self, expr: &Expr) {
|
||||
self.visit_expr_with_type_form(expr, false);
|
||||
}
|
||||
|
||||
fn visit_annotated_arguments(&mut self, slice: &Expr) {
|
||||
let ast::Expr::Tuple(tuple) = slice else {
|
||||
self.visit_annotation(slice);
|
||||
return;
|
||||
};
|
||||
|
||||
let Some((annotation, metadata)) = tuple.elts.split_first() else {
|
||||
self.visit_annotation(slice);
|
||||
return;
|
||||
};
|
||||
|
||||
self.visit_annotation(annotation);
|
||||
|
||||
for metadata_element in metadata {
|
||||
self.visit_value_expression(metadata_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
@@ -882,10 +908,7 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
|
||||
/// Visit an annotation or other expression that should be interpreted as a type form.
|
||||
fn visit_annotation(&mut self, expr: &'_ Expr) {
|
||||
let prev_in_type_form = self.in_type_form;
|
||||
self.in_type_form = true;
|
||||
self.visit_expr(expr);
|
||||
self.in_type_form = prev_in_type_form;
|
||||
self.visit_expr_with_type_form(expr, true);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) {
|
||||
@@ -962,6 +985,15 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
ast::Expr::Subscript(subscript)
|
||||
if matches!(
|
||||
subscript.value.inferred_type(self.model),
|
||||
Some(Type::SpecialForm(SpecialFormType::Annotated))
|
||||
) =>
|
||||
{
|
||||
self.visit_expr(subscript.value.as_ref());
|
||||
self.visit_annotated_arguments(subscript.slice.as_ref());
|
||||
}
|
||||
ast::Expr::Call(call) => {
|
||||
self.visit_expr(call.func.as_ref());
|
||||
|
||||
@@ -1226,6 +1258,36 @@ mod tests {
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#""Foo" @ 6..9: Class [definition]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_annotated_metadata() {
|
||||
let test = SemanticTokenTest::new(
|
||||
"
|
||||
from typing import Annotated
|
||||
|
||||
class Metadata:
|
||||
field = 1
|
||||
|
||||
def f(x: Annotated[int, Metadata.field]): ...
|
||||
",
|
||||
);
|
||||
|
||||
let tokens = test.highlight_file();
|
||||
|
||||
assert_snapshot!(test.to_snapshot(&tokens), @r#"
|
||||
"typing" @ 6..12: Namespace
|
||||
"Annotated" @ 20..29: Variable
|
||||
"Metadata" @ 37..45: Class [definition]
|
||||
"field" @ 51..56: Variable [definition]
|
||||
"1" @ 59..60: Number
|
||||
"f" @ 66..67: Function [definition]
|
||||
"x" @ 68..69: Parameter [definition]
|
||||
"Annotated" @ 71..80: Variable
|
||||
"int" @ 81..84: Class
|
||||
"Metadata" @ 86..94: Class
|
||||
"field" @ 95..100: Variable
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn semantic_tokens_match_class_pattern_keyword_before_positional() {
|
||||
// Regression test for https://github.com/astral-sh/ty/issues/2417
|
||||
|
||||
Reference in New Issue
Block a user