gloss 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +3 -0
  3. data/.github/workflows/{crystal.yml → crystal_specs.yml} +1 -1
  4. data/.github/workflows/{ruby.yml → ruby_specs.yml} +1 -1
  5. data/.gloss.yml +1 -0
  6. data/.rspec +1 -0
  7. data/Gemfile.lock +25 -27
  8. data/README.md +36 -5
  9. data/exe/gloss +13 -2
  10. data/ext/gloss/{src/lib → lib}/cr_ruby.cr +0 -0
  11. data/ext/gloss/lib/rbs_types.cr +3 -0
  12. data/ext/gloss/spec/parser_spec.cr +83 -50
  13. data/ext/gloss/src/cr_ast.cr +146 -72
  14. data/ext/gloss/src/gloss.cr +2 -2
  15. data/ext/gloss/src/lexer.cr +59 -1
  16. data/ext/gloss/src/parser.cr +4 -4
  17. data/ext/gloss/src/rb_ast.cr +152 -57
  18. data/lib/gloss.rb +15 -7
  19. data/lib/gloss/cli.rb +85 -27
  20. data/lib/gloss/config.rb +18 -10
  21. data/lib/gloss/errors.rb +13 -7
  22. data/lib/gloss/initializer.rb +11 -6
  23. data/lib/gloss/logger.rb +29 -0
  24. data/lib/gloss/parser.rb +22 -5
  25. data/lib/gloss/prog_loader.rb +141 -0
  26. data/lib/gloss/scope.rb +7 -2
  27. data/lib/gloss/source.rb +17 -14
  28. data/lib/gloss/type_checker.rb +105 -66
  29. data/lib/gloss/utils.rb +44 -0
  30. data/lib/gloss/version.rb +6 -1
  31. data/lib/gloss/visitor.rb +667 -0
  32. data/lib/gloss/watcher.rb +63 -19
  33. data/lib/gloss/writer.rb +35 -18
  34. data/sig/core.rbs +2 -0
  35. data/sig/fast_blank.rbs +4 -0
  36. data/sig/gls.rbs +3 -0
  37. data/sig/listen.rbs +1 -0
  38. data/sig/optparse.rbs +6 -0
  39. data/sig/rubygems.rbs +9 -0
  40. data/sig/yaml.rbs +3 -0
  41. data/src/exe/gloss +19 -0
  42. data/src/lib/gloss.gl +26 -0
  43. data/src/lib/gloss/cli.gl +70 -0
  44. data/src/lib/gloss/config.gl +21 -0
  45. data/src/lib/gloss/errors.gl +11 -0
  46. data/src/lib/gloss/initializer.gl +20 -0
  47. data/src/lib/gloss/logger.gl +21 -0
  48. data/src/lib/gloss/parser.gl +31 -0
  49. data/src/lib/gloss/prog_loader.gl +133 -0
  50. data/src/lib/gloss/scope.gl +7 -0
  51. data/src/lib/gloss/source.gl +32 -0
  52. data/src/lib/gloss/type_checker.gl +119 -0
  53. data/src/lib/gloss/utils.gl +38 -0
  54. data/src/lib/gloss/version.gl +3 -0
  55. data/src/lib/gloss/visitor.gl +575 -0
  56. data/src/lib/gloss/watcher.gl +66 -0
  57. data/src/lib/gloss/writer.gl +35 -0
  58. metadata +35 -8
  59. data/lib/gloss/builder.rb +0 -393
  60. data/src/lib/hrb/initializer.gl +0 -22
  61. data/src/lib/hrb/watcher.gl +0 -32
data/lib/gloss/scope.rb CHANGED
@@ -1,9 +1,14 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
2
5
 
3
6
  module Gloss
4
7
  class Scope < Hash
5
8
  def [](k)
6
- fetch(k) { raise "Undefined expression for current scope: #{k}" }
9
+ fetch(k) { ||
10
+ raise("Undefined expression for current scope: #{k}")
11
+ }
7
12
  end
8
13
  end
9
14
  end
data/lib/gloss/source.rb CHANGED
@@ -1,31 +1,34 @@
1
- # frozen_string_literal: true
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
2
5
 
3
6
  module Gloss
4
7
  class Source < String
5
8
  def initialize(indent_level)
6
- super()
7
9
  @indent_level = indent_level
10
+ super()
8
11
  end
9
-
10
12
  def write(*args)
11
- args.each do |a|
12
- self << a
13
- end
13
+ args.each() { |a|
14
+ self.<<(a)
15
+ }
16
+ self
14
17
  end
15
-
16
18
  def write_indnt(*args)
17
- write(*args.map { |a| "#{(" " * @indent_level)}#{a}" })
19
+ write(*args.map() { |a|
20
+ "#{" ".*(@indent_level)}#{a}" })
18
21
  end
19
-
20
22
  def write_ln(*args)
21
- write_indnt(*args.map { |a| a.strip << "\n" })
23
+ write_indnt(*args.map() { |a|
24
+ a.strip
25
+ .<<("\n")
26
+ })
22
27
  end
23
-
24
- def increment_indent
28
+ def increment_indent()
25
29
  @indent_level += 1
