tapioca 0.4.13 → 0.4.18

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