[ty] Implement patterns and typevars in the LSP (#21671)

## Summary

**This is the final goto-targets with missing
goto-definition/declaration implementations!
You can now theoretically click on all the user-defined names in all the
syntax. 🎉**

This adds:

* goto definition/declaration on patterns/typevars
* find-references/rename on patterns/typevars
* fixes syntax highlighting of `*rest` patterns

This notably *does not* add:

* goto-type for patterns/typevars 
* hover for patterns/typevars (because that's just goto-type for names)

Also I realized we were at the precipice of one of the great GotoTarget
sins being resolved, and so I made import aliases also resolve to a
ResolvedDefinition. This removes a ton of cruft and prevents further
backsliding.

Note however that import aliases are, in general, completely jacked up
when it comes to find-references/renames (both before and after this
PR). Previously you could try to rename an import alias and it just
wouldn't do anything. With this change we instead refuse to even let you
try to rename it.

Sorting out why import aliases are jacked up is an ongoing thing I hope
to handle in a followup.

## Test Plan

You'll surely not regret checking in 86 snapshot tests
This commit is contained in:
Aria Desires 2025-11-28 08:41:21 -05:00 committed by GitHub
parent 5e1b2eef57
commit c534bfaf01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 2329 additions and 113 deletions

View File

@ -17,7 +17,7 @@ use ty_python_semantic::{
use crate::docstring::Docstring; use crate::docstring::Docstring;
use crate::find_node::covering_node; use crate::find_node::covering_node;
use crate::goto::DefinitionsOrTargets; use crate::goto::Definitions;
use crate::importer::{ImportRequest, Importer}; use crate::importer::{ImportRequest, Importer};
use crate::symbols::QueryPattern; use crate::symbols::QueryPattern;
use crate::{Db, all_symbols}; use crate::{Db, all_symbols};
@ -220,9 +220,7 @@ impl<'db> Completion<'db> {
db: &'db dyn Db, db: &'db dyn Db,
semantic: SemanticCompletion<'db>, semantic: SemanticCompletion<'db>,
) -> Completion<'db> { ) -> Completion<'db> {
let definition = semantic let definition = semantic.ty.and_then(|ty| Definitions::from_ty(db, ty));
.ty
.and_then(|ty| DefinitionsOrTargets::from_ty(db, ty));
let documentation = definition.and_then(|def| def.docstring(db)); let documentation = definition.and_then(|def| def.docstring(db));
let is_type_check_only = semantic.is_type_check_only(db); let is_type_check_only = semantic.is_type_check_only(db);
Completion { Completion {

View File

@ -212,16 +212,9 @@ pub(crate) enum GotoTarget<'a> {
/// The resolved definitions for a `GotoTarget` /// The resolved definitions for a `GotoTarget`
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) enum DefinitionsOrTargets<'db> { pub(crate) struct Definitions<'db>(pub Vec<ResolvedDefinition<'db>>);
/// We computed actual Definitions we can do followup queries on.
Definitions(Vec<ResolvedDefinition<'db>>),
/// We directly computed a navigation.
///
/// We can't get docs or usefully compute goto-definition for this.
Targets(crate::NavigationTargets),
}
impl<'db> DefinitionsOrTargets<'db> { impl<'db> Definitions<'db> {
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> { pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
let ty_def = ty.definition(db)?; let ty_def = ty.definition(db)?;
let resolved = match ty_def { let resolved = match ty_def {
@ -237,7 +230,7 @@ impl<'db> DefinitionsOrTargets<'db> {
ResolvedDefinition::Definition(definition) ResolvedDefinition::Definition(definition)
} }
}; };
Some(DefinitionsOrTargets::Definitions(vec![resolved])) Some(Definitions(vec![resolved]))
} }
/// Get the "goto-declaration" interpretation of this definition /// Get the "goto-declaration" interpretation of this definition
@ -247,12 +240,7 @@ impl<'db> DefinitionsOrTargets<'db> {
self, self,
db: &'db dyn ty_python_semantic::Db, db: &'db dyn ty_python_semantic::Db,
) -> Option<crate::NavigationTargets> { ) -> Option<crate::NavigationTargets> {
match self { definitions_to_navigation_targets(db, None, self.0)
DefinitionsOrTargets::Definitions(definitions) => {
definitions_to_navigation_targets(db, None, definitions)
}
DefinitionsOrTargets::Targets(targets) => Some(targets),
}
} }
/// Get the "goto-definition" interpretation of this definition /// Get the "goto-definition" interpretation of this definition
@ -263,12 +251,7 @@ impl<'db> DefinitionsOrTargets<'db> {
self, self,
db: &'db dyn ty_python_semantic::Db, db: &'db dyn ty_python_semantic::Db,
) -> Option<crate::NavigationTargets> { ) -> Option<crate::NavigationTargets> {
match self { definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), self.0)
DefinitionsOrTargets::Definitions(definitions) => {
definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), definitions)
}
DefinitionsOrTargets::Targets(targets) => Some(targets),
}
} }
/// Get the docstring for this definition /// Get the docstring for this definition
@ -277,13 +260,7 @@ impl<'db> DefinitionsOrTargets<'db> {
/// so this will check both the goto-declarations and goto-definitions (in that order) /// so this will check both the goto-declarations and goto-definitions (in that order)
/// and return the first one found. /// and return the first one found.
pub(crate) fn docstring(self, db: &'db dyn crate::Db) -> Option<Docstring> { pub(crate) fn docstring(self, db: &'db dyn crate::Db) -> Option<Docstring> {
let definitions = match self { for definition in &self.0 {
DefinitionsOrTargets::Definitions(definitions) => definitions,
// Can't find docs for these
// (make more cases DefinitionOrTargets::Definitions to get more docs!)
DefinitionsOrTargets::Targets(_) => return None,
};
for definition in &definitions {
// If we got a docstring from the original definition, use it // If we got a docstring from the original definition, use it
if let Some(docstring) = definition.docstring(db) { if let Some(docstring) = definition.docstring(db) {
return Some(Docstring::new(docstring)); return Some(Docstring::new(docstring));
@ -296,7 +273,7 @@ impl<'db> DefinitionsOrTargets<'db> {
let stub_mapper = StubMapper::new(db); let stub_mapper = StubMapper::new(db);
// Try to find the corresponding implementation definition // Try to find the corresponding implementation definition
for definition in stub_mapper.map_definitions(definitions) { for definition in stub_mapper.map_definitions(self.0) {
if let Some(docstring) = definition.docstring(db) { if let Some(docstring) = definition.docstring(db) {
return Some(Docstring::new(docstring)); return Some(Docstring::new(docstring));
} }
@ -399,36 +376,32 @@ impl GotoTarget<'_> {
&self, &self,
model: &SemanticModel<'db>, model: &SemanticModel<'db>,
alias_resolution: ImportAliasResolution, alias_resolution: ImportAliasResolution,
) -> Option<DefinitionsOrTargets<'db>> { ) -> Option<Definitions<'db>> {
use crate::NavigationTarget; let definitions = match self {
match self { GotoTarget::Expression(expression) => definitions_for_expression(model, *expression),
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression)
.map(DefinitionsOrTargets::Definitions),
// For already-defined symbols, they are their own definitions // For already-defined symbols, they are their own definitions
GotoTarget::FunctionDef(function) => Some(DefinitionsOrTargets::Definitions(vec![ GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
ResolvedDefinition::Definition(function.definition(model)), function.definition(model),
])), )]),
GotoTarget::ClassDef(class) => Some(DefinitionsOrTargets::Definitions(vec![ GotoTarget::ClassDef(class) => Some(vec![ResolvedDefinition::Definition(
ResolvedDefinition::Definition(class.definition(model)), class.definition(model),
])), )]),
GotoTarget::Parameter(parameter) => Some(DefinitionsOrTargets::Definitions(vec![ GotoTarget::Parameter(parameter) => Some(vec![ResolvedDefinition::Definition(
ResolvedDefinition::Definition(parameter.definition(model)), parameter.definition(model),
])), )]),
// For import aliases (offset within 'y' or 'z' in "from x import y as z") // For import aliases (offset within 'y' or 'z' in "from x import y as z")
GotoTarget::ImportSymbolAlias { GotoTarget::ImportSymbolAlias {
alias, import_from, .. alias, import_from, ..
} => { } => {
let symbol_name = alias.name.as_str(); let symbol_name = alias.name.as_str();
Some(DefinitionsOrTargets::Definitions( Some(definitions_for_imported_symbol(
definitions_for_imported_symbol(
model, model,
import_from, import_from,
symbol_name, symbol_name,
alias_resolution, alias_resolution,
),
)) ))
} }
@ -448,14 +421,9 @@ impl GotoTarget<'_> {
if alias_resolution == ImportAliasResolution::ResolveAliases { if alias_resolution == ImportAliasResolution::ResolveAliases {
definitions_for_module(model, Some(alias.name.as_str()), 0) definitions_for_module(model, Some(alias.name.as_str()), 0)
} else { } else {
let alias_range = alias.asname.as_ref().unwrap().range; alias.asname.as_ref().map(|name| {
Some(DefinitionsOrTargets::Targets( definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
crate::NavigationTargets::single(NavigationTarget { })
file: model.file(),
focus_range: alias_range,
full_range: alias.range(),
}),
))
} }
} }
@ -463,45 +431,44 @@ impl GotoTarget<'_> {
GotoTarget::KeywordArgument { GotoTarget::KeywordArgument {
keyword, keyword,
call_expression, call_expression,
} => Some(DefinitionsOrTargets::Definitions( } => Some(definitions_for_keyword_argument(
definitions_for_keyword_argument(model, keyword, call_expression), model,
keyword,
call_expression,
)), )),
// For exception variables, they are their own definitions (like parameters) // For exception variables, they are their own definitions (like parameters)
GotoTarget::ExceptVariable(except_handler) => { GotoTarget::ExceptVariable(except_handler) => {
Some(DefinitionsOrTargets::Definitions(vec![ Some(vec![ResolvedDefinition::Definition(
ResolvedDefinition::Definition(except_handler.definition(model)), except_handler.definition(model),
])) )])
} }
// For pattern match rest variables, they are their own definitions // Patterns are glorified assignments but we have to look them up by ident
// because they're not expressions
GotoTarget::PatternMatchRest(pattern_mapping) => { GotoTarget::PatternMatchRest(pattern_mapping) => {
if let Some(rest_name) = &pattern_mapping.rest { pattern_mapping.rest.as_ref().map(|name| {
let range = rest_name.range; definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
Some(DefinitionsOrTargets::Targets( })
crate::NavigationTargets::single(NavigationTarget::new(
model.file(),
range,
)),
))
} else {
None
}
} }
// For pattern match as names, they are their own definitions GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
GotoTarget::PatternMatchAsName(pattern_as) => { definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
if let Some(name) = &pattern_as.name { }),
let range = name.range;
Some(DefinitionsOrTargets::Targets( GotoTarget::PatternKeywordArgument(pattern_keyword) => {
crate::NavigationTargets::single(NavigationTarget::new( let name = &pattern_keyword.attr;
model.file(), Some(definitions_for_name(
range, model,
)), name.as_str(),
AnyNodeRef::Identifier(name),
)) ))
} else {
None
} }
GotoTarget::PatternMatchStarName(pattern_star) => {
pattern_star.name.as_ref().map(|name| {
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
})
} }
// For callables, both the definition of the callable and the actual function impl are relevant. // For callables, both the definition of the callable and the actual function impl are relevant.
@ -516,7 +483,7 @@ impl GotoTarget<'_> {
if definitions.is_empty() { if definitions.is_empty() {
None None
} else { } else {
Some(DefinitionsOrTargets::Definitions(definitions)) Some(definitions)
} }
} }
@ -524,14 +491,14 @@ impl GotoTarget<'_> {
let (definitions, _) = let (definitions, _) =
ty_python_semantic::definitions_for_bin_op(model, expression)?; ty_python_semantic::definitions_for_bin_op(model, expression)?;
Some(DefinitionsOrTargets::Definitions(definitions)) Some(definitions)
} }
GotoTarget::UnaryOp { expression, .. } => { GotoTarget::UnaryOp { expression, .. } => {
let (definitions, _) = let (definitions, _) =
ty_python_semantic::definitions_for_unary_op(model, expression)?; ty_python_semantic::definitions_for_unary_op(model, expression)?;
Some(DefinitionsOrTargets::Definitions(definitions)) Some(definitions)
} }
// String annotations sub-expressions require us to recurse into the sub-AST // String annotations sub-expressions require us to recurse into the sub-AST
@ -545,23 +512,47 @@ impl GotoTarget<'_> {
.node() .node()
.as_expr_ref()?; .as_expr_ref()?;
definitions_for_expression(&submodel, subexpr) definitions_for_expression(&submodel, subexpr)
.map(DefinitionsOrTargets::Definitions)
} }
// nonlocal and global are essentially loads, but again they're statements,
// so we need to look them up by ident
GotoTarget::NonLocal { identifier } | GotoTarget::Globals { identifier } => { GotoTarget::NonLocal { identifier } | GotoTarget::Globals { identifier } => {
Some(DefinitionsOrTargets::Definitions(definitions_for_name( Some(definitions_for_name(
model, model,
identifier.as_str(), identifier.as_str(),
AnyNodeRef::Identifier(identifier), AnyNodeRef::Identifier(identifier),
))) ))
} }
// TODO: implement these // These are declarations of sorts, but they're stmts and not exprs, so look up by ident.
GotoTarget::PatternKeywordArgument(..) GotoTarget::TypeParamTypeVarName(type_var) => {
| GotoTarget::PatternMatchStarName(..) let name = &type_var.name;
| GotoTarget::TypeParamTypeVarName(..) Some(definitions_for_name(
| GotoTarget::TypeParamParamSpecName(..) model,
| GotoTarget::TypeParamTypeVarTupleName(..) => None, name.as_str(),
AnyNodeRef::Identifier(name),
))
} }
GotoTarget::TypeParamParamSpecName(name) => {
let name = &name.name;
Some(definitions_for_name(
model,
name.as_str(),
AnyNodeRef::Identifier(name),
))
}
GotoTarget::TypeParamTypeVarTupleName(name) => {
let name = &name.name;
Some(definitions_for_name(
model,
name.as_str(),
AnyNodeRef::Identifier(name),
))
}
};
definitions.map(Definitions)
} }
/// Returns the text representation of this goto target. /// Returns the text representation of this goto target.
@ -1050,12 +1041,10 @@ fn definitions_for_module<'db>(
model: &SemanticModel<'db>, model: &SemanticModel<'db>,
module: Option<&str>, module: Option<&str>,
level: u32, level: u32,
) -> Option<DefinitionsOrTargets<'db>> { ) -> Option<Vec<ResolvedDefinition<'db>>> {
let module = model.resolve_module(module, level)?; let module = model.resolve_module(module, level)?;
let file = module.file(model.db())?; let file = module.file(model.db())?;
Some(DefinitionsOrTargets::Definitions(vec![ Some(vec![ResolvedDefinition::Module(file)])
ResolvedDefinition::Module(file),
]))
} }
/// Helper function to extract module component information from a dotted module name /// Helper function to extract module component information from a dotted module name

