tapioca 0.4.21 → 0.4.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 832665c6f2a41c3eb5cfda2b0939b4a3f2d29eb624b64de49565b8275f461326
4
- data.tar.gz: 29e1f7066050ce4f308b1dc8591dd7de236e19560618a37c9120a4835ac1b295
3
+ metadata.gz: 8e3ea6e1fb437f52cfb699c43b4c3d46115592ac09a69aec5a8d0701906f42cd
4
+ data.tar.gz: 9fba45117a3f1f72ab8d3278838fb7d4a860eaa7cadc03535d99be618e8994c9
5
5
  SHA512:
6
- metadata.gz: a16a1125e343398d7a2190aea74ed2fafc3b9ad5d4aaac8a2b72ae4a75cd65bdb651dcf84528189eec39296eebb42382f847221651190e611f1c7260923653ba
7
- data.tar.gz: d636730273b8eb9d828de5609b73c73cb677ec0cf372a1ae5d028604ec53422cbe3c607d557c0b4070dadf704c0023cef1dfb40d09ddb61327d7793db65a0ef0
6
+ metadata.gz: 6519978a03ff03499f223d810a9f0d2bfd74e406017efb300a72fff0d95e1e08b64d2165ae1b54bad48f8767c5f831fe5f4398185282a5643224499355d7badc
7
+ data.tar.gz: 28ace672d799beb84aa802fe0773e574d3a827c53e15564d643dbe04e6330d6abb11d640a06b064641781258600ce73f3b5695915caaf9ce0813679dede867b2
data/Gemfile CHANGED
@@ -4,18 +4,18 @@ source("https://rubygems.org")
4
4
 
5
5
  gemspec
6
6
 
7
- gem 'rubocop-shopify', require: false
8
-
9
- group(:deployment, :development) do
10
- gem("rake")
11
- end
12
-
13
- gem("yard", "~> 0.9.25")
14
- gem("pry-byebug")
15
7
  gem("minitest")
16
8
  gem("minitest-hooks")
17
9
  gem("minitest-reporters")
10
+ gem("pry-byebug")
11
+ gem("rubocop-shopify", require: false)
12
+ gem("rubocop-sorbet", ">= 0.4.1")
18
13
  gem("sorbet")
14
+ gem("yard", "~> 0.9.25")
15
+
16
+ group(:deployment, :development) do
17
+ gem("rake")
18
+ end
19
19
 
20
20
  group(:development, :test) do
21
21
  gem("smart_properties", ">= 1.15.0", require: false)
@@ -26,14 +26,13 @@ group(:development, :test) do
26
26
  gem("activerecord-typedstore", "~> 1.3", require: false)
27
27
  gem("sqlite3")
28
28
  gem("identity_cache", "~> 1.0", require: false)
29
- gem('cityhash', git: 'https://github.com/csfrancis/cityhash.git',
30
- ref: '3cfc7d01f333c01811d5e834f1495eaa29f87c36', require: false)
29
+ gem("cityhash", git: "https://github.com/csfrancis/cityhash.git",
30
+ ref: "3cfc7d01f333c01811d5e834f1495eaa29f87c36", require: false)
31
31
  gem("activemodel-serializers-xml", "~> 1.0", require: false)
32
32
  gem("activeresource", "~> 5.1", require: false)
33
- gem("google-protobuf", "~>3.12.0", require: false)
33
+ gem("google-protobuf", "~> 3.12.0", require: false)
34
34
  # Fix version to 0.14.1 since it is the last version to support Ruby 2.4
35
35
  gem("shopify-money", "= 0.14.1", require: false)
36
- gem("sidekiq", "~>5.0", require: false) # Version 6 dropped support for Ruby 2.4
36
+ gem("sidekiq", "~> 5.0", require: false) # Version 6 dropped support for Ruby 2.4
37
+ gem("nokogiri", "1.10.10", require: false) # Lock to last supported for Ruby 2.4
37
38
  end
