From 3738ab1c46197650bdfda349db5e546f634361d9 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Mon, 1 Dec 2025 17:53:45 +0100 Subject: [PATCH] [ty] Fix find references for type defined in stub (#21732) --- crates/ty_ide/src/find_references.rs | 66 ++++++++++++++++++++++++++++ crates/ty_ide/src/references.rs | 17 +++---- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/crates/ty_ide/src/find_references.rs b/crates/ty_ide/src/find_references.rs index 856e1b8b2c..a6b60bc6c2 100644 --- a/crates/ty_ide/src/find_references.rs +++ b/crates/ty_ide/src/find_references.rs @@ -1656,4 +1656,70 @@ func_alias() | "); } + + #[test] + fn stub_target() { + let test = CursorTest::builder() + .source( + "path.pyi", + r#" + class Path: + def __init__(self, path: str): ... + "#, + ) + .source( + "path.py", + r#" + class Path: + def __init__(self, path: str): + self.path = path + "#, + ) + .source( + "importer.py", + r#" + from path import Path + + a: Path = Path("test") + "#, + ) + .build(); + + assert_snapshot!(test.references(), @r###" + info[references]: Reference 1 + --> path.pyi:2:7 + | + 2 | class Path: + | ^^^^ + 3 | def __init__(self, path: str): ... + | + + info[references]: Reference 2 + --> importer.py:2:18 + | + 2 | from path import Path + | ^^^^ + 3 | + 4 | a: Path = Path("test") + | + + info[references]: Reference 3 + --> importer.py:4:4 + | + 2 | from path import Path + 3 | + 4 | a: Path = Path("test") + | ^^^^ + | + + info[references]: Reference 4 + --> importer.py:4:11 + | + 2 | from path import Path + 3 | + 4 | a: Path = Path("test") + | ^^^^ + | + "###); + } } diff --git a/crates/ty_ide/src/references.rs b/crates/ty_ide/src/references.rs index 79d111155a..27f1a3f2cb 100644 --- a/crates/ty_ide/src/references.rs +++ b/crates/ty_ide/src/references.rs @@ -12,7 +12,7 @@ use crate::find_node::CoveringNode; use crate::goto::GotoTarget; -use crate::{Db, NavigationTarget, ReferenceKind, ReferenceTarget}; +use crate::{Db, NavigationTargets, ReferenceKind, ReferenceTarget}; use ruff_db::files::File; use ruff_python_ast::{ self as ast, AnyNodeRef, @@ -49,10 +49,9 @@ pub(crate) fn references( // When finding references, do not resolve any local aliases. let model = SemanticModel::new(db, file); - let target_definitions_nav = goto_target + let target_definitions = goto_target .get_definition_targets(&model, ImportAliasResolution::PreserveAliases)? - .definition_targets(db)?; - let target_definitions: Vec = target_definitions_nav.into_iter().collect(); + .declaration_targets(db)?; // Extract the target text from the goto target for fast comparison let target_text = goto_target.to_string()?; @@ -115,7 +114,7 @@ pub(crate) fn references( fn references_for_file( db: &dyn Db, file: File, - target_definitions: &[NavigationTarget], + target_definitions: &NavigationTargets, target_text: &str, mode: ReferencesMode, references: &mut Vec, @@ -159,7 +158,7 @@ fn is_symbol_externally_visible(goto_target: &GotoTarget<'_>) -> bool { struct LocalReferencesFinder<'a> { model: &'a SemanticModel<'a>, tokens: &'a Tokens, - target_definitions: &'a [NavigationTarget], + target_definitions: &'a NavigationTargets, references: &'a mut Vec, mode: ReferencesMode, target_text: &'a str, @@ -318,12 +317,10 @@ impl LocalReferencesFinder<'_> { GotoTarget::from_covering_node(self.model, covering_node, offset, self.tokens) { // Get the definitions for this goto target - if let Some(current_definitions_nav) = goto_target + if let Some(current_definitions) = goto_target .get_definition_targets(self.model, ImportAliasResolution::PreserveAliases) .and_then(|definitions| definitions.declaration_targets(self.model.db())) { - let current_definitions: Vec = - current_definitions_nav.into_iter().collect(); // Check if any of the current definitions match our target definitions if self.navigation_targets_match(¤t_definitions) { // Determine if this is a read or write reference @@ -337,7 +334,7 @@ impl LocalReferencesFinder<'_> { } /// Check if `Vec` match our target definitions - fn navigation_targets_match(&self, current_targets: &[NavigationTarget]) -> bool { + fn navigation_targets_match(&self, current_targets: &NavigationTargets) -> bool { // Since we're comparing the same symbol, all definitions should be equivalent // We only need to check against the first target definition if let Some(first_target) = self.target_definitions.iter().next() {