@@ -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 , MethodDefinition , Parameter } ;
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
@@ -207,6 +207,73 @@ macro_rules! collect_candidates {
207207 } ;
208208}
209209
210+ /// Returns the method definitions associated with a method declaration, resolving aliases.
211+ ///
212+ /// For regular method declarations, returns the `MethodDefinition`s directly.
213+ /// For alias declarations, follows the alias to the original method and returns its definitions.
214+ ///
215+ /// Returns an empty vec if the declaration is not a method declaration.
216+ #[ must_use]
217+ pub fn method_definitions ( graph : & Graph , declaration_id : DeclarationId ) -> Vec < ( DefinitionId , & MethodDefinition ) > {
218+ let decl = graph
219+ . declarations ( )
220+ . get ( & declaration_id)
221+ . expect ( "declaration should exist in graph" ) ;
222+ assert ! (
223+ matches!( decl, Declaration :: Method ( _) ) ,
224+ "expected a method declaration, got {:?}" ,
225+ decl. kind( )
226+ ) ;
227+
228+ let owner_id = * decl. owner_id ( ) ;
229+ let mut result = Vec :: new ( ) ;
230+
231+ for def_id in decl. definitions ( ) {
232+ let defn = graph
233+ . definitions ( )
234+ . get ( def_id)
235+ . expect ( "definition should exist in graph" ) ;
236+
237+ match defn {
238+ Definition :: Method ( method_def) => {
239+ result. push ( ( * def_id, method_def. as_ref ( ) ) ) ;
240+ }
241+ Definition :: MethodAlias ( alias_def) => {
242+ // Resolve alias: look up the original method in the owner's members
243+ let owner = graph
244+ . declarations ( )
245+ . get ( & owner_id)
246+ . expect ( "owner declaration should exist" )
247+ . as_namespace ( )
248+ . expect ( "owner should be a namespace" ) ;
249+
250+ // Alias target may not exist (e.g. aliasing an undefined method)
251+ let Some ( original_decl_id) = owner. member ( alias_def. old_name_str_id ( ) ) else {
252+ continue ;
253+ } ;
254+
255+ let original_decl = graph
256+ . declarations ( )
257+ . get ( original_decl_id)
258+ . expect ( "original declaration should exist" ) ;
259+
260+ for original_def_id in original_decl. definitions ( ) {
261+ let original_defn = graph
262+ . definitions ( )
263+ . get ( original_def_id)
264+ . expect ( "original definition should exist in graph" ) ;
265+ if let Definition :: Method ( original_method_def) = original_defn {
266+ result. push ( ( * original_def_id, original_method_def. as_ref ( ) ) ) ;
267+ }
268+ }
269+ }
270+ _ => { }
271+ }
272+ }
273+
274+ result
275+ }
276+
210277/// Determines all possible completion candidates based on the current context of the cursor. There are multiple cases
211278/// that change what has to be collected for completion:
212279///
@@ -1621,4 +1688,99 @@ mod tests {
16211688
16221689 assert ! ( !candidates. iter( ) . any( |c| matches!( c, CompletionCandidate :: Keyword ( _) ) ) ) ;
16231690 }
1691+
1692+ /// Helper to get source text at an offset
1693+ fn source_at < ' a > ( source : & ' a str , offset : & crate :: offset:: Offset ) -> & ' a str {
1694+ & source[ offset. start ( ) as usize ..offset. end ( ) as usize ]
1695+ }
1696+
1697+ #[ test]
1698+ fn test_method_definitions_returns_method_definitions ( ) {
1699+ let mut context = GraphTest :: new ( ) ;
1700+ // 0123456789...
1701+ let source = "class Foo\n def bar(a, b); end\n end\n " ;
1702+ context. index_uri ( "file:///foo.rb" , source) ;
1703+ context. resolve ( ) ;
1704+
1705+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#bar()" ) ) ;
1706+ assert_eq ! ( 1 , defs. len( ) ) ;
1707+ assert_eq ! ( "def bar(a, b); end" , source_at( source, defs[ 0 ] . 1 . offset( ) ) ) ;
1708+ }
1709+
1710+ #[ test]
1711+ fn test_method_definitions_resolves_alias ( ) {
1712+ let mut context = GraphTest :: new ( ) ;
1713+ let source = "class Foo\n def bar(a, b); end\n alias_method :baz, :bar\n end\n " ;
1714+ context. index_uri ( "file:///foo.rb" , source) ;
1715+ context. resolve ( ) ;
1716+
1717+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#baz()" ) ) ;
1718+ assert_eq ! ( 1 , defs. len( ) ) ;
1719+ // Returns the original method's definition
1720+ assert_eq ! ( "def bar(a, b); end" , source_at( source, defs[ 0 ] . 1 . offset( ) ) ) ;
1721+ }
1722+
1723+ #[ test]
1724+ fn test_method_definitions_alias_to_undefined_method ( ) {
1725+ let mut context = GraphTest :: new ( ) ;
1726+ context. index_uri ( "file:///foo.rb" , "class Foo\n alias_method :baz, :nonexistent\n end\n " ) ;
1727+ context. resolve ( ) ;
1728+
1729+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#baz()" ) ) ;
1730+ assert ! ( defs. is_empty( ) ) ;
1731+ }
1732+
1733+ #[ test]
1734+ fn test_method_definitions_with_override ( ) {
1735+ let mut context = GraphTest :: new ( ) ;
1736+ let source = "class Foo\n def bar(a); end\n def bar(a, b); end\n end\n " ;
1737+ context. index_uri ( "file:///foo.rb" , source) ;
1738+ context. resolve ( ) ;
1739+
1740+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#bar()" ) ) ;
1741+ assert_eq ! ( 2 , defs. len( ) ) ;
1742+
1743+ let mut texts: Vec < & str > = defs. iter ( ) . map ( |( _, d) | source_at ( source, d. offset ( ) ) ) . collect ( ) ;
1744+ texts. sort ( ) ;
1745+ assert_eq ! ( vec![ "def bar(a); end" , "def bar(a, b); end" ] , texts) ;
1746+ }
1747+
1748+ #[ test]
1749+ #[ should_panic( expected = "expected a method declaration" ) ]
1750+ fn test_method_definitions_panics_for_non_method ( ) {
1751+ let mut context = GraphTest :: new ( ) ;
1752+ context. index_uri ( "file:///foo.rb" , "class Foo; end" ) ;
1753+ context. resolve ( ) ;
1754+
1755+ method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo" ) ) ;
1756+ }
1757+
1758+ #[ test]
1759+ fn test_method_definitions_from_rbs ( ) {
1760+ let mut context = GraphTest :: new ( ) ;
1761+ let source = "class Foo\n def bar: (String name) -> void\n | (Integer id) -> String\n end\n " ;
1762+ context. index_rbs_uri ( "file:///foo.rbs" , source) ;
1763+ context. resolve ( ) ;
1764+
1765+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#bar()" ) ) ;
1766+ assert_eq ! ( 1 , defs. len( ) ) ;
1767+ assert_eq ! (
1768+ "def bar: (String name) -> void\n | (Integer id) -> String" ,
1769+ source_at( source, defs[ 0 ] . 1 . offset( ) )
1770+ ) ;
1771+ }
1772+
1773+ #[ test]
1774+ fn test_method_definitions_from_rbs_alias ( ) {
1775+ let mut context = GraphTest :: new ( ) ;
1776+ let rb_source = "class Foo\n def bar(a, b); end\n end\n " ;
1777+ let rbs_source = "class Foo\n alias baz bar\n end\n " ;
1778+ context. index_uri ( "file:///foo.rb" , rb_source) ;
1779+ context. index_rbs_uri ( "file:///foo.rbs" , rbs_source) ;
1780+ context. resolve ( ) ;
1781+
1782+ let defs = method_definitions ( context. graph ( ) , DeclarationId :: from ( "Foo#baz()" ) ) ;
1783+ assert_eq ! ( 1 , defs. len( ) ) ;
1784+ assert_eq ! ( "def bar(a, b); end" , source_at( rb_source, defs[ 0 ] . 1 . offset( ) ) ) ;
1785+ }
16241786}
0 commit comments