Skip to content

Commit c534bfa

Browse files
authored
[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
1 parent 5e1b2ee commit c534bfa

File tree

12 files changed

+2329
-113
lines changed

12 files changed

+2329
-113
lines changed

crates/ty_ide/src/completion.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use ty_python_semantic::{
1717

1818
use crate::docstring::Docstring;
1919
use crate::find_node::covering_node;
20-
use crate::goto::DefinitionsOrTargets;
20+
use crate::goto::Definitions;
2121
use crate::importer::{ImportRequest, Importer};
2222
use crate::symbols::QueryPattern;
2323
use crate::{Db, all_symbols};
@@ -220,9 +220,7 @@ impl<'db> Completion<'db> {
220220
db: &'db dyn Db,
221221
semantic: SemanticCompletion<'db>,
222222
) -> Completion<'db> {
223-
let definition = semantic
224-
.ty
225-
.and_then(|ty| DefinitionsOrTargets::from_ty(db, ty));
223+
let definition = semantic.ty.and_then(|ty| Definitions::from_ty(db, ty));
226224
let documentation = definition.and_then(|def| def.docstring(db));
227225
let is_type_check_only = semantic.is_type_check_only(db);
228226
Completion {

crates/ty_ide/src/goto.rs

Lines changed: 95 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -212,16 +212,9 @@ pub(crate) enum GotoTarget<'a> {
212212

213213
/// The resolved definitions for a `GotoTarget`
214214
#[derive(Debug, Clone)]
215-
pub(crate) enum DefinitionsOrTargets<'db> {
216-
/// We computed actual Definitions we can do followup queries on.
217-
Definitions(Vec<ResolvedDefinition<'db>>),
218-
/// We directly computed a navigation.
219-
///
220-
/// We can't get docs or usefully compute goto-definition for this.
221-
Targets(crate::NavigationTargets),
222-
}
215+
pub(crate) struct Definitions<'db>(pub Vec<ResolvedDefinition<'db>>);
223216

224-
impl<'db> DefinitionsOrTargets<'db> {
217+
impl<'db> Definitions<'db> {
225218
pub(crate) fn from_ty(db: &'db dyn crate::Db, ty: Type<'db>) -> Option<Self> {
226219
let ty_def = ty.definition(db)?;
227220
let resolved = match ty_def {
@@ -237,7 +230,7 @@ impl<'db> DefinitionsOrTargets<'db> {
237230
ResolvedDefinition::Definition(definition)
238231
}
239232
};
240-
Some(DefinitionsOrTargets::Definitions(vec![resolved]))
233+
Some(Definitions(vec![resolved]))
241234
}
242235

243236
/// Get the "goto-declaration" interpretation of this definition
@@ -247,12 +240,7 @@ impl<'db> DefinitionsOrTargets<'db> {
247240
self,
248241
db: &'db dyn ty_python_semantic::Db,
249242
) -> Option<crate::NavigationTargets> {
250-
match self {
251-
DefinitionsOrTargets::Definitions(definitions) => {
252-
definitions_to_navigation_targets(db, None, definitions)
253-
}
254-
DefinitionsOrTargets::Targets(targets) => Some(targets),
255-
}
243+
definitions_to_navigation_targets(db, None, self.0)
256244
}
257245

258246
/// Get the "goto-definition" interpretation of this definition
@@ -263,12 +251,7 @@ impl<'db> DefinitionsOrTargets<'db> {
263251
self,
264252
db: &'db dyn ty_python_semantic::Db,
265253
) -> Option<crate::NavigationTargets> {
266-
match self {
267-
DefinitionsOrTargets::Definitions(definitions) => {
268-
definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), definitions)
269-
}
270-
DefinitionsOrTargets::Targets(targets) => Some(targets),
271-
}
254+
definitions_to_navigation_targets(db, Some(&StubMapper::new(db)), self.0)
272255
}
273256

274257
/// Get the docstring for this definition
@@ -277,13 +260,7 @@ impl<'db> DefinitionsOrTargets<'db> {
277260
/// so this will check both the goto-declarations and goto-definitions (in that order)
278261
/// and return the first one found.
279262
pub(crate) fn docstring(self, db: &'db dyn crate::Db) -> Option<Docstring> {
280-
let definitions = match self {
281-
DefinitionsOrTargets::Definitions(definitions) => definitions,
282-
// Can't find docs for these
283-
// (make more cases DefinitionOrTargets::Definitions to get more docs!)
284-
DefinitionsOrTargets::Targets(_) => return None,
285-
};
286-
for definition in &definitions {
263+
for definition in &self.0 {
287264
// If we got a docstring from the original definition, use it
288265
if let Some(docstring) = definition.docstring(db) {
289266
return Some(Docstring::new(docstring));
@@ -296,7 +273,7 @@ impl<'db> DefinitionsOrTargets<'db> {
296273
let stub_mapper = StubMapper::new(db);
297274

298275
// Try to find the corresponding implementation definition
299-
for definition in stub_mapper.map_definitions(definitions) {
276+
for definition in stub_mapper.map_definitions(self.0) {
300277
if let Some(docstring) = definition.docstring(db) {
301278
return Some(Docstring::new(docstring));
302279
}
@@ -399,36 +376,32 @@ impl GotoTarget<'_> {
399376
&self,
400377
model: &SemanticModel<'db>,
401378
alias_resolution: ImportAliasResolution,
402-
) -> Option<DefinitionsOrTargets<'db>> {
403-
use crate::NavigationTarget;
404-
match self {
405-
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression)
406-
.map(DefinitionsOrTargets::Definitions),
379+
) -> Option<Definitions<'db>> {
380+
let definitions = match self {
381+
GotoTarget::Expression(expression) => definitions_for_expression(model, *expression),
407382
// For already-defined symbols, they are their own definitions
408-
GotoTarget::FunctionDef(function) => Some(DefinitionsOrTargets::Definitions(vec![
409-
ResolvedDefinition::Definition(function.definition(model)),
410-
])),
383+
GotoTarget::FunctionDef(function) => Some(vec![ResolvedDefinition::Definition(
384+
function.definition(model),
385+
)]),
411386

412-
GotoTarget::ClassDef(class) => Some(DefinitionsOrTargets::Definitions(vec![
413-
ResolvedDefinition::Definition(class.definition(model)),
414-
])),
387+
GotoTarget::ClassDef(class) => Some(vec![ResolvedDefinition::Definition(
388+
class.definition(model),
389+
)]),
415390

416-
GotoTarget::Parameter(parameter) => Some(DefinitionsOrTargets::Definitions(vec![
417-
ResolvedDefinition::Definition(parameter.definition(model)),
418-
])),
391+
GotoTarget::Parameter(parameter) => Some(vec![ResolvedDefinition::Definition(
392+
parameter.definition(model),
393+
)]),
419394

420395
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
421396
GotoTarget::ImportSymbolAlias {
422397
alias, import_from, ..
423398
} => {
424399
let symbol_name = alias.name.as_str();
425-
Some(DefinitionsOrTargets::Definitions(
426-
definitions_for_imported_symbol(
427-
model,
428-
import_from,
429-
symbol_name,
430-
alias_resolution,
431-
),
400+
Some(definitions_for_imported_symbol(
401+
model,
402+
import_from,
403+
symbol_name,
404+
alias_resolution,
432405
))
433406
}
434407

@@ -448,60 +421,54 @@ impl GotoTarget<'_> {
448421
if alias_resolution == ImportAliasResolution::ResolveAliases {
449422
definitions_for_module(model, Some(alias.name.as_str()), 0)
450423
} else {
451-
let alias_range = alias.asname.as_ref().unwrap().range;
452-
Some(DefinitionsOrTargets::Targets(
453-
crate::NavigationTargets::single(NavigationTarget {
454-
file: model.file(),
455-
focus_range: alias_range,
456-
full_range: alias.range(),
457-
}),
458-
))
424+
alias.asname.as_ref().map(|name| {
425+
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
426+
})
459427
}
460428
}
461429

462430
// Handle keyword arguments in call expressions
463431
GotoTarget::KeywordArgument {
464432
keyword,
465433
call_expression,
466-
} => Some(DefinitionsOrTargets::Definitions(
467-
definitions_for_keyword_argument(model, keyword, call_expression),
434+
} => Some(definitions_for_keyword_argument(
435+
model,
436+
keyword,
437+
call_expression,
468438
)),
469439

470440
// For exception variables, they are their own definitions (like parameters)
471441
GotoTarget::ExceptVariable(except_handler) => {
472-
Some(DefinitionsOrTargets::Definitions(vec![
473-
ResolvedDefinition::Definition(except_handler.definition(model)),
474-
]))
442+
Some(vec![ResolvedDefinition::Definition(
443+
except_handler.definition(model),
444+
)])
475445
}
476446

477-
// For pattern match rest variables, they are their own definitions
447+
// Patterns are glorified assignments but we have to look them up by ident
448+
// because they're not expressions
478449
GotoTarget::PatternMatchRest(pattern_mapping) => {
479-
if let Some(rest_name) = &pattern_mapping.rest {
480-
let range = rest_name.range;
481-
Some(DefinitionsOrTargets::Targets(
482-
crate::NavigationTargets::single(NavigationTarget::new(
483-
model.file(),
484-
range,
485-
)),
486-
))
487-
} else {
488-
None
489-
}
450+
pattern_mapping.rest.as_ref().map(|name| {
451+
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
452+
})
490453
}
491454

492-
// For pattern match as names, they are their own definitions
493-
GotoTarget::PatternMatchAsName(pattern_as) => {
494-
if let Some(name) = &pattern_as.name {
495-
let range = name.range;
496-
Some(DefinitionsOrTargets::Targets(
497-
crate::NavigationTargets::single(NavigationTarget::new(
498-
model.file(),
499-
range,
500-
)),
501-
))
502-
} else {
503-
None
504-
}
455+
GotoTarget::PatternMatchAsName(pattern_as) => pattern_as.name.as_ref().map(|name| {
456+
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
457+
}),
458+
459+
GotoTarget::PatternKeywordArgument(pattern_keyword) => {
460+
let name = &pattern_keyword.attr;
461+
Some(definitions_for_name(
462+
model,
463+
name.as_str(),
464+
AnyNodeRef::Identifier(name),
465+
))
466+
}
467+
468+
GotoTarget::PatternMatchStarName(pattern_star) => {
469+
pattern_star.name.as_ref().map(|name| {
470+
definitions_for_name(model, name.as_str(), AnyNodeRef::Identifier(name))
471+
})
505472
}
506473

507474
// For callables, both the definition of the callable and the actual function impl are relevant.
@@ -516,22 +483,22 @@ impl GotoTarget<'_> {
516483
if definitions.is_empty() {
517484
None
518485
} else {
519-
Some(DefinitionsOrTargets::Definitions(definitions))
486+
Some(definitions)
520487
}
521488
}
522489

523490
GotoTarget::BinOp { expression, .. } => {
524491
let (definitions, _) =
525492
ty_python_semantic::definitions_for_bin_op(model, expression)?;
526493

527-
Some(DefinitionsOrTargets::Definitions(definitions))
494+
Some(definitions)
528495
}
529496

530497
GotoTarget::UnaryOp { expression, .. } => {
531498
let (definitions, _) =
532499
ty_python_semantic::definitions_for_unary_op(model, expression)?;
533500

534-
Some(DefinitionsOrTargets::Definitions(definitions))
501+
Some(definitions)
535502
}
536503

537504
// String annotations sub-expressions require us to recurse into the sub-AST
@@ -545,23 +512,47 @@ impl GotoTarget<'_> {
545512
.node()
546513
.as_expr_ref()?;
547514
definitions_for_expression(&submodel, subexpr)
548-
.map(DefinitionsOrTargets::Definitions)
549515
}
516+
517+
// nonlocal and global are essentially loads, but again they're statements,
518+
// so we need to look them up by ident
550519
GotoTarget::NonLocal { identifier } | GotoTarget::Globals { identifier } => {
551-
Some(DefinitionsOrTargets::Definitions(definitions_for_name(
520+
Some(definitions_for_name(
552521
model,
553522
identifier.as_str(),
554523
AnyNodeRef::Identifier(identifier),
555-
)))
524+
))
556525
}
557526

558-
// TODO: implement these
559-
GotoTarget::PatternKeywordArgument(..)
560-
| GotoTarget::PatternMatchStarName(..)
561-
| GotoTarget::TypeParamTypeVarName(..)
562-
| GotoTarget::TypeParamParamSpecName(..)
563-
| GotoTarget::TypeParamTypeVarTupleName(..) => None,
564-
}
527+
// These are declarations of sorts, but they're stmts and not exprs, so look up by ident.
528+
GotoTarget::TypeParamTypeVarName(type_var) => {
529+
let name = &type_var.name;
530+
Some(definitions_for_name(
531+
model,
532+
name.as_str(),
533+
AnyNodeRef::Identifier(name),
534+
))
535+
}
536+
537+
GotoTarget::TypeParamParamSpecName(name) => {
538+
let name = &name.name;
539+
Some(definitions_for_name(
540+
model,
541+
name.as_str(),
542+
AnyNodeRef::Identifier(name),
543+
))
544+
}
545+
546+
GotoTarget::TypeParamTypeVarTupleName(name) => {
547+
let name = &name.name;
548+
Some(definitions_for_name(
549+
model,
550+
name.as_str(),
551+
AnyNodeRef::Identifier(name),
552+
))
553+
}
554+
};
555+
definitions.map(Definitions)
565556
}
566557

567558
/// Returns the text representation of this goto target.
@@ -1050,12 +1041,10 @@ fn definitions_for_module<'db>(
10501041
model: &SemanticModel<'db>,
10511042
module: Option<&str>,
10521043
level: u32,
1053-
) -> Option<DefinitionsOrTargets<'db>> {
1044+
) -> Option<Vec<ResolvedDefinition<'db>>> {
10541045
let module = model.resolve_module(module, level)?;
10551046
let file = module.file(model.db())?;
1056-
Some(DefinitionsOrTargets::Definitions(vec![
1057-
ResolvedDefinition::Module(file),
1058-
]))
1047+
Some(vec![ResolvedDefinition::Module(file)])
10591048
}
10601049

10611050
/// Helper function to extract module component information from a dotted module name

0 commit comments

Comments
 (0)