26
30
  end
27
-
28
- def decrement_indent
31
+ def decrement_indent()
29
32
  @indent_level -= 1
30
33
  end
31
34
  end
@@ -1,75 +1,114 @@
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 "set"
3
7
  module Gloss
4
8
  class TypeChecker
5
- attr_reader :steep_target, :top_level_decls
6
-
7
- def initialize
8
- @steep_target = Steep::Project::Target.new(
9
- name: "gloss",
10
- options: Steep::Project::Options.new,
11
- source_patterns: ["gloss"],
12
- ignore_patterns: [],
13
- signature_patterns: []
14
- )
15
- @top_level_decls = {}
16
- Dir.glob("sig/**/*.rbs").each do |fp|
17
- next if !@steep_target.possible_signature_file?(fp) || @steep_target.signature_file?(fp)
18
-
19
- Steep.logger.info { "Adding signature file: #{fp}" }
20
- @steep_target.add_signature path, (Pathname(".") + fp).cleanpath.read
21
- end
9
+ Project = Struct.new(:"targets")
10
+ attr_reader(:"steep_target", :"top_level_decls", :"env", :"rbs_gem_dir")
11
+ def initialize()
12
+ @steep_target = Steep::Project::Target.new(name: "gloss", options: Steep::Project::Options.new
13
+ .tap() { |o|
14
+ o.allow_unknown_constant_assignment=(true)
15
+ }, source_patterns: ["**/*.rb"], ignore_patterns: Array.new, signature_patterns: ["sig"])
16
+ @top_level_decls = Set.new
17
+ @rbs_gem_dir = Utils.gem_path_for("rbs")
18
+ env_loader = RBS::EnvironmentLoader.new
19
+ @env = RBS::Environment.from_loader(env_loader)
20
+ project = Steep::Project.new(steepfile_path: Pathname.new(Config.src_dir)
21
+ .realpath)
22
+ project.targets
23
+ .<<(@steep_target)
24
+ loader = Steep::Project::FileLoader.new(project: project)
22
25
  end
23
-
24
- def run(rb_str)
25
- unless check_types(rb_str)
26
- raise Errors::TypeError,
27
- @steep_target.errors.map { |e|
28
- case e
29
- when Steep::Errors::NoMethod
30
- "Unknown method :#{e.method}, location: #{e.type.location.inspect}"
31
- when Steep::Errors::MethodBodyTypeMismatch
32
- "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
33
- when Steep::Errors::IncompatibleArguments
34
- "Invalid argmuents - method type: #{e.method_type}, receiver type: #{e.receiver_type}"
35
- when Steep::Errors::ReturnTypeMismatch
36
- "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
37
- when Steep::Errors::IncompatibleAssignment
38
- "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
39
- else
40
- e.inspect
41
- end
42
- }.join("\n")
26
+ def run(filepath, rb_str)
27
+ begin
28
+ valid_types = check_types(filepath, rb_str)
29
+ rescue ParseError => e
30
+ throw(:"error", "")
31
+ rescue => e
32
+ throw(:"error", "Type checking Error: #{e.message} (#{e.class})")
43
33
  end
44
-
45
- true
46
- end
47
-
48
- def check_types(rb_str)
49
- env_loader = RBS::EnvironmentLoader.new
50
- env = RBS::Environment.from_loader(env_loader)
51
-
52
- @top_level_decls.each do |_, decl|
53
- env << decl
34
+ unless valid_types
35
+ errors = @steep_target.errors
36
+ .map() { |e|
37
+ case e
38
+ when Steep::Diagnostic::Ruby::NoMethod
39
+ "Unknown method :#{e.method}, location: #{e.type
40
+ .location
41
+ .inspect}"
42
+ when Steep::Diagnostic::Ruby::MethodBodyTypeMismatch
43
+ "Invalid method body type - expected: #{e.expected}, actual: #{e.actual}"
44
+ when Steep::Diagnostic::Ruby::IncompatibleArguments
45
+ "Invalid argmuents - method type: #{e.method_types
46
+ .first}\nmethod name: #{e.method_name}"
47
+ when Steep::Diagnostic::Ruby::ReturnTypeMismatch
48
+ "Invalid return type - expected: #{e.expected}, actual: #{e.actual}"
49
+ when Steep::Diagnostic::Ruby::IncompatibleAssignment
50
+ "Invalid assignment - cannot assign #{e.rhs_type} to type #{e.lhs_type}"
51
+ when Steep::Diagnostic::Ruby::UnexpectedBlockGiven
52
+ "Unexpected block given"
53
+ else
54
+ "#{e.header_line}\n#{e.inspect}"
55
+ end
56
+ }
57
+ .join("\n")
58
+ throw(:"error", errors)
54
59
  end
