tapioca 0.4.26 → 0.5.0

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -14
  3. data/README.md +2 -2
  4. data/Rakefile +5 -7
  5. data/exe/tapioca +2 -2
  6. data/lib/tapioca/cli.rb +256 -2
  7. data/lib/tapioca/compilers/dsl/aasm.rb +122 -0
  8. data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +52 -12
  9. data/lib/tapioca/compilers/dsl/action_mailer.rb +6 -9
  10. data/lib/tapioca/compilers/dsl/active_job.rb +8 -12
  11. data/lib/tapioca/compilers/dsl/active_model_attributes.rb +131 -0
  12. data/lib/tapioca/compilers/dsl/active_record_associations.rb +33 -54
  13. data/lib/tapioca/compilers/dsl/active_record_columns.rb +10 -105
  14. data/lib/tapioca/compilers/dsl/active_record_enum.rb +8 -10
  15. data/lib/tapioca/compilers/dsl/active_record_scope.rb +7 -10
  16. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +5 -8
  17. data/lib/tapioca/compilers/dsl/active_resource.rb +9 -37
  18. data/lib/tapioca/compilers/dsl/active_storage.rb +98 -0
  19. data/lib/tapioca/compilers/dsl/active_support_concern.rb +108 -0
  20. data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +13 -8
  21. data/lib/tapioca/compilers/dsl/base.rb +96 -82
  22. data/lib/tapioca/compilers/dsl/config.rb +111 -0
  23. data/lib/tapioca/compilers/dsl/frozen_record.rb +5 -7
  24. data/lib/tapioca/compilers/dsl/identity_cache.rb +66 -29
  25. data/lib/tapioca/compilers/dsl/protobuf.rb +19 -69
  26. data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +25 -12
  27. data/lib/tapioca/compilers/dsl/smart_properties.rb +19 -31
  28. data/lib/tapioca/compilers/dsl/state_machines.rb +56 -78
  29. data/lib/tapioca/compilers/dsl/url_helpers.rb +7 -10
  30. data/lib/tapioca/compilers/dsl_compiler.rb +22 -38
  31. data/lib/tapioca/compilers/requires_compiler.rb +2 -2
  32. data/lib/tapioca/compilers/sorbet.rb +26 -5
  33. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +138 -153
  34. data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +4 -4
  35. data/lib/tapioca/compilers/todos_compiler.rb +1 -1
  36. data/lib/tapioca/config.rb +2 -0
  37. data/lib/tapioca/config_builder.rb +4 -2
  38. data/lib/tapioca/constant_locator.rb +6 -8
  39. data/lib/tapioca/gemfile.rb +2 -4
  40. data/lib/tapioca/generator.rb +124 -40
  41. data/lib/tapioca/generic_type_registry.rb +25 -98
  42. data/lib/tapioca/helpers/active_record_column_type_helper.rb +98 -0
  43. data/lib/tapioca/internal.rb +1 -9
  44. data/lib/tapioca/loader.rb +11 -31
  45. data/lib/tapioca/rbi_ext/model.rb +122 -0
  46. data/lib/tapioca/reflection.rb +131 -0
  47. data/lib/tapioca/sorbet_ext/fixed_hash_patch.rb +1 -1
  48. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +72 -4
  49. data/lib/tapioca/sorbet_ext/name_patch.rb +1 -1
  50. data/lib/tapioca/version.rb +1 -1
  51. data/lib/tapioca.rb +2 -0
  52. metadata +34 -22
  53. data/lib/tapioca/cli/main.rb +0 -146
  54. data/lib/tapioca/core_ext/class.rb +0 -28
  55. data/lib/tapioca/core_ext/string.rb +0 -18
  56. data/lib/tapioca/rbi/model.rb +0 -405
  57. data/lib/tapioca/rbi/printer.rb +0 -410
  58. data/lib/tapioca/rbi/rewriters/group_nodes.rb +0 -106
  59. data/lib/tapioca/rbi/rewriters/nest_non_public_methods.rb +0 -65
  60. data/lib/tapioca/rbi/rewriters/nest_singleton_methods.rb +0 -42
  61. data/lib/tapioca/rbi/rewriters/sort_nodes.rb +0 -86
  62. data/lib/tapioca/rbi/visitor.rb +0 -21
