tapioca 0.4.13 → 0.4.18

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.
@@ -60,7 +60,7 @@ module Tapioca
60
60
 
61
61
  model.create_module(module_name) do |mod|
62
62
  scope_method_names.each do |scope_method|
63
- generate_scope_method(scope_method, mod)
63
+ generate_scope_method(scope_method.to_s, mod)
64
64
  end
65
65
  end
66
66
 
@@ -123,7 +123,7 @@ module Tapioca
123
123
  # Compile a Ruby method return type into a Parlour type
124
124
  sig do
125
125
  params(method_def: T.any(Method, UnboundMethod))
126
- .returns(String)
126
+ .returns(T.nilable(String))
127
127
  end
128
128
  def compile_method_return_type_to_parlour(method_def)
129
129
  signature = T::Private::Methods.signature_for_method(method_def)
@@ -89,7 +89,7 @@ module Tapioca
89
89
  class UrlHelpers < Base
90
90
  extend T::Sig
91
91
 
92
- sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
92
+ sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
93
93
  def decorate(root, constant)
94
94
  case constant
95
95
  when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
@@ -127,7 +127,7 @@ module Tapioca
127
127
 
128
128
  private
129
129
 
130
- sig { params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module)).void }
130
+ sig { params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
131
131
  def generate_module_for(root, constant)
132
132
  root.create_module(T.must(constant.name)) do |mod|
133
133
  mod.create_include("::ActionDispatch::Routing::UrlFor")
@@ -143,7 +143,7 @@ module Tapioca
143
143
  end
144
144
  end
145
145
 
146
- sig { params(mod: Parlour::RbiGenerator::Namespace, constant: T.class_of(Module), helper_module: Module).void }
146
+ sig { params(mod: Parlour::RbiGenerator::Namespace, constant: Module, helper_module: Module).void }
147
147
  def create_mixins_for(mod, constant, helper_module)
148
148
  include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
149
149
  extend_helper = constant.singleton_class.ancestors.include?(helper_module)
@@ -8,6 +8,7 @@ module Tapioca
8
8
  module Compilers
9
9
  module Sorbet
10
10
  SORBET = Pathname.new(Gem::Specification.find_by_name("sorbet-static").full_gem_path) / "libexec" / "sorbet"
11
+ EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
11
12
 
12
13
  class << self
13
14
  extend(T::Sig)
@@ -26,7 +27,9 @@ module Tapioca
26
27
 
27
28
  sig { returns(String) }
28
29
  def sorbet_path
29
- SORBET.to_s.shellescape
30
+ sorbet_path = ENV.fetch(EXE_PATH_ENV_VAR, SORBET)
31
+ sorbet_path = SORBET if sorbet_path.empty?
32
+ sorbet_path.to_s.shellescape
30
33
  end
31
34
  end
32
35
  end
@@ -74,9 +74,15 @@ module Tapioca
74
74
  compile(symbol, constant)
75
75
  end
76
76
 
77
- sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) }
78
- def resolve_constant(symbol, inherit: false)
79
- Object.const_get(symbol, inherit)
77
+ sig do
78
+ params(
79
+ symbol: String,
80
+ inherit: T::Boolean,
81
+ namespace: Module
82
+ ).returns(BasicObject).checked(:never)
83
+ end
84
+ def resolve_constant(symbol, inherit: false, namespace: Object)
85
+ namespace.const_get(symbol, inherit)
80
86
  rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
81
87
  nil
82
88
  end
@@ -95,6 +101,7 @@ module Tapioca
95
101
  return if alias_namespaced?(name)
96
102
  return if seen?(name)
97
103
  return unless parent_declares_constant?(name)
104
+ return if T::Enum === constant # T::Enum instances are defined via `compile_enums`
98
105
 
99
106
  mark_seen(name)
100
107
  compile_constant(name, constant)
@@ -141,9 +148,11 @@ module Tapioca
141
148
  def compile_object(name, value)
142
149
  return if symbol_ignored?(name)
143
150
  klass = class_of(value)
144
- return if name_of(klass)&.start_with?("T::Types::", "T::Private::")
151
+ klass_name = name_of(klass)
152
+
153
+ return if klass_name&.start_with?("T::Types::", "T::Private::")
145
154
 
