@@ -6,10 +6,10 @@ use std::thread;
66use url:: Url ;
77
88use crate :: model:: declaration:: { Ancestor , Declaration } ;
9- use crate :: model:: definitions:: { Definition , Parameter } ;
9+ use crate :: model:: definitions:: { Definition , MethodAliasDefinition , Parameter , Receiver } ;
1010use crate :: model:: graph:: { Graph , OBJECT_ID } ;
1111use crate :: model:: identity_maps:: IdentityHashSet ;
12- use crate :: model:: ids:: { DeclarationId , NameId , StringId , UriId } ;
12+ use crate :: model:: ids:: { DeclarationId , DefinitionId , NameId , StringId , UriId } ;
1313use crate :: model:: keywords:: { self , Keyword } ;
1414use crate :: model:: name:: NameRef ;
1515
@@ -458,6 +458,99 @@ fn method_argument_completion<'a>(
458458 Ok ( candidates)
459459}
460460
461+ /// Result of looking up the enclosing namespace for a definition.
462+ #[ allow( dead_code) ]
463+ enum EnclosingNamespace {
464+ /// Found a namespace (class, module, or singleton class).
465+ Found ( DeclarationId ) ,
466+ /// The definition has no enclosing namespace (top-level).
467+ NotFound ,
468+ /// The enclosing namespace could not be determined because name resolution failed.
469+ Unresolved ( DefinitionId ) ,
470+ }
471+
472+ /// Walks up the lexical nesting chain from the given `DefinitionId` to find
473+ /// the nearest enclosing namespace (class, module, or singleton class).
474+ fn enclosing_namespace ( graph : & Graph , starting_id : Option < & DefinitionId > ) -> EnclosingNamespace {
475+ let mut current = starting_id;
476+ while let Some ( id) = current {
477+ let def = graph. definitions ( ) . get ( id) . unwrap ( ) ;
478+ let is_namespace_def = matches ! (
479+ def,
480+ Definition :: Class ( _) | Definition :: Module ( _) | Definition :: SingletonClass ( _)
481+ ) ;
482+
483+ let Some ( decl_id) = graph. definition_id_to_declaration_id ( * id) else {
484+ if is_namespace_def {
485+ return EnclosingNamespace :: Unresolved ( * id) ;
486+ }
487+ current = def. lexical_nesting_id ( ) . as_ref ( ) ;
488+ continue ;
489+ } ;
490+ if is_namespace_def {
491+ return EnclosingNamespace :: Found ( * decl_id) ;
492+ }
493+ current = def. lexical_nesting_id ( ) . as_ref ( ) ;
494+ }
495+ EnclosingNamespace :: NotFound
496+ }
497+
498+ /// Result of dealiasing a single level of method alias.
499+ pub enum DealiasMethodResult {
500+ /// The alias target is a concrete method definition.
501+ Method ( DefinitionId ) ,
502+ /// The alias target is another alias.
503+ Alias ( DefinitionId ) ,
504+ }
505+
506+ /// Dereferences a `MethodAliasDefinition` by one level, returning the definitions
507+ /// found under the aliased name. Returns an empty vector if the alias target cannot
508+ /// be found (e.g., unresolved constant receiver or missing member).
509+ ///
510+ /// # Panics
511+ ///
512+ /// Panics if a `SelfReceiver` definition cannot be resolved to a namespace with a singleton class.
513+ #[ must_use]
514+ pub fn dealias_method ( graph : & Graph , alias : & MethodAliasDefinition ) -> Vec < DealiasMethodResult > {
515+ let owner_id = match alias. receiver ( ) {
516+ Some ( Receiver :: SelfReceiver ( def_id) ) => {
517+ let decl_id = graph. definition_id_to_declaration_id ( * def_id) . unwrap ( ) ;
518+ let decl = graph. declarations ( ) . get ( decl_id) . unwrap ( ) ;
519+ let ns = decl. as_namespace ( ) . unwrap ( ) ;
520+ * ns. singleton_class ( ) . unwrap ( )
521+ }
522+ Some ( Receiver :: ConstantReceiver ( name_id) ) => {
523+ let Some ( & id) = graph. name_id_to_declaration_id ( * name_id) else {
524+ return vec ! [ ] ;
525+ } ;
526+ id
527+ }
528+ None => match enclosing_namespace ( graph, alias. lexical_nesting_id ( ) . as_ref ( ) ) {
529+ EnclosingNamespace :: Found ( id) => id,
530+ EnclosingNamespace :: NotFound | EnclosingNamespace :: Unresolved ( _) => return vec ! [ ] ,
531+ } ,
532+ } ;
533+
534+ let owner_decl = graph. declarations ( ) . get ( & owner_id) . unwrap ( ) ;
535+ let ns = owner_decl. as_namespace ( ) . unwrap ( ) ;
536+
537+ let Some ( & method_decl_id) = ns. member ( alias. old_name_str_id ( ) ) else {
538+ return vec ! [ ] ;
539+ } ;
540+ let method_decl = graph. declarations ( ) . get ( & method_decl_id) . unwrap ( ) ;
541+ assert ! ( matches!( method_decl, Declaration :: Method ( _) ) ) ;
542+
543+ method_decl
544+ . definitions ( )
545+ . iter ( )
546+ . filter_map ( |def_id| match graph. definitions ( ) . get ( def_id) {
547+ Some ( Definition :: Method ( _) ) => Some ( DealiasMethodResult :: Method ( * def_id) ) ,
548+ Some ( Definition :: MethodAlias ( _) ) => Some ( DealiasMethodResult :: Alias ( * def_id) ) ,
549+ _ => None ,
550+ } )
551+ . collect ( )
552+ }
553+
461554#[ cfg( test) ]
462555mod tests {
463556 use std:: str:: FromStr ;
@@ -1683,6 +1776,123 @@ mod tests {
16831776 assert ! ( !candidates. iter( ) . any( |c| matches!( c, CompletionCandidate :: Keyword ( _) ) ) ) ;
16841777 }
16851778
1779+ fn get_method_alias_def < ' a > ( graph : & ' a Graph , decl_name : & str ) -> & ' a MethodAliasDefinition {
1780+ let decl = graph. declarations ( ) . get ( & DeclarationId :: from ( decl_name) ) . unwrap ( ) ;
1781+ for def_id in decl. definitions ( ) {
1782+ if let Some ( Definition :: MethodAlias ( alias) ) = graph. definitions ( ) . get ( def_id) {
1783+ return alias;
1784+ }
1785+ }
1786+ panic ! ( "No MethodAliasDefinition found for {decl_name}" ) ;
1787+ }
1788+
1789+ #[ test]
1790+ fn dealias_method_basic ( ) {
1791+ let mut context = GraphTest :: new ( ) ;
1792+ context. index_uri (
1793+ "file:///foo.rb" ,
1794+ "
1795+ class Foo
1796+ def foo(a, b); end
1797+ alias bar foo
1798+ end
1799+ " ,
1800+ ) ;
1801+ context. resolve ( ) ;
1802+
1803+ let alias = get_method_alias_def ( context. graph ( ) , "Foo#bar()" ) ;
1804+ let results = dealias_method ( context. graph ( ) , alias) ;
1805+ assert_eq ! ( results. len( ) , 1 ) ;
1806+ assert ! ( matches!( results[ 0 ] , DealiasMethodResult :: Method ( _) ) ) ;
1807+ }
1808+
1809+ #[ test]
1810+ fn dealias_method_chained ( ) {
1811+ let mut context = GraphTest :: new ( ) ;
1812+ context. index_uri (
1813+ "file:///foo.rb" ,
1814+ "
1815+ class Foo
1816+ def foo(x); end
1817+ alias bar foo
1818+ alias baz bar
1819+ end
1820+ " ,
1821+ ) ;
1822+ context. resolve ( ) ;
1823+
1824+ // baz -> bar: one level returns the alias to bar
1825+ let alias = get_method_alias_def ( context. graph ( ) , "Foo#baz()" ) ;
1826+ let results = dealias_method ( context. graph ( ) , alias) ;
1827+ assert_eq ! ( results. len( ) , 1 ) ;
1828+ assert ! ( matches!( results[ 0 ] , DealiasMethodResult :: Alias ( _) ) ) ;
1829+
1830+ // bar -> foo: one more level returns the method definition
1831+ let alias = get_method_alias_def ( context. graph ( ) , "Foo#bar()" ) ;
1832+ let results = dealias_method ( context. graph ( ) , alias) ;
1833+ assert_eq ! ( results. len( ) , 1 ) ;
1834+ assert ! ( matches!( results[ 0 ] , DealiasMethodResult :: Method ( _) ) ) ;
1835+ }
1836+
1837+ #[ test]
1838+ fn dealias_method_unresolved ( ) {
1839+ let mut context = GraphTest :: new ( ) ;
1840+ context. index_uri (
1841+ "file:///foo.rb" ,
1842+ "
1843+ class Foo
1844+ alias bar nonexistent
1845+ end
1846+ " ,
1847+ ) ;
1848+ context. resolve ( ) ;
1849+
1850+ let alias = get_method_alias_def ( context. graph ( ) , "Foo#bar()" ) ;
1851+ let results = dealias_method ( context. graph ( ) , alias) ;
1852+ assert ! ( results. is_empty( ) ) ;
1853+ }
1854+
1855+ #[ test]
1856+ fn dealias_method_multiple_definitions ( ) {
1857+ let mut context = GraphTest :: new ( ) ;
1858+ context. index_uri (
1859+ "file:///foo.rb" ,
1860+ "
1861+ class Foo
1862+ def foo(a); end
1863+ end
1864+ " ,
1865+ ) ;
1866+ context. index_uri (
1867+ "file:///foo2.rb" ,
1868+ "
1869+ class Foo
1870+ alias foo baz # another alias definition of #foo()
1871+ alias bar foo
1872+ end
1873+ " ,
1874+ ) ;
1875+ context. resolve ( ) ;
1876+
1877+ let alias = get_method_alias_def ( context. graph ( ) , "Foo#bar()" ) ;
1878+ let results = dealias_method ( context. graph ( ) , alias) ;
1879+ assert_eq ! ( results. len( ) , 2 ) ;
1880+ assert_eq ! (
1881+ results
1882+ . iter( )
1883+ . filter( |r| matches!( r, DealiasMethodResult :: Method ( _) ) )
1884+ . count( ) ,
1885+ 1
1886+ ) ;
1887+ assert_eq ! (
1888+ results
1889+ . iter( )
1890+ . filter( |r| matches!( r, DealiasMethodResult :: Alias ( _) ) )
1891+ . count( ) ,
1892+ 1
1893+ ) ;
1894+ }
1895+
16861896 #[ test]
16871897 fn method_call_completion_excludes_keywords ( ) {
16881898 let mut context = GraphTest :: new ( ) ;
0 commit comments