Skip to content

Commit ea66bbb

Browse files
committed
Handle instance variable references
1 parent baf8023 commit ea66bbb

9 files changed

Lines changed: 734 additions & 159 deletions

File tree

rust/rubydex/src/indexing/local_graph.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use crate::model::definitions::Definition;
55
use crate::model::document::Document;
66
use crate::model::graph::NameDependent;
77
use crate::model::identity_maps::IdentityHashMap;
8-
use crate::model::ids::{ConstantReferenceId, DefinitionId, MethodReferenceId, NameId, StringId, UriId};
8+
use crate::model::ids::{
9+
ConstantReferenceId, DefinitionId, InstanceVariableReferenceId, MethodReferenceId, NameId, StringId, UriId,
10+
};
911
use crate::model::name::{Name, NameRef};
10-
use crate::model::references::{ConstantReference, MethodRef};
12+
use crate::model::references::{ConstantReference, InstanceVariableRef, InstanceVariableReference, MethodRef};
1113
use crate::model::string_ref::StringRef;
1214
use crate::offset::Offset;
1315

@@ -19,6 +21,7 @@ type LocalGraphParts = (
1921
IdentityHashMap<NameId, NameRef>,
2022
IdentityHashMap<ConstantReferenceId, ConstantReference>,
2123
IdentityHashMap<MethodReferenceId, MethodRef>,
24+
IdentityHashMap<InstanceVariableReferenceId, InstanceVariableReference>,
2225
IdentityHashMap<NameId, Vec<NameDependent>>,
2326
);
2427

@@ -31,6 +34,7 @@ pub struct LocalGraph {
3134
names: IdentityHashMap<NameId, NameRef>,
3235
constant_references: IdentityHashMap<ConstantReferenceId, ConstantReference>,
3336
method_references: IdentityHashMap<MethodReferenceId, MethodRef>,
37+
instance_variable_references: IdentityHashMap<InstanceVariableReferenceId, InstanceVariableReference>,
3438
name_dependents: IdentityHashMap<NameId, Vec<NameDependent>>,
3539
}
3640

@@ -45,6 +49,7 @@ impl LocalGraph {
4549
names: IdentityHashMap::default(),
4650
constant_references: IdentityHashMap::default(),
4751
method_references: IdentityHashMap::default(),
52+
instance_variable_references: IdentityHashMap::default(),
4853
name_dependents: IdentityHashMap::default(),
4954
}
5055
}
@@ -187,6 +192,27 @@ impl LocalGraph {
187192
reference_id
188193
}
189194

195+
// Instance variable references
196+
197+
#[must_use]
198+
pub fn instance_variable_references(
199+
&self,
200+
) -> &IdentityHashMap<InstanceVariableReferenceId, InstanceVariableReference> {
201+
&self.instance_variable_references
202+
}
203+
204+
pub fn add_instance_variable_reference(&mut self, reference: InstanceVariableRef) -> InstanceVariableReferenceId {
205+
let reference_id = reference.id();
206+
let entry = InstanceVariableReference::Unresolved(Box::new(reference));
207+
208+
if self.instance_variable_references.insert(reference_id, entry).is_some() {
209+
debug_assert!(false, "InstanceVariableReferenceId collision in local graph");
210+
}
211+
212+
self.document.add_instance_variable_reference(reference_id);
213+
reference_id
214+
}
215+
190216
// Diagnostics
191217

192218
#[must_use]
@@ -218,6 +244,7 @@ impl LocalGraph {
218244
self.names,
219245
self.constant_references,
220246
self.method_references,
247+
self.instance_variable_references,
221248
self.name_dependents,
222249
)
223250
}

