tapioca 0.19.0 → 0.19.2

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -27
  3. data/lib/tapioca/cli.rb +11 -1
  4. data/lib/tapioca/commands/abstract_dsl.rb +6 -1
  5. data/lib/tapioca/commands/check_shims.rb +3 -0
  6. data/lib/tapioca/commands/configure.rb +1 -0
  7. data/lib/tapioca/commands/dsl_generate.rb +15 -0
  8. data/lib/tapioca/commands/todo.rb +8 -2
  9. data/lib/tapioca/dsl/compiler.rb +13 -13
  10. data/lib/tapioca/dsl/compilers/aasm.rb +1 -1
  11. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +2 -2
  12. data/lib/tapioca/dsl/compilers/action_mailer.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/action_text.rb +1 -1
  14. data/lib/tapioca/dsl/compilers/active_job.rb +1 -1
  15. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
  16. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +1 -1
  17. data/lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb +1 -1
  18. data/lib/tapioca/dsl/compilers/active_record_associations.rb +1 -1
  19. data/lib/tapioca/dsl/compilers/active_record_columns.rb +1 -1
  20. data/lib/tapioca/dsl/compilers/active_record_delegated_types.rb +65 -17
  21. data/lib/tapioca/dsl/compilers/active_record_enum.rb +1 -1
  22. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +1 -1
  23. data/lib/tapioca/dsl/compilers/active_record_relations.rb +1 -1
  24. data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
  25. data/lib/tapioca/dsl/compilers/active_record_secure_token.rb +1 -1
  26. data/lib/tapioca/dsl/compilers/active_record_store.rb +1 -1
  27. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +1 -1
  28. data/lib/tapioca/dsl/compilers/active_resource.rb +1 -1
  29. data/lib/tapioca/dsl/compilers/active_storage.rb +2 -2
  30. data/lib/tapioca/dsl/compilers/active_support_concern.rb +6 -6
  31. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +1 -1
  32. data/lib/tapioca/dsl/compilers/active_support_environment_inquirer.rb +1 -1
  33. data/lib/tapioca/dsl/compilers/active_support_time_ext.rb +1 -1
  34. data/lib/tapioca/dsl/compilers/config.rb +2 -2
  35. data/lib/tapioca/dsl/compilers/frozen_record.rb +1 -1
  36. data/lib/tapioca/dsl/compilers/graphql_input_object.rb +1 -1
  37. data/lib/tapioca/dsl/compilers/graphql_mutation.rb +1 -1
  38. data/lib/tapioca/dsl/compilers/identity_cache.rb +2 -2
  39. data/lib/tapioca/dsl/compilers/json_api_client_resource.rb +1 -1
  40. data/lib/tapioca/dsl/compilers/kredis.rb +1 -1
  41. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +2 -2
  42. data/lib/tapioca/dsl/compilers/protobuf.rb +1 -1
  43. data/lib/tapioca/dsl/compilers/rails_generators.rb +1 -1
  44. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
  45. data/lib/tapioca/dsl/compilers/smart_properties.rb +1 -1
  46. data/lib/tapioca/dsl/compilers/state_machines.rb +2 -2
  47. data/lib/tapioca/dsl/compilers/url_helpers.rb +7 -7
  48. data/lib/tapioca/dsl/helpers/graphql_type_helper.rb +4 -2
  49. data/lib/tapioca/dsl/pipeline.rb +13 -37
  50. data/lib/tapioca/gem/events.rb +7 -7
  51. data/lib/tapioca/gem/listeners/methods.rb +8 -8
  52. data/lib/tapioca/gem/listeners/mixins.rb +3 -3
  53. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +5 -3
  54. data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +1 -1
  55. data/lib/tapioca/gem/pipeline.rb +18 -18
  56. data/lib/tapioca/helpers/sorbet_helper.rb +19 -0
  57. data/lib/tapioca/helpers/test/dsl_compiler.rb +2 -9
  58. data/lib/tapioca/internal.rb +2 -0
  59. data/lib/tapioca/rbi_ext/model.rb +8 -5
  60. data/lib/tapioca/rbs/rewriter.rb +80 -25
  61. data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +6 -6
  62. data/lib/tapioca/runtime/generic_type_registry.rb +17 -18
  63. data/lib/tapioca/runtime/reflection.rb +20 -20
  64. data/lib/tapioca/runtime/trackers/constant_definition.rb +3 -3
  65. data/lib/tapioca/runtime/trackers/method_definition.rb +4 -4
  66. data/lib/tapioca/runtime/trackers/mixin.rb +5 -5
  67. data/lib/tapioca/runtime/trackers/required_ancestor.rb +2 -2
  68. data/lib/tapioca/runtime/trackers/tracker.rb +1 -1
  69. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +1 -21
  70. data/lib/tapioca/sorbet_ext/generic_type_patch.rb +48 -0
  71. data/lib/tapioca/sorbet_ext/void_patch.rb +29 -0
  72. data/lib/tapioca/static/symbol_loader.rb +19 -1
  73. data/lib/tapioca/version.rb +1 -1
  74. metadata +8 -6
