rubydex 0.2.0 → 0.2.2

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.
@@ -6,13 +6,14 @@ use std::thread;
6
6
  use url::Url;
7
7
 
8
8
  use crate::model::built_in::OBJECT_ID;
9
- use crate::model::declaration::{Ancestor, Declaration};
9
+ use crate::model::declaration::{Ancestor, Declaration, Namespace};
10
10
  use crate::model::definitions::{Definition, Parameter};
11
11
  use crate::model::graph::Graph;
12
12
  use crate::model::identity_maps::IdentityHashSet;
13
- use crate::model::ids::{DeclarationId, NameId, StringId, UriId};
13
+ use crate::model::ids::{DeclarationId, DefinitionId, NameId, StringId, UriId};
14
14
  use crate::model::keywords::{self, Keyword};
15
15
  use crate::model::name::NameRef;
16
+ use crate::model::visibility::Visibility;
16
17
 
17
18
  /// Controls how declaration names are matched against the search query.
18
19
  #[derive(Default)]
@@ -169,18 +170,37 @@ pub enum CompletionCandidate {
169
170
  pub enum CompletionReceiver {
170
171
  /// Completion requested for an expression with no previous token (e.g.: at the start of a line with nothing before)
171
172
  /// Includes: all keywords, all global variables and reacheable instance variables, class variables, constants and methods
172
- Expression(NameId),
173
+ ///
174
+ /// `nesting_name_id` represents the lexical scope. It is walked for constants and drives class-variable
175
+ /// lookup (cvars follow lexical scope in Ruby, not self).
176
+ /// `self_decl_id` overrides the self-type used for methods and instance variables when the runtime `self`
177
+ /// diverges from the innermost lexical scope — for example `def Foo.bar` (where self is `Foo` but the
178
+ /// lexical scope is the outer namespace) or `def self.bar`. Callers may pass a `ConstantAlias` id; it is
179
+ /// unwrapped to the target namespace. When `None`, self is derived from the innermost lexical scope.
180
+ Expression {
181
+ self_decl_id: Option<DeclarationId>,
182
+ nesting_name_id: NameId,
183
+ },
173
184
  /// Completion requested after a namespace access operator (e.g.: `Foo::`)
174
- /// Includes: all constants and singleton methods for the namespace and its ancestors
175
- NamespaceAccess(DeclarationId),
185
+ /// Includes: all constants and singleton methods for the namespace and its ancestors.
186
+ NamespaceAccess {
187
+ self_decl_id: Option<DeclarationId>,
188
+ namespace_decl_id: DeclarationId,
189
+ },
176
190
  /// Completion requested after a method call operator (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`).
177
191
  /// In the case of singleton completion (e.g.: `Foo.`), the declaration ID should be for the singleton class (i.e.: `Foo::<Foo>`)
178
- /// Includes: all methods that exist on the type of the receiver and its ancestors
179
- MethodCall(DeclarationId),
192
+ /// Includes: all methods that exist on the type of the receiver and its ancestors.
193
+ MethodCall {
194
+ self_decl_id: Option<DeclarationId>,
195
+ receiver_decl_id: DeclarationId,
196
+ },
180
197
  /// Completion requested inside a method call's argument list (e.g.: `foo.bar(|)`)
181
198
  /// Includes: everything expressions do plus keyword parameter names of the method being called
199
+ ///
200
+ /// Same `self_decl_id` / `nesting_name_id` split as `Expression`.
182
201
  MethodArgument {
183
- self_name_id: NameId,
202
+ self_decl_id: Option<DeclarationId>,
203
+ nesting_name_id: NameId,
184
204
  method_decl_id: DeclarationId,
185
205
  },
186
206
  }
@@ -204,26 +224,58 @@ impl<'a> CompletionContext<'a> {
204
224
  }
205
225
  }
206
226
 
207
- /// Collects completion candidate members
208
- macro_rules! collect_candidates {
209
- // Collect all members with no filtering
210
- ($declaration:expr, $context:expr, $candidates:expr) => {
211
- for (member_str_id, member_declaration_id) in $declaration.members() {
212
- if $context.dedup(member_str_id) {
213
- $candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
214
- }
215
- }
227
+ /// Method visibility depends both on the type of self and the current lexical scope (due to protected methods)
228
+ fn method_visible_at_call(
229
+ graph: &Graph,
230
+ method_id: DeclarationId,
231
+ defined_in: DeclarationId,
232
+ caller_self: Option<DeclarationId>,
233
+ receiver: DeclarationId,
234
+ ) -> bool {
235
+ let Some(visibility) = graph.visibility(&method_id) else {
236
+ return true;
216
237
  };
217
- // Collect only members matching certain kinds
218
- ($graph:expr, $declaration:expr, $context:expr, $candidates:expr, $kinds:pat) => {
219
- for (member_str_id, member_declaration_id) in $declaration.members() {
220
- let member = $graph.declarations().get(member_declaration_id).unwrap();
221
238
 
222
- if matches!(member, $kinds) && $context.dedup(member_str_id) {
223
- $candidates.push(CompletionCandidate::Declaration(*member_declaration_id));
224
- }
239
+ match visibility {
240
+ Visibility::Public => true,
241
+ Visibility::Private | Visibility::ModuleFunction => caller_self == Some(receiver),
242
+ Visibility::Protected => caller_self.is_some_and(|cs| {
243
+ let defined_in = graph.declarations().get(&defined_in).unwrap().as_namespace().unwrap();
244
+ let descendants = defined_in.descendants();
245
+ descendants.contains(&cs) && descendants.contains(&receiver)
246
+ }),
247
+ }
248
+ }
249
+
250
+ /// Walks one namespace's direct members. `kind_filter` selects the declaration kinds to surface; `visibility_filter`
251
+ /// decides whether each surviving candidate is reachable from the access site.
252
+ fn collect_members<'a>(
253
+ graph: &'a Graph,
254
+ namespace_id: DeclarationId,
255
+ kind_filter: fn(&Declaration) -> bool,
256
+ visibility_filter: impl Fn(DeclarationId) -> bool,
257
+ completion_ctx: &mut CompletionContext<'a>,
258
+ candidates: &mut Vec<CompletionCandidate>,
259
+ ) {
260
+ let namespace = graph.declarations().get(&namespace_id).unwrap().as_namespace().unwrap();
261
+
262
+ for (member_str_id, member_decl_id) in namespace.members() {
263
+ let member = graph.declarations().get(member_decl_id).unwrap();
264
+
265
+ if !kind_filter(member) {
266
+ continue;
225
267
  }
226
- };
268
+
269
+ if !completion_ctx.dedup(member_str_id) {
270
+ continue;
271
+ }
272
+
273
+ if !visibility_filter(*member_decl_id) {
274
+ continue;
275
+ }
276
+
277
+ candidates.push(CompletionCandidate::Declaration(*member_decl_id));
278
+ }
227
279
  }
228
280
 
229
281
  /// Determines all possible completion candidates based on the current context of the cursor. There are multiple cases
@@ -233,7 +285,6 @@ macro_rules! collect_candidates {
233
285
  /// global variables that are reacheable from the current lexical scope and self type
234
286
  /// - Expression in method arguments collects everything that expressions do and all keyword parameter names that are
235
287
  /// applicable to the method being called
236
- /// everything else
237
288
  /// - Namespace access (e.g.: `Foo::`) collects all constants and singleton methods for the namespace that `Foo`
238
289
  /// resolves to
239
290
  /// - Method calls on anything (e.g.: `foo.`, `@bar.`, `@@baz.`, `Qux.`) collects all methods that exist on the type
@@ -245,19 +296,30 @@ macro_rules! collect_candidates {
245
296
  ///
246
297
  /// # Errors
247
298
  ///
248
- /// Will error if the given `self_name_id` does not point to a namespace declaration
299
+ /// Will error if the given `self_decl_id` does not resolve to a namespace declaration (directly or via
300
+ /// a constant alias).
249
301
  pub fn completion_candidates<'a>(
250
302
  graph: &'a Graph,
251
303
  context: CompletionContext<'a>,
252
304
  ) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
253
305
  match context.completion_receiver {
254
- CompletionReceiver::Expression(self_name_id) => expression_completion(graph, self_name_id, context),
255
- CompletionReceiver::NamespaceAccess(decl_id) => namespace_access_completion(graph, decl_id, context),
256
- CompletionReceiver::MethodCall(decl_id) => method_call_completion(graph, decl_id, context),
306
+ CompletionReceiver::Expression {
307
+ self_decl_id,
308
+ nesting_name_id,
309
+ } => expression_completion(graph, self_decl_id, nesting_name_id, context),
310
+ CompletionReceiver::NamespaceAccess {
311
+ self_decl_id,
312
+ namespace_decl_id,
313
+ } => namespace_access_completion(graph, self_decl_id, namespace_decl_id, context),
314
+ CompletionReceiver::MethodCall {
315
+ self_decl_id,
316
+ receiver_decl_id,
317
+ } => method_call_completion(graph, self_decl_id, receiver_decl_id, context),
257
318
  CompletionReceiver::MethodArgument {
258
- self_name_id,
319
+ self_decl_id,
320
+ nesting_name_id,
259
321
  method_decl_id,
260
- } => method_argument_completion(graph, self_name_id, method_decl_id, context),
322
+ } => method_argument_completion(graph, self_decl_id, nesting_name_id, method_decl_id, context),
261
323
  }
262
324
  }
263
325
 
@@ -286,12 +348,14 @@ fn resolve_to_namespace(graph: &Graph, decl_id: DeclarationId) -> Result<Option<
286
348
  /// Collect completion for a namespace access (e.g.: `Foo::`)
287
349
  fn namespace_access_completion<'a>(
288
350
  graph: &'a Graph,
351
+ self_decl_id: Option<DeclarationId>,
289
352
  namespace_decl_id: DeclarationId,
290
353
  mut context: CompletionContext<'a>,
291
354
  ) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
292
355
  let Some(resolved_id) = resolve_to_namespace(graph, namespace_decl_id)? else {
293
356
  return Ok(Vec::new());
294
357
  };
358
+ let resolved_caller_self_id = self_decl_id.map(|id| resolve_self_namespace(graph, id)).transpose()?;
295
359
  let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
296
360
  let mut candidates = Vec::new();
297
361
 
@@ -305,26 +369,35 @@ fn namespace_access_completion<'a>(
305
369
  break;
306
370
  }
307
371
 
308
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
309
-
310
- collect_candidates!(
372
+ collect_members(
311
373
  graph,
312
- &ancestor_decl,
313
- context,
314
- candidates,
315
- Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
374
+ *ancestor_id,
375
+ |d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
376
+ |id| !matches!(graph.visibility(&id), Some(Visibility::Private)),
377
+ &mut context,
378
+ &mut candidates,
316
379
  );
317
380
  }
318
381
  }
319
382
 
320
- // Collect singleton methods from the singleton class and its ancestors
383
+ // The receiver of an explicit `Foo::` call is the singleton class, so visibility checks
384
+ // compare against it (not against `Foo` itself).
321
385
  if let Some(singleton_id) = namespace.singleton_class() {
322
386
  let singleton = graph.declarations().get(singleton_id).unwrap().as_namespace().unwrap();
387
+ let receiver = *singleton_id;
323
388
 
324
389
  for ancestor in singleton.ancestors() {
325
390
  if let Ancestor::Complete(ancestor_id) = ancestor {
326
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
327
- collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
391
+ let defined_in = *ancestor_id;
392
+
393
+ collect_members(
394
+ graph,
395
+ defined_in,
396
+ |d| d.as_method().is_some(),
397
+ |id| method_visible_at_call(graph, id, defined_in, resolved_caller_self_id, receiver),
398
+ &mut context,
399
+ &mut candidates,
400
+ );
328
401
  }
329
402
  }
330
403
  }
@@ -335,117 +408,248 @@ fn namespace_access_completion<'a>(
335
408
  /// Collect completion for a method call (e.g.: `foo.`, `@bar.`, `Baz.`)
336
409
  fn method_call_completion<'a>(
337
410
  graph: &'a Graph,
411
+ self_decl_id: Option<DeclarationId>,
338
412
  receiver_decl_id: DeclarationId,
339
413
  mut context: CompletionContext<'a>,
