rubydex 0.2.6-aarch64-linux → 0.2.8-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/THIRD_PARTY_LICENSES.html +39 -6
  3. data/exe/rdx +2 -0
  4. data/ext/rubydex/declaration.c +115 -106
  5. data/ext/rubydex/definition.c +100 -80
  6. data/ext/rubydex/diagnostic.c +5 -0
  7. data/ext/rubydex/document.c +23 -31
  8. data/ext/rubydex/extconf.rb +1 -1
  9. data/ext/rubydex/graph.c +257 -70
  10. data/ext/rubydex/handle.h +13 -0
  11. data/ext/rubydex/location.c +5 -0
  12. data/ext/rubydex/reference.c +51 -46
  13. data/ext/rubydex/rubydex.c +5 -0
  14. data/ext/rubydex/signature.c +5 -0
  15. data/ext/rubydex/utils.c +13 -0
  16. data/ext/rubydex/utils.h +4 -0
  17. data/lib/rubydex/3.2/rubydex.so +0 -0
  18. data/lib/rubydex/3.3/rubydex.so +0 -0
  19. data/lib/rubydex/3.4/rubydex.so +0 -0
  20. data/lib/rubydex/4.0/rubydex.so +0 -0
  21. data/lib/rubydex/bin/rubydex_mcp +0 -0
  22. data/lib/rubydex/errors.rb +3 -0
  23. data/lib/rubydex/graph.rb +9 -28
  24. data/lib/rubydex/librubydex_sys.so +0 -0
  25. data/lib/rubydex/version.rb +1 -1
  26. data/rbi/rubydex.rbi +15 -7
  27. data/rust/Cargo.lock +119 -14
  28. data/rust/rubydex/Cargo.toml +19 -1
  29. data/rust/rubydex/benches/graph_memory.rs +46 -0
  30. data/rust/rubydex/src/config.rs +275 -0
  31. data/rust/rubydex/src/errors.rs +2 -0
  32. data/rust/rubydex/src/lib.rs +7 -0
  33. data/rust/rubydex/src/main.rs +148 -5
  34. data/rust/rubydex/src/model/declaration.rs +16 -55
  35. data/rust/rubydex/src/model/graph.rs +123 -17
  36. data/rust/rubydex/src/model/ids.rs +30 -0
  37. data/rust/rubydex/src/model/string_ref.rs +12 -4
  38. data/rust/rubydex/src/query.rs +83 -15
  39. data/rust/rubydex/src/resolution.rs +198 -151
  40. data/rust/rubydex/tests/cli.rs +66 -0
  41. data/rust/rubydex-mcp/src/server.rs +1 -1
  42. data/rust/rubydex-sys/src/declaration_api.rs +2 -2
  43. data/rust/rubydex-sys/src/graph_api.rs +127 -46
  44. metadata +4 -2
@@ -1,5 +1,4 @@
1
1
  use crate::assert_mem_size;
2
- use crate::diagnostic::Diagnostic;
3
2
  use crate::model::ids::{
4
3
  ClassVariableReferenceId, GlobalVariableReferenceId, InstanceVariableReferenceId, MethodReferenceId,
5
4
  };