38
-
39
- gem "rubocop-sorbet", ">= 0.4.1"
@@ -0,0 +1,71 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "parlour"
5
+
6
+ begin
7
+ require "active_job"
8
+ rescue LoadError
9
+ return
10
+ end
11
+
12
+ module Tapioca
13
+ module Compilers
14
+ module Dsl
15
+ # `Tapioca::Compilers::Dsl::ActiveJob` generates RBI files for subclasses of
16
+ # [`ActiveJob::Base`](https://api.rubyonrails.org/classes/ActiveJob/Base.html).
17
+ #
18
+ # For example, with the following `ActiveJob` subclass:
19
+ #
20
+ # ~~~rb
21
+ # class NotifyUserJob < ActiveJob::Base
22
+ # def perform(user)
23
+ # # ...
24
+ # end
25
+ # end
26
+ # ~~~
27
+ #
28
+ # this generator will produce the RBI file `notify_user_job.rbi` with the following content:
29
+ #
30
+ # ~~~rbi
31
+ # # notify_user_job.rbi
32
+ # # typed: true
33
+ # class NotifyUserJob
34
+ # sig { params(user: T.untyped).returns(NotifyUserJob) }
35
+ # def self.perform_later(user); end
36
+ #
37
+ # sig { params(user: T.untyped).returns(NotifyUserJob) }
38
+ # def self.perform_now(user); end
39
+ # end
40
+ # ~~~
41
+ class ActiveJob < Base
42
+ extend T::Sig
43
+
44
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::ActiveJob::Base)).void }
45
+ def decorate(root, constant)
46
+ root.path(constant) do |job|
47
+ next unless constant.instance_methods(false).include?(:perform)
48
+
49
+ method = constant.instance_method(:perform)
50
+ parameters = compile_method_parameters_to_parlour(method)
51
+
52
+ %w[perform_later perform_now].each do |name|
53
+ create_method(
54
+ job,
55
+ name,
56
+ parameters: parameters,
57
+ return_type: constant.name,
58
+ class_method: true
59
+ )
60
+ end
61
+ end
62
+ end
63
+
64
+ sig { override.returns(T::Enumerable[Module]) }
65
+ def gather_constants
66
+ ::ActiveJob::Base.descendants
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -22,7 +22,7 @@ module Tapioca
22
22
  name_in_project?(files, req)
23
23
  end
24
24
  end.sort.uniq.map do |name|
25
- "require '#{name}'\n"
25
+ "require \"#{name}\"\n"
26
26
  end.join
27
27
  end
28
28
 
@@ -23,24 +23,34 @@ module Tapioca
23
23
  @indent = indent
24
24
  @seen = Set.new
25
25
  @alias_namespace ||= Set.new
26
+ @symbol_queue = T.let(symbols.sort.dup, T::Array[String])
26
27
  end
27
28
 
28
29
  sig { returns(String) }
29
30
  def generate
30
- symbols
31
- .sort
32
- .map { |symbol| generate_from_symbol(symbol) }
33
- .compact
34
- .join("\n\n")
35
- .concat("\n")
31
+ rbi = RBI::Tree.new
32
+
33
+ generate_from_symbol(rbi, T.must(@symbol_queue.shift)) until @symbol_queue.empty?
34
+
35
+ rbi.nest_singleton_methods!
36
+ rbi.nest_non_public_methods!
37
+ rbi.group_nodes!
38
+ rbi.sort_nodes!
39
+ rbi.string
36
40
  end
37
41
 
38
42
  private
39
43
 
44
+ def add_to_symbol_queue(name)
45
+ @symbol_queue << name unless symbols.include?(name) || symbol_ignored?(name)
46
+ end
47
+
40
48
  sig { returns(T::Set[String]) }
41
49
  def symbols
42
- symbols = Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(gem.files)
43
- symbols.union(engine_symbols(symbols))
50
+ @symbols ||= begin
51
+ symbols = Tapioca::Compilers::SymbolTable::SymbolLoader.list_from_paths(gem.files)
52
+ symbols.union(engine_symbols(symbols))
53
+ end
44
54
  end
45
55
 
46
56
  sig { params(symbols: T::Set[String]).returns(T::Set[String]) }
@@ -65,13 +75,13 @@ module Tapioca
65
75
  Set.new
66
76
  end
67
77
 
68
- sig { params(symbol: String).returns(T.nilable(String)) }
69
- def generate_from_symbol(symbol)
78
+ sig { params(tree: RBI::Tree, symbol: String).void }
79
+ def generate_from_symbol(tree, symbol)
70
80
  constant = resolve_constant(symbol)
71
81
 
72
82
  return unless constant
73
83
 
74
- compile(symbol, constant)
84
+ compile(tree, symbol, constant)
75
85
  end
76
86
 
77
87
  sig do
@@ -87,12 +97,8 @@ module Tapioca
87
97
  nil
88
98
  end
89
99
 
90
- sig do
91
- params(name: T.nilable(String), constant: BasicObject)
92
- .returns(T.nilable(String))
93
- .checked(:never)
94
- end
95
- def compile(name, constant)
100
+ sig { params(tree: RBI::Tree, name: T.nilable(String), constant: BasicObject).void.checked(:never) }
101
+ def compile(tree, name, constant)
96
102
  return unless constant
97
103
  return unless name
98
104
  return if name.strip.empty?
@@ -100,33 +106,28 @@ module Tapioca
100
106
  return if name.downcase == name
101
107
  return if alias_namespaced?(name)
102
108
  return if seen?(name)
103
- return unless parent_declares_constant?(name)
104
109
  return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
105
110
 
106
111
  mark_seen(name)
107
- compile_constant(name, constant)
112
+ compile_constant(tree, name, constant)
108
113
  end
109
114
 
110
- sig do
111
- params(name: String, constant: BasicObject)
112
- .returns(T.nilable(String))
113
- .checked(:never)
114
- end
115
- def compile_constant(name, constant)
115
+ sig { params(tree: RBI::Tree, name: String, constant: BasicObject).void.checked(:never) }
116
+ def compile_constant(tree, name, constant)
116
117
  case constant