@@ -1,8 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'json'
5
- require 'tempfile'
4
+ require "json"
5
+ require "tempfile"
6
6
 
7
7
  module Tapioca
8
8
  module Compilers
@@ -25,7 +25,7 @@ module Tapioca
25
25
 
26
26
  sig { params(paths: T::Array[String]).returns(T::Set[String]) }
27
27
  def load_symbols(paths)
28
- output = T.cast(Tempfile.create('sorbet') do |file|
28
+ output = T.cast(Tempfile.create("sorbet") do |file|
29
29
  file.write(Array(paths).join("\n"))
30
30
  file.flush
31
31
 
@@ -69,7 +69,7 @@ module Tapioca
69
69
  # TODO: CLASS is removed since v0.4.4730 of Sorbet
70
70
  # but keeping here for backward compatibility. Remove
71
71
  # once the minimum version is moved past that.
72
- next unless %w[CLASS CLASS_OR_MODULE STATIC_FIELD].include?(kind)
72
+ next unless ["CLASS", "CLASS_OR_MODULE", "STATIC_FIELD"].include?(kind)
73
73
  next if name =~ /[<>()$]/
74
74
  next if name =~ /^[0-9]+$/
75
75
  next if name == "T::Helpers"
@@ -13,7 +13,7 @@ module Tapioca
13
13
  def compile
14
14
  list_todos.each_line.map do |line|
15
15
  next if line.include?("<") || line.include?("class_of")
16
- "module #{line.strip.gsub('T.untyped::', '')}; end"
16
+ "module #{line.strip.gsub("T.untyped::", "")}; end"
17
17
  end.compact.join("\n")
18
18
  end
19
19
 
@@ -9,9 +9,11 @@ module Tapioca
9
9
  const(:prerequire, T.nilable(String))
10
10
  const(:postrequire, String)
11
11
  const(:exclude, T::Array[String])
12
+ const(:exclude_generators, T::Array[String])
12
13
  const(:typed_overrides, T::Hash[String, String])
13
14
  const(:todos_path, String)
14
15
  const(:generators, T::Array[String])
16
+ const(:file_header, T::Boolean, default: true)
15
17
 
16
18
  sig { returns(Pathname) }
17
19
  def outpath
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'yaml'
4
+ require "yaml"
5
5
 
6
6
  module Tapioca
7
7
  class ConfigBuilder
@@ -33,7 +33,7 @@ module Tapioca
33
33
  sig { params(command: Symbol).returns(T::Hash[String, T.untyped]) }
34
34
  def default_options(command)
35
35
  default_outdir = case command
36
- when :sync, :generate
36
+ when :sync, :generate, :gem
37
37
  Config::DEFAULT_GEMDIR
38
38
  when :dsl
39
39
  Config::DEFAULT_DSLDIR
@@ -62,9 +62,11 @@ module Tapioca
62
62
  "postrequire" => Config::DEFAULT_POSTREQUIRE,
63
63
  "outdir" => nil,
64
64
  "exclude" => [],
65
+ "exclude_generators" => [],
65
66
  "typed_overrides" => Config::DEFAULT_OVERRIDES,
66
67
  "todos_path" => Config::DEFAULT_TODOSPATH,
67
68
  "generators" => [],
69
+ "file_header" => true,
68
70
  }.freeze, T::Hash[String, T.untyped])
69
71
  end
70
72
  end
@@ -1,7 +1,7 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'set'
4
+ require "set"
5
5
 
6
6
  module Tapioca
7
7
  # Registers a TracePoint immediately upon load to track points at which
@@ -9,15 +9,14 @@ module Tapioca
9
9
  # correspondence between classes/modules and files, as this information isn't
10
10
  # available in the ruby runtime without extra accounting.
11
11
  module ConstantLocator
12
- @class_files = {}
12
+ extend Reflection
13
13
 
