Skip to content

Commit c56b8b1

Browse files
author
Catherine Gasnier
committed
Fix bugs with SCIP field enclosing_range
Summary: Test Plan: Reviewers: Subscribers: Tasks: Tags:
1 parent 3d78b3f commit c56b8b1

File tree

2 files changed

+95
-7
lines changed

2 files changed

+95
-7
lines changed

crates/ide/src/static_index.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,42 @@ impl StaticIndex<'_> {
232232
let id = if let Some(it) = self.def_map.get(&def) {
233233
*it
234234
} else {
235-
let it = self.tokens.insert(TokenStaticData {
235+
// For methods/functions, get the actual AST node range to avoid issues where
236+
// full_range incorrectly includes the entire impl block.
237+
let definition_body = match def {
238+
Definition::Function(_) => {
239+
// Get the function's definition position which works reliably
240+
let def_nav = def.try_to_nav(&sema).map(UpmappingResult::call_site);
241+
242+
def_nav.and_then(|nav| {
243+
// Parse the file where the definition is located
244+
// Convert FileId to EditionedFileId for parsing
245+
let editioned_file_id = hir::EditionedFileId::current_edition_guess_origin(self.db, nav.file_id);
246+
let parse = sema.parse(editioned_file_id);
247+
let root = parse.syntax();
248+
249+
// Find the token at the definition position
250+
let token = root.token_at_offset(nav.focus_or_full_range().start()).right_biased()?;
251+
252+
// Walk up to find the function node
253+
let fn_node = token.parent_ancestors().find_map(syntax::ast::Fn::cast)?;
254+
255+
// Use the function node's range
256+
Some(FileRange {
257+
file_id: nav.file_id,
258+
range: fn_node.syntax().text_range(),
259+
})
260+
})
261+
}
262+
_ => {
263+
// For non-functions, use the existing NavigationTarget approach
264+
def.try_to_nav(&sema)
265+
.map(UpmappingResult::call_site)
266+
.map(|it| FileRange { file_id: it.file_id, range: it.full_range })
267+
}
268+
};
269+
270+
let it = self.tokens.insert(TokenStaticData {
236271
documentation: documentation_for_definition(&sema, def, scope_node),
237272
hover: Some(hover_for_definition(
238273
&sema,
@@ -249,10 +284,7 @@ impl StaticIndex<'_> {
249284
definition: def.try_to_nav(&sema).map(UpmappingResult::call_site).map(|it| {
250285
FileRange { file_id: it.file_id, range: it.focus_or_full_range() }
251286
}),
252-
definition_body: def
253-
.try_to_nav(&sema)
254-
.map(UpmappingResult::call_site)
255-
.map(|it| FileRange { file_id: it.file_id, range: it.full_range }),
287+
definition_body,
256288
references: vec![],
257289
moniker: current_crate.and_then(|cc| def_to_moniker(self.db, def, cc)),
258290
display_name: def

crates/rust-analyzer/src/cli/scip.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ impl flags::Scip {
189189
symbol_roles |= scip_types::SymbolRole::Definition as i32;
190190
}
191191

192-
let enclosing_range = match token.definition_body {
193-
Some(def_body) if def_body.file_id == file_id => {
192+
let enclosing_range = match (is_definition, token.definition_body) {
193+
(true, Some(def_body)) if def_body.file_id == file_id => {
194194
text_range_to_scip_range(&line_index, def_body.range)
195195
}
196196
_ => Vec::new(),
@@ -946,4 +946,60 @@ pub mod example_mod {
946946

947947
assert_eq!(token.definition_body, Some(expected_range));
948948
}
949+
950+
#[test]
951+
fn method_in_impl_has_enclosing_range() {
952+
let s = r#"
953+
struct Foo;
954+
impl Foo {
955+
fn method(&self) {}
956+
}
957+
"#;
958+
959+
let mut host = AnalysisHost::default();
960+
let change_fixture = ChangeFixture::parse(s);
961+
host.raw_database_mut().apply_change(change_fixture.change);
962+
963+
let analysis = host.analysis();
964+
let si = StaticIndex::compute(
965+
&analysis,
966+
VendoredLibrariesConfig::Included {
967+
workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
968+
},
969+
);
970+
971+
let file = si.files.first().unwrap();
972+
// Find the definition token for `method` - it should have a definition_body
973+
let method_token = file.tokens.iter().find_map(|(_range, id)| {
974+
let token = si.tokens.get(*id).unwrap();
975+
// Check if this is the method definition by looking at the display name and kind
976+
if token.display_name.as_deref() == Some("method")
977+
&& token.kind == SymbolInformationKind::Method
978+
{
979+
Some(id)
980+
} else {
981+
None
982+
}
983+
}).expect("Should find method token");
984+
985+
let token = si.tokens.get(*method_token).unwrap();
986+
987+
// The definition_body should exist and should be just the method, not the entire impl block
988+
let def_body = token.definition_body.expect("Method should have a definition_body");
989+
990+
// The definition_body should cover just the method
991+
// Based on the test output, we can see the actual range is 27..46
992+
// Let's verify it's not the entire impl block (which would be larger)
993+
let impl_start = s.find("impl Foo").unwrap();
994+
let impl_end = s.rfind('}').unwrap() + 1;
995+
let impl_range = TextRange::new((impl_start as u32).into(), (impl_end as u32).into());
996+
997+
// The method body should NOT be the same as the entire impl block
998+
assert_ne!(def_body.range, impl_range,
999+
"Method enclosing range should not be the entire impl block");
1000+
1001+
// The method body should be smaller than the impl block
1002+
assert!(def_body.range.len() < impl_range.len(),
1003+
"Method enclosing range should be smaller than the impl block");
1004+
}
9491005
}

0 commit comments

Comments
 (0)