tapioca 0.4.21 → 0.4.22

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