146
- type_name = public_module?(klass) && name_of(klass) || "T.untyped"
155
+ type_name = public_module?(klass) && klass_name || "T.untyped"
147
156
  indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
148
157
  end
149
158
 
@@ -180,37 +189,38 @@ module Tapioca
180
189
 
181
190
  [
182
191
  compile_module_helpers(constant),
192
+ compile_type_variables(constant),
183
193
  compile_mixins(constant),
184
194
  compile_mixes_in_class_methods(constant),
185
195
  compile_props(constant),
196
+ compile_enums(constant),
186
197
  methods,
187
- ].select { |b| b != "" }.join("\n\n")
198
+ ].select { |b| b && !b.empty? }.join("\n\n")
188
199
  end
189
200
  end
190
201
 
191
- sig { params(constant: Module).returns(String) }
202
+ sig { params(constant: Module).returns(T.nilable(String)) }
192
203
  def compile_module_helpers(constant)
193
204
  abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
194
205
 
195
- if abstract_type
196
- indented("#{abstract_type}!")
197
- elsif T::Private::Final.final_module?(constant)
198
- indented("final!")
199
- elsif T::Private::Sealed.sealed_module?(constant)
200
- indented("sealed!")
201
- else
202
- ""
203
- end
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")
204
214
  end
205
215
 
206
- sig { params(constant: Module).returns(String) }
216
+ sig { params(constant: Module).returns(T.nilable(String)) }
207
217
  def compile_props(constant)
208
- return "" unless T::Props::ClassMethods === constant
218
+ return unless T::Props::ClassMethods === constant
209
219
 
210
220
  constant.props.map do |name, prop|
211
221
  method = "prop"
212
222
  method = "const" if prop.fetch(:immutable, false)
213
- type = prop.fetch(:type_object, "T.untyped")
223
+ type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void")
214
224
 
215
225
  if prop.key?(:default)
216
226
  indented("#{method} :#{name}, #{type}, default: T.unsafe(nil)")
@@ -220,6 +230,23 @@ module Tapioca
220
230
  end.join("\n")
221
231
  end
222
232
 
233
+ sig { params(constant: Module).returns(T.nilable(String)) }
234
+ def compile_enums(constant)
235
+ return unless T::Enum > constant
236
+
237
+ enums = T.unsafe(constant).values.map do |enum_type|
238
+ enum_type.instance_variable_get(:@const_name).to_s
239
+ end
240
+
241
+ content = [
242
+ indented('enums do'),
243
+ *enums.map { |e| indented(" #{e} = new") }.join("\n"),
244
+ indented('end'),
245
+ ]
246
+
247
+ content.join("\n")
248
+ end
249
+
223
250
  sig { params(name: String, constant: Module).returns(T.nilable(String)) }
224
251
  def compile_subconstants(name, constant)
225
252
  output = constants_of(constant).sort.uniq.map do |constant_name|
@@ -234,11 +261,71 @@ module Tapioca
234
261
  compile(symbol, subconstant)
235
262
  end.compact
236
263
 
237
- return "" if output.empty?
264
+ return if output.empty?
238
265
 
239
266
  "\n" + output.join("\n\n")
240
267
  end
241
268
 
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
275
+
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
283
+ end
284
+
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?
320
+
321
+ [
322
+ indented("extend T::Generic"),
323
+ "",
324
+ *type_variable_declarations,
325
+ ].compact.join("\n")
326
+ end
327
+ end
328
+
242
329
  sig { params(constant: Class).returns(String) }
243
330
  def compile_superclass(constant)
244
331
  superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
@@ -348,9 +435,13 @@ module Tapioca
348
435
  end
349
436
 
350
437
  define_singleton_method(:include) do |mod|
351
- before = singleton_class.ancestors
352
- super(mod).tap do
353
- mixins_from_modules[mod] = singleton_class.ancestors - before
438
+ begin
439
+ before = singleton_class.ancestors
440
+ super(mod).tap do
441
+ mixins_from_modules[mod] = singleton_class.ancestors - before
442
+ end
443
+ rescue Exception # rubocop:disable Lint/RescueException
444
+ # this is a best effort, bail if we can't perform this
354
445
  end
355
446
  end
356
447
 