340
414
  ) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
341
415
  let Some(resolved_id) = resolve_to_namespace(graph, receiver_decl_id)? else {
342
416
  return Ok(Vec::new());
343
417
  };
418
+ let resolved_caller_self_id = self_decl_id.map(|id| resolve_self_namespace(graph, id)).transpose()?;
344
419
  let namespace = graph.declarations().get(&resolved_id).unwrap().as_namespace().unwrap();
345
420
  let mut candidates = Vec::new();
346
421
 
347
422
  for ancestor in namespace.ancestors() {
348
423
  if let Ancestor::Complete(ancestor_id) = ancestor {
349
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
350
- collect_candidates!(graph, &ancestor_decl, context, candidates, Declaration::Method(_));
424
+ let defined_in = *ancestor_id;
425
+ collect_members(
426
+ graph,
427
+ defined_in,
428
+ |d| d.as_method().is_some(),
429
+ |id| method_visible_at_call(graph, id, defined_in, resolved_caller_self_id, resolved_id),
430
+ &mut context,
431
+ &mut candidates,
432
+ );
351
433
  }
352
434
  }
353
435
 
354
436
  Ok(candidates)
355
437
  }
356
438
 
439
+ fn resolve_self_namespace(graph: &Graph, decl_id: DeclarationId) -> Result<DeclarationId, Box<dyn Error>> {
440
+ resolve_to_namespace(graph, decl_id)?
441
+ .ok_or_else(|| format!("self declaration {decl_id:?} not found in graph").into())
442
+ }
443
+
357
444
  /// Collect completion for an expression
358
445
  fn expression_completion<'a>(
359
446
  graph: &'a Graph,
360
- self_name_id: NameId,
447
+ self_decl_id: Option<DeclarationId>,
448
+ nesting_name_id: NameId,
361
449
  mut context: CompletionContext<'a>,
362
450
  ) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
363
- let Some(name_ref) = graph.names().get(&self_name_id) else {
364
- return Err(format!("Name {self_name_id} not found in graph").into());
451
+ let Some(name_ref) = graph.names().get(&nesting_name_id) else {
452
+ return Err(format!("Name {nesting_name_id} not found in graph").into());
365
453
  };
366
454
  let NameRef::Resolved(name_ref) = name_ref else {
367
- return Err(format!("Expected name {self_name_id} to be resolved").into());
455
+ return Err(format!("Expected name {nesting_name_id} to be resolved").into());
456
+ };
457
+ // When no explicit self is given, self is the innermost lexical scope (the nesting's own declaration).
458
+ // When explicit, follow constant aliases so callers can pass whatever the expression that set self
459
+ // resolves to without having to unwrap aliases themselves. Missing or non-namespace decls are graph
460
+ // inconsistencies and surfaced as errors.
461
+ let resolved_self_decl_id = match self_decl_id {
462
+ Some(id) => resolve_self_namespace(graph, id)?,
463
+ None => *name_ref.declaration_id(),
368
464
  };
369
- let Some(self_decl) = graph
465
+ let self_decl = graph
466
+ .declarations()
467
+ .get(&resolved_self_decl_id)
468
+ .unwrap()
469
+ .as_namespace()
470
+ .ok_or("Expected associated declaration to be a namespace")?;
471
+ let innermost_lexical_decl = graph
370
472
  .declarations()
371
473
  .get(name_ref.declaration_id())
372
- .and_then(|d| d.as_namespace())
373
- else {
374
- return Err("Expected associated declaration to be a namespace".into());
375
- };
474
+ .unwrap()
475
+ .as_namespace()
476
+ .unwrap();
477
+
376
478
  let mut candidates = Vec::new();
377
479
 
378
- // Walk the name's lexical scopes, collecting all constant completion members
379
- let mut current_name_id = Some(self_name_id);
480
+ // Collect constants. Immediate scope includes inheritance. Outer scopes only include `Object` inheritance when it's a module
481
+ collect_constants_from_lexical_scope(graph, innermost_lexical_decl, &mut context, &mut candidates);
482
+ collect_constants_from_outer_nesting(graph, name_ref, &mut context, &mut candidates);
380
483
 
381
- while let Some(id) = current_name_id {
382
- let NameRef::Resolved(name_ref) = graph.names().get(&id).unwrap() else {
383
- break;
384
- };
484
+ // Collect class variables, which are based on the inheritance chain of the attached object of the immediate lexical scope
485
+ collect_class_variables_from_lexical_scope(graph, name_ref, &mut context, &mut candidates);
486
+
487
+ // Globals are accessible from anywhere, regardless of lexical scope or `self` type.
488
+ collect_members(
489
+ graph,
490
+ *OBJECT_ID,
491
+ |d| d.as_global_variable().is_some(),
492
+ |_| true,
493
+ &mut context,
494
+ &mut candidates,
495
+ );
496
+
497
+ // Collect methods and instance variables, which are based on the inheritance chain of the `self` type (which may
498
+ // not match the immediate lexical scope)
499
+ collect_methods_and_ivars_from_self(graph, self_decl, &mut context, &mut candidates);
500
+
501
+ // Keywords are always available in expression contexts
502
+ candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
503
+ Ok(candidates)
504
+ }
505
+
506
+ /// Collects constants reachable from the innermost lexical scope's ancestor chain. Module bodies also fall back to
507
+ /// `Object`'s ancestor chain to mirror Ruby's resolution rules.
508
+ fn collect_constants_from_lexical_scope<'a>(
509
+ graph: &'a Graph,
510
+ innermost_lexical_decl: &'a Namespace,
511
+ context: &mut CompletionContext<'a>,
512
+ candidates: &mut Vec<CompletionCandidate>,
513
+ ) {
514
+ for ancestor in innermost_lexical_decl.ancestors() {
515
+ if let Ancestor::Complete(ancestor_id) = ancestor {
516
+ collect_members(
517
+ graph,
518
+ *ancestor_id,
519
+ |d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
520
+ |_| true,
521
+ context,
522
+ candidates,
523
+ );
524
+ }
525
+ }
526
+
527
+ if matches!(innermost_lexical_decl, Namespace::Module(_)) {
528
+ let object = graph.declarations().get(&OBJECT_ID).unwrap().as_namespace().unwrap();
529
+
530
+ for ancestor in object.ancestors() {
531
+ if let Ancestor::Complete(ancestor_id) = ancestor {
532
+ collect_members(
533
+ graph,
534
+ *ancestor_id,
535
+ |d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
536
+ |_| true,
537
+ context,
538
+ candidates,
539
+ );
540
+ }
541
+ }
542
+ }
543
+ }
544
+
545
+ /// Collects class variables visible from the current lexical scope. Class variables are resolved
546
+ /// lexically and singleton classes are skipped: inside `class Bar; class << Foo; @@cvar` the
547
+ /// cvar belongs to `Bar`, not `Foo`. We walk the lexical chain (innermost outward) until we find
548
+ /// a non-singleton namespace, then walk that namespace's ancestors.
549
+ fn collect_class_variables_from_lexical_scope<'a>(
550
+ graph: &'a Graph,
551
+ name_ref: &crate::model::name::ResolvedName,
552
+ context: &mut CompletionContext<'a>,
553
+ candidates: &mut Vec<CompletionCandidate>,
554
+ ) {
555
+ let mut decl = graph
556
+ .declarations()
557
+ .get(name_ref.declaration_id())
558
+ .unwrap()
559
+ .as_namespace()
560
+ .unwrap();
561
+ let mut current_name_id = *name_ref.nesting();
385
562
 
386
- let nesting_decl = graph
563
+ while matches!(decl, Namespace::SingletonClass(_)) {
564
+ let Some(parent_name_id) = current_name_id else {
565
+ // No non-singleton lexical scope (invalid Ruby). Skip cvar collection.
566
+ return;
567
+ };
568
+ let NameRef::Resolved(parent_ref) = graph.names().get(&parent_name_id).unwrap() else {
569
+ return;
570
+ };
571
+ decl = graph
387
572
  .declarations()
388
- .get(name_ref.declaration_id())
573
+ .get(parent_ref.declaration_id())
389
574
  .unwrap()
390
575
  .as_namespace()
391
576
  .unwrap();
577
+ current_name_id = *parent_ref.nesting();
578
+ }
579
+
580
+ for ancestor in decl.ancestors() {
581
+ if let Ancestor::Complete(ancestor_id) = ancestor {
582
+ collect_members(
583
+ graph,
584
+ *ancestor_id,
585
+ |d| d.as_class_variable().is_some(),
586
+ |_| true,
587
+ context,
588
+ candidates,
589
+ );
590
+ }
591
+ }
592
+ }
593
+
594
+ /// Walks the outer lexical nesting chain (excluding the innermost scope) to collect constants reachable through
595
+ /// enclosing classes/modules.
596
+ fn collect_constants_from_outer_nesting<'a>(
597
+ graph: &'a Graph,
598
+ name_ref: &crate::model::name::ResolvedName,
599
+ context: &mut CompletionContext<'a>,
600
+ candidates: &mut Vec<CompletionCandidate>,
601
+ ) {
602
+ let mut current_name_id = *name_ref.nesting();
603
+
604
+ while let Some(id) = current_name_id {
605
+ let NameRef::Resolved(parent_ref) = graph.names().get(&id).unwrap() else {
606
+ break;
607
+ };
392
608
 
393
- collect_candidates!(
609
+ collect_members(
394
610
  graph,
395
- &nesting_decl,
611
+ *parent_ref.declaration_id(),
612
+ |d| d.as_namespace().is_some() || d.as_constant().is_some() || d.as_constant_alias().is_some(),
613
+ |_| true,
396
614
  context,
397
615
  candidates,
398
- Declaration::Namespace(_) | Declaration::Constant(_) | Declaration::ConstantAlias(_)
399
616
  );
400
617
 
401
- current_name_id = *name_ref.nesting();
618
+ current_name_id = *parent_ref.nesting();
402
619
  }
620
+ }
403
621
 
404
- // Include all top level constants and globals, which are accessible everywhere
405
- let object = graph.declarations().get(&OBJECT_ID).unwrap().as_namespace().unwrap();
406
- collect_candidates!(
407
- graph,
408
- &object,
409
- context,
410
- candidates,
411
- Declaration::Namespace(_)
412
- | Declaration::Constant(_)
413
- | Declaration::ConstantAlias(_)
414
- | Declaration::GlobalVariable(_)
415
- );
416
-
417
- // Walk ancestors collecting all applicable completion members
622
+ /// Collects methods and instance variables along `self`'s ancestor chain. The chain may differ
623
+ /// from the lexical chain when `self` was rebound (e.g., `def Foo.baz` written inside `class Bar`).
624
+ fn collect_methods_and_ivars_from_self<'a>(
625
+ graph: &'a Graph,
626
+ self_decl: &'a Namespace,
627
+ context: &mut CompletionContext<'a>,
628
+ candidates: &mut Vec<CompletionCandidate>,
629
+ ) {
418
630
  for ancestor in self_decl.ancestors() {
419
631
  if let Ancestor::Complete(ancestor_id) = ancestor {
420
- let ancestor_decl = graph.declarations().get(ancestor_id).unwrap().as_namespace().unwrap();
421
- collect_candidates!(&ancestor_decl, context, candidates);
422
-
423
- // Collect class variables from the attached object, which are available at any singleton class level
424
- // within self
425
- let attached_object = graph.attached_object(ancestor_decl);
426
- collect_candidates!(
632
+ collect_members(
427
633
  graph,
428
- &attached_object,
634
+ *ancestor_id,
635
+ |d| d.as_method().is_some() || d.as_instance_variable().is_some(),
636
+ |_| true,
429
637
  context,
430
638
  candidates,
431
- Declaration::ClassVariable(_)
432
639
  );
433
640
  }
434
641
  }
435
-
436
- // Keywords are always available in expression contexts
437
- candidates.extend(keywords::KEYWORDS.iter().map(CompletionCandidate::Keyword));
438
- Ok(candidates)
439
642
  }
440
643
 
441
644
  /// Collect completion for a method argument (e.g.: `foo.bar(|)`)
442
645
  fn method_argument_completion<'a>(
443
646
  graph: &'a Graph,
444
- self_name_id: NameId,
647
+ self_decl_id: Option<DeclarationId>,
648
+ nesting_name_id: NameId,
445
649
  method_decl_id: DeclarationId,
446
650
  context: CompletionContext<'a>,
447
651
  ) -> Result<Vec<CompletionCandidate>, Box<dyn Error>> {
448
- let mut candidates = expression_completion(graph, self_name_id, context)?;
652
+ let mut candidates = expression_completion(graph, self_decl_id, nesting_name_id, context)?;
449
653
  let Some(method_decl) = graph.declarations().get(&method_decl_id) else {
450
654
  return Ok(candidates);
451
655
  };
@@ -470,6 +674,153 @@ fn method_argument_completion<'a>(
470
674
  Ok(candidates)
471
675
  }
