gloss 0.0.1 → 0.0.6

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/crystal_specs.yml +26 -0
  4. data/.github/workflows/ruby_specs.yml +33 -0
  5. data/.gitignore +2 -1
  6. data/.rspec +1 -0
  7. data/Gemfile +0 -1
  8. data/Gemfile.lock +26 -34
  9. data/LICENSE +21 -0
  10. data/README.md +59 -7
  11. data/Rakefile +4 -0
  12. data/exe/gloss +15 -1
  13. data/ext/gloss/Makefile +27 -5
  14. data/ext/gloss/extconf.rb +3 -25
  15. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  16. data/ext/gloss/lib/rbs_types.cr +3 -0
  17. data/ext/gloss/spec/parser_spec.cr +124 -0
  18. data/ext/gloss/spec/spec_helper.cr +2 -0
  19. data/ext/gloss/src/cr_ast.cr +129 -31
  20. data/ext/gloss/src/gloss.cr +12 -18
  21. data/ext/gloss/src/lexer.cr +59 -1
  22. data/ext/gloss/src/parser.cr +548 -254
  23. data/ext/gloss/src/rb_ast.cr +153 -27
  24. data/gloss.gemspec +1 -0
  25. data/lib/gloss.rb +4 -1
  26. data/lib/gloss/builder.rb +607 -409
  27. data/lib/gloss/cli.rb +64 -24
  28. data/lib/gloss/config.rb +16 -10
  29. data/lib/gloss/errors.rb +13 -7
  30. data/lib/gloss/initializer.rb +11 -6
  31. data/lib/gloss/logger.rb +34 -0
  32. data/lib/gloss/parser.rb +35 -0
  33. data/lib/gloss/scope.rb +8 -3
  34. data/lib/gloss/source.rb +18 -15
  35. data/lib/gloss/type_checker.rb +96 -0
  36. data/lib/gloss/version.rb +6 -1
  37. data/lib/gloss/watcher.rb +63 -19
  38. data/lib/gloss/writer.rb +18 -12
  39. data/sig/gloss.rbs +3 -0
  40. data/sig/listen.rbs +1 -0
  41. data/src/lib/gloss/builder.gl +546 -0
  42. data/src/lib/gloss/cli.gl +55 -0
  43. data/src/lib/gloss/config.gl +21 -0
  44. data/src/lib/gloss/errors.gl +11 -0
  45. data/src/lib/gloss/initializer.gl +20 -0
  46. data/src/lib/gloss/logger.gl +26 -0
  47. data/src/lib/gloss/parser.gl +31 -0
  48. data/src/lib/gloss/scope.gl +9 -0
  49. data/src/lib/gloss/source.gl +32 -0
  50. data/src/lib/gloss/type_checker.gl +101 -0
  51. data/src/lib/gloss/version.gl +3 -0
  52. data/src/lib/gloss/watcher.gl +67 -0
  53. data/src/lib/gloss/writer.gl +33 -0
  54. metadata +42 -6
  55. data/lib/gloss.bundle.dwarf +0 -0
  56. data/src/lib/hrb/initializer.gl +0 -22
  57. data/src/lib/hrb/watcher.gl +0 -32
data/lib/gloss/writer.rb CHANGED
@@ -1,26 +1,32 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
2
 
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ require "pathname"
7
+ require "fileutils"
3
8
  module Gloss
4
9
  module Utils
5
10
  module_function
6
-
7
11
  def src_path_to_output_path(src_path)