@@ -23,8 +23,10 @@ module Tapioca
23
23
  def compile_signature(signature, parameters)
24
24
  parameter_types = signature.arg_types.to_h #: Hash[Symbol, T::Types::Base]
25
25
  parameter_types.merge!(signature.kwarg_types)
26
- parameter_types[signature.rest_name] = signature.rest_type if signature.has_rest
27
- parameter_types[signature.keyrest_name] = signature.keyrest_type if signature.has_keyrest
26
+ rest_type = signature.rest_type
27
+ parameter_types[signature.rest_name] = rest_type if rest_type
28
+ keyrest_type = signature.keyrest_type
29
+ parameter_types[signature.keyrest_name] = keyrest_type if keyrest_type
28
30
  parameter_types[signature.block_name] = signature.block_type if signature.block_name
29
31
 
30
32
  sig = RBI::Sig.new
@@ -40,7 +42,7 @@ module Tapioca
40
42
  sig.return_type = return_type
41
43
  @pipeline.push_symbol(return_type)
42
44
 
43
- sig.type_params.concat(extract_type_parameters(parameter_types.values.map(&:to_s).push(return_type)))
45
+ sig.type_params.concat(extract_type_parameters(parameter_types.values.map(&:to_s).append(return_type)))
44
46
 
45
47
  case signature.mode
46
48
  when "abstract"
@@ -22,7 +22,7 @@ module Tapioca
22
22
  node << sclass if sclass.nodes.length > 1
23
23
  end
24
24
 
25
- #: (RBI::Tree tree, T::Module[top] constant) -> void
25
+ #: (RBI::Tree tree, Module[top] constant) -> void
26
26
  def compile_type_variable_declarations(tree, constant)
27
27
  # Try to find the type variables defined on this constant, bail if we can't
28
28
  type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
@@ -72,29 +72,29 @@ module Tapioca
72
72
  @events << Gem::ConstantFound.new(symbol, constant)
73
73
  end
74
74
 
75
- #: (String symbol, T::Module[top] constant) -> void
75
+ #: (String symbol, Module[top] constant) -> void
76
76
  def push_foreign_constant(symbol, constant)
77
77
  @events << Gem::ForeignConstantFound.new(symbol, constant)
78
78
  end
79
79
 
80
- #: (String symbol, T::Module[top] constant, RBI::Const node) -> void
80
+ #: (String symbol, Module[top] constant, RBI::Const node) -> void
81
81
  def push_const(symbol, constant, node)
82
82
  @events << Gem::ConstNodeAdded.new(symbol, constant, node)
83
83
  end
84
84
 
85
- #: (String symbol, T::Module[top] constant, RBI::Scope node) -> void
85
+ #: (String symbol, Module[top] constant, RBI::Scope node) -> void
86
86
  def push_scope(symbol, constant, node)
87
87
  @events << Gem::ScopeNodeAdded.new(symbol, constant, node)
88
88
  end
89
89
 