472
676
 
677
+ /// Reasons [`find_member_in_ancestors`] could not produce a target declaration.
678
+ #[derive(Debug, PartialEq, Eq)]
679
+ pub enum FindMemberError {
680
+ /// The provided declaration id does not exist in the graph.
681
+ DeclarationNotFound,
682
+ /// The declaration exists but is not a namespace, so it has no members or ancestor chain to search.
683
+ NotNamespace,
684
+ /// The declaration is a namespace, but no matching member exists on it or any of its ancestors.
685
+ MemberNotFound,
686
+ }
687
+
688
+ /// Finds the given member on the ancestor chain of the declaration. Use `only_inherited` to skip all ancestors until
689
+ /// the main namespace and start from its parent.
690
+ ///
691
+ /// # Errors
692
+ ///
693
+ /// Returns a [`FindMemberError`] describing why no target declaration could be produced (declaration not found, not a
694
+ /// namespace, or member missing on the ancestor chain).
695
+ ///
696
+ /// # Panics
697
+ ///
698
+ /// Will panic if we incorrectly store ancestors that are not namespaces.
699
+ pub fn find_member_in_ancestors(
700
+ graph: &Graph,
701
+ declaration_id: DeclarationId,
702
+ member_str_id: StringId,
703
+ only_inherited: bool,
704
+ ) -> Result<DeclarationId, FindMemberError> {
705
+ let declaration = graph
706
+ .declarations()
707
+ .get(&declaration_id)
708
+ .ok_or(FindMemberError::DeclarationNotFound)?;
709
+ let namespace = declaration.as_namespace().ok_or(FindMemberError::NotNamespace)?;
710
+ let mut found_main_namespace = false;
711
+
712
+ for ancestor in namespace.ancestors() {
713
+ let Ancestor::Complete(ancestor_id) = ancestor else {
714
+ continue;
715
+ };
716
+
717
+ if only_inherited && !found_main_namespace {
718
+ if *ancestor_id == declaration_id {
719
+ found_main_namespace = true;
720
+ }
721
+ continue;
722
+ }
723
+
724
+ if let Some(member_id) = graph
725
+ .declarations()
726
+ .get(ancestor_id)
727
+ .unwrap()
728
+ .as_namespace()
729
+ .unwrap()
730
+ .member(&member_str_id)
731
+ {
732
+ return Ok(*member_id);
733
+ }
734
+ }
735
+
736
+ Err(FindMemberError::MemberNotFound)
737
+ }
738
+
739
+ /// Reasons [`follow_method_alias`] could not produce a target declaration.
740
+ #[derive(Debug, PartialEq, Eq)]
741
+ pub enum AliasResolutionError {
742
+ /// The provided definition id is not a `MethodAlias`.
743
+ NotAnAlias,
744
+ /// The alias's owner could not be resolved (e.g., a `ConstantReceiver` whose name never resolved to a declaration,
745
+ /// or a singleton-class chain whose attached object isn't resolvable).
746
+ UnresolvedOwner,
747
+ /// The chain of aliases forms a cycle. The chain was abandoned at the first revisit.
748
+ Cycle,
749
+ /// The alias's `old_name` does not exist on the owner or any of its ancestors.
750
+ TargetNotFound,
751
+ /// The resolved target is not a method declaration. Indicates a graph inconsistency since method-name lookups
752
+ /// should only land on `Declaration::Method`.
753
+ TargetNotMethod,
754
+ }
755
+
756
+ /// Follows `alias_id` through any chain of further `MethodAlias` definitions and returns the `DeclarationId` of the
757
+ /// final method declaration that has at least one non-alias definition (a regular `def`, `attr_*`, etc.).
758
+ ///
759
+ /// # Errors
760
+ ///
761
+ /// Returns an `AliasResolutionError` describing why the chain could not be resolved (not an alias, unresolved owner,
762
+ /// cyclic chain, target missing, or target not a method).
763
+ ///
764
+ /// # Panics
765
+ ///
766
+ /// Panics if the graph is internally inconsistent
767
+ pub fn follow_method_alias(graph: &Graph, alias_id: DefinitionId) -> Result<DeclarationId, AliasResolutionError> {
768
+ let mut seen: IdentityHashSet<DeclarationId> = IdentityHashSet::default();
769
+ let mut current = alias_id;
770
+
771
+ loop {
772
+ let Some(Definition::MethodAlias(alias)) = graph.definitions().get(&current) else {
773
+ return Err(AliasResolutionError::NotAnAlias);
774
+ };
775
+
776
+ let owner_id = graph
777
+ .definition_id_to_declaration_id(current)
778
+ .and_then(|decl_id| graph.declarations().get(decl_id))
779
+ .map(|decl| *decl.owner_id())
780
+ .ok_or(AliasResolutionError::UnresolvedOwner)?;
781
+
782
+ let target_id = match find_member_in_ancestors(graph, owner_id, *alias.old_name_str_id(), false) {
783
+ Ok(id) => id,
784
+ Err(FindMemberError::MemberNotFound) => return Err(AliasResolutionError::TargetNotFound),
785
+ Err(err @ (FindMemberError::DeclarationNotFound | FindMemberError::NotNamespace)) => {
786
+ unreachable!("alias owner must be a valid namespace declaration, got {err:?}")
787
+ }
788
+ };
789
+
790
+ if !seen.insert(target_id) {
791
+ return Err(AliasResolutionError::Cycle);
792
+ }
793
+
794
+ let Declaration::Method(target) = graph
795
+ .declarations()
796
+ .get(&target_id)
797
+ .expect("member returned by find_member_in_ancestors must exist")
798
+ else {
799
+ return Err(AliasResolutionError::TargetNotMethod);
800
+ };
801
+
802
+ // Stop at the first non-alias definition; otherwise track the smallest alias `DefinitionId` so the trace stays
803
+ // deterministic across runs. (If two aliases target different methods, we just pick one of them.)
804
+ let mut maybe_next_alias: Option<DefinitionId> = None;
805
+
806
+ for &def_id in target.definitions() {
807
+ if !matches!(
808
+ graph
809
+ .definitions()
810
+ .get(&def_id)
811
+ .expect("declaration definition_id must exist in the graph"),
812
+ Definition::MethodAlias(_),
813
+ ) {
814
+ return Ok(target_id);
815
+ }
816
+
817
+ maybe_next_alias = Some(maybe_next_alias.map_or(def_id, |m| m.min(def_id)));
818
+ }
819
+
820
+ current = maybe_next_alias.ok_or(AliasResolutionError::TargetNotFound)?;
821
+ }
822
+ }
823
+
473
824
  #[cfg(test)]