117
118
  when Module
118
119
  if name_of(constant) != name
119
- compile_alias(name, constant)
120
+ compile_alias(tree, name, constant)
120
121
  else
121
- compile_module(name, constant)
122
+ compile_module(tree, name, constant)
122
123
  end
123
124
  else
124
- compile_object(name, constant)
125
+ compile_object(tree, name, constant)
125
126
  end
126
127
  end
127
128
 
128
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
129
- def compile_alias(name, constant)
129
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
130
+ def compile_alias(tree, name, constant)
130
131
  return if symbol_ignored?(name)
131
132
 
132
133
  target = name_of(constant)
@@ -137,119 +138,99 @@ module Tapioca
137
138
 
138
139
  return if IGNORED_SYMBOLS.include?(name)
139
140
 
140
- indented("#{name} = #{target}")
141
+ tree << RBI::Const.new(name, target)
141
142
  end
142
143
 
143
- sig do
144
- params(name: String, value: BasicObject)
145
- .returns(T.nilable(String))
146
- .checked(:never)
147
- end
148
- def compile_object(name, value)
144
+ sig { params(tree: RBI::Tree, name: String, value: BasicObject).void.checked(:never) }
145
+ def compile_object(tree, name, value)
149
146
  return if symbol_ignored?(name)
150
147
  klass = class_of(value)
151
148
  klass_name = name_of(klass)
152
149
 
150
+ if klass_name == "T::Private::Types::TypeAlias"
151
+ tree << RBI::Const.new(name, "T.type_alias { #{T.unsafe(value).aliased_type} }")
152
+ return
153
+ end
154
+
153
155
  return if klass_name&.start_with?("T::Types::", "T::Private::")
154
156
 
155
- type_name = public_module?(klass) && klass_name || "T.untyped"
156
- indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
157
+ type_name = klass_name || "T.untyped"
158
+ # TODO: Do this in a more generic and clean way.
159
+ type_name = "#{type_name}[T.untyped]" if type_name == "ObjectSpace::WeakMap"
160
+
161
+ tree << RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})")
157
162
  end
158
163
 
159
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
160
- def compile_module(name, constant)
161
- return unless public_module?(constant)
164
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
165
+ def compile_module(tree, name, constant)
162
166
  return unless defined_in_gem?(constant, strict: false)
163
167
 
164
- header =
168
+ scope =
165
169
  if constant.is_a?(Class)
166
- indented("class #{name}#{compile_superclass(constant)}")
170
+ superclass = compile_superclass(constant)
171
+ RBI::Class.new(name, superclass_name: superclass)
167
172
  else
168
- indented("module #{name}")
173
+ RBI::Module.new(name)
169
174
  end
170
175
 
171
- body = compile_body(name, constant)
176
+ compile_body(scope, name, constant)
172
177
 
173
- return if symbol_ignored?(name) && body.nil?
178
+ return if symbol_ignored?(name) && scope.empty?
174
179
 
175
- [
176
- header,
177
- body,
178
- indented("end"),
179
- compile_subconstants(name, constant),
180
- ].select { |b| !b.nil? && b.strip != "" }.join("\n")
180
+ tree << scope
181
+ compile_subconstants(tree, name, constant)
181
182
  end
182
183
 
183
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
184
- def compile_body(name, constant)
185
- with_indentation do
186
- methods = compile_methods(name, constant)
187
-
188
- return if symbol_ignored?(name) && methods.nil?
189
-
190
- [
191
- compile_module_helpers(constant),
192
- compile_type_variables(constant),
193
- compile_mixins(constant),
194
- compile_mixes_in_class_methods(constant),
195
- compile_props(constant),
196
- compile_enums(constant),
197
- methods,
198
- ].select { |b| b && !b.empty? }.join("\n\n")
199
- end
184
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
185
+ def compile_body(tree, name, constant)
186
+ compile_methods(tree, name, constant)
187
+ compile_module_helpers(tree, constant)
188
+ compile_type_variables(tree, constant)
189
+ compile_mixins(tree, constant)
190
+ compile_mixes_in_class_methods(tree, constant)
191
+ compile_props(tree, constant)
192
+ compile_enums(tree, constant)
200
193
  end
201
194
 
202
- sig { params(constant: Module).returns(T.nilable(String)) }
203
- def compile_module_helpers(constant)
195
+ sig { params(tree: RBI::Tree, constant: Module).void }
196
+ def compile_module_helpers(tree, constant)
204
197
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
205
198
 
206
- helpers = []
207
- helpers << indented("#{abstract_type}!") if abstract_type
208
- helpers << indented("final!") if T::Private::Final.final_module?(constant)
209
- helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
210
-
211
- return if helpers.empty?
212
-
213
- helpers.join("\n")
199
+ tree << RBI::Helper.new(abstract_type.to_s) if abstract_type
200
+ tree << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
201
+ tree << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
214
202
  end
215
203
 