90
- #: (String symbol, T::Module[top] constant, RBI::Scope node) -> void
90
+ #: (String symbol, Module[top] constant, RBI::Scope node) -> void
91
91
  def push_foreign_scope(symbol, constant, node)
92
92
  @events << Gem::ForeignScopeNodeAdded.new(symbol, constant, node)
93
93
  end
94
94
 
95
95
  #: (
96
96
  #| String symbol,
97
- #| T::Module[top] constant,
97
+ #| Module[top] constant,
98
98
  #| UnboundMethod method,
99
99
  #| RBI::Method node,
100
100
  #| untyped signature,
@@ -150,7 +150,7 @@ module Tapioca
150
150
  end
151
151
  end
152
152
 
153
- #: (Symbol method_name, T::Module[top] owner) -> MethodDefinitionLookupResult
153
+ #: (Symbol method_name, Module[top] owner) -> MethodDefinitionLookupResult
154
154
  def method_definition_in_gem(method_name, owner)
155
155
  definitions = Tapioca::Runtime::Trackers::MethodDefinition.method_definitions_for(method_name, owner)
156
156
 
@@ -176,7 +176,7 @@ module Tapioca
176
176
 
177
177
  # Helpers
178
178
 
179
- #: (T::Module[top] constant) -> String?
179
+ #: (Module[top] constant) -> String?
180
180
  def name_of(constant)
181
181
  name = name_of_proxy_target(constant, super(class_of(constant)))
182
182
  return name if name
@@ -248,7 +248,7 @@ module Tapioca
248
248
 
249
249
  # Compiling
250
250
 
251
- #: (String symbol, T::Module[top] constant) -> void
251
+ #: (String symbol, Module[top] constant) -> void
252
252
  def compile_foreign_constant(symbol, constant)
253
253
  return if skip_foreign_constant?(symbol, constant)
254
254
  return if seen?(symbol)
@@ -274,7 +274,7 @@ module Tapioca
274
274
  end
275
275
  end
276
276
 
277
- #: (String name, T::Module[top] constant) -> void
277
+ #: (String name, Module[top] constant) -> void
278
278
  def compile_alias(name, constant)
279
279
  return if seen?(name)
280
280
 
@@ -329,7 +329,7 @@ module Tapioca
329
329
  @root << node
330
330
  end
331
331
 
332
- #: (String name, T::Module[top] constant) -> void
332
+ #: (String name, Module[top] constant) -> void
333
333
  def compile_module(name, constant)
334
334
  return if skip_module?(name, constant)
335
335
  return if seen?(name)
@@ -340,7 +340,7 @@ module Tapioca
340
340
  push_scope(name, constant, scope)
341
341
  end
342
342
 
343
- #: (String name, T::Module[top] constant) -> RBI::Scope
343
+ #: (String name, Module[top] constant) -> RBI::Scope
344
344
  def compile_scope(name, constant)
345
345
  scope = if constant.is_a?(Class)
346
346
  superclass = compile_superclass(constant)
@@ -423,7 +423,7 @@ module Tapioca
423
423
  false
424
424
  end
425
425
 
426
- #: (String name, T::Module[top] constant) -> bool
426
+ #: (String name, Module[top] constant) -> bool
427
427
  def skip_alias?(name, constant)
428
428
  return true if symbol_in_payload?(name)
429
429
  return true unless constant_in_gem?(name)
@@ -441,12 +441,12 @@ module Tapioca
441
441
  false
442
442
  end
443
443
 
444
- #: (String name, T::Module[top] constant) -> bool
444
+ #: (String name, Module[top] constant) -> bool
445
445
  def skip_foreign_constant?(name, constant)
446
446
  Tapioca::TypeVariableModule === constant
447
447
  end
448
448
 
449
- #: (String name, T::Module[top] constant) -> bool
449
+ #: (String name, Module[top] constant) -> bool
450
450
  def skip_module?(name, constant)
451
451
  return true unless defined_in_gem?(constant, strict: false)
452
452
  return true if Tapioca::TypeVariableModule === constant
@@ -454,7 +454,7 @@ module Tapioca
454
454
  false
