gloss 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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