216
- sig { params(constant: Module).returns(T.nilable(String)) }
217
- def compile_props(constant)
204
+ sig { params(tree: RBI::Tree, constant: Module).void }
205
+ def compile_props(tree, constant)
218
206
  return unless T::Props::ClassMethods === constant
219
207
 
220
208
  constant.props.map do |name, prop|
221
- method = "prop"
222
- method = "const" if prop.fetch(:immutable, false)
223
209
  type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
224
210
 
225
- if prop.key?(:default)
226
- indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
211
+ default = prop.key?(:default) ? "T.unsafe(nil)" : nil
212
+ tree << if prop.fetch(:immutable, false)
213
+ RBI::TStructConst.new(name.to_s, type, default: default)
227
214
  else
228
- indented("#{method} :#{name}, #{type}")
215
+ RBI::TStructProp.new(name.to_s, type, default: default)
229
216
  end
230
- end.join("\n")
217
+ end
231
218
  end
232
219
 
233
- sig { params(constant: Module).returns(T.nilable(String)) }
234
- def compile_enums(constant)
220
+ sig { params(tree: RBI::Tree, constant: Module).void }
221
+ def compile_enums(tree, constant)
235
222
  return unless T::Enum > constant
236
223
 
237
224
  enums = T.unsafe(constant).values.map do |enum_type|
238
225
  enum_type.instance_variable_get(:@const_name).to_s
239
226
  end
240
227
 
241
- content = [
242
- indented('enums do'),
243
- *enums.map { |e| indented(" #{e} = new") }.join("\n"),
244
- indented('end'),
245
- ]
246
-
247
- content.join("\n")
228
+ tree << RBI::TEnumBlock.new(enums)
248
229
  end
249
230
 
250
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
251
- def compile_subconstants(name, constant)
252
- output = constants_of(constant).sort.uniq.map do |constant_name|
231
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
232
+ def compile_subconstants(tree, name, constant)
233
+ constants_of(constant).sort.uniq.map do |constant_name|
253
234
  symbol = (name == "Object" ? "" : name) + "::#{constant_name}"
254
235
  subconstant = resolve_constant(symbol)
255
236
 
@@ -258,75 +239,58 @@ module Tapioca
258
239
  next if (Object == constant || BasicObject == constant) && Module === subconstant
259
240
  next unless subconstant
260
241
 
261
- compile(symbol, subconstant)
262
- end.compact
263
-
264
- return if output.empty?
265
-
266
- "\n" + output.join("\n\n")
242
+ compile(tree, symbol, subconstant)
243
+ end
267
244
  end
268
245
 
269
- sig { params(constant: Module).returns(T.nilable(String)) }
270
- def compile_type_variables(constant)
271
- type_variables = compile_type_variable_declarations(constant)
272
- singleton_class_type_variables = compile_type_variable_declarations(singleton_class_of(constant))
273
-
274
- return if !type_variables && !singleton_class_type_variables
246
+ sig { params(tree: RBI::Tree, constant: Module).void }
247
+ def compile_type_variables(tree, constant)
248
+ compile_type_variable_declarations(tree, constant)
275
249
 
276
- type_variables += "\n" if type_variables
277
- singleton_class_type_variables += "\n" if singleton_class_type_variables
278
-
279
- [
280
- type_variables,
281
- singleton_class_type_variables,
282
- ].compact.join("\n").rstrip
250
+ sclass = RBI::SingletonClass.new
251
+ compile_type_variable_declarations(sclass, singleton_class_of(constant))
252
+ tree << sclass if sclass.nodes.length > 1
283
253
  end
284
254
 
285
- sig { params(constant: Module).returns(T.nilable(String)) }
286
- def compile_type_variable_declarations(constant)
287
- with_indentation_for_constant(constant) do
288
- # Try to find the type variables defined on this constant, bail if we can't
289
- type_variables = GenericTypeRegistry.lookup_type_variables(constant)
290
- return unless type_variables
291
-
292
- # Create a map of subconstants (via their object ids) to their names.
293
- # We need this later when we want to lookup the name of the registered type
294
- # variable via the value of the type variable constant.
295
- subconstant_to_name_lookup = constants_of(constant).map do |constant_name|
296
- [
297
- object_id_of(resolve_constant(constant_name.to_s, namespace: constant)),
298
- constant_name,
299
- ]
300
- end.to_h
301
-
302
- # Map each type variable to its string representation.
303
- #
304
- # Each entry of `type_variables` maps an object_id to a String,
305
- # and the order they are inserted into the hash is the order they should be
306
- # defined in the source code.
307
- #
308
- # By looping over these entries and then getting the actual constant name
309
- # from the `subconstant_to_name_lookup` we defined above, gives us all the
310
- # information we need to serialize type variable definitions.
311
- type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable|
312
- constant_name = subconstant_to_name_lookup[type_variable_id]
313
- # Here, we know that constant_value will be an instance of
314
- # T::Types::CustomTypeVariable, which knows how to serialize
315
- # itself to a type_member/type_template
316
- indented("#{constant_name} = #{serialized_type_variable}")
317
- end.compact
318
-
319
- return if type_variable_declarations.empty?
255
+ sig { params(tree: RBI::Tree, constant: Module).void }
256
+ def compile_type_variable_declarations(tree, constant)
257
+ # Try to find the type variables defined on this constant, bail if we can't
258
+ type_variables = GenericTypeRegistry.lookup_type_variables(constant)
259
+ return unless type_variables
320
260
 