455
455
  end
456
456
 
457
- #: (T::Module[top] constant, ?strict: bool) -> bool
457
+ #: (Module[top] constant, ?strict: bool) -> bool
458
458
  def defined_in_gem?(constant, strict: true)
459
459
  files = get_file_candidates(constant)
460
460
  .merge(Runtime::Trackers::ConstantDefinition.files_for(constant))
@@ -466,7 +466,7 @@ module Tapioca
466
466
  end
467
467
  end
468
468
 
469
- #: (T::Module[top] constant) -> Set[String]
469
+ #: (Module[top] constant) -> Set[String]
470
470
  def get_file_candidates(constant)
471
471
  file_candidates_for(constant)
472
472
  rescue ArgumentError, NameError
@@ -497,7 +497,7 @@ module Tapioca
497
497
 
498
498
  # Helpers
499
499
 
500
- #: ((T::Module[top] & T::Generic) constant) -> String
500
+ #: ((Module[top] & T::Generic) constant) -> String
501
501
  def generic_name_of(constant)
502
502
  type_name = T.must(constant.name)
503
503
  return type_name if type_name =~ /\[.*\]$/
@@ -513,7 +513,7 @@ module Tapioca
513
513
  "#{type_name}[#{type_variable_names}]"
514
514
  end
515
515
 
516
- #: (T::Module[top] constant, String? class_name) -> String?
516
+ #: (Module[top] constant, String? class_name) -> String?
517
517
  def name_of_proxy_target(constant, class_name)
518
518
  return unless class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy"
519
519
 
@@ -22,6 +22,25 @@ module Tapioca
22
22
  SPOOM_CONTEXT.srb(sorbet_args.join(" "), sorbet_bin: sorbet_path)
23
23
  end
24
24
 
25
+ #: (String, rbi_mode: bool) { (String stderr) -> void } -> void
26
+ def sorbet_syntax_check!(source, rbi_mode:, &on_failure)
27
+ quoted_source = "\"#{source}\""
28
+
29
+ result = if rbi_mode
30
+ # --e-rbi cannot be used on its own, so we pass a dummy value like `-e ""`
31
+ sorbet("--no-config", "--stop-after=parser", "-e", '""', "--e-rbi", quoted_source)
32
+ else
33
+ sorbet("--no-config", "--stop-after=parser", "-e", quoted_source)
34
+ end
35
+
36
+ unless result.status
37
+ stderr = result.err #: as !nil
38
+ on_failure.call(stderr)
39
+ end
40
+
41
+ nil
42
+ end
43
+
25
44
  #: -> String
26
45
  def sorbet_path
27
46
  sorbet_path = ENV.fetch(SORBET_EXE_PATH_ENV_VAR, SORBET_BIN)
@@ -94,21 +94,14 @@ module Tapioca
94
94
  compiler.decorate
95
95
 
96
96
  rbi = Tapioca::DEFAULT_RBI_FORMATTER.print_file(file)
97
- result = sorbet(
98
- "--no-config",
99
- "--stop-after",
100
- "parser",
101
- "-e",
102
- "\"#{rbi}\"",
103
- )
104
97
 
105
- unless result.status
98
+ sorbet_syntax_check!(rbi, rbi_mode: true) do |stderr|
106
99
  raise(SyntaxError, <<~MSG)
107
100
  Expected generated RBI file for `#{constant_name}` to not have any parsing errors.
108
101
 
109
102
  Got these parsing errors:
110
103
 
111
- #{result.err}
104
+ #{stderr}
112
105
  MSG
113
106
  end
114
107
 
@@ -12,7 +12,9 @@ require "tapioca/runtime/dynamic_mixin_compiler"
12
12
  require "tapioca/sorbet_ext/backcompat_patches"
13
13
  require "tapioca/sorbet_ext/name_patch"
14
14
  require "tapioca/sorbet_ext/generic_name_patch"
15
+ require "tapioca/sorbet_ext/generic_type_patch"
15
16
  require "tapioca/sorbet_ext/proc_bind_patch"