14
- NAME = Module.instance_method(:name)
15
- private_constant :NAME
14
+ @class_files = {}
16
15
 
17
16
  # Immediately activated upon load. Observes class/module definition.
18
17
  TracePoint.trace(:class) do |tp|
19
18
  unless tp.self.singleton_class?
20
- key = NAME.bind(tp.self).call
19
+ key = name_of(tp.self)
21
20
  @class_files[key] ||= Set.new
22
21
  @class_files[key] << tp.path
23
22
  end
@@ -26,11 +25,10 @@ module Tapioca
26
25
  # Returns the files in which this class or module was opened. Doesn't know
27
26
  # about situations where the class was opened prior to +require+ing,
28
27
  # or where metaprogramming was used via +eval+, etc.
29
- def files_for(klass)
30
- name = String === klass ? klass : NAME.bind(klass).call
28
+ def self.files_for(klass)
29
+ name = String === klass ? klass : name_of(klass)
31
30
  files = @class_files[name]
32
31
  files || Set.new
33
32
  end
34
- module_function :files_for
35
33
  end
36
34
  end
@@ -62,7 +62,7 @@ module Tapioca
62
62
  [dependencies, missing_specs]
63
63
  end
64
64
 
65
- sig { returns([T::Array[::Gem::Specification], T::Array[String]]) }
65
+ sig { returns([T::Enumerable[Spec], T::Array[String]]) }
66
66
  def materialize_deps
67
67
  deps = definition.locked_gems.dependencies.values
68
68
  missing_specs = T::Array[String].new
@@ -95,9 +95,7 @@ module Tapioca
95
95
  class GemSpec
96
96
  extend(T::Sig)
97
97
 
98
- IGNORED_GEMS = T.let(%w{
99
- sorbet sorbet-static sorbet-runtime
100
- }.freeze, T::Array[String])
98
+ IGNORED_GEMS = T.let(["sorbet", "sorbet-static", "sorbet-runtime"].freeze, T::Array[String])
101
99
 
102
100
  sig { returns(String) }
103
101
  attr_reader :full_gem_path, :version
@@ -1,9 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pathname'
5
- require 'thor'
6
- require "tapioca/core_ext/string"
4
+ require "pathname"
5
+ require "thor"
6
+ require "rake"
7
7
 
8
8
  module Tapioca
9
9
  class Generator < ::Thor::Shell::Color
@@ -63,11 +63,8 @@ module Tapioca
63
63
  File.delete(requires_path) if File.exist?(requires_path)
64
64
 
65
65
  content = String.new
66
- content << rbi_header(
67
- "#{Config::DEFAULT_COMMAND} require",
68
- reason: "explicit gem requires",
69
- strictness: "false"
70
- )
66
+ content << "# typed: true\n"
67
+ content << "# frozen_string_literal: true\n\n"
71
68
  content << rb_string
72
69
 
73
70
  outdir = File.dirname(requires_path)
@@ -121,11 +118,13 @@ module Tapioca
121
118
  params(
122
119
  requested_constants: T::Array[String],
123
120
  should_verify: T::Boolean,
124
- quiet: T::Boolean
121
+ quiet: T::Boolean,
122
+ verbose: T::Boolean
125
123
  ).void
126
124
  end
127
- def build_dsl(requested_constants, should_verify: false, quiet: false)
125
+ def build_dsl(requested_constants, should_verify: false, quiet: false, verbose: false)
128
126
  load_application(eager_load: requested_constants.empty?)
127
+ abort_if_pending_migrations!
129
128
  load_dsl_generators
130
129
 
131
130
  if should_verify
@@ -140,20 +139,26 @@ module Tapioca
140
139
 
141
140
  compiler = Compilers::DslCompiler.new(
142
141
  requested_constants: constantize(requested_constants),
143
- requested_generators: config.generators,
142
+ requested_generators: constantize_generators(config.generators),
143
+ excluded_generators: constantize_generators(config.exclude_generators),
144
144
  error_handler: ->(error) {
145
145
  say_error(error, :bold, :red)
146
146
  }
147
147
  )
148
148
 
149
149
  compiler.run do |constant, contents|