8
- src_path.sub(%r{\A(?:\./)?#{Config.src_dir}/?}, "")
12
+ src_path.sub("#{Config.src_dir}/", "")
13
+ .sub(/\.gl$/, ".rb")
9
14
  end
10
15
  end
11
-
12
16
  class Writer
13
17
  include Utils
14
-
15
- def initialize(content, src_path, output_path = nil)
16
- @content, @src_path = content, src_path
17
- @output_path = output_path || src_path_to_output_path(src_path)
18
+ def initialize(content, src_path, output_path = Pathname.new(src_path_to_output_path(src_path)))
19
+ @content = content
20
+ @output_path = output_path
18
21
  end
19
-
20
- def run
21
- File.open(@output_path, "wb") do |file|
22
- file << @content
22
+ def run()
23
+ unless @output_path.parent
24
+ .exist?
25
+ FileUtils.mkdir_p(@output_path.parent)
23
26
  end
27
+ File.open(@output_path, "wb") { |file|
28
+ file.puts(@content)
29
+ }
24
30
  end
25
31
  end
26
32
  end
data/sig/gloss.rbs ADDED
@@ -0,0 +1,3 @@
1
+ module Gloss
2
+ def self.parse_buffer: (String) -> String
3
+ end
data/sig/listen.rbs CHANGED
@@ -19,6 +19,7 @@ module Listen
19
19
  ?ignore: Regexp | Array[Regexp],
20
20
  ?ignore!: Regexp,
21
21
  ?only: Regexp?,
22
+ ?latency: (Integer | Float)?,
22
23
  ?polling_fallback_message: String?) {
23
24
  (Array[String] modified, Array[String] added, Array[String] removed) -> void
24
25
  } -> Listener
@@ -0,0 +1,546 @@
1
+ module Gloss
2
+ module Utils
3
+ module_function
4
+
5
+ def with_file_header(str)
6
+ "#{Builder::FILE_HEADER}\n\n#{str}"
7
+ end
8
+ end
9
+
10
+ class Builder
11
+ FILE_HEADER = <<~RUBY
12
+ #{"# frozen_string_literal: true\n" if Config.frozen_string_literals}
13
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
14
+ ##### See #{Config.src_dir}/ to make changes
15
+ RUBY
16
+
17
+ include Utils
18
+
19
+ attr_reader :tree
20
+
21
+ def initialize(tree_hash, type_checker = nil)
22
+ @indent_level = 0
23
+ @inside_macro = false
24
+ @eval_vars = false
25
+ @current_scope = nil
26
+ @tree = tree_hash
27
+ @type_checker = type_checker
28
+ end
29
+
30
+ def run
31
+ rb_output = visit_node(@tree)
32
+ with_file_header(rb_output)
33
+ end
34
+
35
+ # type node = Hash[Symbol, String | Array[String | node] | Hash[Symbol, node]] | true | false
36
+
37
+ def visit_node(node : Hash[Symbol, Any], scope = Scope.new) : String
38
+ src = Source.new(@indent_level)
39
+ case node[:type]
40
+ when "ClassNode"
41
+ class_name = visit_node(node[:name])
42
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
43
+ superclass_type = nil
44
+ superclass_output = nil
45
+ if node[:superclass]
46
+ @eval_vars = true
47
+ superclass_output = visit_node(node[:superclass])
48
+ @eval_vars = false
49
+ superclass_type = RBS::Parser.parse_type superclass_output
50
+ if node.dig(:superclass, :type) == "Generic"
51
+ superclass_output = superclass_output[/^[^\[]+/]
52
+ end
53
+ end
54
+
55
+ src.write_ln "class #{class_name}#{" < #{superclass_output}" if superclass_output}"
56
+
57
+ class_type = RBS::AST::Declarations::Class.new(
58
+ name: RBS::TypeName.new(
59
+ namespace: current_namespace,
60
+ name: class_name.to_sym
61
+ ),
62
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
63
+ super_class: superclass_type,
64
+ members: Array.new, # TODO
65
+ annotations: Array.new, # TODO
66
+ location: node[:location],
67
+ comment: node[:comment]
68
+ )
69
+ old_parent_scope = @current_scope
70
+ @current_scope = class_type
71
+
72
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
73
+
74
+ src.write_ln "end"
75
+
76
+ @current_scope = old_parent_scope
77
+
78
+ @current_scope.members << class_type if @current_scope
79
+
80
+ if @type_checker
81
+ @type_checker.top_level_decls[class_type.name.name] = class_type unless @current_scope
82
+ end
83
+ when "ModuleNode"
84
+ module_name = visit_node node[:name]
85
+ src.write_ln "module #{module_name}"
86
+
87
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
88
+
89
+ module_type = RBS::AST::Declarations::Module.new(
90
+ name: RBS::TypeName.new(
91
+ namespace: current_namespace,
92
+ name: module_name.to_sym
93
+ ),
94
+ type_params: RBS::AST::Declarations::ModuleTypeParams.new, # responds to #add to add params
95
+ self_types: Array.new, # TODO
96
+ members: Array.new, # TODO
97
+ annotations: Array.new, # TODO
98
+ location: node[:location],
99
+ comment: node[:comment]
100
+ )
101
+ old_parent_scope = @current_scope
102
+ @current_scope = module_type
103
+
104
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
105
+
106
+ @current_scope = old_parent_scope
107
+
108
+ @current_scope.members << module_type if @current_scope
109
+
110
+ if @type_checker
111
+ @type_checker.top_level_decls[module_type.name.name] = module_type unless @current_scope
112
+ end
113
+ src.write_ln "end"
114
+ when "DefNode"
115
+ args = render_args(node)
116
+ receiver = node[:receiver] ? visit_node(node[:receiver]) : nil
117
+ src.write_ln "def #{"#{receiver}." if receiver}#{node[:name]}#{args[:representation]}"
118
+
119
+ return_type = if node[:return_type]
120
+ RBS::Types::ClassInstance.new(
121
+ name: RBS::TypeName.new(
122
+ name: eval(visit_node(node[:return_type])).to_s.to_sym,
123
+ namespace: RBS::Namespace.root
124
+ ),
125
+ args: EMPTY_ARRAY, # TODO
126
+ location: node[:location]
127
+ )
128
+ else
129
+ RBS::Types::Bases::Any.new(
130
+ location: node[:location]
131
+ )
132
+ end
133
+
134
+ method_types = [
135
+ RBS::MethodType.new(
136
+ type_params: EMPTY_ARRAY, # TODO
137
+ type: RBS::Types::Function.new(
138
+ required_positionals: args.dig(:types, :required_positionals),
139
+ optional_positionals: args.dig(:types, :optional_positionals),
140
+ rest_positionals: args.dig(:types, :rest_positionals),
141
+ trailing_positionals: args.dig(:types, :trailing_positionals),
142
+ required_keywords: args.dig(:types, :required_keywords),
143
+ optional_keywords: args.dig(:types, :optional_keywords),
144
+ rest_keywords: args.dig(:types, :rest_keywords),
145
+ return_type: return_type
146
+ ),
147
+ block: node[:yield_arg_count] ?
148
+ RBS::Types::Block.new(
149
+ type: RBS::Types::Function.new(
150
+ required_positionals: Array.new,
151
+ optional_positionals: Array.new,
152
+ rest_positionals: nil,
153
+ trailing_positionals: Array.new,
154
+ required_keywords: Hash.new,
155
+ optional_keywords: Hash.new,
156
+ rest_keywords: nil,
157
+ return_type: RBS::Types::Bases::Any.new(location: node[:location])
158
+ ),
159
+ required: !!(node[:block_arg] || node[:yield_arg_count])
160
+ ) : nil,
161
+ location: node[:location]
162
+ )
163
+ ]
164
+ method_definition = RBS::AST::Members::MethodDefinition.new(
165
+ name: node[:name].to_sym,
166
+ kind: receiver ? :class : :instance,
167
+ types: method_types,
168
+ annotations: EMPTY_ARRAY, # TODO
169
+ location: node[:location],
170
+ comment: node[:comment],
171
+ overload: false
172
+ )
173
+
174
+ if @current_scope
175
+ @current_scope.members << method_definition
176
+ else
177
+ @type_checker.type_env << method_definition if @type_checker # should be new class declaration for Object with method_definition as private method
178
+ end
179
+
180
+ indented(src) { src.write_ln visit_node(node[:body]) if node[:body] }
181
+
182
+ src.write_ln "end"
183
+
184
+ when "VisibilityModifier"
185
+
186
+ src.write_ln "#{node[:visibility]} #{visit_node(node[:exp])}"
187
+ when "CollectionNode"
188
+ node[:children].each { |a| src.write visit_node(a, scope) }
189
+ when "Call"
190
+ obj = node[:object] ? "#{visit_node(node[:object], scope)}." : ""
191
+ arg_arr = node.fetch(:args) { EMPTY_ARRAY }
192
+ arg_arr += node[:named_args] if node[:named_args]
193
+ args = if !arg_arr.empty? || node[:block_arg]
194
+ "#{arg_arr.map { |a| visit_node(a, scope).strip }.reject(&:blank?).join(", ")}#{"&#{visit_node(node[:block_arg]).strip}" if node[:block_arg]}"
195
+ else
196
+ nil
197
+ end
198
+ block = node[:block] ? " #{visit_node(node[:block])}" : nil
199
+ has_parens = !!(node[:has_parentheses] || args || block)
200
+ opening_delimiter = if has_parens
201
+ "("
202
+ else
203
+ nil
204
+ end
205
+ call = "#{obj}#{node[:name]}#{opening_delimiter}#{args}#{")" if has_parens}#{block}"
206
+ src.write_ln(call)
207
+
208
+ when "Block"
209
+ args = render_args node
210
+ src.write "{ #{args[:representation].gsub(/(\A\(|\)\z)/,'|')}\n"
211
+
212
+ indented(src) { src.write visit_node(node[:body]) }
213
+
214
+ src.write_ln "}"
215
+
216
+ when "RangeLiteral"
217
+ dots = node[:exclusive] ? "..." : ".."
218
+
219
+ # parentheses around the whole thing help the compatibility with precendence of operators in some situations
220
+ # eg. (1..3).cover? 2 vs. 1..3.cover? 2
221
+ # parentheses around either number help with things like arithemtic ((x-1)..(y+5))
222
+ src.write "(", "(", visit_node(node[:from]), ")", dots, "(", visit_node(node[:to]), ")", ")"
223
+
224
+ when "LiteralNode"
225
+
226
+ src.write node[:value]
227
+
228
+ when "ArrayLiteral"
229
+
230
+ src.write("[", node[:elements].map { |e| visit_node(e).strip }.join(", "), "]")
231
+ src.write ".freeze" if node[:frozen]
232
+
233
+ when "StringInterpolation"
234
+
235
+ contents = node[:contents].inject(String.new) do |str, c|
236
+ str << case c[:type]
237
+ when "LiteralNode"
238
+ c[:value][1...-1]
239
+ else
240
+ [%q|#{|, visit_node(c).strip, "}"].join
241
+ end
242
+ end
243
+ src.write '"', contents, '"'
244
+
245
+ when "Path"
246
+
247
+ src.write node[:value]
248
+
249
+ when "Require"
250
+
251
+ src.write_ln %(require "#{node[:value]}")
252
+
253
+ when "Assign", "OpAssign"
254
+
255
+ src.write_ln "#{visit_node(node[:target])} #{node[:op]}= #{visit_node(node[:value]).strip}"
256
+
257
+ when "MultiAssign"
258
+
259
+ src.write_ln "#{node[:targets].map{ |t| visit_node(t).strip }.join(", ")} = #{node[:values].map { |v| visit_node(v).strip }.join(", ")}"
260
+
261
+ when "Var"
262
+
263
+ if @eval_vars
264
+ src.write scope[node[:name]]
265
+ else
266
+ src.write node[:name]
267
+ end
268
+
269
+ when "InstanceVar"
270
+
271
+ src.write node[:name]
272
+
273
+ when "GlobalVar"
274
+
275
+ src.write node[:name]
276
+
277
+ when "Arg"
278
+ val = node[:external_name]
279
+ if node[:keyword_arg]
280
+ val += ":"
281
+ val += " #{visit_node(node[:value])}" if node[:value]
282
+ elsif node[:value]
283
+ val += " = #{visit_node(node[:value])}"
284
+ end
285
+
286
+ src.write val
287
+
288
+ when "UnaryExpr"
289
+
290
+ src.write "#{node[:op]}#{visit_node(node[:value]).strip}"
291
+
292
+ when "BinaryOp"
293
+
294
+ src.write visit_node(node[:left]).strip, " #{node[:op]} ", visit_node(node[:right]).strip
295
+
296
+ when "HashLiteral"
297
+
298
+ contents = node[:elements].map do |k, v|
299
+ key = case k
300
+ when String
301
+ k.to_sym.inspect
302
+ else
303
+ visit_node k
304
+ end
305
+ value = visit_node v
306
+ "#{key} => #{value}"
307
+ end
308
+
309
+ src.write "{#{contents.join(",\n")}}"
310
+ src.write ".freeze" if node[:frozen]
311
+
312
+ when "Enum"
313
+ src.write_ln "module #{node[:name]}"
314
+ node[:members].each_with_index do |m, i|
315
+ indented(src) { src.write_ln(visit_node(m) + (!m[:value] ? " = #{i}" : "")) }
316
+ end
317
+ src.write_ln "end"
318
+ when "If"
319
+ src.write_ln "(if #{visit_node(node[:condition]).strip}"
320
+
321
+ indented(src) { src.write_ln visit_node(node[:then]) }
322
+
323
+ if node[:else]
324
+ src.write_ln "else"
325
+ indented(src) { src.write_ln visit_node(node[:else]) }
326
+ end
327
+
328
+ src.write_ln "end)"
329
+ when "Unless"
330
+ src.write_ln "unless #{visit_node node[:condition]}"
331
+ indented(src) { src.write_ln visit_node(node[:then]) }
332
+
333
+ if node[:else]
334
+ src.write_ln "else"
335
+ indented(src) { src.write_ln visit_node(node[:else]) }
336
+ end
337
+
338
+ src.write_ln "end"
339
+ when "Case"
340
+ src.write "case"
341
+ src.write " #{visit_node(node[:condition]).strip}\n" if node[:condition]
342
+ indented(src) do
343
+ node[:whens].each do |w|
344
+ src.write_ln visit_node(w)
345
+ end
346
+ if node[:else]
347
+ src.write_ln "else"
348
+ indented(src) do
349
+ src.write_ln visit_node(node[:else])
350
+ end
351
+ end
352
+ end
353
+ src.write_ln "end"
354
+ when "When"
355
+ src.write_ln "when #{node[:conditions].map { |n| visit_node(n) }.join(", ")}"
356
+
357
+ indented(src) { src.write_ln(node[:body] ? visit_node(node[:body]) : "# no op") }
358
+ when "MacroFor"
359
+ vars, expr, body = node[:vars], node[:expr], node[:body]
360
+ var_names = vars.map { |v| visit_node v }
361
+ @inside_macro = true
362
+ indent_level = @indent_level
363
+ @indent_level -= 1 unless indent_level.zero?
364
+ expanded : Array[String] = eval(visit_node(expr)).map do |*a|
365
+ locals = [var_names.join(%(", "))].zip(a).to_h
366
+ locals.merge!(scope) if @inside_macro
367
+ visit_node(body, locals)
368
+ end.flatten
369
+ @indent_level += 1 unless indent_level.zero?
370
+ expanded.each { |e| src.write e }
371
+ @inside_macro = false
372
+ when "MacroLiteral"
373
+ src.write node[:value]
374
+ when "MacroExpression"
375
+ if node[:output]
376
+ expr = visit_node node[:expr], scope
377
+ val = scope[expr]
378
+ src.write val
379
+ end
380
+ when "MacroIf"
381
+ if evaluate_macro_condition(node[:condition], scope)
382
+ src.write_ln visit_node(node[:then], scope) if node[:then]
383
+ else
384
+ src.write_ln visit_node(node[:else], scope) if node[:else]
385
+ end
386
+ when "Return"
387
+ val = node[:value] ? " #{visit_node(node[:value]).strip}" : nil
388
+ src.write "return#{val}"
389
+ when "TypeDeclaration"
390
+ src.write_ln "# @type var #{visit_node(node[:var])}: #{visit_node(node[:declared_type])}"
391
+ src.write_ln "#{visit_node(node[:var])} = #{visit_node(node[:value])}"
392
+ when "ExceptionHandler"
393
+ src.write_ln "begin"
394
+ indented src do
395
+ src.write_ln visit_node(node[:body])
396
+ end
397
+ if node[:rescues]
398
+ node[:rescues].each do |r|
399
+ src.write_ln "rescue #{r[:types].map { |n| visit_node n }.join(", ") if r[:types]}#{" => #{r[:name]}" if r[:name]}"
400
+ indented(src) { src.write_ln visit_node(r[:body]) } if r[:body]
401
+ end
402
+ end
403
+ if node[:else]
404
+ src.write_ln "else"
405
+ indented(src) { src.write_ln visit_node(node[:else]) }
406
+ end
407
+ if node[:ensure]
408
+ src.write_ln "ensure"
409
+ indented(src) { src.write_ln visit_node(node[:ensure]) }
410
+ end
411
+ src.write_ln "end"
412
+ when "Generic"
413
+ src.write "#{visit_node(node[:name])}[#{node[:args].map { |a| visit_node a }.join(", ")}]"
414
+ when "Proc"
415
+ fn = node[:function]
416
+ src.write "->#{render_args(fn)} { #{visit_node fn[:body]} }"
417
+ when "Include"
418
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
419
+ name = visit_node node[:name]
420
+ src.write_ln "include #{name}"
421
+ type = RBS::AST::Members::Include.new(
422
+ name: method(:TypeName).call(name),
423
+ args: Array.new,
424
+ annotations: Array.new,
425
+ location: node[:location],
426
+ comment: node[:comment]
427
+ )
428
+ if @current_scope
429
+ @current_scope.members << type
430
+ else
431
+ @type_checker.type_env << type
432
+ end
433
+ when "Extend"
434
+ current_namespace = @current_scope ? @current_scope.name.to_namespace : RBS::Namespace.root
435
+ name = visit_node node[:name]
436
+ src.write_ln "extend #{name}"
437
+ type = RBS::AST::Members::Extend.new(
438
+ name: method(:TypeName).call(name),
439
+ args: Array.new,
440
+ annotations: Array.new,
441
+ location: node[:location],
442
+ comment: node[:comment]
443
+ )
444
+ if @current_scope
445
+ @current_scope.members << type
446
+ else
447
+ @type_checker.type_env << type
448
+ end
449
+ when "RegexLiteral"
450
+ contents = visit_node node[:value]
451
+ src.write Regexp.new(contents.undump).inspect
452
+ when "Union"
453
+ types = node[:types]
454
+ output = if types.length == 2 && types[1][:type] == "Path" && types[1]["value"] == nil
455
+ "#{visit_node(types[0])}?"
456
+ else
457
+ types.map { |t| visit_node(t) }.join(" | ")
458
+ end
459
+ src.write output
460
+ when "Next"
461
+ val = " #{node[:value]}" if node[:value]
462
+ src.write "next#{val}"
463
+ when "EmptyNode"
464
+ # pass
465
+ else
466
+ raise "Not implemented: #{node[:type]}"
467
+ end
468
+
469
+ src
470
+ end
471
+
472
+ private def evaluate_macro_condition(condition_node, scope)
473
+ @eval_vars = true
474
+ eval(visit_node(condition_node, scope))
475
+ @eval_vars = false
476
+ end
477
+
478
+ private def indented(src)
479
+ increment_indent(src)
480
+ yield
481
+ decrement_indent(src)
482
+ end
483
+
484
+ private def increment_indent(src)
485
+ @indent_level += 1
486
+ src.increment_indent
487
+ end
488
+
489
+ private def decrement_indent(src)
490
+ @indent_level -= 1
491
+ src.decrement_indent
492
+ end
493
+
494
+ # TODO: allow NamedTuple as return type
495
+ private def render_args(node)# : { representation: String, types: Hash[Symbol, Any] }
496
+ rp : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| !a[:value] }
497
+ op : Array[Hash[Symbol, Any]] = node.fetch(:positional_args) { EMPTY_ARRAY }.filter { |a| a[:value] }
498
+ rkw : Hash[Symbol, Any] = node.fetch(:req_kw_args) { EMPTY_HASH }
499
+ okw : Hash[Symbol, Any] = node.fetch(:opt_kw_args) { EMPTY_HASH }
500
+ rest_p : String? = node[:rest_p_args] ? visit_node(node[:rest_p_args]) : nil
501
+ rest_kw : Hash[Symbol, Any]? = node[:rest_kw_args]
502
+ return nil if [rp, op, rkw, okw, rest_p, rest_kw].all? { |a| a && a.empty? }
503
+
504
+ contents = [
505
+ rp.map { |a| visit_node(a) },
506
+ op.map { |a| "#{a[:name]} = #{visit_node(a[:value]).strip}" },
507
+ rkw.map { |name, _| "#{name}:" },
508
+ okw.map { |name, value| "#{name}: #{value}" },
509
+ rest_p ? "*#{rest_p}" : "",
510
+ rest_kw ? "**#{visit_node(rest_kw)}" : ""
511
+ ].reject(&:empty?).flatten.join(", ")
512
+ representation = "(#{contents})"
513
+ rp.map! do |a|
514
+ RBS::Types::Function::Param.new(
515
+ name: visit_node(a).to_sym,
516
+ type: RBS::Types::Bases::Any.new(
517
+ location: a[:location]
518
+ )
519
+ )
520
+ end
521
+ op.map! do |a|
522
+ RBS::Types::Function::Param.new(
523
+ name: visit_node(a).to_sym,
524
+ type: RBS::Types::Bases::Any.new(location: a[:location])
525
+ )
526
+ end
527
+ rest_p = (rpa = node[:rest_p_args]) ? RBS::Types::Function::Param.new(name: visit_node(rpa).to_sym, type: RBS::Types::Bases::Any.new(location: node[:location])) : nil
528
+ {
529
+ representation: representation,
530
+ types: {
531
+ required_positionals: rp,
532
+ optional_positionals: op,
533
+ rest_positionals: rest_p,
534
+ trailing_positionals: EMPTY_ARRAY, # TODO
535
+ required_keywords: node[:req_kw_args] || EMPTY_HASH,
536
+ optional_keywords: node[:opt_kw_args] || EMPTY_HASH,
537
+ rest_keywords: node[:rest_kw_args] ?
538
+ RBS::Types::Function::Param.new(
539
+ name: visit_node(node[:rest_kw_args]).to_sym,
540
+ type: RBS::Types::Bases::Any.new(location: node[:location])
541
+ ) : nil
542
+ }
543
+ }
544
+ end
545
+ end
546
+ end