261
+ # Create a map of subconstants (via their object ids) to their names.
262
+ # We need this later when we want to lookup the name of the registered type
263
+ # variable via the value of the type variable constant.
264
+ subconstant_to_name_lookup = constants_of(constant).map do |constant_name|
321
265
  [
322
- indented("extend T::Generic"),
323
- "",
324
- *type_variable_declarations,
325
- ].compact.join("\n")
266
+ object_id_of(resolve_constant(constant_name.to_s, namespace: constant)),
267
+ constant_name,
268
+ ]
269
+ end.to_h
270
+
271
+ # Map each type variable to its string representation.
272
+ #
273
+ # Each entry of `type_variables` maps an object_id to a String,
274
+ # and the order they are inserted into the hash is the order they should be
275
+ # defined in the source code.
276
+ #
277
+ # By looping over these entries and then getting the actual constant name
278
+ # from the `subconstant_to_name_lookup` we defined above, gives us all the
279
+ # information we need to serialize type variable definitions.
280
+ type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable|
281
+ constant_name = subconstant_to_name_lookup[type_variable_id]
282
+ # Here, we know that constant_value will be an instance of
283
+ # T::Types::CustomTypeVariable, which knows how to serialize
284
+ # itself to a type_member/type_template
285
+ tree << RBI::TypeMember.new(constant_name.to_s, serialized_type_variable)
326
286
  end
287
+
288
+ return if type_variable_declarations.empty?
289
+
290
+ tree << RBI::Extend.new("T::Generic")
327
291
  end
328
292
 
329
- sig { params(constant: Class).returns(String) }
293
+ sig { params(constant: Class).returns(T.nilable(String)) }
330
294
  def compile_superclass(constant)
331
295
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
332
296
 
@@ -334,15 +298,6 @@ module Tapioca
334
298
  constant_name = name_of(constant)
335
299
  constant = superclass
336
300
 
337
- # Some classes have superclasses that are private constants
338
- # so if we generate code with that superclass, the output
339
- # will not be compilable (since private constants are not
340
- # publicly visible).
341
- #
342
- # So we skip superclasses that are not public and walk up the
343
- # chain.
344
- next unless public_module?(superclass)
345
-
346
301
  # Some types have "themselves" as their superclass
347
302
  # which can happen via:
348
303
  #
@@ -362,7 +317,9 @@ module Tapioca
362
317
  # B = A
363
318
  # A.superclass.name #=> "B"
364
319
  # B #=> A
365
- superclass_name = T.must(name_of(superclass))
320
+ superclass_name = name_of(superclass)
321
+ next unless superclass_name
322
+
366
323
  resolved_superclass = resolve_constant(superclass_name)
367
324
  next unless Module === resolved_superclass
368
325
  next if name_of(resolved_superclass) == constant_name
@@ -371,17 +328,19 @@ module Tapioca
371
328
  break
372
329
  end
373
330
 
374
- return "" if superclass == ::Object || superclass == ::Delegator
375
- return "" if superclass.nil?
331
+ return if superclass == ::Object || superclass == ::Delegator
332
+ return if superclass.nil?
376
333
 
377
334
  name = name_of(superclass)
378
- return "" if name.nil? || name.empty?
335
+ return if name.nil? || name.empty?
336
+
337
+ add_to_symbol_queue(name)
379
338
 
380
- " < ::#{name}"
339
+ "::#{name}"
381
340
  end
382
341
 
383
- sig { params(constant: Module).returns(String) }
384
- def compile_mixins(constant)
342
+ sig { params(tree: RBI::Tree, constant: Module).void }
343
+ def compile_mixins(tree, constant)
385
344
  singleton_class = singleton_class_of(constant)
386
345
 
387
346
  interesting_ancestors = interesting_ancestors_of(constant)
@@ -390,42 +349,46 @@ module Tapioca
390
349
  prepend = interesting_ancestors.take_while { |c| !are_equal?(constant, c) }
391
350
  include = interesting_ancestors.drop(prepend.size + 1)
392
351
  extend = interesting_singleton_class_ancestors.reject do |mod|
393
- !public_module?(mod) || Module != class_of(mod) || are_equal?(mod, singleton_class)
352
+ Module != class_of(mod) || are_equal?(mod, singleton_class)
394
353
  end
395
354
 
396
- prepends = prepend
355
+ prepend
397
356
  .reverse
398
357
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
399
- .select(&method(:public_module?))
400
358
  .map do |mod|
359
+ add_to_symbol_queue(name_of(mod))
360
+
401
361
  # TODO: Sorbet currently does not handle prepend
402
362
  # properly for method resolution, so we generate an
403
363
  # include statement instead
