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