55
- env = env.resolve_type_names
56
-
57
- @steep_target.instance_variable_set("@environment", env)
58
- @steep_target.add_source("gloss", rb_str)
59
-
60
- definition_builder = RBS::DefinitionBuilder.new(env: env)
61
- factory = Steep::AST::Types::Factory.new(builder: definition_builder)
62
- check = Steep::Subtyping::Check.new(factory: factory)
63
- validator = Steep::Signature::Validator.new(checker: check)
64
- validator.validate
65
-
66
- raise Errors::TypeValidationError, validator.each_error.to_a.join("\n") unless validator.no_error?
67
-
68
- @steep_target.run_type_check(env, check, Time.now)
69
-
70
- @steep_target.status.is_a?(Steep::Project::Target::TypeCheckStatus) &&
71
- @steep_target.no_error? &&
72
- @steep_target.errors.empty?
60
+ true
61
+ end
62
+ def ready_for_checking!()
63
+ @top_level_decls.each() { |decl|
64
+ @env.<<(decl)
65
+ }
66
+ @env = @env.resolve_type_names
67
+ @steep_target.instance_variable_set("@environment", @env)
68
+ end
69
+ def check_types(filepath, rb_str)
70
+ @steep_target.add_source(filepath, rb_str)
71
+ ready_for_checking!
72
+ @steep_target.type_check
73
+ (if @steep_target.status
74
+ .is_a?(Steep::Project::Target::SignatureErrorStatus)
75
+ throw(:"error", @steep_target.status
76
+ .errors
77
+ .map() { |e|
78
+ msg = case e
79
+ when Steep::Diagnostic::Signature::UnknownTypeName
80
+ "Unknown type name: #{e.name
81
+ .name} (#{e.location
82
+ .source
83
+ .[](/^.*$/)})"
84
+ when Steep::Diagnostic::Signature::InvalidTypeApplication
85
+ "Invalid type application: #{e.header_line}"
86
+ when Steep::Diagnostic::Signature::DuplicatedMethodDefinition
87
+ "Duplicated method: #{e.header_line}"
88
+ else
89
+ e.header_line
90
+ end
91
+ " SignatureSyntaxError:\n Location: #{e.location}\n Message: \"#{msg}\"" }
92
+ .join("\n"))
93
+ end)
94
+ @steep_target.source_files
95
+ .each() { |path, f|
96
+ (if f.status
97
+ .is_a?(Steep::Project::SourceFile::ParseErrorStatus)
98
+ e = f.status
99
+ .error
100
+ throw(:"error", "#{e.class}: #{e.message}")
101
+ end)
102
+ }
103
+ @steep_target.status
104
+ .is_a?(Steep::Project::Target::TypeCheckStatus) && @steep_target.no_error? && @steep_target.errors
105
+ .empty?
106
+ end
107
+ def load_sig_path(path)
108
+ Gloss.logger
109
+ .debug("Loading signature file for #{path}")
110
+ @steep_target.add_signature(path, File.open(path)
111
+ .read)
73
112
  end
74
113
  end
75
114
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ require "rubygems"
7
+ module Gloss
8
+ module Utils
9
+ module_function
10
+ def absolute_path(path)
11
+ pn = Pathname.new(path)
12
+ (if pn.absolute?
13
+ pn.to_s
14
+ else
15
+ ap = File.absolute_path(path)
16
+ (if File.exist?(ap)
17
+ ap
18
+ else
19
+ throw(:"error", "File path #{path} does not exist (also looked for #{ap})")
20
+ end)
21
+ end)
22
+ end
23
+ def gem_path_for(gem_name)
24
+ begin
25
+ Gem.ui
26
+ .instance_variable_set(:"@outs", StringIO.new)
27
+ Gem::GemRunner.new
28
+ .run(["which", gem_name])
29
+ Gem.ui
30
+ .outs
31
+ .string
32
+ rescue SystemExit => e
33
+ nil
34
+ end
35
+ end
36
+ def with_file_header(str)
37
+ "#{Visitor::FILE_HEADER}\n\n#{str}"
38
+ end
39
+ def src_path_to_output_path(src_path)
40
+ src_path.sub("#{Config.src_dir}/", "")
41
+ .sub(/\.gl$/, ".rb")
42
+ end
43
+ end
44
+ end
data/lib/gloss/version.rb CHANGED
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
1
6
  module Gloss
2
- VERSION = "0.0.2"
7
+ VERSION = "0.1.0"
3
8
  end