404
- indented("include(#{qualified_name_of(mod)})")
364
+ qname = qualified_name_of(mod)
365
+ tree << RBI::Include.new(T.must(qname))
405
366
  end
406
367
 
407
- includes = include
368
+ include
408
369
  .reverse
409
370
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
410
- .select(&method(:public_module?))
411
371
  .map do |mod|
412
- indented("include(#{qualified_name_of(mod)})")
372
+ add_to_symbol_queue(name_of(mod))
373
+
374
+ qname = qualified_name_of(mod)
375
+ tree << RBI::Include.new(T.must(qname))
413
376
  end
414
377
 
415
- extends = extend
378
+ extend
416
379
  .reverse
417
380
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
418
- .select(&method(:public_module?))
419
381
  .map do |mod|
420
- indented("extend(#{qualified_name_of(mod)})")
421
- end
382
+ add_to_symbol_queue(name_of(mod))
422
383
 
423
- (prepends + includes + extends).join("\n")
384
+ qname = qualified_name_of(mod)
385
+ tree << RBI::Extend.new(T.must(qname))
386
+ end
424
387
  end
425
388
 
426
- sig { params(constant: Module).returns(String) }
427
- def compile_mixes_in_class_methods(constant)
428
- return "" if constant.is_a?(Class)
389
+ sig { params(tree: RBI::Tree, constant: Module).void }
390
+ def compile_mixes_in_class_methods(tree, constant)
391
+ return if constant.is_a?(Class)
429
392
 
430
393
  mixins_from_modules = {}
431
394
 
@@ -457,11 +420,13 @@ module Tapioca
457
420
  dynamic_extends_from_dynamic_includes = mixins_from_modules.values.flatten
458
421
  dynamic_extends = all_dynamic_extends - dynamic_extends_from_dynamic_includes
459
422
 
460
- result = all_dynamic_includes
423
+ all_dynamic_includes
461
424
  .select { |mod| (name = name_of(mod)) && !name.start_with?("T::") }
462
- .select(&method(:public_module?))
463
425
  .map do |mod|
464
- indented("include(#{qualified_name_of(mod)})")
426
+ add_to_symbol_queue(name_of(mod))
427
+
428
+ qname = qualified_name_of(mod)
429
+ tree << RBI::Include.new(T.must(qname))
465
430
  end.join("\n")
466
431
 
467
432
  ancestors = singleton_class_of(constant).ancestors
@@ -473,69 +438,57 @@ module Tapioca
473
438
  mixed_in_module = if extends_as_concern && Module === class_methods_module
474
439
  class_methods_module
475
440
  else
476
- dynamic_extends.find do |mod|
477
- mod != constant && public_module?(mod)
478
- end
441
+ dynamic_extends.find { |mod| mod != constant }
479
442
  end
480
443
 
481
- return result if mixed_in_module.nil?
444
+ return if mixed_in_module.nil?
482
445
 
483
446
  qualified_name = qualified_name_of(mixed_in_module)
484
- return result if qualified_name == ""
447
+ return if qualified_name.nil? || qualified_name == ""
485
448
 
486
- [
487
- result,
488
- indented("mixes_in_class_methods(#{qualified_name})"),
489
- ].select { |b| b != "" }.join("\n\n")
449
+ tree << RBI::MixesInClassMethods.new(qualified_name)
490
450
  rescue
491
- ""
451
+ nil # silence errors
492
452
  end
493
453
 
494
- sig { params(name: String, constant: Module).returns(T.nilable(String)) }
495
- def compile_methods(name, constant)
496
- initialize_method = compile_method(
454
+ sig { params(tree: RBI::Tree, name: String, constant: Module).void }
455
+ def compile_methods(tree, name, constant)
456
+ compile_method(
457
+ tree,
497
458
  name,
498
459
  constant,
499
460
  initialize_method_for(constant)
500
461
  )
501
462
 
502
- instance_methods = compile_directly_owned_methods(name, constant)
503
- singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
504
-
505
- return if symbol_ignored?(name) && !instance_methods && !singleton_methods
506
-
507
- [
508
- initialize_method,
509
- instance_methods,
510
- singleton_methods,
511
- ].select { |b| b && !b.strip.empty? }.join("\n\n")
463
+ compile_directly_owned_methods(tree, name, constant)
464
+ compile_directly_owned_methods(tree, name, singleton_class_of(constant))
512
465
  end
513
466
 
514
- sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(T.nilable(String)) }
515
- def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
516
- with_indentation_for_constant(mod) do
517
- methods = method_names_by_visibility(mod)
518
- .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
519
- .flat_map do |visibility, method_list|
520
- compiled = method_list.sort!.map do |name|
521
- next if name == :initialize
522
- compile_method(module_name, mod, mod.instance_method(name))
523
- end
524
- compiled.compact!
525
-
526
- unless compiled.empty? || visibility == :public
527
- # add visibility badge
528
- compiled.unshift('', indented(visibility.to_s), '')
467
+ sig do
468
+ params(
469
+ tree: RBI::Tree,
470
+ module_name: String,
471
+ mod: Module,
472
+ for_visibility: T::Array[Symbol]
473
+ ).void
474
+ end
475
+ def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
476
+ method_names_by_visibility(mod)
477
+ .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
478
+ .each do |visibility, method_list|
479
+ method_list.sort!.map do |name|
480
+ next if name == :initialize
481
+ vis = case visibility
482
+ when :protected
483
+ RBI::Visibility::Protected
484
+ when :private
485
+ RBI::Visibility::Private
486
+ else
487
+ RBI::Visibility::Public
529
488
  end