@@ -411,29 +502,19 @@ module Tapioca
411
502
  instance_methods = compile_directly_owned_methods(name, constant)
412
503
  singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
413
504
 
414
- return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty?
505
+ return if symbol_ignored?(name) && !instance_methods && !singleton_methods
415
506
 
416
507
  [
417
- initialize_method || "",
508
+ initialize_method,
418
509
  instance_methods,
419
510
  singleton_methods,
420
- ].select { |b| b.strip != "" }.join("\n\n")
511
+ ].select { |b| b && !b.strip.empty? }.join("\n\n")
421
512
  end
422
513
 
423
- sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
514
+ sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(T.nilable(String)) }
424
515
  def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
425
- indent_step = 0
426
- preamble = nil
427
- postamble = nil
428
-
429
- if mod.singleton_class?
430
- indent_step = 1
431
- preamble = indented("class << self")
432
- postamble = indented("end")
433
- end
434
-
435
- methods = with_indentation(indent_step) do
436
- method_names_by_visibility(mod)
516
+ with_indentation_for_constant(mod) do
517
+ methods = method_names_by_visibility(mod)
437
518
  .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
438
519
  .flat_map do |visibility, method_list|
439
520
  compiled = method_list.sort!.map do |name|
@@ -450,16 +531,11 @@ module Tapioca
450
531
  compiled
451
532
  end
452
533
  .compact
453
- .join("\n")
454
- end
455
534
 
456
- return "" if methods.strip == ""
535
+ return if methods.empty?
457
536
 
458
- [
459
- preamble,
460
- methods,
461
- postamble,
462
- ].compact.join("\n")
537
+ methods.join("\n")
538
+ end
463
539
  end
464
540
 
465
541
  sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
@@ -639,6 +715,33 @@ module Tapioca
639
715
  @indent -= 2 * step
640
716
  end
641
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
+
642
745
  sig { params(str: String).returns(String) }
643
746
  def indented(str)
644
747
  " " * @indent + str
@@ -839,12 +942,12 @@ module Tapioca
839
942
  nil
840
943
  end
841
944
 
842
- sig { params(constant: Module).returns(String) }
945
+ sig { params(constant: T::Types::Base).returns(String) }
843
946
  def type_of(constant)
844
947
  constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
845
948
  end
846
949
 
847
- sig { params(object: Object).returns(T::Boolean).checked(:never) }
950
+ sig { params(object: BasicObject).returns(Integer).checked(:never) }
848
951
  def object_id_of(object)
849
952
  Object.instance_method(:object_id).bind(object).call
850
953
  end
@@ -8,7 +8,6 @@ module Tapioca
8
8
  const(:outdir, String)
9
9
  const(:prerequire, T.nilable(String))
10
10
  const(:postrequire, String)
11
- const(:generate_command, String)
12
11
  const(:exclude, T::Array[String])
13
12
  const(:typed_overrides, T::Hash[String, String])
14
13
  const(:todos_path, String)
@@ -27,6 +26,7 @@ module Tapioca
27
26
  TAPIOCA_PATH = T.let("#{SORBET_PATH}/tapioca", String)
28
27
  TAPIOCA_CONFIG = T.let("#{TAPIOCA_PATH}/config.yml", String)
29
28
 
29
+ DEFAULT_COMMAND = T.let("bin/tapioca", String)
30
30
  DEFAULT_POSTREQUIRE = T.let("#{TAPIOCA_PATH}/require.rb", String)
31
31
  DEFAULT_RBIDIR = T.let("#{SORBET_PATH}/rbi", String)
32
32
  DEFAULT_DSLDIR = T.let("#{DEFAULT_RBIDIR}/dsl", String)
@@ -10,9 +10,13 @@ module Tapioca
10
10
 
11
11
  sig { params(command: Symbol, options: T::Hash[String, T.untyped]).returns(Config) }
12
12
  def from_options(command, options)
13
- Config.from_hash(
14
- merge_options(default_options(command), config_options, options)
15
- )
13
+ merged_options = merge_options(default_options(command), config_options, options)
14
+
15
+ puts(<<~MSG) if merged_options.include?("generate_command")
16
+ DEPRECATION: The `-c` and `--cmd` flags will be removed in a future release.
17
+ MSG
18
+
19
+ Config.from_hash(merged_options)
16
20
  end