17
+ require "tapioca/sorbet_ext/void_patch"
16
18
  require "tapioca/runtime/generic_type_registry"
17
19
 
18
20
  # The rewriter needs to be loaded very early so RBS comments within Tapioca itself are rewritten
@@ -3,7 +3,7 @@
3
3
 
4
4
  module RBI
5
5
  class Tree
6
- #: (T::Module[top] constant) ?{ (Scope scope) -> void } -> Scope
6
+ #: (Module[top] constant) ?{ (Scope scope) -> void } -> Scope
7
7
  def create_path(constant, &block)
8
8
  constant_name = Tapioca::Runtime::Reflection.name_of(constant)
9
9
  raise "given constant does not have a name" unless constant_name
@@ -65,11 +65,10 @@ module RBI
65
65
  #| ?return_type: String?,
66
66
  #| ?class_method: bool,
67
67
  #| ?visibility: RBI::Visibility,
68
- #| ?comments: Array[RBI::Comment],
69
- #| ?type_params: Array[String]
68
+ #| ?comments: Array[RBI::Comment]
70
69
  #| ) ?{ (RBI::Method node) -> void } -> void
71
70
  def create_method(name, parameters: [], return_type: nil, class_method: false, visibility: RBI::Public.new,
72
- comments: [], type_params: [], &block)
71
+ comments: [], &block)
73
72
  return unless Tapioca::RBIHelper.valid_method_name?(name)
74
73
 
75
74
  sigs = []
@@ -78,7 +77,11 @@ module RBI
78
77
  # If there is no block, and the params and return type have not been supplied, then
79
78
  # we create a single signature with the given parameters and return type
80
79
  params = parameters.map { |param| RBI::SigParam.new(param.param.name.to_s, param.type) }
81
- sigs << RBI::Sig.new(params: params, return_type: return_type || "T.untyped", type_params: type_params)
80
+ return_type ||= "T.untyped"
81
+ type_params = Tapioca::RBIHelper.extract_type_parameters(parameters.map(&:type).append(return_type))
82
+
83
+ sig = RBI::Sig.new(params: params, return_type: return_type, type_params: type_params)
84
+ sigs << sig
82
85
  end
83
86
 