474
825
  mod tests {
475
826
  use std::str::FromStr;
@@ -523,30 +874,37 @@ mod tests {
523
874
 
524
875
  macro_rules! assert_completion_eq {
525
876
  ($context:expr, $receiver:expr, $expected:expr) => {
526
- assert_eq!(
527
- $expected,
528
- *completion_candidates($context.graph(), CompletionContext::new($receiver))
529
- .unwrap()
530
- .iter()
531
- .map(|candidate| candidate_label(&$context, candidate))
532
- .collect::<Vec<_>>()
533
- );
877
+ let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
878
+ .unwrap()
879
+ .iter()
880
+ .map(|candidate| candidate_label(&$context, candidate))
881
+ .collect();
882
+ actual.sort();
883
+
884
+ let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
885
+ expected.sort();
886
+
887
+ assert_eq!(expected, actual);
534
888
  };
535
889
  }
536
890
 
537
891
  /// Asserts declaration and keyword argument completion candidates, excluding language keywords.
538
892
  /// Language keywords are always present in expression contexts and tested separately.
893
+ /// Both sides are sorted before comparison so tests are not coupled to candidate emission order.
539
894
  macro_rules! assert_declaration_completion_eq {
540
895
  ($context:expr, $receiver:expr, $expected:expr) => {
541
- assert_eq!(
542
- $expected,
543
- *completion_candidates($context.graph(), CompletionContext::new($receiver))
544
- .unwrap()
545
- .iter()
546
- .filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
547
- .map(|candidate| candidate_label(&$context, candidate))
548
- .collect::<Vec<_>>()
549
- );
896
+ let mut actual: Vec<String> = completion_candidates($context.graph(), CompletionContext::new($receiver))
897
+ .unwrap()
898
+ .iter()
899
+ .filter(|c| !matches!(c, CompletionCandidate::Keyword(_)))
900
+ .map(|candidate| candidate_label(&$context, candidate))
901
+ .collect();
902
+ actual.sort();
903
+
904
+ let mut expected: Vec<String> = $expected.into_iter().map(String::from).collect();
905
+ expected.sort();
906
+
907
+ assert_eq!(expected, actual);
550
908
  };
551
909
  }
552
910
 
@@ -761,8 +1119,12 @@ mod tests {
761
1119
  let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
762
1120
  assert_declaration_completion_eq!(
763
1121
  context,
764
- CompletionReceiver::Expression(name_id),
1122
+ CompletionReceiver::Expression {
1123
+ self_decl_id: None,
1124
+ nesting_name_id: name_id,
1125
+ },
765
1126
  [
1127
+ "Foo::CONST",
766
1128
  "Class",
767
1129
  "BasicObject",
768
1130
  "Child",
@@ -772,7 +1134,6 @@ mod tests {
772
1134
  "Foo",
773
1135
  "Object",
774
1136
  "Child#baz()",
775
- "Foo::CONST",
776
1137
  "Foo#bar()",
777
1138
  "Parent#initialize()",
778
1139
  "Parent#@var"
@@ -807,7 +1168,10 @@ mod tests {
807
1168
  let name_id = Name::new(StringId::from("Child"), ParentScope::None, None).id();
808
1169
  assert_declaration_completion_eq!(
809
1170
  context,
810
- CompletionReceiver::Expression(name_id),
1171
+ CompletionReceiver::Expression {
1172
+ self_decl_id: None,
1173
+ nesting_name_id: name_id,
1174
+ },
811
1175
  [
812
1176
  "Class",
813
1177
  "BasicObject",
@@ -853,7 +1217,10 @@ mod tests {
853
1217
  let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
854
1218
  assert_declaration_completion_eq!(
855
1219
  context,
856
- CompletionReceiver::Expression(name_id),
1220
+ CompletionReceiver::Expression {
1221
+ self_decl_id: None,
1222
+ nesting_name_id: name_id,
1223
+ },
857
1224
  [
858
1225
  "Foo",
859
1226
  "Class",
@@ -900,7 +1267,10 @@ mod tests {
900
1267
  let name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_id), Some(foo_id)).id();
901
1268
  assert_declaration_completion_eq!(
902
1269
  context,
903
- CompletionReceiver::Expression(name_id),
1270
+ CompletionReceiver::Expression {
1271
+ self_decl_id: None,
1272
+ nesting_name_id: name_id,
1273
+ },
904
1274
  [
905
1275
  "Module",
906
1276
  "Class",
@@ -917,7 +1287,10 @@ mod tests {
917
1287
  let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
918
1288
  assert_declaration_completion_eq!(
919
1289
  context,
920
- CompletionReceiver::Expression(name_id),
1290
+ CompletionReceiver::Expression {
1291
+ self_decl_id: None,
1292
+ nesting_name_id: name_id,
1293
+ },
921
1294
  [
922
1295
  "Module",
923
1296
  "Class",
@@ -966,9 +1339,11 @@ mod tests {
966
1339
  .id();
967
1340
  assert_declaration_completion_eq!(
968
1341
  context,
969
- CompletionReceiver::Expression(name_id),
1342
+ CompletionReceiver::Expression {
1343
+ self_decl_id: None,
1344
+ nesting_name_id: name_id,
1345
+ },
970
1346
  [
971
- "Foo::CONST_A",
972
1347
  "Module",
973
1348
  "Class",
974
1349
  "Object",
@@ -976,6 +1351,7 @@ mod tests {
976
1351
  "Kernel",
977
1352
  "Foo",
978
1353
  "Bar",
1354
+ "Foo::CONST_A",
979
1355
  "Bar#bar_m()",
980
1356
  "Bar#bar_m2()"
981
1357
  ]
@@ -984,7 +1360,10 @@ mod tests {
984
1360
  let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
985
1361
  assert_declaration_completion_eq!(
986
1362
  context,
987
- CompletionReceiver::Expression(name_id),
1363
+ CompletionReceiver::Expression {
1364
+ self_decl_id: None,
1365
+ nesting_name_id: name_id,
1366
+ },
988
1367
  [
989
1368
  "Module",
990
1369
  "Class",
@@ -1025,16 +1404,19 @@ mod tests {
1025
1404
  // when the user types the unqualified name CONST
1026
1405
  assert_declaration_completion_eq!(
1027
1406
  context,
1028
- CompletionReceiver::Expression(name_id),
1407
+ CompletionReceiver::Expression {
1408
+ self_decl_id: None,
1409
+ nesting_name_id: name_id,
1410
+ },
1029
1411
  [
1030
- "Foo::CONST",
1031
- "Foo::Bar",
1032
1412
  "Class",
1033
1413
  "Object",
1034
1414
  "BasicObject",
1035
1415
  "Kernel",
1036
1416
  "Foo",
1037
1417
  "Module",
1418
+ "Foo::CONST",
1419
+ "Foo::Bar",
1038
1420
  "Foo::Bar#baz()"
1039
1421
  ]
1040
1422
  );
@@ -1069,19 +1451,11 @@ mod tests {
1069
1451
  .id();
1070
1452
  assert_declaration_completion_eq!(
1071
1453
  context,
1072
- CompletionReceiver::Expression(name_id),
1073
- [
1074
- "Foo::Bar",
1075
- "$var2",
1076
- "$var",
1077
- "BasicObject",
1078
- "Object",
1079
- "Kernel",
1080
- "Module",
1081
- "Foo",
1082
- "Class",
1083
- "Foo::Bar#bar_m()"
1084
- ]
1454
+ CompletionReceiver::Expression {
1455
+ self_decl_id: None,
1456
+ nesting_name_id: name_id,
1457
+ },
1458
+ ["Foo::Bar", "$var2", "$var", "Foo::Bar#bar_m()"]
1085
1459
  );
1086
1460
  }
1087
1461
 
@@ -1108,7 +1482,10 @@ mod tests {
1108
1482
 
1109
1483
  assert_completion_eq!(
1110
1484
  context,
1111
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1485
+ CompletionReceiver::NamespaceAccess {
1486
+ self_decl_id: None,
1487
+ namespace_decl_id: DeclarationId::from("Foo")
1488
+ },
1112
1489
  ["Foo::CONST", "Foo::Bar", "Foo::<Foo>#class_method()"]
1113
1490
  );
1114
1491
  }
@@ -1141,7 +1518,10 @@ mod tests {
1141
1518
 
1142
1519
  assert_completion_eq!(
1143
1520
  context,
1144
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
1521
+ CompletionReceiver::NamespaceAccess {
1522
+ self_decl_id: None,
1523
+ namespace_decl_id: DeclarationId::from("Child")
1524
+ },
1145
1525
  [
1146
1526
  "Child::CHILD_CONST",
1147
1527
  "Parent::PARENT_CONST",
@@ -1179,7 +1559,10 @@ mod tests {
1179
1559
 
1180
1560
  assert_completion_eq!(
1181
1561
  context,
1182
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Child")),
1562
+ CompletionReceiver::NamespaceAccess {
1563
+ self_decl_id: None,
1564
+ namespace_decl_id: DeclarationId::from("Child")
1565
+ },
1183
1566
  ["Child::CONST", "Child::<Child>#shared_method()"]
1184
1567
  );
1185
1568
  }
@@ -1202,7 +1585,10 @@ mod tests {
1202
1585
 
1203
1586
  assert_completion_eq!(
1204
1587
  context,
1205
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1588
+ CompletionReceiver::NamespaceAccess {
1589
+ self_decl_id: None,
1590
+ namespace_decl_id: DeclarationId::from("Foo")
1591
+ },
1206
1592
  ["Foo::CONST"]
1207
1593
  );
1208
1594
  }
@@ -1224,7 +1610,10 @@ mod tests {
1224
1610
 
1225
1611
  assert_completion_eq!(
1226
1612
  context,
1227
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1613
+ CompletionReceiver::NamespaceAccess {
1614
+ self_decl_id: None,
1615
+ namespace_decl_id: DeclarationId::from("Foo")
1616
+ },
1228
1617
  ["Foo::CONST", "Foo::Bar"]
1229
1618
  );
1230
1619
  }
@@ -1254,7 +1643,10 @@ mod tests {
1254
1643
 
1255
1644
  assert_completion_eq!(
1256
1645
  context,
1257
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo::MyOriginal")),
1646
+ CompletionReceiver::NamespaceAccess {
1647
+ self_decl_id: None,
1648
+ namespace_decl_id: DeclarationId::from("Foo::MyOriginal")
1649
+ },
1258
1650
  [
1259
1651
  "Original::CONST",
1260
1652
  "Original::Nested",
@@ -1286,7 +1678,10 @@ mod tests {
1286
1678
 
1287
1679
  assert_completion_eq!(
1288
1680
  context,
1289
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Alias2")),
1681
+ CompletionReceiver::NamespaceAccess {
1682
+ self_decl_id: None,
1683
+ namespace_decl_id: DeclarationId::from("Alias2")
1684
+ },
1290
1685
  ["Original::CONST", "Original::<Original>#class_method()"]
1291
1686
  );
1292
1687
  }
@@ -1313,7 +1708,10 @@ mod tests {
1313
1708
 
1314
1709
  assert_completion_eq!(
1315
1710
  context,
1316
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1711
+ CompletionReceiver::NamespaceAccess {
1712
+ self_decl_id: None,
1713
+ namespace_decl_id: DeclarationId::from("Foo")
1714
+ },
1317
1715
  ["Foo::CONST", "Foo::<Foo>#class_method()"]
1318
1716
  );
1319
1717
  }
@@ -1347,7 +1745,10 @@ mod tests {
1347
1745
 
1348
1746
  assert_completion_eq!(
1349
1747
  context,
1350
- CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo")),
1748
+ CompletionReceiver::NamespaceAccess {
1749
+ self_decl_id: None,
1750
+ namespace_decl_id: DeclarationId::from("Foo")
1751
+ },
1351
1752
  ["Foo::FOO_CONST", "Bar::CONST", "Foo::<Foo>#foo_class_method()"]
1352
1753
  );
1353
1754
  }
@@ -1375,7 +1776,10 @@ mod tests {
1375
1776
 
1376
1777
  assert_completion_eq!(
1377
1778
  context,
1378
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1779
+ CompletionReceiver::MethodCall {
1780
+ self_decl_id: None,
1781
+ receiver_decl_id: DeclarationId::from("Foo")
1782
+ },
1379
1783
  ["Foo#baz()", "Foo#bar()"]
1380
1784
  );
1381
1785
  }
@@ -1405,7 +1809,10 @@ mod tests {
1405
1809
 
1406
1810
  assert_completion_eq!(
1407
1811
  context,
1408
- CompletionReceiver::MethodCall(DeclarationId::from("Foo::MyOriginal")),
1812
+ CompletionReceiver::MethodCall {
1813
+ self_decl_id: None,
1814
+ receiver_decl_id: DeclarationId::from("Foo::MyOriginal")
1815
+ },
1409
1816
  ["Original#baz()", "Original#bar()"]
1410
1817
  );
1411
1818
  }
@@ -1430,7 +1837,10 @@ mod tests {
1430
1837
 
1431
1838
  assert_completion_eq!(
1432
1839
  context,
1433
- CompletionReceiver::MethodCall(DeclarationId::from("Child")),
1840
+ CompletionReceiver::MethodCall {
1841
+ self_decl_id: None,
1842
+ receiver_decl_id: DeclarationId::from("Child")
1843
+ },
1434
1844
  ["Child#child_method()", "Parent#parent_method()"]
1435
1845
  );
1436
1846
  }
@@ -1457,7 +1867,10 @@ mod tests {
1457
1867
 
1458
1868
  assert_completion_eq!(
1459
1869
  context,
1460
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1870
+ CompletionReceiver::MethodCall {
1871
+ self_decl_id: None,
1872
+ receiver_decl_id: DeclarationId::from("Foo")
1873
+ },
1461
1874
  ["Foo#foo_method()", "Mixin#mixin_method()"]
1462
1875
  );
1463
1876
  }
@@ -1484,7 +1897,10 @@ mod tests {
1484
1897
 
1485
1898
  assert_completion_eq!(
1486
1899
  context,
1487
- CompletionReceiver::MethodCall(DeclarationId::from("Child")),
1900
+ CompletionReceiver::MethodCall {
1901
+ self_decl_id: None,
1902
+ receiver_decl_id: DeclarationId::from("Child")
1903
+ },
1488
1904
  ["Child#shared_method()", "Child#child_only()", "Parent#parent_only()"]
1489
1905
  );
1490
1906
  }
@@ -1512,7 +1928,10 @@ mod tests {
1512
1928
 
1513
1929
  assert_completion_eq!(
1514
1930
  context,
1515
- CompletionReceiver::MethodCall(DeclarationId::from("Foo")),
1931
+ CompletionReceiver::MethodCall {
1932
+ self_decl_id: None,
1933
+ receiver_decl_id: DeclarationId::from("Foo")
1934
+ },
1516
1935
  ["Foo#initialize()", "Foo#bar()"]
1517
1936
  );
1518
1937
  }
@@ -1537,7 +1956,10 @@ mod tests {
1537
1956
 
1538
1957
  assert_completion_eq!(
1539
1958
  context,
1540
- CompletionReceiver::MethodCall(DeclarationId::from("Foo::<Foo>")),
1959
+ CompletionReceiver::MethodCall {
1960
+ self_decl_id: None,
1961
+ receiver_decl_id: DeclarationId::from("Foo::<Foo>")
1962
+ },
1541
1963
  ["Foo::<Foo>#baz()", "Foo::<Foo>#bar()"]
1542
1964
  );
1543
1965
  }
@@ -1560,7 +1982,8 @@ mod tests {
1560
1982
  assert_declaration_completion_eq!(
1561
1983
  context,
1562
1984
  CompletionReceiver::MethodArgument {
1563
- self_name_id: name_id,
1985
+ self_decl_id: None,
1986
+ nesting_name_id: name_id,
1564
1987
  method_decl_id: DeclarationId::from("Foo#greet()"),
1565
1988
  },
1566
1989
  [
@@ -1595,7 +2018,8 @@ mod tests {
1595
2018
  assert_declaration_completion_eq!(
1596
2019
  context,
1597
2020
  CompletionReceiver::MethodArgument {
1598
- self_name_id: name_id,
2021
+ self_decl_id: None,
2022
+ nesting_name_id: name_id,
1599
2023
  method_decl_id: DeclarationId::from("Foo#bar()"),
1600
2024
  },
1601
2025
  ["Class", "Object", "BasicObject", "Kernel", "Foo", "Module", "Foo#bar()"]
@@ -1620,7 +2044,8 @@ mod tests {
1620
2044
  assert_declaration_completion_eq!(
1621
2045
  context,
1622
2046
  CompletionReceiver::MethodArgument {
1623
- self_name_id: name_id,
2047
+ self_decl_id: None,
2048
+ nesting_name_id: name_id,
1624
2049
  method_decl_id: DeclarationId::from("Foo#search()"),
1625
2050
  },
1626
2051
  // Only RequiredKeyword and OptionalKeyword, not RestKeyword (**opts)
@@ -1663,7 +2088,8 @@ mod tests {
1663
2088
  assert_declaration_completion_eq!(
1664
2089
  context,
1665
2090
  CompletionReceiver::MethodArgument {
1666
- self_name_id: name_id,
2091
+ self_decl_id: None,
2092
+ nesting_name_id: name_id,
1667
2093
  method_decl_id: DeclarationId::from("Foo#bar()"),
1668
2094
  },
1669
2095
  [
@@ -1689,7 +2115,10 @@ mod tests {
1689
2115
  let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
1690
2116
  assert_completion_eq!(
1691
2117
  context,
1692
- CompletionReceiver::Expression(name_id),
2118
+ CompletionReceiver::Expression {
2119
+ self_decl_id: None,
2120
+ nesting_name_id: name_id,
2121
+ },
1693
2122
  [
1694
2123
  "Class",
1695
2124
  "Object",
@@ -1752,7 +2181,8 @@ mod tests {
1752
2181
  assert_completion_eq!(
1753
2182
  context,
1754
2183
  CompletionReceiver::MethodArgument {
1755
- self_name_id: name_id,
2184
+ self_decl_id: None,
2185
+ nesting_name_id: name_id,
1756
2186
  method_decl_id: DeclarationId::from("Foo#bar()"),
1757
2187
  },
1758
2188
  [
@@ -1817,7 +2247,10 @@ mod tests {
1817
2247
 
1818
2248
  let candidates = completion_candidates(
1819
2249
  context.graph(),
1820
- CompletionContext::new(CompletionReceiver::NamespaceAccess(DeclarationId::from("Foo"))),
2250
+ CompletionContext::new(CompletionReceiver::NamespaceAccess {
2251
+ self_decl_id: None,
2252
+ namespace_decl_id: DeclarationId::from("Foo"),
2253
+ }),
1821
2254
  )
1822
2255
  .unwrap();
1823
2256
 
@@ -1832,10 +2265,1490 @@ mod tests {
1832
2265
 
1833
2266
  let candidates = completion_candidates(
1834
2267
  context.graph(),
1835
- CompletionContext::new(CompletionReceiver::MethodCall(DeclarationId::from("Foo"))),
2268
+ CompletionContext::new(CompletionReceiver::MethodCall {
2269
+ self_decl_id: None,
2270
+ receiver_decl_id: DeclarationId::from("Foo"),
2271
+ }),
1836
2272
  )
1837
2273
  .unwrap();
1838
2274
 
1839
2275
  assert!(!candidates.iter().any(|c| matches!(c, CompletionCandidate::Keyword(_))));
1840
2276
  }
2277
+
2278
+ #[test]
2279
+ fn expression_completion_class_variables_follow_lexical_scope() {
2280
+ // `@@cvar` in Ruby is resolved via the innermost lexical class/module's ancestor chain,
2281
+ // NOT `self`'s ancestor chain. Inside `def Foo.bar` written inside `Outer`, the lexical
2282
+ // scope is `[Outer]`, so `@@outer_cvar` is reachable and `@@foo_cvar` (which lives on
2283
+ // `Foo`, not on any lexical ancestor) must not be offered.
2284
+ let mut context = GraphTest::new();
2285
+
2286
+ context.index_uri(
2287
+ "file:///foo.rb",
2288
+ "
2289
+ module Outer
2290
+ @@outer_cvar = 1
2291
+
2292
+ class Foo
2293
+ @@foo_cvar = 2
2294
+ def self.singleton_m; end
2295
+ end
2296
+ end
2297
+ ",
2298
+ );
2299
+ context.resolve();
2300
+
2301
+ let outer_name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2302
+ assert_declaration_completion_eq!(
2303
+ context,
2304
+ CompletionReceiver::Expression {
2305
+ self_decl_id: Some(DeclarationId::from("Outer::Foo::<Foo>")),
2306
+ nesting_name_id: outer_name_id,
2307
+ },
2308
+ [
2309
+ "Module",
2310
+ "Class",
2311
+ "Object",
2312
+ "BasicObject",
2313
+ "Kernel",
2314
+ "Outer",
2315
+ "Outer::Foo",
2316
+ "Outer#@@outer_cvar",
2317
+ "Outer::Foo::<Foo>#singleton_m()"
2318
+ ]
2319
+ );
2320
+ }
2321
+
2322
+ #[test]
2323
+ fn expression_completion_follows_self_decl_alias() {
2324
+ let mut context = GraphTest::new();
2325
+
2326
+ context.index_uri(
2327
+ "file:///foo.rb",
2328
+ "
2329
+ module Outer
2330
+ class Original
2331
+ def original_m; end
2332
+ end
2333
+
2334
+ MyAlias = Original
2335
+ end
2336
+ ",
2337
+ );
2338
+ context.resolve();
2339
+
2340
+ let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2341
+ // `self_decl_id` points to the alias `Outer::MyAlias`, which is a `ConstantAlias` rather than a `Namespace`.
2342
+ // The completion should still collect members from the aliased namespace (`Outer::Original`) instead of
2343
+ // returning an error, so callers do not have to unwrap aliases themselves.
2344
+ assert_declaration_completion_eq!(
2345
+ context,
2346
+ CompletionReceiver::Expression {
2347
+ self_decl_id: Some(DeclarationId::from("Outer::MyAlias")),
2348
+ nesting_name_id: name_id,
2349
+ },
2350
+ [
2351
+ "Outer::MyAlias",
2352
+ "Outer::Original",
2353
+ "Class",
2354
+ "Object",
2355
+ "BasicObject",
2356
+ "Outer",
2357
+ "Kernel",
2358
+ "Module",
2359
+ "Outer::Original#original_m()"
2360
+ ]
2361
+ );
2362
+ }
2363
+
2364
+ #[test]
2365
+ fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_class_variables() {
2366
+ let mut context = GraphTest::new();
2367
+
2368
+ context.index_uri(
2369
+ "file:///foo.rb",
2370
+ "
2371
+ class Foo
2372
+ @@class_var = 1
2373
+ end
2374
+
2375
+ class Bar
2376
+ @@other_class_var = 2
2377
+
2378
+ def Foo.baz
2379
+ # completion here
2380
+ end
2381
+ end
2382
+ ",
2383
+ );
2384
+ context.resolve();
2385
+
2386
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2387
+ assert_declaration_completion_eq!(
2388
+ context,
2389
+ CompletionReceiver::Expression {
2390
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2391
+ nesting_name_id: name_id,
2392
+ },
2393
+ [
2394
+ "Module",
2395
+ "Class",
2396
+ "Object",
2397
+ "BasicObject",
2398
+ "Kernel",
2399
+ "Foo",
2400
+ "Bar",
2401
+ "Foo::<Foo>#baz()",
2402
+ "Bar#@@other_class_var"
2403
+ ]
2404
+ );
2405
+ }
2406
+
2407
+ #[test]
2408
+ fn expression_completion_in_method_definition_with_receiver_uses_lexical_scope_for_constants() {
2409
+ let mut context = GraphTest::new();
2410
+
2411
+ context.index_uri(
2412
+ "file:///foo.rb",
2413
+ "
2414
+ class Foo
2415
+ CONST = 1
2416
+ end
2417
+
2418
+ class Bar
2419
+ OTHER_CONST = 2
2420
+
2421
+ def Foo.baz
2422
+ # completion here
2423
+ end
2424
+ end
2425
+ ",
2426
+ );
2427
+ context.resolve();
2428
+
2429
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2430
+ assert_declaration_completion_eq!(
2431
+ context,
2432
+ CompletionReceiver::Expression {
2433
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2434
+ nesting_name_id: name_id,
2435
+ },
2436
+ [
2437
+ "Bar::OTHER_CONST",
2438
+ "Module",
2439
+ "Class",
2440
+ "Object",
2441
+ "BasicObject",
2442
+ "Kernel",
2443
+ "Foo",
2444
+ "Bar",
2445
+ "Foo::<Foo>#baz()"
2446
+ ]
2447
+ );
2448
+ }
2449
+
2450
+ #[test]
2451
+ fn expression_completion_does_not_leak_constants_reachable_only_through_self_ancestors() {
2452
+ let mut context = GraphTest::new();
2453
+
2454
+ context.index_uri(
2455
+ "file:///foo.rb",
2456
+ "
2457
+ module Mixin
2458
+ MIXIN_CONST = 1
2459
+ end
2460
+
2461
+ class Foo
2462
+ extend Mixin
2463
+ end
2464
+
2465
+ class Bar
2466
+ def Foo.baz
2467
+ # completion here
2468
+ end
2469
+ end
2470
+ ",
2471
+ );
2472
+ context.resolve();
2473
+
2474
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2475
+ assert_declaration_completion_eq!(
2476
+ context,
2477
+ CompletionReceiver::Expression {
2478
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2479
+ nesting_name_id: name_id,
2480
+ },
2481
+ [
2482
+ "Class",
2483
+ "BasicObject",
2484
+ "Mixin",
2485
+ "Object",
2486
+ "Kernel",
2487
+ "Module",
2488
+ "Foo",
2489
+ "Bar",
2490
+ "Foo::<Foo>#baz()"
2491
+ ]
2492
+ );
2493
+ }
2494
+
2495
+ #[test]
2496
+ fn expression_completion_at_top_level_includes_object_constants() {
2497
+ let mut context = GraphTest::new();
2498
+
2499
+ context.index_uri(
2500
+ "file:///foo.rb",
2501
+ "
2502
+ class Foo
2503
+ CONST = 1
2504
+ end
2505
+
2506
+ TOP_CONST = 2
2507
+ ",
2508
+ );
2509
+ context.resolve();
2510
+
2511
+ let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
2512
+ assert_declaration_completion_eq!(
2513
+ context,
2514
+ CompletionReceiver::Expression {
2515
+ self_decl_id: None,
2516
+ nesting_name_id: name_id,
2517
+ },
2518
+ ["Module", "Class", "Object", "BasicObject", "Kernel", "Foo", "TOP_CONST"]
2519
+ );
2520
+ }
2521
+
2522
+ #[test]
2523
+ fn expression_completion_in_module_body_falls_back_to_object_constants() {
2524
+ let mut context = GraphTest::new();
2525
+
2526
+ context.index_uri(
2527
+ "file:///foo.rb",
2528
+ "
2529
+ TOP_CONST = 1
2530
+
2531
+ module Mod
2532
+ MOD_CONST = 2
2533
+ end
2534
+ ",
2535
+ );
2536
+ context.resolve();
2537
+
2538
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2539
+ assert_declaration_completion_eq!(
2540
+ context,
2541
+ CompletionReceiver::Expression {
2542
+ self_decl_id: None,
2543
+ nesting_name_id: name_id,
2544
+ },
2545
+ [
2546
+ "Mod::MOD_CONST",
2547
+ "Module",
2548
+ "Class",
2549
+ "Object",
2550
+ "BasicObject",
2551
+ "Kernel",
2552
+ "TOP_CONST",
2553
+ "Mod"
2554
+ ]
2555
+ );
2556
+ }
2557
+
2558
+ #[test]
2559
+ fn expression_completion_in_module_body_falls_back_to_object_ancestors() {
2560
+ let mut context = GraphTest::new();
2561
+
2562
+ context.index_uri(
2563
+ "file:///foo.rb",
2564
+ "
2565
+ module Kernel
2566
+ CONST = 1
2567
+ end
2568
+
2569
+ module Mod
2570
+ # completion here
2571
+ end
2572
+ ",
2573
+ );
2574
+ context.resolve();
2575
+
2576
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2577
+ assert_declaration_completion_eq!(
2578
+ context,
2579
+ CompletionReceiver::Expression {
2580
+ self_decl_id: None,
2581
+ nesting_name_id: name_id,
2582
+ },
2583
+ [
2584
+ "Class",
2585
+ "Object",
2586
+ "Kernel",
2587
+ "BasicObject",
2588
+ "Mod",
2589
+ "Module",
2590
+ "Kernel::CONST",
2591
+ ]
2592
+ );
2593
+ }
2594
+
2595
+ #[test]
2596
+ fn expression_completion_at_top_level_offers_methods_from_object_chain() {
2597
+ let mut context = GraphTest::new();
2598
+
2599
+ context.index_uri(
2600
+ "file:///foo.rb",
2601
+ "
2602
+ def my_top_method; end
2603
+
2604
+ module Kernel
2605
+ def kernel_helper; end
2606
+ end
2607
+ ",
2608
+ );
2609
+ context.resolve();
2610
+
2611
+ let name_id = Name::new(StringId::from("Object"), ParentScope::None, None).id();
2612
+ assert_declaration_completion_eq!(
2613
+ context,
2614
+ CompletionReceiver::Expression {
2615
+ self_decl_id: None,
2616
+ nesting_name_id: name_id,
2617
+ },
2618
+ [
2619
+ "Object",
2620
+ "Kernel",
2621
+ "BasicObject",
2622
+ "Module",
2623
+ "Class",
2624
+ "Object#my_top_method()",
2625
+ "Kernel#kernel_helper()"
2626
+ ]
2627
+ );
2628
+ }
2629
+
2630
+ #[test]
2631
+ fn expression_completion_in_basic_object_subclass_excludes_object_constants() {
2632
+ let mut context = GraphTest::new();
2633
+
2634
+ context.index_uri(
2635
+ "file:///foo.rb",
2636
+ "
2637
+ TOP_CONST = 1
2638
+
2639
+ class Bar < BasicObject
2640
+ BAR_CONST = 2
2641
+ end
2642
+ ",
2643
+ );
2644
+ context.resolve();
2645
+
2646
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2647
+ assert_declaration_completion_eq!(
2648
+ context,
2649
+ CompletionReceiver::Expression {
2650
+ self_decl_id: None,
2651
+ nesting_name_id: name_id,
2652
+ },
2653
+ ["Bar::BAR_CONST"]
2654
+ );
2655
+ }
2656
+
2657
+ #[test]
2658
+ fn expression_completion_class_variables_in_singleton_class_block_use_outer_lexical_scope() {
2659
+ let mut context = GraphTest::new();
2660
+
2661
+ context.index_uri(
2662
+ "file:///foo.rb",
2663
+ "
2664
+ class Foo
2665
+ @@foo_cvar = 1
2666
+ end
2667
+
2668
+ class Bar
2669
+ @@bar_cvar = 2
2670
+
2671
+ class << Foo
2672
+ # completion here
2673
+ end
2674
+ end
2675
+ ",
2676
+ );
2677
+ context.resolve();
2678
+
2679
+ let bar_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2680
+ let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, Some(bar_id)).id();
2681
+ let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), Some(bar_id)).id();
2682
+
2683
+ assert_declaration_completion_eq!(
2684
+ context,
2685
+ CompletionReceiver::Expression {
2686
+ self_decl_id: None,
2687
+ nesting_name_id,
2688
+ },
2689
+ ["Bar#@@bar_cvar"]
2690
+ );
2691
+ }
2692
+
2693
+ #[test]
2694
+ fn expression_completion_in_def_self_method_inside_module_body() {
2695
+ let mut context = GraphTest::new();
2696
+
2697
+ context.index_uri(
2698
+ "file:///foo.rb",
2699
+ "
2700
+ module Mod
2701
+ MOD_CONST = 1
2702
+
2703
+ def self.helper
2704
+ # completion here
2705
+ end
2706
+ end
2707
+ ",
2708
+ );
2709
+ context.resolve();
2710
+
2711
+ let name_id = Name::new(StringId::from("Mod"), ParentScope::None, None).id();
2712
+ assert_declaration_completion_eq!(
2713
+ context,
2714
+ CompletionReceiver::Expression {
2715
+ self_decl_id: Some(DeclarationId::from("Mod::<Mod>")),
2716
+ nesting_name_id: name_id,
2717
+ },
2718
+ [
2719
+ "Mod::MOD_CONST",
2720
+ "Module",
2721
+ "Class",
2722
+ "Object",
2723
+ "BasicObject",
2724
+ "Kernel",
2725
+ "Mod",
2726
+ "Mod::<Mod>#helper()"
2727
+ ]
2728
+ );
2729
+ }
2730
+
2731
+ #[test]
2732
+ fn expression_completion_in_singleton_class_block_at_top_level() {
2733
+ let mut context = GraphTest::new();
2734
+
2735
+ context.index_uri(
2736
+ "file:///foo.rb",
2737
+ "
2738
+ class Foo
2739
+ @@foo_cvar = 1
2740
+ end
2741
+
2742
+ class << Foo
2743
+ # completion here
2744
+ end
2745
+ ",
2746
+ );
2747
+ context.resolve();
2748
+
2749
+ let foo_ref_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2750
+ let nesting_name_id = Name::new(StringId::from("<Foo>"), ParentScope::Attached(foo_ref_id), None).id();
2751
+
2752
+ assert_declaration_completion_eq!(
2753
+ context,
2754
+ CompletionReceiver::Expression {
2755
+ self_decl_id: None,
2756
+ nesting_name_id,
2757
+ },
2758
+ ["Module", "Class", "Object", "BasicObject", "Kernel", "Foo"]
2759
+ );
2760
+ }
2761
+
2762
+ #[test]
2763
+ fn expression_completion_errors_when_self_decl_id_does_not_exist() {
2764
+ let mut context = GraphTest::new();
2765
+
2766
+ context.index_uri(
2767
+ "file:///foo.rb",
2768
+ "
2769
+ class Foo
2770
+ end
2771
+ ",
2772
+ );
2773
+ context.resolve();
2774
+
2775
+ let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2776
+ let result = completion_candidates(
2777
+ context.graph(),
2778
+ CompletionContext::new(CompletionReceiver::Expression {
2779
+ self_decl_id: Some(DeclarationId::from("Nonexistent")),
2780
+ nesting_name_id: name_id,
2781
+ }),
2782
+ );
2783
+
2784
+ assert!(result.is_err(), "missing self_decl_id should surface as an error");
2785
+ }
2786
+
2787
+ #[test]
2788
+ fn expression_completion_errors_when_self_decl_id_is_not_a_namespace() {
2789
+ let mut context = GraphTest::new();
2790
+
2791
+ context.index_uri(
2792
+ "file:///foo.rb",
2793
+ "
2794
+ class Foo
2795
+ CONST = 1
2796
+ end
2797
+ ",
2798
+ );
2799
+ context.resolve();
2800
+
2801
+ let name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2802
+ let result = completion_candidates(
2803
+ context.graph(),
2804
+ CompletionContext::new(CompletionReceiver::Expression {
2805
+ // CONST resolves to a `Constant`, not a `Namespace` and not an alias.
2806
+ self_decl_id: Some(DeclarationId::from("Foo::CONST")),
2807
+ nesting_name_id: name_id,
2808
+ }),
2809
+ );
2810
+
2811
+ assert!(result.is_err(), "non-namespace self_decl_id should surface as an error");
2812
+ }
2813
+
2814
+ #[test]
2815
+ fn expression_completion_follows_chained_self_decl_alias() {
2816
+ let mut context = GraphTest::new();
2817
+
2818
+ context.index_uri(
2819
+ "file:///foo.rb",
2820
+ "
2821
+ module Outer
2822
+ class Original
2823
+ def original_m; end
2824
+ end
2825
+
2826
+ FirstAlias = Original
2827
+ SecondAlias = FirstAlias
2828
+ end
2829
+ ",
2830
+ );
2831
+ context.resolve();
2832
+
2833
+ let name_id = Name::new(StringId::from("Outer"), ParentScope::None, None).id();
2834
+ assert_declaration_completion_eq!(
2835
+ context,
2836
+ CompletionReceiver::Expression {
2837
+ self_decl_id: Some(DeclarationId::from("Outer::SecondAlias")),
2838
+ nesting_name_id: name_id,
2839
+ },
2840
+ [
2841
+ "Outer::FirstAlias",
2842
+ "Outer::SecondAlias",
2843
+ "Outer::Original",
2844
+ "Module",
2845
+ "Class",
2846
+ "Object",
2847
+ "BasicObject",
2848
+ "Outer",
2849
+ "Kernel",
2850
+ "Outer::Original#original_m()"
2851
+ ]
2852
+ );
2853
+ }
2854
+
2855
+ #[test]
2856
+ fn expression_completion_includes_private_singleton_method_when_self_matches_owner() {
2857
+ let mut context = GraphTest::new();
2858
+
2859
+ context.index_uri(
2860
+ "file:///foo.rb",
2861
+ "
2862
+ class Foo
2863
+ def self.bar; end
2864
+ private_class_method :bar
2865
+ end
2866
+
2867
+ class Bar
2868
+ def Foo.baz
2869
+ # completion here
2870
+ end
2871
+ end
2872
+ ",
2873
+ );
2874
+ context.resolve();
2875
+
2876
+ let name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2877
+ assert_declaration_completion_eq!(
2878
+ context,
2879
+ CompletionReceiver::Expression {
2880
+ self_decl_id: Some(DeclarationId::from("Foo::<Foo>")),
2881
+ nesting_name_id: name_id,
2882
+ },
2883
+ [
2884
+ "Module",
2885
+ "Class",
2886
+ "Object",
2887
+ "BasicObject",
2888
+ "Kernel",
2889
+ "Foo",
2890
+ "Bar",
2891
+ "Foo::<Foo>#bar()",
2892
+ "Foo::<Foo>#baz()"
2893
+ ]
2894
+ );
2895
+ }
2896
+
2897
+ #[test]
2898
+ fn method_call_completion_excludes_private_method_for_external_call() {
2899
+ let mut context = GraphTest::new();
2900
+
2901
+ context.index_uri(
2902
+ "file:///foo.rb",
2903
+ "
2904
+ class Foo
2905
+ private
2906
+
2907
+ def bar; end
2908
+ end
2909
+ ",
2910
+ );
2911
+ context.resolve();
2912
+
2913
+ assert_completion_eq!(
2914
+ context,
2915
+ CompletionReceiver::MethodCall {
2916
+ self_decl_id: None,
2917
+ receiver_decl_id: DeclarationId::from("Foo")
2918
+ },
2919
+ [] as [&str; 0]
2920
+ );
2921
+ }
2922
+
2923
+ #[test]
2924
+ fn expression_completion_includes_private_instance_method_inside_self_ancestor_chain() {
2925
+ let mut context = GraphTest::new();
2926
+
2927
+ context.index_uri(
2928
+ "file:///foo.rb",
2929
+ "
2930
+ class Foo
2931
+ def baz
2932
+ # completion here
2933
+ end
2934
+
2935
+ private
2936
+
2937
+ def bar; end
2938
+ end
2939
+
2940
+ class Bar < Foo
2941
+ def qux
2942
+ # completion here
2943
+ end
2944
+ end
2945
+ ",
2946
+ );
2947
+ context.resolve();
2948
+
2949
+ let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
2950
+ assert_declaration_completion_eq!(
2951
+ context,
2952
+ CompletionReceiver::Expression {
2953
+ self_decl_id: None,
2954
+ nesting_name_id: foo_name_id,
2955
+ },
2956
+ [
2957
+ "Module",
2958
+ "Class",
2959
+ "Object",
2960
+ "BasicObject",
2961
+ "Kernel",
2962
+ "Foo",
2963
+ "Bar",
2964
+ "Foo#baz()",
2965
+ "Foo#bar()"
2966
+ ]
2967
+ );
2968
+
2969
+ let bar_name_id = Name::new(StringId::from("Bar"), ParentScope::None, None).id();
2970
+ assert_declaration_completion_eq!(
2971
+ context,
2972
+ CompletionReceiver::Expression {
2973
+ self_decl_id: None,
2974
+ nesting_name_id: bar_name_id,
2975
+ },
2976
+ [
2977
+ "Module",
2978
+ "Class",
2979
+ "Object",
2980
+ "BasicObject",
2981
+ "Kernel",
2982
+ "Foo",
2983
+ "Bar",
2984
+ "Foo#baz()",
2985
+ "Foo#bar()",
2986
+ "Bar#qux()"
2987
+ ]
2988
+ );
2989
+ }
2990
+
2991
+ #[test]
2992
+ fn method_call_completion_includes_protected_method_when_caller_shares_ancestor_with_receiver() {
2993
+ let mut context = GraphTest::new();
2994
+
2995
+ context.index_uri(
2996
+ "file:///foo.rb",
2997
+ "
2998
+ class Account
2999
+ protected
3000
+
3001
+ def balance; end
3002
+ end
3003
+
3004
+ class Savings < Account
3005
+ end
3006
+
3007
+ class Checking < Account
3008
+ end
3009
+ ",
3010
+ );
3011
+ context.resolve();
3012
+
3013
+ // Caller's self is `Account` (or any descendant). Both caller and receiver descend from
3014
+ // the defining class, satisfying MRI's `caller.class <= defined_class && recv.class <= defined_class`.
3015
+ assert_completion_eq!(
3016
+ context,
3017
+ CompletionReceiver::MethodCall {
3018
+ self_decl_id: Some(DeclarationId::from("Account")),
3019
+ receiver_decl_id: DeclarationId::from("Savings"),
3020
+ },
3021
+ ["Account#balance()"]
3022
+ );
3023
+
3024
+ assert_completion_eq!(
3025
+ context,
3026
+ CompletionReceiver::MethodCall {
3027
+ self_decl_id: Some(DeclarationId::from("Savings")),
3028
+ receiver_decl_id: DeclarationId::from("Checking"),
3029
+ },
3030
+ ["Account#balance()"]
3031
+ );
3032
+ }
3033
+
3034
+ #[test]
3035
+ fn method_call_completion_excludes_protected_method_when_caller_does_not_share_ancestor() {
3036
+ let mut context = GraphTest::new();
3037
+ context.index_uri(
3038
+ "file:///foo.rb",
3039
+ "
3040
+ class Account
3041
+ protected
3042
+
3043
+ def balance; end
3044
+ end
3045
+
3046
+ class Unrelated
3047
+ end
3048
+ ",
3049
+ );
3050
+ context.resolve();
3051
+
3052
+ // Receiver is `Account`; caller's self is `Unrelated`. Caller is not a descendant of the
3053
+ // defining class, so the protected check fails and `balance` is hidden.
3054
+ assert_completion_eq!(
3055
+ context,
3056
+ CompletionReceiver::MethodCall {
3057
+ self_decl_id: Some(DeclarationId::from("Unrelated")),
3058
+ receiver_decl_id: DeclarationId::from("Account"),
3059
+ },
3060
+ [] as [&str; 0]
3061
+ );
3062
+ }
3063
+
3064
+ #[test]
3065
+ fn method_call_completion_includes_private_method_when_receiver_is_self() {
3066
+ let mut context = GraphTest::new();
3067
+ context.index_uri(
3068
+ "file:///foo.rb",
3069
+ "
3070
+ class Foo
3071
+ def public_method; end
3072
+
3073
+ private
3074
+
3075
+ def private_method; end
3076
+ end
3077
+ ",
3078
+ );
3079
+ context.resolve();
3080
+
3081
+ // Caller's self is `Foo`, matching the receiver — `private_method` becomes visible
3082
+ // (Ruby 3.0+ allows `self.foo` for private methods, and our completion treats receiver-equals-self as the
3083
+ // implicit-receiver case).
3084
+ assert_completion_eq!(
3085
+ context,
3086
+ CompletionReceiver::MethodCall {
3087
+ self_decl_id: Some(DeclarationId::from("Foo")),
3088
+ receiver_decl_id: DeclarationId::from("Foo"),
3089
+ },
3090
+ ["Foo#public_method()", "Foo#private_method()"]
3091
+ );
3092
+ }
3093
+
3094
+ #[test]
3095
+ fn method_call_completion_includes_protected_method_through_included_module() {
3096
+ let mut context = GraphTest::new();
3097
+ context.index_uri(
3098
+ "file:///foo.rb",
3099
+ "
3100
+ module Sharable
3101
+ protected
3102
+
3103
+ def shared_secret; end
3104
+ end
3105
+
3106
+ class A
3107
+ include Sharable
3108
+ end
3109
+
3110
+ class B
3111
+ include Sharable
3112
+ end
3113
+ ",
3114
+ );
3115
+ context.resolve();
3116
+
3117
+ assert_completion_eq!(
3118
+ context,
3119
+ CompletionReceiver::MethodCall {
3120
+ self_decl_id: Some(DeclarationId::from("A")),
3121
+ receiver_decl_id: DeclarationId::from("B"),
3122
+ },
3123
+ ["Sharable#shared_secret()"]
3124
+ );
3125
+ }
3126
+
3127
+ #[test]
3128
+ fn method_call_completion_excludes_protected_when_caller_class_is_not_descendant_of_defined_class() {
3129
+ let mut context = GraphTest::new();
3130
+ context.index_uri(
3131
+ "file:///foo.rb",
3132
+ "
3133
+ class Animal
3134
+ end
3135
+
3136
+ class Dog < Animal
3137
+ protected
3138
+
3139
+ def secret_trick; end
3140
+ end
3141
+ ",
3142
+ );
3143
+ context.resolve();
3144
+
3145
+ assert_completion_eq!(
3146
+ context,
3147
+ CompletionReceiver::MethodCall {
3148
+ self_decl_id: Some(DeclarationId::from("Animal")),
3149
+ receiver_decl_id: DeclarationId::from("Dog"),
3150
+ },
3151
+ [] as [&str; 0]
3152
+ );
3153
+ }
3154
+
3155
+ #[test]
3156
+ fn method_call_completion_excludes_visibility_restricted_methods_at_top_level() {
3157
+ let mut context = GraphTest::new();
3158
+ context.index_uri(
3159
+ "file:///foo.rb",
3160
+ "
3161
+ class Foo
3162
+ def pub_inst; end
3163
+
3164
+ protected
3165
+
3166
+ def prot_inst; end
3167
+
3168
+ private
3169
+
3170
+ def priv_inst; end
3171
+ end
3172
+ ",
3173
+ );
3174
+ context.resolve();
3175
+
3176
+ assert_completion_eq!(
3177
+ context,
3178
+ CompletionReceiver::MethodCall {
3179
+ self_decl_id: None,
3180
+ receiver_decl_id: DeclarationId::from("Foo"),
3181
+ },
3182
+ ["Foo#pub_inst()"]
3183
+ );
3184
+ }
3185
+
3186
+ #[test]
3187
+ fn method_call_completion_hides_method_when_subclass_overrides_with_stricter_visibility() {
3188
+ let mut context = GraphTest::new();
3189
+ context.index_uri(
3190
+ "file:///foo.rb",
3191
+ "
3192
+ class Parent
3193
+ def foo; end
3194
+ end
3195
+
3196
+ class Child < Parent
3197
+ private
3198
+
3199
+ def foo; end
3200
+ end
3201
+ ",
3202
+ );
3203
+ context.resolve();
3204
+
3205
+ assert_completion_eq!(
3206
+ context,
3207
+ CompletionReceiver::MethodCall {
3208
+ self_decl_id: None,
3209
+ receiver_decl_id: DeclarationId::from("Child"),
3210
+ },
3211
+ [] as [&str; 0]
3212
+ );
3213
+ }
3214
+
3215
+ #[test]
3216
+ fn namespace_access_completion_excludes_private_constant() {
3217
+ let mut context = GraphTest::new();
3218
+ context.index_uri(
3219
+ "file:///foo.rb",
3220
+ "
3221
+ class Foo
3222
+ PUB = 1
3223
+ PRIV = 2
3224
+ private_constant :PRIV
3225
+ end
3226
+ ",
3227
+ );
3228
+ context.resolve();
3229
+
3230
+ assert_completion_eq!(
3231
+ context,
3232
+ CompletionReceiver::NamespaceAccess {
3233
+ self_decl_id: None,
3234
+ namespace_decl_id: DeclarationId::from("Foo"),
3235
+ },
3236
+ ["Foo::PUB"]
3237
+ );
3238
+ }
3239
+
3240
+ #[test]
3241
+ fn namespace_access_completion_excludes_inherited_private_constant() {
3242
+ let mut context = GraphTest::new();
3243
+ context.index_uri(
3244
+ "file:///foo.rb",
3245
+ "
3246
+ class Parent
3247
+ SECRET = 1
3248
+ private_constant :SECRET
3249
+ end
3250
+
3251
+ class Child < Parent
3252
+ end
3253
+ ",
3254
+ );
3255
+ context.resolve();
3256
+
3257
+ assert_completion_eq!(
3258
+ context,
3259
+ CompletionReceiver::NamespaceAccess {
3260
+ self_decl_id: None,
3261
+ namespace_decl_id: DeclarationId::from("Child"),
3262
+ },
3263
+ [] as [&str; 0]
3264
+ );
3265
+ }
3266
+
3267
+ #[test]
3268
+ fn expression_completion_includes_private_constant_within_lexical_scope() {
3269
+ let mut context = GraphTest::new();
3270
+ context.index_uri(
3271
+ "file:///foo.rb",
3272
+ "
3273
+ class Foo
3274
+ SECRET = 1
3275
+ private_constant :SECRET
3276
+
3277
+ def use_it
3278
+ # completion here
3279
+ end
3280
+ end
3281
+ ",
3282
+ );
3283
+ context.resolve();
3284
+
3285
+ let foo_name_id = Name::new(StringId::from("Foo"), ParentScope::None, None).id();
3286
+ assert_declaration_completion_eq!(
3287
+ context,
3288
+ CompletionReceiver::Expression {
3289
+ self_decl_id: None,
3290
+ nesting_name_id: foo_name_id,
3291
+ },
3292
+ [
3293
+ "Module",
3294
+ "Class",
3295
+ "Object",
3296
+ "BasicObject",
3297
+ "Kernel",
3298
+ "Foo",
3299
+ "Foo::SECRET",
3300
+ "Foo#use_it()"
3301
+ ]
3302
+ );
3303
+ }
3304
+
3305
+ /// Returns the smallest `MethodAlias` `DefinitionId` for the declaration named `alias_decl_fqn`
3306
+ /// (e.g., `"Foo#aliased()"`). Picking the smallest mirrors `follow_method_alias`'s own
3307
+ /// determinism rule for tests where multiple aliases share a declaration (e.g. cross-file fixtures).
3308
+ fn alias_def_id(context: &GraphTest, alias_decl_fqn: &str) -> DefinitionId {
3309
+ let decl = context
3310
+ .graph()
3311
+ .declarations()
3312
+ .get(&DeclarationId::from(alias_decl_fqn))
3313
+ .unwrap_or_else(|| panic!("expected declaration {alias_decl_fqn}"));
3314
+
3315
+ decl.definitions()
3316
+ .iter()
3317
+ .copied()
3318
+ .filter(|def_id| {
3319
+ matches!(
3320
+ context.graph().definitions().get(def_id),
3321
+ Some(Definition::MethodAlias(_)),
3322
+ )
3323
+ })
3324
+ .min()
3325
+ .unwrap_or_else(|| panic!("declaration {alias_decl_fqn} has no MethodAlias definition"))
3326
+ }
3327
+
3328
+ /// Asserts that the alias declared as `$alias_fqn` follows to the declaration `$target_fqn`.
3329
+ macro_rules! assert_alias_target {
3330
+ ($context:expr, $alias_fqn:expr, $target_fqn:expr $(,)?) => {{
3331
+ let context = $context;
3332
+ assert_eq!(
3333
+ follow_method_alias(context.graph(), alias_def_id(context, $alias_fqn)),
3334
+ Ok(DeclarationId::from($target_fqn)),
3335
+ );
3336
+ }};
3337
+ }
3338
+
3339
+ #[test]
3340
+ fn follow_method_alias_to_local_method() {
3341
+ let mut context = GraphTest::new();
3342
+ context.index_uri(
3343
+ "file:///foo.rb",
3344
+ r"
3345
+ class Foo
3346
+ def original; end
3347
+ alias aliased original
3348
+ end
3349
+ ",
3350
+ );
3351
+ context.resolve();
3352
+
3353
+ assert_alias_target!(&context, "Foo#aliased()", "Foo#original()");
3354
+ }
3355
+
3356
+ #[test]
3357
+ fn follow_method_alias_through_chain_of_aliases() {
3358
+ let mut context = GraphTest::new();
3359
+ context.index_uri(
3360
+ "file:///foo.rb",
3361
+ r"
3362
+ class Foo
3363
+ def real; end
3364
+ alias mid real
3365
+ alias outer mid
3366
+ end
3367
+ ",
3368
+ );
3369
+ context.resolve();
3370
+
3371
+ assert_alias_target!(&context, "Foo#outer()", "Foo#real()");
3372
+ }
3373
+
3374
+ #[test]
3375
+ fn follow_method_alias_detects_two_step_cycle() {
3376
+ let mut context = GraphTest::new();
3377
+ context.index_uri(
3378
+ "file:///foo.rb",
3379
+ r"
3380
+ class Foo
3381
+ alias a b
3382
+ alias b a
3383
+ end
3384
+ ",
3385
+ );
3386
+ context.resolve();
3387
+
3388
+ assert_eq!(
3389
+ follow_method_alias(context.graph(), alias_def_id(&context, "Foo#a()")),
3390
+ Err(AliasResolutionError::Cycle),
3391
+ );
3392
+ assert_eq!(
3393
+ follow_method_alias(context.graph(), alias_def_id(&context, "Foo#b()")),
3394
+ Err(AliasResolutionError::Cycle),
3395
+ );
3396
+ }
3397
+
3398
+ #[test]
3399
+ fn follow_method_alias_detects_multi_step_cycle() {
3400
+ let mut context = GraphTest::new();
3401
+ context.index_uri(
3402
+ "file:///foo.rb",
3403
+ r"
3404
+ class Foo
3405
+ alias a b
3406
+ alias b c
3407
+ alias c a
3408
+ end
3409
+ ",
3410
+ );
3411
+ context.resolve();
3412
+
3413
+ for alias_fqn in ["Foo#a()", "Foo#b()", "Foo#c()"] {
3414
+ assert_eq!(
3415
+ follow_method_alias(context.graph(), alias_def_id(&context, alias_fqn)),
3416
+ Err(AliasResolutionError::Cycle),
3417
+ "expected {alias_fqn} to be detected as part of the cycle",
3418
+ );
3419
+ }
3420
+ }
3421
+
3422
+ #[test]
3423
+ fn follow_method_alias_detects_self_cycle() {
3424
+ let mut context = GraphTest::new();
3425
+ context.index_uri(
3426
+ "file:///foo.rb",
3427
+ r"
3428
+ class Foo
3429
+ alias foo foo
3430
+ end
3431
+ ",
3432
+ );
3433
+ context.resolve();
3434
+
3435
+ assert_eq!(
3436
+ follow_method_alias(context.graph(), alias_def_id(&context, "Foo#foo()")),
3437
+ Err(AliasResolutionError::Cycle),
3438
+ );
3439
+ }
3440
+
3441
+ #[test]
3442
+ fn follow_method_alias_to_inherited_method() {
3443
+ let mut context = GraphTest::new();
3444
+ context.index_uri(
3445
+ "file:///foo.rb",
3446
+ r"
3447
+ class Parent
3448
+ def inherited_m; end
3449
+ end
3450
+
3451
+ class Child < Parent
3452
+ alias aliased inherited_m
3453
+ end
3454
+ ",
3455
+ );
3456
+ context.resolve();
3457
+
3458
+ assert_alias_target!(&context, "Child#aliased()", "Parent#inherited_m()");
3459
+ }
3460
+
3461
+ #[test]
3462
+ fn follow_method_alias_with_constant_receiver() {
3463
+ let mut context = GraphTest::new();
3464
+ context.index_uri(
3465
+ "file:///foo.rb",
3466
+ r"
3467
+ class Bar
3468
+ def to_s; end
3469
+ end
3470
+
3471
+ class Foo
3472
+ Bar.alias_method(:new_to_s, :to_s)
3473
+ end
3474
+ ",
3475
+ );
3476
+ context.resolve();
3477
+
3478
+ assert_alias_target!(&context, "Bar#new_to_s()", "Bar#to_s()");
3479
+ }
3480
+
3481
+ #[test]
3482
+ fn follow_method_alias_in_singleton_class_body() {
3483
+ let mut context = GraphTest::new();
3484
+ context.index_uri(
3485
+ "file:///foo.rb",
3486
+ r"
3487
+ class Foo
3488
+ def self.find; end
3489
+
3490
+ class << self
3491
+ alias_method :find_old, :find
3492
+ end
3493
+ end
3494
+ ",
3495
+ );
3496
+ context.resolve();
3497
+
3498
+ assert_alias_target!(&context, "Foo::<Foo>#find_old()", "Foo::<Foo>#find()");
3499
+ }
3500
+
3501
+ #[test]
3502
+ fn follow_method_alias_in_singleton_class_body_misses_instance_method() {
3503
+ let mut context = GraphTest::new();
3504
+ context.index_uri(
3505
+ "file:///foo.rb",
3506
+ r"
3507
+ class Foo
3508
+ def regular; end
3509
+
3510
+ class << self
3511
+ alias_method :other, :regular
3512
+ end
3513
+ end
3514
+ ",
3515
+ );
3516
+ context.resolve();
3517
+
3518
+ assert_eq!(
3519
+ follow_method_alias(context.graph(), alias_def_id(&context, "Foo::<Foo>#other()")),
3520
+ Err(AliasResolutionError::TargetNotFound),
3521
+ );
3522
+ }
3523
+
3524
+ #[test]
3525
+ fn follow_method_alias_to_attr_reader() {
3526
+ let mut context = GraphTest::new();
3527
+ context.index_uri(
3528
+ "file:///foo.rb",
3529
+ r"
3530
+ class Foo
3531
+ attr_reader :name
3532
+ alias display name
3533
+ end
3534
+ ",
3535
+ );
3536
+ context.resolve();
3537
+
3538
+ assert_alias_target!(&context, "Foo#display()", "Foo#name()");
3539
+ }
3540
+
3541
+ #[test]
3542
+ fn follow_method_alias_to_attr_accessor() {
3543
+ let mut context = GraphTest::new();
3544
+ context.index_uri(
3545
+ "file:///foo.rb",
3546
+ r"
3547
+ class Foo
3548
+ attr_accessor :age
3549
+ alias years age
3550
+ end
3551
+ ",
3552
+ );
3553
+ context.resolve();
3554
+
3555
+ assert_alias_target!(&context, "Foo#years()", "Foo#age()");
3556
+ }
3557
+
3558
+ #[test]
3559
+ fn follow_method_alias_to_method_in_prepended_module() {
3560
+ let mut context = GraphTest::new();
3561
+ context.index_uri(
3562
+ "file:///foo.rb",
3563
+ r"
3564
+ module M
3565
+ def original; end
3566
+ end
3567
+
3568
+ class Foo
3569
+ prepend M
3570
+ alias aliased original
3571
+ end
3572
+ ",
3573
+ );
3574
+ context.resolve();
3575
+
3576
+ assert_alias_target!(&context, "Foo#aliased()", "M#original()");
3577
+ }
3578
+
3579
+ #[test]
3580
+ fn follow_method_alias_ignores_visibility_of_target() {
3581
+ let mut context = GraphTest::new();
3582
+ context.index_uri(
3583
+ "file:///foo.rb",
3584
+ r"
3585
+ class Foo
3586
+ private def secret; end
3587
+ alias revealed secret
3588
+ end
3589
+ ",
3590
+ );
3591
+ context.resolve();
3592
+
3593
+ assert_alias_target!(&context, "Foo#revealed()", "Foo#secret()");
3594
+ }
3595
+
3596
+ #[test]
3597
+ fn follow_method_alias_picks_last_when_multiple_targets() {
3598
+ let mut context = GraphTest::new();
3599
+ context.index_uri(
3600
+ "file:///foo.rb",
3601
+ r"
3602
+ class Foo
3603
+ def bar; end
3604
+ def qux; end
3605
+ alias double bar
3606
+ alias double qux
3607
+ end
3608
+ ",
3609
+ );
3610
+ context.resolve();
3611
+
3612
+ assert_alias_target!(&context, "Foo#double()", "Foo#qux()");
3613
+ }
3614
+
3615
+ #[test]
3616
+ fn follow_method_alias_returns_target_not_found_when_target_missing() {
3617
+ let mut context = GraphTest::new();
3618
+ context.index_uri(
3619
+ "file:///foo.rb",
3620
+ r"
3621
+ class Foo
3622
+ alias aliased nonexistent
3623
+ end
3624
+ ",
3625
+ );
3626
+ context.resolve();
3627
+
3628
+ assert_eq!(
3629
+ follow_method_alias(context.graph(), alias_def_id(&context, "Foo#aliased()")),
3630
+ Err(AliasResolutionError::TargetNotFound),
3631
+ );
3632
+ }
3633
+
3634
+ #[test]
3635
+ fn find_member_in_ancestors_returns_member_in_main_namespace() {
3636
+ let mut context = GraphTest::new();
3637
+ context.index_uri(
3638
+ "file:///foo.rb",
3639
+ r"
3640
+ class Foo
3641
+ def bar; end
3642
+ end
3643
+ ",
3644
+ );
3645
+ context.resolve();
3646
+
3647
+ assert_eq!(
3648
+ find_member_in_ancestors(
3649
+ context.graph(),
3650
+ DeclarationId::from("Foo"),
3651
+ StringId::from("bar()"),
3652
+ false,
3653
+ ),
3654
+ Ok(DeclarationId::from("Foo#bar()")),
3655
+ );
3656
+ }
3657
+
3658
+ #[test]
3659
+ fn find_member_in_ancestors_returns_inherited_member() {
3660
+ let mut context = GraphTest::new();
3661
+ context.index_uri(
3662
+ "file:///foo.rb",
3663
+ r"
3664
+ class Parent
3665
+ def inherited_method; end
3666
+ end
3667
+
3668
+ class Child < Parent
3669
+ end
3670
+ ",
3671
+ );
3672
+ context.resolve();
3673
+
3674
+ assert_eq!(
3675
+ find_member_in_ancestors(
3676
+ context.graph(),
3677
+ DeclarationId::from("Child"),
3678
+ StringId::from("inherited_method()"),
3679
+ false,
3680
+ ),
3681
+ Ok(DeclarationId::from("Parent#inherited_method()")),
3682
+ );
3683
+ }
3684
+
3685
+ #[test]
3686
+ fn find_member_in_ancestors_returns_member_not_found_when_member_missing() {
3687
+ let mut context = GraphTest::new();
3688
+ context.index_uri(
3689
+ "file:///foo.rb",
3690
+ r"
3691
+ class Foo
3692
+ end
3693
+ ",
3694
+ );
3695
+ context.resolve();
3696
+
3697
+ assert_eq!(
3698
+ find_member_in_ancestors(
3699
+ context.graph(),
3700
+ DeclarationId::from("Foo"),
3701
+ StringId::from("missing()"),
3702
+ false,
3703
+ ),
3704
+ Err(FindMemberError::MemberNotFound),
3705
+ );
3706
+ }
3707
+
3708
+ #[test]
3709
+ fn find_member_in_ancestors_returns_not_a_namespace_for_method_declaration() {
3710
+ let mut context = GraphTest::new();
3711
+ context.index_uri(
3712
+ "file:///foo.rb",
3713
+ r"
3714
+ class Foo
3715
+ def bar; end
3716
+ end
3717
+ ",
3718
+ );
3719
+ context.resolve();
3720
+
3721
+ assert_eq!(
3722
+ find_member_in_ancestors(
3723
+ context.graph(),
3724
+ DeclarationId::from("Foo#bar()"),
3725
+ StringId::from("anything"),
3726
+ false,
3727
+ ),
3728
+ Err(FindMemberError::NotNamespace),
3729
+ );
3730
+ }
3731
+
3732
+ #[test]
3733
+ fn find_member_in_ancestors_returns_declaration_not_found_for_unknown_id() {
3734
+ let mut context = GraphTest::new();
3735
+ context.index_uri(
3736
+ "file:///foo.rb",
3737
+ r"
3738
+ class Foo
3739
+ end
3740
+ ",
3741
+ );
3742
+ context.resolve();
3743
+
3744
+ assert_eq!(
3745
+ find_member_in_ancestors(
3746
+ context.graph(),
3747
+ DeclarationId::from("DoesNotExist"),
3748
+ StringId::from("anything"),
3749
+ false,
3750
+ ),
3751
+ Err(FindMemberError::DeclarationNotFound),
3752
+ );
3753
+ }
1841
3754
  }