17
21
 
18
22
  private
@@ -40,14 +44,6 @@ module Tapioca
40
44
  DEFAULT_OPTIONS.merge("outdir" => default_outdir)
41
45
  end
42
46
 
43
- sig { returns(String) }
44
- def default_command
45
- command = File.basename($PROGRAM_NAME)
46
- args = ARGV.join(" ")
47
-
48
- "#{command} #{args}".strip
49
- end
50
-
51
47
  sig { params(options: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
52
48
  def merge_options(*options)
53
49
  options.each_with_object({}) do |option, result|
@@ -65,7 +61,6 @@ module Tapioca
65
61
  DEFAULT_OPTIONS = T.let({
66
62
  "postrequire" => Config::DEFAULT_POSTREQUIRE,
67
63
  "outdir" => nil,
68
- "generate_command" => default_command,
69
64
  "exclude" => [],
70
65
  "typed_overrides" => Config::DEFAULT_OVERRIDES,
71
66
  "todos_path" => Config::DEFAULT_TODOSPATH,
@@ -17,27 +17,23 @@ module Tapioca
17
17
  )
18
18
  end
19
19
 
20
+ sig { returns(Bundler::Definition) }
21
+ attr_reader(:definition)
22
+
23
+ sig { returns(T::Array[Gem]) }
24
+ attr_reader(:dependencies)
25
+
26
+ sig { returns(T::Array[String]) }
27
+ attr_reader(:missing_specs)
28
+
20
29
  sig { void }
21
30
  def initialize
22
31
  @gemfile = T.let(File.new(Bundler.default_gemfile), File)
23
32
  @lockfile = T.let(File.new(Bundler.default_lockfile), File)
24
- @dependencies = T.let(nil, T.nilable(T::Array[Gem]))
25
- @definition = T.let(nil, T.nilable(Bundler::Definition))
26
- end
27
-
28
- sig { returns(T::Array[Gem]) }
29
- def dependencies
30
- @dependencies ||= begin
31
- specs = definition.locked_gems.specs.to_a
32
-
33
- definition
34
- .resolve
35
- .materialize(specs)
36
- .map { |spec| Gem.new(spec) }
37
- .reject { |gem| gem.ignore?(dir) }
38
- .uniq(&:rbi_file_name)
39
- .sort_by(&:rbi_file_name)
40
- end
33
+ @definition = T.let(Bundler::Dsl.evaluate(gemfile, lockfile, {}), Bundler::Definition)
34
+ dependencies, missing_specs = load_dependencies
35
+ @dependencies = T.let(dependencies, T::Array[Gem])
36
+ @missing_specs = T.let(missing_specs, T::Array[String])
41
37
  end
42
38
 
43
39
  sig { params(gem_name: String).returns(T.nilable(Gem)) }
@@ -55,6 +51,23 @@ module Tapioca
55
51
  sig { returns(File) }
56
52
  attr_reader(:gemfile, :lockfile)
57
53
 
54
+ sig { returns([T::Array[Gem], T::Array[String]]) }
55
+ def load_dependencies
56
+ deps = definition.locked_gems.dependencies.values
57
+
58
+ missing_specs = T::Array[String].new
59
+
60
+ dependencies = definition
61
+ .resolve
62
+ .materialize(deps, missing_specs)
63
+ .map { |spec| Gem.new(spec) }
64
+ .reject { |gem| gem.ignore?(dir) }
65
+ .uniq(&:rbi_file_name)
66
+ .sort_by(&:rbi_file_name)
67
+
68
+ [dependencies, missing_specs]
69
+ end
70
+
58
71
  sig { returns(Bundler::Runtime) }
59
72
  def runtime
60
73
  Bundler::Runtime.new(File.dirname(gemfile.path), definition)
@@ -65,11 +78,6 @@ module Tapioca
65
78
  definition.groups
66
79
  end
67
80
 
68
- sig { returns(Bundler::Definition) }
69
- def definition
70
- @definition ||= Bundler::Dsl.evaluate(gemfile, lockfile, {})
71
- end
72
-
73
81
  sig { returns(String) }
74
82
  def dir
75
83
  File.expand_path(gemfile.path + "/..")