66require "rbs"
77require "console"
88require_relative "wrapper"
9+ require_relative "type"
910
1011module Decode
1112 module RBS
1213 # Represents a Ruby method definition wrapper for RBS generation.
1314 class Method < Wrapper
14-
1515 # Initialize a new method wrapper.
1616 # @parameter definition [Decode::Definition] The method definition to wrap.
1717 def initialize ( definition )
1818 super
1919 @signatures = nil
20+ @keyword_arguments = nil
21+ @return_type = nil
22+ @parameters = nil
2023 end
2124
2225 # Extract method signatures from the method definition.
@@ -25,10 +28,28 @@ def signatures
2528 @signatures ||= extract_signatures
2629 end
2730
31+ # Extract keyword arguments from the method definition.
32+ # @returns [Hash] Hash with :required and :optional keys.
33+ def keyword_arguments
34+ @keyword_arguments ||= extract_keyword_arguments ( @definition , nil )
35+ end
36+
37+ # Extract return type from the method definition.
38+ # @returns [::RBS::Types::t] The RBS return type.
39+ def return_type
40+ @return_type ||= extract_return_type ( @definition , nil ) || ::RBS ::Parser . parse_type ( "untyped" )
41+ end
42+
43+ # Extract parameters from the method definition.
44+ # @returns [Array] Array of RBS parameter objects.
45+ def parameters
46+ @parameters ||= extract_parameters ( @definition , nil )
47+ end
48+
2849 # Convert the method definition to RBS AST
2950 def to_rbs_ast ( index = nil )
3051 method_name = @definition . name
31- comment = extract_comment ( @definition )
52+ comment = self . comment
3253
3354 overloads = [ ]
3455 if signatures . any?
@@ -40,8 +61,9 @@ def to_rbs_ast(index = nil)
4061 )
4162 end
4263 else
43- return_type = extract_return_type ( @definition , index ) || ::RBS ::Parser . parse_type ( "untyped" )
44- parameters = extract_parameters ( @definition , index )
64+ return_type = self . return_type
65+ parameters = self . parameters
66+ keywords = self . keyword_arguments
4567 block_type = extract_block_type ( @definition , index )
4668
4769 method_type = ::RBS ::MethodType . new (
@@ -51,8 +73,8 @@ def to_rbs_ast(index = nil)
5173 optional_positionals : [ ] ,
5274 rest_positionals : nil ,
5375 trailing_positionals : [ ] ,
54- required_keywords : { } ,
55- optional_keywords : { } ,
76+ required_keywords : keywords [ :required ] ,
77+ optional_keywords : keywords [ :optional ] ,
5678 rest_keywords : nil ,
5779 return_type : return_type
5880 ) ,
@@ -91,13 +113,23 @@ def extract_return_type(definition, index)
91113 # Look for @returns tags in the method's documentation
92114 documentation = definition . documentation
93115
94- # Find @returns tag
95- returns_tag = documentation &.filter ( Decode ::Comment ::Returns ) &.first
116+ # Find all @returns tags
117+ returns_tags = documentation &.filter ( Decode ::Comment ::Returns ) &.to_a
96118
97- if returns_tag
98- # Parse the type from the tag
99- type_string = returns_tag . type . strip
100- parse_type_string ( type_string )
119+ if returns_tags &.any?
120+ if returns_tags . length == 1
121+ # Single return type
122+ type_string = returns_tags . first . type . strip
123+ Type . parse ( type_string )
124+ else
125+ # Multiple return types - create union
126+ types = returns_tags . map do |tag |
127+ type_string = tag . type . strip
128+ Type . parse ( type_string )
129+ end
130+
131+ ::RBS ::Types ::Union . new ( types : types , location : nil )
132+ end
101133 else
102134 # Infer return type based on method name patterns
103135 infer_return_type ( definition )
@@ -109,14 +141,15 @@ def extract_parameters(definition, index)
109141 documentation = definition . documentation
110142 return [ ] unless documentation
111143
112- # Find @parameter tags
144+ # Find @parameter tags (but not @option tags, which are handled separately)
113145 param_tags = documentation . filter ( Decode ::Comment ::Parameter ) . to_a
146+ param_tags = param_tags . reject { |tag | tag . is_a? ( Decode ::Comment ::Option ) }
114147 return [ ] if param_tags . empty?
115148
116149 param_tags . map do |tag |
117150 name = tag . name
118151 type_string = tag . type . strip
119- type = parse_type_string ( type_string )
152+ type = Type . parse ( type_string )
120153
121154 ::RBS ::Types ::Function ::Param . new (
122155 type : type ,
@@ -125,6 +158,38 @@ def extract_parameters(definition, index)
125158 end
126159 end
127160
161+ # Extract keyword arguments from @option tags
162+ def extract_keyword_arguments ( definition , index )
163+ documentation = definition . documentation
164+ return { required : { } , optional : { } } unless documentation
165+
166+ # Find @option tags
167+ option_tags = documentation . filter ( Decode ::Comment ::Option ) . to_a
168+ return { required : { } , optional : { } } if option_tags . empty?
169+
170+ keywords = { required : { } , optional : { } }
171+
172+ option_tags . each do |tag |
173+ name = tag . name . to_s
174+ # Remove leading colon if present (e.g., ":cached" -> "cached")
175+ name = name . sub ( /\A :/ , "" )
176+
177+ type_string = tag . type . strip
178+ type = Type . parse ( type_string )
179+
180+ # Determine if the keyword is optional based on the type annotation
181+ # If the type is nullable (contains nil or ends with ?), make it optional
182+ if Type . nullable? ( type )
183+ keywords [ :optional ] [ name . to_sym ] = type
184+ else
185+ keywords [ :required ] [ name . to_sym ] = type
186+ end
187+ end
188+
189+ keywords
190+ end
191+
192+
128193 # Extract block type from method documentation
129194 def extract_block_type ( definition , index )
130195 documentation = definition . documentation
@@ -138,7 +203,7 @@ def extract_block_type(definition, index)
138203 block_params = yields_tag . filter ( Decode ::Comment ::Parameter ) . map do |param_tag |
139204 name = param_tag . name
140205 type_string = param_tag . type . strip
141- type = parse_type_string ( type_string )
206+ type = Type . parse ( type_string )
142207
143208 ::RBS ::Types ::Function ::Param . new (
144209 type : type ,
@@ -199,19 +264,6 @@ def infer_return_type(definition)
199264 ::RBS ::Parser . parse_type ( "untyped" )
200265 end
201266
202- # Parse a type string and convert it to RBS type
203- def parse_type_string ( type_string )
204- # This is for backwards compatibility with the old syntax, eventually we will emit warnings for these:
205- type_string = type_string . tr ( "()" , "[]" )
206- type_string . gsub! ( "| Nil" , "| nil" )
207- type_string . gsub! ( "Boolean" , "bool" )
208-
209- return ::RBS ::Parser . parse_type ( type_string )
210- rescue => error
211- Console . warn ( self , "Failed to parse type string: #{ type_string } " , error )
212- return ::RBS ::Parser . parse_type ( "untyped" )
213- end
214-
215267 end
216268 end
217269end
0 commit comments