rust/rubydex/src/indexing/ruby_indexer.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::model::definitions::{
1313
use crate::model::document::Document;
1414
use crate::model::ids::{DefinitionId, NameId, StringId, UriId};
1515
use crate::model::name::{Name, ParentScope};
16-
use crate::model::references::{ConstantReference, MethodRef};
16+
use crate::model::references::{ConstantReference, InstanceVariableRef, MethodRef};
1717
use crate::model::visibility::Visibility;
1818
use crate::offset::Offset;
1919

@@ -557,6 +557,15 @@ impl<'a> RubyIndexer<'a> {
557557
definition_id
558558
}
559559

560+
fn add_instance_variable_reference(&mut self, location: &ruby_prism::Location) {
561+
let name = Self::location_to_string(location);
562+
let str_id = self.local_graph.intern_string(name);
563+
let offset = Offset::from_prism_location(location);
564+
let parent_nesting_id = self.parent_nesting_id();
565+
let reference = InstanceVariableRef::new(str_id, self.uri_id, offset, parent_nesting_id);
566+
self.local_graph.add_instance_variable_reference(reference);
567+
}
568+
560569
/// Adds a class variable definition.
561570
///
562571
/// Class variables use lexical scoping - they belong to the lexically enclosing class/module,
@@ -2196,7 +2205,7 @@ impl Visit<'_> for RubyIndexer<'_> {
21962205
}
21972206

21982207
fn visit_instance_variable_operator_write_node(&mut self, node: &ruby_prism::InstanceVariableOperatorWriteNode) {
2199-
self.add_instance_variable_definition(&node.name_loc());
2208+
self.add_instance_variable_reference(&node.name_loc());
22002209
self.visit(&node.value());
22012210
}
22022211

@@ -2210,6 +2219,10 @@ impl Visit<'_> for RubyIndexer<'_> {
22102219
self.visit(&node.value());
22112220
}
22122221

2222+
fn visit_instance_variable_read_node(&mut self, node: &ruby_prism::InstanceVariableReadNode) {
2223+
self.add_instance_variable_reference(&node.location());
2224+
}
2225+
22132226
fn visit_class_variable_and_write_node(&mut self, node: &ruby_prism::ClassVariableAndWriteNode) {
22142227
self.add_class_variable_definition(&node.name_loc());
22152228
self.visit(&node.value());

rust/rubydex/src/indexing/ruby_indexer_tests.rs

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,34 @@ macro_rules! assert_not_promotable {
103103
}};
104104
}
105105

106+
macro_rules! assert_instance_variable_references_eq {
107+
($context:expr, $expected_names:expr) => {{
108+
let mut actual_references = $context
109+
.graph()
110+
.instance_variable_references()
111+
.values()
112+
.map(|r| {
113+
(
114+
r.offset().start(),
115+
$context.graph().strings().get(r.str_id()).unwrap().as_str(),
116+
)
117+
})
118+
.collect::<Vec<_>>();
119+
120+
actual_references.sort();
121+
122+
let actual_names = actual_references.iter().map(|(_, name)| *name).collect::<Vec<_>>();
123+
124+
assert_eq!(
125+
$expected_names,
126+
actual_names.as_slice(),
127+
"instance variable references mismatch: expected `{:?}`, got `{:?}`",
128+
$expected_names,
129+
actual_names
130+
);
131+
}};
132+
}
133+
106134
fn index_source(source: &str) -> LocalGraphTest {
107135
LocalGraphTest::new("file:///foo.rb", source)
108136
}
@@ -1801,6 +1829,38 @@ fn constant_with_colon_colon_call_is_promotable() {
18011829
});
18021830
}
18031831

1832+
#[test]
1833+
fn instance_variable_reads_are_indexed_as_references() {
1834+
let context = index_source({
1835+
"
1836+
class Foo
1837+
def bar
1838+
@baz
1839+
end
1840+
end
1841+
"
1842+
});
1843+
1844+
assert_no_local_diagnostics!(&context);
1845+
assert_instance_variable_references_eq!(context, ["@baz"]);
1846+
}
1847+
1848+
#[test]
1849+
fn instance_variable_operator_writes_are_references() {
1850+
let context = index_source({
1851+
"
1852+
class Foo
1853+
def bar
1854+
@baz += 1
1855+
end
1856+
end
1857+
"
1858+
});
1859+
1860+
assert_no_local_diagnostics!(&context);
1861+
assert_instance_variable_references_eq!(context, ["@baz"]);
1862+
}
1863+
18041864
mod constant_tests {
18051865
use super::*;
18061866

@@ -2114,11 +2174,6 @@ mod variable_tests {
21142174
});
21152175
});
21162176