View File

@ -1397,6 +1397,486 @@ def function():
"); ");
} }
#[test]
fn goto_declaration_match_name_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
|
"#);
}
#[test]
fn goto_declaration_match_name_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn goto_declaration_match_rest_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
|
"#);
}
#[test]
fn goto_declaration_match_rest_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", *ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn goto_declaration_match_as_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
|
"#);
}
#[test]
fn goto_declaration_match_as_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
|
info: Source
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn goto_declaration_match_keyword_stmt() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=a<CURSOR>b):
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
|
info: Source
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
|
");
}
#[test]
fn goto_declaration_match_keyword_binding() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
|
info: Source
--> main.py:11:17
|
9 | match event:
10 | case Click(x, button=ab):
11 | x = ab
| ^^
|
");
}
#[test]
fn goto_declaration_match_class_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Cl<CURSOR>ick(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @r#"
info[goto-declaration]: Declaration
--> main.py:2:7
|
2 | class Click:
| ^^^^^
3 | __match_args__ = ("position", "button")
4 | def __init__(self, pos, btn):
|
info: Source
--> main.py:10:14
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^^^^
11 | x = ab
|
"#);
}
#[test]
fn goto_declaration_match_class_field_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, but<CURSOR>ton=ab):
x = ab
"#,
);
assert_snapshot!(test.goto_declaration(), @"No goto target found");
}
#[test]
fn goto_declaration_typevar_name_stmt() {
let test = cursor_test(
r#"
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info: Source
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn goto_declaration_typevar_name_binding() {
let test = cursor_test(
r#"
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info: Source
--> main.py:2:37
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn goto_declaration_typevar_spec_stmt() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info: Source
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
");
}
#[test]
fn goto_declaration_typevar_spec_binding() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info: Source
--> main.py:3:43
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
");
}
#[test]
fn goto_declaration_typevar_tuple_stmt() {
let test = cursor_test(
r#"
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info: Source
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
");
}
#[test]
fn goto_declaration_typevar_tuple_binding() {
let test = cursor_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
"#,
);
assert_snapshot!(test.goto_declaration(), @r"
info[goto-declaration]: Declaration
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info: Source
--> main.py:2:38
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
");
}
#[test] #[test]
fn goto_declaration_property_getter_setter() { fn goto_declaration_property_getter_setter() {
let test = cursor_test( let test = cursor_test(

View File

@ -898,6 +898,552 @@ cls = MyClass
assert_snapshot!(test.references(), @"No references found"); assert_snapshot!(test.references(), @"No references found");
} }
#[test]
fn references_match_name_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_name_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_rest_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", *ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_rest_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", *ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_as_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_as_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
|
info[references]: Reference 2
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
5 | x = ab
| ^^
|
"#);
}
#[test]
fn references_match_keyword_stmt() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=a<CURSOR>b):
x = ab
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
|
info[references]: Reference 2
--> main.py:11:17
|
9 | match event:
10 | case Click(x, button=ab):
11 | x = ab
| ^^
|
");
}
#[test]
fn references_match_keyword_binding() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
|
info[references]: Reference 2
--> main.py:11:17
|
9 | match event:
10 | case Click(x, button=ab):
11 | x = ab
| ^^
|
");
}
#[test]
fn references_match_class_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Cl<CURSOR>ick(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.references(), @r#"
info[references]: Reference 1
--> main.py:2:7
|
2 | class Click:
| ^^^^^
3 | __match_args__ = ("position", "button")
4 | def __init__(self, pos, btn):
|
info[references]: Reference 2
--> main.py:8:20
|
6 | self.button: str = btn
7 |
8 | def my_func(event: Click):
| ^^^^^
9 | match event:
10 | case Click(x, button=ab):
|
info[references]: Reference 3
--> main.py:10:14
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^^^^
11 | x = ab
|
"#);
}
#[test]
fn references_match_class_field_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, but<CURSOR>ton=ab):
x = ab
"#,
);
assert_snapshot!(test.references(), @"No references found");
}
#[test]
fn references_typevar_name_stmt() {
let test = cursor_test(
r#"
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info[references]: Reference 2
--> main.py:2:37
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info[references]: Reference 3
--> main.py:2:46
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn references_typevar_name_binding() {
let test = cursor_test(
r#"
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info[references]: Reference 2
--> main.py:2:37
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info[references]: Reference 3
--> main.py:2:46
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn references_typevar_spec_stmt() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info[references]: Reference 2
--> main.py:3:43
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info[references]: Reference 3
--> main.py:3:53
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
");
}
#[test]
fn references_typevar_spec_binding() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info[references]: Reference 2
--> main.py:3:43
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
info[references]: Reference 3
--> main.py:3:53
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^
|
");
}
#[test]
fn references_typevar_tuple_stmt() {
let test = cursor_test(
r#"
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info[references]: Reference 2
--> main.py:2:38
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info[references]: Reference 3
--> main.py:2:50
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
");
}
#[test]
fn references_typevar_tuple_binding() {
let test = cursor_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
"#,
);
assert_snapshot!(test.references(), @r"
info[references]: Reference 1
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info[references]: Reference 2
--> main.py:2:38
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
info[references]: Reference 3
--> main.py:2:50
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^
|
");
}
#[test] #[test]
fn test_multi_file_function_references() { fn test_multi_file_function_references() {
let test = CursorTest::builder() let test = CursorTest::builder()

View File

@ -964,6 +964,282 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @"No goto target found"); assert_snapshot!(test.goto_type_definition(), @"No goto target found");
} }
#[test]
fn goto_type_match_name_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_match_name_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]
fn goto_type_match_rest_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_match_rest_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]
fn goto_type_match_as_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_match_as_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]
fn goto_type_match_keyword_stmt() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=a<CURSOR>b):
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_match_keyword_binding() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]
fn goto_type_match_class_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Cl<CURSOR>ick(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> main.py:2:7
|
2 | class Click:
| ^^^^^
3 | __match_args__ = ("position", "button")
4 | def __init__(self, pos, btn):
|
info: Source
--> main.py:10:14
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^^^^
11 | x = ab
|
"#);
}
#[test]
fn goto_type_match_class_field_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, but<CURSOR>ton=ab):
x = ab
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_typevar_name_stmt() {
let test = cursor_test(
r#"
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
info[goto-type-definition]: Type definition
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info: Source
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn goto_type_typevar_name_binding() {
let test = cursor_test(
r#"
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @r"
info[goto-type-definition]: Type definition
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
info: Source
--> main.py:2:37
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^
|
");
}
#[test]
fn goto_type_typevar_spec_stmt() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_typevar_spec_binding() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]
fn goto_type_typevar_tuple_stmt() {
let test = cursor_test(
r#"
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
}
#[test]
fn goto_type_typevar_tuple_binding() {
let test = cursor_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
"#,
);
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test] #[test]
fn goto_type_on_keyword_argument() { fn goto_type_on_keyword_argument() {
let test = cursor_test( let test = cursor_test(

View File

@ -1759,6 +1759,398 @@ def function():
assert_snapshot!(test.hover(), @"Hover provided no content"); assert_snapshot!(test.hover(), @"Hover provided no content");
} }
#[test]
fn hover_match_name_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_match_name_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.hover(), @r#"
@Todo
---------------------------------------------
```python
@Todo
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ab]:
5 | x = ab
| ^-
| ||
| |Cursor offset
| source
|
"#);
}
#[test]
fn hover_match_rest_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_match_rest_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.hover(), @r#"
@Todo
---------------------------------------------
```python
@Todo
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", *ab]:
5 | x = ab
| ^-
| ||
| |Cursor offset
| source
|
"#);
}
#[test]
fn hover_match_as_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_match_as_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.hover(), @r#"
@Todo
---------------------------------------------
```python
@Todo
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:5:17
|
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
5 | x = ab
| ^-
| ||
| |Cursor offset
| source
|
"#);
}
#[test]
fn hover_match_keyword_stmt() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=a<CURSOR>b):
x = ab
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_match_keyword_binding() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.hover(), @r"
@Todo
---------------------------------------------
```python
@Todo
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:11:17
|
9 | match event:
10 | case Click(x, button=ab):
11 | x = ab
| ^-
| ||
| |Cursor offset
| source
|
");
}
#[test]
fn hover_match_class_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Cl<CURSOR>ick(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.hover(), @r"
<class 'Click'>
---------------------------------------------
```python
<class 'Click'>
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:10:14
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^-^^
| | |
| | Cursor offset
| source
11 | x = ab
|
");
}
#[test]
fn hover_match_class_field_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, but<CURSOR>ton=ab):
x = ab
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_typevar_name_stmt() {
let test = cursor_test(
r#"
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.hover(), @r"
AB@Alias1 (invariant)
---------------------------------------------
```python
AB@Alias1 (invariant)
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^-
| ||
| |Cursor offset
| source
|
");
}
#[test]
fn hover_typevar_name_binding() {
let test = cursor_test(
r#"
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
"#,
);
assert_snapshot!(test.hover(), @r"
AB@Alias1 (invariant)
---------------------------------------------
```python
AB@Alias1 (invariant)
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:37
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^-
| ||
| |Cursor offset
| source
|
");
}
#[test]
fn hover_typevar_spec_stmt() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_typevar_spec_binding() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
"#,
);
assert_snapshot!(test.hover(), @r"
(
...
) -> tuple[typing.ParamSpec]
---------------------------------------------
```python
(
...
) -> tuple[typing.ParamSpec]
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:3:43
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^-
| ||
| |Cursor offset
| source
|
");
}
#[test]
fn hover_typevar_tuple_stmt() {
let test = cursor_test(
r#"
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.hover(), @"Hover provided no content");
}
#[test]
fn hover_typevar_tuple_binding() {
let test = cursor_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
"#,
);
assert_snapshot!(test.hover(), @r"
@Todo
---------------------------------------------
```python
@Todo
```
---------------------------------------------
info[hover]: Hovered content is
--> main.py:2:38
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^-
| ||
| |Cursor offset
| source
|
");
}
#[test] #[test]
fn hover_module_import() { fn hover_module_import() {
let mut test = cursor_test( let mut test = cursor_test(

View File

@ -1946,6 +1946,131 @@ mod tests {
"#); "#);
} }
#[test]
fn test_match_name_binding() {
let mut test = inlay_hint_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = ab
"#,
);
assert_snapshot!(test.inlay_hints(), @r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x[: @Todo] = ab
"#);
}
#[test]
fn test_match_rest_binding() {
let mut test = inlay_hint_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = ab
"#,
);
assert_snapshot!(test.inlay_hints(), @r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x[: @Todo] = ab
"#);
}
#[test]
fn test_match_as_binding() {
let mut test = inlay_hint_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = ab
"#,
);
assert_snapshot!(test.inlay_hints(), @r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x[: @Todo] = ab
"#);
}
#[test]
fn test_match_keyword_binding() {
let mut test = inlay_hint_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.inlay_hints(), @r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x[: @Todo] = ab
"#);
}
#[test]
fn test_typevar_name_binding() {
let mut test = inlay_hint_test(
r#"
type Alias1[AB: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.inlay_hints(), @"type Alias1[AB: int = bool] = tuple[AB, list[AB]]");
}
#[test]
fn test_typevar_spec_binding() {
let mut test = inlay_hint_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.inlay_hints(), @r"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
");
}
#[test]
fn test_typevar_tuple_binding() {
let mut test = inlay_hint_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.inlay_hints(), @"type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]");
}
#[test] #[test]
fn test_many_literals() { fn test_many_literals() {
let mut test = inlay_hint_test( let mut test = inlay_hint_test(

View File

@ -219,6 +219,11 @@ impl<'a> SourceOrderVisitor<'a> for LocalReferencesFinder<'a> {
self.check_identifier_reference(name); self.check_identifier_reference(name);
} }
} }
AnyNodeRef::PatternMatchStar(pattern_star) if self.should_include_declaration() => {
if let Some(name) = &pattern_star.name {
self.check_identifier_reference(name);
}
}
AnyNodeRef::PatternMatchMapping(pattern_mapping) AnyNodeRef::PatternMatchMapping(pattern_mapping)
if self.should_include_declaration() => if self.should_include_declaration() =>
{ {
@ -226,6 +231,15 @@ impl<'a> SourceOrderVisitor<'a> for LocalReferencesFinder<'a> {
self.check_identifier_reference(rest_name); self.check_identifier_reference(rest_name);
} }
} }
AnyNodeRef::TypeParamParamSpec(param_spec) if self.should_include_declaration() => {
self.check_identifier_reference(&param_spec.name);
}
AnyNodeRef::TypeParamTypeVarTuple(param_tuple) if self.should_include_declaration() => {
self.check_identifier_reference(&param_tuple.name);
}
AnyNodeRef::TypeParamTypeVar(param_var) if self.should_include_declaration() => {
self.check_identifier_reference(&param_var.name);
}
AnyNodeRef::ExprStringLiteral(string_expr) if self.should_include_declaration() => { AnyNodeRef::ExprStringLiteral(string_expr) if self.should_include_declaration() => {
// Highlight the sub-AST of a string annotation // Highlight the sub-AST of a string annotation
if let Some((sub_ast, sub_model)) = self.model.enter_string_annotation(string_expr) if let Some((sub_ast, sub_model)) = self.model.enter_string_annotation(string_expr)

View File

@ -495,6 +495,390 @@ class DataProcessor:
assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename"); assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
} }
#[test]
fn rename_match_name_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_name_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:22
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_rest_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_rest_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", *ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:23
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", *ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_as_stmt() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as a<CURSOR>b]:
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_as_binding() {
let test = cursor_test(
r#"
def my_func(command: str):
match command.split():
case ["get", ("a" | "b") as ab]:
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 2 locations)
--> main.py:4:37
|
2 | def my_func(command: str):
3 | match command.split():
4 | case ["get", ("a" | "b") as ab]:
| ^^
5 | x = ab
| --
|
"#);
}
#[test]
fn rename_match_keyword_stmt() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=a<CURSOR>b):
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 2 locations)
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
| --
|
");
}
#[test]
fn rename_match_keyword_binding() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, button=ab):
x = a<CURSOR>b
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 2 locations)
--> main.py:10:30
|
8 | def my_func(event: Click):
9 | match event:
10 | case Click(x, button=ab):
| ^^
11 | x = ab
| --
|
");
}
#[test]
fn rename_match_class_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Cl<CURSOR>ick(x, button=ab):
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @r#"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:7
|
2 | class Click:
| ^^^^^
3 | __match_args__ = ("position", "button")
4 | def __init__(self, pos, btn):
|
::: main.py:8:20
|
6 | self.button: str = btn
7 |
8 | def my_func(event: Click):
| -----
9 | match event:
10 | case Click(x, button=ab):
| -----
11 | x = ab
|
"#);
}
#[test]
fn rename_match_class_field_name() {
let test = cursor_test(
r#"
class Click:
__match_args__ = ("position", "button")
def __init__(self, pos, btn):
self.position: int = pos
self.button: str = btn
def my_func(event: Click):
match event:
case Click(x, but<CURSOR>ton=ab):
x = ab
"#,
);
assert_snapshot!(test.rename("XY"), @"Cannot rename");
}
#[test]
fn rename_typevar_name_stmt() {
let test = cursor_test(
r#"
type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^ -- --
|
");
}
#[test]
fn rename_typevar_name_binding() {
let test = cursor_test(
r#"
type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:13
|
2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
| ^^ -- --
|
");
}
#[test]
fn rename_typevar_spec_stmt() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^ -- --
|
");
}
#[test]
fn rename_typevar_spec_binding() {
let test = cursor_test(
r#"
from typing import Callable
type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:3:15
|
2 | from typing import Callable
3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
| ^^ -- --
|
");
}
#[test]
fn rename_typevar_tuple_stmt() {
let test = cursor_test(
r#"
type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^ -- --
|
");
}
#[test]
fn rename_typevar_tuple_binding() {
let test = cursor_test(
r#"
type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
"#,
);
assert_snapshot!(test.rename("XY"), @r"
info[rename]: Rename symbol (found 3 locations)
--> main.py:2:14
|
2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
| ^^ -- --
|
");
}
#[test] #[test]
fn test_cannot_rename_import_module_component() { fn test_cannot_rename_import_module_component() {
// Test that we cannot rename parts of module names in import statements // Test that we cannot rename parts of module names in import statements

View File

@ -1060,6 +1060,16 @@ impl SourceOrderVisitor<'_> for SemanticTokenVisitor<'_> {
); );
} }
} }
ast::Pattern::MatchStar(pattern_star) => {
// Just the one ident here
if let Some(rest_name) = &pattern_star.name {
self.add_token(
rest_name.range(),
SemanticTokenType::Variable,
SemanticTokenModifier::empty(),
);
}
}
_ => { _ => {
// For all other pattern types, use the default walker // For all other pattern types, use the default walker
ruff_python_ast::visitor::source_order::walk_pattern(self, pattern); ruff_python_ast::visitor::source_order::walk_pattern(self, pattern);
@ -2485,6 +2495,7 @@ def process_data(data):
"rest" @ 154..158: Variable "rest" @ 154..158: Variable
"person" @ 181..187: Variable "person" @ 181..187: Variable
"first" @ 202..207: Variable "first" @ 202..207: Variable
"remaining" @ 210..219: Variable
"sequence" @ 224..232: Variable "sequence" @ 224..232: Variable
"print" @ 246..251: Function "print" @ 246..251: Function
"First: " @ 254..261: String "First: " @ 254..261: String

View File

@ -7,7 +7,7 @@
//! and overloads. //! and overloads.
use crate::docstring::Docstring; use crate::docstring::Docstring;
use crate::goto::DefinitionsOrTargets; use crate::goto::Definitions;
use crate::{Db, find_node::covering_node}; use crate::{Db, find_node::covering_node};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::parsed::parsed_module; use ruff_db::parsed::parsed_module;
@ -214,8 +214,7 @@ fn get_callable_documentation(
db: &dyn crate::Db, db: &dyn crate::Db,
definition: Option<Definition>, definition: Option<Definition>,
) -> Option<Docstring> { ) -> Option<Docstring> {
DefinitionsOrTargets::Definitions(vec![ResolvedDefinition::Definition(definition?)]) Definitions(vec![ResolvedDefinition::Definition(definition?)]).docstring(db)
.docstring(db)
} }
/// Create `ParameterDetails` objects from parameter label offsets. /// Create `ParameterDetails` objects from parameter label offsets.

View File

@ -1064,6 +1064,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
.. ..
}) => (name, &None, default), }) => (name, &None, default),
}; };
self.scopes_by_expression
.record_expression(name, self.current_scope());
let symbol = self.add_symbol(name.id.clone()); let symbol = self.add_symbol(name.id.clone());
// TODO create Definition for PEP 695 typevars // TODO create Definition for PEP 695 typevars
// note that the "bound" on the typevar is a totally different thing than whether // note that the "bound" on the typevar is a totally different thing than whether