@@ -0,0 +1,667 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##### This file was generated by Gloss; any changes made here will be overwritten.
4
+ ##### See src/ to make changes
5
+
6
+ module Gloss
7
+ class Visitor
8
+ FILE_HEADER = " #{(if Config.frozen_string_literals
9
+ "# frozen_string_literal: true\n"
10
+ end)}\n ##### This file was generated by Gloss; any changes made here will be overwritten.\n ##### See #{Config.src_dir}/ to make changes"
11
+ attr_reader(:"tree")
12
+ def initialize(tree_hash, type_checker = nil, on_new_file_referenced = nil)
13
+ @on_new_file_referenced = on_new_file_referenced
14
+ @indent_level = 0
15
+ @inside_macro = false
16
+ @eval_vars = false
17
+ @current_scope = nil
18
+ @tree = tree_hash
19
+ @type_checker = type_checker
20
+ @after_module_function = false
21
+ end
22
+ def run()
23
+ rb_output = visit_node(@tree)
24
+ Utils.with_file_header(rb_output)
25
+ end
26
+ def visit_node(node, scope = Scope.new)
27
+ src = Source.new(@indent_level)
28
+ case node.[](:"type")
29
+ when "ClassNode"
30
+ class_name = visit_node(node.[](:"name"))
31
+ current_namespace = (if @current_scope
32
+ @current_scope.name
33
+ .to_namespace
34
+ else
35
+ RBS::Namespace.root
36
+ end)
37
+ superclass_type = nil
38
+ superclass_output = ""
39
+ (if node.[](:"superclass")
40
+ @eval_vars = true
41
+ superclass_output = visit_node(node.[](:"superclass"))
42
+ @eval_vars = false
43
+ args = Array.new
44
+ (if node.dig(:"superclass", :"type")
45
+ .==("Generic")
46
+ superclass_output = superclass_output.[](/^[^\[]+/) || superclass_output
47
+ args = node.dig(:"superclass", :"args")
48
+ .map() { |n|
49
+ RBS::Parser.parse_type(visit_node(n))
50
+ }
51
+ end)
52
+ class_name_index = superclass_output.index(/[^(?:::)]+\z/) || 0
53
+ namespace = superclass_output.[](0, class_name_index)
54
+ superclass_name = superclass_output.[](/[^(?:::)]+\z/) || superclass_output
55
+ superclass_type = RBS::AST::Declarations::Class::Super.new(name: RBS::TypeName.new(namespace: method(:"Namespace")
56
+ .call(namespace), name: superclass_name.to_sym), args: args, location: build_location(node))
57
+ end)
58
+ src.write_ln("class #{class_name}#{unless superclass_output.blank?
59
+ " < #{superclass_output}"
60
+ end}")
61
+ class_type = RBS::AST::Declarations::Class.new(name: RBS::TypeName.new(namespace: current_namespace, name: class_name.to_sym), type_params: RBS::AST::Declarations::ModuleTypeParams.new, super_class: superclass_type, members: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
62
+ old_parent_scope = @current_scope
63
+ @current_scope = class_type
64
+ indented(src) { ||
65
+ (if node.[](:"body")
66
+ src.write_ln(visit_node(node.[](:"body")))
67
+ end)
68
+ }
69
+ src.write_ln("end")
70
+ @current_scope = old_parent_scope
71
+ (if @current_scope
72
+ @current_scope.members
73
+ .<<(class_type)
74
+ end)
75
+ (if @type_checker && !@current_scope
76
+ @type_checker.top_level_decls
77
+ .add(class_type)
78
+ end)
79
+ when "ModuleNode"
80
+ existing_module_function_state = @after_module_function.dup
81
+ @after_module_function = false
82
+ module_name = visit_node(node.[](:"name"))
83
+ src.write_ln("module #{module_name}")
84
+ current_namespace = (if @current_scope
85
+ @current_scope.name
86
+ .to_namespace
87
+ else
88
+ RBS::Namespace.root
89
+ end)
90
+ module_type = RBS::AST::Declarations::Module.new(name: RBS::TypeName.new(namespace: current_namespace, name: module_name.to_sym), type_params: RBS::AST::Declarations::ModuleTypeParams.new, self_types: Array.new, members: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
91
+ old_parent_scope = @current_scope
92
+ @current_scope = module_type
93
+ indented(src) { ||
94
+ (if node.[](:"body")
95
+ src.write_ln(visit_node(node.[](:"body")))
96
+ end)
97
+ }
98
+ @current_scope = old_parent_scope
99
+ (if @current_scope
100
+ @current_scope.members
101
+ .<<(module_type)
102
+ end)
103
+ (if @type_checker && !@current_scope
104
+ @type_checker.top_level_decls
105
+ .add(module_type)
106
+ end)
107
+ src.write_ln("end")
108
+ @after_module_function = existing_module_function_state
109
+ when "DefNode"
110
+ args = render_args(node)
111
+ receiver = (if node.[](:"receiver")
112
+ visit_node(node.[](:"receiver"))
113
+ else
114
+ nil
115
+ end)
116
+ src.write_ln("def #{(if receiver
117
+ "#{receiver}."
118
+ end)}#{node.[](:"name")}#{args.[](:"representation")}")
119
+ return_type = (if node.[](:"return_type")
120
+ RBS::Types::ClassInstance.new(name: RBS::TypeName.new(name: eval(visit_node(node.[](:"return_type")))
121
+ .to_s
122
+ .to_sym, namespace: RBS::Namespace.root), args: EMPTY_ARRAY, location: build_location(node))
123
+ else
124
+ RBS::Types::Bases::Any.new(location: build_location(node))
125
+ end)
126
+ method_types = [RBS::MethodType.new(type_params: EMPTY_ARRAY, type: RBS::Types::Function.new(required_positionals: args.dig(:"types", :"required_positionals"), optional_positionals: args.dig(:"types", :"optional_positionals"), rest_positionals: args.dig(:"types", :"rest_positionals"), trailing_positionals: args.dig(:"types", :"trailing_positionals"), required_keywords: args.dig(:"types", :"required_keywords"), optional_keywords: args.dig(:"types", :"optional_keywords"), rest_keywords: args.dig(:"types", :"rest_keywords"), return_type: return_type), block: (if node.[](:"yield_arg_count")
127
+ RBS::Types::Block.new(type: RBS::Types::Function.new(required_positionals: Array.new, optional_positionals: Array.new, rest_positionals: nil, trailing_positionals: Array.new, required_keywords: Hash.new, optional_keywords: Hash.new, rest_keywords: nil, return_type: RBS::Types::Bases::Any.new(location: build_location(node))), required: !!node.[](:"block_arg") || node.[](:"yield_arg_count"))
128
+ else
129
+ nil
130
+ end), location: build_location(node))]
131
+ method_definition = RBS::AST::Members::MethodDefinition.new(name: node.[](:"name")
132
+ .to_sym, kind: (if @after_module_function
133
+ :"singleton_instance"
134
+ else
135
+ (if receiver
136
+ :"singleton"
137
+ else
138
+ :"instance"
139
+ end)
140
+ end), types: method_types, annotations: EMPTY_ARRAY, location: build_location(node), comment: node.[](:"comment"), overload: false)
141
+ (if @current_scope
142
+ @current_scope.members
143
+ .<<(method_definition)
144
+ else
145
+ (if @type_checker
146
+ @type_checker.type_env
147
+ .<<(method_definition)
148
+ end)
149
+ end)
150
+ indented(src) { ||
151
+ (if node.[](:"body")
152
+ src.write_ln(visit_node(node.[](:"body")))
153
+ end)
154
+ }
155
+ src.write_ln("end")
156
+ when "VisibilityModifier"
157
+ src.write_ln("#{node.[](:"visibility")} #{visit_node(node.[](:"exp"))}")
158
+ when "CollectionNode"
159
+ node.[](:"children")
160
+ .each() { |a|
161
+ src.write(visit_node(a, scope))
162
+ }
163
+ when "Call"
164
+ obj = (if node.[](:"object")
165
+ "#{visit_node(node.[](:"object"), scope)}."
166
+ else
167
+ ""
168
+ end)
169
+ arg_arr = node.fetch(:"args") { ||
170
+ EMPTY_ARRAY }
171
+ (if node.[](:"named_args")
172
+ arg_arr += node.[](:"named_args")
173
+ end)
174
+ args = (if !arg_arr.empty? || node.[](:"block_arg")
175
+ "#{arg_arr.map() { |a|
176
+ visit_node(a, scope)
177
+ .strip
178
+ }
179
+ .reject(&:"blank?")
180
+ .join(", ")}#{(if node.[](:"block_arg")
181
+ "&#{visit_node(node.[](:"block_arg"))
182
+ .strip}"
183
+ end)}"
184
+ else
185
+ nil
186
+ end)
187
+ block = (if node.[](:"block")
188
+ " #{visit_node(node.[](:"block"))}"
189
+ else
190
+ nil
191
+ end)
192
+ has_parens = !!node.[](:"has_parentheses") || args || block
193
+ opening_delimiter = (if has_parens
194
+ "("
195
+ else
196
+ nil
197
+ end)
198
+ name = node.[](:"name")
199
+ call = "#{obj}#{name}#{opening_delimiter}#{args}#{(if has_parens
200
+ ")"
201
+ end)}#{block}"
202
+ case name
203
+ when "require_relative"
204
+ (if @on_new_file_referenced
205
+ @on_new_file_referenced.call(name, true)
206
+ end)
207
+ when "module_function"
208
+ @after_module_function = true
209
+ end
210
+ src.write_ln(call)
211
+ when "Block"
212
+ args = render_args(node)
213
+ src.write("{ #{args.[](:"representation")
214
+ .gsub(/(\A\(|\)\z)/, "|")}\n")
215
+ indented(src) { ||
216
+ src.write(visit_node(node.[](:"body")))
217
+ }
218
+ src.write_ln("}")
219
+ when "RangeLiteral"
220
+ dots = (if node.[](:"exclusive")
221
+ "..."
222
+ else
223
+ ".."
224
+ end)
225
+ src.write("(", "(", visit_node(node.[](:"from")), ")", dots, "(", visit_node(node.[](:"to")), ")", ")")
226
+ when "LiteralNode"
227
+ src.write(node.[](:"value"))
228
+ when "ArrayLiteral"
229
+ src.write("[", node.[](:"elements")
230
+ .map() { |e|
231
+ visit_node(e)
232
+ .strip
233
+ }
234
+ .join(", "), "]")
235
+ (if node.[](:"frozen")
236
+ src.write(".freeze")
237
+ end)
238
+ when "StringInterpolation"
239
+ contents = node.[](:"contents")
240
+ .inject(String.new) { |str, c|
241
+ str.<<(case c.[](:"type")
242
+ when "LiteralNode"
243
+ c.[](:"value")
244
+ .[](((1)...(-1)))
245
+ else
246
+ ["\#{", visit_node(c)
247
+ .strip, "}"].join
248
+ end)
249
+ }
250
+ src.write("\"", contents, "\"")
251
+ when "Path"
252
+ src.write(node.[](:"value"))
253
+ when "Require"
254
+ path = node.[](:"value")
255
+ src.write_ln("require \"#{path}\"")
256
+ (if @on_new_file_referenced
257
+ @on_new_file_referenced.call(path, false)
258
+ end)
259
+ when "Assign", "OpAssign"
260
+ src.write_ln("#{visit_node(node.[](:"target"))} #{node.[](:"op")}= #{visit_node(node.[](:"value"))
261
+ .strip}")
262
+ when "MultiAssign"
263
+ src.write_ln("#{node.[](:"targets")
264
+ .map() { |t|
265
+ visit_node(t)
266
+ .strip
267
+ }
268
+ .join(", ")} = #{node.[](:"values")
269
+ .map() { |v|
270
+ visit_node(v)
271
+ .strip
272
+ }
273
+ .join(", ")}")
274
+ when "Var"
275
+ (if @eval_vars
276
+ src.write(scope.[](node.[](:"name")))
277
+ else
278
+ src.write(node.[](:"name"))
279
+ end)
280
+ when "InstanceVar"
281
+ src.write(node.[](:"name"))
282
+ when "GlobalVar"
283
+ src.write(node.[](:"name"))
284
+ when "Arg"
285
+ val = node.[](:"external_name")
286
+ (if node.[](:"keyword_arg")
287
+ val += ":"
288
+ (if node.[](:"value")
289
+ val += " #{visit_node(node.[](:"value"))}"
290
+ end)
291
+ else
292
+ (if node.[](:"value")
293
+ val += " = #{visit_node(node.[](:"value"))}"
294
+ end)
295
+ end)
296
+ src.write(val)
297
+ when "UnaryExpr"
298
+ src.write("#{node.[](:"op")}#{visit_node(node.[](:"value"))
299
+ .strip}")
300
+ when "BinaryOp"
301
+ src.write(visit_node(node.[](:"left"))
302
+ .strip, " #{node.[](:"op")} ", visit_node(node.[](:"right"))
303
+ .strip)
304
+ when "HashLiteral"
305
+ contents = node.[](:"elements")
306
+ .map() { |k, v|
307
+ key = case k
308
+ when String
309
+ k.to_sym
310
+ .inspect
311
+ else
312
+ visit_node(k)
313
+ end
314
+ value = visit_node(v)
315
+ "#{key} => #{value}" }
316
+ src.write("{#{contents.join(",\n")}}")
317
+ (if node.[](:"frozen")
318
+ src.write(".freeze")
319
+ end)
320
+ when "Enum"
321
+ src.write_ln("module #{node.[](:"name")}")
322
+ node.[](:"members")
323
+ .each_with_index() { |m, i|
324
+ indented(src) { ||
325
+ src.write_ln(visit_node(m)
326
+ .+((if !m.[](:"value")
327
+ " = #{i}"
328
+ else
329
+ ""
330
+ end)))
331
+ }
332
+ }
333
+ src.write_ln("end")
334
+ when "If"
335
+ src.write_ln("(if #{visit_node(node.[](:"condition"))
336
+ .strip}")
337
+ indented(src) { ||
338
+ src.write_ln(visit_node(node.[](:"then")))
339
+ }
340
+ (if node.[](:"else")
341
+ src.write_ln("else")
342
+ indented(src) { ||
343
+ src.write_ln(visit_node(node.[](:"else")))
344
+ }
345
+ end)
346
+ src.write_ln("end)")
347
+ when "Unless"
348
+ src.write_ln("unless #{visit_node(node.[](:"condition"))}")
349
+ indented(src) { ||
350
+ src.write_ln(visit_node(node.[](:"then")))
351
+ }
352
+ (if node.[](:"else")
353
+ src.write_ln("else")
354
+ indented(src) { ||
355
+ src.write_ln(visit_node(node.[](:"else")))
356
+ }
357
+ end)
358
+ src.write_ln("end")
359
+ when "Case"
360
+ src.write("case")
361
+ (if node.[](:"condition")
362
+ src.write(" #{visit_node(node.[](:"condition"))
363
+ .strip}\n")
364
+ end)
365
+ indented(src) { ||
366
+ node.[](:"whens")
367
+ .each() { |w|
368
+ src.write_ln(visit_node(w))
369
+ }
370
+ (if node.[](:"else")
371
+ src.write_ln("else")
372
+ indented(src) { ||
373
+ src.write_ln(visit_node(node.[](:"else")))
374
+ }
375
+ end)
376
+ }
377
+ src.write_ln("end")
378
+ when "When"
379
+ src.write_ln("when #{node.[](:"conditions")
380
+ .map() { |n|
381
+ visit_node(n)
382
+ }
383
+ .join(", ")}")
384
+ indented(src) { ||
385
+ src.write_ln((if node.[](:"body")
386
+ visit_node(node.[](:"body"))
387
+ else
388
+ "# no op"
389
+ end))
390
+ }
391
+ when "MacroFor"
392
+ vars, expr, body = node.[](:"vars"), node.[](:"expr"), node.[](:"body")
393
+ var_names = vars.map() { |v|
394
+ visit_node(v)
395
+ }
396
+ @inside_macro = true
397
+ indent_level = @indent_level
398
+ unless indent_level.zero?
399
+ @indent_level -= 1
400
+ end
401
+ # @type var expanded: Array[String]
402
+ expanded = eval(visit_node(expr))
403
+ .map() { |*a|
404
+ locals = [var_names.join("\", \"")].zip(a)
405
+ .to_h
406
+ (if @inside_macro
407
+ locals.merge!(scope)
408
+ end)
409
+ visit_node(body, locals)
410
+ }
411
+ .flatten
412
+ unless indent_level.zero?
413
+ @indent_level += 1
414
+ end
415
+ expanded.each() { |e|
416
+ src.write(e)
417
+ }
418
+ @inside_macro = false
419
+ when "MacroLiteral"
420
+ src.write(node.[](:"value"))
421
+ when "MacroExpression"
422
+ (if node.[](:"output")
423
+ expr = visit_node(node.[](:"expr"), scope)
424
+ val = scope.[](expr)
425
+ src.write(val)
426
+ end)
427
+ when "MacroIf"
428
+ (if evaluate_macro_condition(node.[](:"condition"), scope)
429
+ (if node.[](:"then")
430
+ src.write_ln(visit_node(node.[](:"then"), scope))
431
+ end)
432
+ else
433
+ (if node.[](:"else")
434
+ src.write_ln(visit_node(node.[](:"else"), scope))
435
+ end)
436
+ end)
437
+ when "Return"
438
+ val = (if node.[](:"value")
439
+ " #{visit_node(node.[](:"value"))
440
+ .strip}"
441
+ else
442
+ nil
443
+ end)
444
+ src.write("return#{val}")
445
+ when "TypeDeclaration"
446
+ src.write_ln("# @type var #{visit_node(node.[](:"var"))}: #{visit_node(node.[](:"declared_type"))}")
447
+ value = (if node.[](:"value")
448
+ " = #{visit_node(node.[](:"value"))}"
449
+ else
450
+ nil
451
+ end)
452
+ src.write_ln("#{visit_node(node.[](:"var"))}#{value}")
453
+ when "ExceptionHandler"
454
+ src.write_ln("begin")
455
+ indented(src) { ||
456
+ src.write_ln(visit_node(node.[](:"body")))
457
+ }
458
+ (if node.[](:"rescues")
459
+ node.[](:"rescues")
460
+ .each() { |r|
461
+ src.write_ln("rescue #{(if r.[](:"types")
462
+ r.[](:"types")
463
+ .map() { |n|
464
+ visit_node(n)
465
+ }
466
+ .join(", ")
467
+ end)}#{(if r.[](:"name")
468
+ " => #{r.[](:"name")}"
469
+ end)}")
470
+ (if r.[](:"body")
471
+ indented(src) { ||
472
+ src.write_ln(visit_node(r.[](:"body")))
473
+ }
474
+ end)
475
+ }
476
+ end)
477
+ (if node.[](:"else")
478
+ src.write_ln("else")
479
+ indented(src) { ||
480
+ src.write_ln(visit_node(node.[](:"else")))
481
+ }
482
+ end)
483
+ (if node.[](:"ensure")
484
+ src.write_ln("ensure")
485
+ indented(src) { ||
486
+ src.write_ln(visit_node(node.[](:"ensure")))
487
+ }
488
+ end)
489
+ src.write_ln("end")
490
+ when "Generic"
491
+ src.write("#{visit_node(node.[](:"name"))}[#{node.[](:"args")
492
+ .map() { |a|
493
+ visit_node(a)
494
+ }
495
+ .join(", ")}]")
496
+ when "Proc"
497
+ fn = node.[](:"function")
498
+ src.write("->#{render_args(fn)} { #{visit_node(fn.[](:"body"))} }")
499
+ when "Include"
500
+ current_namespace = (if @current_scope
501
+ @current_scope.name
502
+ .to_namespace
503
+ else
504
+ RBS::Namespace.root
505
+ end)
506
+ name = visit_node(node.[](:"name"))
507
+ src.write_ln("include #{name}")
508
+ type = RBS::AST::Members::Include.new(name: method(:"TypeName")
509
+ .call(name), args: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
510
+ (if @current_scope
511
+ @current_scope.members
512
+ .<<(type)
513
+ else
514
+ @type_checker.type_env
515
+ .<<(type)
516
+ end)
517
+ when "Extend"
518
+ current_namespace = (if @current_scope
519
+ @current_scope.name
520
+ .to_namespace
521
+ else
522
+ RBS::Namespace.root
523
+ end)
524
+ name = visit_node(node.[](:"name"))
525
+ src.write_ln("extend #{name}")
526
+ type = RBS::AST::Members::Extend.new(name: method(:"TypeName")
527
+ .call(name), args: Array.new, annotations: Array.new, location: build_location(node), comment: node.[](:"comment"))
528
+ (if @current_scope
529
+ @current_scope.members
530
+ .<<(type)
531
+ else
532
+ @type_checker.type_env
533
+ .<<(type)
534
+ end)
535
+ when "RegexLiteral"
536
+ contents = visit_node(node.[](:"value"))
537
+ src.write(Regexp.new(contents.undump)
538
+ .inspect)
539
+ when "Union"
540
+ types = node.[](:"types")
541
+ output = (if types.length
542
+ .==(2) && types.[](1)
543
+ .[](:"type")
544
+ .==("Path") && types.[](1)
545
+ .[]("value")
546
+ .==(nil)
547
+ "#{visit_node(types.[](0))}?"
548
+ else
549
+ types.map() { |t|
550
+ visit_node(t)
551
+ }
552
+ .join(" | ")
553
+ end)
554
+ src.write(output)
555
+ when "Next"
556
+ (if node.[](:"value")
557
+ val = " #{node.[](:"value")}"
558
+ end)
559
+ src.write("next#{val}")
560
+ when "EmptyNode"
561
+ # no op
562
+ else
563
+ raise("Not implemented: #{node.[](:"type")}")
564
+ end
565
+ src
566
+ end
567
+ private def evaluate_macro_condition(condition_node, scope)
568
+ @eval_vars = true
569
+ eval(visit_node(condition_node, scope))
570
+ @eval_vars = false
571
+ end
572
+ private def indented(src)
573
+ increment_indent(src)
574
+ yield
575
+ decrement_indent(src)
576
+ end
577
+ private def increment_indent(src)
578
+ @indent_level += 1
579
+ src.increment_indent
580
+ end
581
+ private def decrement_indent(src)
582
+ @indent_level -= 1
583
+ src.decrement_indent
584
+ end
585
+ private def render_args(node)
586
+ # @type var rp: Array[Hash[Symbol, Any]]
587
+ rp = node.fetch(:"positional_args") { ||
588
+ EMPTY_ARRAY }
589
+ .filter() { |a|
590
+ !a.[](:"value") }
591
+ # @type var op: Array[Hash[Symbol, Any]]
592
+ op = node.fetch(:"positional_args") { ||
593
+ EMPTY_ARRAY }
594
+ .filter() { |a|
595
+ a.[](:"value")
596
+ }
597
+ # @type var rkw: Hash[Symbol, Any]
598
+ rkw = node.fetch(:"req_kw_args") { ||
599
+ EMPTY_HASH }
600
+ # @type var okw: Hash[Symbol, Any]
601
+ okw = node.fetch(:"opt_kw_args") { ||
602
+ EMPTY_HASH }
603
+ # @type var rest_p: String?
604
+ rest_p = (if node.[](:"rest_p_args")
605
+ visit_node(node.[](:"rest_p_args"))
606
+ else
607
+ nil
608
+ end)
609
+ # @type var rest_kw: Hash[Symbol, Any]?
610
+ rest_kw = node.[](:"rest_kw_args")
611
+ (if [rp, op, rkw, okw, rest_p, rest_kw].all?() { |a|
612
+ a && a.empty? }
613
+ return nil
614
+ end)
615
+ contents = [rp.map() { |a|
616
+ visit_node(a)
617
+ }, op.map() { |a|
618
+ "#{a.[](:"name")} = #{visit_node(a.[](:"value"))
619
+ .strip}" }, rkw.map() { |name, _|
620
+ "#{name}:" }, okw.map() { |name, value|
621
+ "#{name}: #{value}" }, (if rest_p
622
+ "*#{rest_p}"
623
+ else
624
+ ""
625
+ end), (if rest_kw
626
+ "**#{visit_node(rest_kw)}"
627
+ else
628
+ ""
629
+ end)].reject(&:"empty?")
630
+ .flatten
631
+ .join(", ")
632
+ representation = "(#{contents})"
633
+ rp_args = rp.map() { |a|
634
+ RBS::Types::Function::Param.new(name: visit_node(a)
635
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(a)))
636
+ }
637
+ op_args = op.map() { |a|
638
+ RBS::Types::Function::Param.new(name: visit_node(a)
639
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(a)))
640
+ }
641
+ rpa = (if rest_p
642
+ RBS::Types::Function::Param.new(name: rest_p.to_sym, type: RBS::Types::Bases::Any.new(location: build_location(node)))
643
+ else
644
+ nil
645
+ end)
646
+ {:representation => representation,
647
+ :types => {:required_positionals => rp_args,
648
+ :optional_positionals => op_args,
649
+ :rest_positionals => rpa,
650
+ :trailing_positionals => EMPTY_ARRAY,
651
+ :required_keywords => node.[](:"req_kw_args") || EMPTY_HASH,
652
+ :optional_keywords => node.[](:"opt_kw_args") || EMPTY_HASH,
653
+ :rest_keywords => (if node.[](:"rest_kw_args")
654
+ RBS::Types::Function::Param.new(name: visit_node(node.[](:"rest_kw_args"))
655
+ .to_sym, type: RBS::Types::Bases::Any.new(location: build_location(node)))
656
+ else
657
+ nil
658
+ end)
659
+ }.freeze}.freeze
660
+ end
661
+ def build_location(node)
662
+ unless node.[](:"location")
663
+ return nil
664
+ end
665
+ end
666
+ end
667
+ end