530
-
531
- compiled
489
+ compile_method(tree, module_name, mod, mod.instance_method(name), vis)
532
490
  end
533
- .compact
534
-
535
- return if methods.empty?
536
-
537
- methods.join("\n")
538
- end
491
+ end
539
492
  end
540
493
 
541
494
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -559,12 +512,14 @@ module Tapioca
559
512
 
560
513
  sig do
561
514
  params(
515
+ tree: RBI::Tree,
562
516
  symbol_name: String,
563
517
  constant: Module,
564
- method: T.nilable(UnboundMethod)
565
- ).returns(T.nilable(String))
518
+ method: T.nilable(UnboundMethod),
519
+ visibility: RBI::Visibility
520
+ ).void
566
521
  end
567
- def compile_method(symbol_name, constant, method)
522
+ def compile_method(tree, symbol_name, constant, method, visibility = RBI::Visibility::Public)
568
523
  return unless method
569
524
  return unless method.owner == constant
570
525
  return if symbol_ignored?(symbol_name) && !method_in_gem?(method)
@@ -611,37 +566,34 @@ module Tapioca
611
566
  [type, name]
612
567
  end
613
568
 
614
- parameter_list = sanitized_parameters.map do |type, name|
569
+ rbi_method = RBI::Method.new(method_name, is_singleton: constant.singleton_class?, visibility: visibility)
570
+ rbi_method.sigs << compile_signature(signature, sanitized_parameters) if signature
571
+
572
+ sanitized_parameters.each do |type, name|
615
573
  case type
616
574
  when :req
617
- name
575
+ rbi_method << RBI::Param.new(name)
618
576
  when :opt
619
- "#{name} = T.unsafe(nil)"
577
+ rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
620
578
  when :rest
621
- "*#{name}"
579
+ rbi_method << RBI::RestParam.new(name)
622
580
  when :keyreq
623
- "#{name}:"
581
+ rbi_method << RBI::KwParam.new(name)
624
582
  when :key
625
- "#{name}: T.unsafe(nil)"
583
+ rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
626
584
  when :keyrest
627
- "**#{name}"
585
+ rbi_method << RBI::KwRestParam.new(name)
628
586
  when :block
629
- "&#{name}"
587
+ rbi_method << RBI::BlockParam.new(name)
630
588
  end
631
- end.join(', ')
632
-
633
- parameter_list = "(#{parameter_list})" if parameter_list != ""
634
- signature_str = indented(compile_signature(signature, sanitized_parameters)) if signature
589
+ end
635
590
 
636
- [
637
- signature_str,
638
- indented("def #{method_name}#{parameter_list}; end"),
639
- ].compact.join("\n")
591
+ tree << rbi_method
640
592
  end
641
593
 
642
594
  TYPE_PARAMETER_MATCHER = /T\.type_parameter\(:?([[:word:]]+)\)/
643
595
 
644
- sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(String) }
596
+ sig { params(signature: T.untyped, parameters: T::Array[[Symbol, String]]).returns(RBI::Sig) }
645
597
  def compile_signature(signature, parameters)
646
598
  parameter_types = T.let(signature.arg_types.to_h, T::Hash[Symbol, T::Types::Base])
647
599
  parameter_types.merge!(signature.kwarg_types)
@@ -649,41 +601,35 @@ module Tapioca
649
601
  parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
650
602
  parameter_types[signature.block_name] = signature.block_type if signature.block_name
651
603
 
652
- params = parameters.map do |_, name|
653
- type = parameter_types[name.to_sym]
654
- "#{name}: #{type}"
655
- end.join(", ")
604
+ sig = RBI::Sig.new
605
+
606
+ parameters.each do |_, name|
607
+ type = sanitize_signature_types(parameter_types[name.to_sym].to_s)
608
+ add_to_symbol_queue(type)
609
+ sig << RBI::SigParam.new(name, type)
610
+ end
656
611
 
657
- returns = type_of(signature.return_type)
612
+ return_type = type_of(signature.return_type)
613
+ sig.return_type = sanitize_signature_types(return_type)
614
+ add_to_symbol_queue(sig.return_type)
658
615
 
659
- type_parameters = (params + returns).scan(TYPE_PARAMETER_MATCHER).flatten.uniq.map { |p| ":#{p}" }.join(", ")
660
- type_parameters = ".type_parameters(#{type_parameters})" unless type_parameters.empty?
616
+ parameter_types.values.join(", ").scan(TYPE_PARAMETER_MATCHER).flatten.uniq.each do |k, _|
617
+ sig.type_params << k
618
+ end
661
619
 