84
87
  method = RBI::Method.new(
@@ -1,39 +1,93 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "require-hooks/setup"
5
-
6
4
  # This code rewrites RBS comments back into Sorbet's signatures as the files are being loaded.
7
5
  # This will allow `sorbet-runtime` to wrap the methods as if they were originally written with the `sig{}` blocks.
8
6
  # This will in turn allow Tapioca to use this signatures to generate typed RBI files.
9
7
 
10
- begin
11
- # When in a `bootsnap` environment, files are loaded from the cache and won't trigger the `source_transform` method.
12
- # The `require-hooks` gem comes with a `bootsnap` mode that will disable the `bootsnap/compile_cache/iseq` caching.
13
- # Sadly, we're way to early in the boot process to use it as bootsnap won't be loaded yet and the `require-hooks`
14
- # setup won't pick it up.
15
- #
16
- # As a workaround, if we can preemptively require `bootsnap` and `bootsnap/compile_cache/iseq` we manually override
17
- # the `load_iseq` method to disable the caching mechanism.
18
- #
19
- # This will make the Rails app load slower but allows us to trigger the RBS -> RBI source transform.
20
- require "bootsnap"
21
- require "bootsnap/compile_cache/iseq"
22
-
23
- module Bootsnap
24
- module CompileCache
25
- module ISeq
26
- module InstructionSequenceMixin
27
- #: (String) -> RubyVM::InstructionSequence
28
- def load_iseq(path)
29
- super if defined?(super)
8
+ module Tapioca
9
+ module RBS
10
+ class HostBootsnapSetupError < StandardError; end
11
+
12
+ # Raises when the host calls `Bootsnap.setup` after tapioca's setup. Host's call
13
+ # would overwrite tapioca's cache directory, so rewritten iseqs would end up in
14
+ # the host's regular cache.
15
+ module BootsnapGuard
16
+ extend T::Sig
17
+
18
+ sig { params(_kwargs: T.untyped).void }
19
+ def setup(**_kwargs)
20
+ Kernel.raise HostBootsnapSetupError, <<~MSG
21
+ Bootsnap.setup was called while TAPIOCA_RBS_CACHE=1 is set. Tapioca already
22
+ configured bootsnap with a dedicated cache directory; re-running setup
23
+ would overwrite that config and start writing rewritten iseqs into your
24
+ host's cache.
25
+
26
+ Gate your host's Bootsnap.setup on the env var, e.g. in config/boot.rb:
27
+
28
+ require "bootsnap/setup" unless ENV["TAPIOCA_RBS_CACHE"] == "1"
29
+ MSG
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # When TAPIOCA_RBS_CACHE=1, set up bootsnap with a dedicated cache directory
36
+ # and load require-hooks so the RBS-rewritten iseqs get cached. Subsequent
37
+ # runs read the rewritten iseq directly and skip the rewrite.
38
+ #
39
+ # After our setup, BootsnapGuard is prepended so the host application can't
40
+ # replace our cache directory.
41
+ if ENV["TAPIOCA_RBS_CACHE"] == "1"
42
+ begin
43
+ require "bootsnap"
44
+ # Respect BOOTSNAP_READONLY for consumers reading a pre-populated cache
45
+ # (e.g. a CI prime step).
46
+ readonly = !["0", "false", false].include?(ENV.fetch("BOOTSNAP_READONLY") { false })
47
+ Bootsnap.setup(
48
+ cache_dir: ENV.fetch("TAPIOCA_BOOTSNAP_CACHE_DIR", File.join(Dir.pwd, "tmp/cache/bootsnap-tapioca-rbs")),
49
+ development_mode: true,
50
+ load_path_cache: true,
51
+ compile_cache_iseq: true,
52
+ compile_cache_yaml: true,
53
+ readonly: readonly,
54
+ revalidation: true,
55
+ )
56
+ Bootsnap.log_stats!
57
+ Bootsnap.singleton_class.prepend(Tapioca::RBS::BootsnapGuard)
58
+ rescue LoadError
59
+ # Bootsnap is not in the bundle, skip iseq caching.
60
+ end
61
+
62
+ require "require-hooks/setup"
63
+ else
64
+ require "require-hooks/setup"
65
+
66
+ begin
67
+ # Disable Bootsnap's iseq cache unless TAPIOCA_RBS_CACHE=1 enabled the separate cache above.
68
+ #
69
+ # This is necessary because host apps can call Bootsnap.setup after tapioca loads this file. When that happens,
70
+ # Bootsnap installs `load_iseq` and serves files from its cache, which bypasses RequireHooks.source_transform.
71
+ # Preloading bootsnap's iseq support lets us override `load_iseq` before setup installs it, preserving the default
72
+ # RBS rewrite behavior at the cost of slower app boot.
73
+ require "bootsnap"
74
+ require "bootsnap/compile_cache/iseq"
75
+
76
+ module Bootsnap
77
+ module CompileCache
78
+ module ISeq
79
+ module InstructionSequenceMixin
80
+ #: (String) -> RubyVM::InstructionSequence
81
+ def load_iseq(path)
82
+ super if defined?(super) # Disable Bootsnap's hook, but trigger any others.
83
+ end
30
84
  end
31
85
  end
32
86
  end
33
87
  end
88
+ rescue LoadError
89
+ # Bootsnap is not in the bundle, we don't need to do anything.
34
90
  end
35
- rescue LoadError
36
- # Bootsnap is not in the bundle, we don't need to do anything.
37
91
  end
38
92
 
39
93
  # We need to include `T::Sig` very early to make sure that the `sig` method is available since gems using RBS comments
@@ -47,7 +101,8 @@ RequireHooks.source_transform(patterns: ["**/*.rb"]) do |path, source|
47
101
 
48
102
  # For performance reasons, we only rewrite files that use Sorbet.
49
103
  if source =~ /^\s*#\s*typed: (ignore|false|true|strict|strong|__STDLIB_INTERNAL)/
50
- Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(source, file: path)
104
+ # Sorbet runtime only supports one signature per method, so keep the last overload.
105
+ Spoom::Sorbet::Translate.rbs_comments_to_sorbet_sigs(source, file: path, overloads_strategy: :translate_last)
51
106
  end
52
107
  rescue Spoom::Sorbet::Translate::Error
53
108
  # If we can't translate the RBS comments back into Sorbet's signatures, we just skip the file.
@@ -6,7 +6,7 @@ module Tapioca
6
6
  class DynamicMixinCompiler
7
7
  include Runtime::Reflection
8
8
 
9
- #: Array[T::Module[top]]
9
+ #: Array[Module[top]]
10
10
  attr_reader :dynamic_extends, :dynamic_includes
11
11
 
12
12
  #: Array[Symbol]
@@ -15,7 +15,7 @@ module Tapioca
15
15
  #: Array[Symbol]
16
16
  attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
17
17
 
18
- #: (T::Module[top] constant) -> void
18
+ #: (Module[top] constant) -> void
19
19
  def initialize(constant)
20
20
  @constant = constant
21
21
  mixins_from_modules = {}.compare_by_identity
@@ -109,12 +109,12 @@ module Tapioca
109
109
  # is the list of all dynamically extended modules because of that
110
110
  # constant. We grab that value by deleting the key for the original
111
111
  # constant.
112
- @dynamic_extends = mixins_from_modules.delete(constant) || [] #: Array[T::Module[top]]
112
+ @dynamic_extends = mixins_from_modules.delete(constant) || [] #: Array[Module[top]]
113
113
 
114
114
  # Since we deleted the original constant from the list of keys, all
115
115
  # the keys that remain are the ones that are dynamically included modules
116
116
  # during the include of the original constant.
117
- @dynamic_includes = mixins_from_modules.keys #: Array[T::Module[top]]
117
+ @dynamic_includes = mixins_from_modules.keys #: Array[Module[top]]
118
118
 
119
119
  @class_attribute_readers = class_attribute_readers #: Array[Symbol]
120
120
  @class_attribute_writers = class_attribute_writers #: Array[Symbol]
@@ -173,7 +173,7 @@ module Tapioca
173
173
  tree << RBI::Include.new("GeneratedInstanceMethods")
174
174
  end
175
175
 
176
- #: (RBI::Tree tree) -> [Array[T::Module[top]], Array[T::Module[top]]]
176
+ #: (RBI::Tree tree) -> [Array[Module[top]], Array[Module[top]]]
177
177
  def compile_mixes_in_class_methods(tree)
178
178
  includes = dynamic_includes.filter_map do |mod|
179
179
  qname = qualified_name_of(mod)
@@ -208,7 +208,7 @@ module Tapioca
208
208
  [[], []] # silence errors
209
209
  end
210
210
 
211
- #: (T::Module[top] mod, Array[T::Module[top]] dynamic_extends) -> bool
211
+ #: (Module[top] mod, Array[Module[top]] dynamic_extends) -> bool
212
212
  def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
213
213
  dynamic_extends.any? do |dynamic_extend|
214
214
  mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
@@ -6,7 +6,7 @@ module Tapioca
6
6
  # This class is responsible for storing and looking up information related to generic types.
7
7
  #
8
8
  # The class stores 2 different kinds of data, in two separate lookup tables:
9
- # 1. a lookup of generic type instances by name: `@generic_instances`
9
+ # 1. a lookup of generic type instances by constant and name: `@generic_instances`
10
10
  # 2. a lookup of type variable serializer by constant and type variable
11
11
  # instance: `@type_variables`
12
12
  #
@@ -21,16 +21,16 @@ module Tapioca
21
21
  # variable to type variable serializers. This allows us to associate type variables
22
22
  # to the constant names that represent them, easily.
23
23
  module GenericTypeRegistry
24
- @generic_instances = {} #: Hash[String, T::Module[top]]
24
+ @generic_instances = {}.compare_by_identity #: Hash[Module[top], Hash[String, Module[top]]]
25
25
 
26
- @type_variables = {}.compare_by_identity #: Hash[T::Module[top], Array[TypeVariableModule]]
26
+ @type_variables = {}.compare_by_identity #: Hash[Module[top], Array[TypeVariableModule]]
27
27
 
28
28
  class GenericType < T::Types::Simple
29
- #: (T::Module[top] raw_type, T::Module[top] underlying_type) -> void
29
+ #: (Module[top] raw_type, Module[top] underlying_type) -> void
30
30
  def initialize(raw_type, underlying_type)
31
31
  super(raw_type)
32
32
 
33
- @underlying_type = underlying_type #: T::Module[top]
33
+ @underlying_type = underlying_type #: Module[top]
34
34
  end
35
35
 
36
36
  # @override
@@ -45,14 +45,15 @@ module Tapioca
45
45
  # and cloning the given constant so that we can return a type that is the same
46
46
  # as the current type but is a different instance and has a different name method.
47
47
  #
48
- # We cache those cloned instances by their name in `@generic_instances`, so that
49
- # we don't keep instantiating a new type every single time it is referenced.
48
+ # We cache those cloned instances by their original constant and their name in
49
+ # `@generic_instances`, so that we don't keep instantiating a new type every single
50
+ # time it is referenced.
50
51
  # For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
51
52
  # result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
52
53
  # 2 hash lookups (for the other two `Foo[Integer]`s).
53
54
  #
54
55
  # This method returns the created or cached clone of the constant.
55
- #: (untyped constant, untyped types) -> T::Module[top]
56
+ #: (untyped constant, untyped types) -> Module[top]
56
57
  def register_type(constant, types)
57
58
  # Build the name of the instantiated generic type,
58
59
  # something like `"Foo[X, Y, Z]"`
@@ -64,15 +65,18 @@ module Tapioca
64
65
  #
65
66
  # Also, we try to memoize the generic type based on the name, so that
66
67
  # we don't have to keep recreating them all the time.
67
- @generic_instances[name] ||= create_generic_type(constant, name)
68
+ generic_instances = @generic_instances[constant] ||= {}
69
+ generic_instances[name] ||= create_generic_type(constant, name)
68
70
  end
69
71
 
70
72
  #: (Object instance) -> bool
71
73
  def generic_type_instance?(instance)
72
- @generic_instances.values.any? { |generic_type| generic_type === instance }
74
+ @generic_instances.values.any? do |generic_instances|
75
+ generic_instances.values.any? { |generic_type| generic_type === instance }
76
+ end
73
77
  end
74
78
 
75
- #: (T::Module[top] constant) -> Array[TypeVariableModule]?
79
+ #: (Module[top] constant) -> Array[TypeVariableModule]?
76
80
  def lookup_type_variables(constant)
77
81
  @type_variables[constant]
78
82
  end
@@ -88,14 +92,14 @@ module Tapioca
88
92
  # can return it from the original methods as well.
89
93
  #: (untyped constant, TypeVariableModule type_variable) -> void
90
94
  def register_type_variable(constant, type_variable)
91
- type_variables = lookup_or_initialize_type_variables(constant)
95
+ type_variables = @type_variables[constant] ||= []
92
96
 
93
97
  type_variables << type_variable
94
98
  end
95
99
 
96
100
  private
97
101
 
98
- #: (T::Module[top] constant, String name) -> T::Module[top]
102
+ #: (Module[top] constant, String name) -> Module[top]
99
103
  def create_generic_type(constant, name)
100
104
  generic_type = case constant
101
105
  when Class
@@ -163,11 +167,6 @@ module Tapioca
163
167
  owner.send(:define_method, :inherited, inherited_method)
164
168
  end
165
169
  end
166
-
167
- #: (T::Module[top] constant) -> Array[TypeVariableModule]
168
- def lookup_or_initialize_type_variables(constant)
169
- @type_variables[constant] ||= []
170
- end
171
170
  end
172
171
  end
173
172
  end