@@ -92,7 +91,7 @@ macro_rules! namespace_declaration {
92
91
  #[derive(Debug)]
93
92
  pub struct $name {
94
93
  /// The fully qualified name of this declaration
95
- name: String,
94
+ name: Box<str>,
96
95
  /// The list of definition IDs that compose this declaration
97
96
  definition_ids: Vec<DefinitionId>,
98
97
  /// The set of references that are made to this declaration
@@ -111,15 +110,13 @@ macro_rules! namespace_declaration {
111
110
  descendants: IdentityHashSet<DeclarationId>,
112
111
  /// The singleton class associated with this declaration
113
112
  singleton_class_id: Option<DeclarationId>,
114
- /// Diagnostics associated with this declaration
115
- diagnostics: Vec<Diagnostic>,
116
113
  }
117
114
 
118
115
  impl $name {
119
116
  #[must_use]
120
117
  pub fn new(name: String, owner_id: DeclarationId) -> Self {
121
118
  Self {
122
- name,
119
+ name: name.into_boxed_str(),
123
120
  definition_ids: Vec::new(),
124
121
  members: IdentityHashMap::default(),
125
122
  references: IdentityHashSet::default(),
@@ -127,13 +124,11 @@ macro_rules! namespace_declaration {
127
124
  ancestors: Ancestors::Partial(Vec::new()),
128
125
  descendants: IdentityHashSet::default(),
129
126
  singleton_class_id: None,
130
- diagnostics: Vec::new(),
131
127
  }
132
128
  }
133
129
 
134
- pub fn extend(&mut self, mut other: Declaration) {
130
+ pub fn extend(&mut self, other: Declaration) {
135
131
  self.definition_ids.extend(other.definitions());
136
- self.diagnostics.extend(other.take_diagnostics());
137
132
 
138
133
  match other {
139
134
  Declaration::Namespace(namespace) => {
@@ -236,32 +231,28 @@ macro_rules! simple_declaration {
236
231
  #[derive(Debug)]
237
232
  pub struct $name {
238
233
  /// The fully qualified name of this declaration
239
- name: String,
234
+ name: Box<str>,
240
235
  /// The list of definition IDs that compose this declaration
241
236
  definition_ids: Vec<DefinitionId>,
242
237
  /// The set of references that are made to this declaration
243
238
  references: IdentityHashSet<$reference_type>,
244
239
  /// The ID of the owner of this declaration
245
240
  owner_id: DeclarationId,
246
- /// Diagnostics associated with this declaration
247
- diagnostics: Vec<Diagnostic>,
248
241
  }
249
242
 
250
243
  impl $name {
251
244
  #[must_use]
252
245
  pub fn new(name: String, owner_id: DeclarationId) -> Self {
253
246
  Self {
254
- name,
247
+ name: name.into_boxed_str(),
255
248
  definition_ids: Vec::new(),
256
249
  references: IdentityHashSet::default(),
257
250
  owner_id,
258
- diagnostics: Vec::new(),
259
251
  }
260
252
  }
261
253
 
262
- pub fn extend(&mut self, mut other: $name) {
254
+ pub fn extend(&mut self, other: $name) {
263
255
  self.definition_ids.extend(other.definitions());
264
- self.diagnostics.extend(other.take_diagnostics());
265
256
  self.references.extend(other.references());
266
257
  }
267
258
 
@@ -278,10 +269,6 @@ macro_rules! simple_declaration {
278
269
  self.references.remove(reference_id);
279
270
  }
280
271
 
281
- pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
282
- std::mem::take(&mut self.diagnostics)
283
- }
284
-
285
272
  #[must_use]
286
273
  pub fn definitions(&self) -> &[DefinitionId] {
287
274
  &self.definition_ids
@@ -436,23 +423,6 @@ impl Declaration {
436
423
  })
437
424
  }
438
425
 
439
- #[must_use]
440
- pub fn diagnostics(&self) -> &[Diagnostic] {
441
- all_declarations!(self, it => &it.diagnostics)
442
- }
443
-
444
- pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
445
- all_declarations!(self, it => std::mem::take(&mut it.diagnostics))
446
- }
447
-
448
- pub fn add_diagnostic(&mut self, diagnostic: Diagnostic) {
449
- all_declarations!(self, it => it.diagnostics.push(diagnostic));
450
- }
451
-
452
- pub fn clear_diagnostics(&mut self) {
453
- all_declarations!(self, it => it.diagnostics.clear());
454
- }
455
-
456
426
  #[must_use]
457
427
  pub fn reference_count(&self) -> usize {
458
428
  all_declarations!(self, it => it.references.len())
@@ -546,15 +516,6 @@ impl Namespace {
546
516
  all_namespaces!(self, it => &it.members)
547
517
  }
548
518
 
549
- #[must_use]
550
- pub fn diagnostics(&self) -> &[Diagnostic] {
551
- all_namespaces!(self, it => &it.diagnostics)
552
- }
553
-
554
- pub fn take_diagnostics(&mut self) -> Vec<Diagnostic> {
555
- all_namespaces!(self, it => std::mem::take(&mut it.diagnostics))
556
- }
557
-
558
519
  pub fn extend(&mut self, other: Declaration) {
559
520
  all_namespaces!(self, it => it.extend(other));
560
521
  }
@@ -647,25 +608,25 @@ impl Namespace {
647
608
  }
648
609
 
649
610
  namespace_declaration!(Class, ClassDeclaration);
650
- assert_mem_size!(ClassDeclaration, 216);
611
+ assert_mem_size!(ClassDeclaration, 184);
651
612
  namespace_declaration!(Module, ModuleDeclaration);
652
- assert_mem_size!(ModuleDeclaration, 216);
613
+ assert_mem_size!(ModuleDeclaration, 184);
653
614
  namespace_declaration!(SingletonClass, SingletonClassDeclaration);
654
- assert_mem_size!(SingletonClassDeclaration, 216);
615
+ assert_mem_size!(SingletonClassDeclaration, 184);
655
616
  namespace_declaration!(Todo, TodoDeclaration);
656
- assert_mem_size!(TodoDeclaration, 216);
617
+ assert_mem_size!(TodoDeclaration, 184);
657
618
  simple_declaration!(ConstantDeclaration, ConstantReferenceId);
658
- assert_mem_size!(ConstantDeclaration, 112);
619
+ assert_mem_size!(ConstantDeclaration, 80);
659
620
  simple_declaration!(MethodDeclaration, MethodReferenceId);
660
- assert_mem_size!(MethodDeclaration, 112);
621
+ assert_mem_size!(MethodDeclaration, 80);
661
622
  simple_declaration!(GlobalVariableDeclaration, GlobalVariableReferenceId);
662
- assert_mem_size!(GlobalVariableDeclaration, 112);
623
+ assert_mem_size!(GlobalVariableDeclaration, 80);
663
624
  simple_declaration!(InstanceVariableDeclaration, InstanceVariableReferenceId);
664
- assert_mem_size!(InstanceVariableDeclaration, 112);
625
+ assert_mem_size!(InstanceVariableDeclaration, 80);
665
626
  simple_declaration!(ClassVariableDeclaration, ClassVariableReferenceId);
666
- assert_mem_size!(ClassVariableDeclaration, 112);
627
+ assert_mem_size!(ClassVariableDeclaration, 80);
667
628
  simple_declaration!(ConstantAliasDeclaration, ConstantReferenceId);
668
- assert_mem_size!(ConstantAliasDeclaration, 112);
629
+ assert_mem_size!(ConstantAliasDeclaration, 80);
669
630
 
670
631
  #[cfg(test)]
671
632
  mod tests {
@@ -1,9 +1,11 @@
1
1
  use std::collections::HashSet;
2
2
  use std::collections::hash_map::Entry;
3
- use std::path::PathBuf;
3
+ use std::path::{Path, PathBuf};
4
4
 
5
5
  use crate::assert_mem_size;
6
+ use crate::config::Config;
6
7
  use crate::diagnostic::Diagnostic;
8
+ use crate::errors::Errors;
7
9
  use crate::indexing::local_graph::LocalGraph;
8
10
  use crate::model::built_in::{OBJECT_ID, add_built_in_data};
9
11
  use crate::model::declaration::{Ancestor, Declaration, Namespace};
@@ -11,7 +13,10 @@ use crate::model::definitions::{Definition, MethodVisibilityDefinition, Receiver
11
13
  use crate::model::document::Document;
12
14
  use crate::model::encoding::Encoding;
13
15
  use crate::model::identity_maps::{IdentityHashMap, IdentityHashSet};
14
- use crate::model::ids::{ConstantReferenceId, DeclarationId, DefinitionId, MethodReferenceId, NameId, StringId, UriId};
16
+ use crate::model::ids::{
17
+ ConstantReferenceId, DeclarationId, DefinitionId, MethodReferenceId, NameId, StringId, UriId,
18
+ declaration_id_from_lookup_name,
19
+ };
15
20
  use crate::model::name::{Name, NameRef, ParentScope, ResolvedName};
16
21
  use crate::model::references::{ConstantReference, MethodRef};
17
22
  use crate::model::string_ref::StringRef;
@@ -85,10 +90,10 @@ pub struct Graph {
85
90
  /// Drained by `take_pending_work()` before resolution.
86
91
  pending_work: Vec<Unit>,
87
92
 
88
- /// Paths to exclude from file discovery during indexing.
89
- excluded_paths: HashSet<PathBuf>,
93
+ /// Project configuration
94
+ config: Config,
90
95
  }
91
- assert_mem_size!(Graph, 336);
96
+ assert_mem_size!(Graph, 352);
92
97
 
93
98
  impl Graph {
94
99
  #[must_use]
@@ -104,7 +109,7 @@ impl Graph {
104
109
  position_encoding: Encoding::default(),
105
110
  name_dependents: IdentityHashMap::default(),
106
111
  pending_work: Vec::default(),
107
- excluded_paths: HashSet::new(),
112
+ config: Config::new(),
108
113
  };
109
114
 
110
115
  add_built_in_data(&mut graph);
@@ -126,13 +131,40 @@ impl Graph {
126
131
  /// Adds paths to exclude from file discovery during indexing. Excluded directories will be skipped entirely during
127
132
  /// directory traversal.
128
133
  pub fn exclude_paths(&mut self, paths: Vec<PathBuf>) {
129
- self.excluded_paths.extend(paths);
134
+ self.config.exclude_paths(paths);
130
135
  }
131
136
 
132
137
  /// Returns the set of paths excluded from file discovery.
133
138
  #[must_use]
134
- pub fn excluded_paths(&self) -> &HashSet<PathBuf> {
135
- &self.excluded_paths
139
+ pub fn excluded_paths(&self) -> HashSet<PathBuf> {
140
+ self.config.excluded_paths()
141
+ }
142
+
143
+ /// Returns the root directory of the workspace being indexed.
144
+ #[must_use]
145
+ pub fn workspace_path(&self) -> &Path {
146
+ self.config.workspace_path()
147
+ }
148
+
149
+ /// Sets the root directory of the workspace being indexed.
150
+ pub fn set_workspace_path(&mut self, workspace_path: PathBuf) {
151
+ self.config.set_workspace_path(workspace_path);
152
+ }
153
+
154
+ /// Loads a configuration file. Pass `None` to load the default `rubydex.toml` configuration file if it exists
155
+ ///
156
+ /// # Errors
157
+ ///
158
+ /// Returns an [`Errors::ConfigNotFound`] if an explicitly requested file does not exist or an
159
+ /// [`Errors::ConfigError`] if a file cannot otherwise be read or its contents are malformed.
160
+ pub fn load_config(&mut self, config_path: Option<&Path>) -> Result<(), Errors> {
161
+ match config_path {
162
+ Some(path) => {
163
+ let path = self.config.workspace_path().join(path);
164
+ self.config.load_file(&path)
165
+ }
166
+ None => self.config.load_default(),
167
+ }
136
168
  }
137
169
 
138
170
  /// # Panics
@@ -486,10 +518,7 @@ impl Graph {
486
518
 
487
519
  #[must_use]
488
520
  pub fn all_diagnostics(&self) -> Vec<&Diagnostic> {
489
- let document_diagnostics = self.documents.values().flat_map(Document::diagnostics);
490
- let declaration_diagnostics = self.declarations.values().flat_map(Declaration::diagnostics);
491
-
492
- document_diagnostics.chain(declaration_diagnostics).collect()
521
+ self.documents.values().flat_map(Document::diagnostics).collect()
493
522
  }
494
523
 
495
524
  /// Interns a string in the graph unless already interned. This method is only used to back the
@@ -552,7 +581,7 @@ impl Graph {
552
581
 
553
582
  #[must_use]
554
583
  pub fn get(&self, name: &str) -> Option<Vec<&Definition>> {
555
- let declaration_id = DeclarationId::from(name);
584
+ let declaration_id = declaration_id_from_lookup_name(name);
556
585
  let declaration = self.declarations.get(&declaration_id)?;
557
586
 
558
587
  Some(
@@ -1201,9 +1230,6 @@ impl Graph {
1201
1230
  for def_id in detach_def_ids {
1202
1231
  decl.remove_definition(def_id);
1203
1232
  }
1204
- if !detach_def_ids.is_empty() {
1205
- decl.clear_diagnostics();
1206
- }
1207
1233
  }
1208
1234
 
1209
1235
  let Some(decl) = self.declarations.get(&decl_id) else {
@@ -1247,6 +1273,10 @@ impl Graph {
1247
1273
  let unqualified_str_id = StringId::from(&decl.unqualified_name());
1248
1274
  let owner_id = *decl.owner_id();
1249
1275
  let is_singleton_class = matches!(decl, Declaration::Namespace(Namespace::SingletonClass(_)));
1276
+ let ancestors_to_detach: Vec<Ancestor> = decl
1277
+ .as_namespace()
1278
+ .map(|ns| ns.ancestors().iter().copied().collect())
1279
+ .unwrap_or_default();
1250
1280
 
1251
1281
  for def_id in def_ids {
1252
1282
  self.push_work(Unit::Definition(def_id));
@@ -1261,6 +1291,16 @@ impl Graph {
1261
1291
  ns.remove_member(&unqualified_str_id);
1262
1292
  }
1263
1293
  }
1294
+
1295
+ // Detach from each complete ancestor's descendant set so we don't leave a stale id in descendants
1296
+ for ancestor in ancestors_to_detach {
1297
+ if let Ancestor::Complete(ancestor_id) = ancestor
1298
+ && let Some(anc_decl) = self.declarations.get_mut(&ancestor_id)
1299
+ && let Some(ns) = anc_decl.as_namespace_mut()
1300
+ {
1301
+ ns.remove_descendant(&decl_id);
1302
+ }
1303
+ }
1264
1304
  }
1265
1305
 
1266
1306
  self.declarations.remove(&decl_id);
@@ -1547,6 +1587,10 @@ mod tests {
1547
1587
  assert_members_eq, assert_no_diagnostics, assert_no_members,
1548
1588
  };
1549
1589
 
1590
+ fn definition_ids(definitions: Vec<&Definition>) -> Vec<DefinitionId> {
1591
+ definitions.into_iter().map(Definition::id).collect()
1592
+ }
1593
+
1550
1594
  #[test]
1551
1595
  fn deleting_a_uri() {
1552
1596
  let mut context = GraphTest::new();
@@ -1969,6 +2013,34 @@ mod tests {
1969
2013
  assert_eq!(definitions[0].offset().start(), 6);
1970
2014
  }
1971
2015
 
2016
+ #[test]
2017
+ fn get_accepts_leading_double_colon() {
2018
+ let mut context = GraphTest::new();
2019
+
2020
+ context.index_uri("file:///foo.rb", "module Foo; module Bar; end; end");
2021
+ context.resolve();
2022
+
2023
+ // Built-in declarations (root-scoped):
2024
+ let unqualified = context.graph().get("Object").expect("unqualified `Object` lookup");
2025
+ let qualified = context.graph().get("::Object").expect("qualified `::Object` lookup");
2026
+ assert_eq!(definition_ids(unqualified), definition_ids(qualified));
2027
+
2028
+ // Indexed declarations, top-level and nested:
2029
+ let unqualified = context.graph().get("Foo").expect("unqualified `Foo` lookup");
2030
+ let qualified = context.graph().get("::Foo").expect("qualified `::Foo` lookup");
2031
+ assert_eq!(definition_ids(unqualified), definition_ids(qualified));
2032
+
2033
+ let unqualified = context.graph().get("Foo::Bar").expect("unqualified `Foo::Bar` lookup");
2034
+ let qualified = context
2035
+ .graph()
2036
+ .get("::Foo::Bar")
2037
+ .expect("qualified `::Foo::Bar` lookup");
2038
+ assert_eq!(definition_ids(unqualified), definition_ids(qualified));
2039
+
2040
+ // Unknown names still return None when prefixed:
2041
+ assert!(context.graph().get("::DoesNotExist").is_none());
2042
+ }
2043
+
1972
2044
  #[test]
1973
2045
  fn adding_another_definition_from_a_different_uri() {
1974
2046
  let mut context = GraphTest::new();
@@ -2476,6 +2548,7 @@ mod tests {
2476
2548
 
2477
2549
  #[cfg(test)]
2478
2550
  mod incremental_resolution_tests {
2551
+ use crate::model::ids::DeclarationId;
2479
2552
  use crate::model::name::NameRef;
2480
2553
  use crate::test_utils::GraphTest;
2481
2554
  use crate::{
@@ -4103,4 +4176,37 @@ mod incremental_resolution_tests {
4103
4176
  assert_declaration_exists!(context, "Foo::<Foo>");
4104
4177
  assert_declaration_exists!(context, "Bar::<Bar>");
4105
4178
  }
4179
+
4180
+ #[test]
4181
+ fn constant_references_through_object_inheritance_are_invalidated() {
4182
+ let mut context = GraphTest::new();
4183
+ context.index_uri("file:///a.rb", "class Foo; end");
4184
+ context.index_uri(
4185
+ "file:///b.rb",
4186
+ r"
4187
+ module Wrap
4188
+ class Foo; end
4189
+ ::Foo
4190
+ Foo
4191
+ end
4192
+ ",
4193
+ );
4194
+ context.resolve();
4195
+
4196
+ context.delete_uri("file:///a.rb");
4197
+ context.resolve();
4198
+
4199
+ let kernel = context
4200
+ .graph()
4201
+ .declarations()
4202
+ .get(&DeclarationId::from("Kernel"))
4203
+ .unwrap();
4204
+
4205
+ for id in kernel.as_namespace().unwrap().descendants() {
4206
+ assert!(
4207
+ context.graph().declarations().contains_key(id),
4208
+ "Kernel has stale descendant id {id:?} with no backing declaration"
4209
+ );
4210
+ }
4211
+ }
4106
4212
  } // mod incremental_resolution_tests
@@ -9,6 +9,15 @@ pub struct DeclarationMarker;
9
9
  /// `DeclarationId` represents the ID of a fully qualified name. For example, `Foo::Bar` or `Foo#my_method`
10
10
  pub type DeclarationId = Id<DeclarationMarker>;
11
11
 
12
+ /// Creates a `DeclarationId` from a user-facing lookup name.
13
+ ///
14
+ /// Declaration names are stored without the root-scope marker, so `"::Object"`
15
+ /// and `"Object"` must resolve to the same declaration ID.
16
+ #[must_use]
17
+ pub fn declaration_id_from_lookup_name(name: &str) -> DeclarationId {
18
+ DeclarationId::from(name.strip_prefix("::").unwrap_or(name))
19
+ }
20
+
12
21
  #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
13
22
  pub struct DefinitionMarker;
14
23
 
@@ -84,3 +93,24 @@ assert_mem_size!(ClassVariableReferenceId, 8);
84
93
  pub struct InstanceVariableMarker;
85
94
  pub type InstanceVariableReferenceId = Id<InstanceVariableMarker>;
86
95
  assert_mem_size!(InstanceVariableReferenceId, 8);
96
+
97
+ #[cfg(test)]
98
+ mod tests {
99
+ use super::*;
100
+
101
+ #[test]
102
+ fn declaration_id_from_lookup_name_accepts_root_scope_marker() {
103
+ assert_eq!(
104
+ DeclarationId::from("Object"),
105
+ declaration_id_from_lookup_name("::Object")
106
+ );
107
+ assert_eq!(
108
+ DeclarationId::from("Foo::Bar"),
109
+ declaration_id_from_lookup_name("::Foo::Bar")
110
+ );
111
+ assert_ne!(
112
+ DeclarationId::from("Object"),
113
+ declaration_id_from_lookup_name("::::Object")
114
+ );
115
+ }
116
+ }
@@ -9,15 +9,23 @@ use std::ops::Deref;
9
9
  /// graph when its count reaches zero.
10
10
  #[derive(Debug)]
11
11
  pub struct StringRef {
12
- value: String,
12
+ value: Box<str>,
13
13
  ref_count: u32,
14
14
  }
15
- assert_mem_size!(StringRef, 32);
15
+ assert_mem_size!(StringRef, 24);
16
16
 
17
17
  impl StringRef {
18
18
  #[must_use]
19
19
  pub fn new(value: String) -> Self {
20
- Self { value, ref_count: 1 }
20
+ Self {
21
+ value: value.into_boxed_str(),
22
+ ref_count: 1,
23
+ }
24
+ }
25
+
26
+ #[must_use]
27
+ pub fn as_str(&self) -> &str {
28
+ &self.value
21
29
  }
22
30
 
23
31
  #[must_use]
@@ -44,7 +52,7 @@ impl StringRef {
44
52
  }
45
53
 
46
54
  impl Deref for StringRef {
47
- type Target = String;
55
+ type Target = str;
48
56
 
49
57
  fn deref(&self) -> &Self::Target {
50
58
  &self.value
@@ -27,12 +27,25 @@ pub enum MatchMode {
27
27
  Exact,
28
28
  }
29
29
 
30
+ /// Searches all declarations in parallel based on fully qualified names. Accepts multiple queries in case the caller
31
+ /// wants to find multiple patterns without having to re-traverse the graph and also accepts match mode.
32
+ ///
33
+ /// Note: an empty query returns all declarations, so if any are included the rest of the queries will be ignored.
34
+ ///
30
35
  /// # Panics
31
36
  ///
32
37
  /// Will panic if any of the threads panic
33
- pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) -> Vec<DeclarationId> {
38
+ pub fn declaration_search(graph: &Graph, queries: &[&str], match_mode: &MatchMode) -> Vec<DeclarationId> {
34
39
  let num_threads = thread::available_parallelism().map_or(4, std::num::NonZero::get);
35
40
  let declarations = graph.declarations();
41
+
42
+ // An empty query matches all declarations as per the LSP specification and is equivalent to fetching all of them
43
+ // directly. Since an empty query matches all, there's no point in checking the other queries or pay the price of
44
+ // spawning threads.
45
+ if queries.iter().any(|q| q.is_empty()) {
46
+ return declarations.keys().copied().collect();
47
+ }
48
+
36
49
  let ids: Vec<DeclarationId> = declarations.keys().copied().collect();
37
50
  let chunk_size = ids.len().div_ceil(num_threads);
38
51
 
@@ -48,16 +61,8 @@ pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) ->
48
61
  chunk
49
62
  .iter()
50
63
  .filter(|id| {
51
- let declaration = declarations.get(id).unwrap();
52
- let name = declaration.name();
53
- match match_mode {
54
- MatchMode::Fuzzy => {
55
- // When the query is empty, we return everything as per the LSP specification.
56
- // Otherwise, we compute the match score and return anything with a score greater than zero
57
- query.is_empty() || match_score(query, name) > 0
58
- }
59
- MatchMode::Exact => name.contains(query),
60
- }
64
+ let name = declarations.get(id).unwrap().name();
65
+ queries.iter().any(|query| matches_query(query, name, match_mode))
61
66
  })
62
67
  .copied()
63
68
  .collect::<Vec<_>>()
@@ -69,6 +74,15 @@ pub fn declaration_search(graph: &Graph, query: &str, match_mode: &MatchMode) ->
69
74
  })
70
75
  }
71
76
 
77
+ /// Returns whether a single `query` matches `name` under the given [`MatchMode`].
78
+ #[must_use]
79
+ fn matches_query(query: &str, name: &str, match_mode: &MatchMode) -> bool {
80
+ match match_mode {
81
+ MatchMode::Fuzzy => match_score(query, name) > 0,
82
+ MatchMode::Exact => name.contains(query),
83
+ }
84
+ }
85
+
72
86
  #[must_use]
73
87
  fn match_score(query: &str, target: &str) -> usize {
74
88
  let mut query_chars = query.chars().peekable();
@@ -836,7 +850,7 @@ mod tests {
836
850
  assert_results_eq!($context, $query, &MatchMode::default(), $expected);
837
851
  };
838
852
  ($context:expr, $query:expr, $match_mode:expr, $expected:expr) => {
839
- let actual = declaration_search(&$context.graph(), $query, $match_mode);
853
+ let actual = declaration_search(&$context.graph(), &[$query], $match_mode);
840
854
  assert_eq!(
841
855
  actual,
842
856
  $expected
@@ -945,8 +959,8 @@ mod tests {
945
959
  "
946
960
  });
947
961
  context.resolve();
948
- let exact_results = declaration_search(context.graph(), "", &MatchMode::Exact);
949
- let fuzzy_results = declaration_search(context.graph(), "", &MatchMode::Fuzzy);
962
+ let exact_results = declaration_search(context.graph(), &[""], &MatchMode::Exact);
963
+ let fuzzy_results = declaration_search(context.graph(), &[""], &MatchMode::Fuzzy);
950
964
 
951
965
  assert_eq!(exact_results.len(), fuzzy_results.len());
952
966
  assert_eq!(context.graph().declarations().len(), exact_results.len());
@@ -972,6 +986,51 @@ mod tests {
972
986
  assert_results_eq!(context, "#Is_A?()", ["Foo#is_a_foo?()", "Bar#is_a?()"]);
973
987
  }
974
988
 
989
+ #[test]
990
+ fn multiple_queries_return_union_of_matches() {
991
+ let mut context = GraphTest::new();
992
+ context.index_uri("file:///foo.rb", {
993
+ r"
994
+ class Foo
995
+ def foo_method; end
996
+ def bar_method; end
997
+ def other_method; end
998
+ end
999
+ "
1000
+ });
1001
+ context.resolve();
1002
+
1003
+ let results = declaration_search(context.graph(), &["#foo_method()", "#bar_method()"], &MatchMode::Exact);
1004
+ let mut names: Vec<String> = results
1005
+ .iter()
1006
+ .map(|id| context.graph().declarations().get(id).unwrap().name().to_string())
1007
+ .collect();
1008
+ names.sort();
1009
+
1010
+ assert_eq!(names, ["Foo#bar_method()", "Foo#foo_method()"]);
1011
+ }
1012
+
1013
+ #[test]
1014
+ fn overlapping_queries_do_not_duplicate_results() {
1015
+ let mut context = GraphTest::new();
1016
+ context.index_uri("file:///foo.rb", {
1017
+ r"
1018
+ class Foo
1019
+ def is_a_foo?; end
1020
+ end
1021
+ "
1022
+ });
1023
+ context.resolve();
1024
+
1025
+ let results = declaration_search(context.graph(), &["is_a", "foo?"], &MatchMode::Exact);
1026
+ let matches = results
1027
+ .iter()
1028
+ .filter(|id| context.graph().declarations().get(id).unwrap().name() == "Foo#is_a_foo?()")
1029
+ .count();
1030
+
1031
+ assert_eq!(matches, 1);
1032
+ }
1033
+
975
1034
  fn test_root() -> PathBuf {
976
1035
  let root = if cfg!(windows) { "C:\\" } else { "/" };
977
1036
  PathBuf::from_str(root).unwrap()
@@ -2767,7 +2826,16 @@ mod tests {
2767
2826
  self_decl_id: None,
2768
2827
  nesting_name_id,
2769
2828
  },
2770
- ["Bar#@@bar_cvar"]
2829
+ [
2830
+ "Bar",
2831
+ "Bar#@@bar_cvar",
2832
+ "BasicObject",
2833
+ "Class",
2834
+ "Foo",
2835
+ "Kernel",
2836
+ "Module",
2837
+ "Object"
2838
+ ]
2771
2839
  );
2772
2840
  }
2773
2841