tapioca 0.4.27 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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 +2 -9
  44. data/lib/tapioca/loader.rb +13 -33
  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 -1
  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