Skip to content

Commit b37e65e

Browse files
committed
Move uses_of() to the index
1 parent 10a8ec1 commit b37e65e

5 files changed

Lines changed: 38 additions & 31 deletions

File tree

crates/oak_db/src/identifier.rs

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ use crate::Name;
1919
#[derive(Debug, Clone)]
2020
pub enum Identifier<'db> {
2121
/// Cursor on a binding use or definition site tracked by the semantic
22-
/// index. The range is the use or def site's text range. Its start is
23-
/// the offset to pass to `resolve_at`.
22+
/// index. The range is the use or def site's text range.
2423
Variable { name: Name<'db>, range: TextRange },
2524
/// Cursor on the RHS name of a `$` or `@` extract expression. Member
2625
/// names are not tracked by the semantic index.
2726
Member {
28-
name: String,
27+
name: Name<'db>,
2928
kind: MemberKind,
3029
operator_range: TextRange,
3130
name_range: TextRange,
@@ -71,7 +70,7 @@ impl<'db> Identifier<'db> {
7170

7271
if let Some((name, kind, operator_range, name_range)) = classify_member(&root, snapped) {
7372
return Some(Identifier::Member {
74-
name,
73+
name: Name::new(db, name.as_str()),
7574
kind,
7675
operator_range,
7776
name_range,
@@ -84,28 +83,8 @@ impl<'db> Identifier<'db> {
8483

8584
impl<'db> File {
8685
/// All use-site ranges for `name` in this file, across every scope.
87-
///
88-
/// Used as the candidate pool for find-references: each returned range is
89-
/// confirmed by calling `resolve_at(range.start())` and checking whether
90-
/// its definition set intersects the target.
9186
pub fn uses_of(self, db: &'db dyn Db, name: Name<'db>) -> Vec<TextRange> {
92-
let index = self.semantic_index(db);
93-
let name_str = name.text(db);
94-
let mut ranges = Vec::new();
95-
96-
for scope_id in index.scope_ids() {
97-
let symbols = index.symbols(scope_id);
98-
let Some(symbol_id) = symbols.id(name_str) else {
99-
continue;
100-
};
101-
for (_use_id, use_site) in index.uses(scope_id).iter() {
102-
if use_site.symbol() == symbol_id {
103-
ranges.push(use_site.range());
104-
}
105-
}
106-
}
107-
108-
ranges
87+
self.semantic_index(db).uses_of(name.text(db).as_str())
10988
}
11089

11190
/// All ranges where `name` appears as the RHS of a `$` or `@` with the
@@ -141,6 +120,9 @@ fn classify_member(
141120
root: &RSyntaxNode,
142121
offset: TextSize,
143122
) -> Option<(String, MemberKind, TextRange, TextRange)> {
123+
// Snapping put `offset` at the name's start, which is the operator's end,
124+
// so `token_at_offset()` reports `Between(op, name)`. Take the name on the
125+
// right side.
144126
let token = root.token_at_offset(offset).right_biased()?;
145127
if !is_name_token(&token) {
146128
return None;
@@ -150,8 +132,7 @@ fn classify_member(
150132
let selector_node = token.parent()?;
151133
let extract = RExtractExpression::cast(selector_node.parent()?)?;
152134

153-
// Token is after the operator -> it's on the RHS, not LHS.
154-
// RHS start == op end (adjacent bytes), so use strict <.
135+
// Token is after the operator -> it's on the RHS, not LHS
155136
let op = extract.operator().ok()?;
156137
if token.text_trimmed_range().start() < op.text_trimmed_range().end() {
157138
return None;
@@ -201,6 +182,9 @@ fn snap_to_name_at_boundary(root: &RSyntaxNode, offset: TextSize) -> TextSize {
201182
}
202183

203184
fn is_name_token(token: &RSyntaxToken) -> bool {
185+
// FIXME: Right now we're too liberal with strings. We should only match
186+
// them when they stand for an identifier, e.g. in the LHS of `<-` or in
187+
// function call position.
204188
matches!(
205189
token.kind(),
206190
RSyntaxKind::IDENT | RSyntaxKind::R_STRING_LITERAL

crates/oak_db/src/tests/identifier.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ fn test_classify_on_dollar_member() {
102102
operator_range,
103103
name_range,
104104
}) => {
105-
assert_eq!(name, "bar");
105+
assert_eq!(name.text(&db).as_str(), "bar");
106106
assert_eq!(kind, MemberKind::Dollar);
107107
assert_eq!(operator_range, text_range(3, 4));
108108
assert_eq!(name_range, text_range(4, 7));
@@ -119,7 +119,7 @@ fn test_classify_on_at_member() {
119119

120120
match Identifier::classify(&db, file, offset(4)) {
121121
Some(Identifier::Member { name, kind, .. }) => {
122-
assert_eq!(name, "bar");
122+
assert_eq!(name.text(&db).as_str(), "bar");
123123
assert_eq!(kind, MemberKind::At);
124124
},
125125
other => panic!("expected Member, got {other:?}"),

crates/oak_ide/src/find_references.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ pub fn find_references(
3030
Identifier::Variable { range, .. } => {
3131
find_variable_references(db, file, range.start(), include_declaration)
3232
},
33-
Identifier::Member { name, kind, .. } => find_member_references(db, file, &name, kind),
33+
Identifier::Member { name, kind, .. } => {
34+
find_member_references(db, file, name.text(db).as_str(), kind)
35+
},
3436
}
3537
}
3638

@@ -56,6 +58,11 @@ fn find_variable_references(
5658
all_matching_files(db, name.text(db).as_str())
5759
};
5860

61+
// Rust-Analyzer does a pure text search across all files, then resolves
62+
// each occurrences. We are more aligned with ty: we filter files by a text
63+
// search, but then we walk a post-parse tree. ty walks a raw AST, we walk
64+
// the index via `uses_of()`. The latter is more to the point.
65+
5966
// A candidate is a reference when it resolves to the same binding as the cursor.
6067
let target_set: HashSet<Definition<'_>> = target_defs.iter().copied().collect();
6168

crates/oak_ide/tests/integration/rename.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fn test_rename_non_renamable_errors() {
139139
let file = upsert(&mut db, "test.R", "dplyr::mutate\n");
140140

141141
let err = rename(&db, file, offset(7), "x").unwrap_err();
142-
assert!(err.to_string().contains("renamable identifier"));
142+
assert!(err.to_string().contains("Can't rename identifier"));
143143
}
144144

145145
#[test]

crates/oak_semantic/src/semantic_index.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,22 @@ impl SemanticIndex {
188188
Some((scope, id, use_site))
189189
}
190190

191+
/// All use-site ranges for `name`, across every scope in the file.
192+
pub fn uses_of(&self, name: &str) -> Vec<TextRange> {
193+
let mut ranges = Vec::new();
194+
for scope_id in self.scope_ids() {
195+
let Some(symbol_id) = self.symbols(scope_id).id(name) else {
196+
continue;
197+
};
198+
for (_use_id, use_site) in self.uses(scope_id).iter() {
199+
if use_site.symbol() == symbol_id {
200+
ranges.push(use_site.range());
201+
}
202+
}
203+
}
204+
ranges
205+
}
206+
191207
/// Iterate direct child scopes of `scope`.
192208
pub fn child_scope_ids(&self, scope_id: ScopeId) -> ChildScopeIdsIter<'_> {
193209
let descendants = &self.scopes[scope_id].descendants;

0 commit comments

Comments
 (0)