150
- constant_name = Module.instance_method(:name).bind(constant).call
150
+ constant_name = T.must(Reflection.name_of(constant))
151
+
152
+ if verbose && !quiet
153
+ say("Processing: ", [:yellow])
154
+ say(constant_name)
155
+ end
151
156
 
152
157
  filename = compile_dsl_rbi(
153
158
  constant_name,
154
159
  contents,
155
160
  outpath: outpath,
156
- quiet: should_verify || quiet
161
+ quiet: should_verify || quiet && !verbose
157
162
  )
158
163
 
159
164
  if filename
@@ -174,8 +179,15 @@ module Tapioca
174
179
  end
175
180
  end
176
181
 
177
- sig { void }
178
- def sync_rbis_with_gemfile
182
+ sig { params(should_verify: T::Boolean).void }
183
+ def sync_rbis_with_gemfile(should_verify: false)
184
+ if should_verify
185
+ say("Checking for out-of-date RBIs...")
186
+ say("")
187
+ perform_sync_verification
188
+ return
189
+ end
190
+
179
191
  anything_done = [
180
192
  perform_removals,
181
193
  perform_additions,
@@ -195,7 +207,7 @@ module Tapioca
195
207
 
196
208
  EMPTY_RBI_COMMENT = <<~CONTENT
197
209
  # THIS IS AN EMPTY RBI FILE.
198
- # see https://github.com/Shopify/tapioca/blob/master/README.md#manual-gem-requires
210
+ # see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires
199
211
  CONTENT
200
212
 
201
213
  sig { returns(Gemfile) }
@@ -205,7 +217,7 @@ module Tapioca
205
217
 
206
218
  sig { returns(Loader) }
207
219
  def loader
208
- @loader ||= Loader.new(bundle)
220
+ @loader ||= Loader.new
209
221
  end
210
222
 
211
223
  sig { returns(Compilers::SymbolTableCompiler) }
@@ -217,7 +229,7 @@ module Tapioca
217
229
  def require_gem_file
218
230
  say("Requiring all gems to prepare for compiling... ")
219
231
  begin
220
- loader.load_bundle(config.prerequire, config.postrequire)
232
+ loader.load_bundle(bundle, config.prerequire, config.postrequire)
221
233
  rescue LoadError => e
222
234
  explain_failed_require(config.postrequire, e)
223
235
  exit(1)
@@ -225,7 +237,7 @@ module Tapioca
225
237
  say(" Done", :green)
226
238
  unless bundle.missing_specs.empty?
227
239
  say(" completed with missing specs: ")
228
- say(bundle.missing_specs.join(', '), :yellow)
240
+ say(bundle.missing_specs.join(", "), :yellow)
229
241
  end
230
242
  puts
231
243
  end
@@ -285,11 +297,9 @@ module Tapioca
285
297
  sig { params(constant_names: T::Array[String]).returns(T::Array[Module]) }
286
298
  def constantize(constant_names)
287
299
  constant_map = constant_names.map do |name|
288
- begin
289
- [name, Object.const_get(name)]
290
- rescue NameError
291
- [name, nil]
292
- end
300
+ [name, Object.const_get(name)]
301
+ rescue NameError
302
+ [name, nil]
293
303
  end.to_h
294
304
 
295
305
  unprocessable_constants = constant_map.select { |_, v| v.nil? }
@@ -305,6 +315,32 @@ module Tapioca
305
315
  constant_map.values
306
316
  end
307
317
 
318
+ sig { params(generator_names: T::Array[String]).returns(T::Array[T.class_of(Compilers::Dsl::Base)]) }
319
+ def constantize_generators(generator_names)
320
+ generator_map = generator_names.map do |name|
321
+ # Try to find built-in tapioca generator first, then globally defined generator. The
322
+ # explicit `break` ensures the class is returned, not the `potential_name`.
323
+ generator_klass = ["Tapioca::Compilers::Dsl::#{name}", name].find do |potential_name|
324
+ break Object.const_get(potential_name)
325
+ rescue NameError
326
+ # Skip if we can't find generator by the potential name
327
+ end
328
+
329
+ [name, generator_klass]
330
+ end.to_h
331
+
332
+ unprocessable_generators = generator_map.select { |_, v| v.nil? }
333
+ unless unprocessable_generators.empty?
334
+ unprocessable_generators.each do |name, _|
335
+ say("Error: Cannot find generator '#{name}'", :red)
336
+ end
337
+
338
+ exit(1)
339
+ end
340
+
341
+ generator_map.values
342
+ end
343
+
308
344
  sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
309
345
  def existing_rbi_filenames(requested_constants, path: config.outpath)
310
346
  filenames = if requested_constants.empty?
@@ -321,7 +357,7 @@ module Tapioca
321
357
  sig { returns(T::Hash[String, String]) }
322
358
  def existing_rbis
323
359
  @existing_rbis ||= Pathname.glob((config.outpath / "*@*.rbi").to_s)
324
- .map { |f| T.cast(f.basename(".*").to_s.split('@', 2), [String, String]) }
360
+ .map { |f| T.cast(f.basename(".*").to_s.split("@", 2), [String, String]) }
325
361
  .to_h
326
362
  end
327
363
 
@@ -335,7 +371,7 @@ module Tapioca
335
371
 
336
372
  sig { params(constant_name: String).returns(Pathname) }
337
373
  def dsl_rbi_filename(constant_name)
338
- config.outpath / "#{constant_name.underscore}.rbi"
374
+ config.outpath / "#{underscore(constant_name)}.rbi"
339
375
  end
340
376
 
341
377
  sig { params(gem_name: String, version: String).returns(Pathname) }
@@ -483,7 +519,13 @@ module Tapioca
483
519
  # typed: #{strictness}
484
520
  SIGIL
485
521
 
486
- [statement, sigil].compact.join("\n").strip.concat("\n\n")
522
+ if config.file_header
523
+ [statement, sigil].compact.join("\n").strip.concat("\n\n")
524
+ elsif sigil
525
+ sigil.strip.concat("\n\n")
526
+ else
527
+ ""
528
+ end
487
529
  end
488
530
 
489
531
  sig { params(gem: Gemfile::GemSpec).void }
@@ -525,7 +567,7 @@ module Tapioca
525
567
  def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
526
568
  return if contents.nil?
527
569
 
528
- rbi_name = constant_name.underscore + ".rbi"
570
+ rbi_name = underscore(constant_name) + ".rbi"
529
571
  filename = outpath / rbi_name
530
572
 
531
573
  out = String.new
@@ -598,11 +640,47 @@ module Tapioca
598
640
  def perform_dsl_verification(dir)
599
641
  diff = verify_dsl_rbi(tmp_dir: dir)
600
642
 
643
+ report_diff_and_exit_if_out_of_date(diff, "dsl")
644
+ ensure
645
+ FileUtils.remove_entry(dir)
646
+ end
647
+
648
+ sig { params(files: T::Set[Pathname]).void }
649
+ def purge_stale_dsl_rbi_files(files)
650
+ if files.any?
651
+ say("Removing stale RBI files...")
652
+
653
+ files.sort.each do |filename|
654
+ remove(filename)
655
+ end
656
+ say("")
657
+ end
658
+ end
659
+
660
+ sig { void }
661
+ def perform_sync_verification
662
+ diff = {}
663
+
664
+ removed_rbis.each do |gem_name|
665
+ filename = existing_rbi(gem_name)
666
+ diff[filename] = :removed
667
+ end
668
+
669
+ added_rbis.each do |gem_name|
670
+ filename = expected_rbi(gem_name)
671
+ diff[filename] = gem_rbi_exists?(gem_name) ? :changed : :added
672
+ end
673
+
674
+ report_diff_and_exit_if_out_of_date(diff, "sync")
675
+ end
676
+
677
+ sig { params(diff: T::Hash[String, Symbol], command: String).void }
678
+ def report_diff_and_exit_if_out_of_date(diff, command)
601
679
  if diff.empty?
602
680
  say("Nothing to do, all RBIs are up-to-date.")
603
681
  else
604
682
  say("RBI files are out-of-date. In your development environment, please run:", :green)
605
- say(" `#{Config::DEFAULT_COMMAND} dsl`", [:green, :bold])
683
+ say(" `#{Config::DEFAULT_COMMAND} #{command}`", [:green, :bold])
606
684
  say("Once it is complete, be sure to commit and push any changes", :green)
607
685
 
608
686
  say("")
@@ -614,20 +692,26 @@ module Tapioca
614
692
 
615
693
  exit(1)
616
694
  end
617
- ensure
618
- FileUtils.remove_entry(dir)
619
695
  end
620
696
 
621
- sig { params(files: T::Set[Pathname]).void }
622
- def purge_stale_dsl_rbi_files(files)
623
- if files.any?
624
- say("Removing stale RBI files...")
697
+ sig { void }
698
+ def abort_if_pending_migrations!
699
+ return unless File.exist?("config/application.rb")
625
700
 
626
- files.sort.each do |filename|
627
- remove(filename)
628
- end
629
- say("")
630
- end
701
+ Rails.application.load_tasks
702
+ Rake::Task["db:abort_if_pending_migrations"].invoke if Rake::Task.task_defined?("db:abort_if_pending_migrations")
703
+ end
704
+
705
+ sig { params(class_name: String).returns(String) }
706
+ def underscore(class_name)
707
+ return class_name unless /[A-Z-]|::/.match?(class_name)
708
+
709
+ word = class_name.to_s.gsub("::", "/")
710
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
711
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
712
+ word.tr!("-", "_")
713
+ word.downcase!
714
+ word
631
715
  end
632
716
  end
633
717
  end
@@ -20,14 +20,15 @@ module Tapioca
20
20
  # variable to type variable serializers. This allows us to associate type variables
21
21
  # to the constant names that represent them, easily.
22
22
  module GenericTypeRegistry
23
+ TypeVariable = T.type_alias { T.any(TypeMember, TypeTemplate) }
23
24
  @generic_instances = T.let(
24
25
  {},
25
26
  T::Hash[String, Module]
26
27
  )
27
28
 
28
29
  @type_variables = T.let(
29
- {},
30
- T::Hash[Integer, T::Hash[Integer, String]]
30
+ {}.compare_by_identity,
31
+ T::Hash[Module, T::Hash[TypeVariable, String]]
31
32
  )
32
33
 
33
34
  class << self
@@ -49,7 +50,7 @@ module Tapioca
49
50
  # Build the name of the instantiated generic type,
50
51
  # something like `"Foo[X, Y, Z]"`
51
52
  type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
52
- name = "#{name_of(constant)}[#{type_list}]"
53
+ name = "#{Reflection.name_of(constant)}[#{type_list}]"
53
54
 
54
55
  # Create a generic type with an overridden `name`
55
56
  # method that returns the name we constructed above.
@@ -59,35 +60,30 @@ module Tapioca
59
60
  @generic_instances[name] ||= create_generic_type(constant, name)
60
61
  end
61
62
 
62
- sig do
63
- params(
64
- constant: T.untyped,
65
- type_member: T::Types::TypeVariable,
66
- fixed: T.untyped,
67
- lower: T.untyped,
68
- upper: T.untyped
69
- ).void
70
- end
71
- def register_type_member(constant, type_member, fixed, lower, upper)
72
- register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
63
+ sig { params(constant: Module).returns(T.nilable(T::Hash[TypeVariable, String])) }
64
+ def lookup_type_variables(constant)
65
+ @type_variables[constant]
73
66
  end
74
67
 
68
+ # This method is called from intercepted calls to `type_member` and `type_template`.
69
+ # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
70
+ # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
71
+ #
72
+ # This method creates a `String` with that data and stores it in the
73
+ # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
74
+ #
75
+ # Finally, the original `type_variable` is returned from this method, so that the caller
76
+ # can return it from the original methods as well.
75
77
  sig do
76
78
  params(
77
79
  constant: T.untyped,
78
- type_template: T::Types::TypeVariable,
79
- fixed: T.untyped,
80
- lower: T.untyped,
81
- upper: T.untyped
80
+ type_variable: TypeVariable,
82
81
  ).void
83
82
  end
84
- def register_type_template(constant, type_template, fixed, lower, upper)
85
- register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
86
- end
83
+ def register_type_variable(constant, type_variable)
84
+ type_variables = lookup_or_initialize_type_variables(constant)
87
85
 
88
- sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
89
- def lookup_type_variables(constant)
90
- @type_variables[object_id_of(constant)]
86
+ type_variables[type_variable] = type_variable.serialize
91
87
  end
92
88
 
93
89
  private
@@ -117,39 +113,6 @@ module Tapioca
117
113
  generic_type
118
114
  end
119
115
 
120
- # This method is called from intercepted calls to `type_member` and `type_template`.
121
- # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
122
- # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
123
- #
124
- # This method creates a `String` with that data and stores it in the
125
- # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
126
- #
127
- # Finally, the original `type_variable` is returned from this method, so that the caller
128
- # can return it from the original methods as well.
129
- sig do
130
- params(
131
- constant: T.untyped,
132
- type_variable_type: T.enum([:type_member, :type_template]),
133
- type_variable: T::Types::TypeVariable,
134
- fixed: T.untyped,
135
- lower: T.untyped,
136
- upper: T.untyped
137
- ).void
138
- end
139
- # rubocop:disable Metrics/ParameterLists
140
- def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
141
- # rubocop:enable Metrics/ParameterLists
142
- type_variables = lookup_or_initialize_type_variables(constant)
143
-
144
- type_variables[object_id_of(type_variable)] = serialize_type_variable(
145
- type_variable_type,
146
- type_variable.variance,
147
- fixed,
148
- lower,
149
- upper
150
- )
151
- end
152
-
153
116
  sig { params(constant: Class).returns(Class) }
154
117
  def create_safe_subclass(constant)
155
118
  # Lookup the "inherited" class method
@@ -164,11 +127,9 @@ module Tapioca
164
127
  # Otherwise, some inherited method could be preventing us
165
128
  # from creating subclasses, so let's override it and rescue
166
129
  owner.send(:define_method, :inherited) do |s|
167
- begin
168
- inherited_method.call(s)
169
- rescue
170
- # Ignoring errors
171
- end
130
+ inherited_method.call(s)
131
+ rescue
132
+ # Ignoring errors
172
133
  end
173
134
 
174
135
  # return a subclass
@@ -179,43 +140,9 @@ module Tapioca
179
140
  end
180
141
  end
181
142
 
182
- sig { params(constant: Module).returns(T::Hash[Integer, String]) }
143
+ sig { params(constant: Module).returns(T::Hash[TypeVariable, String]) }
183
144
  def lookup_or_initialize_type_variables(constant)
184
- @type_variables[object_id_of(constant)] ||= {}
185
- end
186
-
187
- sig do
188
- params(
189
- type_variable_type: Symbol,
190
- variance: Symbol,
191
- fixed: T.untyped,
192
- lower: T.untyped,
193
- upper: T.untyped
194
- ).returns(String)
195
- end
196
- def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
197
- parts = []
198
- parts << ":#{variance}" unless variance == :invariant
199
- parts << "fixed: #{fixed}" if fixed
200
- parts << "lower: #{lower}" unless lower == T.untyped
201
- parts << "upper: #{upper}" unless upper == BasicObject
202
-
203
- parameters = parts.join(", ")
204
-
205
- serialized = T.let(type_variable_type.to_s, String)
206
- serialized += "(#{parameters})" unless parameters.empty?
207
-
208
- serialized
209
- end
210
-
211
- sig { params(constant: Module).returns(T.nilable(String)) }
212
- def name_of(constant)
213
- Module.instance_method(:name).bind(constant).call
214
- end
215
-
216
- sig { params(object: BasicObject).returns(Integer) }
217
- def object_id_of(object)
218
- Object.instance_method(:object_id).bind(object).call
145
+ @type_variables[constant] ||= {}.compare_by_identity
219
146
  end
220
147
  end
221
148
  end