2117-
assert_definition_at!(&context, "8:1-8:5", InstanceVariable, |def| {
2118-
assert_def_str_eq!(&context, def, "@bar");
2119-
assert!(def.lexical_nesting_id().is_none());
2120-
});
2121-
21222177
assert_definition_at!(&context, "9:1-9:5", InstanceVariable, |def| {
21232178
assert_def_str_eq!(&context, def, "@baz");
21242179
assert!(def.lexical_nesting_id().is_none());
@@ -2130,24 +2185,20 @@ mod variable_tests {
21302185
});
21312186

21322187
assert_definition_at!(&context, "12:1-16:4", Class, |bar_class_def| {
2133-
assert_definition_at!(&context, "13:3-13:7", InstanceVariable, |def| {
2134-
assert_def_str_eq!(&context, def, "@foo");
2135-
assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2136-
assert_eq!(bar_class_def.members()[0], def.id());
2137-
});
2138-
21392188
assert_definition_at!(&context, "14:3-14:7", InstanceVariable, |def| {
21402189
assert_def_str_eq!(&context, def, "@bar");
21412190
assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2142-
assert_eq!(bar_class_def.members()[1], def.id());
2191+
assert_eq!(bar_class_def.members()[0], def.id());
21432192
});
21442193

21452194
assert_definition_at!(&context, "15:3-15:7", InstanceVariable, |def| {
21462195
assert_def_str_eq!(&context, def, "@baz");
21472196
assert_eq!(bar_class_def.id(), def.lexical_nesting_id().unwrap());
2148-
assert_eq!(bar_class_def.members()[2], def.id());
2197+
assert_eq!(bar_class_def.members()[1], def.id());
21492198
});
21502199
});
2200+
2201+
assert_instance_variable_references_eq!(context, ["@bar", "@foo"]);
21512202
}
21522203

21532204
#[test]

rust/rubydex/src/model/declaration.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,43 @@ impl Declaration {
448448
_ => unreachable!("Cannot remove constant reference from {} declaration", self.kind()),
449449
}
450450
}
451+
452+
/// Returns the instance variable reference IDs for `InstanceVariable` declarations.
453+
/// Returns `None` for other declaration types.
454+
#[must_use]
455+
pub fn instance_variable_references(&self) -> Option<&IdentityHashSet<InstanceVariableReferenceId>> {
456+
match self {
457+
Declaration::InstanceVariable(it) => Some(it.references()),
458+
_ => None,
459+
}
460+
}
461+
462+
/// Adds an instance variable reference to this declaration.
463+
///
464+
/// # Panics
465+
///
466+
/// Panics if called on a declaration that doesn't track instance variable references.
467+
pub fn add_instance_variable_reference(&mut self, reference_id: InstanceVariableReferenceId) {
468+
match self {
469+
Declaration::InstanceVariable(it) => it.add_reference(reference_id),
470+
_ => unreachable!("Cannot add instance variable reference to {} declaration", self.kind()),
471+
}
472+
}
473+
474+
/// Removes an instance variable reference from this declaration.
475+
///
476+
/// # Panics
477+
///
478+
/// Panics if called on a declaration that doesn't track instance variable references.
479+
pub fn remove_instance_variable_reference(&mut self, reference_id: &InstanceVariableReferenceId) {
480+
match self {
481+
Declaration::InstanceVariable(it) => it.remove_reference(reference_id),
482+
_ => unreachable!(
483+
"Cannot remove instance variable reference from {} declaration",
484+
self.kind()
485+
),
486+
}
487+
}
451488
}
452489

453490
#[derive(Debug)]

rust/rubydex/src/model/document.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use line_index::LineIndex;
44
use url::Url;
55

66
use crate::diagnostic::Diagnostic;
7-
use crate::model::ids::{ConstantReferenceId, DefinitionId, MethodReferenceId};
7+
use crate::model::ids::{ConstantReferenceId, DefinitionId, InstanceVariableReferenceId, MethodReferenceId};
88

99
// Represents a document currently loaded into memory. Identified by its unique URI, it holds the edges to all
1010
// definitions and references discovered in it
@@ -15,6 +15,7 @@ pub struct Document {
1515
definition_ids: Vec<DefinitionId>,
1616
method_reference_ids: Vec<MethodReferenceId>,
1717
constant_reference_ids: Vec<ConstantReferenceId>,
18+
instance_variable_reference_ids: Vec<InstanceVariableReferenceId>,
1819
diagnostics: Vec<Diagnostic>,
1920
}
2021

@@ -27,6 +28,7 @@ impl Document {
2728
definition_ids: Vec::new(),
2829
method_reference_ids: Vec::new(),
2930
constant_reference_ids: Vec::new(),
31+
instance_variable_reference_ids: Vec::new(),
3032
diagnostics: Vec::new(),
3133
}
3234
}
@@ -73,6 +75,15 @@ impl Document {
7375
self.constant_reference_ids.push(reference_id);
7476
}
7577

78+
#[must_use]
79+
pub fn instance_variable_references(&self) -> &[InstanceVariableReferenceId] {
80+
&self.instance_variable_reference_ids
81+
}
82+
83+
pub fn add_instance_variable_reference(&mut self, reference_id: InstanceVariableReferenceId) {
84+
self.instance_variable_reference_ids.push(reference_id);
85+
}
86+
7687
#[must_use]
7788
pub fn diagnostics(&self) -> &[Diagnostic] {
7889
&self.diagnostics

0 commit comments

Comments
 (0)