662
- mode = case signature.mode
620
+ case signature.mode
663
621
  when "abstract"
664
- ".abstract"
622
+ sig.is_abstract = true
665
623
  when "override"
666
- ".override"
624
+ sig.is_override = true
667
625
  when "overridable_override"
668
- ".overridable.override"
626
+ sig.is_overridable = true
627
+ sig.is_override = true
669
628
  when "overridable"
670
- ".overridable"
671
- else
672
- ""
629
+ sig.is_overridable = true
673
630
  end
674
631
 
675
- signature_body = +""
676
- signature_body << mode
677
- signature_body << type_parameters
678
- signature_body << ".params(#{params})" unless params.empty?
679
- signature_body << ".returns(#{returns})"
680
- signature_body = signature_body
681
- .gsub(".returns(<VOID>)", ".void")
682
- .gsub("<NOT-TYPED>", "T.untyped")
683
- .gsub(".params()", "")
684
- .gsub(TYPE_PARAMETER_MATCHER, "T.type_parameter(:\\1)")[1..-1]
685
-
686
- "sig { #{signature_body} }"
632
+ sig
687
633
  end
688
634
 
689
635
  sig { params(symbol_name: String).returns(T::Boolean) }
@@ -699,54 +645,6 @@ module Tapioca
699
645
  !!name.match(/^[[:word:]]+[?!=]?$/)
700
646
  end
701
647
 
702
- sig do
703
- type_parameters(:U)
704
- .params(
705
- step: Integer,
706
- _blk: T.proc
707
- .returns(T.type_parameter(:U))
708
- )
709
- .returns(T.type_parameter(:U))
710
- end
711
- def with_indentation(step = 1, &_blk)
712
- @indent += 2 * step
713
- yield
714
- ensure
715
- @indent -= 2 * step
716
- end
717
-
718
- sig do
719
- params(
720
- constant: Module,
721
- blk: T.proc
722
- .returns(T.nilable(String))
723
- )
724
- .returns(T.nilable(String))
725
- end
726
- def with_indentation_for_constant(constant, &blk)
727
- step = if constant.singleton_class?
728
- 1
729
- else
730
- 0
731
- end
732
-
733
- result = with_indentation(step, &blk)
734
-
735
- return result unless result
736
- return result unless constant.singleton_class?
737
-
738
- [
739
- indented("class << self"),
740
- result,
741
- indented("end"),
742
- ].compact.join("\n")
743
- end
744
-
745
- sig { params(str: String).returns(String) }
746
- def indented(str)
747
- " " * @indent + str
748
- end
749
-
750
648
  sig { params(method: UnboundMethod).returns(T::Boolean) }
751
649
  def method_in_gem?(method)
752
650
  source_location = method.source_location&.first
@@ -804,33 +702,6 @@ module Tapioca
804
702
  nil
805
703
  end
806
704
 
807
- def parent_declares_constant?(name)
808
- name_parts = name.split("::")
809
-
810
- parent_name = name_parts[0...-1].join("::")
811
- parent_name = parent_name[2..-1] if parent_name.start_with?("::")
812
- parent_name = 'Object' if parent_name == ""
813
- parent = T.cast(resolve_constant(parent_name), T.nilable(Module))
814
-
815
- return false unless parent
816
-
817
- constants_of(parent).include?(name_parts.last.to_sym)
818
- end
819
-
820
- sig { params(constant: Module).returns(T::Boolean) }
821
- def public_module?(constant)
822
- constant_name = name_of(constant)
823
- return false unless constant_name
824
- return false if constant_name.start_with?('T::Private')
825
-
826
- begin
827
- # can't use !! here because the constant might override ! and mess with us
828
- Module === eval(constant_name) # rubocop:disable Security/Eval
829
- rescue NameError
830
- false
831
- end
832
- end
833
-
834
705
  sig { params(constant: BasicObject).returns(Class).checked(:never) }
835
706
  def class_of(constant)
836
707
  Kernel.instance_method(:class).bind(constant).call
@@ -912,7 +783,7 @@ module Tapioca
912
783
  begin
913
784
  target = Kernel.instance_method(:send).bind(constant).call(:target)
914
785
  rescue NoMethodError
915
- return nil
786
+ return
916
787
  end
917
788
 
918
789
  raw_name_of(target)
@@ -942,6 +813,15 @@ module Tapioca
942
813
  nil
943
814
  end
944
815
 
816
+ sig { params(sig_string: String).returns(String) }
817
+ def sanitize_signature_types(sig_string)
818
+ sig_string
819
+ .gsub(".returns(<VOID>)", ".void")
820
+ .gsub("<VOID>", "void")
821
+ .gsub("<NOT-TYPED>", "T.untyped")
822
+ .gsub(".params()", "")
823
+ end
824
+
945
825
  sig { params(constant: T::Types::Base).returns(String) }
946
826
  def type_of(constant)
947
827
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")